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.Signature;
import java.security.SignatureException;
import java.security.interfaces.ECPrivateKey;
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 freenet.crypt.JceLoader;
import freenet.node.FSParseException;
import freenet.support.Base64;
import freenet.support.Logger;
import freenet.support.SimpleFieldSet;
public class ECDSA {
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", "SHA256withECDSA", 91, 72),
P384("secp384r1", "SHA384withECDSA", 120, 104),
P521("secp521r1", "SHA512withECDSA", 158, 139);
public final ECGenParameterSpec spec;
private final KeyPairGenerator keygen;
/** The hash algorithm used to generate the signature */
public final String defaultHashAlgorithm;
/** Expected size of a DER encoded pubkey in bytes */
public final int modulusSize;
/** Maximum (padded) size of a DER-encoded signature (network-format) */
public final int maxSigSize;
protected final Provider kgProvider;
protected final Provider kfProvider;
protected final Provider sigProvider;
/** 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_sign(KeyPair key, Signature sig)
throws SignatureException, InvalidKeyException
{
sig.initSign(key.getPrivate());
byte[] sign = sig.sign();
sig.initVerify(key.getPublic());
boolean verified = sig.verify(sign);
if (!verified)
throw new Error("Verification failed");
}
private Curves(String name, String defaultHashAlgorithm, int modulusSize, int maxSigSize) {
this.spec = new ECGenParameterSpec(name);
Signature sig = 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 Signature compatible with kf/kg */
sig = Signature.getInstance(defaultHashAlgorithm);
selftest_sign(key, sig);
} catch(Throwable e) {
/* we don't care why we fail, just fallback */
Logger.warning(this, "default Signature provider ("+(sig != null ? sig.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);
key = kg.generateKeyPair();
sig = Signature.getInstance(defaultHashAlgorithm, JceLoader.BouncyCastle);
selftest_sign(key, sig);
}
} catch (NoSuchAlgorithmException e) {
Logger.error(ECDSA.class, "NoSuchAlgorithmException : "+e.getMessage(),e);
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
Logger.error(ECDSA.class, "InvalidAlgorithmParameterException : "+e.getMessage(),e);
e.printStackTrace();
} catch (InvalidKeyException e) {
throw new Error(e);
} catch (InvalidKeySpecException e) {
throw new Error(e);
} catch (SignatureException e) {
throw new Error(e);
}
this.kgProvider = kg.getProvider();
this.kfProvider = kf.getProvider();
this.sigProvider = sig.getProvider();
this.keygen = kg;
this.defaultHashAlgorithm = defaultHashAlgorithm;
this.modulusSize = modulusSize;
this.maxSigSize = maxSigSize;
Logger.normal(this, name +": using "+kgProvider+" for KeyPairGenerator(EC)");
Logger.normal(this, name +": using "+kfProvider+" for KeyFactory(EC)");
Logger.normal(this, name +": using "+sigProvider+" for Signature("+defaultHashAlgorithm+")");
}
public synchronized KeyPair generateKeyPair() {
return keygen.generateKeyPair();
}
public SimpleFieldSet getSFS(ECPublicKey pub) {
SimpleFieldSet ecdsaSFS = new SimpleFieldSet(true);
SimpleFieldSet curveSFS = new SimpleFieldSet(true);
curveSFS.putSingle("pub", Base64.encode(pub.getEncoded()));
ecdsaSFS.put(name(), curveSFS);
return ecdsaSFS;
}
public String toString() {
return spec.getName();
}
}
/**
* Initialize the ECDSA object: this will draw some entropy
* @param curve
*/
public ECDSA(Curves curve) {
this.curve = curve;
this.key = curve.keygen.generateKeyPair();
}
/**
* Initialize the ECDSA object: from an SFS generated by asFieldSet()
* @param curve
* @throws FSParseException
*/
public ECDSA(SimpleFieldSet sfs, Curves curve) throws FSParseException {
byte[] pub = null;
byte[] pri = null;
try {
pub = Base64.decode(sfs.get("pub"));
if (pub.length > curve.modulusSize)
throw new InvalidKeyException();
ECPublicKey pubK = getPublicKey(pub, curve);
pri = Base64.decode(sfs.get("pri"));
PKCS8EncodedKeySpec ks = new PKCS8EncodedKeySpec(pri);
KeyFactory kf = KeyFactory.getInstance("EC", curve.kfProvider);
ECPrivateKey privK = (ECPrivateKey) kf.generatePrivate(ks);
this.key = new KeyPair(pubK, privK);
} catch (Exception e) {
throw new FSParseException(e);
}
this.curve = curve;
}
public byte[] sign(byte[]... data) {
byte[] result = null;
try {
while(true) {
Signature sig = Signature.getInstance(curve.defaultHashAlgorithm, curve.sigProvider);
sig.initSign(key.getPrivate());
for(byte[] d: data)
sig.update(d);
result = sig.sign();
// It's a DER encoded signature, most sigs will fit in N bytes
// If it doesn't let's re-sign.
if(result.length <= curve.maxSigSize)
break;
else
Logger.error(this, "DER encoded signature used "+result.length+" bytes, more than expected "+curve.maxSigSize+" - re-signing...");
}
} catch (NoSuchAlgorithmException e) {
Logger.error(this, "NoSuchAlgorithmException : "+e.getMessage(),e);
e.printStackTrace();
} catch (InvalidKeyException e) {
Logger.error(this, "InvalidKeyException : "+e.getMessage(),e);
e.printStackTrace();
} catch (SignatureException e) {
Logger.error(this, "SignatureException : "+e.getMessage(),e);
e.printStackTrace();
}
return result;
}
/**
* Sign data and return a fixed size signature. The data does not need to be hashed, the
* signing code will handle that for us, using an algorithm appropriate for the keysize.
* @return A zero padded DER signature (maxSigSize). Space Inefficient but constant-size.
*/
public byte[] signToNetworkFormat(byte[]... data) {
byte[] plainsig = sign(data);
int targetLength = curve.maxSigSize;
if(plainsig.length != targetLength) {
byte[] newData = new byte[targetLength];
if(plainsig.length < targetLength) {
System.arraycopy(plainsig, 0, newData, 0, plainsig.length);
} else {
throw new IllegalStateException("Too long!");
}
plainsig = newData;
}
return plainsig;
}
public boolean verify(byte[] signature, byte[]... data) {
return verify(curve, getPublicKey(), signature, data);
}
public boolean verify(byte[] signature, int sigoffset, int siglen, byte[]... data) {
return verify(curve, getPublicKey(), signature, sigoffset, siglen, data);
}
public static boolean verify(Curves curve, ECPublicKey key, byte[] signature, byte[]... data) {
return verify(curve, key, signature, 0, signature.length, data);
}
public static boolean verify(Curves curve, ECPublicKey key, byte[] signature, int sigoffset, int siglen, byte[]... data) {
if(key == null || curve == null || signature == null || data == null)
return false;
boolean result = false;
try {
Signature sig = Signature.getInstance(curve.defaultHashAlgorithm, curve.sigProvider);
sig.initVerify(key);
for(byte[] d: data)
sig.update(d);
result = sig.verify(signature, sigoffset, siglen);
} catch (NoSuchAlgorithmException e) {
Logger.error(ECDSA.class, "NoSuchAlgorithmException : "+e.getMessage(),e);
e.printStackTrace();
} catch (InvalidKeyException e) {
Logger.error(ECDSA.class, "InvalidKeyException : "+e.getMessage(),e);
e.printStackTrace();
} catch (SignatureException e) {
Logger.error(ECDSA.class, "SignatureException : "+e.getMessage(),e);
e.printStackTrace();
}
return result;
}
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(ECDSA.class, "NoSuchAlgorithmException : "+e.getMessage(),e);
e.printStackTrace();
} catch (InvalidKeySpecException e) {
Logger.error(ECDSA.class, "InvalidKeySpecException : "+e.getMessage(), e);
e.printStackTrace();
}
return remotePublicKey;
}
/**
* Returns an SFS containing:
* - the private key
* - the public key
* - the name of the curve in use
*
* It should only be used in NodeCrypto
* @param includePrivate - include the (secret) private key
* @return SimpleFieldSet
*/
public SimpleFieldSet asFieldSet(boolean includePrivate) {
SimpleFieldSet fs = new SimpleFieldSet(true);
SimpleFieldSet fsCurve = new SimpleFieldSet(true);
fsCurve.putSingle("pub", Base64.encode(key.getPublic().getEncoded()));
if(includePrivate)
fsCurve.putSingle("pri", Base64.encode(key.getPrivate().getEncoded()));
fs.put(curve.name(), fsCurve);
return fs;
}
}