diff --git a/briar-api/src/org/briarproject/api/transport/TransportConstants.java b/briar-api/src/org/briarproject/api/transport/TransportConstants.java index ae3a6c3daf9af55555e07023020a8a3ac0932629..5f126c7a40db6aa23144ca4591ee453ec60acbba 100644 --- a/briar-api/src/org/briarproject/api/transport/TransportConstants.java +++ b/briar-api/src/org/briarproject/api/transport/TransportConstants.java @@ -8,23 +8,29 @@ public interface TransportConstants { /** The length of the pseudo-random tag in bytes. */ int TAG_LENGTH = 16; - /** The length of the stream header IV in bytes. */ - int STREAM_HEADER_IV_LENGTH = 24; - /** The length of the message authentication code (MAC) in bytes. */ int MAC_LENGTH = 16; + /** The length of the stream header initialisation vector (IV) in bytes. */ + int STREAM_HEADER_IV_LENGTH = 24; + /** The length of the stream header in bytes. */ int STREAM_HEADER_LENGTH = STREAM_HEADER_IV_LENGTH + SecretKey.LENGTH + MAC_LENGTH; - /** The length of the frame initalisation vector (IV) in bytes. */ - int FRAME_IV_LENGTH = 24; + /** The length of the frame nonce in bytes. */ + int FRAME_NONCE_LENGTH = 24; - /** The length of the frame header in bytes. */ - int FRAME_HEADER_LENGTH = 4 + MAC_LENGTH; + /** The length of the plaintext frame header in bytes. */ + int FRAME_HEADER_PLAINTEXT_LENGTH = 4; - /** The maximum length of a frame in bytes, including the header and MAC. */ + /** The length of the encrypted and authenticated frame header in bytes. */ + int FRAME_HEADER_LENGTH = FRAME_HEADER_PLAINTEXT_LENGTH + MAC_LENGTH; + + /** + * The maximum length of an encrypted and authenticated frame in bytes, + * including the header. + */ int MAX_FRAME_LENGTH = 1024; /** The maximum total length of the frame payload and padding in bytes. */ diff --git a/briar-core/src/org/briarproject/crypto/CryptoComponentImpl.java b/briar-core/src/org/briarproject/crypto/CryptoComponentImpl.java index 97350169935db0e25b560f5ea9c79c27ce7bcef9..fc9b4bdb8a1df2434b37f4cb64a943705cfc5ba8 100644 --- a/briar-core/src/org/briarproject/crypto/CryptoComponentImpl.java +++ b/briar-core/src/org/briarproject/crypto/CryptoComponentImpl.java @@ -17,12 +17,10 @@ import org.briarproject.api.transport.TransportKeys; 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; import org.spongycastle.crypto.Digest; import org.spongycastle.crypto.agreement.ECDHCBasicAgreement; import org.spongycastle.crypto.digests.SHA256Digest; -import org.spongycastle.crypto.engines.AESLightEngine; import org.spongycastle.crypto.generators.ECKeyPairGenerator; import org.spongycastle.crypto.generators.PKCS5S2ParametersGenerator; import org.spongycastle.crypto.params.ECKeyGenerationParameters; @@ -30,6 +28,7 @@ import org.spongycastle.crypto.params.ECPrivateKeyParameters; import org.spongycastle.crypto.params.ECPublicKeyParameters; import org.spongycastle.crypto.params.KeyParameter; +import java.nio.charset.Charset; import java.security.GeneralSecurityException; import java.security.SecureRandom; import java.util.ArrayList; @@ -43,6 +42,8 @@ import static java.util.logging.Level.INFO; import static org.briarproject.api.invitation.InvitationConstants.CODE_BITS; import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH; import static org.briarproject.crypto.EllipticCurveConstants.PARAMETERS; +import static org.briarproject.util.ByteUtils.INT_32_BYTES; +import static org.briarproject.util.ByteUtils.INT_64_BYTES; import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED; class CryptoComponentImpl implements CryptoComponent { @@ -57,33 +58,31 @@ class CryptoComponentImpl implements CryptoComponent { private static final int PBKDF_TARGET_MILLIS = 500; private static final int PBKDF_SAMPLES = 30; + private static byte[] ascii(String s) { + return s.getBytes(Charset.forName("US-ASCII")); + } + // KDF label for master key derivation - private static final byte[] MASTER = { 'M', 'A', 'S', 'T', 'E', 'R' }; + private static final byte[] MASTER = ascii("MASTER"); // KDF labels for confirmation code derivation - private static final byte[] A_CONFIRM = - { 'A', '_', 'C', 'O', 'N', 'F', 'I', 'R', 'M' }; - private static final byte[] B_CONFIRM = - { 'B', '_', 'C', 'O', 'N', 'F', 'I', 'R', 'M' }; + private static final byte[] A_CONFIRM = ascii("ALICE_CONFIRMATION_CODE"); + private static final byte[] B_CONFIRM = ascii("BOB_CONFIRMATION_CODE"); // KDF labels for invitation stream header key derivation - private static final byte[] A_INVITE = - { 'A', '_', 'I', 'N', 'V', 'I', 'T', 'E' }; - private static final byte[] B_INVITE = - { 'B', '_', 'I', 'N', 'V', 'I', 'T', 'E' }; + private static final byte[] A_INVITE = ascii("ALICE_INVITATION_KEY"); + private static final byte[] B_INVITE = ascii("BOB_INVITATION_KEY"); // KDF labels for signature nonce derivation - private static final byte[] A_NONCE = { 'A', '_', 'N', 'O', 'N', 'C', 'E' }; - private static final byte[] B_NONCE = { 'B', '_', 'N', 'O', 'N', 'C', 'E' }; + private static final byte[] A_NONCE = ascii("ALICE_SIGNATURE_NONCE"); + private static final byte[] B_NONCE = ascii("BOB_SIGNATURE_NONCE"); // KDF label for group salt derivation - private static final byte[] SALT = { 'S', 'A', 'L', 'T' }; + private static final byte[] SALT = ascii("SALT"); // KDF labels for tag key derivation - private static final byte[] A_TAG = { 'A', '_', 'T', 'A', 'G' }; - private static final byte[] B_TAG = { 'B', '_', 'T', 'A', 'G' }; + private static final byte[] A_TAG = ascii("ALICE_TAG_KEY"); + private static final byte[] B_TAG = ascii("BOB_TAG_KEY"); // KDF labels for header key derivation - private static final byte[] A_HEADER = - { 'A', '_', 'H', 'E', 'A', 'D', 'E', 'R' }; - private static final byte[] B_HEADER = - { 'B', '_', 'H', 'E', 'A', 'D', 'E', 'R' }; + private static final byte[] A_HEADER = ascii("ALICE_HEADER_KEY"); + private static final byte[] B_HEADER = ascii("BOB_HEADER_KEY"); // KDF label for key rotation - private static final byte[] ROTATE = { 'R', 'O', 'T', 'A', 'T', 'E' }; + private static final byte[] ROTATE = ascii("ROTATE"); private final SecureRandom secureRandom; private final ECKeyPairGenerator agreementKeyPairGenerator; @@ -290,8 +289,8 @@ class CryptoComponentImpl implements CryptoComponent { } private SecretKey rotateKey(SecretKey k, long rotationPeriod) { - byte[] period = new byte[4]; - ByteUtils.writeUint32(rotationPeriod, period, 0); + byte[] period = new byte[INT_64_BYTES]; + ByteUtils.writeUint64(rotationPeriod, period, 0); return new SecretKey(macKdf(k, ROTATE, period)); } @@ -311,14 +310,19 @@ class CryptoComponentImpl implements CryptoComponent { if (tag.length < TAG_LENGTH) throw new IllegalArgumentException(); if (streamNumber < 0 || streamNumber > MAX_32_BIT_UNSIGNED) throw new IllegalArgumentException(); - for (int i = 0; i < TAG_LENGTH; i++) tag[i] = 0; - ByteUtils.writeUint32(streamNumber, tag, 0); - BlockCipher cipher = new AESLightEngine(); - if (cipher.getBlockSize() != TAG_LENGTH) - throw new IllegalStateException(); - KeyParameter k = new KeyParameter(tagKey.getBytes()); - cipher.init(true, k); - cipher.processBlock(tag, 0, tag, 0); + // Initialise the PRF + Digest prf = new Blake2sDigest(tagKey.getBytes()); + // The output of the PRF must be long enough to use as a tag + int macLength = prf.getDigestSize(); + if (macLength < TAG_LENGTH) throw new IllegalStateException(); + // The input is the stream number as a 64-bit integer + byte[] input = new byte[INT_64_BYTES]; + ByteUtils.writeUint64(streamNumber, input, 0); + prf.update(input, 0, input.length); + byte[] mac = new byte[macLength]; + prf.doFinal(mac, 0); + // The output is the first TAG_LENGTH bytes of the MAC + System.arraycopy(mac, 0, tag, 0, TAG_LENGTH); } public byte[] encryptWithPassword(byte[] input, String password) { @@ -335,15 +339,16 @@ class CryptoComponentImpl implements CryptoComponent { 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 + macBytes; + int outputLen = salt.length + INT_32_BYTES + iv.length + input.length + + macBytes; 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); + System.arraycopy(iv, 0, output, salt.length + INT_32_BYTES, iv.length); // Initialise the cipher and encrypt the plaintext try { cipher.init(true, key, iv); - int outputOff = salt.length + 4 + iv.length; + int outputOff = salt.length + INT_32_BYTES + iv.length; cipher.process(input, 0, input.length, output, outputOff); return output; } catch (GeneralSecurityException e) { @@ -355,7 +360,8 @@ class CryptoComponentImpl implements CryptoComponent { AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher(); int macBytes = cipher.getMacBytes(); // The input contains the salt, iterations, IV, ciphertext and MAC - if (input.length < PBKDF_SALT_BYTES + 4 + STORAGE_IV_BYTES + macBytes) + if (input.length < PBKDF_SALT_BYTES + INT_32_BYTES + STORAGE_IV_BYTES + + macBytes) return null; // Invalid input byte[] salt = new byte[PBKDF_SALT_BYTES]; System.arraycopy(input, 0, salt, 0, salt.length); @@ -363,7 +369,7 @@ class CryptoComponentImpl implements CryptoComponent { if (iterations < 0 || iterations > Integer.MAX_VALUE) return null; // Invalid iteration count byte[] iv = new byte[STORAGE_IV_BYTES]; - System.arraycopy(input, salt.length + 4, iv, 0, iv.length); + System.arraycopy(input, salt.length + INT_32_BYTES, iv, 0, iv.length); // Derive the key from the password SecretKey key = new SecretKey(pbkdf2(password, salt, (int) iterations)); // Initialise the cipher @@ -374,7 +380,7 @@ class CryptoComponentImpl implements CryptoComponent { } // Try to decrypt the ciphertext (may be invalid) try { - int inputOff = salt.length + 4 + iv.length; + int inputOff = salt.length + INT_32_BYTES + iv.length; int inputLen = input.length - inputOff; byte[] output = new byte[inputLen - macBytes]; cipher.process(input, inputOff, inputLen, output, 0); @@ -392,7 +398,7 @@ class CryptoComponentImpl implements CryptoComponent { int hashLength = digest.getDigestSize(); if (hashLength < SecretKey.LENGTH) throw new IllegalStateException(); // Calculate the hash over the concatenated length-prefixed inputs - byte[] length = new byte[4]; + byte[] length = new byte[INT_32_BYTES]; for (byte[] input : inputs) { ByteUtils.writeUint32(input.length, length, 0); digest.update(length, 0, length.length); @@ -416,7 +422,7 @@ class CryptoComponentImpl implements CryptoComponent { int macLength = prf.getDigestSize(); if (macLength < SecretKey.LENGTH) throw new IllegalStateException(); // Calculate the PRF over the concatenated length-prefixed inputs - byte[] length = new byte[4]; + byte[] length = new byte[INT_32_BYTES]; for (byte[] input : inputs) { ByteUtils.writeUint32(input.length, length, 0); prf.update(length, 0, length.length); diff --git a/briar-core/src/org/briarproject/crypto/FrameEncoder.java b/briar-core/src/org/briarproject/crypto/FrameEncoder.java index efd03ed49bad9c530eb7851571f78fae0f5509a0..68219c7a66ca941c44871ef0832eb3a9b5e10a5b 100644 --- a/briar-core/src/org/briarproject/crypto/FrameEncoder.java +++ b/briar-core/src/org/briarproject/crypto/FrameEncoder.java @@ -2,51 +2,51 @@ package org.briarproject.crypto; import org.briarproject.util.ByteUtils; -import static org.briarproject.api.transport.TransportConstants.FRAME_HEADER_LENGTH; -import static org.briarproject.api.transport.TransportConstants.FRAME_IV_LENGTH; +import static org.briarproject.api.transport.TransportConstants.FRAME_HEADER_PLAINTEXT_LENGTH; +import static org.briarproject.api.transport.TransportConstants.FRAME_NONCE_LENGTH; import static org.briarproject.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH; -import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED; +import static org.briarproject.util.ByteUtils.INT_16_BYTES; +import static org.briarproject.util.ByteUtils.INT_64_BYTES; class FrameEncoder { - static void encodeIv(byte[] iv, long frameNumber, boolean header) { - if (iv.length < FRAME_IV_LENGTH) throw new IllegalArgumentException(); - if (frameNumber < 0 || frameNumber > MAX_32_BIT_UNSIGNED) + static void encodeNonce(byte[] dest, long frameNumber, boolean header) { + if (dest.length < FRAME_NONCE_LENGTH) throw new IllegalArgumentException(); - ByteUtils.writeUint32(frameNumber, iv, 0); - if (header) iv[4] = 1; - else iv[4] = 0; - for (int i = 5; i < FRAME_IV_LENGTH; i++) iv[i] = 0; + if (frameNumber < 0) throw new IllegalArgumentException(); + ByteUtils.writeUint64(frameNumber, dest, 0); + if (header) dest[0] |= 0x80; + for (int i = INT_64_BYTES; i < FRAME_NONCE_LENGTH; i++) dest[i] = 0; } - static void encodeHeader(byte[] header, boolean finalFrame, + static void encodeHeader(byte[] dest, boolean finalFrame, int payloadLength, int paddingLength) { - if (header.length < FRAME_HEADER_LENGTH) + if (dest.length < FRAME_HEADER_PLAINTEXT_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; + ByteUtils.writeUint16(payloadLength, dest, 0); + ByteUtils.writeUint16(paddingLength, dest, INT_16_BYTES); + if (finalFrame) dest[0] |= 0x80; } static boolean isFinalFrame(byte[] header) { - if (header.length < FRAME_HEADER_LENGTH) + if (header.length < FRAME_HEADER_PLAINTEXT_LENGTH) throw new IllegalArgumentException(); return (header[0] & 0x80) == 0x80; } static int getPayloadLength(byte[] header) { - if (header.length < FRAME_HEADER_LENGTH) + if (header.length < FRAME_HEADER_PLAINTEXT_LENGTH) throw new IllegalArgumentException(); return ByteUtils.readUint16(header, 0) & 0x7FFF; } static int getPaddingLength(byte[] header) { - if (header.length < FRAME_HEADER_LENGTH) + if (header.length < FRAME_HEADER_PLAINTEXT_LENGTH) throw new IllegalArgumentException(); - return ByteUtils.readUint16(header, 2); + return ByteUtils.readUint16(header, INT_16_BYTES); } } diff --git a/briar-core/src/org/briarproject/crypto/PseudoRandomImpl.java b/briar-core/src/org/briarproject/crypto/PseudoRandomImpl.java index f89475e48227c7aa1957bbd23a6c8189cb249ac0..2786b2994fc7e8a1e52cb333bf4c12fb620fee31 100644 --- a/briar-core/src/org/briarproject/crypto/PseudoRandomImpl.java +++ b/briar-core/src/org/briarproject/crypto/PseudoRandomImpl.java @@ -3,14 +3,16 @@ package org.briarproject.crypto; import org.briarproject.api.crypto.PseudoRandom; import org.briarproject.util.ByteUtils; +import static org.briarproject.util.ByteUtils.INT_32_BYTES; + class PseudoRandomImpl implements PseudoRandom { private final FortunaGenerator generator; PseudoRandomImpl(int seed1, int seed2) { - byte[] seed = new byte[8]; + byte[] seed = new byte[INT_32_BYTES * 2]; ByteUtils.writeUint32(seed1, seed, 0); - ByteUtils.writeUint32(seed2, seed, 4); + ByteUtils.writeUint32(seed2, seed, INT_32_BYTES); generator = new FortunaGenerator(seed); } diff --git a/briar-core/src/org/briarproject/crypto/StreamDecrypterImpl.java b/briar-core/src/org/briarproject/crypto/StreamDecrypterImpl.java index 89e18bee6119fea1b02809d6f582a9f2696d93e5..e7a1648de7b89b704a8cdc92fe3524b777a59203 100644 --- a/briar-core/src/org/briarproject/crypto/StreamDecrypterImpl.java +++ b/briar-core/src/org/briarproject/crypto/StreamDecrypterImpl.java @@ -10,7 +10,8 @@ import java.io.InputStream; import java.security.GeneralSecurityException; import static org.briarproject.api.transport.TransportConstants.FRAME_HEADER_LENGTH; -import static org.briarproject.api.transport.TransportConstants.FRAME_IV_LENGTH; +import static org.briarproject.api.transport.TransportConstants.FRAME_HEADER_PLAINTEXT_LENGTH; +import static org.briarproject.api.transport.TransportConstants.FRAME_NONCE_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; @@ -22,7 +23,7 @@ class StreamDecrypterImpl implements StreamDecrypter { private final InputStream in; private final AuthenticatedCipher cipher; private final SecretKey streamHeaderKey; - private final byte[] frameIv, frameHeader, frameCiphertext; + private final byte[] frameNonce, frameHeader, frameCiphertext; private SecretKey frameKey; private long frameNumber; @@ -33,8 +34,8 @@ class StreamDecrypterImpl implements StreamDecrypter { this.in = in; this.cipher = cipher; this.streamHeaderKey = streamHeaderKey; - frameIv = new byte[FRAME_IV_LENGTH]; - frameHeader = new byte[FRAME_HEADER_LENGTH]; + frameNonce = new byte[FRAME_NONCE_LENGTH]; + frameHeader = new byte[FRAME_HEADER_PLAINTEXT_LENGTH]; frameCiphertext = new byte[MAX_FRAME_LENGTH]; frameKey = null; frameNumber = 0; @@ -46,6 +47,8 @@ class StreamDecrypterImpl implements StreamDecrypter { if (payload.length < MAX_PAYLOAD_LENGTH) throw new IllegalArgumentException(); if (finalFrame) return -1; + // Don't allow the frame counter to wrap + if (frameNumber < 0) throw new IOException(); // Read the stream header if required if (frameKey == null) readStreamHeader(); // Read the frame header @@ -57,12 +60,12 @@ class StreamDecrypterImpl implements StreamDecrypter { offset += read; } // Decrypt and authenticate the frame header - FrameEncoder.encodeIv(frameIv, frameNumber, true); + FrameEncoder.encodeNonce(frameNonce, frameNumber, true); try { - cipher.init(false, frameKey, frameIv); + cipher.init(false, frameKey, frameNonce); int decrypted = cipher.process(frameCiphertext, 0, FRAME_HEADER_LENGTH, frameHeader, 0); - if (decrypted != FRAME_HEADER_LENGTH - MAC_LENGTH) + if (decrypted != FRAME_HEADER_PLAINTEXT_LENGTH) throw new RuntimeException(); } catch (GeneralSecurityException e) { throw new FormatException(); @@ -82,9 +85,9 @@ class StreamDecrypterImpl implements StreamDecrypter { offset += read; } // Decrypt and authenticate the payload and padding - FrameEncoder.encodeIv(frameIv, frameNumber, false); + FrameEncoder.encodeNonce(frameNonce, frameNumber, false); try { - cipher.init(false, frameKey, frameIv); + cipher.init(false, frameKey, frameNonce); int decrypted = cipher.process(frameCiphertext, FRAME_HEADER_LENGTH, payloadLength + paddingLength + MAC_LENGTH, payload, 0); if (decrypted != payloadLength + paddingLength) diff --git a/briar-core/src/org/briarproject/crypto/StreamEncrypterImpl.java b/briar-core/src/org/briarproject/crypto/StreamEncrypterImpl.java index 988be7e0293c9b91e0833af20fdff5543bbebdd8..6b19fa4cbafe13019ade615438a96bdf598474bc 100644 --- a/briar-core/src/org/briarproject/crypto/StreamEncrypterImpl.java +++ b/briar-core/src/org/briarproject/crypto/StreamEncrypterImpl.java @@ -8,13 +8,13 @@ import java.io.OutputStream; import java.security.GeneralSecurityException; import static org.briarproject.api.transport.TransportConstants.FRAME_HEADER_LENGTH; -import static org.briarproject.api.transport.TransportConstants.FRAME_IV_LENGTH; +import static org.briarproject.api.transport.TransportConstants.FRAME_HEADER_PLAINTEXT_LENGTH; +import static org.briarproject.api.transport.TransportConstants.FRAME_NONCE_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.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH; import static org.briarproject.api.transport.TransportConstants.STREAM_HEADER_LENGTH; -import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED; class StreamEncrypterImpl implements StreamEncrypter { @@ -22,7 +22,7 @@ class StreamEncrypterImpl implements StreamEncrypter { private final AuthenticatedCipher cipher; private final SecretKey streamHeaderKey, frameKey; private final byte[] tag, streamHeaderIv; - private final byte[] frameIv, framePlaintext, frameCiphertext; + private final byte[] frameNonce, frameHeader, framePlaintext, frameCiphertext; private long frameNumber; private boolean writeTag, writeStreamHeader; @@ -36,8 +36,9 @@ class StreamEncrypterImpl implements StreamEncrypter { this.streamHeaderIv = streamHeaderIv; this.streamHeaderKey = streamHeaderKey; this.frameKey = frameKey; - frameIv = new byte[FRAME_IV_LENGTH]; - framePlaintext = new byte[FRAME_HEADER_LENGTH + MAX_PAYLOAD_LENGTH]; + frameNonce = new byte[FRAME_NONCE_LENGTH]; + frameHeader = new byte[FRAME_HEADER_PLAINTEXT_LENGTH]; + framePlaintext = new byte[MAX_PAYLOAD_LENGTH]; frameCiphertext = new byte[MAX_FRAME_LENGTH]; frameNumber = 0; writeTag = (tag != null); @@ -49,34 +50,33 @@ class StreamEncrypterImpl implements StreamEncrypter { 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(); + if (frameNumber < 0) throw new IOException(); // Write the tag if required if (writeTag) writeTag(); // Write the stream header if required if (writeStreamHeader) writeStreamHeader(); // Encode the frame header - FrameEncoder.encodeHeader(framePlaintext, finalFrame, payloadLength, + FrameEncoder.encodeHeader(frameHeader, finalFrame, payloadLength, paddingLength); // Encrypt and authenticate the frame header - FrameEncoder.encodeIv(frameIv, frameNumber, true); + FrameEncoder.encodeNonce(frameNonce, frameNumber, true); try { - cipher.init(true, frameKey, frameIv); - int encrypted = cipher.process(framePlaintext, 0, - FRAME_HEADER_LENGTH - MAC_LENGTH, frameCiphertext, 0); + cipher.init(true, frameKey, frameNonce); + int encrypted = cipher.process(frameHeader, 0, + FRAME_HEADER_PLAINTEXT_LENGTH, frameCiphertext, 0); if (encrypted != FRAME_HEADER_LENGTH) throw new RuntimeException(); } catch (GeneralSecurityException badCipher) { throw new RuntimeException(badCipher); } // Combine the payload and padding - System.arraycopy(payload, 0, framePlaintext, FRAME_HEADER_LENGTH, - payloadLength); + System.arraycopy(payload, 0, framePlaintext, 0, payloadLength); for (int i = 0; i < paddingLength; i++) - framePlaintext[FRAME_HEADER_LENGTH + payloadLength + i] = 0; + framePlaintext[payloadLength + i] = 0; // Encrypt and authenticate the payload and padding - FrameEncoder.encodeIv(frameIv, frameNumber, false); + FrameEncoder.encodeNonce(frameNonce, frameNumber, false); try { - cipher.init(true, frameKey, frameIv); - int encrypted = cipher.process(framePlaintext, FRAME_HEADER_LENGTH, + cipher.init(true, frameKey, frameNonce); + int encrypted = cipher.process(framePlaintext, 0, payloadLength + paddingLength, frameCiphertext, FRAME_HEADER_LENGTH); if (encrypted != payloadLength + paddingLength + MAC_LENGTH) diff --git a/briar-core/src/org/briarproject/util/ByteUtils.java b/briar-core/src/org/briarproject/util/ByteUtils.java index 777d8231899011ac506db8052e22617a7d4dfcae..9213e65d8931072c3a30af52935bf1f330c2f1fa 100644 --- a/briar-core/src/org/briarproject/util/ByteUtils.java +++ b/briar-core/src/org/briarproject/util/ByteUtils.java @@ -12,50 +12,72 @@ public class ByteUtils { */ public static final long MAX_32_BIT_UNSIGNED = 4294967295L; // 2^32 - 1 - public static void writeUint8(int i, byte[] b, int offset) { - if (i < 0) throw new IllegalArgumentException(); - if (i > 255) throw new IllegalArgumentException(); - if (b.length < offset) throw new IllegalArgumentException(); - b[offset] = (byte) i; + /** The number of bytes needed to encode a 16-bit integer. */ + public static final int INT_16_BYTES = 2; + + /** The number of bytes needed to encode a 32-bit integer. */ + public static final int INT_32_BYTES = 4; + + /** The number of bytes needed to encode a 64-bit integer. */ + public static final int INT_64_BYTES = 8; + + public static void writeUint16(int src, byte[] dest, int offset) { + if (src < 0) throw new IllegalArgumentException(); + if (src > MAX_16_BIT_UNSIGNED) throw new IllegalArgumentException(); + if (dest.length < offset + INT_16_BYTES) + throw new IllegalArgumentException(); + dest[offset] = (byte) (src >> 8); + dest[offset + 1] = (byte) (src & 0xFF); } - public static void writeUint16(int i, byte[] b, int offset) { - if (i < 0) throw new IllegalArgumentException(); - if (i > MAX_16_BIT_UNSIGNED) throw new IllegalArgumentException(); - if (b.length < offset + 2) throw new IllegalArgumentException(); - b[offset] = (byte) (i >> 8); - b[offset + 1] = (byte) (i & 0xFF); + public static void writeUint32(long src, byte[] dest, int offset) { + if (src < 0) throw new IllegalArgumentException(); + if (src > MAX_32_BIT_UNSIGNED) throw new IllegalArgumentException(); + if (dest.length < offset + INT_32_BYTES) + throw new IllegalArgumentException(); + dest[offset] = (byte) (src >> 24); + dest[offset + 1] = (byte) (src >> 16 & 0xFF); + dest[offset + 2] = (byte) (src >> 8 & 0xFF); + dest[offset + 3] = (byte) (src & 0xFF); } - public static void writeUint32(long i, byte[] b, int offset) { - if (i < 0) throw new IllegalArgumentException(); - if (i > MAX_32_BIT_UNSIGNED) throw new IllegalArgumentException(); - if (b.length < offset + 4) throw new IllegalArgumentException(); - b[offset] = (byte) (i >> 24); - b[offset + 1] = (byte) (i >> 16 & 0xFF); - b[offset + 2] = (byte) (i >> 8 & 0xFF); - b[offset + 3] = (byte) (i & 0xFF); + public static void writeUint64(long src, byte[] dest, int offset) { + if (src < 0) throw new IllegalArgumentException(); + if (dest.length < offset + INT_64_BYTES) + throw new IllegalArgumentException(); + dest[offset] = (byte) (src >> 56); + dest[offset + 1] = (byte) (src >> 48 & 0xFF); + dest[offset + 2] = (byte) (src >> 40 & 0xFF); + dest[offset + 3] = (byte) (src >> 32 & 0xFF); + dest[offset + 4] = (byte) (src >> 24 & 0xFF); + dest[offset + 5] = (byte) (src >> 16 & 0xFF); + dest[offset + 6] = (byte) (src >> 8 & 0xFF); + dest[offset + 7] = (byte) (src & 0xFF); } - public static int readUint16(byte[] b, int offset) { - if (b.length < offset + 2) throw new IllegalArgumentException(); - return ((b[offset] & 0xFF) << 8) | (b[offset + 1] & 0xFF); + public static int readUint16(byte[] src, int offset) { + if (src.length < offset + INT_16_BYTES) + throw new IllegalArgumentException(); + return ((src[offset] & 0xFF) << 8) | (src[offset + 1] & 0xFF); } - public static long readUint32(byte[] b, int offset) { - if (b.length < offset + 4) throw new IllegalArgumentException(); - return ((b[offset] & 0xFFL) << 24) | ((b[offset + 1] & 0xFFL) << 16) - | ((b[offset + 2] & 0xFFL) << 8) | (b[offset + 3] & 0xFFL); + public static long readUint32(byte[] src, int offset) { + if (src.length < offset + INT_32_BYTES) + throw new IllegalArgumentException(); + return ((src[offset] & 0xFFL) << 24) + | ((src[offset + 1] & 0xFFL) << 16) + | ((src[offset + 2] & 0xFFL) << 8) + | (src[offset + 3] & 0xFFL); } - public static int readUint(byte[] b, int bits) { - if (b.length << 3 < bits) throw new IllegalArgumentException(); - int result = 0; + public static int readUint(byte[] src, int bits) { + if (src.length << 3 < bits) throw new IllegalArgumentException(); + int dest = 0; for (int i = 0; i < bits; i++) { - if ((b[i >> 3] & 128 >> (i & 7)) != 0) result |= 1 << bits - i - 1; + if ((src[i >> 3] & 128 >> (i & 7)) != 0) dest |= 1 << bits - i - 1; } - assert result >= 0; - assert result < 1 << bits; - return result; + assert dest >= 0; + assert dest < 1 << bits; + return dest; } } diff --git a/briar-tests/src/org/briarproject/crypto/StreamDecrypterImplTest.java b/briar-tests/src/org/briarproject/crypto/StreamDecrypterImplTest.java index 4ba24e7ea48f9036dd73982eedd9b332d3dabfbf..152ba5818f05afdea0b7b279fa02dbb1838f6696 100644 --- a/briar-tests/src/org/briarproject/crypto/StreamDecrypterImplTest.java +++ b/briar-tests/src/org/briarproject/crypto/StreamDecrypterImplTest.java @@ -16,6 +16,7 @@ import static org.briarproject.api.transport.TransportConstants.FRAME_HEADER_LEN import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH; import static org.briarproject.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH; import static org.briarproject.api.transport.TransportConstants.STREAM_HEADER_IV_LENGTH; +import static org.briarproject.util.ByteUtils.INT_16_BYTES; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.fail; @@ -119,7 +120,7 @@ public class StreamDecrypterImplTest extends BriarTestCase { // The payload length plus padding length is invalid int payloadLength = MAX_PAYLOAD_LENGTH - 1, paddingLength = 2; ByteUtils.writeUint16(payloadLength, frameHeader, 0); - ByteUtils.writeUint16(paddingLength, frameHeader, 2); + ByteUtils.writeUint16(paddingLength, frameHeader, INT_16_BYTES); byte[] payload = new byte[payloadLength]; random.nextBytes(payload); diff --git a/briar-tests/src/org/briarproject/transport/TestStreamDecrypter.java b/briar-tests/src/org/briarproject/transport/TestStreamDecrypter.java index f95e46740914903240e89f439acaa78869880a5b..985b000a35e9d71a544da2743002ef57d9894ce6 100644 --- a/briar-tests/src/org/briarproject/transport/TestStreamDecrypter.java +++ b/briar-tests/src/org/briarproject/transport/TestStreamDecrypter.java @@ -11,6 +11,7 @@ import static org.briarproject.api.transport.TransportConstants.FRAME_HEADER_LEN 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.STREAM_HEADER_LENGTH; +import static org.briarproject.util.ByteUtils.INT_16_BYTES; class TestStreamDecrypter implements StreamDecrypter { @@ -35,7 +36,7 @@ class TestStreamDecrypter implements StreamDecrypter { } finalFrame = (frame[0] & 0x80) == 0x80; int payloadLength = ByteUtils.readUint16(frame, 0) & 0x7FFF; - int paddingLength = ByteUtils.readUint16(frame, 2); + int paddingLength = ByteUtils.readUint16(frame, INT_16_BYTES); int frameLength = FRAME_HEADER_LENGTH + payloadLength + paddingLength + MAC_LENGTH; while (offset < frameLength) { diff --git a/briar-tests/src/org/briarproject/transport/TestStreamEncrypter.java b/briar-tests/src/org/briarproject/transport/TestStreamEncrypter.java index 2315acfa94ab20f5ba17caa6950eaf47fcf18ac2..5dbf9d3eeeb3ece17e53de8ee9ea5a592df36e94 100644 --- a/briar-tests/src/org/briarproject/transport/TestStreamEncrypter.java +++ b/briar-tests/src/org/briarproject/transport/TestStreamEncrypter.java @@ -9,6 +9,7 @@ import java.io.OutputStream; import static org.briarproject.api.transport.TransportConstants.FRAME_HEADER_LENGTH; import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH; import static org.briarproject.api.transport.TransportConstants.STREAM_HEADER_LENGTH; +import static org.briarproject.util.ByteUtils.INT_16_BYTES; class TestStreamEncrypter implements StreamEncrypter { @@ -27,7 +28,7 @@ class TestStreamEncrypter implements StreamEncrypter { if (writeTagAndHeader) writeTagAndHeader(); byte[] frameHeader = new byte[FRAME_HEADER_LENGTH]; ByteUtils.writeUint16(payloadLength, frameHeader, 0); - ByteUtils.writeUint16(paddingLength, frameHeader, 2); + ByteUtils.writeUint16(paddingLength, frameHeader, INT_16_BYTES); if (finalFrame) frameHeader[0] |= 0x80; out.write(frameHeader); out.write(payload, 0, payloadLength); diff --git a/briar-tests/src/org/briarproject/util/ByteUtilsTest.java b/briar-tests/src/org/briarproject/util/ByteUtilsTest.java index 0df6548bd6c60dff8304b0cacd1748c77a5a9a07..fb9952e392e094a12d2654b755d81cf7f814c0ff 100644 --- a/briar-tests/src/org/briarproject/util/ByteUtilsTest.java +++ b/briar-tests/src/org/briarproject/util/ByteUtilsTest.java @@ -3,7 +3,10 @@ package org.briarproject.util; import org.briarproject.BriarTestCase; import org.junit.Test; +import static org.briarproject.util.ByteUtils.MAX_16_BIT_UNSIGNED; +import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; public class ByteUtilsTest extends BriarTestCase { @@ -17,6 +20,22 @@ public class ByteUtilsTest extends BriarTestCase { assertEquals(65535, ByteUtils.readUint16(b, 1)); } + @Test + public void testReadUint16ValidatesArguments() { + try { + ByteUtils.readUint16(new byte[1], 0); + fail(); + } catch (IllegalArgumentException expected) { + // Expected + } + try { + ByteUtils.readUint16(new byte[2], 1); + fail(); + } catch (IllegalArgumentException expected) { + // Expected + } + } + @Test public void testReadUint32() { byte[] b = StringUtils.fromHexString("0000000000"); @@ -27,27 +46,135 @@ public class ByteUtilsTest extends BriarTestCase { assertEquals(4294967295L, ByteUtils.readUint32(b, 1)); } + @Test + public void testReadUint32ValidatesArguments() { + try { + ByteUtils.readUint32(new byte[3], 0); + fail(); + } catch (IllegalArgumentException expected) { + // Expected + } + try { + ByteUtils.readUint32(new byte[4], 1); + fail(); + } catch (IllegalArgumentException expected) { + // Expected + } + } @Test public void testWriteUint16() { - byte[] b = new byte[3]; + byte[] b = new byte[4]; ByteUtils.writeUint16(0, b, 1); - assertEquals("000000", StringUtils.toHexString(b)); + assertEquals("00000000", StringUtils.toHexString(b)); ByteUtils.writeUint16(1, b, 1); - assertEquals("000001", StringUtils.toHexString(b)); - ByteUtils.writeUint16(65535, b, 1); - assertEquals("00FFFF", StringUtils.toHexString(b)); + assertEquals("00000100", StringUtils.toHexString(b)); + ByteUtils.writeUint16(Short.MAX_VALUE, b, 1); + assertEquals("007FFF00", StringUtils.toHexString(b)); + ByteUtils.writeUint16(MAX_16_BIT_UNSIGNED, b, 1); + assertEquals("00FFFF00", StringUtils.toHexString(b)); + } + + @Test + public void testWriteUint16ValidatesArguments() { + try { + ByteUtils.writeUint16(0, new byte[1], 0); + fail(); + } catch (IllegalArgumentException expected) { + // Expected + } + try { + ByteUtils.writeUint16(0, new byte[2], 1); + fail(); + } catch (IllegalArgumentException expected) { + // Expected + } + try { + ByteUtils.writeUint16(-1, new byte[2], 0); + fail(); + } catch (IllegalArgumentException expected) { + // Expected + } + try { + ByteUtils.writeUint16(MAX_16_BIT_UNSIGNED + 1, new byte[2], 0); + fail(); + } catch (IllegalArgumentException expected) { + // Expected + } } @Test public void testWriteUint32() { - byte[] b = new byte[5]; + byte[] b = new byte[6]; ByteUtils.writeUint32(0, b, 1); - assertEquals("0000000000", StringUtils.toHexString(b)); + assertEquals("000000000000", StringUtils.toHexString(b)); ByteUtils.writeUint32(1, b, 1); - assertEquals("0000000001", StringUtils.toHexString(b)); - ByteUtils.writeUint32(4294967295L, b, 1); - assertEquals("00FFFFFFFF", StringUtils.toHexString(b)); + assertEquals("000000000100", StringUtils.toHexString(b)); + ByteUtils.writeUint32(Integer.MAX_VALUE, b, 1); + assertEquals("007FFFFFFF00", StringUtils.toHexString(b)); + ByteUtils.writeUint32(MAX_32_BIT_UNSIGNED, b, 1); + assertEquals("00FFFFFFFF00", StringUtils.toHexString(b)); + } + + @Test + public void testWriteUint32ValidatesArguments() { + try { + ByteUtils.writeUint32(0, new byte[3], 0); + fail(); + } catch (IllegalArgumentException expected) { + // Expected + } + try { + ByteUtils.writeUint32(0, new byte[4], 1); + fail(); + } catch (IllegalArgumentException expected) { + // Expected + } + try { + ByteUtils.writeUint32(-1, new byte[4], 0); + fail(); + } catch (IllegalArgumentException expected) { + // Expected + } + try { + ByteUtils.writeUint32(MAX_32_BIT_UNSIGNED + 1, new byte[4], 0); + fail(); + } catch (IllegalArgumentException expected) { + // Expected + } + } + + @Test + public void testWriteUint64() { + byte[] b = new byte[10]; + ByteUtils.writeUint64(0, b, 1); + assertEquals("00000000000000000000", StringUtils.toHexString(b)); + ByteUtils.writeUint64(1, b, 1); + assertEquals("00000000000000000100", StringUtils.toHexString(b)); + ByteUtils.writeUint64(Long.MAX_VALUE, b, 1); + assertEquals("007FFFFFFFFFFFFFFF00", StringUtils.toHexString(b)); + } + + @Test + public void testWriteUint64ValidatesArguments() { + try { + ByteUtils.writeUint64(0, new byte[7], 0); + fail(); + } catch (IllegalArgumentException expected) { + // Expected + } + try { + ByteUtils.writeUint64(0, new byte[8], 1); + fail(); + } catch (IllegalArgumentException expected) { + // Expected + } + try { + ByteUtils.writeUint64(-1, new byte[8], 0); + fail(); + } catch (IllegalArgumentException expected) { + // Expected + } } @Test