Skip to content

Commit 57816b6

Browse files
zzzstr4d
authored andcommitted
Implement key encoding and decoding
Follows the docs for java.security.spec.PKCS8EncodedKeySpec and the draft at https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04 Ignores the example in the josefsson draft, required for keytool to work. Closes #13 I2P ticket: https://trac.i2p2.de/ticket/1723 Signed-off-by: str4d <[email protected]>
1 parent 6c77b40 commit 57816b6

File tree

3 files changed

+221
-6
lines changed

3 files changed

+221
-6
lines changed

src/net/i2p/crypto/eddsa/EdDSAPrivateKey.java

Lines changed: 116 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package net.i2p.crypto.eddsa;
22

33
import java.security.PrivateKey;
4+
import java.security.spec.InvalidKeySpecException;
5+
import java.security.spec.PKCS8EncodedKeySpec;
46

57
import net.i2p.crypto.eddsa.math.GroupElement;
8+
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
69
import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
710
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
811

@@ -29,6 +32,11 @@ public EdDSAPrivateKey(EdDSAPrivateKeySpec spec) {
2932
this.edDsaSpec = spec.getParams();
3033
}
3134

35+
public EdDSAPrivateKey(PKCS8EncodedKeySpec spec) throws InvalidKeySpecException {
36+
this(new EdDSAPrivateKeySpec(decode(spec.getEncoded()),
37+
EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512)));
38+
}
39+
3240
public String getAlgorithm() {
3341
return "EdDSA";
3442
}
@@ -37,9 +45,115 @@ public String getFormat() {
3745
return "PKCS#8";
3846
}
3947

48+
/**
49+
* This follows the spec at
50+
* https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04
51+
* AND the docs from
52+
* java.security.spec.PKCS8EncodedKeySpec
53+
* quote:
54+
*<pre>
55+
* The PrivateKeyInfo syntax is defined in the PKCS#8 standard as follows:
56+
* PrivateKeyInfo ::= SEQUENCE {
57+
* version Version,
58+
* privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
59+
* privateKey PrivateKey,
60+
* attributes [0] IMPLICIT Attributes OPTIONAL }
61+
* Version ::= INTEGER
62+
* PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
63+
* PrivateKey ::= OCTET STRING
64+
* Attributes ::= SET OF Attribute
65+
*</pre>
66+
*
67+
*<pre>
68+
* AlgorithmIdentifier ::= SEQUENCE
69+
* {
70+
* algorithm OBJECT IDENTIFIER,
71+
* parameters ANY OPTIONAL
72+
* }
73+
*</pre>
74+
*
75+
* Note that the private key encoding is not fully specified in the Josefsson draft version 04,
76+
* and the example could be wrong, as it's lacking Version and AlgorithmIdentifier.
77+
* This will hopefully be clarified in the next draft.
78+
* But sun.security.pkcs.PKCS8Key expects them so we must include them for keytool to work.
79+
*
80+
* @return 49 bytes for Ed25519, null for other curves
81+
*/
4082
public byte[] getEncoded() {
41-
// TODO Auto-generated method stub
42-
return null;
83+
// TODO no equals() implemented in spec, but it's essentially a singleton
84+
if (!edDsaSpec.equals(EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512)))
85+
return null;
86+
int totlen = 17 + seed.length;
87+
byte[] rv = new byte[totlen];
88+
int idx = 0;
89+
// sequence
90+
rv[idx++] = 0x30;
91+
rv[idx++] = (byte) (15 + seed.length);
92+
93+
// version
94+
// not in the Josefsson example
95+
rv[idx++] = 0x02;
96+
rv[idx++] = 1;
97+
rv[idx++] = 0;
98+
99+
// Algorithm Identifier
100+
// sequence
101+
// not in the Josefsson example
102+
rv[idx++] = 0x30;
103+
rv[idx++] = 8;
104+
// OID 1.3.101.100
105+
// https://msdn.microsoft.com/en-us/library/windows/desktop/bb540809%28v=vs.85%29.aspx
106+
// not in the Josefsson example
107+
rv[idx++] = 0x06;
108+
rv[idx++] = 3;
109+
rv[idx++] = (1 * 40) + 3;
110+
rv[idx++] = 101;
111+
rv[idx++] = 100;
112+
// params
113+
rv[idx++] = 0x0a;
114+
rv[idx++] = 1;
115+
rv[idx++] = 1; // Ed25519
116+
// the key
117+
rv[idx++] = 0x04; // octet string
118+
rv[idx++] = (byte) seed.length;
119+
System.arraycopy(seed, 0, rv, idx, seed.length);
120+
return rv;
121+
}
122+
123+
/**
124+
* This is really dumb for now.
125+
* See getEncoded().
126+
*
127+
* @return 32 bytes for Ed25519, throws for other curves
128+
*/
129+
private static byte[] decode(byte[] d) throws InvalidKeySpecException {
130+
try {
131+
int idx = 0;
132+
if (d[idx++] != 0x30 ||
133+
d[idx++] != 47 ||
134+
d[idx++] != 0x02 ||
135+
d[idx++] != 1 ||
136+
d[idx++] != 0 ||
137+
d[idx++] != 0x30 ||
138+
d[idx++] != 8 ||
139+
d[idx++] != 0x06 ||
140+
d[idx++] != 3 ||
141+
d[idx++] != (1 * 40) + 3 ||
142+
d[idx++] != 101 ||
143+
d[idx++] != 100 ||
144+
d[idx++] != 0x0a ||
145+
d[idx++] != 1 ||
146+
d[idx++] != 1 ||
147+
d[idx++] != 0x04 ||
148+
d[idx++] != 32) {
149+
throw new InvalidKeySpecException("unsupported key spec");
150+
}
151+
byte[] rv = new byte[32];
152+
System.arraycopy(d, idx, rv, 0, 32);
153+
return rv;
154+
} catch (IndexOutOfBoundsException ioobe) {
155+
throw new InvalidKeySpecException(ioobe);
156+
}
43157
}
44158

45159
public EdDSAParameterSpec getParams() {

src/net/i2p/crypto/eddsa/EdDSAPublicKey.java

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package net.i2p.crypto.eddsa;
22

33
import java.security.PublicKey;
4+
import java.security.spec.InvalidKeySpecException;
5+
import java.security.spec.X509EncodedKeySpec;
46

57
import net.i2p.crypto.eddsa.math.GroupElement;
8+
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
69
import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
710
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
811

@@ -25,6 +28,11 @@ public EdDSAPublicKey(EdDSAPublicKeySpec spec) {
2528
this.edDsaSpec = spec.getParams();
2629
}
2730

31+
public EdDSAPublicKey(X509EncodedKeySpec spec) throws InvalidKeySpecException {
32+
this(new EdDSAPublicKeySpec(decode(spec.getEncoded()),
33+
EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512)));
34+
}
35+
2836
public String getAlgorithm() {
2937
return "EdDSA";
3038
}
@@ -33,9 +41,94 @@ public String getFormat() {
3341
return "X.509";
3442
}
3543

44+
/**
45+
* This follows the spec at
46+
* ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04
47+
* which matches the docs from
48+
* java.security.spec.X509EncodedKeySpec
49+
* quote:
50+
*<pre>
51+
* The SubjectPublicKeyInfo syntax is defined in the X.509 standard as follows:
52+
* SubjectPublicKeyInfo ::= SEQUENCE {
53+
* algorithm AlgorithmIdentifier,
54+
* subjectPublicKey BIT STRING }
55+
*</pre>
56+
*
57+
*<pre>
58+
* AlgorithmIdentifier ::= SEQUENCE
59+
* {
60+
* algorithm OBJECT IDENTIFIER,
61+
* parameters ANY OPTIONAL
62+
* }
63+
*</pre>
64+
*
65+
* @return 47 bytes for Ed25519, null for other curves
66+
*/
3667
public byte[] getEncoded() {
37-
// TODO Auto-generated method stub
38-
return null;
68+
// TODO no equals() implemented in spec, but it's essentially a singleton
69+
if (!edDsaSpec.equals(EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512)))
70+
return null;
71+
int totlen = 15 + Abyte.length;
72+
byte[] rv = new byte[totlen];
73+
int idx = 0;
74+
// sequence
75+
rv[idx++] = 0x30;
76+
rv[idx++] = (byte) (13 + Abyte.length);
77+
// Algorithm Identifier
78+
// sequence
79+
rv[idx++] = 0x30;
80+
rv[idx++] = 8;
81+
// OID 1.3.101.100
82+
// https://msdn.microsoft.com/en-us/library/windows/desktop/bb540809%28v=vs.85%29.aspx
83+
rv[idx++] = 0x06;
84+
rv[idx++] = 3;
85+
rv[idx++] = (1 * 40) + 3;
86+
rv[idx++] = 101;
87+
rv[idx++] = 100;
88+
// params
89+
rv[idx++] = 0x0a;
90+
rv[idx++] = 1;
91+
rv[idx++] = 1; // Ed25519
92+
// the key
93+
rv[idx++] = 0x03; // bit string
94+
rv[idx++] = (byte) (1 + Abyte.length);
95+
rv[idx++] = 0; // number of trailing unused bits
96+
System.arraycopy(Abyte, 0, rv, idx, Abyte.length);
97+
return rv;
98+
}
99+
100+
/**
101+
* This is really dumb for now.
102+
* See getEncoded().
103+
*
104+
* @return 32 bytes for Ed25519, throws for other curves
105+
*/
106+
private static byte[] decode(byte[] d) throws InvalidKeySpecException {
107+
try {
108+
int idx = 0;
109+
if (d[idx++] != 0x30 ||
110+
d[idx++] != 45 ||
111+
d[idx++] != 0x30 ||
112+
d[idx++] != 8 ||
113+
d[idx++] != 0x06 ||
114+
d[idx++] != 3 ||
115+
d[idx++] != (1 * 40) + 3 ||
116+
d[idx++] != 101 ||
117+
d[idx++] != 100 ||
118+
d[idx++] != 0x0a ||
119+
d[idx++] != 1 ||
120+
d[idx++] != 1 ||
121+
d[idx++] != 0x03 ||
122+
d[idx++] != 33 ||
123+
d[idx++] != 0) {
124+
throw new InvalidKeySpecException("unsupported key spec");
125+
}
126+
byte[] rv = new byte[32];
127+
System.arraycopy(d, idx, rv, 0, 32);
128+
return rv;
129+
} catch (IndexOutOfBoundsException ioobe) {
130+
throw new InvalidKeySpecException(ioobe);
131+
}
39132
}
40133

41134
public EdDSAParameterSpec getParams() {

src/net/i2p/crypto/eddsa/KeyFactory.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import java.security.PublicKey;
88
import java.security.spec.InvalidKeySpecException;
99
import java.security.spec.KeySpec;
10+
import java.security.spec.PKCS8EncodedKeySpec;
11+
import java.security.spec.X509EncodedKeySpec;
1012

1113
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
1214
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
@@ -22,15 +24,21 @@ protected PrivateKey engineGeneratePrivate(KeySpec keySpec)
2224
if (keySpec instanceof EdDSAPrivateKeySpec) {
2325
return new EdDSAPrivateKey((EdDSAPrivateKeySpec) keySpec);
2426
}
25-
throw new InvalidKeySpecException("key spec not recognised");
27+
if (keySpec instanceof PKCS8EncodedKeySpec) {
28+
return new EdDSAPrivateKey((PKCS8EncodedKeySpec) keySpec);
29+
}
30+
throw new InvalidKeySpecException("key spec not recognised: " + keySpec.getClass());
2631
}
2732

2833
protected PublicKey engineGeneratePublic(KeySpec keySpec)
2934
throws InvalidKeySpecException {
3035
if (keySpec instanceof EdDSAPublicKeySpec) {
3136
return new EdDSAPublicKey((EdDSAPublicKeySpec) keySpec);
3237
}
33-
throw new InvalidKeySpecException("key spec not recognised");
38+
if (keySpec instanceof X509EncodedKeySpec) {
39+
return new EdDSAPublicKey((X509EncodedKeySpec) keySpec);
40+
}
41+
throw new InvalidKeySpecException("key spec not recognised: " + keySpec.getClass());
3442
}
3543

3644
@SuppressWarnings("unchecked")

0 commit comments

Comments
 (0)