Merged encryption and signature work from crypto-primitives

Bulletin_Board_Server_phase_1
Tal Moran 2015-11-23 02:20:17 +02:00
commit ad121a7bfd
19 changed files with 733 additions and 61 deletions

View File

@ -103,7 +103,6 @@ idea {
def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java"
println "Adding $srcDir"
// add protobuf generated sources to generated source dir.
if ("test".equals(sourceSet.name)) {
testSourceDirs += file(srcDir)

View File

@ -17,7 +17,6 @@ import com.google.protobuf.ByteString;
import meerkat.comm.CommunicationException;
import meerkat.crypto.concrete.ECDSASignature;
import meerkat.crypto.concrete.TestECDSASignature;
import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage;
import meerkat.protobuf.BulletinBoardAPI.FilterType;
import meerkat.protobuf.BulletinBoardAPI.MessageFilter;
@ -48,13 +47,13 @@ public class GenericBulletinBoardServerTest {
signers[0] = new ECDSASignature();
signers[1] = new ECDSASignature();
InputStream keyStream = TestECDSASignature.class.getResourceAsStream(KEYFILE_EXAMPLE);
InputStream keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE);
char[] password = KEYFILE_PASSWORD.toCharArray();
KeyStore.Builder keyStore = signers[0].getPKCS12KeyStoreBuilder(keyStream, password);
signers[0].loadSigningCertificate(keyStore);
signers[0].loadVerificationCertificates(TestECDSASignature.class.getResourceAsStream(CERT1_PEM_EXAMPLE));
signers[0].loadVerificationCertificates(getClass().getResourceAsStream(CERT1_PEM_EXAMPLE));
random = new SecureRandom();
}

View File

@ -46,6 +46,7 @@ dependencies {
compile 'com.google.protobuf:protobuf-java:3.+'
// Crypto
compile 'org.factcenter.qilin:qilin:1.1+'
compile 'org.bouncycastle:bcprov-jdk15on:1.53'
compile 'org.bouncycastle:bcpkix-jdk15on:1.53'
@ -98,7 +99,6 @@ idea {
def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java"
println "Adding $srcDir"
// add protobuf generated sources to generated source dir.
if ("test".equals(sourceSet.name)) {
testSourceDirs += file(srcDir)
@ -153,6 +153,8 @@ task fatCapsule(type: FatCapsule){
repositories {
mavenLocal();
// Prefer the local nexus repository (it may have 3rd party artifacts not found in mavenCentral)
maven {
url nexusRepository

View File

@ -51,7 +51,7 @@ public interface DigitalSignature {
/**
* Sign the content that was added.
* Sign the content that was added using {@link #updateContent(Message)}.
* Reset the DigitalSignature and make it available to sign a new message using the same key.
* @return
* @throws SignatureException
@ -76,7 +76,7 @@ public interface DigitalSignature {
/**
* 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()} or {@link #updateContent(Message)}
* Calling this method again will replace the key.
*
* @param keyStoreBuilder A keystore builder that can be used to load a keystore.

View File

@ -1,6 +1,11 @@
package meerkat.crypto;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import java.io.IOException;
import java.util.Random;
import static meerkat.protobuf.Crypto.*;
/**
@ -13,8 +18,23 @@ public interface Encryption {
* @param rnd
* @return
*/
RerandomizableEncryptedMessage encrypt(Message plaintext, EncryptionRandomness rnd); // TODO: type of exception; throws
RerandomizableEncryptedMessage encrypt(Message plaintext, EncryptionRandomness rnd) throws IOException; // TODO: type of exception; throws
RerandomizableEncryptedMessage rerandomize(RerandomizableEncryptedMessage msg, EncryptionRandomness rnd);
/**
* Rerandomize a ciphertext using the supplied randomness.
* @param msg
* @param rnd
* @return
* @throws InvalidProtocolBufferException
*/
RerandomizableEncryptedMessage rerandomize(RerandomizableEncryptedMessage msg, EncryptionRandomness rnd) throws InvalidProtocolBufferException;
/**
* Generate randomness compatible with {@link #encrypt(Message, EncryptionRandomness) and
* {@link #rerandomize(RerandomizableEncryptedMessage, EncryptionRandomness)}}.
* @param rand
* @return
*/
EncryptionRandomness generateRandomness(Random rand);
}

View File

@ -0,0 +1,152 @@
package meerkat.crypto.concrete;
import com.google.protobuf.ByteString;
import com.google.protobuf.Message;
import meerkat.crypto.DigitalSignature;
import meerkat.protobuf.Crypto;
import meerkat.protobuf.Crypto.Signature;
import meerkat.util.Hex;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.signers.DSAKCalculator;
import org.bouncycastle.crypto.signers.ECDSASigner;
import org.bouncycastle.crypto.signers.HMacDSAKCalculator;
import org.bouncycastle.jcajce.provider.asymmetric.util.DSAEncoder;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.jce.ECKeyUtil;
import org.bouncycastle.jce.ECPointUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.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.interfaces.ECPrivateKey;
import java.security.spec.ECParameterSpec;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
/**
* Sign and verify digital signatures.
*
* Uses deterministic ECDSA signatures as per RFC 6979
*
* This class uses BouncyCastle directly, so will not work with arbitrary PKCS11 providers.
*
* This class is not thread-safe (each thread should have its own instance).
*/
public class ECDSADeterministicSignature extends ECDSASignature {
final Logger logger = LoggerFactory.getLogger(getClass());
/**
* Digest of message contents for deterministic signing.
*/
SHA256Digest msgDigest = new SHA256Digest();
/**
* The actual signing implementation. (used only signing -- superclass is used for verification)
*/
ECDSASigner deterministicSigner;
/**
* Output the DER encoding of the ASN.1 sequence r,s
* @param r
* @param s
* @return
*/
public static byte[] derEncodeSignature(BigInteger r, BigInteger s) {
ASN1Integer[] rs = {new ASN1Integer(r), new ASN1Integer(s)};
DERSequence seq = new DERSequence(rs);
try {
return seq.getEncoded();
} catch (IOException e) {
throw new RuntimeException("Should never happen! DER Encoding exception", e);
}
}
public ECDSADeterministicSignature() {
DSAKCalculator kCalk = new HMacDSAKCalculator(new org.bouncycastle.crypto.digests.SHA256Digest());
deterministicSigner = new ECDSASigner(kCalk);
}
@Override
public void loadSigningCertificate(KeyStore.Builder keyStoreBuilder)
throws CertificateException, UnrecoverableKeyException, IOException {
super.loadSigningCertificate(keyStoreBuilder);
if (!(loadedSigningKey instanceof ECPrivateKey)) {
logger.error("Wrong private key type (expected ECPrivateKey, got {})", loadedSigningKey.getClass());
throw new CertificateException("Wrong signing key type!");
}
ECPrivateKey key = (ECPrivateKey) loadedSigningKey;
AsymmetricKeyParameter baseParams;
try {
baseParams = ECUtil.generatePrivateKeyParameter(key);
} catch (InvalidKeyException e) {
throw new UnrecoverableKeyException("Couldn't convert private key");
}
if (!(baseParams instanceof ECPrivateKeyParameters)) {
logger.error("Error converting to bouncycastle type! (got {})", baseParams.getClass());
throw new UnrecoverableKeyException("Wrong signing key type!");
}
ECPrivateKeyParameters params = (ECPrivateKeyParameters) baseParams;
deterministicSigner.init(true, params);
}
/**
* Add the list of messages to the stream that is being verified/signed.
* Messages are prepended with their length in 32-bit big-endian format.
*
* @param msg
* @throws SignatureException
*/
@Override
public void updateContent(Message msg) throws SignatureException {
assert msg != null;
// We're doing twice the digest work so that we also support verification with the same update.
// If this becomes a problem, we can decide which way to update based on which init was called.
super.updateContent(msg);
msgDigest.update(msg);
}
@Override
public void updateContent(InputStream in) throws IOException, SignatureException {
ByteString inStr = ByteString.readFrom(in);
signer.update(inStr.asReadOnlyByteBuffer());
msgDigest.update(inStr);
}
@Override
public Signature sign() throws SignatureException {
Signature.Builder sig = Signature.newBuilder();
sig.setType(Crypto.SignatureType.ECDSA);
BigInteger[] rawSig = deterministicSigner.generateSignature(msgDigest.digest());
sig.setData(ByteString.copyFrom(derEncodeSignature(rawSig[0], rawSig[1])));
sig.setSignerId(loadedSigningKeyId);
return sig.build();
}
}

View File

@ -36,7 +36,7 @@ public class ECDSASignature extends GlobalCryptoSetup implements DigitalSignatur
final public static String KEYSTORE_TYPE = "PKCS12";
final public static String DEFAULT_SIGNATURE_ALGORITHM = "SHA256withECDSA";
SHA256Digest digest = new SHA256Digest();
SHA256Digest certDigest = new SHA256Digest();
/**
* Buffer used to hold length in for hash update
@ -58,6 +58,11 @@ public class ECDSASignature extends GlobalCryptoSetup implements DigitalSignatur
*/
java.security.Signature signer;
/**
* The currently loaded signing key.
*/
PrivateKey loadedSigningKey;
/**
* Compute a fingerprint of a cert as a SHA256 hash.
@ -67,14 +72,14 @@ public class ECDSASignature extends GlobalCryptoSetup implements DigitalSignatur
*/
public ByteString computeCertificateFingerprint(Certificate cert) {
try {
digest.reset();
certDigest.reset();
byte[] data = cert.getEncoded();
digest.update(data);
return ByteString.copyFrom(digest.digest());
certDigest.update(data);
return ByteString.copyFrom(certDigest.digest());
} catch (CertificateEncodingException e) {
// Shouldn't happen
logger.error("Certificate encoding error", e);
return ByteString.EMPTY;
throw new RuntimeException("Certificate encoding error", e);
}
}
@ -133,7 +138,7 @@ public class ECDSASignature extends GlobalCryptoSetup implements DigitalSignatur
signer.update(msg.toByteString().asReadOnlyByteBuffer());
}
public void updateSigner(InputStream in) throws IOException, SignatureException {
public void updateContent(InputStream in) throws IOException, SignatureException {
ByteString inStr = ByteString.readFrom(in);
signer.update(inStr.asReadOnlyByteBuffer());
}
@ -234,10 +239,13 @@ public class ECDSASignature extends GlobalCryptoSetup implements DigitalSignatur
String alias = aliases.nextElement();
logger.trace("Testing keystore entry {}", alias);
try {
Certificate cert = keyStore.getCertificate(alias);
logger.trace("keystore entry {}, has cert type {}", alias, cert.getClass());
Key key;
try {
key = keyStore.getKey(alias, null);
} catch (UnrecoverableKeyException e) {
@ -267,9 +275,11 @@ public class ECDSASignature extends GlobalCryptoSetup implements DigitalSignatur
}
logger.trace("keystore entry {}, has key type {}", alias, key.getClass());
if (key instanceof PrivateKey) {
loadedSigningKey = (PrivateKey) key;
loadedSigningKeyId = computeCertificateFingerprint(cert);
signer.initSign((PrivateKey) key);
signer.initSign(loadedSigningKey);
logger.debug("Loaded signing key with ID {}", Hex.encode(loadedSigningKeyId));
return;
} else {
logger.info("Certificate {} in keystore does not have a private key", cert.toString());
@ -297,6 +307,8 @@ public class ECDSASignature extends GlobalCryptoSetup implements DigitalSignatur
if (loadedSigningKeyId != null)
signer.initSign(null);
loadedSigningKeyId = null;
loadedSigningKey = null;
// Start garbage collection?
} catch (InvalidKeyException e) {
// Do nothing
}

View File

@ -0,0 +1,140 @@
package meerkat.crypto.concrete;
import com.google.protobuf.ByteString;
import com.google.protobuf.CodedOutputStream;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import meerkat.crypto.Encryption;
import meerkat.protobuf.ConcreteCrypto;
import meerkat.protobuf.Crypto;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.util.PublicKeyFactory;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECFieldElement;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.BigIntegers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import qilin.primitives.concrete.ECElGamal;
import qilin.primitives.concrete.ECGroup;
import qilin.primitives.PseudorandomGenerator;
import qilin.primitives.generic.ElGamal;
import qilin.util.PRGRandom;
import qilin.util.Pair;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.security.spec.*;
import java.util.Random;
/**
* Created by talm on 17/11/15.
*/
public class ECElGamalEncryption extends GlobalCryptoSetup implements Encryption {
final Logger logger = LoggerFactory.getLogger(getClass());
public final static String KEY_ALGORITHM = "ECDH";
/**
* The Qilin format El-Gamal public key
*/
ECElGamal.PK elGamalPK;
ECCurve curve;
ECGroup group;
public ECGroup getGroup() { return group; }
public ECElGamal.PK getElGamalPK() {
return elGamalPK;
}
public void init(ConcreteCrypto.ElGamalPublicKey serializedPk) throws InvalidKeySpecException {
AsymmetricKeyParameter keyParam;
try {
keyParam = PublicKeyFactory.createKey(serializedPk.getSubjectPublicKeyInfo().toByteArray());
} catch (IOException e) {
// Shouldn't every happen
logger.error("Invalid Public Key Encoding", e);
throw new InvalidKeySpecException("Invalid Public Key Encoding", e);
}
if (!(keyParam instanceof ECPublicKeyParameters)) {
logger.error("Public key is a {}, not a valid public EC Key!", keyParam.getClass());
throw new InvalidKeySpecException("Not a valid EC public key!");
}
ECDomainParameters params = ((ECKeyParameters) keyParam).getParameters();
ECParameterSpec ecParams = new ECParameterSpec(params.getCurve(), params.getG(), params.getN(), params.getH(),
params.getSeed());
curve = params.getCurve();
group = new ECGroup(ecParams);
elGamalPK = new ECElGamal.PK(group, ((ECPublicKeyParameters) keyParam).getQ());
}
@Override
public Crypto.RerandomizableEncryptedMessage encrypt(Message plaintext, Crypto.EncryptionRandomness rnd) {
// We write the message using writeDelimited to so the length gets prepended.
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
plaintext.writeDelimitedTo(out);
} catch (IOException e) {
logger.error("Should never happen!", e);
throw new RuntimeException("Error in ByteArrayOutputStream!", e);
}
byte[] msg = out.toByteArray();
ECPoint encodedMsg = group.injectiveEncode(msg, new PRGRandom(msg));
BigInteger rndInt = BigIntegers.fromUnsignedByteArray(rnd.getData().toByteArray());
Pair<ECPoint,ECPoint> cipherText = elGamalPK.encrypt(encodedMsg, rndInt);
ConcreteCrypto.ElGamalCiphertext encodedCipherText = ConcreteCrypto.ElGamalCiphertext.newBuilder()
.setC1(ByteString.copyFrom(cipherText.a.getEncoded(true)))
.setC2(ByteString.copyFrom(cipherText.b.getEncoded(true)))
.build();
return Crypto.RerandomizableEncryptedMessage.newBuilder()
.setData(encodedCipherText.toByteString())
.build();
}
@Override
public Crypto.RerandomizableEncryptedMessage rerandomize(Crypto.RerandomizableEncryptedMessage msg, Crypto.EncryptionRandomness rnd) throws InvalidProtocolBufferException {
BigInteger rndInt = BigIntegers.fromUnsignedByteArray(rnd.getData().toByteArray());
Pair<ECPoint,ECPoint> randomizer = elGamalPK.encrypt(curve.getInfinity(), rndInt);
ConcreteCrypto.ElGamalCiphertext originalEncodedCipher= ConcreteCrypto.ElGamalCiphertext.parseFrom(msg.getData());
Pair<ECPoint,ECPoint> originalCipher = new Pair<>(
curve.decodePoint(originalEncodedCipher.getC1().toByteArray()),
curve.decodePoint(originalEncodedCipher.getC2().toByteArray()));
Pair<ECPoint,ECPoint> newCipher = elGamalPK.add(originalCipher, randomizer);
return Crypto.RerandomizableEncryptedMessage.newBuilder()
.setData(
ConcreteCrypto.ElGamalCiphertext.newBuilder()
.setC1(ByteString.copyFrom(newCipher.a.getEncoded(true)))
.setC2(ByteString.copyFrom(newCipher.b.getEncoded(true)))
.build().toByteString()
).build();
}
@Override
public Crypto.EncryptionRandomness generateRandomness(Random rand) {
BigInteger randomInt = new BigInteger(group.getCurveParams().getN().bitLength() - 1, rand);
Crypto.EncryptionRandomness retval = Crypto.EncryptionRandomness.newBuilder()
.setData(ByteString.copyFrom(BigIntegers.asUnsignedByteArray(randomInt))).build();
return retval;
}
}

View File

@ -4,6 +4,7 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.security.Provider;
import java.security.Security;
/**
@ -13,6 +14,7 @@ public class GlobalCryptoSetup {
final static Logger logger = LoggerFactory.getLogger(GlobalCryptoSetup.class);
static boolean loadedBouncyCastle = false;
static Provider bouncyCastleProvider;
public static boolean hasSecp256k1Curve() {
// For now we just check if the java version is at least 8
@ -22,17 +24,20 @@ public class GlobalCryptoSetup {
return ((major > 1) || ((major > 0) && (minor > 7)));
}
public static void doSetup() {
// 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) {
loadedBouncyCastle = true;
Security.insertProviderAt(new BouncyCastleProvider(), 1);
logger.info("Using BouncyCastle instead of native provider to support secp256k1 named curve");
public static Provider getBouncyCastleProvider() { doSetup(); return bouncyCastleProvider; }
public static synchronized void doSetup() {
if (bouncyCastleProvider == null) {
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) {
loadedBouncyCastle = true;
Security.insertProviderAt(bouncyCastleProvider, 1);
logger.info("Using BouncyCastle instead of native provider to support secp256k1 named curve");
}
}
}
public GlobalCryptoSetup() {
doSetup();
}
public GlobalCryptoSetup() { doSetup(); }
}

View File

@ -78,6 +78,10 @@ public class SHA256Digest extends GlobalCryptoSetup implements Digest {
hash.update(msg);
}
final public void update(ByteBuffer msg) {
hash.update(msg);
}
@Override
public void reset() {
hash.reset();

View File

@ -0,0 +1,22 @@
// Protobufs for specific crypto primitives
syntax = "proto3";
package meerkat;
import 'meerkat/crypto.proto';
option java_package = "meerkat.protobuf";
message ElGamalPublicKey {
// DER-encoded SubjectPublicKeyInfo as in RFC 3279
bytes subject_public_key_info = 1;
}
// An El-Gamal ciphertext
// Each group element should be an ASN.1 encoded curve point with compression.
message ElGamalCiphertext {
bytes c1 = 1; // First group element
bytes c2 = 2; // Second group element
}

View File

@ -9,6 +9,10 @@ enum SignatureType {
DSA = 1;
}
message BigInteger {
bytes data = 1;
}
// A digital signature
message Signature {
SignatureType type = 1;

View File

@ -0,0 +1,52 @@
package meerkat.crypto.concrete;
import com.google.protobuf.ByteString;
import com.google.protobuf.Message;
import meerkat.protobuf.BulletinBoardAPI.UnsignedBulletinBoardMessage;
import meerkat.protobuf.Crypto;
import org.junit.Test;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.KeyStore;
import static org.junit.Assert.*;
/**
* Created by talm on 12/11/15.
*/
public class ECDSADeterministicSignatureTest extends ECDSASignatureTest {
@Override
protected ECDSASignature getSigner() { return new ECDSADeterministicSignature(); }
/**
* Make sure signatures don't vary
*/
@Test
public void testDeterministicSigning() throws Exception {
loadSigningKeys();
for (int i = 0; i < REPEAT_COUNT; ++i) {
BigInteger rawMsg = new BigInteger(50, rand);
Message msg = Crypto.BigInteger.newBuilder()
.setData(ByteString.copyFrom(rawMsg.toByteArray())).build();
Crypto.Signature[] sigs = new Crypto.Signature[REPEAT_COUNT];
signer.updateContent(msg);
sigs[0] = signer.sign();
byte[] canonicalSig = sigs[0].toByteArray();
for (int j = 1; j < sigs.length; ++j) {
signer.updateContent(msg);
sigs[j] = signer.sign();
byte[] newSig = sigs[j].toByteArray();
assertArrayEquals("Signatures on same message differ (i="+i+",j="+j+")", canonicalSig, newSig);
}
}
}
}

View File

@ -1,13 +1,16 @@
package meerkat.crypto.concrete;
import com.google.protobuf.ByteString;
import com.google.protobuf.Message;
import meerkat.protobuf.Crypto;
import meerkat.crypto.concrete.ECDSASignature;
import meerkat.protobuf.BulletinBoardAPI.*;
import org.junit.Before;
import org.junit.Test;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.KeyStore;
import java.util.Random;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@ -15,7 +18,7 @@ import static org.junit.Assert.assertTrue;
/**
* Created by talm on 12/11/15.
*/
public class TestECDSASignature {
public class ECDSASignatureTest {
public static String KEYFILE_EXAMPLE = "/certs/enduser-certs/user1-key-with-password-secret.p12";
public static String KEYFILE_PASSWORD = "secret";
@ -27,16 +30,26 @@ public class TestECDSASignature {
public static String HELLO_WORLD = "hello world!";
public final static int REPEAT_COUNT = 10;
Random rand = new Random(0);
protected ECDSASignature signer;
protected ECDSASignature getSigner() { return new ECDSASignature(); }
@Before
public void setup() throws Exception {
signer = getSigner();
}
@Test
public void loadSignatureKey() throws Exception {
InputStream keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE);
char[] password = KEYFILE_PASSWORD.toCharArray();
ECDSASignature sig = new ECDSASignature();
KeyStore.Builder keyStore = sig.getPKCS12KeyStoreBuilder(keyStream, password);
sig.loadSigningCertificate(keyStore);
KeyStore.Builder keyStore = signer.getPKCS12KeyStoreBuilder(keyStream, password);
signer.loadSigningCertificate(keyStore);
keyStream.close();
}
@ -44,9 +57,7 @@ public class TestECDSASignature {
public void loadPEMVerificationKey() throws Exception {
InputStream certStream = getClass().getResourceAsStream(CERT1_PEM_EXAMPLE);
ECDSASignature sig = new ECDSASignature();
sig.loadVerificationCertificates(certStream);
signer.loadVerificationCertificates(certStream);
certStream.close();
}
@ -54,9 +65,7 @@ public class TestECDSASignature {
public void loadDERVerificationKey() throws Exception {
InputStream certStream = getClass().getResourceAsStream(CERT2_DER_EXAMPLE);
ECDSASignature sig = new ECDSASignature();
sig.loadVerificationCertificates(certStream);
signer.loadVerificationCertificates(certStream);
certStream.close();
}
@ -67,8 +76,6 @@ public class TestECDSASignature {
InputStream msgStream = getClass().getResourceAsStream(MSG_PLAINTEXT_EXAMPLE);
InputStream sigStream = getClass().getResourceAsStream(MSG_SIG_EXAMPLE);
ECDSASignature signer = new ECDSASignature();
signer.loadVerificationCertificates(certStream);
certStream.close();
@ -79,7 +86,7 @@ public class TestECDSASignature {
Crypto.Signature builtSig = sig.build();
signer.initVerify(builtSig);
signer.updateSigner(msgStream);
signer.updateContent(msgStream);
assertTrue("Signature did not verify!", signer.verify());
}
@ -89,8 +96,6 @@ public class TestECDSASignature {
InputStream msgStream = getClass().getResourceAsStream(MSG_PLAINTEXT_EXAMPLE);
InputStream sigStream = getClass().getResourceAsStream(MSG_SIG_EXAMPLE);
ECDSASignature signer = new ECDSASignature();
signer.loadVerificationCertificates(certStream);
certStream.close();
@ -105,7 +110,7 @@ public class TestECDSASignature {
Crypto.Signature builtSig = sig.build();
signer.initVerify(builtSig);
signer.updateSigner(msgStream);
signer.updateContent(msgStream);
assertFalse("Bad Signature passed verification!", signer.verify());
}
@ -116,8 +121,6 @@ public class TestECDSASignature {
InputStream msgStream = getClass().getResourceAsStream(MSG_PLAINTEXT_EXAMPLE);
InputStream sigStream = getClass().getResourceAsStream(MSG_SIG_EXAMPLE);
ECDSASignature signer = new ECDSASignature();
signer.loadVerificationCertificates(certStream);
certStream.close();
@ -130,30 +133,27 @@ public class TestECDSASignature {
Crypto.Signature builtSig = sig.build();
signer.initVerify(builtSig);
signer.updateSigner(msgStream);
signer.updateContent(msgStream);
assertFalse("Signature doesn't match message but passed verification!", signer.verify());
}
@Test
public void signAndVerify() throws Exception {
protected void loadSigningKeys() 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);
}
@Test
public void signAndVerify() throws Exception {
loadSigningKeys();
UnsignedBulletinBoardMessage.Builder unsignedMsgBuilder = UnsignedBulletinBoardMessage.newBuilder();
unsignedMsgBuilder.setData(ByteString.copyFromUtf8(HELLO_WORLD));
unsignedMsgBuilder.addTag("Tag1");
unsignedMsgBuilder.addTag("Tag2");
unsignedMsgBuilder.addTag("Tag3");
UnsignedBulletinBoardMessage usMsg = unsignedMsgBuilder.build();
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();
@ -166,5 +166,55 @@ public class TestECDSASignature {
}
@Test
public void signMultipleAndVerify() throws Exception {
loadSigningKeys();
Message[] msgs = new Message[REPEAT_COUNT];
for (int i = 0; i < msgs.length; ++i) {
BigInteger rawMsg = new BigInteger(50, rand);
msgs[i] = Crypto.BigInteger.newBuilder()
.setData(ByteString.copyFrom(rawMsg.toByteArray())).build();
signer.updateContent(msgs[i]);
}
Crypto.Signature sig = signer.sign();
signer.loadVerificationCertificates(getClass().getResourceAsStream(CERT1_PEM_EXAMPLE));
signer.initVerify(sig);
for (int i = 0; i < msgs.length; ++i) {
signer.updateContent(msgs[i]);
}
assertTrue("Couldn't verify signature on ", signer.verify());
}
@Test
public void multipleSignAndVerify() throws Exception {
loadSigningKeys();
Message[] msgs = new Message[REPEAT_COUNT];
Crypto.Signature[] sigs = new Crypto.Signature[REPEAT_COUNT];
for (int i = 0; i < msgs.length; ++i) {
BigInteger rawMsg = new BigInteger(50, rand);
msgs[i] = Crypto.BigInteger.newBuilder()
.setData(ByteString.copyFrom(rawMsg.toByteArray())).build();
signer.updateContent(msgs[i]);
sigs[i] = signer.sign();
}
signer.loadVerificationCertificates(getClass().getResourceAsStream(CERT1_PEM_EXAMPLE));
for (int i = 0; i < msgs.length; ++i) {
signer.initVerify(sigs[i]);
signer.updateContent(msgs[i]);
assertTrue("Couldn't verify signature on ", signer.verify());
}
}
}

View File

@ -0,0 +1,125 @@
package meerkat.crypto.concrete;
import meerkat.protobuf.BulletinBoardAPI;
import meerkat.protobuf.ConcreteCrypto;
import meerkat.protobuf.Crypto;
import meerkat.protobuf.Voting;
import org.bouncycastle.math.ec.ECPoint;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import qilin.primitives.concrete.ECElGamal;
import qilin.primitives.concrete.ECGroup;
import qilin.primitives.generic.ElGamal;
import qilin.util.Pair;
import java.math.BigInteger;
import java.util.Random;
import static org.junit.Assert.*;
/**
* Test class for {@link ECElGamalEncryption}
*/
public class ECElGamalEncryptionTest {
final Logger logger = LoggerFactory.getLogger(getClass());
/**
* Number of times to repeat probabilistic tests.
*/
public final static int CONFIDENCE = 10;
Random rand = new Random(0); // Insecure deterministic random for testing.
ECElGamal.SK key;
ECGroup group;
ECElGamalEncryption enc;
ConcreteCrypto.ElGamalPublicKey serializedPk;
@Before
public void setup() throws Exception {
group = new ECGroup("secp256k1");
BigInteger sk = ECElGamal.generateSecretKey(group, rand);
key = new ECElGamal.SK(group, sk);
serializedPk = ECElGamalUtils.serializePk(group, key);
enc = new ECElGamalEncryption();
enc.init(serializedPk);
}
Voting.PlaintextBallot genRandomBallot(int numQuestions, int numAnswers, int maxAnswer) {
Voting.PlaintextBallot.Builder ballot = Voting.PlaintextBallot.newBuilder();
ballot.setSerialNumber(rand.nextInt(1000000));
for (int i = 0; i < numQuestions; ++i) {
Voting.BallotAnswer.Builder answers = ballot.addAnswersBuilder();
for (int j = 0; j < numAnswers; ++j) {
answers.addAnswer(rand.nextInt(maxAnswer));
}
}
return ballot.build();
}
/**
* Testing just the key management
* @throws Exception
*/
@Test
public void testPkSerialization() throws Exception {
ECElGamal.PK pk = enc.getElGamalPK();
ECPoint point = enc.getGroup().sample(rand);
Pair<ECPoint, ECPoint> cipher = pk.encrypt(point, pk.getRandom(rand));
ECPoint decrypted = key.decrypt(cipher);
assertEquals("Decrypted value not equal to encrypted value!", point, decrypted);
}
@Test
public void testEncryption() throws Exception {
for (int i = 0; i < CONFIDENCE; ++i) {
Voting.PlaintextBallot msg = genRandomBallot(2,3,16); // 2 questions with 3 answers each, in range 0-15.
if (msg.getSerializedSize() > enc.getGroup().getInjectiveEncodeMsgLength()) {
logger.error("Test Message too big (|msg|={} > max={}), expect failure.",
msg.getSerializedSize(), enc.getGroup().getInjectiveEncodeMsgLength());
}
Crypto.RerandomizableEncryptedMessage cipherText = enc.encrypt(msg, enc.generateRandomness(rand));
Voting.PlaintextBallot decrypted = ECElGamalUtils.decrypt(Voting.PlaintextBallot.class, key, group, cipherText);
assertEquals("Decrypted value differs from encrypted value (i="+i+")!", msg, decrypted);
}
}
@Test
public void testRerandomizeModifiesCiphertext() throws Exception {
Voting.PlaintextBallot msg = genRandomBallot(2,3,16); // 2 questions with 3 answers each, in range 0-15.
Crypto.RerandomizableEncryptedMessage cipher1 = enc.encrypt(msg, enc.generateRandomness(rand));
Crypto.RerandomizableEncryptedMessage cipher2 = enc.rerandomize(cipher1, enc.generateRandomness(rand));
assertNotEquals("Rerandomized cipher identical to original!", cipher1, cipher2);
}
@Test
public void testRerandomizePreservesPlaintext() throws Exception {
for (int i = 0; i < CONFIDENCE; ++i) {
Voting.PlaintextBallot msg = genRandomBallot(2,3,16); // 2 questions with 3 answers each, in range 0-15.
Crypto.RerandomizableEncryptedMessage cipher = enc.encrypt(msg, enc.generateRandomness(rand));
Crypto.RerandomizableEncryptedMessage cipher2 = cipher;
for (int j = 0; j < CONFIDENCE; ++j)
cipher2 = enc.rerandomize(cipher2, enc.generateRandomness(rand));
Voting.PlaintextBallot decrypted = ECElGamalUtils.decrypt(Voting.PlaintextBallot.class, key, group,
cipher2);
assertEquals("Decrypted value differs from original encrypted value (i="+i+")!", msg, decrypted);
}
}
}

View File

@ -0,0 +1,89 @@
package meerkat.crypto.concrete;
import com.google.protobuf.ByteString;
import com.google.protobuf.GeneratedMessage;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import meerkat.protobuf.ConcreteCrypto;
import meerkat.protobuf.Crypto;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import qilin.primitives.concrete.ECElGamal;
import qilin.primitives.concrete.ECGroup;
import qilin.primitives.generic.ElGamal;
import qilin.util.Pair;
import java.io.ByteArrayInputStream;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
/**
* utilities for ECElgamal
*/
public class ECElGamalUtils {
final static Logger logger = LoggerFactory.getLogger(ECElGamalUtils.class);
public final static String ENCRYPTION_KEY_ALGORITHM = "ECDH";
/**
* Serialize an El-Gamal public key into a form acceptable by {@link ECElGamalEncryption}
* @param pk
* @return
*/
public static ConcreteCrypto.ElGamalPublicKey serializePk(ECGroup group, ElGamal.PK<ECPoint> pk) {
ECPoint pkPoint = pk.getPK();
ECParameterSpec params = group.getCurveParams();
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(pkPoint, params);
try {
KeyFactory fact = KeyFactory.getInstance(ENCRYPTION_KEY_ALGORITHM,
GlobalCryptoSetup.getBouncyCastleProvider());
PublicKey javaPk = fact.generatePublic(pubKeySpec);
ConcreteCrypto.ElGamalPublicKey serializedPk = ConcreteCrypto.ElGamalPublicKey.newBuilder()
.setSubjectPublicKeyInfo(ByteString.copyFrom(javaPk.getEncoded())).build();
return serializedPk;
} catch (NoSuchAlgorithmException|InvalidKeySpecException e) {
logger.error("Should never happen!", e);
throw new RuntimeException("Error converting public key!", e);
}
}
/**
* Standard (non-threshold) decryption for testing purposes.
* @param secretKey
* @return
*/
public static <T extends Message> T decrypt(Class<T> plaintextMessageType, ECElGamal.SK secretKey, ECGroup group, Crypto.RerandomizableEncryptedMessage opaqueCipher)
throws InvalidProtocolBufferException {
ConcreteCrypto.ElGamalCiphertext cipherText = ConcreteCrypto.ElGamalCiphertext.parseFrom(opaqueCipher.getData());
ByteString c1encoded = cipherText.getC1();
ByteString c2encoded = cipherText.getC2();
ECPoint c1 = group.decode(c1encoded.toByteArray());
ECPoint c2 = group.decode(c2encoded.toByteArray());
ECPoint plaintextEncoded = secretKey.decrypt(new Pair<ECPoint, ECPoint>(c1, c2));
byte[] plaintext = group.injectiveDecode(plaintextEncoded);
ByteArrayInputStream in = new ByteArrayInputStream(plaintext);
try {
java.lang.reflect.Method newBuilder = plaintextMessageType.getMethod("newBuilder");
GeneratedMessage.Builder builder = (GeneratedMessage.Builder) newBuilder.invoke(plaintextMessageType);
builder.mergeDelimitedFrom(in);
return plaintextMessageType.cast(builder.build());
} catch (Exception e) {
logger.error("Error parsing incoming message", e);
throw new InvalidProtocolBufferException("Plaintext protobuf error");
}
}
}

View File

@ -73,7 +73,6 @@ idea {
def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java"
println "Adding $srcDir"
// add protobuf generated sources to generated source dir.
if ("test".equals(sourceSet.name)) {
testSourceDirs += file(srcDir)

View File

@ -73,7 +73,6 @@ idea {
def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java"
println "Adding $srcDir"
// add protobuf generated sources to generated source dir.
if ("test".equals(sourceSet.name)) {
testSourceDirs += file(srcDir)

View File

@ -72,7 +72,6 @@ idea {
def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java"
println "Adding $srcDir"
// add protobuf generated sources to generated source dir.
if ("test".equals(sourceSet.name)) {
testSourceDirs += file(srcDir)