Added signature key generation to crypto API
parent
ceba09e65c
commit
7e4260b8d5
|
@ -57,7 +57,8 @@ dependencies {
|
|||
|
||||
// Crypto
|
||||
compile 'org.factcenter.qilin:qilin:1.2.+'
|
||||
compile 'org.bouncycastle:bcprov-jdk15on:1.53'
|
||||
compile 'org.bouncycastle:bcprov-jdk15on:1.57'
|
||||
compile 'org.bouncycastle:bcpkix-jdk15on:1.57' // For certificate generation
|
||||
|
||||
testCompile 'junit:junit:4.+'
|
||||
|
||||
|
|
|
@ -48,6 +48,11 @@ public class GenericBulletinBoardSignature implements BulletinBoardSignature {
|
|||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadVerificationCertificate(Crypto.SignatureVerificationKey cert) throws CertificateException {
|
||||
signer.loadVerificationCertificate(cert);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadVerificationCertificates(InputStream certStream) throws CertificateException {
|
||||
signer.loadVerificationCertificates(certStream);
|
||||
|
@ -98,6 +103,9 @@ public class GenericBulletinBoardSignature implements BulletinBoardSignature {
|
|||
return signer.getSignerID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Crypto.SignatureVerificationKey getSignerPublicKey() { return signer.getSignerPublicKey(); }
|
||||
|
||||
@Override
|
||||
public void clearSigningKey() {
|
||||
signer.clearSigningKey();
|
||||
|
|
|
@ -22,6 +22,18 @@ import static meerkat.protobuf.Crypto.*;
|
|||
public interface DigitalSignature {
|
||||
final public static String CERTIFICATE_ENCODING_X509 = "X.509";
|
||||
|
||||
|
||||
/**
|
||||
* Load a verification certificate.
|
||||
* This will be added to the existing set of verification certificates.
|
||||
* @param cert
|
||||
*
|
||||
* @throws CertificateException on parsing errors
|
||||
*/
|
||||
public void loadVerificationCertificate(SignatureVerificationKey cert)
|
||||
throws CertificateException;
|
||||
|
||||
|
||||
/**
|
||||
* Load a set of certificates from an input stream.
|
||||
* This will consume the entire stream.
|
||||
|
@ -94,6 +106,8 @@ public interface DigitalSignature {
|
|||
public KeyStore.Builder getPKCS12KeyStoreBuilder(InputStream keyStream, char[] password)
|
||||
throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Loads a private signing key. The keystore must include both the public and private
|
||||
* key parts.
|
||||
|
@ -111,9 +125,14 @@ public interface DigitalSignature {
|
|||
*/
|
||||
public ByteString getSignerID();
|
||||
|
||||
/**
|
||||
*
|
||||
* @return the signer verification key if it exists, null otherwise.
|
||||
*/
|
||||
public SignatureVerificationKey getSignerPublicKey();
|
||||
|
||||
/**
|
||||
* Clear the signing key (will require authentication to use again).
|
||||
*/
|
||||
public void clearSigningKey();
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package meerkat.crypto;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.security.*;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* A {@link DigitalSignature} that supports key generation.
|
||||
*/
|
||||
public interface DigitalSignatureGenerator extends DigitalSignature {
|
||||
/**
|
||||
* Generate a new verification key / signature key pair.
|
||||
* The keypair is loaded as the default signing key.
|
||||
*
|
||||
* @param serial serial number of certificate
|
||||
* @param notBefore earliest valid time
|
||||
* @param notAfter expiration
|
||||
* @param subjectName name corresponding to cert (will be used as "common name" in x509 cert).
|
||||
*/
|
||||
public void generateSigningCertificate(BigInteger serial, Date notBefore, Date notAfter,
|
||||
String subjectName);
|
||||
|
||||
|
||||
/**
|
||||
* Store a keypair in a given keystore.
|
||||
*
|
||||
* @param store
|
||||
* @param alias
|
||||
* @param password
|
||||
* @throws IOException
|
||||
* @throws KeyStoreException
|
||||
*/
|
||||
public void storeSignatureKeypair(KeyStore store, String alias, char[] password)
|
||||
throws IOException, KeyStoreException;
|
||||
}
|
|
@ -1,28 +1,39 @@
|
|||
package meerkat.crypto.concrete;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.*;
|
||||
import java.security.cert.*;
|
||||
import java.security.cert.Certificate;
|
||||
import java.util.*;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.Message;
|
||||
import meerkat.crypto.DigitalSignatureGenerator;
|
||||
import meerkat.protobuf.Crypto;
|
||||
import meerkat.protobuf.Crypto.Signature;
|
||||
import meerkat.util.Hex;
|
||||
import org.bouncycastle.asn1.ASN1EncodableVector;
|
||||
import org.bouncycastle.asn1.DERSequence;
|
||||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
import org.bouncycastle.asn1.x509.*;
|
||||
import org.bouncycastle.asn1.x509.Extension;
|
||||
import org.bouncycastle.cert.X509v3CertificateBuilder;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.operator.ContentSigner;
|
||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.protobuf.Message;
|
||||
|
||||
import meerkat.crypto.DigitalSignature;
|
||||
import meerkat.protobuf.Crypto.Signature;
|
||||
|
||||
import javax.security.auth.callback.Callback;
|
||||
import javax.security.auth.callback.CallbackHandler;
|
||||
import javax.security.auth.callback.PasswordCallback;
|
||||
import javax.security.auth.callback.UnsupportedCallbackException;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.*;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.*;
|
||||
import java.security.spec.ECGenParameterSpec;
|
||||
import java.util.*;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -30,7 +41,7 @@ import javax.security.auth.callback.UnsupportedCallbackException;
|
|||
*
|
||||
* This class is not thread-safe (each thread should have its own instance).
|
||||
*/
|
||||
public class ECDSASignature implements DigitalSignature {
|
||||
public class ECDSASignature implements DigitalSignatureGenerator {
|
||||
|
||||
private static GlobalCryptoSetup globalCryptoSetup = GlobalCryptoSetup.getInstance();
|
||||
|
||||
|
@ -38,6 +49,9 @@ public class ECDSASignature implements DigitalSignature {
|
|||
|
||||
final public static String KEYSTORE_TYPE = "PKCS12";
|
||||
final public static String DEFAULT_SIGNATURE_ALGORITHM = "SHA256withECDSA";
|
||||
final public static String DEFAULT_KEY_ALGORITHM = "ECDSA";
|
||||
final public static String DEFAULT_KEY_CURVE = "secp256k1";
|
||||
|
||||
|
||||
SHA256Digest certDigest = new SHA256Digest();
|
||||
|
||||
|
@ -56,6 +70,8 @@ public class ECDSASignature implements DigitalSignature {
|
|||
|
||||
ByteString loadedSigningKeyId = null;
|
||||
|
||||
Certificate loadedSigningCert = null;
|
||||
|
||||
/**
|
||||
* The actual signing implementation. (used for both signing and verifying)
|
||||
*/
|
||||
|
@ -100,19 +116,32 @@ public class ECDSASignature implements DigitalSignature {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void loadVerificationCertificate(Certificate cert) throws CertificateException {
|
||||
// Just checking
|
||||
if (!(cert instanceof X509Certificate)) {
|
||||
logger.error("Certificate must be in X509 format; got {} instead!", cert.getClass().getCanonicalName());
|
||||
return;
|
||||
}
|
||||
ByteString keyId = computeCertificateFingerprint(cert);
|
||||
loadedCertificates.put(keyId, cert);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void loadVerificationCertificate(Crypto.SignatureVerificationKey cert) throws CertificateException {
|
||||
ByteArrayInputStream certStream = new ByteArrayInputStream(cert.getData().toByteArray());
|
||||
loadVerificationCertificates(certStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadVerificationCertificates(InputStream certStream)
|
||||
throws CertificateException {
|
||||
CertificateFactory certificateFactory = CertificateFactory.getInstance(CERTIFICATE_ENCODING_X509);
|
||||
Collection<? extends Certificate> certs = certificateFactory.generateCertificates(certStream);
|
||||
for (Certificate cert : certs) {
|
||||
// Just checking
|
||||
if (!(cert instanceof X509Certificate)) {
|
||||
logger.error("Certificate must be in X509 format; got {} instead!", cert.getClass().getCanonicalName());
|
||||
continue;
|
||||
}
|
||||
ByteString keyId = computeCertificateFingerprint(cert);
|
||||
loadedCertificates.put(keyId, cert);
|
||||
loadVerificationCertificate(cert);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -225,6 +254,15 @@ public class ECDSASignature implements DigitalSignature {
|
|||
}
|
||||
|
||||
|
||||
|
||||
public void loadSigningCertificate(Certificate cert, PrivateKey key) throws InvalidKeyException, CertificateException {
|
||||
loadedSigningKey = (PrivateKey) key;
|
||||
loadedSigningKeyId = computeCertificateFingerprint(cert);
|
||||
loadedSigningCert = cert;
|
||||
signer.initSign(loadedSigningKey);
|
||||
logger.debug("Loaded signing key with ID {}", Hex.encode(loadedSigningKeyId));
|
||||
}
|
||||
|
||||
/**
|
||||
* For now we only support PKCS12.
|
||||
* TODO: Support for PKCS11 as well.
|
||||
|
@ -283,11 +321,7 @@ public class ECDSASignature implements DigitalSignature {
|
|||
}
|
||||
logger.trace("keystore entry {}, has key type {}", alias, key.getClass());
|
||||
if (key instanceof PrivateKey) {
|
||||
loadedSigningKey = (PrivateKey) key;
|
||||
loadedSigningKeyId = computeCertificateFingerprint(cert);
|
||||
signer.initSign(loadedSigningKey);
|
||||
logger.debug("Loaded signing key with ID {}", Hex.encode(loadedSigningKeyId));
|
||||
|
||||
loadSigningCertificate(cert, (PrivateKey) key);
|
||||
return;
|
||||
} else {
|
||||
logger.info("Certificate {} in keystore does not have a private key", cert.toString());
|
||||
|
@ -314,6 +348,38 @@ public class ECDSASignature implements DigitalSignature {
|
|||
return loadedSigningKeyId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Crypto.SignatureVerificationKey getSignerPublicKey() {
|
||||
Crypto.SignatureType certType;
|
||||
|
||||
if (loadedSigningCert instanceof X509Certificate) {
|
||||
X509Certificate xcert = (X509Certificate) loadedSigningCert;
|
||||
String algName = xcert.getSigAlgName();
|
||||
if (algName.endsWith("ECDSA")) {
|
||||
certType = Crypto.SignatureType.ECDSA;
|
||||
} else if (algName.endsWith("DSA")) {
|
||||
certType = Crypto.SignatureType.DSA;
|
||||
} else if (algName.endsWith("RSA")) {
|
||||
certType = Crypto.SignatureType.RSA;
|
||||
} else {
|
||||
logger.error("Unknown certificate signature algorithm: {}", algName);
|
||||
throw new RuntimeException("Unknown certificate signature algorithm: " + algName);
|
||||
}
|
||||
} else {
|
||||
logger.error("We don't know how to handle non-X509 certificates");
|
||||
throw new RuntimeException("Unknown Certificate type: " + loadedSigningCert.getClass());
|
||||
}
|
||||
try {
|
||||
return Crypto.SignatureVerificationKey.newBuilder()
|
||||
.setType(certType)
|
||||
.setData(ByteString.copyFrom(loadedSigningCert.getEncoded()))
|
||||
.build();
|
||||
} catch (CertificateEncodingException e) {
|
||||
logger.error("CertificateEncodingException: ", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearSigningKey() {
|
||||
try {
|
||||
// TODO: Check if this really clears the key from memory
|
||||
|
@ -321,6 +387,7 @@ public class ECDSASignature implements DigitalSignature {
|
|||
signer.initSign(null);
|
||||
loadedSigningKeyId = null;
|
||||
loadedSigningKey = null;
|
||||
loadedSigningCert = null;
|
||||
// Start garbage collection?
|
||||
} catch (InvalidKeyException e) {
|
||||
// Do nothing
|
||||
|
@ -328,4 +395,51 @@ public class ECDSASignature implements DigitalSignature {
|
|||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void generateSigningCertificate(BigInteger serial, Date notBefore, Date notAfter,
|
||||
String subjectName) {
|
||||
try {
|
||||
KeyPairGenerator keyGen = KeyPairGenerator.getInstance(DEFAULT_KEY_ALGORITHM);
|
||||
ECGenParameterSpec kpgparams = new ECGenParameterSpec(DEFAULT_KEY_CURVE);
|
||||
keyGen.initialize(kpgparams);
|
||||
KeyPair pair = keyGen.generateKeyPair();
|
||||
|
||||
SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(pair.getPublic().getEncoded());
|
||||
|
||||
// build a certificate generator
|
||||
X500Name subject = new X500Name("cn=" + subjectName);
|
||||
X509v3CertificateBuilder builder = new X509v3CertificateBuilder(subject, serial, notBefore, notAfter,
|
||||
subject, subPubKeyInfo);
|
||||
|
||||
// TODO: Might need to explicitly force SHA256 digest
|
||||
JcaX509ExtensionUtils extensionUtils = new JcaX509ExtensionUtils();
|
||||
|
||||
builder.addExtension(Extension.subjectKeyIdentifier, false, extensionUtils.createSubjectKeyIdentifier(pair.getPublic()));
|
||||
builder.addExtension(Extension.basicConstraints, true, new BasicConstraints(true));
|
||||
KeyUsage usage = new KeyUsage(KeyUsage.keyCertSign | KeyUsage.digitalSignature | KeyUsage.cRLSign);
|
||||
builder.addExtension(Extension.keyUsage, false, usage);
|
||||
ASN1EncodableVector purposes = new ASN1EncodableVector();
|
||||
purposes.add(KeyPurposeId.id_kp_serverAuth);
|
||||
purposes.add(KeyPurposeId.id_kp_clientAuth);
|
||||
purposes.add(KeyPurposeId.anyExtendedKeyUsage);
|
||||
builder.addExtension(Extension.extendedKeyUsage, false, new DERSequence(purposes));
|
||||
|
||||
String signatureAlgorithm = DEFAULT_SIGNATURE_ALGORITHM;
|
||||
|
||||
ContentSigner contentSigner = new JcaContentSignerBuilder(signatureAlgorithm).build(pair.getPrivate());
|
||||
X509Certificate cert = new JcaX509CertificateConverter().getCertificate(builder.build(contentSigner));
|
||||
|
||||
loadSigningCertificate(cert, pair.getPrivate());
|
||||
} catch (Exception e) {
|
||||
// Convert all checked exceptions into runtime exceptions.
|
||||
// TODO: Handle exceptions more elegantly.
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeSignatureKeypair(KeyStore store, String alias, char[] password) throws IOException, KeyStoreException {
|
||||
Certificate chain[] = { loadedSigningCert };
|
||||
store.setKeyEntry(alias, loadedSigningKey, password, chain);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,10 +46,14 @@ public final class GlobalCryptoSetup {
|
|||
bouncyCastleProvider = new BouncyCastleProvider();
|
||||
// Make bouncycastle our default provider if we're running on a JVM version < 8
|
||||
// (earlier version don't support the EC curve we use for signatures)
|
||||
if (!hasSecp256k1Curve() && !loadedBouncyCastle) {
|
||||
if (!loadedBouncyCastle) {
|
||||
loadedBouncyCastle = true;
|
||||
Security.insertProviderAt(bouncyCastleProvider, 1);
|
||||
logger.info("Using BouncyCastle instead of native provider to support secp256k1 named curve");
|
||||
if (!hasSecp256k1Curve()) {
|
||||
Security.insertProviderAt(bouncyCastleProvider, 1);
|
||||
logger.info("Using BouncyCastle as default instead of native provider to support secp256k1 named curve");
|
||||
} else {
|
||||
Security.addProvider(bouncyCastleProvider);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ option java_package = "meerkat.protobuf";
|
|||
enum SignatureType {
|
||||
ECDSA = 0;
|
||||
DSA = 1;
|
||||
RSA = 2;
|
||||
}
|
||||
|
||||
message BigInteger {
|
||||
|
|
|
@ -9,6 +9,7 @@ import org.junit.Test;
|
|||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.security.KeyStore;
|
||||
import java.util.Date;
|
||||
import java.util.Random;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
@ -215,5 +216,24 @@ public class ECDSASignatureTest {
|
|||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void generateKeysSignAndVerify() throws Exception {
|
||||
signer.generateSigningCertificate(BigInteger.ONE, new Date(), new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 365), "testing");
|
||||
|
||||
Crypto.SignatureVerificationKey verificationKey = signer.getSignerPublicKey();
|
||||
|
||||
signer.loadVerificationCertificate(verificationKey);
|
||||
|
||||
BigInteger rawMsg = new BigInteger(50, rand);
|
||||
Crypto.BigInteger usMsg = Crypto.BigInteger.newBuilder()
|
||||
.setData(ByteString.copyFrom(rawMsg.toByteArray())).build();
|
||||
|
||||
signer.updateContent(usMsg);
|
||||
Crypto.Signature sig = signer.sign();
|
||||
|
||||
signer.initVerify(sig);
|
||||
signer.updateContent(usMsg);
|
||||
assertTrue("Couldn't verify signature on ", signer.verify());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue