package freenet.crypt; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.Provider; import java.security.PublicKey; import java.security.interfaces.ECPublicKey; import java.security.spec.ECGenParameterSpec; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Arrays; import javax.crypto.KeyAgreement; import freenet.crypt.JceLoader; import freenet.support.Logger; public class ECDH { public final Curves curve; private final KeyPair key; public enum Curves { // rfc5903 or rfc6460: it's NIST's random/prime curves : suite B // Order matters. Append to the list, do not re-order. P256("secp256r1", 91, 32), P384("secp384r1", 120, 48), P521("secp521r1", 158, 66); public final ECGenParameterSpec spec; private KeyPairGenerator keygenCached; protected final Provider kgProvider; protected final Provider kfProvider; protected final Provider kaProvider; /** Expected size of a pubkey */ public final int modulusSize; /** Expected size of the derived secret (in bytes) */ public final int derivedSecretSize; /** Verify KeyPairGenerator and KeyFactory work correctly */ static private KeyPair selftest(KeyPairGenerator kg, KeyFactory kf, int modulusSize) throws InvalidKeySpecException { KeyPair key = kg.generateKeyPair(); PublicKey pub = key.getPublic(); PrivateKey pk = key.getPrivate(); byte [] pubkey = pub.getEncoded(); byte [] pkey = pk.getEncoded(); if(pubkey.length > modulusSize || pubkey.length == 0) throw new Error("Unexpected pubkey length: "+pubkey.length+"!="+modulusSize); PublicKey pub2 = kf.generatePublic( new X509EncodedKeySpec(pubkey) ); if(!Arrays.equals(pub2.getEncoded(), pubkey)) throw new Error("Pubkey encoding mismatch"); PrivateKey pk2 = kf.generatePrivate( new PKCS8EncodedKeySpec(pkey) ); /* if(!Arrays.equals(pk2.getEncoded(), pkey)) throw new Error("Pubkey encoding mismatch"); */ return key; } static private void selftest_genSecret(KeyPair key, KeyAgreement ka) throws InvalidKeyException { ka.init(key.getPrivate()); ka.doPhase(key.getPublic(), true); ka.generateSecret(); } private Curves(String name, int modulusSize, int derivedSecretSize) { this.spec = new ECGenParameterSpec(name); KeyAgreement ka = null; KeyFactory kf = null; KeyPairGenerator kg = null; // Ensure providers loaded JceLoader.BouncyCastle.toString(); try { KeyPair key = null; try { /* check if default EC keys work correctly */ kg = KeyPairGenerator.getInstance("EC"); kf = KeyFactory.getInstance("EC"); kg.initialize(this.spec); key = selftest(kg, kf, modulusSize); } catch(Throwable e) { /* we don't care why we fail, just fallback */ Logger.warning(this, "default KeyPairGenerator provider ("+(kg != null ? kg.getProvider() : null)+") is broken, falling back to BouncyCastle", e); kg = KeyPairGenerator.getInstance("EC", JceLoader.BouncyCastle); kf = KeyFactory.getInstance("EC", JceLoader.BouncyCastle); kg.initialize(this.spec); key = selftest(kg, kf, modulusSize); } try { /* check default KeyAgreement compatible with kf/kg */ ka = KeyAgreement.getInstance("ECDH"); selftest_genSecret(key, ka); } catch(Throwable e) { /* we don't care why we fail, just fallback */ Logger.warning(this, "default KeyAgreement provider ("+(ka != null ? ka.getProvider() : null)+") is broken or incompatible with KeyPairGenerator, falling back to BouncyCastle", e); kg = KeyPairGenerator.getInstance("EC", JceLoader.BouncyCastle); kf = KeyFactory.getInstance("EC", JceLoader.BouncyCastle); kg.initialize(this.spec); ka = KeyAgreement.getInstance("ECDH", JceLoader.BouncyCastle); selftest_genSecret(key, ka); } } catch(NoSuchAlgorithmException e) { System.out.println(e); e.printStackTrace(System.out); } catch(InvalidKeySpecException e) { System.out.println(e); e.printStackTrace(System.out); } catch(InvalidKeyException e) { System.out.println(e); e.printStackTrace(System.out); } catch(InvalidAlgorithmParameterException e) { System.out.println(e); e.printStackTrace(System.out); } this.modulusSize = modulusSize; this.derivedSecretSize = derivedSecretSize; this.kgProvider = kg.getProvider(); this.kfProvider = kf.getProvider(); this.kaProvider = ka.getProvider(); Logger.normal(this, name +": using "+kgProvider+" for KeyPairGenerator(EC)"); Logger.normal(this, name +": using "+kfProvider+" for KeyFactory(EC)"); Logger.normal(this, name +": using "+kaProvider+" for KeyAgreement(ECDH)"); } private synchronized KeyPairGenerator getKeyPairGenerator() { if(keygenCached != null) return keygenCached; KeyPairGenerator kg = null; try { kg = KeyPairGenerator.getInstance("EC", kgProvider); kg.initialize(spec); } catch (NoSuchAlgorithmException e) { Logger.error(ECDH.class, "NoSuchAlgorithmException : "+e.getMessage(),e); e.printStackTrace(); } catch (InvalidAlgorithmParameterException e) { Logger.error(ECDH.class, "InvalidAlgorithmParameterException : "+e.getMessage(),e); e.printStackTrace(); } keygenCached = kg; return kg; } public synchronized KeyPair generateKeyPair() { return getKeyPairGenerator().generateKeyPair(); } public String toString() { return spec.getName(); } } /** * Initialize the ECDH Exchange: this will draw some entropy * @param curve */ public ECDH(Curves curve) { this.curve = curve; this.key = curve.generateKeyPair(); } /** * Completes the ECDH exchange: this is CPU intensive * @param pubkey * @return a SecretKey or null if it fails * * **THE OUTPUT SHOULD ALWAYS GO THROUGH A KDF** */ public byte[] getAgreedSecret(ECPublicKey pubkey) { try { KeyAgreement ka = null; ka = KeyAgreement.getInstance("ECDH", curve.kaProvider); ka.init(key.getPrivate()); ka.doPhase(pubkey, true); return ka.generateSecret(); } catch (InvalidKeyException e) { Logger.error(this, "InvalidKeyException : "+e.getMessage(),e); e.printStackTrace(); } catch (NoSuchAlgorithmException e) { Logger.error(this, "NoSuchAlgorithmException : "+e.getMessage(),e); e.printStackTrace(); } return null; } public ECPublicKey getPublicKey() { return (ECPublicKey) key.getPublic(); } /** * Returns an ECPublicKey from bytes obtained using ECPublicKey.getEncoded() * @param data * @return ECPublicKey or null if it fails */ public static ECPublicKey getPublicKey(byte[] data, Curves curve) { ECPublicKey remotePublicKey = null; try { X509EncodedKeySpec ks = new X509EncodedKeySpec(data); KeyFactory kf = KeyFactory.getInstance("EC", curve.kfProvider); remotePublicKey = (ECPublicKey)kf.generatePublic(ks); } catch (NoSuchAlgorithmException e) { Logger.error(ECDH.class, "NoSuchAlgorithmException : "+e.getMessage(),e); e.printStackTrace(); } catch (InvalidKeySpecException e) { Logger.error(ECDH.class, "InvalidKeySpecException : "+e.getMessage(), e); e.printStackTrace(); } return remotePublicKey; } /** Initialize the key pair generators, which in turn will create the * global SecureRandom, which may block waiting for entropy from * /dev/random on unix-like systems. So this should be called on startup * during the "may block for entropy" stage. Note that because this can * block, we still have to do lazy initialisation: We do NOT want to * have it blocking *at time of loading the classes*, as this will * likely appear as the node completely failing to load. Running this * after fproxy has started, with a warning timer, allows us to tell * the user what is going on if it takes a while. */ public static void blockingInit() { Curves.P256.getKeyPairGenerator(); // Not used at present. // Anyway Bouncycastle uses a single PRNG. // If these use separate PRNGs, we need to init them explicitly. //Curves.P384.getKeyPairGenerator(); //Curves.P521.getKeyPairGenerator(); } /** Return the public key as a byte[] in network format */ public byte[] getPublicKeyNetworkFormat() { byte[] ret = getPublicKey().getEncoded(); if(ret.length == curve.modulusSize) { return ret; } else if(ret.length > curve.modulusSize) { throw new IllegalStateException("Encoded public key too long: should be "+curve.modulusSize+" bytes but is "+ret.length); } else { Logger.warning(this, "Padding public key from "+ret.length+" to "+curve.modulusSize+" bytes"); byte[] out = new byte[curve.modulusSize]; System.arraycopy(ret, 0, out, 0, ret.length); return ret; } } }