diff --git a/tls/src/main/java/org/bouncycastle/jsse/provider/GcmTls12NonceGeneratorUtil.java b/tls/src/main/java/org/bouncycastle/jsse/provider/GcmTls12NonceGeneratorUtil.java new file mode 100644 index 0000000000..8504232b35 --- /dev/null +++ b/tls/src/main/java/org/bouncycastle/jsse/provider/GcmTls12NonceGeneratorUtil.java @@ -0,0 +1,25 @@ +package org.bouncycastle.jsse.provider; + +import org.bouncycastle.tls.crypto.TlsNonceGenerator; + +final public class GcmTls12NonceGeneratorUtil +{ + private static TlsNonceGeneratorFactory tlsNonceGeneratorFactory = null; + + public static void setGcmTlsNonceGeneratorFactory(final TlsNonceGeneratorFactory factory) + { + tlsNonceGeneratorFactory = factory; + } + + public static boolean isGcmFipsNonceGeneratorFactorySet() + { + return tlsNonceGeneratorFactory != null; + } + + public static TlsNonceGenerator createGcmFipsNonceGenerator(final byte[] baseNonce, final int counterSizeInBits) + { + return tlsNonceGeneratorFactory != null + ? tlsNonceGeneratorFactory.create(baseNonce, counterSizeInBits) + : null; + } +} diff --git a/tls/src/main/java/org/bouncycastle/jsse/provider/TlsNonceGeneratorFactory.java b/tls/src/main/java/org/bouncycastle/jsse/provider/TlsNonceGeneratorFactory.java new file mode 100644 index 0000000000..827bbd170b --- /dev/null +++ b/tls/src/main/java/org/bouncycastle/jsse/provider/TlsNonceGeneratorFactory.java @@ -0,0 +1,8 @@ +package org.bouncycastle.jsse.provider; + +import org.bouncycastle.tls.crypto.TlsNonceGenerator; + +public interface TlsNonceGeneratorFactory +{ + TlsNonceGenerator create(byte[] baseNonce, int counterSizeInBits); +} diff --git a/tls/src/main/java/org/bouncycastle/tls/crypto/impl/TlsAEADCipher.java b/tls/src/main/java/org/bouncycastle/tls/crypto/impl/TlsAEADCipher.java index d7910ccd5c..3b6148d50e 100644 --- a/tls/src/main/java/org/bouncycastle/tls/crypto/impl/TlsAEADCipher.java +++ b/tls/src/main/java/org/bouncycastle/tls/crypto/impl/TlsAEADCipher.java @@ -13,9 +13,13 @@ import org.bouncycastle.tls.crypto.TlsCryptoUtils; import org.bouncycastle.tls.crypto.TlsDecodeResult; import org.bouncycastle.tls.crypto.TlsEncodeResult; +import org.bouncycastle.tls.crypto.TlsNonceGenerator; import org.bouncycastle.tls.crypto.TlsSecret; import org.bouncycastle.util.Arrays; +import static org.bouncycastle.jsse.provider.GcmTls12NonceGeneratorUtil.createGcmFipsNonceGenerator; +import static org.bouncycastle.jsse.provider.GcmTls12NonceGeneratorUtil.isGcmFipsNonceGeneratorFactorySet; + /** * A generic TLS 1.2 AEAD cipher. */ @@ -30,6 +34,8 @@ public final class TlsAEADCipher private static final int NONCE_RFC7905 = 2; private static final long SEQUENCE_NUMBER_PLACEHOLDER = -1L; + private static final byte[] EPOCH_1 = {0x00, 0x01}; + private final TlsCryptoParameters cryptoParams; private final int keySize; private final int macSize; @@ -43,6 +49,7 @@ public final class TlsAEADCipher private final boolean isTLSv13; private final int nonceMode; + private final TlsNonceGenerator gcmFipsNonceGenerator; public TlsAEADCipher(TlsCryptoParameters cryptoParams, TlsAEADCipherImpl encryptCipher, TlsAEADCipherImpl decryptCipher, int keySize, int macSize, int aeadType) throws IOException @@ -91,6 +98,7 @@ public TlsAEADCipher(TlsCryptoParameters cryptoParams, TlsAEADCipherImpl encrypt final boolean isServer = cryptoParams.isServer(); if (isTLSv13) { + gcmFipsNonceGenerator = null; rekeyCipher(securityParameters, decryptCipher, decryptNonce, !isServer); rekeyCipher(securityParameters, encryptCipher, encryptNonce, isServer); return; @@ -121,6 +129,28 @@ public TlsAEADCipher(TlsCryptoParameters cryptoParams, TlsAEADCipherImpl encrypt { throw new TlsFatalAlert(AlertDescription.internal_error); } + + if (AEAD_GCM == aeadType && isGcmFipsNonceGeneratorFactorySet()) + { + final int nonceLength = fixed_iv_length + record_iv_length; + final byte[] baseNonce = Arrays.copyOf(encryptNonce, nonceLength); + final int counterSizeInBits; + if (negotiatedVersion.isDTLS()) + { + counterSizeInBits = (record_iv_length - 2) * 8; // 48 + baseNonce[baseNonce.length - 8] ^= EPOCH_1[0]; + baseNonce[baseNonce.length - 7] ^= EPOCH_1[1]; + } + else + { + counterSizeInBits = record_iv_length * 8; // 64 + } + gcmFipsNonceGenerator = createGcmFipsNonceGenerator(baseNonce, counterSizeInBits); + } + else + { + gcmFipsNonceGenerator = null; + } } public int getCiphertextDecodeLimit(int plaintextLimit) @@ -154,24 +184,33 @@ public int getPlaintextEncodeLimit(int ciphertextLimit) public TlsEncodeResult encodePlaintext(long seqNo, short contentType, ProtocolVersion recordVersion, int headerAllocation, byte[] plaintext, int plaintextOffset, int plaintextLength) throws IOException { - byte[] nonce = new byte[encryptNonce.length + record_iv_length]; + final int nonceSize = encryptNonce.length + record_iv_length; + final byte[] nonce; - switch (nonceMode) + if (null != gcmFipsNonceGenerator) { - case NONCE_RFC5288: - System.arraycopy(encryptNonce, 0, nonce, 0, encryptNonce.length); - // RFC 5288/6655: The nonce_explicit MAY be the 64-bit sequence number. - TlsUtils.writeUint64(seqNo, nonce, encryptNonce.length); - break; - case NONCE_RFC7905: - TlsUtils.writeUint64(seqNo, nonce, nonce.length - 8); - for (int i = 0; i < encryptNonce.length; ++i) + nonce = gcmFipsNonceGenerator.generateNonce(nonceSize); + } + else + { + nonce = new byte[nonceSize]; + switch (nonceMode) { - nonce[i] ^= encryptNonce[i]; + case NONCE_RFC5288: + System.arraycopy(encryptNonce, 0, nonce, 0, encryptNonce.length); + // RFC 5288/6655: The nonce_explicit MAY be the 64-bit sequence number. + TlsUtils.writeUint64(seqNo, nonce, encryptNonce.length); + break; + case NONCE_RFC7905: + TlsUtils.writeUint64(seqNo, nonce, nonce.length - 8); + for (int i = 0; i < encryptNonce.length; ++i) + { + nonce[i] ^= encryptNonce[i]; + } + break; + default: + throw new TlsFatalAlert(AlertDescription.internal_error); } - break; - default: - throw new TlsFatalAlert(AlertDescription.internal_error); } // TODO[tls13, cid] If we support adding padding to (D)TLSInnerPlaintext, this will need review diff --git a/tls/src/test/java/org/bouncycastle/tls/test/AllTests.java b/tls/src/test/java/org/bouncycastle/tls/test/AllTests.java index 64b985fe45..27d9035fdb 100644 --- a/tls/src/test/java/org/bouncycastle/tls/test/AllTests.java +++ b/tls/src/test/java/org/bouncycastle/tls/test/AllTests.java @@ -1,5 +1,6 @@ package org.bouncycastle.tls.test; +import org.bouncycastle.jsse.provider.GcmTls12NonceGeneratorUtil; import org.bouncycastle.test.PrintTestResult; import junit.extensions.TestSetup; @@ -14,6 +15,13 @@ public static void main(String[] args) throws Exception { PrintTestResult.printResult(junit.textui.TestRunner.run(suite())); + PrintTestResult.printResult(junit.textui.TestRunner.run(suiteWithCustomNonceGeneratorForTls12())); + } + + public static Test suiteWithCustomNonceGeneratorForTls12() throws Exception + { + GcmTls12NonceGeneratorUtil.setGcmTlsNonceGeneratorFactory(TestTlsNonceGeneratorFactory.INSTANCE); + return suite(); } public static Test suite() diff --git a/tls/src/test/java/org/bouncycastle/tls/test/TestNonceGenerator.java b/tls/src/test/java/org/bouncycastle/tls/test/TestNonceGenerator.java new file mode 100644 index 0000000000..50bddfa3a5 --- /dev/null +++ b/tls/src/test/java/org/bouncycastle/tls/test/TestNonceGenerator.java @@ -0,0 +1,51 @@ +package org.bouncycastle.tls.test; + +import org.bouncycastle.tls.crypto.TlsNonceGenerator; + +import java.util.Arrays; + +class TestNonceGenerator implements TlsNonceGenerator +{ + private final byte[] baseNonce; + private final long counterMask; + private final int counterBytes; + + private long counterValue; + private boolean counterExhausted; + + TestNonceGenerator(final byte[] baseNonce, final int counterBits) + { + this.baseNonce = Arrays.copyOf(baseNonce, baseNonce.length); + this.counterMask = -1L >>> (64 - counterBits); + this.counterBytes = (counterBits + 7) / 8; + + this.counterValue = 0L; + this.counterExhausted = false; + } + + @Override + public byte[] generateNonce(final int size) + { + if (size != baseNonce.length) + { + throw new IllegalArgumentException("requested length is not equal to the length of the base nonce."); + } + + if (counterExhausted) + { + throw new IllegalStateException("TLS nonce generator exhausted"); + } + + final byte[] nonce = Arrays.copyOf(baseNonce, baseNonce.length); + final int offset = baseNonce.length - counterBytes; + + for (int i = 0; i < counterBytes; i++) + { + nonce[offset + i] ^= (byte)(counterValue >>> ((counterBytes - 1 - i) * 8)); + } + + counterExhausted |= ((++counterValue & counterMask) == 0); + + return nonce; + } +} diff --git a/tls/src/test/java/org/bouncycastle/tls/test/TestTlsNonceGeneratorFactory.java b/tls/src/test/java/org/bouncycastle/tls/test/TestTlsNonceGeneratorFactory.java new file mode 100644 index 0000000000..1be9c760d1 --- /dev/null +++ b/tls/src/test/java/org/bouncycastle/tls/test/TestTlsNonceGeneratorFactory.java @@ -0,0 +1,19 @@ +package org.bouncycastle.tls.test; + +import org.bouncycastle.jsse.provider.TlsNonceGeneratorFactory; +import org.bouncycastle.tls.crypto.TlsNonceGenerator; + +class TestTlsNonceGeneratorFactory implements TlsNonceGeneratorFactory { + public static final TlsNonceGeneratorFactory INSTANCE = new TestTlsNonceGeneratorFactory(); + + private TestTlsNonceGeneratorFactory() + { + // no op + } + + @Override + public TlsNonceGenerator create(final byte[] baseNonce, final int counterSizeInBits) + { + return new TestNonceGenerator(baseNonce, counterSizeInBits); + } +}