Merged encryption and signature work from crypto-primitives
						commit
						ad121a7bfd
					
				|  | @ -103,7 +103,6 @@ idea { | ||||||
| 
 | 
 | ||||||
|             def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java" |             def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java" | ||||||
| 
 | 
 | ||||||
|             println "Adding $srcDir" |  | ||||||
|             // add protobuf generated sources to generated source dir. |             // add protobuf generated sources to generated source dir. | ||||||
|             if ("test".equals(sourceSet.name)) { |             if ("test".equals(sourceSet.name)) { | ||||||
|                 testSourceDirs += file(srcDir) |                 testSourceDirs += file(srcDir) | ||||||
|  |  | ||||||
|  | @ -17,7 +17,6 @@ import com.google.protobuf.ByteString; | ||||||
| 
 | 
 | ||||||
| import meerkat.comm.CommunicationException; | import meerkat.comm.CommunicationException; | ||||||
| import meerkat.crypto.concrete.ECDSASignature; | import meerkat.crypto.concrete.ECDSASignature; | ||||||
| import meerkat.crypto.concrete.TestECDSASignature; |  | ||||||
| import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage; | import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage; | ||||||
| import meerkat.protobuf.BulletinBoardAPI.FilterType; | import meerkat.protobuf.BulletinBoardAPI.FilterType; | ||||||
| import meerkat.protobuf.BulletinBoardAPI.MessageFilter; | import meerkat.protobuf.BulletinBoardAPI.MessageFilter; | ||||||
|  | @ -48,13 +47,13 @@ public class GenericBulletinBoardServerTest { | ||||||
|         signers[0] = new ECDSASignature(); |         signers[0] = new ECDSASignature(); | ||||||
|         signers[1] = new ECDSASignature(); |         signers[1] = new ECDSASignature(); | ||||||
| 		 | 		 | ||||||
| 		InputStream keyStream = TestECDSASignature.class.getResourceAsStream(KEYFILE_EXAMPLE); | 		InputStream keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE); | ||||||
|         char[] password = KEYFILE_PASSWORD.toCharArray(); |         char[] password = KEYFILE_PASSWORD.toCharArray(); | ||||||
|          |          | ||||||
|         KeyStore.Builder keyStore = signers[0].getPKCS12KeyStoreBuilder(keyStream, password); |         KeyStore.Builder keyStore = signers[0].getPKCS12KeyStoreBuilder(keyStream, password); | ||||||
|         signers[0].loadSigningCertificate(keyStore); |         signers[0].loadSigningCertificate(keyStore); | ||||||
|          |          | ||||||
|         signers[0].loadVerificationCertificates(TestECDSASignature.class.getResourceAsStream(CERT1_PEM_EXAMPLE)); |         signers[0].loadVerificationCertificates(getClass().getResourceAsStream(CERT1_PEM_EXAMPLE)); | ||||||
|          |          | ||||||
|         random = new SecureRandom(); |         random = new SecureRandom(); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -46,6 +46,7 @@ dependencies { | ||||||
|     compile 'com.google.protobuf:protobuf-java:3.+' |     compile 'com.google.protobuf:protobuf-java:3.+' | ||||||
| 
 | 
 | ||||||
|     // Crypto |     // Crypto | ||||||
|  |     compile 'org.factcenter.qilin:qilin:1.1+' | ||||||
|     compile 'org.bouncycastle:bcprov-jdk15on:1.53' |     compile 'org.bouncycastle:bcprov-jdk15on:1.53' | ||||||
|     compile 'org.bouncycastle:bcpkix-jdk15on:1.53' |     compile 'org.bouncycastle:bcpkix-jdk15on:1.53' | ||||||
| 
 | 
 | ||||||
|  | @ -98,7 +99,6 @@ idea { | ||||||
| 
 | 
 | ||||||
|             def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java" |             def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java" | ||||||
| 
 | 
 | ||||||
|             println "Adding $srcDir" |  | ||||||
|             // add protobuf generated sources to generated source dir. |             // add protobuf generated sources to generated source dir. | ||||||
|             if ("test".equals(sourceSet.name)) { |             if ("test".equals(sourceSet.name)) { | ||||||
|                 testSourceDirs += file(srcDir) |                 testSourceDirs += file(srcDir) | ||||||
|  | @ -153,6 +153,8 @@ task fatCapsule(type: FatCapsule){ | ||||||
| 
 | 
 | ||||||
| repositories { | repositories { | ||||||
| 
 | 
 | ||||||
|  |         mavenLocal(); | ||||||
|  |          | ||||||
|         // Prefer the local nexus repository (it may have 3rd party artifacts not found in mavenCentral) |         // Prefer the local nexus repository (it may have 3rd party artifacts not found in mavenCentral) | ||||||
|         maven {  |         maven {  | ||||||
|             url nexusRepository |             url nexusRepository | ||||||
|  |  | ||||||
|  | @ -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. |      * Reset the DigitalSignature and make it available to sign a new message using the same key. | ||||||
|      * @return |      * @return | ||||||
|      * @throws SignatureException |      * @throws SignatureException | ||||||
|  | @ -76,7 +76,7 @@ public interface DigitalSignature { | ||||||
|     /** |     /** | ||||||
|      * Loads a private signing key. The keystore must include both the public and private |      * Loads a private signing key. The keystore must include both the public and private | ||||||
|      * key parts. |      * 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. |      * Calling this method again will replace the key. | ||||||
|      * |      * | ||||||
|      * @param keyStoreBuilder A keystore builder that can be used to load a keystore. |      * @param keyStoreBuilder A keystore builder that can be used to load a keystore. | ||||||
|  |  | ||||||
|  | @ -1,6 +1,11 @@ | ||||||
| package meerkat.crypto; | package meerkat.crypto; | ||||||
| 
 | 
 | ||||||
|  | import com.google.protobuf.InvalidProtocolBufferException; | ||||||
| import com.google.protobuf.Message; | import com.google.protobuf.Message; | ||||||
|  | 
 | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.util.Random; | ||||||
|  | 
 | ||||||
| import static meerkat.protobuf.Crypto.*; | import static meerkat.protobuf.Crypto.*; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  | @ -13,8 +18,23 @@ public interface Encryption { | ||||||
|      * @param rnd |      * @param rnd | ||||||
|      * @return |      * @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); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -36,7 +36,7 @@ public class ECDSASignature extends GlobalCryptoSetup implements DigitalSignatur | ||||||
|     final public static String KEYSTORE_TYPE = "PKCS12"; |     final public static String KEYSTORE_TYPE = "PKCS12"; | ||||||
|     final public static String DEFAULT_SIGNATURE_ALGORITHM = "SHA256withECDSA"; |     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 |      * Buffer used to hold length in for hash update | ||||||
|  | @ -58,6 +58,11 @@ public class ECDSASignature extends GlobalCryptoSetup implements DigitalSignatur | ||||||
|      */ |      */ | ||||||
|     java.security.Signature signer; |     java.security.Signature signer; | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * The currently loaded signing key. | ||||||
|  |      */ | ||||||
|  |     PrivateKey loadedSigningKey; | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Compute a fingerprint of a cert as a SHA256 hash. |      * 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) { |     public ByteString computeCertificateFingerprint(Certificate cert) { | ||||||
|         try { |         try { | ||||||
|             digest.reset(); |             certDigest.reset(); | ||||||
|             byte[] data = cert.getEncoded(); |             byte[] data = cert.getEncoded(); | ||||||
|             digest.update(data); |             certDigest.update(data); | ||||||
|             return ByteString.copyFrom(digest.digest()); |             return ByteString.copyFrom(certDigest.digest()); | ||||||
|         } catch (CertificateEncodingException e) { |         } catch (CertificateEncodingException e) { | ||||||
|             // Shouldn't happen
 |             // Shouldn't happen
 | ||||||
|             logger.error("Certificate encoding error", e); |             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()); |         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); |         ByteString inStr = ByteString.readFrom(in); | ||||||
|         signer.update(inStr.asReadOnlyByteBuffer()); |         signer.update(inStr.asReadOnlyByteBuffer()); | ||||||
|     } |     } | ||||||
|  | @ -234,10 +239,13 @@ public class ECDSASignature extends GlobalCryptoSetup implements DigitalSignatur | ||||||
|                 String alias = aliases.nextElement(); |                 String alias = aliases.nextElement(); | ||||||
|                 logger.trace("Testing keystore entry {}", alias); |                 logger.trace("Testing keystore entry {}", alias); | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|                 try { |                 try { | ||||||
|                     Certificate cert = keyStore.getCertificate(alias); |                     Certificate cert = keyStore.getCertificate(alias); | ||||||
|                     logger.trace("keystore entry {}, has cert type {}", alias, cert.getClass()); |                     logger.trace("keystore entry {}, has cert type {}", alias, cert.getClass()); | ||||||
|  | 
 | ||||||
|                     Key key; |                     Key key; | ||||||
|  | 
 | ||||||
|                     try { |                     try { | ||||||
|                         key = keyStore.getKey(alias, null); |                         key = keyStore.getKey(alias, null); | ||||||
|                     } catch (UnrecoverableKeyException e) { |                     } catch (UnrecoverableKeyException e) { | ||||||
|  | @ -267,9 +275,11 @@ public class ECDSASignature extends GlobalCryptoSetup implements DigitalSignatur | ||||||
|                     } |                     } | ||||||
|                     logger.trace("keystore entry {}, has key type {}", alias, key.getClass()); |                     logger.trace("keystore entry {}, has key type {}", alias, key.getClass()); | ||||||
|                     if (key instanceof PrivateKey) { |                     if (key instanceof PrivateKey) { | ||||||
|  |                         loadedSigningKey = (PrivateKey) key; | ||||||
|                         loadedSigningKeyId = computeCertificateFingerprint(cert); |                         loadedSigningKeyId = computeCertificateFingerprint(cert); | ||||||
|                         signer.initSign((PrivateKey) key); |                         signer.initSign(loadedSigningKey); | ||||||
|                         logger.debug("Loaded signing key with ID {}", Hex.encode(loadedSigningKeyId)); |                         logger.debug("Loaded signing key with ID {}", Hex.encode(loadedSigningKeyId)); | ||||||
|  | 
 | ||||||
|                         return; |                         return; | ||||||
|                     } else { |                     } else { | ||||||
|                         logger.info("Certificate {} in keystore does not have a private key", cert.toString()); |                         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) |             if (loadedSigningKeyId != null) | ||||||
|                 signer.initSign(null); |                 signer.initSign(null); | ||||||
|             loadedSigningKeyId = null; |             loadedSigningKeyId = null; | ||||||
|  |             loadedSigningKey = null; | ||||||
|  |             // Start garbage collection?
 | ||||||
|         } catch (InvalidKeyException e) { |         } catch (InvalidKeyException e) { | ||||||
|             // Do nothing
 |             // Do nothing
 | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -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; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -4,6 +4,7 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider; | ||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| 
 | 
 | ||||||
|  | import java.security.Provider; | ||||||
| import java.security.Security; | import java.security.Security; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  | @ -13,6 +14,7 @@ public class GlobalCryptoSetup { | ||||||
|     final static Logger logger = LoggerFactory.getLogger(GlobalCryptoSetup.class); |     final static Logger logger = LoggerFactory.getLogger(GlobalCryptoSetup.class); | ||||||
| 
 | 
 | ||||||
|     static boolean loadedBouncyCastle = false; |     static boolean loadedBouncyCastle = false; | ||||||
|  |     static Provider bouncyCastleProvider; | ||||||
| 
 | 
 | ||||||
|     public static boolean hasSecp256k1Curve() { |     public static boolean hasSecp256k1Curve() { | ||||||
|         // For now we just check if the java version is at least 8
 |         // 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))); |         return ((major > 1) || ((major > 0) && (minor > 7))); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static void doSetup() { |     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
 |             // 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)
 |             // (earlier version don't support the EC curve we use for signatures)
 | ||||||
|             if (!hasSecp256k1Curve() && !loadedBouncyCastle) { |             if (!hasSecp256k1Curve() && !loadedBouncyCastle) { | ||||||
|                 loadedBouncyCastle = true; |                 loadedBouncyCastle = true; | ||||||
|             Security.insertProviderAt(new BouncyCastleProvider(), 1); |                 Security.insertProviderAt(bouncyCastleProvider, 1); | ||||||
|                 logger.info("Using BouncyCastle instead of native provider to support secp256k1 named curve"); |                 logger.info("Using BouncyCastle instead of native provider to support secp256k1 named curve"); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     public GlobalCryptoSetup() { |     public GlobalCryptoSetup() { doSetup(); } | ||||||
|         doSetup(); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -78,6 +78,10 @@ public class SHA256Digest extends GlobalCryptoSetup implements Digest { | ||||||
|         hash.update(msg); |         hash.update(msg); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     final public void update(ByteBuffer msg) { | ||||||
|  |         hash.update(msg); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void reset() { |     public void reset() { | ||||||
|         hash.reset(); |         hash.reset(); | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  | } | ||||||
|  | @ -9,6 +9,10 @@ enum SignatureType { | ||||||
|     DSA = 1; |     DSA = 1; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | message BigInteger { | ||||||
|  |     bytes data = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // A digital signature | // A digital signature | ||||||
| message Signature { | message Signature { | ||||||
|     SignatureType type = 1; |     SignatureType type = 1; | ||||||
|  |  | ||||||
|  | @ -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); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,13 +1,16 @@ | ||||||
| package meerkat.crypto.concrete; | package meerkat.crypto.concrete; | ||||||
| 
 | 
 | ||||||
| import com.google.protobuf.ByteString; | import com.google.protobuf.ByteString; | ||||||
|  | import com.google.protobuf.Message; | ||||||
| import meerkat.protobuf.Crypto; | import meerkat.protobuf.Crypto; | ||||||
| import meerkat.crypto.concrete.ECDSASignature; |  | ||||||
| import meerkat.protobuf.BulletinBoardAPI.*; | import meerkat.protobuf.BulletinBoardAPI.*; | ||||||
|  | import org.junit.Before; | ||||||
| import org.junit.Test; | import org.junit.Test; | ||||||
| 
 | 
 | ||||||
| import java.io.InputStream; | import java.io.InputStream; | ||||||
|  | import java.math.BigInteger; | ||||||
| import java.security.KeyStore; | import java.security.KeyStore; | ||||||
|  | import java.util.Random; | ||||||
| 
 | 
 | ||||||
| import static org.junit.Assert.assertFalse; | import static org.junit.Assert.assertFalse; | ||||||
| import static org.junit.Assert.assertTrue; | import static org.junit.Assert.assertTrue; | ||||||
|  | @ -15,7 +18,7 @@ import static org.junit.Assert.assertTrue; | ||||||
| /** | /** | ||||||
|  * Created by talm on 12/11/15. |  * 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_EXAMPLE = "/certs/enduser-certs/user1-key-with-password-secret.p12"; | ||||||
|     public static String KEYFILE_PASSWORD = "secret"; |     public static String KEYFILE_PASSWORD = "secret"; | ||||||
| 
 | 
 | ||||||
|  | @ -27,16 +30,26 @@ public class TestECDSASignature { | ||||||
| 
 | 
 | ||||||
|     public static String HELLO_WORLD = "hello world!"; |     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 |     @Test | ||||||
|     public void loadSignatureKey() 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(); |         KeyStore.Builder keyStore = signer.getPKCS12KeyStoreBuilder(keyStream, password); | ||||||
| 
 |         signer.loadSigningCertificate(keyStore); | ||||||
|         KeyStore.Builder keyStore = sig.getPKCS12KeyStoreBuilder(keyStream, password); |  | ||||||
|         sig.loadSigningCertificate(keyStore); |  | ||||||
|         keyStream.close(); |         keyStream.close(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -44,9 +57,7 @@ public class TestECDSASignature { | ||||||
|     public void loadPEMVerificationKey() throws Exception { |     public void loadPEMVerificationKey() throws Exception { | ||||||
|         InputStream certStream = getClass().getResourceAsStream(CERT1_PEM_EXAMPLE); |         InputStream certStream = getClass().getResourceAsStream(CERT1_PEM_EXAMPLE); | ||||||
| 
 | 
 | ||||||
|         ECDSASignature sig = new ECDSASignature(); |         signer.loadVerificationCertificates(certStream); | ||||||
| 
 |  | ||||||
|         sig.loadVerificationCertificates(certStream); |  | ||||||
|         certStream.close(); |         certStream.close(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -54,9 +65,7 @@ public class TestECDSASignature { | ||||||
|     public void loadDERVerificationKey() throws Exception { |     public void loadDERVerificationKey() throws Exception { | ||||||
|         InputStream certStream = getClass().getResourceAsStream(CERT2_DER_EXAMPLE); |         InputStream certStream = getClass().getResourceAsStream(CERT2_DER_EXAMPLE); | ||||||
| 
 | 
 | ||||||
|         ECDSASignature sig = new ECDSASignature(); |         signer.loadVerificationCertificates(certStream); | ||||||
| 
 |  | ||||||
|         sig.loadVerificationCertificates(certStream); |  | ||||||
|         certStream.close(); |         certStream.close(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -67,8 +76,6 @@ public class TestECDSASignature { | ||||||
|         InputStream msgStream = getClass().getResourceAsStream(MSG_PLAINTEXT_EXAMPLE); |         InputStream msgStream = getClass().getResourceAsStream(MSG_PLAINTEXT_EXAMPLE); | ||||||
|         InputStream sigStream = getClass().getResourceAsStream(MSG_SIG_EXAMPLE); |         InputStream sigStream = getClass().getResourceAsStream(MSG_SIG_EXAMPLE); | ||||||
| 
 | 
 | ||||||
|         ECDSASignature signer = new ECDSASignature(); |  | ||||||
| 
 |  | ||||||
|         signer.loadVerificationCertificates(certStream); |         signer.loadVerificationCertificates(certStream); | ||||||
|         certStream.close(); |         certStream.close(); | ||||||
| 
 | 
 | ||||||
|  | @ -79,7 +86,7 @@ public class TestECDSASignature { | ||||||
| 
 | 
 | ||||||
|         Crypto.Signature builtSig = sig.build(); |         Crypto.Signature builtSig = sig.build(); | ||||||
|         signer.initVerify(builtSig); |         signer.initVerify(builtSig); | ||||||
|         signer.updateSigner(msgStream); |         signer.updateContent(msgStream); | ||||||
|         assertTrue("Signature did not verify!", signer.verify()); |         assertTrue("Signature did not verify!", signer.verify()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -89,8 +96,6 @@ public class TestECDSASignature { | ||||||
|         InputStream msgStream = getClass().getResourceAsStream(MSG_PLAINTEXT_EXAMPLE); |         InputStream msgStream = getClass().getResourceAsStream(MSG_PLAINTEXT_EXAMPLE); | ||||||
|         InputStream sigStream = getClass().getResourceAsStream(MSG_SIG_EXAMPLE); |         InputStream sigStream = getClass().getResourceAsStream(MSG_SIG_EXAMPLE); | ||||||
| 
 | 
 | ||||||
|         ECDSASignature signer = new ECDSASignature(); |  | ||||||
| 
 |  | ||||||
|         signer.loadVerificationCertificates(certStream); |         signer.loadVerificationCertificates(certStream); | ||||||
|         certStream.close(); |         certStream.close(); | ||||||
| 
 | 
 | ||||||
|  | @ -105,7 +110,7 @@ public class TestECDSASignature { | ||||||
| 
 | 
 | ||||||
|         Crypto.Signature builtSig = sig.build(); |         Crypto.Signature builtSig = sig.build(); | ||||||
|         signer.initVerify(builtSig); |         signer.initVerify(builtSig); | ||||||
|         signer.updateSigner(msgStream); |         signer.updateContent(msgStream); | ||||||
|         assertFalse("Bad Signature passed verification!", signer.verify()); |         assertFalse("Bad Signature passed verification!", signer.verify()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -116,8 +121,6 @@ public class TestECDSASignature { | ||||||
|         InputStream msgStream = getClass().getResourceAsStream(MSG_PLAINTEXT_EXAMPLE); |         InputStream msgStream = getClass().getResourceAsStream(MSG_PLAINTEXT_EXAMPLE); | ||||||
|         InputStream sigStream = getClass().getResourceAsStream(MSG_SIG_EXAMPLE); |         InputStream sigStream = getClass().getResourceAsStream(MSG_SIG_EXAMPLE); | ||||||
| 
 | 
 | ||||||
|         ECDSASignature signer = new ECDSASignature(); |  | ||||||
| 
 |  | ||||||
|         signer.loadVerificationCertificates(certStream); |         signer.loadVerificationCertificates(certStream); | ||||||
|         certStream.close(); |         certStream.close(); | ||||||
| 
 | 
 | ||||||
|  | @ -130,30 +133,27 @@ public class TestECDSASignature { | ||||||
| 
 | 
 | ||||||
|         Crypto.Signature builtSig = sig.build(); |         Crypto.Signature builtSig = sig.build(); | ||||||
|         signer.initVerify(builtSig); |         signer.initVerify(builtSig); | ||||||
|         signer.updateSigner(msgStream); |         signer.updateContent(msgStream); | ||||||
|         assertFalse("Signature doesn't match message but passed verification!", signer.verify()); |         assertFalse("Signature doesn't match message but passed verification!", signer.verify()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     @Test |     protected void loadSigningKeys() throws Exception { | ||||||
|     public void signAndVerify() 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 signer = new ECDSASignature(); |  | ||||||
| 
 |  | ||||||
|         KeyStore.Builder keyStore = signer.getPKCS12KeyStoreBuilder(keyStream, password); |         KeyStore.Builder keyStore = signer.getPKCS12KeyStoreBuilder(keyStream, password); | ||||||
|         signer.loadSigningCertificate(keyStore); |         signer.loadSigningCertificate(keyStore); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|  |     @Test | ||||||
|  |     public void signAndVerify() throws Exception { | ||||||
|  |         loadSigningKeys(); | ||||||
| 
 | 
 | ||||||
|         UnsignedBulletinBoardMessage.Builder unsignedMsgBuilder = UnsignedBulletinBoardMessage.newBuilder(); |         BigInteger rawMsg = new BigInteger(50, rand); | ||||||
|         unsignedMsgBuilder.setData(ByteString.copyFromUtf8(HELLO_WORLD)); |         Crypto.BigInteger usMsg = Crypto.BigInteger.newBuilder() | ||||||
|         unsignedMsgBuilder.addTag("Tag1"); |                 .setData(ByteString.copyFrom(rawMsg.toByteArray())).build(); | ||||||
|         unsignedMsgBuilder.addTag("Tag2"); |  | ||||||
|         unsignedMsgBuilder.addTag("Tag3"); |  | ||||||
| 
 |  | ||||||
|         UnsignedBulletinBoardMessage usMsg = unsignedMsgBuilder.build(); |  | ||||||
| 
 | 
 | ||||||
|         signer.updateContent(usMsg); |         signer.updateContent(usMsg); | ||||||
|         Crypto.Signature sig = signer.sign(); |         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()); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | @ -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); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @ -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"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -73,7 +73,6 @@ idea { | ||||||
| 
 | 
 | ||||||
|             def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java" |             def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java" | ||||||
| 
 | 
 | ||||||
|             println "Adding $srcDir" |  | ||||||
|             // add protobuf generated sources to generated source dir. |             // add protobuf generated sources to generated source dir. | ||||||
|             if ("test".equals(sourceSet.name)) { |             if ("test".equals(sourceSet.name)) { | ||||||
|                 testSourceDirs += file(srcDir) |                 testSourceDirs += file(srcDir) | ||||||
|  |  | ||||||
|  | @ -73,7 +73,6 @@ idea { | ||||||
| 
 | 
 | ||||||
|             def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java" |             def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java" | ||||||
| 
 | 
 | ||||||
|             println "Adding $srcDir" |  | ||||||
|             // add protobuf generated sources to generated source dir. |             // add protobuf generated sources to generated source dir. | ||||||
|             if ("test".equals(sourceSet.name)) { |             if ("test".equals(sourceSet.name)) { | ||||||
|                 testSourceDirs += file(srcDir) |                 testSourceDirs += file(srcDir) | ||||||
|  |  | ||||||
|  | @ -72,7 +72,6 @@ idea { | ||||||
| 
 | 
 | ||||||
|             def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java" |             def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java" | ||||||
| 
 | 
 | ||||||
|             println "Adding $srcDir" |  | ||||||
|             // add protobuf generated sources to generated source dir. |             // add protobuf generated sources to generated source dir. | ||||||
|             if ("test".equals(sourceSet.name)) { |             if ("test".equals(sourceSet.name)) { | ||||||
|                 testSourceDirs += file(srcDir) |                 testSourceDirs += file(srcDir) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue