package net.i2p.crypto.eddsa; import java.security.PrivateKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.util.Arrays; import net.i2p.crypto.eddsa.math.GroupElement; import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec; import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; /** * An EdDSA private key. *<p> * Warning: Private key encoding is not fully specified in the * current IETF draft. This implementation uses PKCS#8 encoding, * and is subject to change. See getEncoded(). *</p><p> * Ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04 *</p> * * @since 0.9.15 * @author str4d * */ public class EdDSAPrivateKey implements EdDSAKey, PrivateKey { private static final long serialVersionUID = 23495873459878957L; private final byte[] seed; private final byte[] h; private final byte[] a; private final GroupElement A; private final byte[] Abyte; private final EdDSAParameterSpec edDsaSpec; public EdDSAPrivateKey(EdDSAPrivateKeySpec spec) { this.seed = spec.getSeed(); this.h = spec.getH(); this.a = spec.geta(); this.A = spec.getA(); this.Abyte = this.A.toByteArray(); this.edDsaSpec = spec.getParams(); } /** * @since 0.9.25 */ public EdDSAPrivateKey(PKCS8EncodedKeySpec spec) throws InvalidKeySpecException { this(new EdDSAPrivateKeySpec(decode(spec.getEncoded()), EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512))); } public String getAlgorithm() { return "EdDSA"; } public String getFormat() { return "PKCS#8"; } /** * This follows the docs from * java.security.spec.PKCS8EncodedKeySpec * quote: *<pre> * The PrivateKeyInfo syntax is defined in the PKCS#8 standard as follows: * PrivateKeyInfo ::= SEQUENCE { * version Version, * privateKeyAlgorithm PrivateKeyAlgorithmIdentifier, * privateKey PrivateKey, * attributes [0] IMPLICIT Attributes OPTIONAL } * Version ::= INTEGER * PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier * PrivateKey ::= OCTET STRING * Attributes ::= SET OF Attribute *</pre> * *<pre> * AlgorithmIdentifier ::= SEQUENCE * { * algorithm OBJECT IDENTIFIER, * parameters ANY OPTIONAL * } *</pre> * * Ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04 * * Note that the private key encoding is not fully specified in the Josefsson draft version 04, * and the example could be wrong, as it's lacking Version and AlgorithmIdentifier. * This will hopefully be clarified in the next draft. * But sun.security.pkcs.PKCS8Key expects them so we must include them for keytool to work. * * This encodes the seed. It will return null if constructed from * a spec which was directly constructed from H, in which case seed is null. * * @return 49 bytes for Ed25519, null for other curves * @since implemented in 0.9.25 */ public byte[] getEncoded() { if (!edDsaSpec.equals(EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512))) return null; int totlen = 17 + seed.length; byte[] rv = new byte[totlen]; int idx = 0; // sequence rv[idx++] = 0x30; rv[idx++] = (byte) (15 + seed.length); // version // not in the Josefsson example rv[idx++] = 0x02; rv[idx++] = 1; rv[idx++] = 0; // Algorithm Identifier // sequence // not in the Josefsson example rv[idx++] = 0x30; rv[idx++] = 8; // OID 1.3.101.100 // https://msdn.microsoft.com/en-us/library/windows/desktop/bb540809%28v=vs.85%29.aspx // not in the Josefsson example rv[idx++] = 0x06; rv[idx++] = 3; rv[idx++] = (1 * 40) + 3; rv[idx++] = 101; rv[idx++] = 100; // params rv[idx++] = 0x0a; rv[idx++] = 1; rv[idx++] = 1; // Ed25519 // the key rv[idx++] = 0x04; // octet string rv[idx++] = (byte) seed.length; System.arraycopy(seed, 0, rv, idx, seed.length); return rv; } /** * This is really dumb for now. * See getEncoded(). * * @return 32 bytes for Ed25519, throws for other curves * @since 0.9.25 */ private static byte[] decode(byte[] d) throws InvalidKeySpecException { try { int idx = 0; if (d[idx++] != 0x30 || d[idx++] != 47 || d[idx++] != 0x02 || d[idx++] != 1 || d[idx++] != 0 || d[idx++] != 0x30 || d[idx++] != 8 || d[idx++] != 0x06 || d[idx++] != 3 || d[idx++] != (1 * 40) + 3 || d[idx++] != 101 || d[idx++] != 100 || d[idx++] != 0x0a || d[idx++] != 1 || d[idx++] != 1 || d[idx++] != 0x04 || d[idx++] != 32) { throw new InvalidKeySpecException("unsupported key spec"); } byte[] rv = new byte[32]; System.arraycopy(d, idx, rv, 0, 32); return rv; } catch (IndexOutOfBoundsException ioobe) { throw new InvalidKeySpecException(ioobe); } } public EdDSAParameterSpec getParams() { return edDsaSpec; } /** * @return will be null if constructed from a spec which was * directly constructed from H */ public byte[] getSeed() { return seed; } /** * @return the hash of the seed */ public byte[] getH() { return h; } /** * @return the private key */ public byte[] geta() { return a; } /** * @return the public key */ public GroupElement getA() { return A; } /** * @return the public key */ public byte[] getAbyte() { return Abyte; } /** * @since 0.9.25 */ @Override public int hashCode() { return Arrays.hashCode(seed); } /** * @since 0.9.25 */ @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof EdDSAPrivateKey)) return false; EdDSAPrivateKey pk = (EdDSAPrivateKey) o; return Arrays.equals(seed, pk.getSeed()) && edDsaSpec.equals(pk.getParams()); } }