diff --git a/core/java/src/net/i2p/crypto/eddsa/EdDSAEngine.java b/core/java/src/net/i2p/crypto/eddsa/EdDSAEngine.java index f693b946d..e6756bad7 100644 --- a/core/java/src/net/i2p/crypto/eddsa/EdDSAEngine.java +++ b/core/java/src/net/i2p/crypto/eddsa/EdDSAEngine.java @@ -11,6 +11,8 @@ import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; import java.util.Arrays; import net.i2p.crypto.eddsa.math.Curve; @@ -54,6 +56,8 @@ import net.i2p.crypto.eddsa.math.ScalarOps; * */ public final class EdDSAEngine extends Signature { + public static final String SIGNATURE_ALGORITHM = "NONEwithEdDSA"; + private MessageDigest digest; private ByteArrayOutputStream baos; private EdDSAKey key; @@ -76,14 +80,14 @@ public final class EdDSAEngine extends Signature { private static class OneShotSpec implements AlgorithmParameterSpec {} /** - * No specific hash requested, allows any EdDSA key. + * No specific EdDSA-internal hash requested, allows any EdDSA key. */ public EdDSAEngine() { - super("EdDSA"); + super(SIGNATURE_ALGORITHM); } /** - * Specific hash requested, only matching keys will be allowed. + * Specific EdDSA-internal hash requested, only matching keys will be allowed. * @param digest the hash algorithm that keys must have to sign or verify. */ public EdDSAEngine(MessageDigest digest) { @@ -147,6 +151,16 @@ public final class EdDSAEngine extends Signature { } } else if (!key.getParams().getHashAlgorithm().equals(digest.getAlgorithm())) throw new InvalidKeyException("Key hash algorithm does not match chosen digest"); + } else if (publicKey.getClass().getName().equals("sun.security.x509.X509Key")) { + // X509Certificate will sometimes contain an X509Key rather than the EdDSAPublicKey itself; the contained + // key is valid but needs to be instanced as an EdDSAPublicKey before it can be used. + EdDSAPublicKey parsedPublicKey; + try { + parsedPublicKey = new EdDSAPublicKey(new X509EncodedKeySpec(publicKey.getEncoded())); + } catch (InvalidKeySpecException ex) { + throw new InvalidKeyException("cannot handle X.509 EdDSA public key: " + publicKey.getAlgorithm()); + } + engineInitVerify(parsedPublicKey); } else { throw new InvalidKeyException("cannot identify EdDSA public key: " + publicKey.getClass()); } @@ -311,6 +325,8 @@ public final class EdDSAEngine extends Signature { * sig = sign() * * + * @param data the message to be signed + * @return the signature * @throws SignatureException if update() already called * @see #ONE_SHOT_MODE * @since 0.9.25 @@ -330,6 +346,10 @@ public final class EdDSAEngine extends Signature { * sig = sign() * * + * @param data byte array containing the message to be signed + * @param off the start of the message inside data + * @param len the length of the message + * @return the signature * @throws SignatureException if update() already called * @see #ONE_SHOT_MODE * @since 0.9.25 @@ -351,6 +371,9 @@ public final class EdDSAEngine extends Signature { * ok = verify(signature) * * + * @param data the message that was signed + * @param signature of the message + * @return true if the signature is valid, false otherwise * @throws SignatureException if update() already called * @see #ONE_SHOT_MODE * @since 0.9.25 @@ -370,6 +393,11 @@ public final class EdDSAEngine extends Signature { * ok = verify(signature) * * + * @param data byte array containing the message that was signed + * @param off the start of the message inside data + * @param len the length of the message + * @param signature of the message + * @return true if the signature is valid, false otherwise * @throws SignatureException if update() already called * @see #ONE_SHOT_MODE * @since 0.9.25 @@ -389,6 +417,11 @@ public final class EdDSAEngine extends Signature { * ok = verify(signature, sigoff, siglen) * * + * @param data the message that was signed + * @param signature byte array containing the signature + * @param sigoff the start of the signature + * @param siglen the length of the signature + * @return true if the signature is valid, false otherwise * @throws SignatureException if update() already called * @see #ONE_SHOT_MODE * @since 0.9.25 @@ -408,6 +441,13 @@ public final class EdDSAEngine extends Signature { * ok = verify(signature, sigoff, siglen) * * + * @param data byte array containing the message that was signed + * @param off the start of the message inside data + * @param len the length of the message + * @param signature byte array containing the signature + * @param sigoff the start of the signature + * @param siglen the length of the signature + * @return true if the signature is valid, false otherwise * @throws SignatureException if update() already called * @see #ONE_SHOT_MODE * @since 0.9.25 diff --git a/core/java/src/net/i2p/crypto/eddsa/EdDSAKey.java b/core/java/src/net/i2p/crypto/eddsa/EdDSAKey.java index d710d8c2f..988045d9b 100644 --- a/core/java/src/net/i2p/crypto/eddsa/EdDSAKey.java +++ b/core/java/src/net/i2p/crypto/eddsa/EdDSAKey.java @@ -11,8 +11,14 @@ import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec; */ public interface EdDSAKey { /** - * return a parameter specification representing the EdDSA domain - * parameters for the key. + * The reported key algorithm for all EdDSA keys + * @since 0.9.36 + */ + public String KEY_ALGORITHM = "EdDSA"; + + /** + * @return a parameter specification representing the EdDSA domain + * parameters for the key. */ public EdDSAParameterSpec getParams(); } diff --git a/core/java/src/net/i2p/crypto/eddsa/EdDSAPrivateKey.java b/core/java/src/net/i2p/crypto/eddsa/EdDSAPrivateKey.java index f875f0d6c..8703c2945 100644 --- a/core/java/src/net/i2p/crypto/eddsa/EdDSAPrivateKey.java +++ b/core/java/src/net/i2p/crypto/eddsa/EdDSAPrivateKey.java @@ -13,11 +13,15 @@ import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; /** * An EdDSA private key. *

- * Warning: Private key encoding is not fully specified in the - * current IETF draft. This implementation uses PKCS#8 encoding, + * Warning: Private key encoding is based on the current curdle WG draft, * and is subject to change. See getEncoded(). *

- * Ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04 + * For compatibility with older releases, decoding supports both the old and new + * draft specifications. See decode(). + *

+ * Ref: https://tools.ietf.org/html/draft-ietf-curdle-pkix-04 + *

+ * Old Ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04 *

* * @since 0.9.15 @@ -33,6 +37,12 @@ public class EdDSAPrivateKey implements EdDSAKey, PrivateKey { private final byte[] Abyte; private final EdDSAParameterSpec edDsaSpec; + // OID 1.3.101.xxx + private static final int OID_OLD = 100; + private static final int OID_ED25519 = 112; + private static final int OID_BYTE = 11; + private static final int IDLEN_BYTE = 6; + public EdDSAPrivateKey(EdDSAPrivateKeySpec spec) { this.seed = spec.getSeed(); this.h = spec.getH(); @@ -47,121 +57,213 @@ public class EdDSAPrivateKey implements EdDSAKey, PrivateKey { */ public EdDSAPrivateKey(PKCS8EncodedKeySpec spec) throws InvalidKeySpecException { this(new EdDSAPrivateKeySpec(decode(spec.getEncoded()), - EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512))); + EdDSANamedCurveTable.ED_25519_CURVE_SPEC)); } + @Override public String getAlgorithm() { - return "EdDSA"; + return KEY_ALGORITHM; } + @Override public String getFormat() { return "PKCS#8"; } /** - * This follows the docs from - * java.security.spec.PKCS8EncodedKeySpec - * quote: + * Returns the public key in its canonical encoding. + *

+ * This implements the following specs: + *

+ *

+ * 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. + *

+ * For keys in older formats, decoding and then re-encoding is sufficient to + * migrate them to the canonical encoding. + *

+ * Relevant spec quotes: *
-     *  The PrivateKeyInfo syntax is defined in the PKCS#8 standard as follows:
-     *  PrivateKeyInfo ::= SEQUENCE {
+     *  OneAsymmetricKey ::= SEQUENCE {
      *    version Version,
      *    privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
      *    privateKey PrivateKey,
-     *    attributes [0] IMPLICIT Attributes OPTIONAL }
+     *    attributes [0] Attributes OPTIONAL,
+     *    ...,
+     *    [[2: publicKey [1] PublicKey OPTIONAL ]],
+     *    ...
+     *  }
+     *
      *  Version ::= INTEGER
      *  PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
      *  PrivateKey ::= OCTET STRING
+     *  PublicKey ::= OCTET STRING
      *  Attributes ::= SET OF Attribute
      *
* *
-     *  AlgorithmIdentifier ::= SEQUENCE
-     *  {
-     *    algorithm           OBJECT IDENTIFIER,
-     *    parameters          ANY OPTIONAL
-     *  }
+     *  ... when encoding a OneAsymmetricKey object, the private key is wrapped
+     *  in a CurvePrivateKey object and wrapped by the OCTET STRING of the
+     *  'privateKey' field.
+     *
+     *  CurvePrivateKey ::= OCTET STRING
      *
* - * Ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04 + *
+     *  AlgorithmIdentifier  ::=  SEQUENCE  {
+     *    algorithm   OBJECT IDENTIFIER,
+     *    parameters  ANY DEFINED BY algorithm OPTIONAL
+     *  }
      *
-     *  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.
+     *  For all of the OIDs, the parameters MUST be absent.
+     *
* - * 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. + *
+     *  id-Ed25519   OBJECT IDENTIFIER ::= { 1 3 101 112 }
+     *
* - * @return 49 bytes for Ed25519, null for other curves + * @return 48 bytes for Ed25519, null for other curves * @since implemented in 0.9.25 */ + @Override public byte[] getEncoded() { - if (!edDsaSpec.equals(EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512))) + if (!edDsaSpec.equals(EdDSANamedCurveTable.ED_25519_CURVE_SPEC)) return null; - int totlen = 17 + seed.length; + if (seed == null) + return null; + int totlen = 16 + seed.length; byte[] rv = new byte[totlen]; int idx = 0; // sequence rv[idx++] = 0x30; - rv[idx++] = (byte) (15 + seed.length); - + rv[idx++] = (byte) (totlen - 2); // version - // not in the Josefsson example rv[idx++] = 0x02; rv[idx++] = 1; + // v1 - no public key included rv[idx++] = 0; - // Algorithm Identifier // sequence - // not in the Josefsson example rv[idx++] = 0x30; - rv[idx++] = 8; - // OID 1.3.101.100 + rv[idx++] = 5; + // OID // 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++] = (byte) OID_ED25519; + // params - absent + // PrivateKey + rv[idx++] = 0x04; // octet string + rv[idx++] = (byte) (2 + seed.length); + // CurvePrivateKey rv[idx++] = 0x04; // octet string rv[idx++] = (byte) seed.length; + // the key System.arraycopy(seed, 0, rv, idx, seed.length); return rv; } /** - * This is really dumb for now. - * See getEncoded(). + * Extracts the private key bytes from the provided encoding. + *

+ * This will decode data conforming to the current spec at + * https://tools.ietf.org/html/draft-ietf-curdle-pkix-04 + * or as inferred from the old spec at + * https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04. + *

+ * Contrary to draft-ietf-curdle-pkix-04, it WILL accept a parameter value + * of NULL, as it is required for interoperability with the default Java + * keystore. Other implementations MUST NOT copy this behaviour from here + * unless they also need to read keys from the default Java keystore. + *

+ * This is really dumb for now. It does not use a general-purpose ASN.1 decoder. + * See also getEncoded(). * * @return 32 bytes for Ed25519, throws for other curves * @since 0.9.25 */ private static byte[] decode(byte[] d) throws InvalidKeySpecException { try { + // + // Setup and OID check + // + int totlen = 48; + int idlen = 5; + int doid = d[OID_BYTE]; + if (doid == OID_OLD) { + totlen = 49; + idlen = 8; + } else if (doid == OID_ED25519) { + // Detect parameter value of NULL + if (d[IDLEN_BYTE] == 7) { + totlen = 50; + idlen = 7; + } + } else { + throw new InvalidKeySpecException("unsupported key spec"); + } + + // + // Pre-decoding check + // + if (d.length != totlen) { + throw new InvalidKeySpecException("invalid key spec length"); + } + + // + // Decoding + // int idx = 0; if (d[idx++] != 0x30 || - d[idx++] != 47 || + d[idx++] != (totlen - 2) || d[idx++] != 0x02 || d[idx++] != 1 || d[idx++] != 0 || d[idx++] != 0x30 || - d[idx++] != 8 || + d[idx++] != idlen || 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++] != 101) { + throw new InvalidKeySpecException("unsupported key spec"); + } + idx++; // OID, checked above + // parameters only with old OID + if (doid == OID_OLD) { + if (d[idx++] != 0x0a || + d[idx++] != 1 || + d[idx++] != 1) { + throw new InvalidKeySpecException("unsupported key spec"); + } + } else { + // Handle parameter value of NULL + // + // Quote https://tools.ietf.org/html/draft-ietf-curdle-pkix-04 : + // For all of the OIDs, the parameters MUST be absent. + // Regardless of the defect in the original 1997 syntax, + // implementations MUST NOT accept a parameters value of NULL. + // + // But Java's default keystore puts it in (when decoding as + // PKCS8 and then re-encoding to pass on), so we must accept it. + if (idlen == 7) { + if (d[idx++] != 0x05 || + d[idx++] != 0) { + throw new InvalidKeySpecException("unsupported key spec"); + } + } + // PrivateKey wrapping the CurvePrivateKey + if (d[idx++] != 0x04 || + d[idx++] != 34) { + throw new InvalidKeySpecException("unsupported key spec"); + } + } + if (d[idx++] != 0x04 || d[idx++] != 32) { throw new InvalidKeySpecException("unsupported key spec"); } @@ -173,6 +275,7 @@ public class EdDSAPrivateKey implements EdDSAKey, PrivateKey { } } + @Override public EdDSAParameterSpec getParams() { return edDsaSpec; } diff --git a/core/java/src/net/i2p/crypto/eddsa/EdDSAPublicKey.java b/core/java/src/net/i2p/crypto/eddsa/EdDSAPublicKey.java index 747d92b7b..383d46a4a 100644 --- a/core/java/src/net/i2p/crypto/eddsa/EdDSAPublicKey.java +++ b/core/java/src/net/i2p/crypto/eddsa/EdDSAPublicKey.java @@ -13,10 +13,15 @@ import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; /** * An EdDSA public key. *

- * Warning: Public key encoding is is based on the - * current IETF draft, and is subject to change. See getEncoded(). + * Warning: Public key encoding is is based on the current curdle WG draft, + * and is subject to change. See getEncoded(). *

- * Ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04 + * For compatibility with older releases, decoding supports both the old and new + * draft specifications. See decode(). + *

+ * Ref: https://tools.ietf.org/html/draft-ietf-curdle-pkix-04 + *

+ * Old Ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04 *

* * @since 0.9.15 @@ -26,13 +31,18 @@ import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; public class EdDSAPublicKey implements EdDSAKey, PublicKey { private static final long serialVersionUID = 9837459837498475L; private final GroupElement A; - private final GroupElement Aneg; + private GroupElement Aneg; private final byte[] Abyte; private final EdDSAParameterSpec edDsaSpec; + // OID 1.3.101.xxx + private static final int OID_OLD = 100; + private static final int OID_ED25519 = 112; + private static final int OID_BYTE = 8; + private static final int IDLEN_BYTE = 3; + public EdDSAPublicKey(EdDSAPublicKeySpec spec) { this.A = spec.getA(); - this.Aneg = spec.getNegativeA(); this.Abyte = this.A.toByteArray(); this.edDsaSpec = spec.getParams(); } @@ -42,65 +52,81 @@ public class EdDSAPublicKey implements EdDSAKey, PublicKey { */ public EdDSAPublicKey(X509EncodedKeySpec spec) throws InvalidKeySpecException { this(new EdDSAPublicKeySpec(decode(spec.getEncoded()), - EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512))); + EdDSANamedCurveTable.ED_25519_CURVE_SPEC)); } + @Override public String getAlgorithm() { - return "EdDSA"; + return KEY_ALGORITHM; } + @Override public String getFormat() { return "X.509"; } /** - * This follows the spec at - * ref: https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04 - * which matches the docs from - * java.security.spec.X509EncodedKeySpec - * quote: + * Returns the public key in its canonical encoding. + *

+ * This implements the following specs: + *

+ *

+ * For keys in older formats, decoding and then re-encoding is sufficient to + * migrate them to the canonical encoding. + *

+ * Relevant spec quotes: *
-     * The SubjectPublicKeyInfo syntax is defined in the X.509 standard as follows:
-     *  SubjectPublicKeyInfo ::= SEQUENCE {
-     *    algorithm AlgorithmIdentifier,
-     *    subjectPublicKey BIT STRING }
-     *
+ * In the X.509 certificate, the subjectPublicKeyInfo field has the + * SubjectPublicKeyInfo type, which has the following ASN.1 syntax: * - *
-     *  AlgorithmIdentifier ::= SEQUENCE
-     *  {
-     *    algorithm           OBJECT IDENTIFIER,
-     *    parameters          ANY OPTIONAL
+     *  SubjectPublicKeyInfo  ::=  SEQUENCE  {
+     *    algorithm         AlgorithmIdentifier,
+     *    subjectPublicKey  BIT STRING
      *  }
      *
* - * @return 47 bytes for Ed25519, null for other curves + *
+     *  AlgorithmIdentifier  ::=  SEQUENCE  {
+     *    algorithm   OBJECT IDENTIFIER,
+     *    parameters  ANY DEFINED BY algorithm OPTIONAL
+     *  }
+     *
+     *  For all of the OIDs, the parameters MUST be absent.
+     *
+ * + *
+     *  id-Ed25519   OBJECT IDENTIFIER ::= { 1 3 101 112 }
+     *
+ * + * @return 44 bytes for Ed25519, null for other curves * @since implemented in 0.9.25 */ + @Override public byte[] getEncoded() { - if (!edDsaSpec.equals(EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512))) + if (!edDsaSpec.equals(EdDSANamedCurveTable.ED_25519_CURVE_SPEC)) return null; - int totlen = 15 + Abyte.length; + int totlen = 12 + Abyte.length; byte[] rv = new byte[totlen]; int idx = 0; // sequence rv[idx++] = 0x30; - rv[idx++] = (byte) (13 + Abyte.length); + rv[idx++] = (byte) (totlen - 2); // Algorithm Identifier // sequence rv[idx++] = 0x30; - rv[idx++] = 8; - // OID 1.3.101.100 + rv[idx++] = 5; + // OID // https://msdn.microsoft.com/en-us/library/windows/desktop/bb540809%28v=vs.85%29.aspx 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 + rv[idx++] = (byte) OID_ED25519; + // params - absent // the key rv[idx++] = 0x03; // bit string rv[idx++] = (byte) (1 + Abyte.length); @@ -110,28 +136,93 @@ public class EdDSAPublicKey implements EdDSAKey, PublicKey { } /** - * This is really dumb for now. - * See getEncoded(). + * Extracts the public key bytes from the provided encoding. + *

+ * This will decode data conforming to the current spec at + * https://tools.ietf.org/html/draft-ietf-curdle-pkix-04 + * or the old spec at + * https://tools.ietf.org/html/draft-josefsson-pkix-eddsa-04. + *

+ * Contrary to draft-ietf-curdle-pkix-04, it WILL accept a parameter value + * of NULL, as it is required for interoperability with the default Java + * keystore. Other implementations MUST NOT copy this behaviour from here + * unless they also need to read keys from the default Java keystore. + *

+ * This is really dumb for now. It does not use a general-purpose ASN.1 decoder. + * See also getEncoded(). + *

* * @return 32 bytes for Ed25519, throws for other curves * @since 0.9.25 */ private static byte[] decode(byte[] d) throws InvalidKeySpecException { try { + // + // Setup and OID check + // + int totlen = 44; + int idlen = 5; + int doid = d[OID_BYTE]; + if (doid == OID_OLD) { + totlen = 47; + idlen = 8; + } else if (doid == OID_ED25519) { + // Detect parameter value of NULL + if (d[IDLEN_BYTE] == 7) { + totlen = 46; + idlen = 7; + } + } else { + throw new InvalidKeySpecException("unsupported key spec"); + } + + // + // Pre-decoding check + // + if (d.length != totlen) { + throw new InvalidKeySpecException("invalid key spec length"); + } + + // + // Decoding + // int idx = 0; if (d[idx++] != 0x30 || - d[idx++] != 45 || + d[idx++] != (totlen - 2) || d[idx++] != 0x30 || - d[idx++] != 8 || + d[idx++] != idlen || 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++] != 0x03 || + d[idx++] != 101) { + throw new InvalidKeySpecException("unsupported key spec"); + } + idx++; // OID, checked above + // parameters only with old OID + if (doid == OID_OLD) { + if (d[idx++] != 0x0a || + d[idx++] != 1 || + d[idx++] != 1) { + throw new InvalidKeySpecException("unsupported key spec"); + } + } else { + // Handle parameter value of NULL + // + // Quote https://tools.ietf.org/html/draft-ietf-curdle-pkix-04 : + // For all of the OIDs, the parameters MUST be absent. + // Regardless of the defect in the original 1997 syntax, + // implementations MUST NOT accept a parameters value of NULL. + // + // But Java's default keystore puts it in (when decoding as + // PKCS8 and then re-encoding to pass on), so we must accept it. + if (idlen == 7) { + if (d[idx++] != 0x05 || + d[idx++] != 0) { + throw new InvalidKeySpecException("unsupported key spec"); + } + } + } + if (d[idx++] != 0x03 || d[idx++] != 33 || d[idx++] != 0) { throw new InvalidKeySpecException("unsupported key spec"); @@ -144,6 +235,7 @@ public class EdDSAPublicKey implements EdDSAKey, PublicKey { } } + @Override public EdDSAParameterSpec getParams() { return edDsaSpec; } @@ -153,7 +245,13 @@ public class EdDSAPublicKey implements EdDSAKey, PublicKey { } public GroupElement getNegativeA() { - return Aneg; + // Only read Aneg once, otherwise read re-ordering might occur between here and return. Requires all GroupElement's fields to be final. + GroupElement ourAneg = Aneg; + if(ourAneg == null) { + ourAneg = A.negate(); + Aneg = ourAneg; + } + return ourAneg; } public byte[] getAbyte() { diff --git a/core/java/src/net/i2p/crypto/eddsa/KeyPairGenerator.java b/core/java/src/net/i2p/crypto/eddsa/KeyPairGenerator.java index 6fd13d865..f42f272d0 100644 --- a/core/java/src/net/i2p/crypto/eddsa/KeyPairGenerator.java +++ b/core/java/src/net/i2p/crypto/eddsa/KeyPairGenerator.java @@ -17,12 +17,12 @@ import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; import net.i2p.util.RandomSource; /** - * Default strength is 256 + * Default keysize is 256 (Ed25519) * * @since 0.9.15 */ public final class KeyPairGenerator extends KeyPairGeneratorSpi { - private static final int DEFAULT_STRENGTH = 256; + private static final int DEFAULT_KEYSIZE = 256; private EdDSAParameterSpec edParams; private SecureRandom random; private boolean initialized; @@ -32,11 +32,11 @@ public final class KeyPairGenerator extends KeyPairGeneratorSpi { static { edParameters = new Hashtable(); - edParameters.put(Integer.valueOf(DEFAULT_STRENGTH), new EdDSAGenParameterSpec(EdDSANamedCurveTable.CURVE_ED25519_SHA512)); + edParameters.put(Integer.valueOf(DEFAULT_KEYSIZE), new EdDSAGenParameterSpec(EdDSANamedCurveTable.ED_25519)); } - public void initialize(int strength, SecureRandom random) { - AlgorithmParameterSpec edParams = edParameters.get(Integer.valueOf(strength)); + public void initialize(int keysize, SecureRandom random) { + AlgorithmParameterSpec edParams = edParameters.get(Integer.valueOf(keysize)); if (edParams == null) throw new InvalidParameterException("unknown key type."); try { @@ -61,7 +61,7 @@ public final class KeyPairGenerator extends KeyPairGeneratorSpi { public KeyPair generateKeyPair() { if (!initialized) - initialize(DEFAULT_STRENGTH, RandomSource.getInstance()); + initialize(DEFAULT_KEYSIZE, RandomSource.getInstance()); byte[] seed = new byte[edParams.getCurve().getField().getb()/8]; random.nextBytes(seed); diff --git a/core/java/src/net/i2p/crypto/eddsa/Utils.java b/core/java/src/net/i2p/crypto/eddsa/Utils.java index 3ade2ced4..f4c4e7c36 100644 --- a/core/java/src/net/i2p/crypto/eddsa/Utils.java +++ b/core/java/src/net/i2p/crypto/eddsa/Utils.java @@ -1,7 +1,7 @@ package net.i2p.crypto.eddsa; /** - * Basic utilities for eddsa. + * Basic utilities for EdDSA. * Not for external use, not maintained as a public API. * * @since 0.9.15 @@ -11,6 +11,8 @@ package net.i2p.crypto.eddsa; public class Utils { /** * Constant-time byte comparison. + * @param b a byte + * @param c a byte * @return 1 if b and c are equal, 0 otherwise. */ public static int equal(int b, int c) { @@ -24,6 +26,8 @@ public class Utils { /** * Constant-time byte[] comparison. + * @param b a byte[] + * @param c a byte[] * @return 1 if b and c are equal, 0 otherwise. */ public static int equal(byte[] b, byte[] c) { diff --git a/core/java/src/net/i2p/crypto/eddsa/math/Curve.java b/core/java/src/net/i2p/crypto/eddsa/math/Curve.java index fba8f29aa..29a4a8a9f 100644 --- a/core/java/src/net/i2p/crypto/eddsa/math/Curve.java +++ b/core/java/src/net/i2p/crypto/eddsa/math/Curve.java @@ -19,6 +19,7 @@ public class Curve implements Serializable { private final GroupElement zeroP2; private final GroupElement zeroP3; + private final GroupElement zeroP3PrecomputedDouble; private final GroupElement zeroPrecomp; public Curve(Field f, byte[] d, FieldElement I) { @@ -30,7 +31,8 @@ public class Curve implements Serializable { FieldElement zero = f.ZERO; FieldElement one = f.ONE; zeroP2 = GroupElement.p2(this, zero, one, one); - zeroP3 = GroupElement.p3(this, zero, one, one, zero); + zeroP3 = GroupElement.p3(this, zero, one, one, zero, false); + zeroP3PrecomputedDouble = GroupElement.p3(this, zero, one, one, zero, true); zeroPrecomp = GroupElement.precomp(this, one, one, zero); } @@ -56,6 +58,8 @@ public class Curve implements Serializable { return zeroP2; case P3: return zeroP3; + case P3PrecomputedDouble: + return zeroP3PrecomputedDouble; case PRECOMP: return zeroPrecomp; default: @@ -64,9 +68,7 @@ public class Curve implements Serializable { } public GroupElement createPoint(byte[] P, boolean precompute) { - GroupElement ge = new GroupElement(this, P); - if (precompute) - ge.precompute(true); + GroupElement ge = new GroupElement(this, P, precompute); return ge; } diff --git a/core/java/src/net/i2p/crypto/eddsa/math/FieldElement.java b/core/java/src/net/i2p/crypto/eddsa/math/FieldElement.java index b55d8705a..06e89493b 100644 --- a/core/java/src/net/i2p/crypto/eddsa/math/FieldElement.java +++ b/core/java/src/net/i2p/crypto/eddsa/math/FieldElement.java @@ -63,5 +63,10 @@ public abstract class FieldElement implements Serializable { public abstract FieldElement pow22523(); + /** + * @since 0.9.36 + */ + public abstract FieldElement cmov(FieldElement val, final int b); + // Note: concrete subclasses must implement hashCode() and equals() } diff --git a/core/java/src/net/i2p/crypto/eddsa/math/GroupElement.java b/core/java/src/net/i2p/crypto/eddsa/math/GroupElement.java index 65f791f95..4179a2b3f 100644 --- a/core/java/src/net/i2p/crypto/eddsa/math/GroupElement.java +++ b/core/java/src/net/i2p/crypto/eddsa/math/GroupElement.java @@ -40,6 +40,11 @@ public class GroupElement implements Serializable { P2, /** Extended (P^3): (X:Y:Z:T) satisfying x=X/Z, y=Y/Z, XY=ZT */ P3, + /** + * Can only be requested. Results in P3 representation but also populates dblPrecmp. + * @since 0.9.36 + */ + P3PrecomputedDouble, /** Completed (P x P): ((X:Z),(Y:T)) satisfying x=X/Z, y=Y/T */ P1P1, /** Precomputed (Duif): (y+x,y-x,2dxy) */ @@ -81,7 +86,29 @@ public class GroupElement implements Serializable { final FieldElement Y, final FieldElement Z, final FieldElement T) { - return new GroupElement(curve, Representation.P3, X, Y, Z, T); + return p3(curve, X, Y, Z, T, false); + } + + /** + * Creates a new group element in P3 representation, potentially with pre-computation. + * + * @param curve The curve. + * @param X The X coordinate. + * @param Y The Y coordinate. + * @param Z The Z coordinate. + * @param T The T coordinate. + * @param precomputeDoubleOnly If true, populate dblPrecmp, else set to null. + * @return The group element in P3 representation. + * @since 0.9.36 + */ + public static GroupElement p3( + final Curve curve, + final FieldElement X, + final FieldElement Y, + final FieldElement Z, + final FieldElement T, + final boolean precomputeDoubleOnly) { + return new GroupElement(curve, Representation.P3, X, Y, Z, T, precomputeDoubleOnly); } /** @@ -175,7 +202,7 @@ public class GroupElement implements Serializable { *

* Variable is package private only so that tests run. */ - GroupElement[][] precmp; + final GroupElement[][] precmp; /** * Precomputed table for {@link #doubleScalarMultiplyVariableTime(GroupElement, byte[], byte[])}, @@ -183,10 +210,10 @@ public class GroupElement implements Serializable { *

* Variable is package private only so that tests run. */ - GroupElement[] dblPrecmp; + final GroupElement[] dblPrecmp; /** - * Creates a group element for a curve. + * Creates a group element for a curve, without any pre-computation. * * @param curve The curve. * @param repr The representation used to represent the group element. @@ -202,12 +229,37 @@ public class GroupElement implements Serializable { final FieldElement Y, final FieldElement Z, final FieldElement T) { + this(curve, repr, X, Y, Z, T, false); + } + + /** + * Creates a group element for a curve, with optional pre-computation. + * + * @param curve The curve. + * @param repr The representation used to represent the group element. + * @param X The X coordinate. + * @param Y The Y coordinate. + * @param Z The Z coordinate. + * @param T The T coordinate. + * @param precomputeDouble If true, populate dblPrecmp, else set to null. + * @since 0.9.36 + */ + public GroupElement( + final Curve curve, + final Representation repr, + final FieldElement X, + final FieldElement Y, + final FieldElement Z, + final FieldElement T, + final boolean precomputeDouble) { this.curve = curve; this.repr = repr; this.X = X; this.Y = Y; this.Z = Z; this.T = T; + this.precmp = null; + this.dblPrecmp = precomputeDouble ? precomputeDouble() : null; } /** @@ -215,8 +267,28 @@ public class GroupElement implements Serializable { *

* A point (x,y) is encoded by storing y in bit 0 to bit 254 and the sign of x in bit 255. * x is recovered in the following way: - *