summary refs log tree commit diff
diff options
context:
space:
mode:
authorPeter Dettman <peter.dettman@bouncycastle.org>2017-06-10 19:37:28 +0700
committerPeter Dettman <peter.dettman@bouncycastle.org>2017-06-10 19:37:28 +0700
commita7031a60eb1ea3859f012bc34c0bae7cbcd7de54 (patch)
tree617869903ca5edb1a1a18054fd14c9a076a09494
parentAdded expired certificates on CRL extension (diff)
downloadBouncyCastle.NET-ed25519-a7031a60eb1ea3859f012bc34c0bae7cbcd7de54.tar.xz
Update GCMBlockCipher from Java API
- includes basic nonce-reuse protections
-rw-r--r--crypto/src/crypto/modes/GCMBlockCipher.cs70
-rw-r--r--crypto/test/src/crypto/test/GCMTest.cs50
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(