Used API compatible with PCKS11 (e.g., smartcards); refactored
parent
b839447f87
commit
baba4df3a9
|
@ -2,9 +2,12 @@ package meerkat.crypto;
|
||||||
|
|
||||||
import com.google.protobuf.Message;
|
import com.google.protobuf.Message;
|
||||||
|
|
||||||
|
import javax.security.auth.callback.CallbackHandler;
|
||||||
|
import javax.security.auth.callback.PasswordCallback;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.KeyStore;
|
||||||
import java.security.SignatureException;
|
import java.security.SignatureException;
|
||||||
import java.security.UnrecoverableKeyException;
|
import java.security.UnrecoverableKeyException;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
|
@ -24,7 +27,7 @@ public interface DigitalSignature {
|
||||||
* This will consume the entire stream.
|
* This will consume the entire stream.
|
||||||
* Certificates can be either DER-encoded (binary) or PEM (base64) encoded.
|
* Certificates can be either DER-encoded (binary) or PEM (base64) encoded.
|
||||||
* This may be called multiple times to load several different certificates.
|
* This may be called multiple times to load several different certificates.
|
||||||
* It must be called before calling {@link #verify(Signature, List)}.
|
* It must be called before calling {@link #verify()}.
|
||||||
* @param certStream source from which certificates are loaded
|
* @param certStream source from which certificates are loaded
|
||||||
* @throws CertificateException on parsing errors
|
* @throws CertificateException on parsing errors
|
||||||
*/
|
*/
|
||||||
|
@ -36,35 +39,55 @@ public interface DigitalSignature {
|
||||||
*/
|
*/
|
||||||
public void clearVerificationCertificates();
|
public void clearVerificationCertificates();
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verify a signature on a list of messages.
|
* Add msg to the content stream to be verified / signed. Each message is always (automatically)
|
||||||
* The signature is computed on the serialized messages, separated with
|
* prepended with its length as a 32-bit unsigned integer in network byte order.
|
||||||
* the {@link Digest#CONCAT_MARKER} string.
|
|
||||||
* For the signature to be valid, the verification key corresponding
|
|
||||||
* to the signature must have been loaded using {@link #loadVerificationCertificates(InputStream)};
|
|
||||||
* otherwise a CertificateException is thrown
|
|
||||||
*
|
*
|
||||||
* @param sig The signature on the messages
|
* @param msg
|
||||||
* @param msgs The messages themselves
|
* @throws SignatureException
|
||||||
* @return
|
|
||||||
* @throws CertificateException the certificate required for verification was not loaded.
|
|
||||||
*/
|
*/
|
||||||
public boolean verify(Signature sig, List<Message> msgs)
|
public void updateContent(Message msg) throws SignatureException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign the content that was added.
|
||||||
|
* @return
|
||||||
|
* @throws SignatureException
|
||||||
|
*/
|
||||||
|
Signature sign() throws SignatureException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the verifier with the certificate whose Id is in sig.
|
||||||
|
* @param sig
|
||||||
|
* @throws CertificateException
|
||||||
|
* @throws InvalidKeyException
|
||||||
|
*/
|
||||||
|
void initVerify(Signature sig)
|
||||||
throws CertificateException, InvalidKeyException;
|
throws CertificateException, InvalidKeyException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads a private signing key. The certificate must be in PKCS12 format and include both
|
* Verify the updated content using the initialized signature.
|
||||||
* the public and private keys.
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean verify();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a private signing key. The keystore must include both the public and private
|
||||||
|
* key parts.
|
||||||
* This method must be called before calling {@link #sign(List)}
|
* This method must be called before calling {@link #sign(List)}
|
||||||
* Calling this method again will replace the key.
|
* Calling this method again will replace the key.
|
||||||
*
|
*
|
||||||
* @param keyStream
|
* @param keyStoreBuilder A keystore builder that can be used to load a keystore.
|
||||||
*/
|
*/
|
||||||
public void loadSigningCertificate(InputStream keyStream, char[] password)
|
public void loadSigningCertificate(KeyStore.Builder keyStoreBuilder)
|
||||||
throws IOException, CertificateException, UnrecoverableKeyException;
|
throws IOException, CertificateException, UnrecoverableKeyException;
|
||||||
|
|
||||||
public Signature sign(List<Message> msg) throws SignatureException;
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the signing key (will require authentication to use again).
|
||||||
|
*/
|
||||||
public void clearSigningKey();
|
public void clearSigningKey();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,16 @@
|
||||||
package meerkat.crypto.concrete;
|
package meerkat.crypto.concrete;
|
||||||
|
|
||||||
import java.io.IOError;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.security.*;
|
import java.security.*;
|
||||||
|
import java.security.cert.*;
|
||||||
import java.security.cert.Certificate;
|
import java.security.cert.Certificate;
|
||||||
import java.security.cert.CertificateException;
|
|
||||||
import java.security.cert.CertificateFactory;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
import meerkat.crypto.Digest;
|
import meerkat.crypto.Digest;
|
||||||
import meerkat.protobuf.Crypto;
|
import meerkat.protobuf.Crypto;
|
||||||
|
import meerkat.util.Hex;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -22,12 +19,15 @@ import com.google.protobuf.Message;
|
||||||
import meerkat.crypto.DigitalSignature;
|
import meerkat.crypto.DigitalSignature;
|
||||||
import meerkat.protobuf.Crypto.Signature;
|
import meerkat.protobuf.Crypto.Signature;
|
||||||
|
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
import javax.security.auth.callback.Callback;
|
||||||
|
import javax.security.auth.callback.CallbackHandler;
|
||||||
|
import javax.security.auth.callback.PasswordCallback;
|
||||||
|
import javax.security.auth.callback.UnsupportedCallbackException;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sign and verify digital signatures.
|
* Sign and verify digital signatures.
|
||||||
* <p>
|
* <p/>
|
||||||
* This class is not thread-safe (each thread should have its own instance).
|
* This class is not thread-safe (each thread should have its own instance).
|
||||||
*/
|
*/
|
||||||
public class ECDSASignature extends GlobalCryptoSetup implements DigitalSignature {
|
public class ECDSASignature extends GlobalCryptoSetup implements DigitalSignature {
|
||||||
|
@ -38,27 +38,39 @@ public class ECDSASignature extends GlobalCryptoSetup implements DigitalSignatu
|
||||||
|
|
||||||
SHA256Digest digest = new SHA256Digest();
|
SHA256Digest digest = new SHA256Digest();
|
||||||
|
|
||||||
Map<ByteString, PublicKey> loadedCertificates = new HashMap<>();
|
Map<ByteString, Certificate> loadedCertificates = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signature currently loaded (will be used in calls to {@link #verify()}).
|
||||||
|
*/
|
||||||
|
ByteString loadedSignature = null;
|
||||||
|
|
||||||
ByteString loadedSigningKeyId = null;
|
ByteString loadedSigningKeyId = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The actual signing implementation.
|
* The actual signing implementation. (used for both signing and verifying)
|
||||||
*/
|
*/
|
||||||
java.security.Signature signer;
|
java.security.Signature signer;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute a fingerprint of a public key as a SHA256 hash.
|
* Compute a fingerprint of a cert as a SHA256 hash.
|
||||||
*
|
*
|
||||||
* @param pubKey
|
* @param cert
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public ByteString computePublicKeyID(PublicKey pubKey) {
|
public ByteString computeCertificateFingerprint(Certificate cert) {
|
||||||
|
try {
|
||||||
digest.reset();
|
digest.reset();
|
||||||
byte[] data = pubKey.getEncoded();
|
byte[] data = cert.getEncoded();
|
||||||
digest.update(data);
|
digest.update(data);
|
||||||
return ByteString.copyFrom(digest.digest());
|
return ByteString.copyFrom(digest.digest());
|
||||||
|
} catch (CertificateEncodingException e) {
|
||||||
|
// Shouldn't happen
|
||||||
|
logger.error("Certificate encoding error", e);
|
||||||
|
return ByteString.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ECDSASignature(java.security.Signature signer) {
|
public ECDSASignature(java.security.Signature signer) {
|
||||||
|
@ -70,7 +82,7 @@ public class ECDSASignature extends GlobalCryptoSetup implements DigitalSignatu
|
||||||
this.signer = java.security.Signature.getInstance(DEFAULT_SIGNATURE_ALGORITHM);
|
this.signer = java.security.Signature.getInstance(DEFAULT_SIGNATURE_ALGORITHM);
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
// Should never happen
|
// Should never happen
|
||||||
logger.error("Couldn't find implementation for {} signatures: {}", DEFAULT_SIGNATURE_ALGORITHM, e);
|
logger.error("Couldn't find implementation for " + DEFAULT_SIGNATURE_ALGORITHM + " signatures", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,8 +98,8 @@ public class ECDSASignature extends GlobalCryptoSetup implements DigitalSignatu
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
PublicKey pubKey = cert.getPublicKey();
|
PublicKey pubKey = cert.getPublicKey();
|
||||||
ByteString keyId = computePublicKeyID(pubKey);
|
ByteString keyId = computeCertificateFingerprint(cert);
|
||||||
loadedCertificates.put(keyId, pubKey);
|
loadedCertificates.put(keyId, cert);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,24 +109,29 @@ public class ECDSASignature extends GlobalCryptoSetup implements DigitalSignatu
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void updateSigner(List<Message> msgs) throws SignatureException {
|
/**
|
||||||
if (msgs == null)
|
* Add the list of messages to the stream that is being verified/signed.
|
||||||
return;
|
* Messages are separated with {@link Digest#CONCAT_MARKER}
|
||||||
boolean first = true;
|
*
|
||||||
for (Message msg : msgs) {
|
* @param msg
|
||||||
if (!first) {
|
* @throws SignatureException
|
||||||
signer.update(Digest.CONCAT_MARKER);
|
*/
|
||||||
}
|
@Override
|
||||||
first = false;
|
public void updateContent(Message msg) throws SignatureException {
|
||||||
|
assert msg != null;
|
||||||
|
int len = msg.getSerializedSize();
|
||||||
|
|
||||||
|
byte[] lenBytes = { (byte) ((len >>> 24) & 0xff), (byte) ((len >>> 16) & 0xff), (byte) ((len >>> 8) & 0xff), (byte) (len & 0xff) };
|
||||||
|
signer.update(lenBytes);
|
||||||
signer.update(msg.toByteString().asReadOnlyByteBuffer());
|
signer.update(msg.toByteString().asReadOnlyByteBuffer());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void updateSigner(InputStream in) throws IOException, SignatureException {
|
public void updateSigner(InputStream in) throws IOException, SignatureException {
|
||||||
ByteString inStr = ByteString.readFrom(in);
|
ByteString inStr = ByteString.readFrom(in);
|
||||||
signer.update(inStr.asReadOnlyByteBuffer());
|
signer.update(inStr.asReadOnlyByteBuffer());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Signature sign() throws SignatureException {
|
public Signature sign() throws SignatureException {
|
||||||
Signature.Builder sig = Signature.newBuilder();
|
Signature.Builder sig = Signature.newBuilder();
|
||||||
sig.setType(Crypto.SignatureType.ECDSA);
|
sig.setType(Crypto.SignatureType.ECDSA);
|
||||||
|
@ -123,105 +140,121 @@ public class ECDSASignature extends GlobalCryptoSetup implements DigitalSignatu
|
||||||
return sig.build();
|
return sig.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void initVerify(Signature sig)
|
public void initVerify(Signature sig)
|
||||||
throws CertificateException, InvalidKeyException {
|
throws CertificateException, InvalidKeyException {
|
||||||
PublicKey pubKey = loadedCertificates.get(sig.getSignerId());
|
Certificate cert = loadedCertificates.get(sig.getSignerId());
|
||||||
if (pubKey == null) {
|
if (cert == null) {
|
||||||
logger.warn("No public key loaded for ID {}!", sig.getSignerId());
|
logger.warn("No certificate loaded for ID {}!", sig.getSignerId());
|
||||||
throw new CertificateException("No public key loaded for " + sig.getSignerId());
|
throw new CertificateException("No certificate loaded for " + sig.getSignerId());
|
||||||
}
|
|
||||||
signer.initVerify(pubKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean verify(Signature sig) {
|
|
||||||
try {
|
|
||||||
return signer.verify(sig.getData().toByteArray());
|
|
||||||
} catch (SignatureException e) {
|
|
||||||
// Should never happen!
|
|
||||||
logger.error("Signature exception: {}", e);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
signer.initVerify(cert.getPublicKey());
|
||||||
|
loadedSignature = sig.getData();
|
||||||
|
loadedSigningKeyId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean verify(Signature sig, List<Message> msgs)
|
public boolean verify() {
|
||||||
throws CertificateException {
|
|
||||||
try {
|
try {
|
||||||
initVerify(sig);
|
return signer.verify(loadedSignature.toByteArray());
|
||||||
updateSigner(msgs);
|
|
||||||
return verify(sig);
|
|
||||||
} catch (InvalidKeyException e) {
|
|
||||||
logger.error("Invalid key exception: {}", e);
|
|
||||||
return false;
|
|
||||||
} catch (SignatureException e) {
|
} catch (SignatureException e) {
|
||||||
// Should never happen!
|
// Happens only if signature is invalid!
|
||||||
logger.error("Signature exception: {}", e);
|
logger.error("Signature exception", e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility method to more easily deal with simple password-protected files.
|
||||||
|
*
|
||||||
|
* @param password
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public CallbackHandler getFixedPasswordHandler(final char[] password) {
|
||||||
|
return new CallbackHandler() {
|
||||||
@Override
|
@Override
|
||||||
public void loadSigningCertificate(InputStream keyStream, char[] password)
|
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
|
||||||
|
for (Callback callback : callbacks) {
|
||||||
|
if (callback instanceof PasswordCallback) {
|
||||||
|
PasswordCallback passwordCallback = (PasswordCallback) callback;
|
||||||
|
logger.debug("Requested password ({})", passwordCallback.getPrompt());
|
||||||
|
passwordCallback.setPassword(password);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a keystore from an input stream in PKCS12 format.
|
||||||
|
*
|
||||||
|
* @param keyStream
|
||||||
|
* @param password
|
||||||
|
* @return
|
||||||
|
* @throws IOException
|
||||||
|
* @throws CertificateException
|
||||||
|
* @throws KeyStoreException
|
||||||
|
* @throws NoSuchAlgorithmException
|
||||||
|
*/
|
||||||
|
public KeyStore.Builder getPKCS12KeyStoreBuilder(InputStream keyStream, char[] password)
|
||||||
|
throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException {
|
||||||
|
KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE);
|
||||||
|
keyStore.load(keyStream, password);
|
||||||
|
return KeyStore.Builder.newInstance(keyStore, new KeyStore.CallbackHandlerProtection(getFixedPasswordHandler(password)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For now we only support PKCS12.
|
||||||
|
* TODO: Support for PKCS11 as well.
|
||||||
|
*
|
||||||
|
* @param keyStoreBuilder
|
||||||
|
* @throws IOException
|
||||||
|
* @throws CertificateException
|
||||||
|
* @throws UnrecoverableKeyException
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void loadSigningCertificate(KeyStore.Builder keyStoreBuilder)
|
||||||
throws IOException, CertificateException, UnrecoverableKeyException {
|
throws IOException, CertificateException, UnrecoverableKeyException {
|
||||||
try {
|
try {
|
||||||
KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE);
|
|
||||||
|
|
||||||
keyStore.load(keyStream, password);
|
KeyStore keyStore = keyStoreBuilder.getKeyStore();
|
||||||
|
|
||||||
KeyStore.ProtectionParameter protParam =
|
|
||||||
new KeyStore.PasswordProtection(password);
|
|
||||||
|
|
||||||
// Iterate through all aliases until we find the first privatekey
|
// Iterate through all aliases until we find the first privatekey
|
||||||
Enumeration<String> aliases = keyStore.aliases();
|
Enumeration<String> aliases = keyStore.aliases();
|
||||||
while (aliases.hasMoreElements()) {
|
while (aliases.hasMoreElements()) {
|
||||||
String alias = aliases.nextElement();
|
String alias = aliases.nextElement();
|
||||||
|
logger.trace("Testing keystore entry {}", alias);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
KeyStore.Entry entry = keyStore.getEntry(alias, protParam);
|
Certificate cert = keyStore.getCertificate(alias);
|
||||||
if (entry instanceof KeyStore.PrivateKeyEntry) {
|
logger.trace("keystore entry {}, has cert type {}", alias, cert.getClass());
|
||||||
KeyStore.PrivateKeyEntry privEntry = (KeyStore.PrivateKeyEntry) entry;
|
Key key = keyStore.getKey(alias, null);
|
||||||
PrivateKey privKey = privEntry.getPrivateKey();
|
logger.trace("keystore entry {}, has key type {}", alias, key.getClass());
|
||||||
PublicKey pubKey = privEntry.getCertificate().getPublicKey();
|
if (key instanceof PrivateKey) {
|
||||||
loadedSigningKeyId = computePublicKeyID(pubKey);
|
loadedSigningKeyId = computeCertificateFingerprint(cert);
|
||||||
signer.initSign(privKey);
|
signer.initSign((PrivateKey) key);
|
||||||
|
logger.debug("Loaded signing key with ID {}", Hex.encode(loadedSigningKeyId));
|
||||||
return;
|
return;
|
||||||
|
} else {
|
||||||
|
logger.info("Certificate {} in keystore does not have a private key", cert.toString());
|
||||||
}
|
}
|
||||||
} catch(InvalidKeyException e) {
|
} catch(InvalidKeyException e) {
|
||||||
logger.info("Read invalid key: {}", e);
|
logger.info("Read invalid key", e);
|
||||||
} catch(UnrecoverableEntryException e) {
|
} catch(UnrecoverableEntryException e) {
|
||||||
logger.info("Read unrecoverable entry: {}", e);
|
logger.info("Read unrecoverable entry", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
} catch (KeyStoreException e) {
|
} catch (KeyStoreException e) {
|
||||||
logger.error("Keystore exception: {}", e);
|
logger.error("Keystore exception", e);
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
logger.error("NoSuchAlgorithmException exception: {}", e);
|
logger.error("NoSuchAlgorithmException exception", e);
|
||||||
throw new CertificateException(e);
|
throw new CertificateException(e);
|
||||||
}
|
}
|
||||||
throw new UnrecoverableKeyException("Didn't find valid private key entry in stream!");
|
logger.error("Didn't find valid private key entry in keystore");
|
||||||
}
|
throw new UnrecoverableKeyException("Didn't find valid private key entry in keystore!");
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Signature sign(List<Message> msgs) {
|
|
||||||
try {
|
|
||||||
updateSigner(msgs);
|
|
||||||
return sign();
|
|
||||||
} catch (SignatureException e) {
|
|
||||||
logger.error("Signature exception: {}", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Signature sign(InputStream in) throws IOException {
|
|
||||||
try {
|
|
||||||
updateSigner(in);
|
|
||||||
return sign();
|
|
||||||
} catch (SignatureException e) {
|
|
||||||
logger.error("Signature exception: {}", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearSigningKey() {
|
public void clearSigningKey() {
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
package meerkat.util;
|
||||||
|
|
||||||
|
import com.google.protobuf.ByteString;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert to/from Hex
|
||||||
|
*/
|
||||||
|
public class Hex {
|
||||||
|
/**
|
||||||
|
* Encode a {@link ByteString} as a hex string.
|
||||||
|
* @param str
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String encode(ByteString str) {
|
||||||
|
StringBuilder s = new StringBuilder();
|
||||||
|
for (byte b : str) {
|
||||||
|
s.append(Integer.toHexString(((int) b) & 0xff));
|
||||||
|
}
|
||||||
|
return s.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String encode(byte[] bytes) {
|
||||||
|
return encode(ByteString.copyFrom(bytes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ def logOps = System.getProperty("log.ops") != null
|
||||||
appender("CONSOLE", ConsoleAppender) {
|
appender("CONSOLE", ConsoleAppender) {
|
||||||
|
|
||||||
filter(ThresholdFilter) {
|
filter(ThresholdFilter) {
|
||||||
level = toLevel(System.getProperty("log.level"), DEBUG)
|
level = toLevel(System.getProperty("log.level"), TRACE)
|
||||||
}
|
}
|
||||||
|
|
||||||
encoder(PatternLayoutEncoder) {
|
encoder(PatternLayoutEncoder) {
|
||||||
|
|
|
@ -2,10 +2,16 @@ package meerkat.crypto.concrete;
|
||||||
|
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
import meerkat.protobuf.Crypto;
|
import meerkat.protobuf.Crypto;
|
||||||
|
import meerkat.protobuf.Voting;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -21,15 +27,18 @@ public class TestECDSASignature {
|
||||||
public static String MSG_PLAINTEXT_EXAMPLE = "/certs/signed-messages/helloworld.txt";
|
public static String MSG_PLAINTEXT_EXAMPLE = "/certs/signed-messages/helloworld.txt";
|
||||||
public static String MSG_SIG_EXAMPLE = "/certs/signed-messages/helloworld.txt.sha256sig";
|
public static String MSG_SIG_EXAMPLE = "/certs/signed-messages/helloworld.txt.sha256sig";
|
||||||
|
|
||||||
|
public static String HELLO_WORLD = "hello world!";
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLoadSignatureKey() throws Exception {
|
public void loadSignatureKey() throws Exception {
|
||||||
InputStream keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE);
|
InputStream keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE);
|
||||||
char[] password = KEYFILE_PASSWORD.toCharArray();
|
char[] password = KEYFILE_PASSWORD.toCharArray();
|
||||||
|
|
||||||
ECDSASignature sig = new ECDSASignature();
|
ECDSASignature sig = new ECDSASignature();
|
||||||
|
|
||||||
sig.loadSigningCertificate(keyStream, password);
|
KeyStore.Builder keyStore = sig.getPKCS12KeyStoreBuilder(keyStream, password);
|
||||||
|
sig.loadSigningCertificate(keyStore);
|
||||||
keyStream.close();
|
keyStream.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +82,91 @@ public class TestECDSASignature {
|
||||||
Crypto.Signature builtSig = sig.build();
|
Crypto.Signature builtSig = sig.build();
|
||||||
signer.initVerify(builtSig);
|
signer.initVerify(builtSig);
|
||||||
signer.updateSigner(msgStream);
|
signer.updateSigner(msgStream);
|
||||||
assertTrue("Signature did not verify!", signer.verify(builtSig));
|
assertTrue("Signature did not verify!", signer.verify());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void verifyInvalidSig() throws Exception {
|
||||||
|
InputStream certStream = getClass().getResourceAsStream(CERT1_PEM_EXAMPLE);
|
||||||
|
InputStream msgStream = getClass().getResourceAsStream(MSG_PLAINTEXT_EXAMPLE);
|
||||||
|
InputStream sigStream = getClass().getResourceAsStream(MSG_SIG_EXAMPLE);
|
||||||
|
|
||||||
|
ECDSASignature signer = new ECDSASignature();
|
||||||
|
|
||||||
|
signer.loadVerificationCertificates(certStream);
|
||||||
|
certStream.close();
|
||||||
|
|
||||||
|
Crypto.Signature.Builder sig = Crypto.Signature.newBuilder();
|
||||||
|
sig.setType(Crypto.SignatureType.ECDSA);
|
||||||
|
sig.setSignerId(signer.loadedCertificates.entrySet().iterator().next().getKey());
|
||||||
|
byte[] sigData = ByteString.readFrom(sigStream).toByteArray();
|
||||||
|
++sigData[0];
|
||||||
|
|
||||||
|
sig.setData(ByteString.copyFrom(sigData));
|
||||||
|
|
||||||
|
|
||||||
|
Crypto.Signature builtSig = sig.build();
|
||||||
|
signer.initVerify(builtSig);
|
||||||
|
signer.updateSigner(msgStream);
|
||||||
|
assertFalse("Bad Signature passed verification!", signer.verify());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void verifyInvalidMsg() throws Exception {
|
||||||
|
InputStream certStream = getClass().getResourceAsStream(CERT1_PEM_EXAMPLE);
|
||||||
|
InputStream msgStream = getClass().getResourceAsStream(MSG_PLAINTEXT_EXAMPLE);
|
||||||
|
InputStream sigStream = getClass().getResourceAsStream(MSG_SIG_EXAMPLE);
|
||||||
|
|
||||||
|
ECDSASignature signer = new ECDSASignature();
|
||||||
|
|
||||||
|
signer.loadVerificationCertificates(certStream);
|
||||||
|
certStream.close();
|
||||||
|
|
||||||
|
Crypto.Signature.Builder sig = Crypto.Signature.newBuilder();
|
||||||
|
sig.setType(Crypto.SignatureType.ECDSA);
|
||||||
|
sig.setSignerId(signer.loadedCertificates.entrySet().iterator().next().getKey());
|
||||||
|
sig.setData(ByteString.readFrom(sigStream));
|
||||||
|
byte[] msgData = ByteString.readFrom(msgStream).toByteArray();
|
||||||
|
++msgData[0];
|
||||||
|
|
||||||
|
Crypto.Signature builtSig = sig.build();
|
||||||
|
signer.initVerify(builtSig);
|
||||||
|
signer.updateSigner(msgStream);
|
||||||
|
assertFalse("Signature doesn't match message but passed verification!", signer.verify());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void signAndVerify() throws Exception {
|
||||||
|
InputStream keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE);
|
||||||
|
char[] password = KEYFILE_PASSWORD.toCharArray();
|
||||||
|
|
||||||
|
ECDSASignature signer = new ECDSASignature();
|
||||||
|
|
||||||
|
KeyStore.Builder keyStore = signer.getPKCS12KeyStoreBuilder(keyStream, password);
|
||||||
|
signer.loadSigningCertificate(keyStore);
|
||||||
|
|
||||||
|
|
||||||
|
Voting.UnsignedBulletinBoardMessage.Builder unsignedMsgBuilder = Voting.UnsignedBulletinBoardMessage.newBuilder();
|
||||||
|
unsignedMsgBuilder.setData(ByteString.copyFromUtf8(HELLO_WORLD));
|
||||||
|
unsignedMsgBuilder.addTags("Tag1");
|
||||||
|
unsignedMsgBuilder.addTags("Tag2");
|
||||||
|
unsignedMsgBuilder.addTags("Tag3");
|
||||||
|
|
||||||
|
Voting.UnsignedBulletinBoardMessage usMsg = unsignedMsgBuilder.build();
|
||||||
|
|
||||||
|
signer.updateContent(usMsg);
|
||||||
|
Crypto.Signature sig = signer.sign();
|
||||||
|
|
||||||
|
signer.loadVerificationCertificates(getClass().getResourceAsStream(CERT1_PEM_EXAMPLE));
|
||||||
|
|
||||||
|
signer.initVerify(sig);
|
||||||
|
signer.updateContent(usMsg);
|
||||||
|
assertTrue("Couldn't verify signature on ", signer.verify());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue