diff options
author | Peter Dettman <peter.dettman@bouncycastle.org> | 2017-06-10 19:37:28 +0700 |
---|---|---|
committer | Peter Dettman <peter.dettman@bouncycastle.org> | 2017-06-10 19:37:28 +0700 |
commit | a7031a60eb1ea3859f012bc34c0bae7cbcd7de54 (patch) | |
tree | 617869903ca5edb1a1a18054fd14c9a076a09494 | |
parent | Added expired certificates on CRL extension (diff) | |
download | BouncyCastle.NET-ed25519-a7031a60eb1ea3859f012bc34c0bae7cbcd7de54.tar.xz |
Update GCMBlockCipher from Java API
- includes basic nonce-reuse protections
-rw-r--r-- | crypto/src/crypto/modes/GCMBlockCipher.cs | 70 | ||||
-rw-r--r-- | crypto/test/src/crypto/test/GCMTest.cs | 50 |
2 files changed, 106 insertions, 14 deletions
diff --git a/crypto/src/crypto/modes/GCMBlockCipher.cs b/crypto/src/crypto/modes/GCMBlockCipher.cs index 9d940fe75..a6cd00401 100644 --- a/crypto/src/crypto/modes/GCMBlockCipher.cs +++ b/crypto/src/crypto/modes/GCMBlockCipher.cs @@ -23,7 +23,9 @@ namespace Org.BouncyCastle.Crypto.Modes // These fields are set by Init and not modified by processing private bool forEncryption; + private bool initialised; private int macSize; + private byte[] lastKey; private byte[] nonce; private byte[] initialAssociatedText; private byte[] H; @@ -90,14 +92,16 @@ namespace Org.BouncyCastle.Crypto.Modes { this.forEncryption = forEncryption; this.macBlock = null; + this.initialised = true; KeyParameter keyParam; + byte[] newNonce = null; if (parameters is AeadParameters) { AeadParameters param = (AeadParameters)parameters; - nonce = param.GetNonce(); + newNonce = param.GetNonce(); initialAssociatedText = param.GetAssociatedText(); int macSizeBits = param.MacSize; @@ -113,7 +117,7 @@ namespace Org.BouncyCastle.Crypto.Modes { ParametersWithIV param = (ParametersWithIV)parameters; - nonce = param.GetIV(); + newNonce = param.GetIV(); initialAssociatedText = null; macSize = 16; keyParam = (KeyParameter)param.Parameters; @@ -126,11 +130,32 @@ namespace Org.BouncyCastle.Crypto.Modes int bufLength = forEncryption ? BlockSize : (BlockSize + macSize); this.bufBlock = new byte[bufLength]; - if (nonce == null || nonce.Length < 1) + if (newNonce == null || newNonce.Length < 1) { throw new ArgumentException("IV must be at least 1 byte"); } + if (forEncryption) + { + if (nonce != null && Arrays.AreEqual(nonce, newNonce)) + { + if (keyParam == null) + { + throw new ArgumentException("cannot reuse nonce for GCM encryption"); + } + if (lastKey != null && Arrays.AreEqual(lastKey, keyParam.GetKey())) + { + throw new ArgumentException("cannot reuse nonce for GCM encryption"); + } + } + } + + nonce = newNonce; + if (keyParam != null) + { + lastKey = keyParam.GetKey(); + } + // TODO Restrict macSize to 16 if nonce length not 12? // Cipher always used in forward mode @@ -186,7 +211,9 @@ namespace Org.BouncyCastle.Crypto.Modes public virtual byte[] GetMac() { - return Arrays.Clone(macBlock); + return macBlock == null + ? new byte[macSize] + : Arrays.Clone(macBlock); } public virtual int GetOutputSize( @@ -219,6 +246,8 @@ namespace Org.BouncyCastle.Crypto.Modes public virtual void ProcessAadByte(byte input) { + CheckStatus(); + atBlock[atBlockPos] = input; if (++atBlockPos == BlockSize) { @@ -231,6 +260,8 @@ namespace Org.BouncyCastle.Crypto.Modes public virtual void ProcessAadBytes(byte[] inBytes, int inOff, int len) { + CheckStatus(); + for (int i = 0; i < len; ++i) { atBlock[atBlockPos] = inBytes[inOff + i]; @@ -270,6 +301,8 @@ namespace Org.BouncyCastle.Crypto.Modes byte[] output, int outOff) { + CheckStatus(); + bufBlock[bufOff] = input; if (++bufOff == bufBlock.Length) { @@ -286,6 +319,8 @@ namespace Org.BouncyCastle.Crypto.Modes byte[] output, int outOff) { + CheckStatus(); + if (input.Length < (inOff + len)) throw new DataLengthException("Input buffer too short"); @@ -325,6 +360,8 @@ namespace Org.BouncyCastle.Crypto.Modes public int DoFinal(byte[] output, int outOff) { + CheckStatus(); + if (totalLength == 0) { InitCipher(); @@ -441,6 +478,8 @@ namespace Org.BouncyCastle.Crypto.Modes { cipher.Reset(); + // note: we do not reset the nonce. + S = new byte[BlockSize]; S_at = new byte[BlockSize]; S_atPre = new byte[BlockSize]; @@ -463,9 +502,16 @@ namespace Org.BouncyCastle.Crypto.Modes macBlock = null; } - if (initialAssociatedText != null) + if (forEncryption) { - ProcessAadBytes(initialAssociatedText, 0, initialAssociatedText.Length); + initialised = false; + } + else + { + if (initialAssociatedText != null) + { + ProcessAadBytes(initialAssociatedText, 0, initialAssociatedText.Length); + } } } @@ -532,5 +578,17 @@ namespace Org.BouncyCastle.Crypto.Modes cipher.ProcessBlock(counter, 0, tmp, 0); return tmp; } + + private void CheckStatus() + { + if (!initialised) + { + if (forEncryption) + { + throw new InvalidOperationException("GCM cipher cannot be reused for encryption"); + } + throw new InvalidOperationException("GCM cipher needs to be initialised"); + } + } } } diff --git a/crypto/test/src/crypto/test/GCMTest.cs b/crypto/test/src/crypto/test/GCMTest.cs index 3f7418fb2..e5e5fc43e 100644 --- a/crypto/test/src/crypto/test/GCMTest.cs +++ b/crypto/test/src/crypto/test/GCMTest.cs @@ -357,12 +357,38 @@ namespace Org.BouncyCastle.Crypto.Tests } // TODO - //AEADTestUtil.testReset(this, new GCMBlockCipher(createAESEngine()), new GCMBlockCipher(createAESEngine()), new AEADParameters(new KeyParameter(new byte[16]), 128, new byte[16])); //AEADTestUtil.testTampering(this, gcm, new AEADParameters(new KeyParameter(new byte[16]), 128, new byte[16])); - //AEADTestUtil.testOutputSizes(this, new GCMBlockCipher(createAESEngine()), new AEADParameters(new KeyParameter( - // new byte[16]), 128, new byte[16])); - //AEADTestUtil.testBufferSizeChecks(this, new GCMBlockCipher(createAESEngine()), new AEADParameters( - // new KeyParameter(new byte[16]), 128, new byte[16])); + + //byte[] P = Strings.toByteArray("Hello world!"); + //byte[] buf = new byte[100]; + + //GCMBlockCipher c = new GCMBlockCipher(createAESEngine()); + //AEADParameters aeadParameters = new AEADParameters(new KeyParameter(new byte[16]), 128, new byte[16]); + //c.init(true, aeadParameters); + + //c.processBytes(P, 0, P.length, buf, 0); + + //c.doFinal(buf, 0); + + //try + //{ + // c.doFinal(buf, 0); + // fail("no exception on reuse"); + //} + //catch (IllegalStateException e) + //{ + // isTrue("wrong message", e.getMessage().equals("GCM cipher cannot be reused for encryption")); + //} + + //try + //{ + // c.init(true, aeadParameters); + // fail("no exception on reuse"); + //} + //catch (IllegalArgumentException e) + //{ + // isTrue("wrong message", e.getMessage().equals("cannot reuse nonce for GCM encryption")); + //} } private void RunTestCase(string[] testVector) @@ -433,13 +459,21 @@ namespace Org.BouncyCastle.Crypto.Tests GcmBlockCipher encCipher = InitCipher(encM, true, parameters); GcmBlockCipher decCipher = InitCipher(decM, false, parameters); CheckTestCase(encCipher, decCipher, testName, SA, P, C, T); + encCipher = InitCipher(encM, true, parameters); CheckTestCase(encCipher, decCipher, testName + " (reused)", SA, P, C, T); // Key reuse AeadParameters keyReuseParams = AeadTestUtilities.ReuseKey(parameters); - encCipher.Init(true, keyReuseParams); - decCipher.Init(false, keyReuseParams); - CheckTestCase(encCipher, decCipher, testName + " (key reuse)", SA, P, C, T); + + try + { + encCipher.Init(true, keyReuseParams); + Fail("no exception"); + } + catch (ArgumentException e) + { + IsTrue("wrong message", "cannot reuse nonce for GCM encryption".Equals(e.Message)); + } } private GcmBlockCipher InitCipher( |