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* 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: - *
+ * 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: + *
* Supported conversions: - *
- * The precomputed tables are used for {@link #scalarMultiply(byte[])} - * and {@link #doubleScalarMultiplyVariableTime(GroupElement, byte[], byte[])}. - * - * @param precomputeSingle should the matrix for scalarMultiply() be precomputed? + * Precomputes table for {@link #scalarMultiply(byte[])}. + * @since 0.9.36 split out from precompute() */ - public synchronized void precompute(final boolean precomputeSingle) { - GroupElement Bi; - - if (precomputeSingle && this.precmp == null) { - // Precomputation for single scalar multiplication. - this.precmp = new GroupElement[32][8]; - // TODO-CR BR: check that this == base point when the method is called. - Bi = this; - for (int i = 0; i < 32; i++) { - GroupElement Bij = Bi; - for (int j = 0; j < 8; j++) { - final FieldElement recip = Bij.Z.invert(); - final FieldElement x = Bij.X.multiply(recip); - final FieldElement y = Bij.Y.multiply(recip); - this.precmp[i][j] = precomp(this.curve, y.add(x), y.subtract(x), x.multiply(y).multiply(this.curve.get2D())); - Bij = Bij.add(Bi.toCached()).toP3(); - } - // Only every second summand is precomputed (16^2 = 256) - for (int k = 0; k < 8; k++) { - Bi = Bi.add(Bi.toCached()).toP3(); - } + private GroupElement[][] precomputeSingle() { + // Precomputation for single scalar multiplication. + GroupElement[][] precmp = new GroupElement[32][8]; + // TODO-CR BR: check that this == base point when the method is called. + GroupElement Bi = this; + for (int i = 0; i < 32; i++) { + GroupElement Bij = Bi; + for (int j = 0; j < 8; j++) { + final FieldElement recip = Bij.Z.invert(); + final FieldElement x = Bij.X.multiply(recip); + final FieldElement y = Bij.Y.multiply(recip); + precmp[i][j] = precomp(this.curve, y.add(x), y.subtract(x), x.multiply(y).multiply(this.curve.get2D())); + Bij = Bij.add(Bi.toCached()).toP3(); + } + // Only every second summand is precomputed (16^2 = 256) + for (int k = 0; k < 8; k++) { + Bi = Bi.add(Bi.toCached()).toP3(); } } + return precmp; + } + /** + * Precomputes table for {@link #doubleScalarMultiplyVariableTime(GroupElement, byte[], byte[])}. + * @since 0.9.36 split out from precompute() + */ + private GroupElement[] precomputeDouble() { // Precomputation for double scalar multiplication. // P,3P,5P,7P,9P,11P,13P,15P - if (this.dblPrecmp != null) - return; - this.dblPrecmp = new GroupElement[8]; - Bi = this; + GroupElement[] dblPrecmp = new GroupElement[8]; + GroupElement Bi = this; for (int i = 0; i < 8; i++) { final FieldElement recip = Bi.Z.invert(); final FieldElement x = Bi.X.multiply(recip); final FieldElement y = Bi.Y.multiply(recip); - this.dblPrecmp[i] = precomp(this.curve, y.add(x), y.subtract(x), x.multiply(y).multiply(this.curve.get2D())); + dblPrecmp[i] = precomp(this.curve, y.add(x), y.subtract(x), x.multiply(y).multiply(this.curve.get2D())); // Bi = edwards(B,edwards(B,Bi)) Bi = this.add(this.add(Bi.toCached()).toP3().toCached()).toP3(); } + return dblPrecmp; } /** @@ -496,7 +587,7 @@ public class GroupElement implements Serializable { * r in P x P representation: *
* r = ((X' : Z'), (Y' : T')) where - *
* r = (X'' : Y'' : Z'') where - *
* r = ((X' : Z'), (Y' : T')) where - *
* Setting A = (Y1 - X1) * (Y2 - X2), B = (Y1 + X1) * (Y2 + X2), C = 2 * d * T1 * T2, D = 2 * Z1 * Z2 we get - *
* r = (X'' : Y'' : Z'' : T'') where - *
* r in P x P representation: - *
* Setting A = (Y1 - X1) * (Y2 - X2), B = (Y1 + X1) * (Y2 + X2), C = 2 * d * T1 * T2, D = 2 * Z1 * Z2 we get - *
* Preconditions: - *
* Postconditions: - *
* Preconditions: - *
* Postconditions: - *
* Preconditions: - *
* Postconditions: - *
* Preconditions: - *
* Postconditions: - *
@@ -382,11 +382,11 @@ public class Ed25519FieldElement extends FieldElement { * Can overlap h with f. *
* Preconditions: - *
* Postconditions: - *
* See {@link #multiply(FieldElement)} for discussion @@ -538,11 +538,11 @@ public class Ed25519FieldElement extends FieldElement { * Can overlap h with f. *
* Preconditions: - *
* Postconditions: - *
* See {@link #multiply(FieldElement)} for discussion
@@ -934,6 +934,30 @@ public class Ed25519FieldElement extends FieldElement {
return multiply(t0);
}
+ /**
+ * Constant-time conditional move. Well, actually it is a conditional copy.
+ * Logic is inspired by the SUPERCOP implementation at:
+ * https://github.com/floodyberry/supercop/blob/master/crypto_sign/ed25519/ref10/fe_cmov.c
+ *
+ * @param val the other field element.
+ * @param b must be 0 or 1, otherwise results are undefined.
+ * @return a copy of this if b == 0, or a copy of val if b == 1.
+ * @since 0.9.36
+ */
+ @Override
+ public FieldElement cmov(FieldElement val, int b) {
+ Ed25519FieldElement that = (Ed25519FieldElement) val;
+ b = -b;
+ int[] result = new int[10];
+ for (int i = 0; i < 10; i++) {
+ result[i] = this.t[i];
+ int x = this.t[i] ^ that.t[i];
+ x &= b;
+ result[i] ^= x;
+ }
+ return new Ed25519FieldElement(this.f, result);
+ }
+
@Override
public int hashCode() {
return Arrays.hashCode(t);
diff --git a/core/java/src/net/i2p/crypto/eddsa/spec/EdDSANamedCurveTable.java b/core/java/src/net/i2p/crypto/eddsa/spec/EdDSANamedCurveTable.java
index 07fba43c3..7704693e7 100644
--- a/core/java/src/net/i2p/crypto/eddsa/spec/EdDSANamedCurveTable.java
+++ b/core/java/src/net/i2p/crypto/eddsa/spec/EdDSANamedCurveTable.java
@@ -1,6 +1,7 @@
package net.i2p.crypto.eddsa.spec;
-import java.util.Hashtable;
+import java.util.HashMap;
+import java.util.Locale;
import net.i2p.crypto.eddsa.Utils;
import net.i2p.crypto.eddsa.math.Curve;
@@ -16,6 +17,9 @@ import net.i2p.crypto.eddsa.math.ed25519.Ed25519ScalarOps;
*
*/
public class EdDSANamedCurveTable {
+ /** RFC 8032 */
+ public static final String ED_25519 = "Ed25519";
+ /** old name */
public static final String CURVE_ED25519_SHA512 = "ed25519-sha-512";
private static final Field ed25519field = new Field(
@@ -27,8 +31,8 @@ public class EdDSANamedCurveTable {
Utils.hexToBytes("a3785913ca4deb75abd841414d0a700098e879777940c78c73fe6f2bee6c0352"), // d
ed25519field.fromByteArray(Utils.hexToBytes("b0a00e4a271beec478e42fad0618432fa7d7fb3d99004d2b0bdfc14f8024832b"))); // I
- private static final EdDSANamedCurveSpec ed25519sha512 = new EdDSANamedCurveSpec(
- CURVE_ED25519_SHA512,
+ public static final EdDSANamedCurveSpec ED_25519_CURVE_SPEC = new EdDSANamedCurveSpec(
+ ED_25519,
ed25519curve,
"SHA-512", // H
new Ed25519ScalarOps(), // l
@@ -36,17 +40,34 @@ public class EdDSANamedCurveTable {
Utils.hexToBytes("5866666666666666666666666666666666666666666666666666666666666666"),
true)); // Precompute tables for B
- private static final Hashtable
- Specifications for curves and keys, and a table for named curves,
- initially containing only the 25519 curve "ed25519-sha-512".
+ Specifications for curves and keys, and a table for named curves.
+ Contains the following curves:
+
diff --git a/core/java/test/junit/net/i2p/crypto/eddsa/math/GroupElementTest.java b/core/java/test/junit/net/i2p/crypto/eddsa/math/GroupElementTest.java
index be6029090..b27946ef0 100644
--- a/core/java/test/junit/net/i2p/crypto/eddsa/math/GroupElementTest.java
+++ b/core/java/test/junit/net/i2p/crypto/eddsa/math/GroupElementTest.java
@@ -760,7 +760,7 @@ public class GroupElementTest {
GroupElement A = new GroupElement(curve, Utils.hexToBytes("d4cf8595571830644bd14af416954d09ab7159751ad9e0f7a6cbd92379e71a66"));
GroupElement B = ed25519.getB();
GroupElement geZero = curve.getZero(GroupElement.Representation.P3);
- geZero.precompute(false);
+ //geZero.precompute(false);
// 0 * GE(0) + 0 * GE(0) = GE(0)
assertThat(geZero.doubleScalarMultiplyVariableTime(geZero, zero, zero),
@@ -802,7 +802,7 @@ public class GroupElementTest {
// Arrange:
final GroupElement basePoint = ed25519.getB();
final GroupElement g = MathUtils.getRandomGroupElement();
- g.precompute(false);
+ //g.precompute(false);
final FieldElement f1 = MathUtils.getRandomFieldElement();
final FieldElement f2 = MathUtils.getRandomFieldElement();
diff --git a/history.txt b/history.txt
index f9d7849cb..0103cec11 100644
--- a/history.txt
+++ b/history.txt
@@ -1,3 +1,6 @@
+2018-07-01 zzz
+ * Crypto: Backport EdDSA versions 0.2/0.3 from github
+
2018-06-30 zzz
* Console: Fix reading flags when symlinked (ticket #2270)
* Router: Reselect jbigi lib when processor changes (ticket #2277)
diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java
index 282c18b42..0725033fa 100644
--- a/router/java/src/net/i2p/router/RouterVersion.java
+++ b/router/java/src/net/i2p/router/RouterVersion.java
@@ -18,7 +18,7 @@ public class RouterVersion {
/** deprecated */
public final static String ID = "Monotone";
public final static String VERSION = CoreVersion.VERSION;
- public final static long BUILD = 2;
+ public final static long BUILD = 3;
/** for example "-test" */
public final static String EXTRA = "";