summary refs log tree commit diff
diff options
context:
space:
mode:
authorPeter Dettman <peter.dettman@bouncycastle.org>2017-06-09 19:11:59 +0700
committerPeter Dettman <peter.dettman@bouncycastle.org>2017-06-09 19:11:59 +0700
commit56d5b48ee27c091a00e6aee4e6fa196634dea32b (patch)
tree988c94f8237c92fb60175d620286506b38f81820
parentAdd validation to RSA public key constructor (diff)
downloadBouncyCastle.NET-ed25519-56d5b48ee27c091a00e6aee4e6fa196634dea32b.tar.xz
Port of latest encodings work from Java
-rw-r--r--crypto/src/crypto/encodings/OaepEncoding.cs60
-rw-r--r--crypto/src/crypto/encodings/Pkcs1Encoding.cs132
-rw-r--r--crypto/test/src/crypto/test/OAEPTest.cs43
-rw-r--r--crypto/test/src/crypto/test/RSABlindedTest.cs8
-rw-r--r--crypto/test/src/crypto/test/RsaTest.cs93
5 files changed, 211 insertions, 125 deletions
diff --git a/crypto/src/crypto/encodings/OaepEncoding.cs b/crypto/src/crypto/encodings/OaepEncoding.cs
index 9f5c563c2..287876f12 100644
--- a/crypto/src/crypto/encodings/OaepEncoding.cs
+++ b/crypto/src/crypto/encodings/OaepEncoding.cs
@@ -3,6 +3,7 @@ using System;
 using Org.BouncyCastle.Crypto.Digests;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Encodings
 {
@@ -13,7 +14,6 @@ namespace Org.BouncyCastle.Crypto.Encodings
         : IAsymmetricBlockCipher
     {
         private byte[] defHash;
-        private IDigest hash;
         private IDigest mgf1Hash;
 
         private IAsymmetricBlockCipher engine;
@@ -48,10 +48,11 @@ namespace Org.BouncyCastle.Crypto.Encodings
             byte[]					encodingParams)
         {
             this.engine = cipher;
-            this.hash = hash;
             this.mgf1Hash = mgf1Hash;
             this.defHash = new byte[hash.GetDigestSize()];
 
+            hash.Reset();
+
             if (encodingParams != null)
             {
                 hash.BlockUpdate(encodingParams, 0, encodingParams.Length);
@@ -204,28 +205,17 @@ namespace Org.BouncyCastle.Crypto.Encodings
             int		inLen)
         {
             byte[] data = engine.ProcessBlock(inBytes, inOff, inLen);
-            byte[] block;
+            byte[] block = new byte[engine.GetOutputBlockSize()];
 
             //
             // as we may have zeros in our leading bytes for the block we produced
             // on encryption, we need to make sure our decrypted block comes back
             // the same size.
             //
-            if (data.Length < engine.GetOutputBlockSize())
-            {
-                block = new byte[engine.GetOutputBlockSize()];
 
-                Array.Copy(data, 0, block, block.Length - data.Length, data.Length);
-            }
-            else
-            {
-                block = data;
-            }
+            Array.Copy(data, 0, block, block.Length - data.Length, data.Length);
 
-            if (block.Length < (2 * defHash.Length) + 1)
-            {
-                throw new InvalidCipherTextException("data too short");
-            }
+            bool shortData = (block.Length < (2 * defHash.Length) + 1);
 
             //
             // unmask the seed.
@@ -252,36 +242,39 @@ namespace Org.BouncyCastle.Crypto.Encodings
             // check the hash of the encoding params.
             // long check to try to avoid this been a source of a timing attack.
             //
+            bool defHashWrong = false;
+
+            for (int i = 0; i != defHash.Length; i++)
             {
-                int diff = 0;
-                for (int i = 0; i < defHash.Length; ++i)
+                if (defHash[i] != block[defHash.Length + i])
                 {
-                    diff |= (byte)(defHash[i] ^ block[defHash.Length + i]);
+                    defHashWrong = true;
                 }
- 
-                if (diff != 0)
-                    throw new InvalidCipherTextException("data hash wrong");
             }
 
             //
             // find the data block
             //
-            int start;
-            for (start = 2 * defHash.Length; start != block.Length; start++)
+            int start = block.Length;
+
+            for (int index = 2 * defHash.Length; index != block.Length; index++)
             {
-                if (block[start] != 0)
+                if (block[index] != 0 & start == block.Length)
                 {
-                    break;
+                    start = index;
                 }
             }
 
-            if (start > (block.Length - 1) || block[start] != 1)
-            {
-                throw new InvalidCipherTextException("data start wrong " + start);
-            }
+            bool dataStartWrong = (start > (block.Length - 1) | block[start] != 1);
 
             start++;
 
+            if (defHashWrong | shortData | dataStartWrong)
+            {
+                Arrays.Fill(block, 0);
+                throw new InvalidCipherTextException("data wrong");
+            }
+
             //
             // extract the data block
             //
@@ -319,9 +312,9 @@ namespace Org.BouncyCastle.Crypto.Encodings
             byte[] C = new byte[4];
             int counter = 0;
 
-            hash.Reset();
+            mgf1Hash.Reset();
 
-            do
+            while (counter < (length / hashBuf.Length))
             {
                 ItoOSP(counter, C);
 
@@ -330,8 +323,9 @@ namespace Org.BouncyCastle.Crypto.Encodings
                 mgf1Hash.DoFinal(hashBuf, 0);
 
                 Array.Copy(hashBuf, 0, mask, counter * hashBuf.Length, hashBuf.Length);
+
+                counter++;
             }
-            while (++counter < (length / hashBuf.Length));
 
             if ((counter * hashBuf.Length) < length)
             {
diff --git a/crypto/src/crypto/encodings/Pkcs1Encoding.cs b/crypto/src/crypto/encodings/Pkcs1Encoding.cs
index 35fd96abe..b2d60fe4c 100644
--- a/crypto/src/crypto/encodings/Pkcs1Encoding.cs
+++ b/crypto/src/crypto/encodings/Pkcs1Encoding.cs
@@ -45,16 +45,18 @@ namespace Org.BouncyCastle.Crypto.Encodings
         }
 
 
-        private SecureRandom			random;
-        private IAsymmetricBlockCipher	engine;
-        private bool					forEncryption;
-        private bool					forPrivateKey;
-        private bool					useStrictLength;
-        private int                     pLen = -1;
-        private byte[]                  fallback = null;
+        private SecureRandom random;
+        private IAsymmetricBlockCipher engine;
+        private bool forEncryption;
+        private bool forPrivateKey;
+        private bool useStrictLength;
+        private int pLen = -1;
+        private byte[] fallback = null;
+        private byte[] blockBuffer = null;
 
         /**
          * Basic constructor.
+         *
          * @param cipher
          */
         public Pkcs1Encoding(
@@ -104,9 +106,7 @@ namespace Org.BouncyCastle.Crypto.Encodings
             get { return engine.AlgorithmName + "/PKCS1Padding"; }
         }
 
-        public void Init(
-            bool				forEncryption,
-            ICipherParameters	parameters)
+        public void Init(bool forEncryption, ICipherParameters parameters)
         {
             AsymmetricKeyParameter kParam;
             if (parameters is ParametersWithRandom)
@@ -126,6 +126,10 @@ namespace Org.BouncyCastle.Crypto.Encodings
 
             this.forPrivateKey = kParam.IsPrivate;
             this.forEncryption = forEncryption;
+            this.blockBuffer = new byte[engine.GetOutputBlockSize()];
+
+            if (pLen > 0 && fallback == null && random == null)
+                throw new ArgumentException("encoder requires random");
         }
 
         public int GetInputBlockSize()
@@ -255,7 +259,6 @@ namespace Org.BouncyCastle.Crypto.Encodings
          * @param inLen Length of the encrypted block.
          * @param pLen Length of the desired output.
          * @return The plaintext without padding, or a random value if the padding was incorrect.
-         * 
          * @throws InvalidCipherTextException
          */
         private byte[] DecodeBlockOrRandom(byte[] input, int inOff, int inLen)
@@ -264,7 +267,7 @@ namespace Org.BouncyCastle.Crypto.Encodings
                 throw new InvalidCipherTextException("sorry, this method is only for decryption, not for signing");
 
             byte[] block = engine.ProcessBlock(input, inOff, inLen);
-            byte[] random = null;
+            byte[] random;
             if (this.fallback == null)
             {
                 random = new byte[this.pLen];
@@ -275,37 +278,25 @@ namespace Org.BouncyCastle.Crypto.Encodings
                 random = fallback;
             }
 
-            /*
-             * TODO: This is a potential dangerous side channel. However, you can
-             * fix this by changing the RSA engine in a way, that it will always
-             * return blocks of the same length and prepend them with 0 bytes if
-             * needed.
-             */
-            if (block.Length < GetOutputBlockSize())
-                throw new InvalidCipherTextException("block truncated");
+            byte[] data = (useStrictLength & (block.Length != engine.GetOutputBlockSize())) ? blockBuffer : block;
 
-            /*
-             * TODO: Potential side channel. Fix it by making the engine always
-             * return blocks of the correct length.
-             */
-            if (useStrictLength && block.Length != engine.GetOutputBlockSize())
-                throw new InvalidCipherTextException("block incorrect size");
-
-            /*
-             * Check the padding.
-             */
-            int correct = Pkcs1Encoding.CheckPkcs1Encoding(block, this.pLen);
+		    /*
+		     * Check the padding.
+		     */
+            int correct = CheckPkcs1Encoding(data, this.pLen);
 
-            /*
-             * Now, to a constant time constant memory copy of the decrypted value
-             * or the random value, depending on the validity of the padding.
-             */
+		    /*
+		     * Now, to a constant time constant memory copy of the decrypted value
+		     * or the random value, depending on the validity of the padding.
+		     */
             byte[] result = new byte[this.pLen];
             for (int i = 0; i < this.pLen; i++)
             {
-                result[i] = (byte)((block[i+(block.Length-pLen)]&(~correct)) | (random[i]&correct));
+                result[i] = (byte)((data[i + (data.Length - pLen)] & (~correct)) | (random[i] & correct));
             }
 
+            Arrays.Fill(data, 0);
+
             return result;
         }
 
@@ -327,56 +318,67 @@ namespace Org.BouncyCastle.Crypto.Encodings
             }
 
             byte[] block = engine.ProcessBlock(input, inOff, inLen);
+            bool incorrectLength = (useStrictLength & (block.Length != engine.GetOutputBlockSize()));
 
+            byte[] data;
             if (block.Length < GetOutputBlockSize())
             {
-                throw new InvalidCipherTextException("block truncated");
+                data = blockBuffer;
             }
-
-            byte type = block[0];
-
-            if (type != 1 && type != 2)
+            else
             {
-                throw new InvalidCipherTextException("unknown block type");
+                data = block;
             }
 
-            if (useStrictLength && block.Length != engine.GetOutputBlockSize())
-            {
-                throw new InvalidCipherTextException("block incorrect size");
-            }
+            byte expectedType = (byte)(forPrivateKey ? 2 : 1);
+            byte type = data[0];
+
+            bool badType = (type != expectedType);
 
             //
             // find and extract the message block.
             //
-            int start;
-            for (start = 1; start != block.Length; start++)
-            {
-                byte pad = block[start];
+            int start = FindStart(type, data);
 
-                if (pad == 0)
-                {
-                    break;
-                }
+            start++;           // data should start at the next byte
 
-                if (type == 1 && pad != (byte)0xff)
-                {
-                    throw new InvalidCipherTextException("block padding incorrect");
-                }
+            if (badType | (start < HeaderLength))
+            {
+                Arrays.Fill(data, 0);
+                throw new InvalidCipherTextException("block incorrect");
             }
 
-            start++;           // data should start at the next byte
-
-            if (start > block.Length || start < HeaderLength)
+            // if we get this far, it's likely to be a genuine encoding error
+            if (incorrectLength)
             {
-                throw new InvalidCipherTextException("no data in block");
+                Arrays.Fill(data, 0);
+                throw new InvalidCipherTextException("block incorrect size");
             }
 
-            byte[] result = new byte[block.Length - start];
+            byte[] result = new byte[data.Length - start];
 
-            Array.Copy(block, start, result, 0, result.Length);
+            Array.Copy(data, start, result, 0, result.Length);
 
             return result;
         }
-    }
 
+        private int FindStart(byte type, byte[] block)
+        {
+            int start = -1;
+            bool padErr = false;
+
+            for (int i = 1; i != block.Length; i++)
+            {
+                byte pad = block[i];
+
+                if (pad == 0 & start < 0)
+                {
+                    start = i;
+                }
+                padErr |= ((type == 1) & (start < 0) & (pad != (byte)0xff));
+            }
+
+            return padErr ? -1 : start;
+        }
+    }
 }
diff --git a/crypto/test/src/crypto/test/OAEPTest.cs b/crypto/test/src/crypto/test/OAEPTest.cs
index ee48a183d..bc1dd9292 100644
--- a/crypto/test/src/crypto/test/OAEPTest.cs
+++ b/crypto/test/src/crypto/test/OAEPTest.cs
@@ -5,7 +5,7 @@ using NUnit.Framework;
 using Org.BouncyCastle.Asn1;
 using Org.BouncyCastle.Asn1.Pkcs;
 using Org.BouncyCastle.Asn1.X509;
-using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Digests;
 using Org.BouncyCastle.Crypto.Encodings;
 using Org.BouncyCastle.Crypto.Engines;
 using Org.BouncyCastle.Crypto.Parameters;
@@ -779,6 +779,47 @@ namespace Org.BouncyCastle.Crypto.Tests
             OaepVecTest(1027, 4, pubParam, privParam, seed_1027_4, input_1027_4, output_1027_4);
             OaepVecTest(1027, 5, pubParam, privParam, seed_1027_5, input_1027_5, output_1027_5);
             OaepVecTest(1027, 6, pubParam, privParam, seed_1027_6, input_1027_6, output_1027_6);
+
+            //
+            // OAEP - public encrypt, private decrypt  differring hashes
+            //
+            IAsymmetricBlockCipher cipher = new OaepEncoding(new RsaEngine(), new Sha256Digest(), new Sha1Digest(), new byte[10]);
+
+            cipher.Init(true, new ParametersWithRandom(pubParam, new SecureRandom()));
+
+            byte[] input = new byte[10];
+
+            byte[] output = cipher.ProcessBlock(input, 0, input.Length);
+
+            cipher.Init(false, privParam);
+
+            output = cipher.ProcessBlock(output, 0, output.Length);
+
+            for (int i = 0; i != input.Length; i++)
+            {
+                if (output[i] != input[i])
+                {
+                    Fail("mixed digest failed decoding");
+                }
+            }
+
+            cipher = new OaepEncoding(new RsaEngine(), new Sha1Digest(), new Sha256Digest(), new byte[10]);
+
+            cipher.Init(true, new ParametersWithRandom(pubParam, new SecureRandom()));
+
+            output = cipher.ProcessBlock(input, 0, input.Length);
+
+            cipher.Init(false, privParam);
+
+            output = cipher.ProcessBlock(output, 0, output.Length);
+
+            for (int i = 0; i != input.Length; i++)
+            {
+                if (output[i] != input[i])
+                {
+                    Fail("mixed digest failed decoding");
+                }
+            }
         }
 
         public static void Main(
diff --git a/crypto/test/src/crypto/test/RSABlindedTest.cs b/crypto/test/src/crypto/test/RSABlindedTest.cs
index 80d6e8e49..75b9f3a07 100644
--- a/crypto/test/src/crypto/test/RSABlindedTest.cs
+++ b/crypto/test/src/crypto/test/RSABlindedTest.cs
@@ -103,22 +103,22 @@ namespace Org.BouncyCastle.Crypto.Tests
 
 		private void doTestTruncatedPkcs1Block(RsaKeyParameters pubParameters, RsaKeyParameters privParameters)
 		{
-			checkForPkcs1Exception(pubParameters, privParameters, truncatedDataBlock, "block truncated");
+			checkForPkcs1Exception(pubParameters, privParameters, truncatedDataBlock, "block incorrect");
 		}
 
 		private void doTestDudPkcs1Block(RsaKeyParameters pubParameters, RsaKeyParameters privParameters)
 		{
-			checkForPkcs1Exception(pubParameters, privParameters, dudBlock, "unknown block type");
+			checkForPkcs1Exception(pubParameters, privParameters, dudBlock, "block incorrect");
 		}
 
 		private void doTestWrongPaddingPkcs1Block(RsaKeyParameters pubParameters, RsaKeyParameters privParameters)
 		{
-			checkForPkcs1Exception(pubParameters, privParameters, incorrectPadding, "block padding incorrect");
+			checkForPkcs1Exception(pubParameters, privParameters, incorrectPadding, "block incorrect");
 		}
 
 		private void doTestMissingDataPkcs1Block(RsaKeyParameters pubParameters, RsaKeyParameters privParameters)
 		{
-			checkForPkcs1Exception(pubParameters, privParameters, missingDataBlock, "no data in block");
+			checkForPkcs1Exception(pubParameters, privParameters, missingDataBlock, "block incorrect");
 		}
 
 		private void checkForPkcs1Exception(RsaKeyParameters pubParameters, RsaKeyParameters privParameters, byte[] inputData, string expectedMessage)
diff --git a/crypto/test/src/crypto/test/RsaTest.cs b/crypto/test/src/crypto/test/RsaTest.cs
index e3fc18d02..e9f30cae9 100644
--- a/crypto/test/src/crypto/test/RsaTest.cs
+++ b/crypto/test/src/crypto/test/RsaTest.cs
@@ -54,10 +54,11 @@ namespace Org.BouncyCastle.Crypto.Tests
 			eng.Init(true, privParameters);
 
 			byte[] data = null;
+            byte[] overSized = null;
 
-			try
-			{
-				data = eng.ProcessBlock(oversizedSig, 0, oversizedSig.Length);
+            try
+            {
+				overSized = data = eng.ProcessBlock(oversizedSig, 0, oversizedSig.Length);
 			}
 			catch (Exception e)
 			{
@@ -70,7 +71,7 @@ namespace Org.BouncyCastle.Crypto.Tests
 
 			try
 			{
-				data = eng.ProcessBlock(data, 0, data.Length);
+				data = eng.ProcessBlock(overSized, 0, overSized.Length);
 
 				Fail("oversized signature block not recognised");
 			}
@@ -82,9 +83,22 @@ namespace Org.BouncyCastle.Crypto.Tests
 				}
 			}
 
+            eng = new Pkcs1Encoding(new RsaEngine(), Hex.Decode("feedbeeffeedbeeffeedbeef"));
+            eng.Init(false, new ParametersWithRandom(privParameters, new SecureRandom()));
+
+            try
+            {
+                data = eng.ProcessBlock(overSized, 0, overSized.Length);
+                IsTrue("not fallback", Arrays.AreEqual(Hex.Decode("feedbeeffeedbeeffeedbeef"), data));
+            }
+            catch (InvalidCipherTextException e)
+            {
+                Fail("RSA: failed - exception " + e.ToString(), e);
+            }
 
-			// Create the encoding with StrictLengthEnabled=false (done thru environment in Java version)
-			Pkcs1Encoding.StrictLengthEnabled = false;
+
+            // Create the encoding with StrictLengthEnabled=false (done thru environment in Java version)
+            Pkcs1Encoding.StrictLengthEnabled = false;
 
 			eng = new Pkcs1Encoding(new RsaEngine());
 
@@ -92,7 +106,7 @@ namespace Org.BouncyCastle.Crypto.Tests
 
 			try
 			{
-				data = eng.ProcessBlock(data, 0, data.Length);
+				data = eng.ProcessBlock(overSized, 0, overSized.Length);
 			}
 			catch (InvalidCipherTextException e)
 			{
@@ -104,22 +118,22 @@ namespace Org.BouncyCastle.Crypto.Tests
 
 		private void doTestTruncatedPkcs1Block(RsaKeyParameters pubParameters, RsaKeyParameters privParameters)
 		{
-			checkForPkcs1Exception(pubParameters, privParameters, truncatedDataBlock, "block truncated");
+			checkForPkcs1Exception(pubParameters, privParameters, truncatedDataBlock, "block incorrect");
 		}
 
 		private void doTestDudPkcs1Block(RsaKeyParameters pubParameters, RsaKeyParameters privParameters)
 		{
-			checkForPkcs1Exception(pubParameters, privParameters, dudBlock, "unknown block type");
+			checkForPkcs1Exception(pubParameters, privParameters, dudBlock, "block incorrect");
 		}
 
 		private void doTestWrongPaddingPkcs1Block(RsaKeyParameters pubParameters, RsaKeyParameters privParameters)
 		{
-			checkForPkcs1Exception(pubParameters, privParameters, incorrectPadding, "block padding incorrect");
+			checkForPkcs1Exception(pubParameters, privParameters, incorrectPadding, "block incorrect");
 		}
 
 		private void doTestMissingDataPkcs1Block(RsaKeyParameters pubParameters, RsaKeyParameters privParameters)
 		{
-			checkForPkcs1Exception(pubParameters, privParameters, missingDataBlock, "no data in block");
+			checkForPkcs1Exception(pubParameters, privParameters, missingDataBlock, "block incorrect");
 		}
 
 		private void checkForPkcs1Exception(RsaKeyParameters pubParameters, RsaKeyParameters privParameters, byte[] inputData, string expectedMessage)
@@ -431,30 +445,65 @@ namespace Org.BouncyCastle.Crypto.Tests
 
 			eng.Init(false, privParameters);
 
-			try
-			{
-				data = eng.ProcessBlock(data, 0, data.Length);
+            byte[] plainData = null;
+            try
+            {
+				plainData = eng.ProcessBlock(data, 0, data.Length);
 			}
 			catch (Exception e)
 			{
 				Fail("failed - exception " + e.ToString());
 			}
 
-			if (!input.Equals(Hex.ToHexString(data)))
-			{
-				Fail("failed PKCS1 public/private Test");
+            if (!input.Equals(Hex.ToHexString(plainData)))
+            {
+                Fail("failed PKCS1 public/private Test");
 			}
 
-			//
-			// PKCS1 - private encrypt, public decrypt
-			//
-			eng = new Pkcs1Encoding(((Pkcs1Encoding)eng).GetUnderlyingCipher());
+            Pkcs1Encoding fEng = new Pkcs1Encoding(new RsaEngine(), input.Length / 2);
+            fEng.Init(false, new ParametersWithRandom(privParameters, new SecureRandom()));
+            try
+            {
+                plainData = fEng.ProcessBlock(data, 0, data.Length);
+            }
+            catch (Exception e)
+            {
+                Fail("failed - exception " + e.ToString(), e);
+            }
+
+            if (!input.Equals(Hex.ToHexString(plainData)))
+            {
+                Fail("failed PKCS1 public/private fixed Test");
+            }
+
+            fEng = new Pkcs1Encoding(new RsaEngine(), input.Length);
+            fEng.Init(false, new ParametersWithRandom(privParameters, new SecureRandom()));
+            try
+            {
+                data = fEng.ProcessBlock(data, 0, data.Length);
+            }
+            catch (Exception e)
+            {
+                Fail("failed - exception " + e.ToString(), e);
+            }
+
+            if (input.Equals(Hex.ToHexString(data)))
+            {
+                Fail("failed to recognise incorrect plaint text length");
+            }
+
+            data = plainData;
+
+            //
+            // PKCS1 - private encrypt, public decrypt
+            //
+            eng = new Pkcs1Encoding(((Pkcs1Encoding)eng).GetUnderlyingCipher());
 
 			eng.Init(true, privParameters);
 
 			try
 			{
-				data = eng.ProcessBlock(data, 0, data.Length);
+				data = eng.ProcessBlock(plainData, 0, plainData.Length);
 			}
 			catch (Exception e)
 			{