From 4d62f63c71fc99ec82767f1b1c345e347f87b9ff Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 6 Sep 2013 12:04:22 +0000 Subject: [PATCH 01/11] * Start of ECDSA branch: - Add ECConstants which looks for named curves and falls back to explicitly defining the curves - Add SigUtil with converters from Java formats (ASN.1, X.509, PKCS#8) to I2P formats for Signatures and SigningKeys - Move ASN.1 converter from DSAEngine to SigUtil, generalize for variable length, add support for longer sequences, add more sanity checks, add more exceptions - Attempt to add BC as a Provider - Add parameters (curve specs) to SigTypes - Add support for ECDSA to DSAEngine and KeyGenerator - Add KeyGenerator main() tests - More javadocs All is Java 1.5 compatible but the actual algorithms, curves, and other support aren't necessarily present in any JVM. Todo: More tests, more fallbacks for various JVMs --- .../src/net/i2p/crypto/CryptoConstants.java | 6 + core/java/src/net/i2p/crypto/DSAEngine.java | 193 ++++++----- core/java/src/net/i2p/crypto/ECConstants.java | 308 ++++++++++++++++++ .../java/src/net/i2p/crypto/KeyGenerator.java | 135 ++++++-- core/java/src/net/i2p/crypto/SigType.java | 22 +- core/java/src/net/i2p/crypto/SigUtil.java | 293 +++++++++++++++++ 6 files changed, 838 insertions(+), 119 deletions(-) create mode 100644 core/java/src/net/i2p/crypto/ECConstants.java create mode 100644 core/java/src/net/i2p/crypto/SigUtil.java diff --git a/core/java/src/net/i2p/crypto/CryptoConstants.java b/core/java/src/net/i2p/crypto/CryptoConstants.java index 228a92e84..21ebe3c1d 100644 --- a/core/java/src/net/i2p/crypto/CryptoConstants.java +++ b/core/java/src/net/i2p/crypto/CryptoConstants.java @@ -30,6 +30,7 @@ package net.i2p.crypto; */ import java.math.BigInteger; +import java.security.spec.DSAParameterSpec; import net.i2p.util.NativeBigInteger; @@ -63,4 +64,9 @@ public class CryptoConstants { + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + "15728E5A8AACAA68FFFFFFFFFFFFFFFF", 16); public static final BigInteger elgg = new NativeBigInteger("2"); + + /** + * @since 0.9.9 + */ + public static final DSAParameterSpec DSA_SHA1_SPEC = new DSAParameterSpec(dsap, dsaq, dsag); } diff --git a/core/java/src/net/i2p/crypto/DSAEngine.java b/core/java/src/net/i2p/crypto/DSAEngine.java index 62e2e6384..9c7718631 100644 --- a/core/java/src/net/i2p/crypto/DSAEngine.java +++ b/core/java/src/net/i2p/crypto/DSAEngine.java @@ -62,6 +62,12 @@ import net.i2p.util.NativeBigInteger; * * Params and rv's changed from Hash to SHA1Hash for version 0.8.1 * Hash variants of sign() and verifySignature() restored in 0.8.3, required by Syndie. + * + * As of 0.9.9, certain methods support ECDSA keys and signatures, i.e. all types + * specified in SigType. The type is specified by the getType() method in + * Signature, SigningPublicKey, and SigningPrivateKey. See Javadocs for individual + * methods for the supported types. Methods encountering an unsupported type + * will throw an IllegalArgumentException. */ public class DSAEngine { private final Log _log; @@ -80,11 +86,26 @@ public class DSAEngine { } /** - * Verify using DSA-SHA1. - * Uses TheCrypto code unless configured to use the java.security libraries. + * Verify using DSA-SHA1 or ECDSA. + * Uses TheCrypto code for DSA-SHA1 unless configured to use the java.security libraries. */ public boolean verifySignature(Signature signature, byte signedData[], SigningPublicKey verifyingKey) { boolean rv; + SigType type = signature.getType(); + if (type != verifyingKey.getType()) + throw new IllegalArgumentException("type mismatch sig=" + signature.getType() + " key=" + verifyingKey.getType()); + if (type != SigType.DSA_SHA1) { + try { + rv = altVerifySig(signature, signedData, verifyingKey); + if ((!rv) && _log.shouldLog(Log.WARN)) + _log.warn(type + " Sig Verify Fail"); + return rv; + } catch (GeneralSecurityException gse) { + if (_log.shouldLog(Log.WARN)) + _log.warn(type + " Sig Verify Fail", gse); + return false; + } + } if (_useJavaLibs) { try { rv = altVerifySigSHA1(signature, signedData, verifyingKey); @@ -104,25 +125,29 @@ public class DSAEngine { } /** - * Verify using DSA-SHA1 + * Verify using DSA-SHA1 ONLY */ public boolean verifySignature(Signature signature, byte signedData[], int offset, int size, SigningPublicKey verifyingKey) { return verifySignature(signature, calculateHash(signedData, offset, size), verifyingKey); } /** - * Verify using DSA-SHA1 + * Verify using DSA-SHA1 ONLY */ public boolean verifySignature(Signature signature, InputStream in, SigningPublicKey verifyingKey) { return verifySignature(signature, calculateHash(in), verifyingKey); } - /** @param hash SHA-1 hash, NOT a SHA-256 hash */ + /** + * Verify using DSA-SHA1 ONLY + * @param hash SHA-1 hash, NOT a SHA-256 hash + */ public boolean verifySignature(Signature signature, SHA1Hash hash, SigningPublicKey verifyingKey) { return verifySig(signature, hash, verifyingKey); } /** + * Nonstandard. * Used by Syndie. * @since 0.8.3 (restored, was removed in 0.8.1 and 0.8.2) */ @@ -131,10 +156,15 @@ public class DSAEngine { } /** + * Verify using DSA-SHA1 or Syndie DSA-SHA256 ONLY. * @param hash either a Hash or a SHA1Hash * @since 0.8.3 */ private boolean verifySig(Signature signature, SimpleDataStructure hash, SigningPublicKey verifyingKey) { + if (signature.getType() != SigType.DSA_SHA1) + throw new IllegalArgumentException("Bad sig type " + signature.getType()); + if (verifyingKey.getType() != SigType.DSA_SHA1) + throw new IllegalArgumentException("Bad key type " + verifyingKey.getType()); long start = _context.clock().now(); try { @@ -184,10 +214,22 @@ public class DSAEngine { } /** - * Sign using DSA-SHA1. + * Sign using DSA-SHA1 or ECDSA. * Uses TheCrypto code unless configured to use the java.security libraries. + * + * @return null on error */ public Signature sign(byte data[], SigningPrivateKey signingKey) { + SigType type = signingKey.getType(); + if (type != SigType.DSA_SHA1) { + try { + return altSign(data, signingKey); + } catch (GeneralSecurityException gse) { + if (_log.shouldLog(Log.WARN)) + _log.warn(type + " Sign Fail", gse); + return null; + } + } if (_useJavaLibs) { try { return altSignSHA1(data, signingKey); @@ -201,7 +243,9 @@ public class DSAEngine { } /** - * Sign using DSA-SHA1 + * Sign using DSA-SHA1 ONLY + * + * @return null on error */ public Signature sign(byte data[], int offset, int length, SigningPrivateKey signingKey) { if ((signingKey == null) || (data == null) || (data.length <= 0)) return null; @@ -210,8 +254,10 @@ public class DSAEngine { } /** - * Sign using DSA-SHA1. + * Sign using DSA-SHA1 ONLY. * Reads the stream until EOF. Does not close the stream. + * + * @return null on error */ public Signature sign(InputStream in, SigningPrivateKey signingKey) { if ((signingKey == null) || (in == null) ) return null; @@ -219,13 +265,21 @@ public class DSAEngine { return sign(h, signingKey); } - /** @param hash SHA-1 hash, NOT a SHA-256 hash */ + /** + * Sign using DSA-SHA1 ONLY. + * + * @param hash SHA-1 hash, NOT a SHA-256 hash + * @return null on error + */ public Signature sign(SHA1Hash hash, SigningPrivateKey signingKey) { return signIt(hash, signingKey); } /** + * Nonstandard. * Used by Syndie. + * + * @return null on error * @since 0.8.3 (restored, was removed in 0.8.1 and 0.8.2) */ public Signature sign(Hash hash, SigningPrivateKey signingKey) { @@ -233,11 +287,16 @@ public class DSAEngine { } /** + * Sign using DSA-SHA1 or Syndie DSA-SHA256 ONLY. + * * @param hash either a Hash or a SHA1Hash + * @return null on error * @since 0.8.3 */ private Signature signIt(SimpleDataStructure hash, SigningPrivateKey signingKey) { if ((signingKey == null) || (hash == null)) return null; + if (signingKey.getType() != SigType.DSA_SHA1) + throw new IllegalArgumentException("Bad key type " + signingKey.getType()); long start = _context.clock().now(); Signature sig = new Signature(); @@ -337,6 +396,27 @@ public class DSAEngine { return new SHA1Hash(digested); } + /** + * Generic verify DSA_SHA1 or ECDSA + * @throws GeneralSecurityException if algorithm unvailable or on other errors + * @since 0.9.9 + */ + private boolean altVerifySig(Signature signature, byte[] data, SigningPublicKey verifyingKey) + throws GeneralSecurityException { + SigType type = signature.getType(); + if (type != verifyingKey.getType()) + throw new IllegalArgumentException("type mismatch sig=" + signature.getType() + " key=" + verifyingKey.getType()); + if (type == SigType.DSA_SHA1) + return altVerifySigSHA1(signature, data, verifyingKey); + + java.security.Signature jsig = java.security.Signature.getInstance(type.getAlgorithmName()); + PublicKey pubKey = SigUtil.toJavaECKey(verifyingKey); + jsig.initVerify(pubKey); + jsig.update(data); + boolean rv = jsig.verify(SigUtil.toJavaSig(signature)); + return rv; + } + /** * Alternate to verifySignature() using java.security libraries. * @throws GeneralSecurityException if algorithm unvailable or on other errors @@ -353,7 +433,7 @@ public class DSAEngine { PublicKey pubKey = keyFact.generatePublic(spec); jsig.initVerify(pubKey); jsig.update(data); - boolean rv = jsig.verify(sigBytesToASN1(signature.getData())); + boolean rv = jsig.verify(SigUtil.toJavaSig(signature)); //if (!rv) { // System.out.println("BAD SIG\n" + net.i2p.util.HexDump.dump(signature.getData())); // System.out.println("BAD SIG\n" + net.i2p.util.HexDump.dump(sigBytesToASN1(signature.getData()))); @@ -361,6 +441,23 @@ public class DSAEngine { return rv; } + /** + * Generic sign DSA_SHA1 or ECDSA + * @throws GeneralSecurityException if algorithm unvailable or on other errors + * @since 0.9.9 + */ + private Signature altSign(byte[] data, SigningPrivateKey privateKey) throws GeneralSecurityException { + SigType type = privateKey.getType(); + if (type == SigType.DSA_SHA1) + return altSignSHA1(data, privateKey); + + java.security.Signature jsig = java.security.Signature.getInstance(type.getAlgorithmName()); + PrivateKey privKey = SigUtil.toJavaECKey(privateKey); + jsig.initSign(privKey, _context.random()); + jsig.update(data); + return SigUtil.fromJavaSig(jsig.sign(), type); + } + /** * Alternate to sign() using java.security libraries. * @throws GeneralSecurityException if algorithm unvailable or on other errors @@ -377,81 +474,7 @@ public class DSAEngine { PrivateKey privKey = keyFact.generatePrivate(spec); jsig.initSign(privKey, _context.random()); jsig.update(data); - return new Signature(aSN1ToSigBytes(jsig.sign())); - } - - /** - * http://download.oracle.com/javase/1.5.0/docs/guide/security/CryptoSpec.html - * Signature Format ASN.1 sequence of two INTEGER values: r and s, in that order: - * SEQUENCE ::= { r INTEGER, s INTEGER } - * - * http://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One - * 30 -- tag indicating SEQUENCE - * xx - length in octets - * - * 02 -- tag indicating INTEGER - * xx - length in octets - * xxxxxx - value - * - * Convert to BigInteger and back so we have the minimum length representation, as required. - * r and s are always non-negative. - * - * @since 0.8.7 - */ - private static byte[] sigBytesToASN1(byte[] sig) { - //System.out.println("pre TO asn1\n" + net.i2p.util.HexDump.dump(sig)); - ByteArrayOutputStream baos = new ByteArrayOutputStream(48); - baos.write(0x30); - baos.write(0); // length to be filled in below - - byte[] tmp = new byte[20]; - baos.write(2); - System.arraycopy(sig, 0, tmp, 0, 20); - BigInteger r = new BigInteger(1, tmp); - byte[] b = r.toByteArray(); - baos.write(b.length); - baos.write(b, 0, b.length); - - baos.write(2); - System.arraycopy(sig, 20, tmp, 0, 20); - BigInteger s = new BigInteger(1, tmp); - b = s.toByteArray(); - baos.write(b.length); - baos.write(b, 0, b.length); - byte[] rv = baos.toByteArray(); - rv[1] = (byte) (rv.length - 2); - //System.out.println("post TO asn1\n" + net.i2p.util.HexDump.dump(rv)); - return rv; - } - - /** - * See above. - * @since 0.8.7 - */ - private static byte[] aSN1ToSigBytes(byte[] asn) { - //System.out.println("pre from asn1\n" + net.i2p.util.HexDump.dump(asn)); - byte[] rv = new byte[40]; - int rlen = asn[3]; - if ((asn[4] & 0x80) != 0) - throw new IllegalArgumentException("R is negative"); - if (rlen > 21) - throw new IllegalArgumentException("R too big " + rlen); - else if (rlen == 21) { - System.arraycopy(asn, 5, rv, 0, 20); - } else - System.arraycopy(asn, 4, rv, 20 - rlen, rlen); - int slenloc = 25 + rlen - 20; - int slen = asn[slenloc]; - if ((asn[slenloc + 1] & 0x80) != 0) - throw new IllegalArgumentException("S is negative"); - if (slen > 21) - throw new IllegalArgumentException("S too big " + slen); - else if (slen == 21) { - System.arraycopy(asn, slenloc + 2, rv, 20, 20); - } else - System.arraycopy(asn, slenloc + 1, rv, 40 - slen, slen); - //System.out.println("post from asn1\n" + net.i2p.util.HexDump.dump(rv)); - return rv; + return SigUtil.fromJavaSig(jsig.sign(), SigType.DSA_SHA1); } //private static final int RUNS = 1000; diff --git a/core/java/src/net/i2p/crypto/ECConstants.java b/core/java/src/net/i2p/crypto/ECConstants.java new file mode 100644 index 000000000..39fef8986 --- /dev/null +++ b/core/java/src/net/i2p/crypto/ECConstants.java @@ -0,0 +1,308 @@ +package net.i2p.crypto; + +import java.lang.reflect.Constructor; +import java.math.BigInteger; +import java.security.AlgorithmParameters; +import java.security.AlgorithmParameterGenerator; +import java.security.GeneralSecurityException; +import java.security.Provider; +import java.security.Security; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.ECField; +import java.security.spec.ECFieldFp; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.EllipticCurve; + +import net.i2p.util.NativeBigInteger; + +/** + * Constants for elliptic curves, from NIST FIPS 186-4 (2013) / ANSI X9.62 + * + * @since 0.9.9 + */ +public class ECConstants { + + private static final boolean DEBUG = true; + + private static void log(String s) { + log(s, null); + } + + private static void log(String s, Throwable t) { + if (DEBUG) { + System.out.println("ECConstants: " + s); + if (t != null) + t.printStackTrace(); + } + } + + static { + if (Security.getProvider("BC") == null) { + try { + Class cls = Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider"); + Constructor con = cls.getConstructor(new Class[0]); + Provider bc = (Provider)con.newInstance(new Object[0]); + Security.addProvider(bc); + log("Added BC provider"); + } catch (Exception e) { + log("Unable to add BC provider", e); + } + } else { + log("BC provider already loaded"); + } + } + + + private static class ECParms { + public final String ps, ns, ss, bs, gxs, gys; + private static final BigInteger A = new NativeBigInteger("-3"); + private static final int H = 1; + + /** + * P and N in decimal, no spaces; + * Seed, B, Gx, Gy in hex, spaces allowed + */ + public ECParms(String pss, String nss, String sss, String bss, String gxss, String gyss) { + ps = pss; ns = nss; ss = sss; bs = bss; gxs = gxss; gys = gyss; + } + + public ECParameterSpec genSpec() { + BigInteger pb = new NativeBigInteger(ps); + BigInteger nb = new NativeBigInteger(ns); + BigInteger sb = new NativeBigInteger(ss.replace(" ", ""), 16); + BigInteger bb = new NativeBigInteger(bs.replace(" ", ""), 16); + BigInteger gxb = new NativeBigInteger(gxs.replace(" ", ""), 16); + BigInteger gyb = new NativeBigInteger(gys.replace(" ", ""), 16); + BigInteger ab = new NativeBigInteger(A.mod(pb)); + ECField field = new ECFieldFp(pb); + EllipticCurve curve = new EllipticCurve(field, ab, bb, sb.toByteArray()); + ECPoint g = new ECPoint(gxb, gyb); + return new ECParameterSpec(curve, g, nb, H); + } + } + + /* + + D.1.2 Curves over Prime Fields + + For each prime p, a pseudo-random curve + E : y**2 = x**3 -3x +b (mod p) + of prime order n is listed 4. (Thus, for these curves, the cofactor is always h = 1.) The following + parameters are given: + + The selection a a = -3 for the coefficient of x was made for reasons of efficiency; see IEEE Std 1363-2000. + + * The prime modulus p + * The order n + * The 160-bit input seed SEED to the SHA-1 based algorithm (i.e., the domain parameter + seed) + * The output c of the SHA-1 based algorithm + * The coefficient b (satisfying b**2 c = -27 (mod p)) + * The base point x coordinate G x + * The base point y coordinate G y + The integers p and n are given in decimal form; bit strings and field elements are given in + hexadecimal. + */ + + /* + D.1.2.1 Curve P-192 + + p= 6277101735386680763835789423207666416083908700390324961279 + n= 6277101735386680763835789423176059013767194773182842284081 + SEED = 3045ae6f c8422f64 ed579528 d38120ea e12196d5 + c= 3099d2bb bfcb2538 542dcd5f b078b6ef 5f3d6fe2 c745de65 + b= 64210519 e59c80e7 0fa7e9ab 72243049 feb8deec c146b9b1 + Gx= 188da80e b03090f6 7cbf20eb 43a18800 f4ff0afd 82ff1012 + Gy= 07192b95 ffc8da78 631011ed 6b24cdd5 73f977a1 1e794811 + */ + + private static final ECParms PARM_P192 = new ECParms( + // P N Seed B Gx Gy + "6277101735386680763835789423207666416083908700390324961279", + "6277101735386680763835789423176059013767194773182842284081", + "3045ae6f c8422f64 ed579528 d38120ea e12196d5", + "64210519 e59c80e7 0fa7e9ab 72243049 feb8deec c146b9b1", + "188da80e b03090f6 7cbf20eb 43a18800 f4ff0afd 82ff1012", + "07192b95 ffc8da78 631011ed 6b24cdd5 73f977a1 1e794811" + ); + + + /* + D.1.2.3 Curve P-256 + + p= + 1157920892103562487626974469494075735300861434152903141955 + 33631308867097853951 + n= + 115792089210356248762697446949407573529996955224135760342 + 422259061068512044369 + SEED = c49d3608 86e70493 6a6678e1 139d26b7 819f7e90 + c= + 7efba166 2985be94 03cb055c 75d4f7e0 ce8d84a9 c5114abc + af317768 0104fa0d + b= + 5ac635d8 aa3a93e7 b3ebbd55 769886bc 651d06b0 cc53b0f6 + 3bce3c3e 27d2604b + Gx= + 6b17d1f2 e12c4247 f8bce6e5 63a440f2 77037d81 2deb33a0 + f4a13945 d898c296 + Gy= + 4fe342e2 fe1a7f9b 8ee7eb4a 7c0f9e16 2bce3357 6b315ece + cbb64068 37bf51f5 + */ + + private static final ECParms PARM_P256 = new ECParms( + // P N Seed B Gx Gy + "1157920892103562487626974469494075735300861434152903141955" + + "33631308867097853951", + "115792089210356248762697446949407573529996955224135760342" + + "422259061068512044369", + "c49d3608 86e70493 6a6678e1 139d26b7 819f7e90", + "5ac635d8 aa3a93e7 b3ebbd55 769886bc 651d06b0 cc53b0f6" + + "3bce3c3e 27d2604b", + "6b17d1f2 e12c4247 f8bce6e5 63a440f2 77037d81 2deb33a0" + + "f4a13945 d898c296", + "4fe342e2 fe1a7f9b 8ee7eb4a 7c0f9e16 2bce3357 6b315ece" + + "cbb64068 37bf51f5" + ); + + /* + D.1.2.4 Curve P-384 + + p= + 3940200619639447921227904010014361380507973927046544666794 + 8293404245721771496870329047266088258938001861606973112319 + n= + 3940200619639447921227904010014361380507973927046544666794 + 6905279627659399113263569398956308152294913554433653942643 + SEED = a335926a a319a27a 1d00896a 6773a482 7acdac73 + c= + 79d1e655 f868f02f ff48dcde e14151dd b80643c1 406d0ca1 + 0dfe6fc5 2009540a 495e8042 ea5f744f 6e184667 cc722483 + b= + b3312fa7 e23ee7e4 988e056b e3f82d19 181d9c6e fe814112 + 0314088f 5013875a c656398d 8a2ed19d 2a85c8ed d3ec2aef + Gx= + aa87ca22 be8b0537 8eb1c71e f320ad74 6e1d3b62 8ba79b98 + 59f741e0 82542a38 5502f25d bf55296c 3a545e38 72760ab7 + G y= + 3617de4a 96262c6f 5d9e98bf 9292dc29 f8f41dbd 289a147c + e9da3113 b5f0b8c0 0a60b1ce 1d7e819d 7a431d7c 90ea0e5f + */ + + private static final ECParms PARM_P384 = new ECParms( + // P N Seed B Gx Gy + "3940200619639447921227904010014361380507973927046544666794" + + "8293404245721771496870329047266088258938001861606973112319", + "3940200619639447921227904010014361380507973927046544666794" + + "6905279627659399113263569398956308152294913554433653942643", + "a335926a a319a27a 1d00896a 6773a482 7acdac73", + "b3312fa7 e23ee7e4 988e056b e3f82d19 181d9c6e fe814112" + + "0314088f 5013875a c656398d 8a2ed19d 2a85c8ed d3ec2aef", + "aa87ca22 be8b0537 8eb1c71e f320ad74 6e1d3b62 8ba79b98" + + "59f741e0 82542a38 5502f25d bf55296c 3a545e38 72760ab7", + "3617de4a 96262c6f 5d9e98bf 9292dc29 f8f41dbd 289a147c" + + "e9da3113 b5f0b8c0 0a60b1ce 1d7e819d 7a431d7c 90ea0e5f" + ); + + /* + D.1.2.5 Curve P-521 + + p= + 686479766013060971498190079908139321726943530014330540939 + 446345918554318339765605212255964066145455497729631139148 + 0858037121987999716643812574028291115057151 + n= + 686479766013060971498190079908139321726943530014330540939 + 446345918554318339765539424505774633321719753296399637136 + 3321113864768612440380340372808892707005449 + SEED = d09e8800 291cb853 96cc6717 393284aa a0da64ba + c= + 0b4 8bfa5f42 0a349495 39d2bdfc 264eeeeb 077688e4 + 4fbf0ad8 f6d0edb3 7bd6b533 28100051 8e19f1b9 ffbe0fe9 + ed8a3c22 00b8f875 e523868c 70c1e5bf 55bad637 + b= + 051 953eb961 8e1c9a1f 929a21a0 b68540ee a2da725b + 99b315f3 b8b48991 8ef109e1 56193951 ec7e937b 1652c0bd + 3bb1bf07 3573df88 3d2c34f1 ef451fd4 6b503f00 + Gx= + c6 858e06b7 0404e9cd 9e3ecb66 2395b442 9c648139 + 053fb521 f828af60 6b4d3dba a14b5e77 efe75928 fe1dc127 + a2ffa8de 3348b3c1 856a429b f97e7e31 c2e5bd66 + Gy= + 118 39296a78 9a3bc004 5c8a5fb4 2c7d1bd9 98f54449 + 579b4468 17afbd17 273e662c 97ee7299 5ef42640 c550b901 + 3fad0761 353c7086 a272c240 88be9476 9fd16650 + */ + + private static final ECParms PARM_P521 = new ECParms( + "686479766013060971498190079908139321726943530014330540939" + + "446345918554318339765605212255964066145455497729631139148" + + "0858037121987999716643812574028291115057151", + "686479766013060971498190079908139321726943530014330540939" + + "446345918554318339765539424505774633321719753296399637136" + + "3321113864768612440380340372808892707005449", + "d09e8800 291cb853 96cc6717 393284aa a0da64ba", + "051 953eb961 8e1c9a1f 929a21a0 b68540ee a2da725b" + + "99b315f3 b8b48991 8ef109e1 56193951 ec7e937b 1652c0bd" + + "3bb1bf07 3573df88 3d2c34f1 ef451fd4 6b503f00", + "c6 858e06b7 0404e9cd 9e3ecb66 2395b442 9c648139" + + "053fb521 f828af60 6b4d3dba a14b5e77 efe75928 fe1dc127" + + "a2ffa8de 3348b3c1 856a429b f97e7e31 c2e5bd66", + "118 39296a78 9a3bc004 5c8a5fb4 2c7d1bd9 98f54449" + + "579b4468 17afbd17 273e662c 97ee7299 5ef42640 c550b901" + + "3fad0761 353c7086 a272c240 88be9476 9fd16650" + ); + + /** + * Generate a spec from a curve name + * @return null iffail + */ + private static ECParameterSpec genSpec(String name) { + // convert the ECGenParameterSpecs to ECParameterSpecs for several reasons: + // 1) to check availability + // 2) efficiency + // 3) SigUtil must cast the AlgorithmParameterSpec to a ECParameterSpec + // to convert a I2P key to a Java key. Sadly, a ECGenParameterSpec + // is not a ECParameterSpec. + try { + AlgorithmParameters ap = AlgorithmParameters.getInstance("EC"); + ECGenParameterSpec ecgps = new ECGenParameterSpec(name); + ap.init(ecgps); + ECParameterSpec rv = ap.getParameterSpec(ECParameterSpec.class); + log("Named curve " + name + " loaded"); + return rv; + } catch (Exception e) { + log("Named curve " + name + " is not available", e); + return null; + } + } + + /** + * Tries curve name1, then name2, then creates new from parms. + * @return null if all fail + */ + private static ECParameterSpec genSpec(String name1, String name2, ECParms parms) { + ECParameterSpec rv = genSpec(name1); + if (rv == null) { + rv = genSpec(name2); + if (rv == null) { + rv = parms.genSpec(); + if (rv != null) + log("Curve " + name2 + " created"); + } + } + return rv; + } + + // standard curve names + // first is OpenJDK 6/7 + // second is BC + public static final ECParameterSpec P192_SPEC = genSpec("secp192r1", "P-192", PARM_P192); + public static final ECParameterSpec P256_SPEC = genSpec("secp256r1", "P-256", PARM_P256); + public static final ECParameterSpec P384_SPEC = genSpec("secp384r1", "P-384", PARM_P384); + public static final ECParameterSpec P521_SPEC = genSpec("secp521r1", "P-521", PARM_P521); + +} diff --git a/core/java/src/net/i2p/crypto/KeyGenerator.java b/core/java/src/net/i2p/crypto/KeyGenerator.java index c6d459cfc..53148ac50 100644 --- a/core/java/src/net/i2p/crypto/KeyGenerator.java +++ b/core/java/src/net/i2p/crypto/KeyGenerator.java @@ -10,6 +10,13 @@ package net.i2p.crypto; */ import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECPoint; import net.i2p.I2PAppContext; import net.i2p.data.Hash; @@ -21,6 +28,12 @@ import net.i2p.data.SigningPublicKey; import net.i2p.data.SimpleDataStructure; import net.i2p.util.NativeBigInteger; +// main() +import net.i2p.data.DataHelper; +import net.i2p.data.Signature; +import net.i2p.util.Clock; +import net.i2p.util.RandomSource; + /** Define a way of generating asymmetrical key pairs as well as symmetrical keys * @author jrandom */ @@ -100,14 +113,16 @@ public class KeyGenerator { SimpleDataStructure[] keys = new SimpleDataStructure[2]; keys[0] = new PublicKey(); keys[1] = new PrivateKey(); - byte[] k0 = aalpha.toByteArray(); - byte[] k1 = a.toByteArray(); // bigInteger.toByteArray returns SIGNED integers, but since they'return positive, // signed two's complement is the same as unsigned - keys[0].setData(padBuffer(k0, PublicKey.KEYSIZE_BYTES)); - keys[1].setData(padBuffer(k1, PrivateKey.KEYSIZE_BYTES)); + try { + keys[0].setData(SigUtil.rectify(aalpha, PublicKey.KEYSIZE_BYTES)); + keys[1].setData(SigUtil.rectify(a, PrivateKey.KEYSIZE_BYTES)); + } catch (InvalidKeyException ike) { + throw new IllegalArgumentException(ike); + } return keys; } @@ -120,13 +135,18 @@ public class KeyGenerator { BigInteger a = new NativeBigInteger(1, priv.toByteArray()); BigInteger aalpha = CryptoConstants.elgg.modPow(a, CryptoConstants.elgp); PublicKey pub = new PublicKey(); - byte [] pubBytes = aalpha.toByteArray(); - pub.setData(padBuffer(pubBytes, PublicKey.KEYSIZE_BYTES)); + try { + pub.setData(SigUtil.rectify(aalpha, PublicKey.KEYSIZE_BYTES)); + } catch (InvalidKeyException ike) { + throw new IllegalArgumentException(ike); + } return pub; } /** Generate a pair of DSA keys, where index 0 is a SigningPublicKey, and - * index 1 is a SigningPrivateKey + * index 1 is a SigningPrivateKey. + * DSA-SHA1 only. + * * @return pair of keys */ public Object[] generateSigningKeypair() { @@ -134,6 +154,8 @@ public class KeyGenerator { } /** + * DSA-SHA1 only. + * * Same as above but different return type * @since 0.8.7 */ @@ -149,15 +171,36 @@ public class KeyGenerator { BigInteger y = CryptoConstants.dsag.modPow(x, CryptoConstants.dsap); keys[0] = new SigningPublicKey(); keys[1] = new SigningPrivateKey(); - byte k0[] = padBuffer(y.toByteArray(), SigningPublicKey.KEYSIZE_BYTES); - byte k1[] = padBuffer(x.toByteArray(), SigningPrivateKey.KEYSIZE_BYTES); - - keys[0].setData(k0); - keys[1].setData(k1); + try { + keys[0].setData(SigUtil.rectify(y, SigningPublicKey.KEYSIZE_BYTES)); + keys[1].setData(SigUtil.rectify(x, SigningPrivateKey.KEYSIZE_BYTES)); + } catch (InvalidKeyException ike) { + throw new IllegalStateException(ike); + } return keys; } - /** Convert a SigningPrivateKey to a SigningPublicKey + /** + * Generic signature type, supports DSA and ECDSA + * @since 0.9.9 + */ + public SimpleDataStructure[] generateSigningKeys(SigType type) throws GeneralSecurityException { + if (type == SigType.DSA_SHA1) + return generateSigningKeys(); + KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC"); + kpg.initialize(type.getParams(), _context.random()); + KeyPair kp = kpg.generateKeyPair(); + ECPublicKey pubkey = (ECPublicKey) kp.getPublic(); + ECPrivateKey privkey = (ECPrivateKey) kp.getPrivate(); + SimpleDataStructure[] keys = new SimpleDataStructure[2]; + keys[0] = SigUtil.fromJavaKey(pubkey, type); + keys[1] = SigUtil.fromJavaKey(privkey, type); + return keys; + } + + /** Convert a SigningPrivateKey to a SigningPublicKey. + * DSA-SHA1 only. + * * @param priv a SigningPrivateKey object * @return a SigningPublicKey object */ @@ -165,27 +208,61 @@ public class KeyGenerator { BigInteger x = new NativeBigInteger(1, priv.toByteArray()); BigInteger y = CryptoConstants.dsag.modPow(x, CryptoConstants.dsap); SigningPublicKey pub = new SigningPublicKey(); - byte [] pubBytes = padBuffer(y.toByteArray(), SigningPublicKey.KEYSIZE_BYTES); - pub.setData(pubBytes); + try { + pub.setData(SigUtil.rectify(y, SigningPublicKey.KEYSIZE_BYTES)); + } catch (InvalidKeyException ike) { + throw new IllegalArgumentException(ike); + } return pub; } - /** - * Pad the buffer w/ leading 0s or trim off leading bits so the result is the - * given length. - */ - private final static byte[] padBuffer(byte src[], int length) { - byte buf[] = new byte[length]; + public static void main(String args[]) { + try { + main2(args); + } catch (Exception e) { + e.printStackTrace(); + } + } - if (src.length > buf.length) // extra bits, chop leading bits - System.arraycopy(src, src.length - buf.length, buf, 0, buf.length); - else if (src.length < buf.length) // short bits, padd w/ 0s - System.arraycopy(src, 0, buf, buf.length - src.length, src.length); - else - // eq - System.arraycopy(src, 0, buf, 0, buf.length); + public static void main2(String args[]) { + RandomSource.getInstance().nextBoolean(); + try { Thread.sleep(1000); } catch (InterruptedException ie) {} + int runs = 500; // warmup + for (int j = 0; j < 2; j++) { + for (int i = 0; i <= 100; i++) { + SigType type = SigType.getByCode(i); + if (type == null) + break; + try { + System.out.println("Testing " + type); + testSig(type, runs); + } catch (Exception e) { + System.out.println("error testing " + type); + e.printStackTrace(); + } + } + runs = 2000; + } + } - return buf; + private static void testSig(SigType type, int runs) throws GeneralSecurityException { + byte src[] = new byte[512]; + long time = 0; + SimpleDataStructure keys[] = KeyGenerator.getInstance().generateSigningKeys(type); + //System.out.println("pubkey " + keys[0]); + //System.out.println("privkey " + keys[1]); + for (int i = 0; i < runs; i++) { + RandomSource.getInstance().nextBytes(src); + long start = System.nanoTime(); + Signature sig = DSAEngine.getInstance().sign(src, (SigningPrivateKey) keys[1]); + boolean ok = DSAEngine.getInstance().verifySignature(sig, src, (SigningPublicKey) keys[0]); + long end = System.nanoTime(); + time += end - start; + if (!ok) + throw new GeneralSecurityException(type + " V(S(data)) fail"); + } + time /= 1000*1000; + System.out.println("Signing Keygen " + runs + " times: " + time + " ms = " + (((double) time) / runs) + " each"); } /****** diff --git a/core/java/src/net/i2p/crypto/SigType.java b/core/java/src/net/i2p/crypto/SigType.java index fbd2d7f46..8405bcb1d 100644 --- a/core/java/src/net/i2p/crypto/SigType.java +++ b/core/java/src/net/i2p/crypto/SigType.java @@ -23,15 +23,27 @@ public enum SigType { * Pubkey 128 bytes; privkey 20 bytes; hash 20 bytes; sig 40 bytes * @since 0.9.8 */ - DSA_SHA1(0, 128, 20, 20, 40, "SHA-1", "SHA1withDSA", null), + DSA_SHA1(0, 128, 20, 20, 40, "SHA-1", "SHA1withDSA", CryptoConstants.DSA_SHA1_SPEC), /** Pubkey 48 bytes; privkey 24 bytes; hash 20 bytes; sig 48 bytes */ - ECDSA_SHA1_P192(1, 48, 24, 20, 48, "SHA-1", "SHA1withECDSA", null), + ECDSA_SHA1_P192(1, 48, 24, 20, 48, "SHA-1", "SHA1withECDSA", ECConstants.P192_SPEC), /** Pubkey 64 bytes; privkey 32 bytes; hash 32 bytes; sig 64 bytes */ - ECDSA_SHA256_P256(2, 64, 32, 32, 64, "SHA-256", "SHA256withECDSA", null), + ECDSA_SHA256_P256(2, 64, 32, 32, 64, "SHA-256", "SHA256withECDSA", ECConstants.P256_SPEC), /** Pubkey 96 bytes; privkey 48 bytes; hash 48 bytes; sig 96 bytes */ - ECDSA_SHA384_P384(3, 96, 48, 48, 96, "SHA-384", "SHA384withECDSA", null), + ECDSA_SHA384_P384(3, 96, 48, 48, 96, "SHA-384", "SHA384withECDSA", ECConstants.P384_SPEC), /** Pubkey 132 bytes; privkey 66 bytes; hash 64 bytes; sig 132 bytes */ - ECDSA_SHA512_P521(4, 132, 66, 64, 132, "SHA-512", "SHA512withECDSA", null), + ECDSA_SHA512_P521(4, 132, 66, 64, 132, "SHA-512", "SHA512withECDSA", ECConstants.P521_SPEC), + + // TESTING.................... + + ECDSA_SHA256_P192(5, 48, 24, 20, 48, "SHA-1", "SHA256withECDSA", ECConstants.P192_SPEC), + ECDSA_SHA256_P384(6, 96, 48, 32, 96, "SHA-256", "SHA256withECDSA", ECConstants.P384_SPEC), + ECDSA_SHA256_P521(7, 132, 66, 32, 132, "SHA-256", "SHA256withECDSA", ECConstants.P521_SPEC), + + ECDSA_SHA384_P256(8, 64, 32, 48, 64, "SHA-384", "SHA384withECDSA", ECConstants.P256_SPEC), + ECDSA_SHA384_P521(9, 132, 66, 48, 132, "SHA-384", "SHA384withECDSA", ECConstants.P521_SPEC), + + ECDSA_SHA512_P256(10, 64, 32, 64, 64, "SHA-512", "SHA512withECDSA", ECConstants.P256_SPEC), + ECDSA_SHA512_P384(11, 96, 48, 64, 96, "SHA-512", "SHA512withECDSA", ECConstants.P384_SPEC), //MD5 //ELGAMAL_SHA256 diff --git a/core/java/src/net/i2p/crypto/SigUtil.java b/core/java/src/net/i2p/crypto/SigUtil.java new file mode 100644 index 000000000..28448c1b3 --- /dev/null +++ b/core/java/src/net/i2p/crypto/SigUtil.java @@ -0,0 +1,293 @@ +package net.i2p.crypto; + +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SignatureException; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECField; +import java.security.spec.ECFieldFp; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.ECPoint; +import java.security.spec.EllipticCurve; + +import net.i2p.data.Signature; +import net.i2p.data.SigningPrivateKey; +import net.i2p.data.SigningPublicKey; + + +/** + * Utilities for Signing keys and Signatures + * + * @since 0.9.9 + */ +class SigUtil { + + /** + * @return JAVA key! + */ + public static PublicKey toJavaKey(SigningPublicKey pk) + throws GeneralSecurityException { + if (pk.getType() == SigType.DSA_SHA1) + throw new UnsupportedOperationException(); + else + return toJavaECKey(pk); + } + + /** + * @return JAVA key! + */ + public static PrivateKey toJavaKey(SigningPrivateKey pk) + throws GeneralSecurityException { + if (pk.getType() == SigType.DSA_SHA1) + throw new UnsupportedOperationException(); + else + return toJavaECKey(pk); + } + + /** + * @param pk JAVA key! + */ + public static SigningPublicKey fromJavaKey(PublicKey pk, SigType type) + throws GeneralSecurityException { + if (type == SigType.DSA_SHA1) + throw new UnsupportedOperationException(); + else + return fromJavaKey((ECPublicKey) pk, type); + } + + /** + * @param pk JAVA key! + */ + public static SigningPrivateKey fromJavaKey(PrivateKey pk, SigType type) + throws GeneralSecurityException { + if (type == SigType.DSA_SHA1) + throw new UnsupportedOperationException(); + else + return fromJavaKey((ECPrivateKey) pk, type); + } + + public static ECPublicKey toJavaECKey(SigningPublicKey pk) + throws GeneralSecurityException { + SigType type = pk.getType(); + int len = type.getPubkeyLen(); + int sublen = len / 2; + byte[] b = pk.getData(); + byte[] bx = new byte[sublen]; + byte[] by = new byte[sublen]; + System.arraycopy(b, 0, bx, 0, sublen); + System.arraycopy(b, sublen, by, 0, sublen); + BigInteger x = new BigInteger(1, bx); + BigInteger y = new BigInteger(1, by); + ECPoint w = new ECPoint(x, y); + // see ECConstants re: casting + ECPublicKeySpec ks = new ECPublicKeySpec(w, (ECParameterSpec) type.getParams()); + KeyFactory kf = KeyFactory.getInstance("EC"); + return (ECPublicKey) kf.generatePublic(ks); + } + + public static ECPrivateKey toJavaECKey(SigningPrivateKey pk) + throws GeneralSecurityException { + SigType type = pk.getType(); + int len = type.getPubkeyLen(); + int sublen = len / 2; + byte[] b = pk.getData(); + BigInteger s = new BigInteger(1, b); + // see ECConstants re: casting + ECPrivateKeySpec ks = new ECPrivateKeySpec(s, (ECParameterSpec) type.getParams()); + KeyFactory kf = KeyFactory.getInstance("EC"); + return (ECPrivateKey) kf.generatePrivate(ks); + } + + public static SigningPublicKey fromJavaKey(ECPublicKey pk, SigType type) + throws GeneralSecurityException { + ECPoint w = pk.getW(); + BigInteger x = w.getAffineX(); + BigInteger y = w.getAffineY(); + int len = type.getPubkeyLen(); + int sublen = len / 2; + byte[] b = new byte[len]; + byte[] bx = rectify(x, sublen); + byte[] by = rectify(y, sublen); + System.arraycopy(bx, 0, b, 0, sublen); + System.arraycopy(by, 0, b, sublen, sublen); + return new SigningPublicKey(type, b); + } + + public static SigningPrivateKey fromJavaKey(ECPrivateKey pk, SigType type) + throws GeneralSecurityException { + BigInteger s = pk.getS(); + int len = type.getPrivkeyLen(); + byte[] bs = rectify(s, len); + return new SigningPrivateKey(type, bs); + } + + /** + * @return ASN.1 representation + */ + public static byte[] toJavaSig(Signature sig) { + return sigBytesToASN1(sig.getData()); + } + + /** + * @param asn ASN.1 representation + * @return a Signature with SigType type + */ + public static Signature fromJavaSig(byte[] asn, SigType type) + throws SignatureException { + return new Signature(type, aSN1ToSigBytes(asn, type.getSigLen())); + } + + /** + * @param bi non-negative + * @return array of exactly len bytes + */ + public static byte[] rectify(BigInteger bi, int len) + throws InvalidKeyException { + byte[] b = bi.toByteArray(); + if (b.length == len) { + // just right + return b; + } + if (b.length > len + 1) + throw new InvalidKeyException("key too big (" + b.length + ") max is " + (len + 1)); + byte[] rv = new byte[len]; + if (b.length == 0) + return rv; + if ((b[0] & 0x80) != 0) + throw new InvalidKeyException("negative"); + if (b.length > len) { + // leading 0 byte + if (b[0] != 0) + throw new InvalidKeyException("key too big (" + b.length + ") max is " + len); + System.arraycopy(b, 1, rv, 0, len); + } else { + // smaller + System.arraycopy(b, 0, rv, len - b.length, b.length); + } + return rv; + } + + /** + * http://download.oracle.com/javase/1.5.0/docs/guide/security/CryptoSpec.html + * Signature Format ASN.1 sequence of two INTEGER values: r and s, in that order: + * SEQUENCE ::= { r INTEGER, s INTEGER } + * + * http://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One + * 30 -- tag indicating SEQUENCE + * xx - length in octets + * + * 02 -- tag indicating INTEGER + * xx - length in octets + * xxxxxx - value + * + * Convert to BigInteger and back so we have the minimum length representation, as required. + * r and s are always non-negative. + * + * Only supports sigs up to about 252 bytes. See code to fix BER encoding for this before you + * add a SigType with bigger signatures. + * + * @throws IllegalArgumentException if too big + * @since 0.8.7, moved to SigUtil in 0.9.9 + */ + private static byte[] sigBytesToASN1(byte[] sig) { + //System.out.println("pre TO asn1\n" + net.i2p.util.HexDump.dump(sig)); + int len = sig.length; + int sublen = len / 2; + byte[] tmp = new byte[sublen]; + + System.arraycopy(sig, 0, tmp, 0, sublen); + BigInteger r = new BigInteger(1, tmp); + byte[] rb = r.toByteArray(); + if (rb.length > 127) + throw new IllegalArgumentException("FIXME R length > 127"); + System.arraycopy(sig, sublen, tmp, 0, sublen); + BigInteger s = new BigInteger(1, tmp); + byte[] sb = s.toByteArray(); + if (sb.length > 127) + throw new IllegalArgumentException("FIXME S length > 127"); + int seqlen = rb.length + sb.length + 4; + if (seqlen > 255) + throw new IllegalArgumentException("FIXME seq length > 255"); + int totlen = seqlen + 2; + if (seqlen > 127) + totlen++; + byte[] rv = new byte[totlen]; + int idx = 0; + + rv[idx++] = 0x30; + if (seqlen > 127) + rv[idx++] =(byte) 0x81; + rv[idx++] = (byte) seqlen; + + rv[idx++] = 0x02; + rv[idx++] = (byte) rb.length; + System.arraycopy(rb, 0, rv, idx, rb.length); + idx += rb.length; + + rv[idx++] = 0x02; + rv[idx++] = (byte) sb.length; + System.arraycopy(sb, 0, rv, idx, sb.length); + + //System.out.println("post TO asn1\n" + net.i2p.util.HexDump.dump(rv)); + return rv; + } + + /** + * See above. + * Only supports sigs up to about 252 bytes. See code to fix BER encoding for bigger than that. + * + * @return len bytes + * @since 0.8.7, moved to SigUtil in 0.9.9 + */ + private static byte[] aSN1ToSigBytes(byte[] asn, int len) + throws SignatureException { + //System.out.println("pre from asn1 len=" + len + "\n" + net.i2p.util.HexDump.dump(asn)); + if (asn[0] != 0x30) + throw new SignatureException("asn[0] = " + (asn[0] & 0xff)); + // handles total len > 127 + int idx = 2; + if ((asn[1] & 0x80) != 0) + idx += asn[1] & 0x7f; + if (asn[idx] != 0x02) + throw new SignatureException("asn[2] = " + (asn[idx] & 0xff)); + byte[] rv = new byte[len]; + int sublen = len / 2; + int rlen = asn[++idx]; + if ((rlen & 0x80) != 0) + throw new SignatureException("FIXME R length > 127"); + if ((asn[++idx] & 0x80) != 0) + throw new SignatureException("R is negative"); + if (rlen > sublen + 1) + throw new SignatureException("R too big " + rlen); + if (rlen == sublen + 1) + System.arraycopy(asn, idx + 1, rv, 0, sublen); + else + System.arraycopy(asn, idx, rv, sublen - rlen, rlen); + idx += rlen; + int slenloc = idx + 1; + if (asn[idx] != 0x02) + throw new SignatureException("asn[s] = " + (asn[idx] & 0xff)); + int slen = asn[slenloc]; + if ((slen & 0x80) != 0) + throw new SignatureException("FIXME S length > 127"); + if ((asn[slenloc + 1] & 0x80) != 0) + throw new SignatureException("S is negative"); + if (slen > sublen + 1) + throw new SignatureException("S too big " + slen); + if (slen == sublen + 1) + System.arraycopy(asn, slenloc + 2, rv, sublen, sublen); + else + System.arraycopy(asn, slenloc + 1, rv, len - slen, slen); + //System.out.println("post from asn1\n" + net.i2p.util.HexDump.dump(rv)); + return rv; + } + +} From d27c4653717e17710170f8090050d3924b39cec4 Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 6 Sep 2013 13:30:47 +0000 Subject: [PATCH 02/11] - KeyPairGen: Catch ProviderException, fallback to BC provider --- core/java/src/net/i2p/crypto/ECConstants.java | 10 ++++- .../java/src/net/i2p/crypto/KeyGenerator.java | 39 ++++++++++++++++++- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/core/java/src/net/i2p/crypto/ECConstants.java b/core/java/src/net/i2p/crypto/ECConstants.java index 39fef8986..49f8c5d7b 100644 --- a/core/java/src/net/i2p/crypto/ECConstants.java +++ b/core/java/src/net/i2p/crypto/ECConstants.java @@ -22,7 +22,7 @@ import net.i2p.util.NativeBigInteger; * * @since 0.9.9 */ -public class ECConstants { +class ECConstants { private static final boolean DEBUG = true; @@ -38,7 +38,10 @@ public class ECConstants { } } + private static final boolean BC_AVAILABLE; + static { + boolean loaded; if (Security.getProvider("BC") == null) { try { Class cls = Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider"); @@ -46,14 +49,19 @@ public class ECConstants { Provider bc = (Provider)con.newInstance(new Object[0]); Security.addProvider(bc); log("Added BC provider"); + loaded = true; } catch (Exception e) { log("Unable to add BC provider", e); + loaded = false; } } else { log("BC provider already loaded"); + loaded = true; } + BC_AVAILABLE = true; } + public static boolean isBCAvailable() { return BC_AVAILABLE; } private static class ECParms { public final String ps, ns, ss, bs, gxs, gys; diff --git a/core/java/src/net/i2p/crypto/KeyGenerator.java b/core/java/src/net/i2p/crypto/KeyGenerator.java index 53148ac50..124801a4b 100644 --- a/core/java/src/net/i2p/crypto/KeyGenerator.java +++ b/core/java/src/net/i2p/crypto/KeyGenerator.java @@ -14,6 +14,7 @@ import java.security.GeneralSecurityException; import java.security.InvalidKeyException; import java.security.KeyPair; import java.security.KeyPairGenerator; +import java.security.ProviderException; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.security.spec.ECPoint; @@ -26,6 +27,7 @@ import net.i2p.data.SessionKey; import net.i2p.data.SigningPrivateKey; import net.i2p.data.SigningPublicKey; import net.i2p.data.SimpleDataStructure; +import net.i2p.util.Log; import net.i2p.util.NativeBigInteger; // main() @@ -188,8 +190,41 @@ public class KeyGenerator { if (type == SigType.DSA_SHA1) return generateSigningKeys(); KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC"); - kpg.initialize(type.getParams(), _context.random()); - KeyPair kp = kpg.generateKeyPair(); + KeyPair kp; + try { + kpg.initialize(type.getParams(), _context.random()); + kp = kpg.generateKeyPair(); + } catch (ProviderException pe) { + // This is a RuntimeException, thx Sun + // Fails for P-192 only, on Ubuntu + Log log = _context.logManager().getLog(KeyGenerator.class); + String pname = kpg.getProvider().getName(); + if ("BC".equals(pname)) { + if (log.shouldLog(Log.WARN)) + log.warn("BC KPG failed", pe); + throw new GeneralSecurityException("BC KPG", pe); + } + if (!ECConstants.isBCAvailable()) + throw new GeneralSecurityException(pname + " KPG", pe); + if (log.shouldLog(Log.WARN)) + log.warn(pname + " KPG failed, trying BC", pe); + try { + kpg = KeyPairGenerator.getInstance("EC", "BC"); + kpg.initialize(type.getParams(), _context.random()); + kp = kpg.generateKeyPair(); + } catch (ProviderException pe2) { + if (log.shouldLog(Log.WARN)) + log.warn("BC KPG failed too", pe2); + // throw original exception + throw new GeneralSecurityException(pname + " KPG", pe); + } catch (GeneralSecurityException gse) { + if (log.shouldLog(Log.WARN)) + log.warn("BC KPG failed too", gse); + gse.printStackTrace(); + // throw original exception + throw new GeneralSecurityException(pname + " KPG", pe); + } + } ECPublicKey pubkey = (ECPublicKey) kp.getPublic(); ECPrivateKey privkey = (ECPrivateKey) kp.getPrivate(); SimpleDataStructure[] keys = new SimpleDataStructure[2]; From 928b4bbbe5a313deae65e716ee57227123cf47d0 Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 6 Sep 2013 13:53:15 +0000 Subject: [PATCH 03/11] - genSpec: fallback to BC provider --- core/java/src/net/i2p/crypto/ECConstants.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/core/java/src/net/i2p/crypto/ECConstants.java b/core/java/src/net/i2p/crypto/ECConstants.java index 49f8c5d7b..4b202a814 100644 --- a/core/java/src/net/i2p/crypto/ECConstants.java +++ b/core/java/src/net/i2p/crypto/ECConstants.java @@ -266,7 +266,7 @@ class ECConstants { /** * Generate a spec from a curve name - * @return null iffail + * @return null if fail */ private static ECParameterSpec genSpec(String name) { // convert the ECGenParameterSpecs to ECParameterSpecs for several reasons: @@ -276,7 +276,18 @@ class ECConstants { // to convert a I2P key to a Java key. Sadly, a ECGenParameterSpec // is not a ECParameterSpec. try { - AlgorithmParameters ap = AlgorithmParameters.getInstance("EC"); + AlgorithmParameters ap; + try { + ap = AlgorithmParameters.getInstance("EC"); + } catch (Exception e) { + if (BC_AVAILABLE) { + log("Named curve " + name + " is not available, trying BC", e); + ap = AlgorithmParameters.getInstance("EC", "BC"); + log("Fallback to BC worked for named curve " + name); + } else { + throw e; + } + } ECGenParameterSpec ecgps = new ECGenParameterSpec(name); ap.init(ecgps); ECParameterSpec rv = ap.getParameterSpec(ECParameterSpec.class); From c4f97ed65edf9519501d360f041e52d6eb4980d7 Mon Sep 17 00:00:00 2001 From: zzz Date: Sat, 7 Sep 2013 18:12:19 +0000 Subject: [PATCH 04/11] - DSAEngine: Start of support for signing hashes with other SigTypes - KeyGenerator: Measure sign and verify separately in test - SU3File: Start to support other SigTypes - SigType: Add getHashInstance(), fix hash length for ECDSA_SHA256_P192 - SHA1Hash: Add no-arg constructor --- core/java/src/net/i2p/crypto/DSAEngine.java | 57 ++++++++++- .../java/src/net/i2p/crypto/KeyGenerator.java | 14 ++- core/java/src/net/i2p/crypto/SHA1Hash.java | 5 + core/java/src/net/i2p/crypto/SU3File.java | 98 +++++++++++++++---- core/java/src/net/i2p/crypto/SigType.java | 24 ++++- 5 files changed, 174 insertions(+), 24 deletions(-) diff --git a/core/java/src/net/i2p/crypto/DSAEngine.java b/core/java/src/net/i2p/crypto/DSAEngine.java index 9c7718631..6cad15bc9 100644 --- a/core/java/src/net/i2p/crypto/DSAEngine.java +++ b/core/java/src/net/i2p/crypto/DSAEngine.java @@ -155,6 +155,31 @@ public class DSAEngine { return verifySig(signature, hash, verifyingKey); } + /** + * Generic signature type. + * + * @param hash SHA1Hash, Hash, Hash384, or Hash512 + * @since 0.9.9 + */ + public boolean verifySignature(Signature signature, SimpleDataStructure hash, SigningPublicKey verifyingKey) { + SigType type = signature.getType(); + if (type != verifyingKey.getType()) + throw new IllegalArgumentException("type mismatch sig=" + type + " key=" + verifyingKey.getType()); + int hashlen = type.getHashLen(); + if (hash.length() != hashlen) + throw new IllegalArgumentException("type mismatch hash=" + hash.getClass() + " sig=" + type); + if (type == SigType.DSA_SHA1) + return verifySig(signature, hash, verifyingKey); + // FIXME hash of hash + try { + return altVerifySig(signature, hash.getData(), verifyingKey); + } catch (GeneralSecurityException gse) { + if (_log.shouldLog(Log.WARN)) + _log.warn(type + " Sig Verify Fail", gse); + return false; + } + } + /** * Verify using DSA-SHA1 or Syndie DSA-SHA256 ONLY. * @param hash either a Hash or a SHA1Hash @@ -286,6 +311,30 @@ public class DSAEngine { return signIt(hash, signingKey); } + /** + * Generic signature type. + * + * @param hash SHA1Hash, Hash, Hash384, or Hash512 + * @return null on error + * @since 0.9.9 + */ + public Signature sign(SimpleDataStructure hash, SigningPrivateKey signingKey) { + SigType type = signingKey.getType(); + int hashlen = type.getHashLen(); + if (hash.length() != hashlen) + throw new IllegalArgumentException("type mismatch hash=" + hash.getClass() + " key=" + type); + if (type == SigType.DSA_SHA1) + return signIt(hash, signingKey); + // FIXME hash of hash + try { + return altSign(hash.getData(), signingKey); + } catch (GeneralSecurityException gse) { + if (_log.shouldLog(Log.WARN)) + _log.warn(type + " Sign Fail", gse); + return null; + } + } + /** * Sign using DSA-SHA1 or Syndie DSA-SHA256 ONLY. * @@ -334,6 +383,9 @@ public class DSAEngine { for (int i = 0; i < 20; i++) { out[i] = rbytes[i + 1]; } + } else if (rbytes.length > 21) { + _log.error("Bad R length " + rbytes.length); + return null; } else { if (_log.shouldLog(Log.DEBUG)) _log.debug("Using short rbytes.length [" + rbytes.length + "]"); //System.arraycopy(rbytes, 0, out, 20 - rbytes.length, rbytes.length); @@ -350,6 +402,9 @@ public class DSAEngine { for (int i = 0; i < 20; i++) { out[i + 20] = sbytes[i + 1]; } + } else if (sbytes.length > 21) { + _log.error("Bad S length " + sbytes.length); + return null; } else { if (_log.shouldLog(Log.DEBUG)) _log.debug("Using short sbytes.length [" + sbytes.length + "]"); //System.arraycopy(sbytes, 0, out, 40 - sbytes.length, sbytes.length); @@ -405,7 +460,7 @@ public class DSAEngine { throws GeneralSecurityException { SigType type = signature.getType(); if (type != verifyingKey.getType()) - throw new IllegalArgumentException("type mismatch sig=" + signature.getType() + " key=" + verifyingKey.getType()); + throw new IllegalArgumentException("type mismatch sig=" + type + " key=" + verifyingKey.getType()); if (type == SigType.DSA_SHA1) return altVerifySigSHA1(signature, data, verifyingKey); diff --git a/core/java/src/net/i2p/crypto/KeyGenerator.java b/core/java/src/net/i2p/crypto/KeyGenerator.java index 124801a4b..3c94a9a92 100644 --- a/core/java/src/net/i2p/crypto/KeyGenerator.java +++ b/core/java/src/net/i2p/crypto/KeyGenerator.java @@ -282,7 +282,8 @@ public class KeyGenerator { private static void testSig(SigType type, int runs) throws GeneralSecurityException { byte src[] = new byte[512]; - long time = 0; + long stime = 0; + long vtime = 0; SimpleDataStructure keys[] = KeyGenerator.getInstance().generateSigningKeys(type); //System.out.println("pubkey " + keys[0]); //System.out.println("privkey " + keys[1]); @@ -290,14 +291,19 @@ public class KeyGenerator { RandomSource.getInstance().nextBytes(src); long start = System.nanoTime(); Signature sig = DSAEngine.getInstance().sign(src, (SigningPrivateKey) keys[1]); + long mid = System.nanoTime(); boolean ok = DSAEngine.getInstance().verifySignature(sig, src, (SigningPublicKey) keys[0]); long end = System.nanoTime(); - time += end - start; + stime += mid - start; + vtime += end - mid; if (!ok) throw new GeneralSecurityException(type + " V(S(data)) fail"); } - time /= 1000*1000; - System.out.println("Signing Keygen " + runs + " times: " + time + " ms = " + (((double) time) / runs) + " each"); + stime /= 1000*1000; + vtime /= 1000*1000; + System.out.println("Sign/verify " + runs + " times: " + (vtime+stime) + " ms = " + + (((double) stime) / runs) + " each sign, " + + (((double) vtime) / runs) + " each verify"); } /****** diff --git a/core/java/src/net/i2p/crypto/SHA1Hash.java b/core/java/src/net/i2p/crypto/SHA1Hash.java index fcb6975a2..ba37ae988 100644 --- a/core/java/src/net/i2p/crypto/SHA1Hash.java +++ b/core/java/src/net/i2p/crypto/SHA1Hash.java @@ -26,6 +26,11 @@ public class SHA1Hash extends SimpleDataStructure { public final static int HASH_LENGTH = SHA1.HASH_LENGTH; + /** @since 0.9.9 */ + public SHA1Hash() { + super(); + } + /** @throws IllegalArgumentException if data is not 20 bytes (null is ok) */ public SHA1Hash(byte data[]) { super(data); diff --git a/core/java/src/net/i2p/crypto/SU3File.java b/core/java/src/net/i2p/crypto/SU3File.java index 3c797be25..c2371c0da 100644 --- a/core/java/src/net/i2p/crypto/SU3File.java +++ b/core/java/src/net/i2p/crypto/SU3File.java @@ -12,6 +12,7 @@ import java.io.OutputStream; import java.security.DigestInputStream; import java.security.DigestOutputStream; import java.security.MessageDigest; +import java.util.EnumSet; import java.util.HashMap; import java.util.Map; @@ -21,6 +22,7 @@ import net.i2p.data.DataHelper; import net.i2p.data.Signature; import net.i2p.data.SigningPrivateKey; import net.i2p.data.SigningPublicKey; +import net.i2p.data.SimpleDataStructure; /** * Succesor to the ".sud" format used in TrustedUpdate. @@ -42,6 +44,7 @@ public class SU3File { private long _contentLength; private SigningPublicKey _signerPubkey; private boolean _headerVerified; + private SigType _sigType; private static final byte[] MAGIC = DataHelper.getUTF8("I2Psu3"); private static final int FILE_VERSION = 0; @@ -55,7 +58,7 @@ public class SU3File { private static final int CONTENT_PLUGIN = 2; private static final int CONTENT_RESEED = 3; - private static final int SIG_DSA_160 = SigType.DSA_SHA1.getCode(); + private static final SigType DEFAULT_TYPE = SigType.DSA_SHA1; /** * Uses TrustedUpdate's default keys for verification. @@ -129,13 +132,14 @@ public class SU3File { if (foo != FILE_VERSION) throw new IOException("bad file version"); skip(in, 1); - int sigType = in.read(); + int sigTypeCode = in.read(); + _sigType = SigType.getByCode(sigTypeCode); // TODO, for other known algos we must start over with a new MessageDigest // (rewind 10 bytes) - if (sigType != SIG_DSA_160) - throw new IOException("bad sig type"); + if (_sigType == null) + throw new IOException("unknown sig type: " + sigTypeCode); _signerLength = (int) DataHelper.readLong(in, 2); - if (_signerLength != Signature.SIGNATURE_BYTES) + if (_signerLength != _sigType.getSigLen()) throw new IOException("bad sig length"); skip(in, 1); int _versionLength = in.read(); @@ -214,7 +218,7 @@ public class SU3File { OutputStream out = null; boolean rv = false; try { - MessageDigest md = SHA1.getInstance(); + MessageDigest md = _sigType.getDigestInstance(); in = new DigestInputStream(new BufferedInputStream(new FileInputStream(_file)), md); if (!_headerVerified) verifyHeader(in); @@ -234,9 +238,10 @@ public class SU3File { } byte[] sha = md.digest(); in.on(false); - Signature signature = new Signature(); + Signature signature = new Signature(_sigType); signature.readBytes(in); - SHA1Hash hash = new SHA1Hash(sha); + SimpleDataStructure hash = _sigType.getHashInstance(); + hash.setData(sha); rv = _context.dsa().verifySignature(signature, hash, _signerPubkey); } catch (DataFormatException dfe) { IOException ioe = new IOException("foo"); @@ -268,14 +273,15 @@ public class SU3File { boolean ok = false; try { in = new BufferedInputStream(new FileInputStream(content)); - MessageDigest md = SHA1.getInstance(); + SigType sigType = privkey.getType(); + MessageDigest md = sigType.getDigestInstance(); out = new DigestOutputStream(new BufferedOutputStream(new FileOutputStream(_file)), md); out.write(MAGIC); out.write((byte) 0); out.write((byte) FILE_VERSION); out.write((byte) 0); - out.write((byte) SIG_DSA_160); - DataHelper.writeLong(out, 2, Signature.SIGNATURE_BYTES); + out.write((byte) sigType.getCode()); + DataHelper.writeLong(out, 2, sigType.getSigLen()); out.write((byte) 0); byte[] verBytes = DataHelper.getUTF8(version); if (verBytes.length == 0 || verBytes.length > 255) @@ -315,7 +321,8 @@ public class SU3File { byte[] sha = md.digest(); out.on(false); - SHA1Hash hash = new SHA1Hash(sha); + SimpleDataStructure hash = sigType.getHashInstance(); + hash.setData(sha); Signature signature = _context.dsa().sign(hash, privkey); signature.writeBytes(out); ok = true; @@ -344,7 +351,10 @@ public class SU3File { if ("showversion".equals(args[0])) { ok = showVersionCLI(args[1]); } else if ("sign".equals(args[0])) { - ok = signCLI(args[1], args[2], args[3], args[4], args[5]); + if (args[1].equals("-t")) + ok = signCLI(args[2], args[3], args[4], args[5], args[6], args[7]); + else + ok = signCLI(args[1], args[2], args[3], args[4], args[5]); } else if ("verifysig".equals(args[0])) { ok = verifySigCLI(args[1]); } else { @@ -359,8 +369,22 @@ public class SU3File { private static final void showUsageCLI() { System.err.println("Usage: SU3File showversion signedFile.su3"); - System.err.println(" SU3File sign inputFile.zip signedFile.su3 privateKeyFile version signerName@mail.i2p"); + System.err.println(" SU3File sign [-t type|code] inputFile.zip signedFile.su3 privateKeyFile version signerName@mail.i2p"); System.err.println(" SU3File verifysig signedFile.su3"); + System.err.println(dumpSigTypes()); + } + + /** @since 0.9.9 */ + private static String dumpSigTypes() { + StringBuilder buf = new StringBuilder(256); + buf.append("Available signature types:\n"); + for (SigType t : EnumSet.allOf(SigType.class)) { + buf.append(" ").append(t).append("\t(code: ").append(t.getCode()).append(')'); + if (t == SigType.DSA_SHA1) + buf.append(" DEFAULT"); + buf.append('\n'); + } + return buf.toString(); } /** @return success */ @@ -371,7 +395,14 @@ public class SU3File { if (versionString.equals("")) System.out.println("No version string found in file '" + signedFile + "'"); else - System.out.println("Version: " + versionString); + System.out.println("Version: " + versionString); + String signerString = file.getSignerString(); + if (signerString.equals("")) + System.out.println("No signer string found in file '" + signedFile + "'"); + else + System.out.println("Signer: " + signerString); + if (file._sigType != null) + System.out.println("SigType: " + file._sigType); return !versionString.equals(""); } catch (IOException ioe) { ioe.printStackTrace(); @@ -380,12 +411,43 @@ public class SU3File { } /** @return success */ - private static final boolean signCLI(String inputFile, String signedFile, String privateKeyFile, - String version, String signerName) { + private static final boolean signCLI(String inputFile, String signedFile, + String privateKeyFile, String version, String signerName) { + return signCLI(DEFAULT_TYPE, inputFile, signedFile, privateKeyFile, version, signerName); + } + + /** + * @return success + * @since 0.9.9 + */ + private static final boolean signCLI(String stype, String inputFile, String signedFile, + String privateKeyFile, String version, String signerName) { + SigType type = null; + try { + type = SigType.valueOf(stype); + } catch (IllegalArgumentException iae) { + try { + int code = Integer.parseInt(stype); + type = SigType.getByCode(code); + } catch (NumberFormatException nfe) {} + } + if (type == null) { + System.out.println("Signature type " + stype + " is not supported"); + return false; + } + return signCLI(type, inputFile, signedFile, privateKeyFile, version, signerName); + } + + /** + * @return success + * @since 0.9.9 + */ + private static final boolean signCLI(SigType type, String inputFile, String signedFile, + String privateKeyFile, String version, String signerName) { InputStream in = null; try { in = new FileInputStream(privateKeyFile); - SigningPrivateKey spk = new SigningPrivateKey(); + SigningPrivateKey spk = new SigningPrivateKey(type); spk.readBytes(in); in.close(); SU3File file = new SU3File(signedFile); diff --git a/core/java/src/net/i2p/crypto/SigType.java b/core/java/src/net/i2p/crypto/SigType.java index 8405bcb1d..ec0bcb8bb 100644 --- a/core/java/src/net/i2p/crypto/SigType.java +++ b/core/java/src/net/i2p/crypto/SigType.java @@ -7,6 +7,9 @@ import java.security.spec.InvalidParameterSpecException; import java.util.HashMap; import java.util.Map; +import net.i2p.data.Hash; +import net.i2p.data.SimpleDataStructure; + /** * Defines the properties for various signature types * that I2P supports or may someday support. @@ -35,7 +38,7 @@ public enum SigType { // TESTING.................... - ECDSA_SHA256_P192(5, 48, 24, 20, 48, "SHA-1", "SHA256withECDSA", ECConstants.P192_SPEC), + ECDSA_SHA256_P192(5, 48, 24, 32, 48, "SHA-256", "SHA256withECDSA", ECConstants.P192_SPEC), ECDSA_SHA256_P384(6, 96, 48, 32, 96, "SHA-256", "SHA256withECDSA", ECConstants.P384_SPEC), ECDSA_SHA256_P521(7, 132, 66, 32, 132, "SHA-256", "SHA256withECDSA", ECConstants.P521_SPEC), @@ -112,6 +115,25 @@ public enum SigType { } } + /** + * @since 0.9.9 + * @throws UnsupportedOperationException if not supported + */ + public SimpleDataStructure getHashInstance() { + switch (getHashLen()) { + case 20: + return new SHA1Hash(); + case 32: + return new Hash(); + case 48: + return new Hash384(); + case 64: + return new Hash512(); + default: + throw new UnsupportedOperationException("Unsupported hash length: " + getHashLen()); + } + } + private static final Map BY_CODE = new HashMap(); static { From 38ec55bc72124a9750ba59ed18b99d8045cad6fb Mon Sep 17 00:00:00 2001 From: zzz Date: Sat, 7 Sep 2013 19:37:02 +0000 Subject: [PATCH 05/11] - DSAEngine: Implement raw ECDSA sign/verify - SU3File: Implement keygen --- core/java/src/net/i2p/crypto/DSAEngine.java | 51 +++++++++- core/java/src/net/i2p/crypto/SU3File.java | 100 +++++++++++++++++--- 2 files changed, 135 insertions(+), 16 deletions(-) diff --git a/core/java/src/net/i2p/crypto/DSAEngine.java b/core/java/src/net/i2p/crypto/DSAEngine.java index 6cad15bc9..c01e39cf4 100644 --- a/core/java/src/net/i2p/crypto/DSAEngine.java +++ b/core/java/src/net/i2p/crypto/DSAEngine.java @@ -170,9 +170,8 @@ public class DSAEngine { throw new IllegalArgumentException("type mismatch hash=" + hash.getClass() + " sig=" + type); if (type == SigType.DSA_SHA1) return verifySig(signature, hash, verifyingKey); - // FIXME hash of hash try { - return altVerifySig(signature, hash.getData(), verifyingKey); + return altVerifySigRaw(signature, hash, verifyingKey); } catch (GeneralSecurityException gse) { if (_log.shouldLog(Log.WARN)) _log.warn(type + " Sig Verify Fail", gse); @@ -325,9 +324,8 @@ public class DSAEngine { throw new IllegalArgumentException("type mismatch hash=" + hash.getClass() + " key=" + type); if (type == SigType.DSA_SHA1) return signIt(hash, signingKey); - // FIXME hash of hash try { - return altSign(hash.getData(), signingKey); + return altSignRaw(hash, signingKey); } catch (GeneralSecurityException gse) { if (_log.shouldLog(Log.WARN)) _log.warn(type + " Sign Fail", gse); @@ -472,6 +470,30 @@ public class DSAEngine { return rv; } + /** + * Generic raw verify ECDSA only + * @throws GeneralSecurityException if algorithm unvailable or on other errors + * @since 0.9.9 + */ + private boolean altVerifySigRaw(Signature signature, SimpleDataStructure hash, SigningPublicKey verifyingKey) + throws GeneralSecurityException { + SigType type = signature.getType(); + if (type != verifyingKey.getType()) + throw new IllegalArgumentException("type mismatch sig=" + type + " key=" + verifyingKey.getType()); + int hashlen = hash.length(); + if (type.getHashLen() != hashlen) + throw new IllegalArgumentException("type mismatch hash=" + hash.getClass() + " key=" + type); + if (type == SigType.DSA_SHA1) + throw new UnsupportedOperationException(); + + java.security.Signature jsig = java.security.Signature.getInstance("NONEwithECDSA"); + PublicKey pubKey = SigUtil.toJavaECKey(verifyingKey); + jsig.initVerify(pubKey); + jsig.update(hash.getData()); + boolean rv = jsig.verify(SigUtil.toJavaSig(signature)); + return rv; + } + /** * Alternate to verifySignature() using java.security libraries. * @throws GeneralSecurityException if algorithm unvailable or on other errors @@ -513,6 +535,27 @@ public class DSAEngine { return SigUtil.fromJavaSig(jsig.sign(), type); } + /** + * Generic raw sign ECDSA only. + * @param hash SHA1Hash, Hash, Hash384, or Hash512 + * @throws GeneralSecurityException if algorithm unvailable or on other errors + * @since 0.9.9 + */ + private Signature altSignRaw(SimpleDataStructure hash, SigningPrivateKey privateKey) throws GeneralSecurityException { + SigType type = privateKey.getType(); + if (type == SigType.DSA_SHA1) + throw new UnsupportedOperationException(); + int hashlen = hash.length(); + if (type.getHashLen() != hashlen) + throw new IllegalArgumentException("type mismatch hash=" + hash.getClass() + " key=" + type); + + java.security.Signature jsig = java.security.Signature.getInstance("NONEwithECDSA"); + PrivateKey privKey = SigUtil.toJavaECKey(privateKey); + jsig.initSign(privKey, _context.random()); + jsig.update(hash.getData()); + return SigUtil.fromJavaSig(jsig.sign(), type); + } + /** * Alternate to sign() using java.security libraries. * @throws GeneralSecurityException if algorithm unvailable or on other errors diff --git a/core/java/src/net/i2p/crypto/SU3File.java b/core/java/src/net/i2p/crypto/SU3File.java index c2371c0da..20aa06666 100644 --- a/core/java/src/net/i2p/crypto/SU3File.java +++ b/core/java/src/net/i2p/crypto/SU3File.java @@ -14,6 +14,7 @@ import java.security.DigestOutputStream; import java.security.MessageDigest; import java.util.EnumSet; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import net.i2p.I2PAppContext; @@ -218,6 +219,7 @@ public class SU3File { OutputStream out = null; boolean rv = false; try { + ////// fixme NPE we don't know the type yet MessageDigest md = _sigType.getDigestInstance(); in = new DigestInputStream(new BufferedInputStream(new FileInputStream(_file)), md); if (!_headerVerified) @@ -357,6 +359,11 @@ public class SU3File { ok = signCLI(args[1], args[2], args[3], args[4], args[5]); } else if ("verifysig".equals(args[0])) { ok = verifySigCLI(args[1]); + } else if ("keygen".equals(args[0])) { + if (args[1].equals("-t")) + ok = genKeysCLI(args[2], args[3], args[4]); + else + ok = genKeysCLI(args[1], args[2]); } else { showUsageCLI(); } @@ -368,9 +375,10 @@ public class SU3File { } private static final void showUsageCLI() { - System.err.println("Usage: SU3File showversion signedFile.su3"); - System.err.println(" SU3File sign [-t type|code] inputFile.zip signedFile.su3 privateKeyFile version signerName@mail.i2p"); - System.err.println(" SU3File verifysig signedFile.su3"); + System.err.println("Usage: SU3File keygen [-t type|code] publicKeyFile privateKeyFile"); + System.err.println(" SU3File showversion signedFile.su3"); + System.err.println(" SU3File sign [-t type|code] inputFile.zip signedFile.su3 privateKeyFile version signerName@mail.i2p"); + System.err.println(" SU3File verifysig signedFile.su3"); System.err.println(dumpSigTypes()); } @@ -387,6 +395,24 @@ public class SU3File { return buf.toString(); } + /** + * @param stype number or name + * @return null if not found + * @since 0.9.9 + */ + private static SigType parseSigType(String stype) { + try { + return SigType.valueOf(stype.toUpperCase(Locale.US)); + } catch (IllegalArgumentException iae) { + try { + int code = Integer.parseInt(stype); + return SigType.getByCode(code); + } catch (NumberFormatException nfe) { + return null; + } + } + } + /** @return success */ private static final boolean showVersionCLI(String signedFile) { try { @@ -422,15 +448,7 @@ public class SU3File { */ private static final boolean signCLI(String stype, String inputFile, String signedFile, String privateKeyFile, String version, String signerName) { - SigType type = null; - try { - type = SigType.valueOf(stype); - } catch (IllegalArgumentException iae) { - try { - int code = Integer.parseInt(stype); - type = SigType.getByCode(code); - } catch (NumberFormatException nfe) {} - } + SigType type = parseSigType(stype); if (type == null) { System.out.println("Signature type " + stype + " is not supported"); return false; @@ -472,6 +490,7 @@ public class SU3File { InputStream in = null; try { SU3File file = new SU3File(signedFile); + //// fixme boolean isValidSignature = file.verifyAndMigrate(new File("/dev/null")); if (isValidSignature) System.out.println("Signature VALID (signed by " + file.getSignerString() + ')'); @@ -484,4 +503,61 @@ public class SU3File { return false; } } + + /** + * @return success + * @since 0.9.9 + */ + private static final boolean genKeysCLI(String publicKeyFile, String privateKeyFile) { + return genKeysCLI(DEFAULT_TYPE, publicKeyFile, privateKeyFile); + } + + /** + * @return success + * @since 0.9.9 + */ + private static final boolean genKeysCLI(String stype, String publicKeyFile, String privateKeyFile) { + SigType type = parseSigType(stype); + if (type == null) { + System.out.println("Signature type " + stype + " is not supported"); + return false; + } + return genKeysCLI(type, publicKeyFile, privateKeyFile); + } + + /** + * @return success + * @since 0.9.9 + */ + private static final boolean genKeysCLI(SigType type, String publicKeyFile, String privateKeyFile) { + FileOutputStream fileOutputStream = null; + I2PAppContext context = I2PAppContext.getGlobalContext(); + try { + SimpleDataStructure signingKeypair[] = context.keyGenerator().generateSigningKeys(type); + SigningPublicKey signingPublicKey = (SigningPublicKey) signingKeypair[0]; + SigningPrivateKey signingPrivateKey = (SigningPrivateKey) signingKeypair[1]; + + fileOutputStream = new FileOutputStream(publicKeyFile); + signingPublicKey.writeBytes(fileOutputStream); + fileOutputStream.close(); + fileOutputStream = null; + + fileOutputStream = new FileOutputStream(privateKeyFile); + signingPrivateKey.writeBytes(fileOutputStream); + + System.out.println("\r\n" + type + " Private key written to: " + privateKeyFile); + System.out.println(type + " Public key written to: " + publicKeyFile); + System.out.println("\r\nPublic key: " + signingPublicKey.toBase64() + "\r\n"); + } catch (Exception e) { + System.err.println("Error writing keys:"); + e.printStackTrace(); + return false; + } finally { + if (fileOutputStream != null) + try { + fileOutputStream.close(); + } catch (IOException ioe) {} + } + return true; + } } From 83ead0c3040b1a78abfd9fc8880159cadf40470d Mon Sep 17 00:00:00 2001 From: zzz Date: Sat, 7 Sep 2013 20:24:40 +0000 Subject: [PATCH 06/11] - SU3File: Readahead to get sigtype on verify, as we need the hash type --- core/java/src/net/i2p/crypto/SU3File.java | 28 +++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/core/java/src/net/i2p/crypto/SU3File.java b/core/java/src/net/i2p/crypto/SU3File.java index 20aa06666..227d01fbb 100644 --- a/core/java/src/net/i2p/crypto/SU3File.java +++ b/core/java/src/net/i2p/crypto/SU3File.java @@ -215,13 +215,33 @@ public class SU3File { * @return true if signature is good */ public boolean verifyAndMigrate(File migrateTo) throws IOException { - DigestInputStream in = null; + InputStream in = null; OutputStream out = null; boolean rv = false; try { - ////// fixme NPE we don't know the type yet + in = new BufferedInputStream(new FileInputStream(_file)); + // read 10 bytes to get the sig type + in.mark(10); + // following is a dup of that in verifyHeader() + byte[] magic = new byte[MAGIC.length]; + DataHelper.read(in, magic); + if (!DataHelper.eq(magic, MAGIC)) + throw new IOException("Not an su3 file"); + skip(in, 1); + int foo = in.read(); + if (foo != FILE_VERSION) + throw new IOException("bad file version"); + skip(in, 1); + int sigTypeCode = in.read(); + _sigType = SigType.getByCode(sigTypeCode); + if (_sigType == null) + throw new IOException("unknown sig type: " + sigTypeCode); + // end duplicate code + // rewind + in.reset(); MessageDigest md = _sigType.getDigestInstance(); - in = new DigestInputStream(new BufferedInputStream(new FileInputStream(_file)), md); + DigestInputStream din = new DigestInputStream(in, md); + in = din; if (!_headerVerified) verifyHeader(in); else @@ -239,7 +259,7 @@ public class SU3File { tot += read; } byte[] sha = md.digest(); - in.on(false); + din.on(false); Signature signature = new Signature(_sigType); signature.readBytes(in); SimpleDataStructure hash = _sigType.getHashInstance(); From 55318cf14bc6d3125e87a573abdd09d672b8e438 Mon Sep 17 00:00:00 2001 From: zzz Date: Sun, 8 Sep 2013 11:57:15 +0000 Subject: [PATCH 07/11] Crypto: Set file modes on written keys; don't overwrite existing files --- core/java/src/net/i2p/crypto/SU3File.java | 15 +++++++++++++-- core/java/src/net/i2p/crypto/TrustedUpdate.java | 16 +++++++++++++--- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/core/java/src/net/i2p/crypto/SU3File.java b/core/java/src/net/i2p/crypto/SU3File.java index 227d01fbb..7cc23552b 100644 --- a/core/java/src/net/i2p/crypto/SU3File.java +++ b/core/java/src/net/i2p/crypto/SU3File.java @@ -24,6 +24,7 @@ import net.i2p.data.Signature; import net.i2p.data.SigningPrivateKey; import net.i2p.data.SigningPublicKey; import net.i2p.data.SimpleDataStructure; +import net.i2p.util.SecureFileOutputStream; /** * Succesor to the ".sud" format used in TrustedUpdate. @@ -550,6 +551,16 @@ public class SU3File { * @since 0.9.9 */ private static final boolean genKeysCLI(SigType type, String publicKeyFile, String privateKeyFile) { + File pubFile = new File(publicKeyFile); + File privFile = new File(privateKeyFile); + if (pubFile.exists()) { + System.out.println("Error: Not overwriting file " + publicKeyFile); + return false; + } + if (privFile.exists()) { + System.out.println("Error: Not overwriting file " + privateKeyFile); + return false; + } FileOutputStream fileOutputStream = null; I2PAppContext context = I2PAppContext.getGlobalContext(); try { @@ -557,12 +568,12 @@ public class SU3File { SigningPublicKey signingPublicKey = (SigningPublicKey) signingKeypair[0]; SigningPrivateKey signingPrivateKey = (SigningPrivateKey) signingKeypair[1]; - fileOutputStream = new FileOutputStream(publicKeyFile); + fileOutputStream = new SecureFileOutputStream(pubFile); signingPublicKey.writeBytes(fileOutputStream); fileOutputStream.close(); fileOutputStream = null; - fileOutputStream = new FileOutputStream(privateKeyFile); + fileOutputStream = new SecureFileOutputStream(privFile); signingPrivateKey.writeBytes(fileOutputStream); System.out.println("\r\n" + type + " Private key written to: " + privateKeyFile); diff --git a/core/java/src/net/i2p/crypto/TrustedUpdate.java b/core/java/src/net/i2p/crypto/TrustedUpdate.java index 51c19e4ea..a9892b171 100644 --- a/core/java/src/net/i2p/crypto/TrustedUpdate.java +++ b/core/java/src/net/i2p/crypto/TrustedUpdate.java @@ -21,6 +21,7 @@ import net.i2p.data.Signature; import net.i2p.data.SigningPrivateKey; import net.i2p.data.SigningPublicKey; import net.i2p.util.Log; +import net.i2p.util.SecureFileOutputStream; import net.i2p.util.VersionComparator; import net.i2p.util.ZipFileComment; @@ -315,20 +316,29 @@ riCe6OlAEiNpcc6mMyIYYWFICbrDFTrDR3wXqwc/Jkcx6L5VVWoagpSzbo3yGhc= /** @return success */ private static final boolean genKeysCLI(String publicKeyFile, String privateKeyFile) { + File pubFile = new File(publicKeyFile); + File privFile = new File(privateKeyFile); + if (pubFile.exists()) { + System.out.println("Error: Not overwriting file " + publicKeyFile); + return false; + } + if (privFile.exists()) { + System.out.println("Error: Not overwriting file " + privateKeyFile); + return false; + } FileOutputStream fileOutputStream = null; - I2PAppContext context = I2PAppContext.getGlobalContext(); try { Object signingKeypair[] = context.keyGenerator().generateSigningKeypair(); SigningPublicKey signingPublicKey = (SigningPublicKey) signingKeypair[0]; SigningPrivateKey signingPrivateKey = (SigningPrivateKey) signingKeypair[1]; - fileOutputStream = new FileOutputStream(publicKeyFile); + fileOutputStream = new SecureFileOutputStream(pubFile); signingPublicKey.writeBytes(fileOutputStream); fileOutputStream.close(); fileOutputStream = null; - fileOutputStream = new FileOutputStream(privateKeyFile); + fileOutputStream = new SecureFileOutputStream(privFile); signingPrivateKey.writeBytes(fileOutputStream); System.out.println("\r\nPrivate key written to: " + privateKeyFile); From 27936fce04f3944b3971bf815f7ba4c3ddbc4b74 Mon Sep 17 00:00:00 2001 From: zzz Date: Sun, 8 Sep 2013 13:06:30 +0000 Subject: [PATCH 08/11] cache key conversion --- .../src/net/i2p/crypto/ElGamalEngine.java | 1 + core/java/src/net/i2p/crypto/SigUtil.java | 53 ++++++++++++++++++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/core/java/src/net/i2p/crypto/ElGamalEngine.java b/core/java/src/net/i2p/crypto/ElGamalEngine.java index 57f40cff9..8ffe1fbdf 100644 --- a/core/java/src/net/i2p/crypto/ElGamalEngine.java +++ b/core/java/src/net/i2p/crypto/ElGamalEngine.java @@ -81,6 +81,7 @@ public class ElGamalEngine { */ public void shutdown() { _ykgen.shutdown(); + SigUtil.clearCaches(); } /** diff --git a/core/java/src/net/i2p/crypto/SigUtil.java b/core/java/src/net/i2p/crypto/SigUtil.java index 28448c1b3..a1ebd2195 100644 --- a/core/java/src/net/i2p/crypto/SigUtil.java +++ b/core/java/src/net/i2p/crypto/SigUtil.java @@ -17,10 +17,12 @@ import java.security.spec.ECPrivateKeySpec; import java.security.spec.ECPublicKeySpec; import java.security.spec.ECPoint; import java.security.spec.EllipticCurve; +import java.util.Map; import net.i2p.data.Signature; import net.i2p.data.SigningPrivateKey; import net.i2p.data.SigningPublicKey; +import net.i2p.util.LHMCache; /** @@ -30,6 +32,11 @@ import net.i2p.data.SigningPublicKey; */ class SigUtil { + private static final Map _pubkeyCache = new LHMCache(64); + private static final Map _privkeyCache = new LHMCache(16); + + private SigUtil() {} + /** * @return JAVA key! */ @@ -74,8 +81,44 @@ class SigUtil { return fromJavaKey((ECPrivateKey) pk, type); } + /** + * @return JAVA key! + */ public static ECPublicKey toJavaECKey(SigningPublicKey pk) throws GeneralSecurityException { + ECPublicKey rv; + synchronized (_pubkeyCache) { + rv = _pubkeyCache.get(pk); + } + if (rv != null) + return rv; + rv = cvtToJavaECKey(pk); + synchronized (_pubkeyCache) { + _pubkeyCache.put(pk, rv); + } + return rv; + } + + /** + * @return JAVA key! + */ + public static ECPrivateKey toJavaECKey(SigningPrivateKey pk) + throws GeneralSecurityException { + ECPrivateKey rv; + synchronized (_privkeyCache) { + rv = _privkeyCache.get(pk); + } + if (rv != null) + return rv; + rv = cvtToJavaECKey(pk); + synchronized (_privkeyCache) { + _privkeyCache.put(pk, rv); + } + return rv; + } + + private static ECPublicKey cvtToJavaECKey(SigningPublicKey pk) + throws GeneralSecurityException { SigType type = pk.getType(); int len = type.getPubkeyLen(); int sublen = len / 2; @@ -93,7 +136,7 @@ class SigUtil { return (ECPublicKey) kf.generatePublic(ks); } - public static ECPrivateKey toJavaECKey(SigningPrivateKey pk) + public static ECPrivateKey cvtToJavaECKey(SigningPrivateKey pk) throws GeneralSecurityException { SigType type = pk.getType(); int len = type.getPubkeyLen(); @@ -290,4 +333,12 @@ class SigUtil { return rv; } + public static void clearCaches() { + synchronized(_pubkeyCache) { + _pubkeyCache.clear(); + } + synchronized(_privkeyCache) { + _privkeyCache.clear(); + } + } } From 1e554dd0fe87eb1179eb8fab8b2765581f6e9161 Mon Sep 17 00:00:00 2001 From: zzz Date: Sun, 8 Sep 2013 21:47:48 +0000 Subject: [PATCH 09/11] - Move I2P-to-Java DSA key conversion from DSAEngine to SigUtil - Add Java-to-I2P DSA key conversion to SigUtil - Export keys from SU3File in Java encoded format instead of I2P format --- core/java/src/net/i2p/crypto/DSAEngine.java | 19 +------- core/java/src/net/i2p/crypto/SU3File.java | 10 +++- core/java/src/net/i2p/crypto/SigUtil.java | 54 +++++++++++++++++++-- 3 files changed, 60 insertions(+), 23 deletions(-) diff --git a/core/java/src/net/i2p/crypto/DSAEngine.java b/core/java/src/net/i2p/crypto/DSAEngine.java index c01e39cf4..0c57035f0 100644 --- a/core/java/src/net/i2p/crypto/DSAEngine.java +++ b/core/java/src/net/i2p/crypto/DSAEngine.java @@ -38,9 +38,6 @@ import java.security.KeyFactory; import java.security.MessageDigest; import java.security.PrivateKey; import java.security.PublicKey; -import java.security.spec.DSAPrivateKeySpec; -import java.security.spec.DSAPublicKeySpec; -import java.security.spec.KeySpec; import net.i2p.I2PAppContext; import net.i2p.data.Hash; @@ -501,13 +498,7 @@ public class DSAEngine { */ private boolean altVerifySigSHA1(Signature signature, byte[] data, SigningPublicKey verifyingKey) throws GeneralSecurityException { java.security.Signature jsig = java.security.Signature.getInstance("SHA1withDSA"); - KeyFactory keyFact = KeyFactory.getInstance("DSA"); - // y p q g - KeySpec spec = new DSAPublicKeySpec(new NativeBigInteger(1, verifyingKey.getData()), - CryptoConstants.dsap, - CryptoConstants.dsaq, - CryptoConstants.dsag); - PublicKey pubKey = keyFact.generatePublic(spec); + PublicKey pubKey = SigUtil.toJavaDSAKey(verifyingKey); jsig.initVerify(pubKey); jsig.update(data); boolean rv = jsig.verify(SigUtil.toJavaSig(signature)); @@ -563,13 +554,7 @@ public class DSAEngine { */ private Signature altSignSHA1(byte[] data, SigningPrivateKey privateKey) throws GeneralSecurityException { java.security.Signature jsig = java.security.Signature.getInstance("SHA1withDSA"); - KeyFactory keyFact = KeyFactory.getInstance("DSA"); - // y p q g - KeySpec spec = new DSAPrivateKeySpec(new NativeBigInteger(1, privateKey.getData()), - CryptoConstants.dsap, - CryptoConstants.dsaq, - CryptoConstants.dsag); - PrivateKey privKey = keyFact.generatePrivate(spec); + PrivateKey privKey = SigUtil.toJavaDSAKey(privateKey); jsig.initSign(privKey, _context.random()); jsig.update(data); return SigUtil.fromJavaSig(jsig.sign(), SigType.DSA_SHA1); diff --git a/core/java/src/net/i2p/crypto/SU3File.java b/core/java/src/net/i2p/crypto/SU3File.java index 7cc23552b..e55828c6e 100644 --- a/core/java/src/net/i2p/crypto/SU3File.java +++ b/core/java/src/net/i2p/crypto/SU3File.java @@ -12,6 +12,8 @@ import java.io.OutputStream; import java.security.DigestInputStream; import java.security.DigestOutputStream; import java.security.MessageDigest; +import java.security.PrivateKey; +import java.security.PublicKey; import java.util.EnumSet; import java.util.HashMap; import java.util.Locale; @@ -547,6 +549,7 @@ public class SU3File { } /** + * Writes Java-encoded keys (X.509 for public and PKCS#8 for private) * @return success * @since 0.9.9 */ @@ -564,17 +567,20 @@ public class SU3File { FileOutputStream fileOutputStream = null; I2PAppContext context = I2PAppContext.getGlobalContext(); try { + // inefficiently go from Java to I2P to Java formats SimpleDataStructure signingKeypair[] = context.keyGenerator().generateSigningKeys(type); SigningPublicKey signingPublicKey = (SigningPublicKey) signingKeypair[0]; SigningPrivateKey signingPrivateKey = (SigningPrivateKey) signingKeypair[1]; + PublicKey pubkey = SigUtil.toJavaKey(signingPublicKey); + PrivateKey privkey = SigUtil.toJavaKey(signingPrivateKey); fileOutputStream = new SecureFileOutputStream(pubFile); - signingPublicKey.writeBytes(fileOutputStream); + fileOutputStream.write(pubkey.getEncoded()); fileOutputStream.close(); fileOutputStream = null; fileOutputStream = new SecureFileOutputStream(privFile); - signingPrivateKey.writeBytes(fileOutputStream); + fileOutputStream.write(privkey.getEncoded()); System.out.println("\r\n" + type + " Private key written to: " + privateKeyFile); System.out.println(type + " Public key written to: " + publicKeyFile); diff --git a/core/java/src/net/i2p/crypto/SigUtil.java b/core/java/src/net/i2p/crypto/SigUtil.java index a1ebd2195..124ed2935 100644 --- a/core/java/src/net/i2p/crypto/SigUtil.java +++ b/core/java/src/net/i2p/crypto/SigUtil.java @@ -7,8 +7,12 @@ import java.security.KeyFactory; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SignatureException; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.DSAPublicKey; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; +import java.security.spec.DSAPrivateKeySpec; +import java.security.spec.DSAPublicKeySpec; import java.security.spec.ECField; import java.security.spec.ECFieldFp; import java.security.spec.ECGenParameterSpec; @@ -17,12 +21,14 @@ import java.security.spec.ECPrivateKeySpec; import java.security.spec.ECPublicKeySpec; import java.security.spec.ECPoint; import java.security.spec.EllipticCurve; +import java.security.spec.KeySpec; import java.util.Map; import net.i2p.data.Signature; import net.i2p.data.SigningPrivateKey; import net.i2p.data.SigningPublicKey; import net.i2p.util.LHMCache; +import net.i2p.util.NativeBigInteger; /** @@ -43,7 +49,7 @@ class SigUtil { public static PublicKey toJavaKey(SigningPublicKey pk) throws GeneralSecurityException { if (pk.getType() == SigType.DSA_SHA1) - throw new UnsupportedOperationException(); + return toJavaDSAKey(pk); else return toJavaECKey(pk); } @@ -54,7 +60,7 @@ class SigUtil { public static PrivateKey toJavaKey(SigningPrivateKey pk) throws GeneralSecurityException { if (pk.getType() == SigType.DSA_SHA1) - throw new UnsupportedOperationException(); + return toJavaDSAKey(pk); else return toJavaECKey(pk); } @@ -65,7 +71,7 @@ class SigUtil { public static SigningPublicKey fromJavaKey(PublicKey pk, SigType type) throws GeneralSecurityException { if (type == SigType.DSA_SHA1) - throw new UnsupportedOperationException(); + return fromJavaKey((DSAPublicKey) pk); else return fromJavaKey((ECPublicKey) pk, type); } @@ -76,7 +82,7 @@ class SigUtil { public static SigningPrivateKey fromJavaKey(PrivateKey pk, SigType type) throws GeneralSecurityException { if (type == SigType.DSA_SHA1) - throw new UnsupportedOperationException(); + return fromJavaKey((DSAPrivateKey) pk); else return fromJavaKey((ECPrivateKey) pk, type); } @@ -172,6 +178,46 @@ class SigUtil { return new SigningPrivateKey(type, bs); } + public static DSAPublicKey toJavaDSAKey(SigningPublicKey pk) + throws GeneralSecurityException { + KeyFactory kf = KeyFactory.getInstance("DSA"); + // y p q g + KeySpec ks = new DSAPublicKeySpec(new NativeBigInteger(1, pk.getData()), + CryptoConstants.dsap, + CryptoConstants.dsaq, + CryptoConstants.dsag); + return (DSAPublicKey) kf.generatePublic(ks); + } + + public static DSAPrivateKey toJavaDSAKey(SigningPrivateKey pk) + throws GeneralSecurityException { + KeyFactory kf = KeyFactory.getInstance("DSA"); + // x p q g + KeySpec ks = new DSAPrivateKeySpec(new NativeBigInteger(1, pk.getData()), + CryptoConstants.dsap, + CryptoConstants.dsaq, + CryptoConstants.dsag); + return (DSAPrivateKey) kf.generatePrivate(ks); + } + + public static SigningPublicKey fromJavaKey(DSAPublicKey pk) + throws GeneralSecurityException { + BigInteger y = pk.getY(); + SigType type = SigType.DSA_SHA1; + int len = type.getPubkeyLen(); + byte[] by = rectify(y, len); + return new SigningPublicKey(type, by); + } + + public static SigningPrivateKey fromJavaKey(DSAPrivateKey pk) + throws GeneralSecurityException { + BigInteger x = pk.getX(); + SigType type = SigType.DSA_SHA1; + int len = type.getPrivkeyLen(); + byte[] bx = rectify(x, len); + return new SigningPrivateKey(type, bx); + } + /** * @return ASN.1 representation */ From 3e3399adc66681946e88c99f29038a4065b16bd1 Mon Sep 17 00:00:00 2001 From: zzz Date: Mon, 9 Sep 2013 00:49:14 +0000 Subject: [PATCH 10/11] - Add Java key import to SigUtil - Import priv key to SU3File in Java encoded format instead of I2P format - New KeyRing stub --- core/java/src/net/i2p/crypto/KeyRing.java | 37 ++++++++++++++++ core/java/src/net/i2p/crypto/SU3File.java | 15 +++---- core/java/src/net/i2p/crypto/SigUtil.java | 54 +++++++++++++++++++++++ 3 files changed, 97 insertions(+), 9 deletions(-) create mode 100644 core/java/src/net/i2p/crypto/KeyRing.java diff --git a/core/java/src/net/i2p/crypto/KeyRing.java b/core/java/src/net/i2p/crypto/KeyRing.java new file mode 100644 index 000000000..6efba6fd9 --- /dev/null +++ b/core/java/src/net/i2p/crypto/KeyRing.java @@ -0,0 +1,37 @@ +package net.i2p.crypto; + +/* + * free (adj.): unencumbered; not under the control of others + * No warranty of any kind, either expressed or implied. + */ + +import java.io.IOException; +import java.security.GeneralSecurityException; + +import net.i2p.data.SigningPublicKey; + +/** + * A backend for storing and retrieving SigningPublicKeys + * to be used for verifying signatures. + * + * @since 0.9.9 + */ +public interface KeyRing { + + /** + * Get a key. + * Throws on all errors. + * @param scope a domain identifier, indicating router update, reseed, etc. + * @return null if none + */ + public SigningPublicKey getKey(String keyName, String scope, SigType type) + throws GeneralSecurityException, IOException; + + /** + * Store a key. + * Throws on all errors. + * @param scope a domain identifier, indicating router update, reseed, etc. + */ + public void setKey(String keyName, String scope, SigningPublicKey key) + throws GeneralSecurityException, IOException; +} diff --git a/core/java/src/net/i2p/crypto/SU3File.java b/core/java/src/net/i2p/crypto/SU3File.java index e55828c6e..dfb88ee6c 100644 --- a/core/java/src/net/i2p/crypto/SU3File.java +++ b/core/java/src/net/i2p/crypto/SU3File.java @@ -11,6 +11,7 @@ import java.io.IOException; import java.io.OutputStream; import java.security.DigestInputStream; import java.security.DigestOutputStream; +import java.security.GeneralSecurityException; import java.security.MessageDigest; import java.security.PrivateKey; import java.security.PublicKey; @@ -485,26 +486,22 @@ public class SU3File { */ private static final boolean signCLI(SigType type, String inputFile, String signedFile, String privateKeyFile, String version, String signerName) { - InputStream in = null; try { - in = new FileInputStream(privateKeyFile); - SigningPrivateKey spk = new SigningPrivateKey(type); - spk.readBytes(in); - in.close(); + File pkfile = new File(privateKeyFile); + PrivateKey pk = SigUtil.importJavaPrivateKey(pkfile, type); + SigningPrivateKey spk = SigUtil.fromJavaKey(pk, type); SU3File file = new SU3File(signedFile); file.write(new File(inputFile), CONTENT_ROUTER, version, signerName, spk); System.out.println("Input file '" + inputFile + "' signed and written to '" + signedFile + "'"); return true; - } catch (DataFormatException dfe) { + } catch (GeneralSecurityException gse) { System.out.println("Error signing input file '" + inputFile + "'"); - dfe.printStackTrace(); + gse.printStackTrace(); return false; } catch (IOException ioe) { System.out.println("Error signing input file '" + inputFile + "'"); ioe.printStackTrace(); return false; - } finally { - if (in != null) try { in.close(); } catch (IOException ioe) {} } } diff --git a/core/java/src/net/i2p/crypto/SigUtil.java b/core/java/src/net/i2p/crypto/SigUtil.java index 124ed2935..641515758 100644 --- a/core/java/src/net/i2p/crypto/SigUtil.java +++ b/core/java/src/net/i2p/crypto/SigUtil.java @@ -1,5 +1,11 @@ package net.i2p.crypto; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.IOException; + import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.InvalidKeyException; @@ -22,6 +28,8 @@ import java.security.spec.ECPublicKeySpec; import java.security.spec.ECPoint; import java.security.spec.EllipticCurve; import java.security.spec.KeySpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; import java.util.Map; import net.i2p.data.Signature; @@ -234,6 +242,52 @@ class SigUtil { return new Signature(type, aSN1ToSigBytes(asn, type.getSigLen())); } + /** + * @return JAVA key! + */ + public static PublicKey importJavaPublicKey(File file, SigType type) + throws GeneralSecurityException, IOException { + byte[] data = getData(file); + KeySpec ks = new X509EncodedKeySpec(data); + String algo = type == SigType.DSA_SHA1 ? "DSA" : "EC"; + KeyFactory kf = KeyFactory.getInstance(algo); + return kf.generatePublic(ks); + } + + /** + * @return JAVA key! + */ + public static PrivateKey importJavaPrivateKey(File file, SigType type) + throws GeneralSecurityException, IOException { + byte[] data = getData(file); + KeySpec ks = new PKCS8EncodedKeySpec(data); + String algo = type == SigType.DSA_SHA1 ? "DSA" : "EC"; + KeyFactory kf = KeyFactory.getInstance(algo); + return kf.generatePrivate(ks); + } + + /** 16 KB max */ + private static byte[] getData(File file) throws IOException { + byte buf[] = new byte[1024]; + InputStream in = null; + ByteArrayOutputStream out = new ByteArrayOutputStream(1024); + try { + in = new FileInputStream(file); + int read = 0; + int tot = 0; + while ( (read = in.read(buf)) != -1) { + out.write(buf, 0, read); + tot += read; + if (tot > 16*1024) + throw new IOException("too big"); + } + return out.toByteArray(); + } finally { + if (in != null) + try { in.close(); } catch (IOException ioe) {} + } + } + /** * @param bi non-negative * @return array of exactly len bytes From 78d4b6d8a792ba6aa7c0d0beb20bfb38d5cca99f Mon Sep 17 00:00:00 2001 From: zzz Date: Mon, 9 Sep 2013 19:46:24 +0000 Subject: [PATCH 11/11] - Simple DirKeyRing backend for testing --- core/java/src/net/i2p/crypto/DirKeyRing.java | 41 ++++++++++++++++++++ core/java/src/net/i2p/crypto/SU3File.java | 26 ++++++++++--- 2 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 core/java/src/net/i2p/crypto/DirKeyRing.java diff --git a/core/java/src/net/i2p/crypto/DirKeyRing.java b/core/java/src/net/i2p/crypto/DirKeyRing.java new file mode 100644 index 000000000..00ac5d098 --- /dev/null +++ b/core/java/src/net/i2p/crypto/DirKeyRing.java @@ -0,0 +1,41 @@ +package net.i2p.crypto; + +/* + * free (adj.): unencumbered; not under the control of others + * No warranty of any kind, either expressed or implied. + */ + +import java.io.File; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.PublicKey; + +import net.i2p.data.SigningPublicKey; + +/** + * Dumb storage in a directory for testing. + * No sanitization of filenames, unsafe. + * + * @since 0.9.9 + */ +class DirKeyRing implements KeyRing { + + private final File _base; + + public DirKeyRing(File baseDir) { + _base = baseDir; + } + + public SigningPublicKey getKey(String keyName, String scope, SigType type) + throws GeneralSecurityException, IOException { + File sd = new File(_base, scope); + File td = new File(sd, Integer.toString(type.getCode())); + File kd = new File(td, keyName + ".key"); + if (!kd.exists()) + return null; + PublicKey pk = SigUtil.importJavaPublicKey(kd, type); + return SigUtil.fromJavaKey(pk, type); + } + + public void setKey(String keyName, String scope, SigningPublicKey key) {} +} diff --git a/core/java/src/net/i2p/crypto/SU3File.java b/core/java/src/net/i2p/crypto/SU3File.java index dfb88ee6c..00f3905ca 100644 --- a/core/java/src/net/i2p/crypto/SU3File.java +++ b/core/java/src/net/i2p/crypto/SU3File.java @@ -27,6 +27,7 @@ import net.i2p.data.Signature; import net.i2p.data.SigningPrivateKey; import net.i2p.data.SigningPublicKey; import net.i2p.data.SimpleDataStructure; +import net.i2p.util.HexDump; import net.i2p.util.SecureFileOutputStream; /** @@ -76,7 +77,8 @@ public class SU3File { * Uses TrustedUpdate's default keys for verification. */ public SU3File(File file) { - this(file, (new TrustedUpdate()).getKeys()); + //this(file, (new TrustedUpdate()).getKeys()); + this(file, null); } /** @@ -190,9 +192,19 @@ public class SU3File { break; } } - if (_signerPubkey == null) - throw new IOException("unknown signer: " + _signer); + } else { + // testing + KeyRing ring = new DirKeyRing(new File("su3keyring")); + try { + _signerPubkey = ring.getKey(_signer, "default", _sigType); + } catch (GeneralSecurityException gse) { + IOException ioe = new IOException("keystore error"); + ioe.initCause(gse); + throw ioe; + } } + if (_signerPubkey == null) + throw new IOException("unknown signer: " + _signer); _headerVerified = true; } @@ -268,6 +280,8 @@ public class SU3File { signature.readBytes(in); SimpleDataStructure hash = _sigType.getHashInstance(); hash.setData(sha); + //System.out.println("hash\n" + HexDump.dump(sha)); + //System.out.println("sig\n" + HexDump.dump(signature.getData())); rv = _context.dsa().verifySignature(signature, hash, _signerPubkey); } catch (DataFormatException dfe) { IOException ioe = new IOException("foo"); @@ -350,6 +364,8 @@ public class SU3File { SimpleDataStructure hash = sigType.getHashInstance(); hash.setData(sha); Signature signature = _context.dsa().sign(hash, privkey); + //System.out.println("hash\n" + HexDump.dump(sha)); + //System.out.println("sig\n" + HexDump.dump(signature.getData())); signature.writeBytes(out); ok = true; } catch (DataFormatException dfe) { @@ -513,9 +529,9 @@ public class SU3File { //// fixme boolean isValidSignature = file.verifyAndMigrate(new File("/dev/null")); if (isValidSignature) - System.out.println("Signature VALID (signed by " + file.getSignerString() + ')'); + System.out.println("Signature VALID (signed by " + file.getSignerString() + ' ' + file._sigType + ')'); else - System.out.println("Signature INVALID (signed by " + file.getSignerString() + ')'); + System.out.println("Signature INVALID (signed by " + file.getSignerString() + ' ' + file._sigType +')'); return isValidSignature; } catch (IOException ioe) { System.out.println("Error verifying input file '" + signedFile + "'");