1818 */
1919package org .apache .sshd .common .util .security .eddsa .generic ;
2020
21+ import java .io .IOException ;
2122import java .security .InvalidKeyException ;
23+ import java .security .PrivateKey ;
2224import java .security .PublicKey ;
25+ import java .security .spec .KeySpec ;
26+ import java .security .spec .PKCS8EncodedKeySpec ;
27+ import java .security .spec .X509EncodedKeySpec ;
2328import java .util .Arrays ;
2429
30+ import org .apache .sshd .common .util .io .der .DERParser ;
31+
2532/**
2633 * Utilities to extract the raw key bytes from ed25519 or ed448 public keys, in a manner that is independent of the
2734 * actual concrete key implementation classes.
@@ -44,10 +51,43 @@ public final class EdDSAUtils {
4451 private static final byte [] ED448_X509_PREFIX = {
4552 0x30 , 0x43 , 0x30 , 0x05 , 0x06 , 0x03 , 0x2b , 0x65 , 0x71 , 0x03 , 0x3a , 0x00 };
4653
54+ // For reconstructing private keys from raw bytes we construct a minimal PKCS#8 encoding, using RFC 5208 (version 0)
55+ // without the public key and without attributes. This is allowed by RFC 5958 (Asymmetric key packages).
56+
57+ // Sequence, length 46, (3 bytes: Version 0), Sequence, length 5, OID, length 3, O, I, D, Octet String, length 34,
58+ // Octet String, length 32
59+ private static final byte [] ED25519_PKCS8_PREFIX = {
60+ 0x30 , 0x2e , 0x02 , 0x01 , 0x00 , 0x30 , 0x05 , 0x06 , 0x03 , 0x2b , 0x65 , 0x70 ,
61+ 0x04 , 0x22 , 0x04 , 0x20 };
62+ // Sequence, length 71, (3 bytes: Version 0), Sequence, length 5, OID, length 3, O, I, D, Octet String, length 59,
63+ // Octet String, length 57
64+ private static final byte [] ED448_PKCS8_PREFIX = {
65+ 0x30 , 0x47 , 0x02 , 0x01 , 0x00 , 0x30 , 0x05 , 0x06 , 0x03 , 0x2b , 0x65 , 0x71 ,
66+ 0x04 , 0x3b , 0x04 , 0x39 };
67+
68+ // The first two numbers of the dotted notation are combined into one byte: (1 * 40 + 3) = 43 = 0x2b
69+ private static final byte [] ED25519_OID = { 0x2b , 0x65 , 0x70 }; // 1.3.101.112
70+ private static final byte [] ED448_OID = { 0x2b , 0x65 , 0x71 }; // 1.3.101.113
71+
4772 private EdDSAUtils () {
4873 throw new IllegalStateException ("No instantiation" );
4974 }
5075
76+ private static boolean arrayEq (byte [] a , byte [] b ) {
77+ if (a == null && b == null ) {
78+ return true ;
79+ }
80+ if (a == null || b == null ) {
81+ return false ;
82+ }
83+ int unequal = a .length ^ b .length ;
84+ int length = Math .min (a .length , b .length );
85+ for (int i = 0 ; i < length ; i ++) {
86+ unequal |= a [i ] ^ b [i ];
87+ }
88+ return unequal == 0 ;
89+ }
90+
5191 private static boolean startsWith (byte [] data , byte [] prefix ) {
5292 if (data == null || prefix == null || prefix .length == 0 || data .length < prefix .length ) {
5393 return false ;
@@ -86,4 +126,143 @@ public static byte[] getBytes(PublicKey key) throws InvalidKeyException {
86126 }
87127 return Arrays .copyOfRange (encoded , encoded .length - n , encoded .length );
88128 }
129+
130+ /**
131+ * Retrieves the raw key bytes from an ed25519 or ed448 {@link PrivateKey}.
132+ *
133+ * @param key {@link PrivateKey} to get the bytes of
134+ * @return the raw key bytes
135+ * @throws InvalidKeyException if the key is not an ed25519 or ed448 key, or if it doesn't use PKCS#8 encoding
136+ */
137+ public static byte [] getBytes (PrivateKey key ) throws InvalidKeyException {
138+ // Extract the private key bytes from the PKCS#8 encoding.
139+ if (!"PKCS#8" .equalsIgnoreCase (key .getFormat ())) {
140+ throw new InvalidKeyException ("Cannot extract private key bytes from a non-PKCS#8 encoding" );
141+ }
142+ byte [] encoded = key .getEncoded ();
143+ if (encoded == null ) {
144+ throw new InvalidKeyException ("Private key " + key .getClass ().getCanonicalName () + " does not support encoding" );
145+ }
146+ try {
147+ return asn1Parse (encoded );
148+ } finally {
149+ Arrays .fill (encoded , (byte ) 0 );
150+ }
151+ }
152+
153+ /**
154+ * Extracts the private key bytes from an encoded EdDSA private key by parsing the bytes as ASN.1 according to RFC
155+ * 5958 (PKCS #8 encoding):
156+ *
157+ * <pre>
158+ * OneAsymmetricKey ::= SEQUENCE {
159+ * version Version,
160+ * privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
161+ * privateKey PrivateKey,
162+ * ...
163+ * }
164+ *
165+ * Version ::= INTEGER
166+ * PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
167+ * PrivateKey ::= OCTET STRING
168+ *
169+ * AlgorithmIdentifier ::= SEQUENCE {
170+ * algorithm OBJECT IDENTIFIER,
171+ * parameters ANY DEFINED BY algorithm OPTIONAL
172+ * }
173+ * </pre>
174+ * <p>
175+ * and RFC 8410: "... when encoding a OneAsymmetricKey object, the private key is wrapped in a CurvePrivateKey
176+ * object and wrapped by the OCTET STRING of the 'privateKey' field."
177+ * </p>
178+ *
179+ * <pre>
180+ * CurvePrivateKey ::= OCTET STRING
181+ * </pre>
182+ *
183+ * @param encoded encoded private key to extract the private key bytes from
184+ * @return the extracted private key bytes
185+ * @throws InvalidKeyException if the private key cannot be extracted
186+ * @see <a href="https://tools.ietf.org/html/rfc5958">RFC 5958</a>
187+ * @see <a href="https://tools.ietf.org/html/rfc8410">RFC 8410</a>
188+ */
189+ private static byte [] asn1Parse (byte [] encoded ) throws InvalidKeyException {
190+ byte [] privateKey = null ;
191+ try (DERParser byteParser = new DERParser (encoded );
192+ DERParser oneAsymmetricKey = byteParser .readObject ().createParser ()) {
193+ oneAsymmetricKey .readObject (); // Skip version
194+ int n ;
195+ try (DERParser algorithmIdentifier = oneAsymmetricKey .readObject ().createParser ()) {
196+ byte [] oid = algorithmIdentifier .readObject ().getValue ();
197+ if (arrayEq (ED25519_OID , oid )) {
198+ n = ED25519_LENGTH ;
199+ } else if (arrayEq (ED448_OID , oid )) {
200+ n = ED448_LENGTH ;
201+ } else {
202+ throw new InvalidKeyException ("Private key is neither ed25519 nor ed448" );
203+ }
204+ }
205+ privateKey = oneAsymmetricKey .readObject ().getValue ();
206+ // The last n bytes of this must be the private key bytes.
207+ return Arrays .copyOfRange (privateKey , privateKey .length - n , privateKey .length );
208+ // Depending on the version there may be optional stuff following, but we don't care about that.
209+ } catch (IOException e ) {
210+ throw new InvalidKeyException ("Cannot parse EdDSA private key" , e );
211+ } finally {
212+ if (privateKey != null ) {
213+ Arrays .fill (privateKey , (byte ) 0 );
214+ }
215+ }
216+ }
217+
218+ /**
219+ * Creates a {@link KeySpec} for re-creating an ed25519 or ed448 public key from the raw key bytes.
220+ *
221+ * @param keyData the raw key bytes
222+ * @return the {@link KeySpec}
223+ * @throws InvalidKeyException if the key bytes do not have the appropriate length for an ed25519 or ed448 key
224+ */
225+ public static KeySpec createPublicKeySpec (byte [] keyData ) throws InvalidKeyException {
226+ // Create an X.509 encoding for ed25519 or ed448, depending on the length of keyData.
227+ if (keyData .length == ED25519_LENGTH ) {
228+ byte [] x509 = Arrays .copyOf (ED25519_X509_PREFIX , ED25519_X509_PREFIX .length + ED25519_LENGTH );
229+ System .arraycopy (keyData , 0 , x509 , ED25519_X509_PREFIX .length , ED25519_LENGTH );
230+ return new X509EncodedKeySpec (x509 );
231+ } else if (keyData .length == ED448_LENGTH ) {
232+ byte [] x509 = Arrays .copyOf (ED448_X509_PREFIX , ED448_X509_PREFIX .length + ED448_LENGTH );
233+ System .arraycopy (keyData , 0 , x509 , ED448_X509_PREFIX .length , ED448_LENGTH );
234+ return new X509EncodedKeySpec (x509 );
235+ }
236+ throw new InvalidKeyException ("Public key data is neither ed25519 nor ed448" );
237+ }
238+
239+ /**
240+ * Creates a {@link KeySpec} for re-creating an ed25519 or ed448 public key from the raw key bytes.
241+ *
242+ * @param keyData the raw key bytes
243+ * @return the {@link KeySpec}
244+ * @throws InvalidKeyException if the key bytes do not have the appropriate length for an ed25519 or ed448 key
245+ */
246+ public static KeySpec createPrivateKeySpec (byte [] keyData ) throws InvalidKeyException {
247+ // Create a PKCS#8 encoding for ed25519 or ed448, depending on the length of keyData.
248+ if (keyData .length == ED25519_LENGTH ) {
249+ byte [] pkcs8 = Arrays .copyOf (ED25519_PKCS8_PREFIX , ED25519_PKCS8_PREFIX .length + ED25519_LENGTH );
250+ try {
251+ System .arraycopy (keyData , 0 , pkcs8 , ED25519_PKCS8_PREFIX .length , ED25519_LENGTH );
252+ return new PKCS8EncodedKeySpec (pkcs8 );
253+ } finally {
254+ Arrays .fill (pkcs8 , (byte ) 0 );
255+ }
256+ } else if (keyData .length == ED448_LENGTH ) {
257+ byte [] pkcs8 = Arrays .copyOf (ED448_PKCS8_PREFIX , ED448_PKCS8_PREFIX .length + ED448_LENGTH );
258+ try {
259+ System .arraycopy (keyData , 0 , pkcs8 , ED448_PKCS8_PREFIX .length , ED448_LENGTH );
260+ return new PKCS8EncodedKeySpec (pkcs8 );
261+ } finally {
262+ Arrays .fill (pkcs8 , (byte ) 0 );
263+ }
264+ }
265+ throw new InvalidKeyException ("Private key data is neither ed25519 nor ed448" );
266+ }
267+
89268}
0 commit comments