2424/**
2525 * An EdDSA private key.
2626 *<p>
27- * Warning: Private key encoding is not fully specified in the
28- * current IETF draft. This implementation uses PKCS#8 encoding,
27+ * Warning: Private key encoding is based on the current curdle WG draft,
2928 * and is subject to change. See getEncoded().
3029 *</p><p>
31- * Ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04
30+ * For compatibility with older releases, decoding supports both the old and new
31+ * draft specifications. See decode().
32+ *</p><p>
33+ * Ref: https://tools.ietf.org/html/draft-ietf-curdle-pkix-04
34+ *</p><p>
35+ * Old Ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04
3236 *</p>
3337 * @author str4d
3438 *
@@ -42,6 +46,12 @@ public class EdDSAPrivateKey implements EdDSAKey, PrivateKey {
4246 private final byte [] Abyte ;
4347 private final EdDSAParameterSpec edDsaSpec ;
4448
49+ // OID 1.3.101.xxx
50+ private static final int OID_OLD = 100 ;
51+ private static final int OID_ED25519 = 112 ;
52+ private static final int OID_BYTE = 11 ;
53+ private static final int IDLEN_BYTE = 6 ;
54+
4555 public EdDSAPrivateKey (EdDSAPrivateKeySpec spec ) {
4656 this .seed = spec .getSeed ();
4757 this .h = spec .getH ();
@@ -67,108 +77,197 @@ public String getFormat() {
6777 }
6878
6979 /**
70- * This follows the docs from
71- * java.security.spec.PKCS8EncodedKeySpec
72- * quote:
80+ * Returns the public key in its canonical encoding.
81+ *<p>
82+ * This implements the following specs:
83+ *<ul><li>
84+ * General encoding: https://tools.ietf.org/html/draft-ietf-curdle-pkix-04
85+ *</li></li>
86+ * Key encoding: https://tools.ietf.org/html/rfc8032
87+ *</li></ul>
88+ *</p><p>
89+ * This encodes the seed. It will return null if constructed from
90+ * a spec which was directly constructed from H, in which case seed is null.
91+ *</p><p>
92+ * For keys in older formats, decoding and then re-encoding is sufficient to
93+ * migrate them to the canonical encoding.
94+ *</p>
95+ * Relevant spec quotes:
7396 *<pre>
74- * The PrivateKeyInfo syntax is defined in the PKCS#8 standard as follows:
75- * PrivateKeyInfo ::= SEQUENCE {
97+ * OneAsymmetricKey ::= SEQUENCE {
7698 * version Version,
7799 * privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
78100 * privateKey PrivateKey,
79- * attributes [0] IMPLICIT Attributes OPTIONAL }
101+ * attributes [0] Attributes OPTIONAL,
102+ * ...,
103+ * [[2: publicKey [1] PublicKey OPTIONAL ]],
104+ * ...
105+ * }
106+ *
80107 * Version ::= INTEGER
81108 * PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
82109 * PrivateKey ::= OCTET STRING
110+ * PublicKey ::= OCTET STRING
83111 * Attributes ::= SET OF Attribute
84112 *</pre>
85113 *
86114 *<pre>
87- * AlgorithmIdentifier ::= SEQUENCE
88- * {
89- * algorithm OBJECT IDENTIFIER,
90- * parameters ANY OPTIONAL
91- * }
115+ * ... when encoding a OneAsymmetricKey object, the private key is wrapped
116+ * in a CurvePrivateKey object and wrapped by the OCTET STRING of the
117+ * 'privateKey' field.
118+ *
119+ * CurvePrivateKey ::= OCTET STRING
92120 *</pre>
93121 *
94- * Ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04
122+ *<pre>
123+ * AlgorithmIdentifier ::= SEQUENCE {
124+ * algorithm OBJECT IDENTIFIER,
125+ * parameters ANY DEFINED BY algorithm OPTIONAL
126+ * }
95127 *
96- * Note that the private key encoding is not fully specified in the Josefsson draft version 04,
97- * and the example could be wrong, as it's lacking Version and AlgorithmIdentifier.
98- * This will hopefully be clarified in the next draft.
99- * But sun.security.pkcs.PKCS8Key expects them so we must include them for keytool to work.
128+ * For all of the OIDs, the parameters MUST be absent.
129+ *</pre>
100130 *
101- * This encodes the seed. It will return null if constructed from
102- * a spec which was directly constructed from H, in which case seed is null.
131+ *<pre>
132+ * id-Ed25519 OBJECT IDENTIFIER ::= { 1 3 101 112 }
133+ *</pre>
103134 *
104- * @return 49 bytes for Ed25519, null for other curves
135+ * @return 48 bytes for Ed25519, null for other curves
105136 */
106137 @ Override
107138 public byte [] getEncoded () {
108139 if (!edDsaSpec .equals (EdDSANamedCurveTable .getByName (EdDSANamedCurveTable .CURVE_ED25519_SHA512 )))
109140 return null ;
110- int totlen = 17 + seed .length ;
141+ if (seed == null )
142+ return null ;
143+ int totlen = 16 + seed .length ;
111144 byte [] rv = new byte [totlen ];
112145 int idx = 0 ;
113146 // sequence
114147 rv [idx ++] = 0x30 ;
115- rv [idx ++] = (byte ) (15 + seed .length );
116-
148+ rv [idx ++] = (byte ) (totlen - 2 );
117149 // version
118- // not in the Josefsson example
119150 rv [idx ++] = 0x02 ;
120151 rv [idx ++] = 1 ;
152+ // v1 - no public key included
121153 rv [idx ++] = 0 ;
122-
123154 // Algorithm Identifier
124155 // sequence
125- // not in the Josefsson example
126156 rv [idx ++] = 0x30 ;
127- rv [idx ++] = 8 ;
128- // OID 1.3.101.100
157+ rv [idx ++] = 5 ;
158+ // OID
129159 // https://msdn.microsoft.com/en-us/library/windows/desktop/bb540809%28v=vs.85%29.aspx
130- // not in the Josefsson example
131160 rv [idx ++] = 0x06 ;
132161 rv [idx ++] = 3 ;
133162 rv [idx ++] = (1 * 40 ) + 3 ;
134163 rv [idx ++] = 101 ;
135- rv [idx ++] = 100 ;
136- // params
137- rv [ idx ++] = 0x0a ;
138- rv [idx ++] = 1 ;
139- rv [idx ++] = 1 ; // Ed25519
140- // the key
164+ rv [idx ++] = ( byte ) OID_ED25519 ;
165+ // params - absent
166+ // PrivateKey
167+ rv [idx ++] = 0x04 ; // octet string
168+ rv [idx ++] = ( byte ) ( 2 + seed . length );
169+ // CurvePrivateKey
141170 rv [idx ++] = 0x04 ; // octet string
142171 rv [idx ++] = (byte ) seed .length ;
172+ // the key
143173 System .arraycopy (seed , 0 , rv , idx , seed .length );
144174 return rv ;
145175 }
146176
147177 /**
148- * This is really dumb for now.
149- * See getEncoded().
178+ * Extracts the private key bytes from the provided encoding.
179+ *<p>
180+ * This will decode data conforming to the current spec at
181+ * https://tools.ietf.org/html/draft-ietf-curdle-pkix-04
182+ * or as inferred from the old spec at
183+ * https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04.
184+ *</p><p>
185+ * Contrary to draft-ietf-curdle-pkix-04, it WILL accept a parameter value
186+ * of NULL, as it is required for interoperability with the default Java
187+ * keystore. Other implementations MUST NOT copy this behaviour from here
188+ * unless they also need to read keys from the default Java keystore.
189+ *</p><p>
190+ * This is really dumb for now. It does not use a general-purpose ASN.1 decoder.
191+ * See also getEncoded().
150192 *
151- * @return 32 bytes for Ed25519, throws for other curves
193+ * @return 32 bytes for Ed25519, throws for other curves
152194 */
153195 private static byte [] decode (byte [] d ) throws InvalidKeySpecException {
154196 try {
197+ //
198+ // Setup and OID check
199+ //
200+ int totlen = 48 ;
201+ int idlen = 5 ;
202+ int doid = d [OID_BYTE ];
203+ if (doid == OID_OLD ) {
204+ totlen = 49 ;
205+ idlen = 8 ;
206+ } else if (doid == OID_ED25519 ) {
207+ // Detect parameter value of NULL
208+ if (d [IDLEN_BYTE ] == 7 ) {
209+ totlen = 50 ;
210+ idlen = 7 ;
211+ }
212+ } else {
213+ throw new InvalidKeySpecException ("unsupported key spec" );
214+ }
215+
216+ //
217+ // Pre-decoding check
218+ //
219+ if (d .length != totlen ) {
220+ throw new InvalidKeySpecException ("invalid key spec length" );
221+ }
222+
223+ //
224+ // Decoding
225+ //
155226 int idx = 0 ;
156227 if (d [idx ++] != 0x30 ||
157- d [idx ++] != 47 ||
228+ d [idx ++] != ( totlen - 2 ) ||
158229 d [idx ++] != 0x02 ||
159230 d [idx ++] != 1 ||
160231 d [idx ++] != 0 ||
161232 d [idx ++] != 0x30 ||
162- d [idx ++] != 8 ||
233+ d [idx ++] != idlen ||
163234 d [idx ++] != 0x06 ||
164235 d [idx ++] != 3 ||
165236 d [idx ++] != (1 * 40 ) + 3 ||
166- d [idx ++] != 101 ||
167- d [idx ++] != 100 ||
168- d [idx ++] != 0x0a ||
169- d [idx ++] != 1 ||
170- d [idx ++] != 1 ||
171- d [idx ++] != 0x04 ||
237+ d [idx ++] != 101 ) {
238+ throw new InvalidKeySpecException ("unsupported key spec" );
239+ }
240+ idx ++; // OID, checked above
241+ // parameters only with old OID
242+ if (doid == OID_OLD ) {
243+ if (d [idx ++] != 0x0a ||
244+ d [idx ++] != 1 ||
245+ d [idx ++] != 1 ) {
246+ throw new InvalidKeySpecException ("unsupported key spec" );
247+ }
248+ } else {
249+ // Handle parameter value of NULL
250+ //
251+ // Quote https://tools.ietf.org/html/draft-ietf-curdle-pkix-04 :
252+ // For all of the OIDs, the parameters MUST be absent.
253+ // Regardless of the defect in the original 1997 syntax,
254+ // implementations MUST NOT accept a parameters value of NULL.
255+ //
256+ // But Java's default keystore puts it in (when decoding as
257+ // PKCS8 and then re-encoding to pass on), so we must accept it.
258+ if (idlen == 7 ) {
259+ if (d [idx ++] != 0x05 ||
260+ d [idx ++] != 0 ) {
261+ throw new InvalidKeySpecException ("unsupported key spec" );
262+ }
263+ }
264+ // PrivateKey wrapping the CurvePrivateKey
265+ if (d [idx ++] != 0x04 ||
266+ d [idx ++] != 34 ) {
267+ throw new InvalidKeySpecException ("unsupported key spec" );
268+ }
269+ }
270+ if (d [idx ++] != 0x04 ||
172271 d [idx ++] != 32 ) {
173272 throw new InvalidKeySpecException ("unsupported key spec" );
174273 }
0 commit comments