Merge branch 'btp-final-crypto-changes' into 'master'

Final crypto changes for BTPv2. #111

This patch brings the implementation into line with the BTPv2 spec. Changes:

* Use BLAKE2s to generate tags
* KDF arguments for key rotation
* Frame IV format

Closes #111.

See merge request !48
This commit is contained in:
akwizgran
2016-01-12 11:17:15 +00:00
11 changed files with 309 additions and 140 deletions

View File

@@ -8,23 +8,29 @@ public interface TransportConstants {
/** The length of the pseudo-random tag in bytes. */ /** The length of the pseudo-random tag in bytes. */
int TAG_LENGTH = 16; 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. */ /** The length of the message authentication code (MAC) in bytes. */
int MAC_LENGTH = 16; 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. */ /** The length of the stream header in bytes. */
int STREAM_HEADER_LENGTH = STREAM_HEADER_IV_LENGTH + SecretKey.LENGTH int STREAM_HEADER_LENGTH = STREAM_HEADER_IV_LENGTH + SecretKey.LENGTH
+ MAC_LENGTH; + MAC_LENGTH;
/** The length of the frame initalisation vector (IV) in bytes. */ /** The length of the frame nonce in bytes. */
int FRAME_IV_LENGTH = 24; int FRAME_NONCE_LENGTH = 24;
/** The length of the frame header in bytes. */ /** The length of the plaintext frame header in bytes. */
int FRAME_HEADER_LENGTH = 4 + MAC_LENGTH; 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; int MAX_FRAME_LENGTH = 1024;
/** The maximum total length of the frame payload and padding in bytes. */ /** The maximum total length of the frame payload and padding in bytes. */

View File

@@ -17,12 +17,10 @@ import org.briarproject.api.transport.TransportKeys;
import org.briarproject.util.ByteUtils; import org.briarproject.util.ByteUtils;
import org.briarproject.util.StringUtils; import org.briarproject.util.StringUtils;
import org.spongycastle.crypto.AsymmetricCipherKeyPair; import org.spongycastle.crypto.AsymmetricCipherKeyPair;
import org.spongycastle.crypto.BlockCipher;
import org.spongycastle.crypto.CipherParameters; import org.spongycastle.crypto.CipherParameters;
import org.spongycastle.crypto.Digest; import org.spongycastle.crypto.Digest;
import org.spongycastle.crypto.agreement.ECDHCBasicAgreement; import org.spongycastle.crypto.agreement.ECDHCBasicAgreement;
import org.spongycastle.crypto.digests.SHA256Digest; import org.spongycastle.crypto.digests.SHA256Digest;
import org.spongycastle.crypto.engines.AESLightEngine;
import org.spongycastle.crypto.generators.ECKeyPairGenerator; import org.spongycastle.crypto.generators.ECKeyPairGenerator;
import org.spongycastle.crypto.generators.PKCS5S2ParametersGenerator; import org.spongycastle.crypto.generators.PKCS5S2ParametersGenerator;
import org.spongycastle.crypto.params.ECKeyGenerationParameters; 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.ECPublicKeyParameters;
import org.spongycastle.crypto.params.KeyParameter; import org.spongycastle.crypto.params.KeyParameter;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.ArrayList; 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.invitation.InvitationConstants.CODE_BITS;
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH; import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
import static org.briarproject.crypto.EllipticCurveConstants.PARAMETERS; 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; import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED;
class CryptoComponentImpl implements CryptoComponent { 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_TARGET_MILLIS = 500;
private static final int PBKDF_SAMPLES = 30; 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 // 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 // KDF labels for confirmation code derivation
private static final byte[] A_CONFIRM = private static final byte[] A_CONFIRM = ascii("ALICE_CONFIRMATION_CODE");
{ 'A', '_', 'C', 'O', 'N', 'F', 'I', 'R', 'M' }; private static final byte[] B_CONFIRM = ascii("BOB_CONFIRMATION_CODE");
private static final byte[] B_CONFIRM =
{ 'B', '_', 'C', 'O', 'N', 'F', 'I', 'R', 'M' };
// KDF labels for invitation stream header key derivation // KDF labels for invitation stream header key derivation
private static final byte[] A_INVITE = private static final byte[] A_INVITE = ascii("ALICE_INVITATION_KEY");
{ 'A', '_', 'I', 'N', 'V', 'I', 'T', 'E' }; private static final byte[] B_INVITE = ascii("BOB_INVITATION_KEY");
private static final byte[] B_INVITE =
{ 'B', '_', 'I', 'N', 'V', 'I', 'T', 'E' };
// KDF labels for signature nonce derivation // KDF labels for signature nonce derivation
private static final byte[] A_NONCE = { 'A', '_', 'N', 'O', 'N', 'C', 'E' }; private static final byte[] A_NONCE = ascii("ALICE_SIGNATURE_NONCE");
private static final byte[] B_NONCE = { 'B', '_', 'N', 'O', 'N', 'C', 'E' }; private static final byte[] B_NONCE = ascii("BOB_SIGNATURE_NONCE");
// KDF label for group salt derivation // 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 // KDF labels for tag key derivation
private static final byte[] A_TAG = { 'A', '_', 'T', 'A', 'G' }; private static final byte[] A_TAG = ascii("ALICE_TAG_KEY");
private static final byte[] B_TAG = { 'B', '_', 'T', 'A', 'G' }; private static final byte[] B_TAG = ascii("BOB_TAG_KEY");
// KDF labels for header key derivation // KDF labels for header key derivation
private static final byte[] A_HEADER = private static final byte[] A_HEADER = ascii("ALICE_HEADER_KEY");
{ 'A', '_', 'H', 'E', 'A', 'D', 'E', 'R' }; private static final byte[] B_HEADER = ascii("BOB_HEADER_KEY");
private static final byte[] B_HEADER =
{ 'B', '_', 'H', 'E', 'A', 'D', 'E', 'R' };
// KDF label for key rotation // 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 SecureRandom secureRandom;
private final ECKeyPairGenerator agreementKeyPairGenerator; private final ECKeyPairGenerator agreementKeyPairGenerator;
@@ -290,8 +289,8 @@ class CryptoComponentImpl implements CryptoComponent {
} }
private SecretKey rotateKey(SecretKey k, long rotationPeriod) { private SecretKey rotateKey(SecretKey k, long rotationPeriod) {
byte[] period = new byte[4]; byte[] period = new byte[INT_64_BYTES];
ByteUtils.writeUint32(rotationPeriod, period, 0); ByteUtils.writeUint64(rotationPeriod, period, 0);
return new SecretKey(macKdf(k, ROTATE, period)); return new SecretKey(macKdf(k, ROTATE, period));
} }
@@ -311,14 +310,19 @@ class CryptoComponentImpl implements CryptoComponent {
if (tag.length < TAG_LENGTH) throw new IllegalArgumentException(); if (tag.length < TAG_LENGTH) throw new IllegalArgumentException();
if (streamNumber < 0 || streamNumber > MAX_32_BIT_UNSIGNED) if (streamNumber < 0 || streamNumber > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
for (int i = 0; i < TAG_LENGTH; i++) tag[i] = 0; // Initialise the PRF
ByteUtils.writeUint32(streamNumber, tag, 0); Digest prf = new Blake2sDigest(tagKey.getBytes());
BlockCipher cipher = new AESLightEngine(); // The output of the PRF must be long enough to use as a tag
if (cipher.getBlockSize() != TAG_LENGTH) int macLength = prf.getDigestSize();
throw new IllegalStateException(); if (macLength < TAG_LENGTH) throw new IllegalStateException();
KeyParameter k = new KeyParameter(tagKey.getBytes()); // The input is the stream number as a 64-bit integer
cipher.init(true, k); byte[] input = new byte[INT_64_BYTES];
cipher.processBlock(tag, 0, tag, 0); 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) { public byte[] encryptWithPassword(byte[] input, String password) {
@@ -335,15 +339,16 @@ class CryptoComponentImpl implements CryptoComponent {
byte[] iv = new byte[STORAGE_IV_BYTES]; byte[] iv = new byte[STORAGE_IV_BYTES];
secureRandom.nextBytes(iv); secureRandom.nextBytes(iv);
// The output contains the salt, iterations, IV, ciphertext and MAC // 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]; byte[] output = new byte[outputLen];
System.arraycopy(salt, 0, output, 0, salt.length); System.arraycopy(salt, 0, output, 0, salt.length);
ByteUtils.writeUint32(iterations, output, 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 // Initialise the cipher and encrypt the plaintext
try { try {
cipher.init(true, key, iv); 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); cipher.process(input, 0, input.length, output, outputOff);
return output; return output;
} catch (GeneralSecurityException e) { } catch (GeneralSecurityException e) {
@@ -355,7 +360,8 @@ class CryptoComponentImpl implements CryptoComponent {
AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher(); AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher();
int macBytes = cipher.getMacBytes(); int macBytes = cipher.getMacBytes();
// The input contains the salt, iterations, IV, ciphertext and MAC // 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 return null; // Invalid input
byte[] salt = new byte[PBKDF_SALT_BYTES]; byte[] salt = new byte[PBKDF_SALT_BYTES];
System.arraycopy(input, 0, salt, 0, salt.length); System.arraycopy(input, 0, salt, 0, salt.length);
@@ -363,7 +369,7 @@ class CryptoComponentImpl implements CryptoComponent {
if (iterations < 0 || iterations > Integer.MAX_VALUE) if (iterations < 0 || iterations > Integer.MAX_VALUE)
return null; // Invalid iteration count return null; // Invalid iteration count
byte[] iv = new byte[STORAGE_IV_BYTES]; 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 // Derive the key from the password
SecretKey key = new SecretKey(pbkdf2(password, salt, (int) iterations)); SecretKey key = new SecretKey(pbkdf2(password, salt, (int) iterations));
// Initialise the cipher // Initialise the cipher
@@ -374,7 +380,7 @@ class CryptoComponentImpl implements CryptoComponent {
} }
// Try to decrypt the ciphertext (may be invalid) // Try to decrypt the ciphertext (may be invalid)
try { try {
int inputOff = salt.length + 4 + iv.length; int inputOff = salt.length + INT_32_BYTES + iv.length;
int inputLen = input.length - inputOff; int inputLen = input.length - inputOff;
byte[] output = new byte[inputLen - macBytes]; byte[] output = new byte[inputLen - macBytes];
cipher.process(input, inputOff, inputLen, output, 0); cipher.process(input, inputOff, inputLen, output, 0);
@@ -392,7 +398,7 @@ class CryptoComponentImpl implements CryptoComponent {
int hashLength = digest.getDigestSize(); int hashLength = digest.getDigestSize();
if (hashLength < SecretKey.LENGTH) throw new IllegalStateException(); if (hashLength < SecretKey.LENGTH) throw new IllegalStateException();
// Calculate the hash over the concatenated length-prefixed inputs // 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) { for (byte[] input : inputs) {
ByteUtils.writeUint32(input.length, length, 0); ByteUtils.writeUint32(input.length, length, 0);
digest.update(length, 0, length.length); digest.update(length, 0, length.length);
@@ -416,7 +422,7 @@ class CryptoComponentImpl implements CryptoComponent {
int macLength = prf.getDigestSize(); int macLength = prf.getDigestSize();
if (macLength < SecretKey.LENGTH) throw new IllegalStateException(); if (macLength < SecretKey.LENGTH) throw new IllegalStateException();
// Calculate the PRF over the concatenated length-prefixed inputs // 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) { for (byte[] input : inputs) {
ByteUtils.writeUint32(input.length, length, 0); ByteUtils.writeUint32(input.length, length, 0);
prf.update(length, 0, length.length); prf.update(length, 0, length.length);

View File

@@ -2,51 +2,51 @@ package org.briarproject.crypto;
import org.briarproject.util.ByteUtils; import org.briarproject.util.ByteUtils;
import static org.briarproject.api.transport.TransportConstants.FRAME_HEADER_LENGTH; import static org.briarproject.api.transport.TransportConstants.FRAME_HEADER_PLAINTEXT_LENGTH;
import static org.briarproject.api.transport.TransportConstants.FRAME_IV_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.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 { class FrameEncoder {
static void encodeIv(byte[] iv, long frameNumber, boolean header) { static void encodeNonce(byte[] dest, long frameNumber, boolean header) {
if (iv.length < FRAME_IV_LENGTH) throw new IllegalArgumentException(); if (dest.length < FRAME_NONCE_LENGTH)
if (frameNumber < 0 || frameNumber > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
ByteUtils.writeUint32(frameNumber, iv, 0); if (frameNumber < 0) throw new IllegalArgumentException();
if (header) iv[4] = 1; ByteUtils.writeUint64(frameNumber, dest, 0);
else iv[4] = 0; if (header) dest[0] |= 0x80;
for (int i = 5; i < FRAME_IV_LENGTH; i++) iv[i] = 0; 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) { int payloadLength, int paddingLength) {
if (header.length < FRAME_HEADER_LENGTH) if (dest.length < FRAME_HEADER_PLAINTEXT_LENGTH)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
if (payloadLength < 0) throw new IllegalArgumentException(); if (payloadLength < 0) throw new IllegalArgumentException();
if (paddingLength < 0) throw new IllegalArgumentException(); if (paddingLength < 0) throw new IllegalArgumentException();
if (payloadLength + paddingLength > MAX_PAYLOAD_LENGTH) if (payloadLength + paddingLength > MAX_PAYLOAD_LENGTH)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
ByteUtils.writeUint16(payloadLength, header, 0); ByteUtils.writeUint16(payloadLength, dest, 0);
ByteUtils.writeUint16(paddingLength, header, 2); ByteUtils.writeUint16(paddingLength, dest, INT_16_BYTES);
if (finalFrame) header[0] |= 0x80; if (finalFrame) dest[0] |= 0x80;
} }
static boolean isFinalFrame(byte[] header) { static boolean isFinalFrame(byte[] header) {
if (header.length < FRAME_HEADER_LENGTH) if (header.length < FRAME_HEADER_PLAINTEXT_LENGTH)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
return (header[0] & 0x80) == 0x80; return (header[0] & 0x80) == 0x80;
} }
static int getPayloadLength(byte[] header) { static int getPayloadLength(byte[] header) {
if (header.length < FRAME_HEADER_LENGTH) if (header.length < FRAME_HEADER_PLAINTEXT_LENGTH)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
return ByteUtils.readUint16(header, 0) & 0x7FFF; return ByteUtils.readUint16(header, 0) & 0x7FFF;
} }
static int getPaddingLength(byte[] header) { static int getPaddingLength(byte[] header) {
if (header.length < FRAME_HEADER_LENGTH) if (header.length < FRAME_HEADER_PLAINTEXT_LENGTH)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
return ByteUtils.readUint16(header, 2); return ByteUtils.readUint16(header, INT_16_BYTES);
} }
} }

View File

@@ -3,14 +3,16 @@ package org.briarproject.crypto;
import org.briarproject.api.crypto.PseudoRandom; import org.briarproject.api.crypto.PseudoRandom;
import org.briarproject.util.ByteUtils; import org.briarproject.util.ByteUtils;
import static org.briarproject.util.ByteUtils.INT_32_BYTES;
class PseudoRandomImpl implements PseudoRandom { class PseudoRandomImpl implements PseudoRandom {
private final FortunaGenerator generator; private final FortunaGenerator generator;
PseudoRandomImpl(int seed1, int seed2) { 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(seed1, seed, 0);
ByteUtils.writeUint32(seed2, seed, 4); ByteUtils.writeUint32(seed2, seed, INT_32_BYTES);
generator = new FortunaGenerator(seed); generator = new FortunaGenerator(seed);
} }

View File

@@ -10,7 +10,8 @@ import java.io.InputStream;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import static org.briarproject.api.transport.TransportConstants.FRAME_HEADER_LENGTH; 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.MAC_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_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.MAX_PAYLOAD_LENGTH;
@@ -22,7 +23,7 @@ class StreamDecrypterImpl implements StreamDecrypter {
private final InputStream in; private final InputStream in;
private final AuthenticatedCipher cipher; private final AuthenticatedCipher cipher;
private final SecretKey streamHeaderKey; private final SecretKey streamHeaderKey;
private final byte[] frameIv, frameHeader, frameCiphertext; private final byte[] frameNonce, frameHeader, frameCiphertext;
private SecretKey frameKey; private SecretKey frameKey;
private long frameNumber; private long frameNumber;
@@ -33,8 +34,8 @@ class StreamDecrypterImpl implements StreamDecrypter {
this.in = in; this.in = in;
this.cipher = cipher; this.cipher = cipher;
this.streamHeaderKey = streamHeaderKey; this.streamHeaderKey = streamHeaderKey;
frameIv = new byte[FRAME_IV_LENGTH]; frameNonce = new byte[FRAME_NONCE_LENGTH];
frameHeader = new byte[FRAME_HEADER_LENGTH]; frameHeader = new byte[FRAME_HEADER_PLAINTEXT_LENGTH];
frameCiphertext = new byte[MAX_FRAME_LENGTH]; frameCiphertext = new byte[MAX_FRAME_LENGTH];
frameKey = null; frameKey = null;
frameNumber = 0; frameNumber = 0;
@@ -46,6 +47,8 @@ class StreamDecrypterImpl implements StreamDecrypter {
if (payload.length < MAX_PAYLOAD_LENGTH) if (payload.length < MAX_PAYLOAD_LENGTH)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
if (finalFrame) return -1; if (finalFrame) return -1;
// Don't allow the frame counter to wrap
if (frameNumber < 0) throw new IOException();
// Read the stream header if required // Read the stream header if required
if (frameKey == null) readStreamHeader(); if (frameKey == null) readStreamHeader();
// Read the frame header // Read the frame header
@@ -57,12 +60,12 @@ class StreamDecrypterImpl implements StreamDecrypter {
offset += read; offset += read;
} }
// Decrypt and authenticate the frame header // Decrypt and authenticate the frame header
FrameEncoder.encodeIv(frameIv, frameNumber, true); FrameEncoder.encodeNonce(frameNonce, frameNumber, true);
try { try {
cipher.init(false, frameKey, frameIv); cipher.init(false, frameKey, frameNonce);
int decrypted = cipher.process(frameCiphertext, 0, int decrypted = cipher.process(frameCiphertext, 0,
FRAME_HEADER_LENGTH, frameHeader, 0); FRAME_HEADER_LENGTH, frameHeader, 0);
if (decrypted != FRAME_HEADER_LENGTH - MAC_LENGTH) if (decrypted != FRAME_HEADER_PLAINTEXT_LENGTH)
throw new RuntimeException(); throw new RuntimeException();
} catch (GeneralSecurityException e) { } catch (GeneralSecurityException e) {
throw new FormatException(); throw new FormatException();
@@ -82,9 +85,9 @@ class StreamDecrypterImpl implements StreamDecrypter {
offset += read; offset += read;
} }
// Decrypt and authenticate the payload and padding // Decrypt and authenticate the payload and padding
FrameEncoder.encodeIv(frameIv, frameNumber, false); FrameEncoder.encodeNonce(frameNonce, frameNumber, false);
try { try {
cipher.init(false, frameKey, frameIv); cipher.init(false, frameKey, frameNonce);
int decrypted = cipher.process(frameCiphertext, FRAME_HEADER_LENGTH, int decrypted = cipher.process(frameCiphertext, FRAME_HEADER_LENGTH,
payloadLength + paddingLength + MAC_LENGTH, payload, 0); payloadLength + paddingLength + MAC_LENGTH, payload, 0);
if (decrypted != payloadLength + paddingLength) if (decrypted != payloadLength + paddingLength)

View File

@@ -8,13 +8,13 @@ import java.io.OutputStream;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import static org.briarproject.api.transport.TransportConstants.FRAME_HEADER_LENGTH; 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.MAC_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_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.MAX_PAYLOAD_LENGTH;
import static org.briarproject.api.transport.TransportConstants.STREAM_HEADER_IV_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.api.transport.TransportConstants.STREAM_HEADER_LENGTH;
import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED;
class StreamEncrypterImpl implements StreamEncrypter { class StreamEncrypterImpl implements StreamEncrypter {
@@ -22,7 +22,7 @@ class StreamEncrypterImpl implements StreamEncrypter {
private final AuthenticatedCipher cipher; private final AuthenticatedCipher cipher;
private final SecretKey streamHeaderKey, frameKey; private final SecretKey streamHeaderKey, frameKey;
private final byte[] tag, streamHeaderIv; private final byte[] tag, streamHeaderIv;
private final byte[] frameIv, framePlaintext, frameCiphertext; private final byte[] frameNonce, frameHeader, framePlaintext, frameCiphertext;
private long frameNumber; private long frameNumber;
private boolean writeTag, writeStreamHeader; private boolean writeTag, writeStreamHeader;
@@ -36,8 +36,9 @@ class StreamEncrypterImpl implements StreamEncrypter {
this.streamHeaderIv = streamHeaderIv; this.streamHeaderIv = streamHeaderIv;
this.streamHeaderKey = streamHeaderKey; this.streamHeaderKey = streamHeaderKey;
this.frameKey = frameKey; this.frameKey = frameKey;
frameIv = new byte[FRAME_IV_LENGTH]; frameNonce = new byte[FRAME_NONCE_LENGTH];
framePlaintext = new byte[FRAME_HEADER_LENGTH + MAX_PAYLOAD_LENGTH]; frameHeader = new byte[FRAME_HEADER_PLAINTEXT_LENGTH];
framePlaintext = new byte[MAX_PAYLOAD_LENGTH];
frameCiphertext = new byte[MAX_FRAME_LENGTH]; frameCiphertext = new byte[MAX_FRAME_LENGTH];
frameNumber = 0; frameNumber = 0;
writeTag = (tag != null); writeTag = (tag != null);
@@ -49,34 +50,33 @@ class StreamEncrypterImpl implements StreamEncrypter {
if (payloadLength + paddingLength > MAX_PAYLOAD_LENGTH) if (payloadLength + paddingLength > MAX_PAYLOAD_LENGTH)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
// Don't allow the frame counter to wrap // 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 // Write the tag if required
if (writeTag) writeTag(); if (writeTag) writeTag();
// Write the stream header if required // Write the stream header if required
if (writeStreamHeader) writeStreamHeader(); if (writeStreamHeader) writeStreamHeader();
// Encode the frame header // Encode the frame header
FrameEncoder.encodeHeader(framePlaintext, finalFrame, payloadLength, FrameEncoder.encodeHeader(frameHeader, finalFrame, payloadLength,
paddingLength); paddingLength);
// Encrypt and authenticate the frame header // Encrypt and authenticate the frame header
FrameEncoder.encodeIv(frameIv, frameNumber, true); FrameEncoder.encodeNonce(frameNonce, frameNumber, true);
try { try {
cipher.init(true, frameKey, frameIv); cipher.init(true, frameKey, frameNonce);
int encrypted = cipher.process(framePlaintext, 0, int encrypted = cipher.process(frameHeader, 0,
FRAME_HEADER_LENGTH - MAC_LENGTH, frameCiphertext, 0); FRAME_HEADER_PLAINTEXT_LENGTH, frameCiphertext, 0);
if (encrypted != FRAME_HEADER_LENGTH) throw new RuntimeException(); if (encrypted != FRAME_HEADER_LENGTH) throw new RuntimeException();
} catch (GeneralSecurityException badCipher) { } catch (GeneralSecurityException badCipher) {
throw new RuntimeException(badCipher); throw new RuntimeException(badCipher);
} }
// Combine the payload and padding // Combine the payload and padding
System.arraycopy(payload, 0, framePlaintext, FRAME_HEADER_LENGTH, System.arraycopy(payload, 0, framePlaintext, 0, payloadLength);
payloadLength);
for (int i = 0; i < paddingLength; i++) 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 // Encrypt and authenticate the payload and padding
FrameEncoder.encodeIv(frameIv, frameNumber, false); FrameEncoder.encodeNonce(frameNonce, frameNumber, false);
try { try {
cipher.init(true, frameKey, frameIv); cipher.init(true, frameKey, frameNonce);
int encrypted = cipher.process(framePlaintext, FRAME_HEADER_LENGTH, int encrypted = cipher.process(framePlaintext, 0,
payloadLength + paddingLength, frameCiphertext, payloadLength + paddingLength, frameCiphertext,
FRAME_HEADER_LENGTH); FRAME_HEADER_LENGTH);
if (encrypted != payloadLength + paddingLength + MAC_LENGTH) if (encrypted != payloadLength + paddingLength + MAC_LENGTH)

View File

@@ -12,50 +12,72 @@ public class ByteUtils {
*/ */
public static final long MAX_32_BIT_UNSIGNED = 4294967295L; // 2^32 - 1 public static final long MAX_32_BIT_UNSIGNED = 4294967295L; // 2^32 - 1
public static void writeUint8(int i, byte[] b, int offset) { /** The number of bytes needed to encode a 16-bit integer. */
if (i < 0) throw new IllegalArgumentException(); public static final int INT_16_BYTES = 2;
if (i > 255) throw new IllegalArgumentException();
if (b.length < offset) throw new IllegalArgumentException(); /** The number of bytes needed to encode a 32-bit integer. */
b[offset] = (byte) i; 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) { public static void writeUint32(long src, byte[] dest, int offset) {
if (i < 0) throw new IllegalArgumentException(); if (src < 0) throw new IllegalArgumentException();
if (i > MAX_16_BIT_UNSIGNED) throw new IllegalArgumentException(); if (src > MAX_32_BIT_UNSIGNED) throw new IllegalArgumentException();
if (b.length < offset + 2) throw new IllegalArgumentException(); if (dest.length < offset + INT_32_BYTES)
b[offset] = (byte) (i >> 8); throw new IllegalArgumentException();
b[offset + 1] = (byte) (i & 0xFF); 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) { public static void writeUint64(long src, byte[] dest, int offset) {
if (i < 0) throw new IllegalArgumentException(); if (src < 0) throw new IllegalArgumentException();
if (i > MAX_32_BIT_UNSIGNED) throw new IllegalArgumentException(); if (dest.length < offset + INT_64_BYTES)
if (b.length < offset + 4) throw new IllegalArgumentException(); throw new IllegalArgumentException();
b[offset] = (byte) (i >> 24); dest[offset] = (byte) (src >> 56);
b[offset + 1] = (byte) (i >> 16 & 0xFF); dest[offset + 1] = (byte) (src >> 48 & 0xFF);
b[offset + 2] = (byte) (i >> 8 & 0xFF); dest[offset + 2] = (byte) (src >> 40 & 0xFF);
b[offset + 3] = (byte) (i & 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) { public static int readUint16(byte[] src, int offset) {
if (b.length < offset + 2) throw new IllegalArgumentException(); if (src.length < offset + INT_16_BYTES)
return ((b[offset] & 0xFF) << 8) | (b[offset + 1] & 0xFF); throw new IllegalArgumentException();
return ((src[offset] & 0xFF) << 8) | (src[offset + 1] & 0xFF);
} }
public static long readUint32(byte[] b, int offset) { public static long readUint32(byte[] src, int offset) {
if (b.length < offset + 4) throw new IllegalArgumentException(); if (src.length < offset + INT_32_BYTES)
return ((b[offset] & 0xFFL) << 24) | ((b[offset + 1] & 0xFFL) << 16) throw new IllegalArgumentException();
| ((b[offset + 2] & 0xFFL) << 8) | (b[offset + 3] & 0xFFL); 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) { public static int readUint(byte[] src, int bits) {
if (b.length << 3 < bits) throw new IllegalArgumentException(); if (src.length << 3 < bits) throw new IllegalArgumentException();
int result = 0; int dest = 0;
for (int i = 0; i < bits; i++) { 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 dest >= 0;
assert result < 1 << bits; assert dest < 1 << bits;
return result; return dest;
} }
} }

View File

@@ -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.MAC_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAX_PAYLOAD_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_IV_LENGTH;
import static org.briarproject.util.ByteUtils.INT_16_BYTES;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
@@ -119,7 +120,7 @@ public class StreamDecrypterImplTest extends BriarTestCase {
// The payload length plus padding length is invalid // The payload length plus padding length is invalid
int payloadLength = MAX_PAYLOAD_LENGTH - 1, paddingLength = 2; int payloadLength = MAX_PAYLOAD_LENGTH - 1, paddingLength = 2;
ByteUtils.writeUint16(payloadLength, frameHeader, 0); ByteUtils.writeUint16(payloadLength, frameHeader, 0);
ByteUtils.writeUint16(paddingLength, frameHeader, 2); ByteUtils.writeUint16(paddingLength, frameHeader, INT_16_BYTES);
byte[] payload = new byte[payloadLength]; byte[] payload = new byte[payloadLength];
random.nextBytes(payload); random.nextBytes(payload);

View File

@@ -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.MAC_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_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.api.transport.TransportConstants.STREAM_HEADER_LENGTH;
import static org.briarproject.util.ByteUtils.INT_16_BYTES;
class TestStreamDecrypter implements StreamDecrypter { class TestStreamDecrypter implements StreamDecrypter {
@@ -35,7 +36,7 @@ class TestStreamDecrypter implements StreamDecrypter {
} }
finalFrame = (frame[0] & 0x80) == 0x80; finalFrame = (frame[0] & 0x80) == 0x80;
int payloadLength = ByteUtils.readUint16(frame, 0) & 0x7FFF; 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 int frameLength = FRAME_HEADER_LENGTH + payloadLength + paddingLength
+ MAC_LENGTH; + MAC_LENGTH;
while (offset < frameLength) { while (offset < frameLength) {

View File

@@ -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.FRAME_HEADER_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH; import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
import static org.briarproject.api.transport.TransportConstants.STREAM_HEADER_LENGTH; import static org.briarproject.api.transport.TransportConstants.STREAM_HEADER_LENGTH;
import static org.briarproject.util.ByteUtils.INT_16_BYTES;
class TestStreamEncrypter implements StreamEncrypter { class TestStreamEncrypter implements StreamEncrypter {
@@ -27,7 +28,7 @@ class TestStreamEncrypter implements StreamEncrypter {
if (writeTagAndHeader) writeTagAndHeader(); if (writeTagAndHeader) writeTagAndHeader();
byte[] frameHeader = new byte[FRAME_HEADER_LENGTH]; byte[] frameHeader = new byte[FRAME_HEADER_LENGTH];
ByteUtils.writeUint16(payloadLength, frameHeader, 0); ByteUtils.writeUint16(payloadLength, frameHeader, 0);
ByteUtils.writeUint16(paddingLength, frameHeader, 2); ByteUtils.writeUint16(paddingLength, frameHeader, INT_16_BYTES);
if (finalFrame) frameHeader[0] |= 0x80; if (finalFrame) frameHeader[0] |= 0x80;
out.write(frameHeader); out.write(frameHeader);
out.write(payload, 0, payloadLength); out.write(payload, 0, payloadLength);

View File

@@ -3,7 +3,10 @@ package org.briarproject.util;
import org.briarproject.BriarTestCase; import org.briarproject.BriarTestCase;
import org.junit.Test; 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.assertEquals;
import static org.junit.Assert.fail;
public class ByteUtilsTest extends BriarTestCase { public class ByteUtilsTest extends BriarTestCase {
@@ -17,6 +20,22 @@ public class ByteUtilsTest extends BriarTestCase {
assertEquals(65535, ByteUtils.readUint16(b, 1)); 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 @Test
public void testReadUint32() { public void testReadUint32() {
byte[] b = StringUtils.fromHexString("0000000000"); byte[] b = StringUtils.fromHexString("0000000000");
@@ -27,27 +46,135 @@ public class ByteUtilsTest extends BriarTestCase {
assertEquals(4294967295L, ByteUtils.readUint32(b, 1)); 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 @Test
public void testWriteUint16() { public void testWriteUint16() {
byte[] b = new byte[3]; byte[] b = new byte[4];
ByteUtils.writeUint16(0, b, 1); ByteUtils.writeUint16(0, b, 1);
assertEquals("000000", StringUtils.toHexString(b)); assertEquals("00000000", StringUtils.toHexString(b));
ByteUtils.writeUint16(1, b, 1); ByteUtils.writeUint16(1, b, 1);
assertEquals("000001", StringUtils.toHexString(b)); assertEquals("00000100", StringUtils.toHexString(b));
ByteUtils.writeUint16(65535, b, 1); ByteUtils.writeUint16(Short.MAX_VALUE, b, 1);
assertEquals("00FFFF", StringUtils.toHexString(b)); 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 @Test
public void testWriteUint32() { public void testWriteUint32() {
byte[] b = new byte[5]; byte[] b = new byte[6];
ByteUtils.writeUint32(0, b, 1); ByteUtils.writeUint32(0, b, 1);
assertEquals("0000000000", StringUtils.toHexString(b)); assertEquals("000000000000", StringUtils.toHexString(b));
ByteUtils.writeUint32(1, b, 1); ByteUtils.writeUint32(1, b, 1);
assertEquals("0000000001", StringUtils.toHexString(b)); assertEquals("000000000100", StringUtils.toHexString(b));
ByteUtils.writeUint32(4294967295L, b, 1); ByteUtils.writeUint32(Integer.MAX_VALUE, b, 1);
assertEquals("00FFFFFFFF", StringUtils.toHexString(b)); 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 @Test