Changed the root package from net.sf.briar to org.briarproject.

This commit is contained in:
akwizgran
2014-01-08 16:18:30 +00:00
parent dce70f487c
commit 832476412c
427 changed files with 2507 additions and 2507 deletions

View File

@@ -0,0 +1,71 @@
package org.briarproject.crypto;
import java.security.GeneralSecurityException;
import javax.crypto.Cipher;
import org.briarproject.api.crypto.AuthenticatedCipher;
import org.briarproject.api.crypto.SecretKey;
import org.spongycastle.crypto.DataLengthException;
import org.spongycastle.crypto.InvalidCipherTextException;
import org.spongycastle.crypto.modes.AEADBlockCipher;
import org.spongycastle.crypto.params.AEADParameters;
import org.spongycastle.crypto.params.KeyParameter;
class AuthenticatedCipherImpl implements AuthenticatedCipher {
private final AEADBlockCipher cipher;
private final int macLength;
AuthenticatedCipherImpl(AEADBlockCipher cipher, int macLength) {
this.cipher = cipher;
this.macLength = macLength;
}
public int doFinal(byte[] input, int inputOff, int len, byte[] output,
int outputOff) throws GeneralSecurityException {
int processed = 0;
if(len != 0) {
processed = cipher.processBytes(input, inputOff, len, output,
outputOff);
}
try {
return processed + cipher.doFinal(output, outputOff + processed);
} catch(DataLengthException e) {
throw new GeneralSecurityException(e.getMessage());
} catch(InvalidCipherTextException e) {
throw new GeneralSecurityException(e.getMessage());
}
}
public void init(int opmode, SecretKey key, byte[] iv, byte[] aad)
throws GeneralSecurityException {
KeyParameter k = new KeyParameter(key.getEncoded());
AEADParameters params = new AEADParameters(k, macLength * 8, iv, aad);
try {
switch(opmode) {
case Cipher.ENCRYPT_MODE:
case Cipher.WRAP_MODE:
cipher.init(true, params);
break;
case Cipher.DECRYPT_MODE:
case Cipher.UNWRAP_MODE:
cipher.init(false, params);
break;
default:
throw new IllegalArgumentException();
}
} catch(IllegalArgumentException e) {
throw new GeneralSecurityException(e.getMessage());
}
}
public int getMacLength() {
return macLength;
}
public int getBlockSize() {
return cipher.getUnderlyingCipher().getBlockSize();
}
}

View File

@@ -0,0 +1,526 @@
package org.briarproject.crypto;
import static java.util.logging.Level.INFO;
import static javax.crypto.Cipher.DECRYPT_MODE;
import static javax.crypto.Cipher.ENCRYPT_MODE;
import static org.briarproject.api.invitation.InvitationConstants.CODE_BITS;
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
import static org.briarproject.crypto.EllipticCurveConstants.P;
import static org.briarproject.crypto.EllipticCurveConstants.PARAMETERS;
import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
import org.briarproject.api.crypto.AuthenticatedCipher;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.KeyPair;
import org.briarproject.api.crypto.KeyParser;
import org.briarproject.api.crypto.MessageDigest;
import org.briarproject.api.crypto.PrivateKey;
import org.briarproject.api.crypto.PseudoRandom;
import org.briarproject.api.crypto.PublicKey;
import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.crypto.Signature;
import org.briarproject.util.ByteUtils;
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
import org.spongycastle.crypto.BlockCipher;
import org.spongycastle.crypto.CipherParameters;
import org.spongycastle.crypto.Mac;
import org.spongycastle.crypto.agreement.ECDHCBasicAgreement;
import org.spongycastle.crypto.digests.SHA384Digest;
import org.spongycastle.crypto.engines.AESLightEngine;
import org.spongycastle.crypto.generators.ECKeyPairGenerator;
import org.spongycastle.crypto.generators.PKCS5S2ParametersGenerator;
import org.spongycastle.crypto.macs.HMac;
import org.spongycastle.crypto.modes.AEADBlockCipher;
import org.spongycastle.crypto.modes.GCMBlockCipher;
import org.spongycastle.crypto.params.ECKeyGenerationParameters;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.spongycastle.crypto.params.KeyParameter;
import org.spongycastle.util.Strings;
class CryptoComponentImpl implements CryptoComponent {
private static final Logger LOG =
Logger.getLogger(CryptoComponentImpl.class.getName());
private static final int CIPHER_KEY_BYTES = 32; // 256 bits
private static final int AGREEMENT_KEY_PAIR_BITS = 384;
private static final int SIGNATURE_KEY_PAIR_BITS = 384;
private static final int MAC_BYTES = 16; // 128 bits
private static final int STORAGE_IV_BYTES = 16; // 128 bits
private static final int PBKDF_SALT_BYTES = 16; // 128 bits
private static final int PBKDF_TARGET_MILLIS = 500;
private static final int PBKDF_SAMPLES = 30;
// Labels for secret derivation
private static final byte[] MASTER = { 'M', 'A', 'S', 'T', 'E', 'R', '\0' };
private static final byte[] SALT = { 'S', 'A', 'L', 'T', '\0' };
private static final byte[] FIRST = { 'F', 'I', 'R', 'S', 'T', '\0' };
private static final byte[] ROTATE = { 'R', 'O', 'T', 'A', 'T', 'E', '\0' };
// Label for confirmation code derivation
private static final byte[] CODE = { 'C', 'O', 'D', 'E', '\0' };
// Label for invitation nonce derivation
private static final byte[] NONCE = { 'N', 'O', 'N', 'C', 'E', '\0' };
// Labels for key derivation
private static final byte[] A_TAG = { 'A', '_', 'T', 'A', 'G', '\0' };
private static final byte[] B_TAG = { 'B', '_', 'T', 'A', 'G', '\0' };
private static final byte[] A_FRAME_A =
{ 'A', '_', 'F', 'R', 'A', 'M', 'E', '_', 'A', '\0' };
private static final byte[] A_FRAME_B =
{ 'A', '_', 'F', 'R', 'A', 'M', 'E', '_', 'B', '\0' };
private static final byte[] B_FRAME_A =
{ 'B', '_', 'F', 'R', 'A', 'M', 'E', '_', 'A', '\0' };
private static final byte[] B_FRAME_B =
{ 'B', '_', 'F', 'R', 'A', 'M', 'E', '_', 'B', '\0' };
// Blank secret for argument validation
private static final byte[] BLANK_SECRET = new byte[CIPHER_KEY_BYTES];
private final KeyParser agreementKeyParser, signatureKeyParser;
private final SecureRandom secureRandom;
private final ECKeyPairGenerator agreementKeyPairGenerator;
private final ECKeyPairGenerator signatureKeyPairGenerator;
CryptoComponentImpl() {
agreementKeyParser = new Sec1KeyParser(PARAMETERS, P,
AGREEMENT_KEY_PAIR_BITS);
signatureKeyParser = new Sec1KeyParser(PARAMETERS, P,
SIGNATURE_KEY_PAIR_BITS);
secureRandom = new SecureRandom();
ECKeyGenerationParameters params = new ECKeyGenerationParameters(
PARAMETERS, secureRandom);
agreementKeyPairGenerator = new ECKeyPairGenerator();
agreementKeyPairGenerator.init(params);
signatureKeyPairGenerator = new ECKeyPairGenerator();
signatureKeyPairGenerator.init(params);
}
public SecretKey generateSecretKey() {
byte[] b = new byte[CIPHER_KEY_BYTES];
secureRandom.nextBytes(b);
return new SecretKeyImpl(b);
}
public MessageDigest getMessageDigest() {
return new DoubleDigest(new SHA384Digest());
}
public PseudoRandom getPseudoRandom(int seed1, int seed2) {
return new PseudoRandomImpl(getMessageDigest(), seed1, seed2);
}
public SecureRandom getSecureRandom() {
return secureRandom;
}
public Signature getSignature() {
return new SignatureImpl(secureRandom);
}
public KeyPair generateAgreementKeyPair() {
AsymmetricCipherKeyPair keyPair =
agreementKeyPairGenerator.generateKeyPair();
// Return a wrapper that uses the SEC 1 encoding
ECPublicKeyParameters ecPublicKey =
(ECPublicKeyParameters) keyPair.getPublic();
PublicKey publicKey = new Sec1PublicKey(ecPublicKey,
AGREEMENT_KEY_PAIR_BITS);
ECPrivateKeyParameters ecPrivateKey =
(ECPrivateKeyParameters) keyPair.getPrivate();
PrivateKey privateKey = new Sec1PrivateKey(ecPrivateKey,
AGREEMENT_KEY_PAIR_BITS);
return new KeyPair(publicKey, privateKey);
}
public KeyParser getAgreementKeyParser() {
return agreementKeyParser;
}
public KeyPair generateSignatureKeyPair() {
AsymmetricCipherKeyPair keyPair =
signatureKeyPairGenerator.generateKeyPair();
// Return a wrapper that uses the SEC 1 encoding
ECPublicKeyParameters ecPublicKey =
(ECPublicKeyParameters) keyPair.getPublic();
PublicKey publicKey = new Sec1PublicKey(ecPublicKey,
SIGNATURE_KEY_PAIR_BITS);
ECPrivateKeyParameters ecPrivateKey =
(ECPrivateKeyParameters) keyPair.getPrivate();
PrivateKey privateKey = new Sec1PrivateKey(ecPrivateKey,
SIGNATURE_KEY_PAIR_BITS);
return new KeyPair(publicKey, privateKey);
}
public KeyParser getSignatureKeyParser() {
return signatureKeyParser;
}
public int generateInvitationCode() {
int codeBytes = (int) Math.ceil(CODE_BITS / 8.0);
byte[] random = new byte[codeBytes];
secureRandom.nextBytes(random);
return ByteUtils.readUint(random, CODE_BITS);
}
public int[] deriveConfirmationCodes(byte[] secret) {
if(secret.length != CIPHER_KEY_BYTES)
throw new IllegalArgumentException();
if(Arrays.equals(secret, BLANK_SECRET))
throw new IllegalArgumentException();
byte[] alice = counterModeKdf(secret, CODE, 0);
byte[] bob = counterModeKdf(secret, CODE, 1);
int[] codes = new int[2];
codes[0] = ByteUtils.readUint(alice, CODE_BITS);
codes[1] = ByteUtils.readUint(bob, CODE_BITS);
ByteUtils.erase(alice);
ByteUtils.erase(bob);
return codes;
}
public byte[][] deriveInvitationNonces(byte[] secret) {
if(secret.length != CIPHER_KEY_BYTES)
throw new IllegalArgumentException();
if(Arrays.equals(secret, BLANK_SECRET))
throw new IllegalArgumentException();
byte[] alice = counterModeKdf(secret, NONCE, 0);
byte[] bob = counterModeKdf(secret, NONCE, 1);
return new byte[][] { alice, bob };
}
public byte[] deriveMasterSecret(byte[] theirPublicKey,
KeyPair ourKeyPair, boolean alice) throws GeneralSecurityException {
MessageDigest messageDigest = getMessageDigest();
byte[] ourPublicKey = ourKeyPair.getPublic().getEncoded();
byte[] ourHash = messageDigest.digest(ourPublicKey);
byte[] theirHash = messageDigest.digest(theirPublicKey);
byte[] aliceInfo, bobInfo;
if(alice) {
aliceInfo = ourHash;
bobInfo = theirHash;
} else {
aliceInfo = theirHash;
bobInfo = ourHash;
}
PrivateKey ourPriv = ourKeyPair.getPrivate();
PublicKey theirPub = agreementKeyParser.parsePublicKey(theirPublicKey);
// The raw secret comes from the key agreement algorithm
byte[] raw = deriveSharedSecret(ourPriv, theirPub);
// Derive the cooked secret from the raw secret using the
// concatenation KDF
byte[] cooked = concatenationKdf(raw, MASTER, aliceInfo, bobInfo);
ByteUtils.erase(raw);
return cooked;
}
// Package access for testing
byte[] deriveSharedSecret(PrivateKey priv, PublicKey pub)
throws GeneralSecurityException {
if(!(priv instanceof Sec1PrivateKey))
throw new IllegalArgumentException();
if(!(pub instanceof Sec1PublicKey))
throw new IllegalArgumentException();
ECPrivateKeyParameters ecPriv = ((Sec1PrivateKey) priv).getKey();
ECPublicKeyParameters ecPub = ((Sec1PublicKey) pub).getKey();
ECDHCBasicAgreement agreement = new ECDHCBasicAgreement();
agreement.init(ecPriv);
// FIXME: Should we use another format for the shared secret?
return agreement.calculateAgreement(ecPub).toByteArray();
}
public byte[] deriveGroupSalt(byte[] secret) {
if(secret.length != CIPHER_KEY_BYTES)
throw new IllegalArgumentException();
if(Arrays.equals(secret, BLANK_SECRET))
throw new IllegalArgumentException();
return counterModeKdf(secret, SALT, 0);
}
public byte[] deriveInitialSecret(byte[] secret, int transportIndex) {
if(secret.length != CIPHER_KEY_BYTES)
throw new IllegalArgumentException();
if(Arrays.equals(secret, BLANK_SECRET))
throw new IllegalArgumentException();
if(transportIndex < 0) throw new IllegalArgumentException();
return counterModeKdf(secret, FIRST, transportIndex);
}
public byte[] deriveNextSecret(byte[] secret, long period) {
if(secret.length != CIPHER_KEY_BYTES)
throw new IllegalArgumentException();
if(Arrays.equals(secret, BLANK_SECRET))
throw new IllegalArgumentException();
if(period < 0 || period > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
return counterModeKdf(secret, ROTATE, period);
}
public SecretKey deriveTagKey(byte[] secret, boolean alice) {
if(secret.length != CIPHER_KEY_BYTES)
throw new IllegalArgumentException();
if(Arrays.equals(secret, BLANK_SECRET))
throw new IllegalArgumentException();
if(alice) return deriveKey(secret, A_TAG, 0);
else return deriveKey(secret, B_TAG, 0);
}
public SecretKey deriveFrameKey(byte[] secret, long connection,
boolean alice, boolean initiator) {
if(secret.length != CIPHER_KEY_BYTES)
throw new IllegalArgumentException();
if(Arrays.equals(secret, BLANK_SECRET))
throw new IllegalArgumentException();
if(connection < 0 || connection > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
if(alice) {
if(initiator) return deriveKey(secret, A_FRAME_A, connection);
else return deriveKey(secret, A_FRAME_B, connection);
} else {
if(initiator) return deriveKey(secret, B_FRAME_A, connection);
else return deriveKey(secret, B_FRAME_B, connection);
}
}
private SecretKey deriveKey(byte[] secret, byte[] label, long context) {
if(secret.length != CIPHER_KEY_BYTES)
throw new IllegalArgumentException();
if(Arrays.equals(secret, BLANK_SECRET))
throw new IllegalArgumentException();
byte[] key = counterModeKdf(secret, label, context);
return new SecretKeyImpl(key);
}
public AuthenticatedCipher getFrameCipher() {
AEADBlockCipher cipher = new GCMBlockCipher(new AESLightEngine());
return new AuthenticatedCipherImpl(cipher, MAC_BYTES);
}
public void encodeTag(byte[] tag, SecretKey tagKey, long connection) {
if(tag.length < TAG_LENGTH) throw new IllegalArgumentException();
if(connection < 0 || connection > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
for(int i = 0; i < TAG_LENGTH; i++) tag[i] = 0;
ByteUtils.writeUint32(connection, tag, 0);
BlockCipher cipher = new AESLightEngine();
assert cipher.getBlockSize() == TAG_LENGTH;
KeyParameter k = new KeyParameter(tagKey.getEncoded());
cipher.init(true, k);
cipher.processBlock(tag, 0, tag, 0);
ByteUtils.erase(k.getKey());
}
public byte[] encryptWithPassword(byte[] input, char[] password) {
// Generate a random salt
byte[] salt = new byte[PBKDF_SALT_BYTES];
secureRandom.nextBytes(salt);
// Calibrate the KDF
int iterations = chooseIterationCount(PBKDF_TARGET_MILLIS);
// Derive the key from the password
byte[] keyBytes = pbkdf2(password, salt, iterations);
SecretKey key = new SecretKeyImpl(keyBytes);
// Generate a random IV
byte[] iv = new byte[STORAGE_IV_BYTES];
secureRandom.nextBytes(iv);
// The output contains the salt, iterations, IV, ciphertext and MAC
int outputLen = salt.length + 4 + iv.length + input.length + MAC_BYTES;
byte[] output = new byte[outputLen];
System.arraycopy(salt, 0, output, 0, salt.length);
ByteUtils.writeUint32(iterations, output, salt.length);
System.arraycopy(iv, 0, output, salt.length + 4, iv.length);
// Initialise the cipher and encrypt the plaintext
try {
AEADBlockCipher c = new GCMBlockCipher(new AESLightEngine());
AuthenticatedCipher cipher = new AuthenticatedCipherImpl(c,
MAC_BYTES);
cipher.init(ENCRYPT_MODE, key, iv, null);
int outputOff = salt.length + 4 + iv.length;
cipher.doFinal(input, 0, input.length, output, outputOff);
return output;
} catch(GeneralSecurityException e) {
throw new RuntimeException(e);
} finally {
key.erase();
}
}
public byte[] decryptWithPassword(byte[] input, char[] password) {
// The input contains the salt, iterations, IV, ciphertext and MAC
if(input.length < PBKDF_SALT_BYTES + 4 + STORAGE_IV_BYTES + MAC_BYTES)
return null; // Invalid
byte[] salt = new byte[PBKDF_SALT_BYTES];
System.arraycopy(input, 0, salt, 0, salt.length);
long iterations = ByteUtils.readUint32(input, salt.length);
if(iterations < 0 || iterations > Integer.MAX_VALUE)
return null; // Invalid
byte[] iv = new byte[STORAGE_IV_BYTES];
System.arraycopy(input, salt.length + 4, iv, 0, iv.length);
// Derive the key from the password
byte[] keyBytes = pbkdf2(password, salt, (int) iterations);
SecretKey key = new SecretKeyImpl(keyBytes);
// Initialise the cipher
AuthenticatedCipher cipher;
try {
AEADBlockCipher c = new GCMBlockCipher(new AESLightEngine());
cipher = new AuthenticatedCipherImpl(c, MAC_BYTES);
cipher.init(DECRYPT_MODE, key, iv, null);
} catch(GeneralSecurityException e) {
key.erase();
throw new RuntimeException(e);
}
// Try to decrypt the ciphertext (may be invalid)
try {
int inputOff = salt.length + 4 + iv.length;
int inputLen = input.length - inputOff;
byte[] output = new byte[inputLen - MAC_BYTES];
cipher.doFinal(input, inputOff, inputLen, output, 0);
return output;
} catch(GeneralSecurityException e) {
return null; // Invalid
} finally {
key.erase();
}
}
// Key derivation function based on a hash function - see NIST SP 800-56A,
// section 5.8
private byte[] concatenationKdf(byte[] rawSecret, byte[] label,
byte[] initiatorInfo, byte[] responderInfo) {
// The output of the hash function must be long enough to use as a key
MessageDigest messageDigest = getMessageDigest();
if(messageDigest.getDigestLength() < CIPHER_KEY_BYTES)
throw new RuntimeException();
// The length of every field must fit in an unsigned 8-bit integer
if(rawSecret.length > 255) throw new IllegalArgumentException();
if(label.length > 255) throw new IllegalArgumentException();
if(initiatorInfo.length > 255) throw new IllegalArgumentException();
if(responderInfo.length > 255) throw new IllegalArgumentException();
// All fields are length-prefixed
messageDigest.update((byte) rawSecret.length);
messageDigest.update(rawSecret);
messageDigest.update((byte) label.length);
messageDigest.update(label);
messageDigest.update((byte) initiatorInfo.length);
messageDigest.update(initiatorInfo);
messageDigest.update((byte) responderInfo.length);
messageDigest.update(responderInfo);
byte[] hash = messageDigest.digest();
// The secret is the first CIPHER_KEY_BYTES bytes of the hash
byte[] output = new byte[CIPHER_KEY_BYTES];
System.arraycopy(hash, 0, output, 0, output.length);
ByteUtils.erase(hash);
return output;
}
// Key derivation function based on a PRF in counter mode - see
// NIST SP 800-108, section 5.1
private byte[] counterModeKdf(byte[] secret, byte[] label, long context) {
if(secret.length != CIPHER_KEY_BYTES)
throw new IllegalArgumentException();
if(Arrays.equals(secret, BLANK_SECRET))
throw new IllegalArgumentException();
// The label must be null-terminated
if(label[label.length - 1] != '\0')
throw new IllegalArgumentException();
// Initialise the PRF
Mac prf = new HMac(new SHA384Digest());
KeyParameter k = new KeyParameter(secret);
prf.init(k);
int macLength = prf.getMacSize();
// The output of the PRF must be long enough to use as a key
if(macLength < CIPHER_KEY_BYTES) throw new RuntimeException();
byte[] mac = new byte[macLength], output = new byte[CIPHER_KEY_BYTES];
prf.update((byte) 0); // Counter
prf.update(label, 0, label.length); // Null-terminated
byte[] contextBytes = new byte[4];
ByteUtils.writeUint32(context, contextBytes, 0);
prf.update(contextBytes, 0, contextBytes.length);
prf.update((byte) CIPHER_KEY_BYTES); // Output length
prf.doFinal(mac, 0);
System.arraycopy(mac, 0, output, 0, output.length);
ByteUtils.erase(mac);
ByteUtils.erase(k.getKey());
return output;
}
// Password-based key derivation function - see PKCS#5 v2.1, section 5.2
private byte[] pbkdf2(char[] password, byte[] salt, int iterations) {
byte[] utf8 = toUtf8ByteArray(password);
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator();
gen.init(utf8, salt, iterations);
int keyLengthInBits = CIPHER_KEY_BYTES * 8;
CipherParameters p = gen.generateDerivedParameters(keyLengthInBits);
ByteUtils.erase(utf8);
return ((KeyParameter) p).getKey();
}
// Package access for testing
int chooseIterationCount(int targetMillis) {
List<Long> quickSamples = new ArrayList<Long>(PBKDF_SAMPLES);
List<Long> slowSamples = new ArrayList<Long>(PBKDF_SAMPLES);
long iterationNanos = 0, initNanos = 0;
while(iterationNanos <= 0 || initNanos <= 0) {
// Sample the running time with one iteration and two iterations
for(int i = 0; i < PBKDF_SAMPLES; i++) {
quickSamples.add(sampleRunningTime(1));
slowSamples.add(sampleRunningTime(2));
}
// Calculate the iteration time and the initialisation time
long quickMedian = median(quickSamples);
long slowMedian = median(slowSamples);
iterationNanos = slowMedian - quickMedian;
initNanos = quickMedian - iterationNanos;
if(LOG.isLoggable(INFO)) {
LOG.info("Init: " + initNanos + ", iteration: "
+ iterationNanos);
}
}
long targetNanos = targetMillis * 1000L * 1000L;
long iterations = (targetNanos - initNanos) / iterationNanos;
if(LOG.isLoggable(INFO)) LOG.info("Target iterations: " + iterations);
if(iterations < 1) return 1;
if(iterations > Integer.MAX_VALUE) return Integer.MAX_VALUE;
return (int) iterations;
}
private long sampleRunningTime(int iterations) {
byte[] password = { 'p', 'a', 's', 's', 'w', 'o', 'r', 'd' };
byte[] salt = new byte[PBKDF_SALT_BYTES];
int keyLengthInBits = CIPHER_KEY_BYTES * 8;
long start = System.nanoTime();
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator();
gen.init(password, salt, iterations);
gen.generateDerivedParameters(keyLengthInBits);
return System.nanoTime() - start;
}
private long median(List<Long> list) {
int size = list.size();
if(size == 0) throw new IllegalArgumentException();
Collections.sort(list);
if(size % 2 == 1) return list.get(size / 2);
return list.get(size / 2 - 1) + list.get(size / 2) / 2;
}
byte[] toUtf8ByteArray(char[] c) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
Strings.toUTF8ByteArray(c, out);
byte[] utf8 = out.toByteArray();
// Erase the output stream's buffer
out.reset();
out.write(new byte[utf8.length]);
return utf8;
} catch(IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,50 @@
package org.briarproject.crypto;
import static java.util.concurrent.TimeUnit.SECONDS;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import javax.inject.Singleton;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.CryptoExecutor;
import org.briarproject.api.lifecycle.LifecycleManager;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
public class CryptoModule extends AbstractModule {
/** The maximum number of executor threads. */
private static final int MAX_EXECUTOR_THREADS =
Runtime.getRuntime().availableProcessors();
private final ExecutorService cryptoExecutor;
public CryptoModule() {
// The queue is unbounded, so tasks can be dependent
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
// Discard tasks that are submitted during shutdown
RejectedExecutionHandler policy =
new ThreadPoolExecutor.DiscardPolicy();
// Create a limited # of threads and keep them in the pool for 60 secs
cryptoExecutor = new ThreadPoolExecutor(0, MAX_EXECUTOR_THREADS,
60, SECONDS, queue, policy);
}
protected void configure() {
bind(CryptoComponent.class).to(
CryptoComponentImpl.class).in(Singleton.class);
}
@Provides @Singleton @CryptoExecutor
Executor getCryptoExecutor(LifecycleManager lifecycleManager) {
lifecycleManager.registerForShutdown(cryptoExecutor);
return cryptoExecutor;
}
}

View File

@@ -0,0 +1,63 @@
package org.briarproject.crypto;
import org.briarproject.api.crypto.MessageDigest;
import org.spongycastle.crypto.Digest;
/**
* A message digest that prevents length extension attacks - see Ferguson and
* Schneier, <i>Practical Cryptography</i>, chapter 6.
* <p>
* "Let h be an interative hash function. The hash function h<sub>d</sub> is
* defined by h<sub>d</sub> := h(h(m)), and has a claimed security level of
* min(k, n/2) where k is the security level of h and n is the size of the hash
* result."
*/
class DoubleDigest implements MessageDigest {
private final Digest delegate;
DoubleDigest(Digest delegate) {
this.delegate = delegate;
}
public byte[] digest() {
byte[] digest = new byte[delegate.getDigestSize()];
delegate.doFinal(digest, 0); // h(m)
delegate.update(digest, 0, digest.length);
delegate.doFinal(digest, 0); // h(h(m))
return digest;
}
public byte[] digest(byte[] input) {
delegate.update(input, 0, input.length);
return digest();
}
public int digest(byte[] buf, int offset, int len) {
byte[] digest = digest();
len = Math.min(len, digest.length);
System.arraycopy(digest, 0, buf, offset, len);
return len;
}
public int getDigestLength() {
return delegate.getDigestSize();
}
public void reset() {
delegate.reset();
}
public void update(byte input) {
delegate.update(input);
}
public void update(byte[] input) {
delegate.update(input, 0, input.length);
}
public void update(byte[] input, int offset, int len) {
delegate.update(input, offset, len);
}
}

View File

@@ -0,0 +1,70 @@
package org.briarproject.crypto;
import java.math.BigInteger;
import org.spongycastle.crypto.params.ECDomainParameters;
import org.spongycastle.math.ec.ECCurve;
import org.spongycastle.math.ec.ECFieldElement;
import org.spongycastle.math.ec.ECPoint;
/** Parameters for curve brainpoolP384r1 - see RFC 5639. */
interface EllipticCurveConstants {
/**
* The prime specifying the finite field. (This is called p in RFC 5639 and
* q in SEC 2.)
*/
BigInteger P = new BigInteger("8CB91E82" + "A3386D28" + "0F5D6F7E" +
"50E641DF" + "152F7109" + "ED5456B4" + "12B1DA19" + "7FB71123" +
"ACD3A729" + "901D1A71" + "87470013" + "3107EC53", 16);
/**
* A coefficient of the equation y^2 = x^3 + A*x + B defining the elliptic
* curve. (This is called A in RFC 5639 and a in SEC 2.)
*/
BigInteger A = new BigInteger("7BC382C6" + "3D8C150C" + "3C72080A" +
"CE05AFA0" + "C2BEA28E" + "4FB22787" + "139165EF" + "BA91F90F" +
"8AA5814A" + "503AD4EB" + "04A8C7DD" + "22CE2826", 16);
/**
* A coefficient of the equation y^2 = x^3 + A*x + B defining the elliptic
* curve. (This is called B in RFC 5639 b in SEC 2.)
*/
BigInteger B = new BigInteger("04A8C7DD" + "22CE2826" + "8B39B554" +
"16F0447C" + "2FB77DE1" + "07DCD2A6" + "2E880EA5" + "3EEB62D5" +
"7CB43902" + "95DBC994" + "3AB78696" + "FA504C11", 16);
/**
* The x co-ordinate of the base point G. (This is called x in RFC 5639 and
* SEC 2.)
*/
BigInteger X = new BigInteger("1D1C64F0" + "68CF45FF" + "A2A63A81" +
"B7C13F6B" + "8847A3E7" + "7EF14FE3" + "DB7FCAFE" + "0CBD10E8" +
"E826E034" + "36D646AA" + "EF87B2E2" + "47D4AF1E", 16);
/**
* The y co-ordinate of the base point G. (This is called y in RFC 5639 and
* SEC 2.)
*/
BigInteger Y = new BigInteger("8ABE1D75" + "20F9C2A4" + "5CB1EB8E" +
"95CFD552" + "62B70B29" + "FEEC5864" + "E19C054F" + "F9912928" +
"0E464621" + "77918111" + "42820341" + "263C5315", 16);
/**
* The order of the base point G. (This is called q in RFC 5639 and n in
* SEC 2.)
*/
BigInteger Q = new BigInteger("8CB91E82" + "A3386D28" + "0F5D6F7E" +
"50E641DF" + "152F7109" + "ED5456B3" + "1F166E6C" + "AC0425A7" +
"CF3AB6AF" + "6B7FC310" + "3B883202" + "E9046565", 16);
/** The cofactor of G. (This is called h in RFC 5639 and SEC 2.) */
BigInteger H = BigInteger.ONE;
// Static parameter objects derived from the above parameters
ECCurve CURVE = new ECCurve.Fp(P, A, B);
ECPoint G = new ECPoint.Fp(CURVE,
new ECFieldElement.Fp(P, X),
new ECFieldElement.Fp(P, Y));
ECDomainParameters PARAMETERS = new ECDomainParameters(CURVE, G, Q, H);
}

View File

@@ -0,0 +1,41 @@
package org.briarproject.crypto;
import org.briarproject.api.crypto.MessageDigest;
import org.briarproject.api.crypto.PseudoRandom;
import org.briarproject.util.ByteUtils;
class PseudoRandomImpl implements PseudoRandom {
private final MessageDigest messageDigest;
private byte[] state;
private int offset;
PseudoRandomImpl(MessageDigest messageDigest, int seed1, int seed2) {
this.messageDigest = messageDigest;
byte[] seedBytes = new byte[8];
ByteUtils.writeUint32(seed1, seedBytes, 0);
ByteUtils.writeUint32(seed2, seedBytes, 4);
messageDigest.update(seedBytes);
state = messageDigest.digest();
offset = 0;
}
public synchronized byte[] nextBytes(int bytes) {
byte[] b = new byte[bytes];
int half = state.length / 2;
int off = 0, len = b.length, available = half - offset;
while(available < len) {
System.arraycopy(state, offset, b, off, available);
off += available;
len -= available;
messageDigest.update(state, half, half);
state = messageDigest.digest();
offset = 0;
available = half;
}
System.arraycopy(state, offset, b, off, len);
offset += len;
return b;
}
}

View File

@@ -0,0 +1,86 @@
package org.briarproject.crypto;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import org.briarproject.api.crypto.KeyParser;
import org.briarproject.api.crypto.PrivateKey;
import org.briarproject.api.crypto.PublicKey;
import org.spongycastle.crypto.params.ECDomainParameters;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.spongycastle.math.ec.ECFieldElement;
import org.spongycastle.math.ec.ECPoint;
/**
* A key parser that uses the encoding defined in "SEC 1: Elliptic Curve
* Cryptography", section 2.3 (Certicom Corporation, May 2009). Point
* compression is not used.
*/
class Sec1KeyParser implements KeyParser {
private final ECDomainParameters params;
private final BigInteger modulus;
private final int keyBits, bytesPerInt, publicKeyBytes, privateKeyBytes;
Sec1KeyParser(ECDomainParameters params, BigInteger modulus, int keyBits) {
this.params = params;
this.modulus = modulus;
this.keyBits = keyBits;
bytesPerInt = (keyBits + 7) / 8;
publicKeyBytes = 1 + 2 * bytesPerInt;
privateKeyBytes = bytesPerInt;
}
public PublicKey parsePublicKey(byte[] encodedKey)
throws GeneralSecurityException {
// The validation procedure comes from SEC 1, section 3.2.2.1. Note
// that SEC 1 parameter names are used below, not RFC 5639 names
if(encodedKey.length != publicKeyBytes)
throw new GeneralSecurityException();
// The first byte must be 0x04
if(encodedKey[0] != 4) throw new GeneralSecurityException();
// The x co-ordinate must be >= 0 and < p
byte[] xBytes = new byte[bytesPerInt];
System.arraycopy(encodedKey, 1, xBytes, 0, bytesPerInt);
BigInteger x = new BigInteger(1, xBytes); // Positive signum
if(x.compareTo(modulus) >= 0) throw new GeneralSecurityException();
// The y co-ordinate must be >= 0 and < p
byte[] yBytes = new byte[bytesPerInt];
System.arraycopy(encodedKey, 1 + bytesPerInt, yBytes, 0, bytesPerInt);
BigInteger y = new BigInteger(1, yBytes); // Positive signum
if(y.compareTo(modulus) >= 0) throw new GeneralSecurityException();
// Verify that y^2 == x^3 + ax + b (mod p)
BigInteger a = params.getCurve().getA().toBigInteger();
BigInteger b = params.getCurve().getB().toBigInteger();
BigInteger lhs = y.multiply(y).mod(modulus);
BigInteger rhs = x.multiply(x).add(a).multiply(x).add(b).mod(modulus);
if(!lhs.equals(rhs)) throw new GeneralSecurityException();
// We know the point (x, y) is on the curve, so we can create the point
ECFieldElement elementX = new ECFieldElement.Fp(modulus, x);
ECFieldElement elementY = new ECFieldElement.Fp(modulus, y);
ECPoint pub = new ECPoint.Fp(params.getCurve(), elementX, elementY);
// Verify that the point (x, y) is not the point at infinity
if(pub.isInfinity()) throw new GeneralSecurityException();
// Verify that the point (x, y) times n is the point at infinity
if(!pub.multiply(params.getN()).isInfinity())
throw new GeneralSecurityException();
// Construct a public key from the point (x, y) and the params
ECPublicKeyParameters k = new ECPublicKeyParameters(pub, params);
return new Sec1PublicKey(k, keyBits);
}
public PrivateKey parsePrivateKey(byte[] encodedKey)
throws GeneralSecurityException {
if(encodedKey.length != privateKeyBytes)
throw new GeneralSecurityException();
BigInteger d = new BigInteger(1, encodedKey); // Positive signum
// Verify that the private value is < n
if(d.compareTo(params.getN()) >= 0)
throw new GeneralSecurityException();
// Construct a private key from the private value and the params
ECPrivateKeyParameters k = new ECPrivateKeyParameters(d, params);
return new Sec1PrivateKey(k, keyBits);
}
}

View File

@@ -0,0 +1,27 @@
package org.briarproject.crypto;
import org.briarproject.api.crypto.PrivateKey;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
class Sec1PrivateKey implements PrivateKey {
private final ECPrivateKeyParameters key;
private final int bytesPerInt;
Sec1PrivateKey(ECPrivateKeyParameters key, int keyBits) {
this.key = key;
bytesPerInt = (keyBits + 7) / 8;
}
public byte[] getEncoded() {
byte[] encodedKey = new byte[bytesPerInt];
byte[] d = key.getD().toByteArray();
Sec1Utils.convertToFixedLength(d, encodedKey, bytesPerInt, 0);
return encodedKey;
}
ECPrivateKeyParameters getKey() {
return key;
}
}

View File

@@ -0,0 +1,37 @@
package org.briarproject.crypto;
import org.briarproject.api.crypto.PublicKey;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
/**
* An elliptic curve public key that uses the encoding defined in "SEC 1:
* Elliptic Curve Cryptography", section 2.3 (Certicom Corporation, May 2009).
* Point compression is not used.
*/
class Sec1PublicKey implements PublicKey {
private final ECPublicKeyParameters key;
private final int bytesPerInt, publicKeyBytes;
Sec1PublicKey(ECPublicKeyParameters key, int keyBits) {
this.key = key;
bytesPerInt = (keyBits + 7) / 8;
publicKeyBytes = 1 + 2 * bytesPerInt;
}
public byte[] getEncoded() {
byte[] encodedKey = new byte[publicKeyBytes];
encodedKey[0] = 4;
byte[] x = key.getQ().getX().toBigInteger().toByteArray();
Sec1Utils.convertToFixedLength(x, encodedKey, bytesPerInt, 1);
byte[] y = key.getQ().getY().toBigInteger().toByteArray();
Sec1Utils.convertToFixedLength(y, encodedKey, bytesPerInt,
1 + bytesPerInt);
return encodedKey;
}
ECPublicKeyParameters getKey() {
return key;
}
}

View File

@@ -0,0 +1,15 @@
package org.briarproject.crypto;
class Sec1Utils {
static void convertToFixedLength(byte[] src, byte[] dest, int destLen,
int destOff) {
if(src.length < destLen) {
destOff += destLen - src.length;
System.arraycopy(src, 0, dest, destOff, src.length);
} else {
int srcOff = src.length - destLen;
System.arraycopy(src, srcOff, dest, destOff, destLen);
}
}
}

View File

@@ -0,0 +1,30 @@
package org.briarproject.crypto;
import org.briarproject.api.crypto.SecretKey;
import org.briarproject.util.ByteUtils;
class SecretKeyImpl implements SecretKey {
private final byte[] key;
private boolean erased = false; // Locking: this
SecretKeyImpl(byte[] key) {
this.key = key;
}
public synchronized byte[] getEncoded() {
if(erased) throw new IllegalStateException();
return key;
}
public SecretKey copy() {
return new SecretKeyImpl(key.clone());
}
public synchronized void erase() {
if(erased) throw new IllegalStateException();
ByteUtils.erase(key);
erased = true;
}
}

View File

@@ -0,0 +1,58 @@
package org.briarproject.crypto;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import org.briarproject.api.crypto.PrivateKey;
import org.briarproject.api.crypto.PublicKey;
import org.briarproject.api.crypto.Signature;
import org.spongycastle.crypto.digests.SHA384Digest;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.spongycastle.crypto.params.ParametersWithRandom;
import org.spongycastle.crypto.signers.DSADigestSigner;
import org.spongycastle.crypto.signers.ECDSASigner;
class SignatureImpl implements Signature {
private final SecureRandom secureRandom;
private final DSADigestSigner signer;
SignatureImpl(SecureRandom secureRandom) {
this.secureRandom = secureRandom;
signer = new DSADigestSigner(new ECDSASigner(), new SHA384Digest());
}
public void initSign(PrivateKey k) throws GeneralSecurityException {
if(!(k instanceof Sec1PrivateKey)) throw new GeneralSecurityException();
ECPrivateKeyParameters priv = ((Sec1PrivateKey) k).getKey();
signer.init(true, new ParametersWithRandom(priv, secureRandom));
}
public void initVerify(PublicKey k) throws GeneralSecurityException {
if(!(k instanceof Sec1PublicKey)) throw new GeneralSecurityException();
ECPublicKeyParameters pub = ((Sec1PublicKey) k).getKey();
signer.init(false, pub);
}
public void update(byte b) {
signer.update(b);
}
public void update(byte[] b) {
update(b, 0, b.length);
}
public void update(byte[] b, int off, int len) {
signer.update(b, off, len);
}
public byte[] sign() {
return signer.generateSignature();
}
public boolean verify(byte[] signature) {
return signer.verifySignature(signature);
}
}