mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-14 19:59:05 +01:00
Pull-Merge of latest changes from main repo
This commit is contained in:
@@ -20,7 +20,7 @@ class AuthenticatedCipherImpl implements AuthenticatedCipher {
|
||||
this.macLength = macLength;
|
||||
}
|
||||
|
||||
public int doFinal(byte[] input, int inputOff, int len, byte[] output,
|
||||
public int process(byte[] input, int inputOff, int len, byte[] output,
|
||||
int outputOff) throws GeneralSecurityException {
|
||||
int processed = 0;
|
||||
if(len != 0) {
|
||||
@@ -38,7 +38,7 @@ class AuthenticatedCipherImpl implements AuthenticatedCipher {
|
||||
|
||||
public void init(boolean encrypt, SecretKey key, byte[] iv, byte[] aad)
|
||||
throws GeneralSecurityException {
|
||||
KeyParameter k = new KeyParameter(key.getEncoded());
|
||||
KeyParameter k = new KeyParameter(key.getBytes());
|
||||
AEADParameters params = new AEADParameters(k, macLength * 8, iv, aad);
|
||||
try {
|
||||
cipher.init(encrypt, params);
|
||||
|
||||
@@ -7,8 +7,6 @@ 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;
|
||||
@@ -31,6 +29,7 @@ import org.briarproject.api.crypto.SecretKey;
|
||||
import org.briarproject.api.crypto.Signature;
|
||||
import org.briarproject.api.system.SeedProvider;
|
||||
import org.briarproject.util.ByteUtils;
|
||||
import org.briarproject.util.StringUtils;
|
||||
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.spongycastle.crypto.BlockCipher;
|
||||
import org.spongycastle.crypto.CipherParameters;
|
||||
@@ -44,11 +43,11 @@ 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.modes.gcm.BasicGCMMultiplier;
|
||||
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 {
|
||||
|
||||
@@ -114,7 +113,7 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
public SecretKey generateSecretKey() {
|
||||
byte[] b = new byte[CIPHER_KEY_BYTES];
|
||||
secureRandom.nextBytes(b);
|
||||
return new SecretKeyImpl(b);
|
||||
return new SecretKey(b);
|
||||
}
|
||||
|
||||
public MessageDigest getMessageDigest() {
|
||||
@@ -188,8 +187,6 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -223,9 +220,7 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
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;
|
||||
return concatenationKdf(raw, MASTER, aliceInfo, bobInfo);
|
||||
}
|
||||
|
||||
// Package access for testing
|
||||
@@ -296,12 +291,16 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
}
|
||||
|
||||
private SecretKey deriveKey(byte[] secret, byte[] label, long context) {
|
||||
byte[] key = counterModeKdf(secret, label, context);
|
||||
return new SecretKeyImpl(key);
|
||||
return new SecretKey(counterModeKdf(secret, label, context));
|
||||
}
|
||||
|
||||
public AuthenticatedCipher getFrameCipher() {
|
||||
AEADBlockCipher a = new GCMBlockCipher(new AESLightEngine());
|
||||
return getAuthenticatedCipher();
|
||||
}
|
||||
|
||||
private AuthenticatedCipher getAuthenticatedCipher() {
|
||||
AEADBlockCipher a = new GCMBlockCipher(new AESLightEngine(),
|
||||
new BasicGCMMultiplier());
|
||||
return new AuthenticatedCipherImpl(a, MAC_BYTES);
|
||||
}
|
||||
|
||||
@@ -313,21 +312,19 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
ByteUtils.writeUint32(streamNumber, tag, 0);
|
||||
BlockCipher cipher = new AESLightEngine();
|
||||
assert cipher.getBlockSize() == TAG_LENGTH;
|
||||
KeyParameter k = new KeyParameter(tagKey.getEncoded());
|
||||
KeyParameter k = new KeyParameter(tagKey.getBytes());
|
||||
cipher.init(true, k);
|
||||
cipher.processBlock(tag, 0, tag, 0);
|
||||
ByteUtils.erase(k.getKey());
|
||||
}
|
||||
|
||||
public byte[] encryptWithPassword(byte[] input, char[] password) {
|
||||
public byte[] encryptWithPassword(byte[] input, String 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);
|
||||
SecretKey key = new SecretKey(pbkdf2(password, salt, iterations));
|
||||
// Generate a random IV
|
||||
byte[] iv = new byte[STORAGE_IV_BYTES];
|
||||
secureRandom.nextBytes(iv);
|
||||
@@ -338,22 +335,18 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
ByteUtils.writeUint32(iterations, output, salt.length);
|
||||
System.arraycopy(iv, 0, output, salt.length + 4, iv.length);
|
||||
// Initialise the cipher and encrypt the plaintext
|
||||
AuthenticatedCipher cipher = getAuthenticatedCipher();
|
||||
try {
|
||||
AEADBlockCipher a = new GCMBlockCipher(new AESLightEngine());
|
||||
AuthenticatedCipher cipher = new AuthenticatedCipherImpl(a,
|
||||
MAC_BYTES);
|
||||
cipher.init(true, key, iv, null);
|
||||
int outputOff = salt.length + 4 + iv.length;
|
||||
cipher.doFinal(input, 0, input.length, output, outputOff);
|
||||
cipher.process(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) {
|
||||
public byte[] decryptWithPassword(byte[] input, String 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
|
||||
@@ -365,16 +358,12 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
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);
|
||||
SecretKey key = new SecretKey(pbkdf2(password, salt, (int) iterations));
|
||||
// Initialise the cipher
|
||||
AuthenticatedCipher cipher;
|
||||
AuthenticatedCipher cipher = getAuthenticatedCipher();
|
||||
try {
|
||||
AEADBlockCipher a = new GCMBlockCipher(new AESLightEngine());
|
||||
cipher = new AuthenticatedCipherImpl(a, MAC_BYTES);
|
||||
cipher.init(false, key, iv, null);
|
||||
} catch(GeneralSecurityException e) {
|
||||
key.erase();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
// Try to decrypt the ciphertext (may be invalid)
|
||||
@@ -382,12 +371,10 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
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);
|
||||
cipher.process(input, inputOff, inputLen, output, 0);
|
||||
return output;
|
||||
} catch(GeneralSecurityException e) {
|
||||
return null; // Invalid
|
||||
} finally {
|
||||
key.erase();
|
||||
return null; // Invalid ciphertext
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,7 +404,6 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -447,20 +433,17 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
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);
|
||||
private byte[] pbkdf2(String password, byte[] salt, int iterations) {
|
||||
byte[] utf8 = StringUtils.toUtf8(password);
|
||||
Digest digest = new SHA384Digest();
|
||||
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(digest);
|
||||
gen.init(utf8, salt, iterations);
|
||||
int keyLengthInBits = CIPHER_KEY_BYTES * 8;
|
||||
CipherParameters p = gen.generateDerivedParameters(keyLengthInBits);
|
||||
ByteUtils.erase(utf8);
|
||||
return ((KeyParameter) p).getKey();
|
||||
}
|
||||
|
||||
@@ -512,18 +495,4 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
if(size % 2 == 1) return list.get(size / 2);
|
||||
return list.get(size / 2 - 1) + list.get(size / 2) / 2;
|
||||
}
|
||||
|
||||
private 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ import javax.inject.Singleton;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.crypto.CryptoExecutor;
|
||||
import org.briarproject.api.crypto.PasswordStrengthEstimator;
|
||||
import org.briarproject.api.crypto.StreamDecrypterFactory;
|
||||
import org.briarproject.api.crypto.StreamEncrypterFactory;
|
||||
import org.briarproject.api.lifecycle.LifecycleManager;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
@@ -44,6 +46,8 @@ public class CryptoModule extends AbstractModule {
|
||||
CryptoComponentImpl.class).in(Singleton.class);
|
||||
bind(PasswordStrengthEstimator.class).to(
|
||||
PasswordStrengthEstimatorImpl.class);
|
||||
bind(StreamDecrypterFactory.class).to(StreamDecrypterFactoryImpl.class);
|
||||
bind(StreamEncrypterFactory.class).to(StreamEncrypterFactoryImpl.class);
|
||||
}
|
||||
|
||||
@Provides @Singleton @CryptoExecutor
|
||||
|
||||
48
briar-core/src/org/briarproject/crypto/FrameEncoder.java
Normal file
48
briar-core/src/org/briarproject/crypto/FrameEncoder.java
Normal file
@@ -0,0 +1,48 @@
|
||||
package org.briarproject.crypto;
|
||||
|
||||
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.IV_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
|
||||
import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
||||
|
||||
import org.briarproject.util.ByteUtils;
|
||||
|
||||
class FrameEncoder {
|
||||
|
||||
static void encodeIv(byte[] iv, long frameNumber, boolean header) {
|
||||
if(iv.length < IV_LENGTH) throw new IllegalArgumentException();
|
||||
if(frameNumber < 0 || frameNumber > MAX_32_BIT_UNSIGNED)
|
||||
throw new IllegalArgumentException();
|
||||
ByteUtils.writeUint32(frameNumber, iv, 0);
|
||||
if(header) iv[4] = 1;
|
||||
else iv[4] = 0;
|
||||
for(int i = 5; i < IV_LENGTH; i++) iv[i] = 0;
|
||||
}
|
||||
|
||||
static void encodeHeader(byte[] header, boolean finalFrame,
|
||||
int payloadLength, int paddingLength) {
|
||||
if(header.length < HEADER_LENGTH) throw new IllegalArgumentException();
|
||||
if(payloadLength < 0) throw new IllegalArgumentException();
|
||||
if(paddingLength < 0) throw new IllegalArgumentException();
|
||||
if(payloadLength + paddingLength > MAX_PAYLOAD_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
ByteUtils.writeUint16(payloadLength, header, 0);
|
||||
ByteUtils.writeUint16(paddingLength, header, 2);
|
||||
if(finalFrame) header[0] |= 0x80;
|
||||
}
|
||||
|
||||
static boolean isFinalFrame(byte[] header) {
|
||||
if(header.length < HEADER_LENGTH) throw new IllegalArgumentException();
|
||||
return (header[0] & 0x80) == 0x80;
|
||||
}
|
||||
|
||||
static int getPayloadLength(byte[] header) {
|
||||
if(header.length < HEADER_LENGTH) throw new IllegalArgumentException();
|
||||
return ByteUtils.readUint16(header, 0) & 0x7FFF;
|
||||
}
|
||||
|
||||
static int getPaddingLength(byte[] header) {
|
||||
if(header.length < HEADER_LENGTH) throw new IllegalArgumentException();
|
||||
return ByteUtils.readUint16(header, 2);
|
||||
}
|
||||
}
|
||||
@@ -13,9 +13,10 @@ class PasswordStrengthEstimatorImpl implements PasswordStrengthEstimator {
|
||||
private static final double STRONG = Math.log(Math.pow(LOWER + UPPER +
|
||||
DIGIT + OTHER, 10));
|
||||
|
||||
public float estimateStrength(char[] password) {
|
||||
public float estimateStrength(String password) {
|
||||
HashSet<Character> unique = new HashSet<Character>();
|
||||
for(char c : password) unique.add(c);
|
||||
int length = password.length();
|
||||
for(int i = 0; i < length; i++) unique.add(password.charAt(i));
|
||||
boolean lower = false, upper = false, digit = false, other = false;
|
||||
for(char c : unique) {
|
||||
if(Character.isLowerCase(c)) lower = true;
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
package org.briarproject.crypto;
|
||||
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.briarproject.api.crypto.SecretKey;
|
||||
import org.briarproject.util.ByteUtils;
|
||||
|
||||
class SecretKeyImpl implements SecretKey {
|
||||
|
||||
private final byte[] key;
|
||||
|
||||
private boolean erased = false;
|
||||
|
||||
private final Lock synchLock = new ReentrantLock();
|
||||
|
||||
SecretKeyImpl(byte[] key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public byte[] getEncoded() {
|
||||
synchLock.lock();
|
||||
try{
|
||||
if(erased) throw new IllegalStateException();
|
||||
return key;
|
||||
}
|
||||
finally{
|
||||
synchLock.unlock();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public SecretKey copy() {
|
||||
return new SecretKeyImpl(key.clone());
|
||||
}
|
||||
|
||||
public void erase() {
|
||||
synchLock.lock();
|
||||
try{
|
||||
if(erased) throw new IllegalStateException();
|
||||
ByteUtils.erase(key);
|
||||
erased = true;
|
||||
}
|
||||
finally{
|
||||
synchLock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.briarproject.crypto;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.crypto.SecretKey;
|
||||
import org.briarproject.api.crypto.StreamDecrypter;
|
||||
import org.briarproject.api.crypto.StreamDecrypterFactory;
|
||||
import org.briarproject.api.transport.StreamContext;
|
||||
|
||||
class StreamDecrypterFactoryImpl implements StreamDecrypterFactory {
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
|
||||
@Inject
|
||||
StreamDecrypterFactoryImpl(CryptoComponent crypto) {
|
||||
this.crypto = crypto;
|
||||
}
|
||||
|
||||
public StreamDecrypter createStreamDecrypter(InputStream in,
|
||||
StreamContext ctx) {
|
||||
// Derive the frame key
|
||||
byte[] secret = ctx.getSecret();
|
||||
long streamNumber = ctx.getStreamNumber();
|
||||
boolean alice = !ctx.getAlice();
|
||||
SecretKey frameKey = crypto.deriveFrameKey(secret, streamNumber, alice);
|
||||
// Create the decrypter
|
||||
return new StreamDecrypterImpl(in, crypto.getFrameCipher(), frameKey);
|
||||
}
|
||||
|
||||
public StreamDecrypter createInvitationStreamDecrypter(InputStream in,
|
||||
byte[] secret, boolean alice) {
|
||||
// Derive the frame key
|
||||
SecretKey frameKey = crypto.deriveFrameKey(secret, 0, alice);
|
||||
// Create the decrypter
|
||||
return new StreamDecrypterImpl(in, crypto.getFrameCipher(), frameKey);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package org.briarproject.crypto;
|
||||
|
||||
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.IV_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.crypto.AuthenticatedCipher;
|
||||
import org.briarproject.api.crypto.SecretKey;
|
||||
import org.briarproject.api.crypto.StreamDecrypter;
|
||||
|
||||
class StreamDecrypterImpl implements StreamDecrypter {
|
||||
|
||||
private final InputStream in;
|
||||
private final AuthenticatedCipher frameCipher;
|
||||
private final SecretKey frameKey;
|
||||
private final byte[] iv, aad, header, ciphertext;
|
||||
|
||||
private long frameNumber;
|
||||
private boolean finalFrame;
|
||||
|
||||
StreamDecrypterImpl(InputStream in, AuthenticatedCipher frameCipher,
|
||||
SecretKey frameKey) {
|
||||
this.in = in;
|
||||
this.frameCipher = frameCipher;
|
||||
this.frameKey = frameKey;
|
||||
iv = new byte[IV_LENGTH];
|
||||
aad = new byte[IV_LENGTH];
|
||||
header = new byte[HEADER_LENGTH];
|
||||
ciphertext = new byte[MAX_FRAME_LENGTH];
|
||||
frameNumber = 0;
|
||||
finalFrame = false;
|
||||
}
|
||||
|
||||
public int readFrame(byte[] payload) throws IOException {
|
||||
if(payload.length < MAX_PAYLOAD_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
if(finalFrame) return -1;
|
||||
// Read the header
|
||||
int offset = 0;
|
||||
while(offset < HEADER_LENGTH) {
|
||||
int read = in.read(ciphertext, offset, HEADER_LENGTH - offset);
|
||||
if(read == -1) throw new EOFException();
|
||||
offset += read;
|
||||
}
|
||||
// Decrypt and authenticate the header
|
||||
FrameEncoder.encodeIv(iv, frameNumber, true);
|
||||
FrameEncoder.encodeIv(aad, frameNumber, true);
|
||||
try {
|
||||
frameCipher.init(false, frameKey, iv, aad);
|
||||
int decrypted = frameCipher.process(ciphertext, 0, HEADER_LENGTH,
|
||||
header, 0);
|
||||
if(decrypted != HEADER_LENGTH - MAC_LENGTH)
|
||||
throw new RuntimeException();
|
||||
} catch(GeneralSecurityException e) {
|
||||
throw new FormatException();
|
||||
}
|
||||
// Decode and validate the header
|
||||
finalFrame = FrameEncoder.isFinalFrame(header);
|
||||
int payloadLength = FrameEncoder.getPayloadLength(header);
|
||||
int paddingLength = FrameEncoder.getPaddingLength(header);
|
||||
if(payloadLength + paddingLength > MAX_PAYLOAD_LENGTH)
|
||||
throw new FormatException();
|
||||
// Read the payload and padding
|
||||
int frameLength = HEADER_LENGTH + payloadLength + paddingLength
|
||||
+ MAC_LENGTH;
|
||||
while(offset < frameLength) {
|
||||
int read = in.read(ciphertext, offset, frameLength - offset);
|
||||
if(read == -1) throw new EOFException();
|
||||
offset += read;
|
||||
}
|
||||
// Decrypt and authenticate the payload and padding
|
||||
FrameEncoder.encodeIv(iv, frameNumber, false);
|
||||
FrameEncoder.encodeIv(aad, frameNumber, false);
|
||||
try {
|
||||
frameCipher.init(false, frameKey, iv, aad);
|
||||
int decrypted = frameCipher.process(ciphertext, HEADER_LENGTH,
|
||||
payloadLength + paddingLength + MAC_LENGTH, payload, 0);
|
||||
if(decrypted != payloadLength + paddingLength)
|
||||
throw new RuntimeException();
|
||||
} catch(GeneralSecurityException e) {
|
||||
throw new FormatException();
|
||||
}
|
||||
// If there's any padding it must be all zeroes
|
||||
for(int i = 0; i < paddingLength; i++)
|
||||
if(payload[payloadLength + i] != 0) throw new FormatException();
|
||||
frameNumber++;
|
||||
return payloadLength;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.briarproject.crypto;
|
||||
|
||||
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.crypto.SecretKey;
|
||||
import org.briarproject.api.crypto.StreamEncrypter;
|
||||
import org.briarproject.api.crypto.StreamEncrypterFactory;
|
||||
import org.briarproject.api.transport.StreamContext;
|
||||
|
||||
class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
|
||||
@Inject
|
||||
StreamEncrypterFactoryImpl(CryptoComponent crypto) {
|
||||
this.crypto = crypto;
|
||||
}
|
||||
|
||||
public StreamEncrypter createStreamEncrypter(OutputStream out,
|
||||
StreamContext ctx) {
|
||||
byte[] secret = ctx.getSecret();
|
||||
long streamNumber = ctx.getStreamNumber();
|
||||
boolean alice = ctx.getAlice();
|
||||
// Encode the tag
|
||||
byte[] tag = new byte[TAG_LENGTH];
|
||||
SecretKey tagKey = crypto.deriveTagKey(secret, alice);
|
||||
crypto.encodeTag(tag, tagKey, streamNumber);
|
||||
// Derive the frame key
|
||||
SecretKey frameKey = crypto.deriveFrameKey(secret, streamNumber, alice);
|
||||
// Create the encrypter
|
||||
return new StreamEncrypterImpl(out, crypto.getFrameCipher(), frameKey,
|
||||
tag);
|
||||
}
|
||||
|
||||
public StreamEncrypter createInvitationStreamEncrypter(OutputStream out,
|
||||
byte[] secret, boolean alice) {
|
||||
// Derive the frame key
|
||||
SecretKey frameKey = crypto.deriveFrameKey(secret, 0, alice);
|
||||
// Create the encrypter
|
||||
return new StreamEncrypterImpl(out, crypto.getFrameCipher(), frameKey,
|
||||
null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package org.briarproject.crypto;
|
||||
|
||||
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.IV_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH;
|
||||
import static org.briarproject.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
|
||||
import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import org.briarproject.api.crypto.AuthenticatedCipher;
|
||||
import org.briarproject.api.crypto.SecretKey;
|
||||
import org.briarproject.api.crypto.StreamEncrypter;
|
||||
|
||||
class StreamEncrypterImpl implements StreamEncrypter {
|
||||
|
||||
private final OutputStream out;
|
||||
private final AuthenticatedCipher frameCipher;
|
||||
private final SecretKey frameKey;
|
||||
private final byte[] tag, iv, aad, plaintext, ciphertext;
|
||||
|
||||
private long frameNumber;
|
||||
private boolean writeTag;
|
||||
|
||||
StreamEncrypterImpl(OutputStream out, AuthenticatedCipher frameCipher,
|
||||
SecretKey frameKey, byte[] tag) {
|
||||
this.out = out;
|
||||
this.frameCipher = frameCipher;
|
||||
this.frameKey = frameKey;
|
||||
this.tag = tag;
|
||||
iv = new byte[IV_LENGTH];
|
||||
aad = new byte[IV_LENGTH];
|
||||
plaintext = new byte[HEADER_LENGTH + MAX_PAYLOAD_LENGTH];
|
||||
ciphertext = new byte[MAX_FRAME_LENGTH];
|
||||
frameNumber = 0;
|
||||
writeTag = (tag != null);
|
||||
}
|
||||
|
||||
public void writeFrame(byte[] payload, int payloadLength,
|
||||
int paddingLength, boolean finalFrame) throws IOException {
|
||||
if(payloadLength + paddingLength > MAX_PAYLOAD_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
// Don't allow the frame counter to wrap
|
||||
if(frameNumber > MAX_32_BIT_UNSIGNED) throw new IOException();
|
||||
// Write the tag if required
|
||||
if(writeTag) {
|
||||
out.write(tag, 0, tag.length);
|
||||
writeTag = false;
|
||||
}
|
||||
// Encode the header
|
||||
FrameEncoder.encodeHeader(plaintext, finalFrame, payloadLength,
|
||||
paddingLength);
|
||||
// Encrypt and authenticate the header
|
||||
FrameEncoder.encodeIv(iv, frameNumber, true);
|
||||
FrameEncoder.encodeIv(aad, frameNumber, true);
|
||||
try {
|
||||
frameCipher.init(true, frameKey, iv, aad);
|
||||
int encrypted = frameCipher.process(plaintext, 0,
|
||||
HEADER_LENGTH - MAC_LENGTH, ciphertext, 0);
|
||||
if(encrypted != HEADER_LENGTH) throw new RuntimeException();
|
||||
} catch(GeneralSecurityException badCipher) {
|
||||
throw new RuntimeException(badCipher);
|
||||
}
|
||||
// Combine the payload and padding
|
||||
System.arraycopy(payload, 0, plaintext, HEADER_LENGTH, payloadLength);
|
||||
for(int i = 0; i < paddingLength; i++)
|
||||
plaintext[HEADER_LENGTH + payloadLength + i] = 0;
|
||||
// Encrypt and authenticate the payload and padding
|
||||
FrameEncoder.encodeIv(iv, frameNumber, false);
|
||||
FrameEncoder.encodeIv(aad, frameNumber, false);
|
||||
try {
|
||||
frameCipher.init(true, frameKey, iv, aad);
|
||||
int encrypted = frameCipher.process(plaintext, HEADER_LENGTH,
|
||||
payloadLength + paddingLength, ciphertext, HEADER_LENGTH);
|
||||
if(encrypted != payloadLength + paddingLength + MAC_LENGTH)
|
||||
throw new RuntimeException();
|
||||
} catch(GeneralSecurityException badCipher) {
|
||||
throw new RuntimeException(badCipher);
|
||||
}
|
||||
// Write the frame
|
||||
out.write(ciphertext, 0, HEADER_LENGTH + payloadLength + paddingLength
|
||||
+ MAC_LENGTH);
|
||||
frameNumber++;
|
||||
}
|
||||
|
||||
public void flush() throws IOException {
|
||||
// Write the tag if required
|
||||
if(writeTag) {
|
||||
out.write(tag, 0, tag.length);
|
||||
writeTag = false;
|
||||
}
|
||||
out.flush();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user