diff options
author | Peter Dettman <peter.dettman@bouncycastle.org> | 2017-06-09 19:11:59 +0700 |
---|---|---|
committer | Peter Dettman <peter.dettman@bouncycastle.org> | 2017-06-09 19:11:59 +0700 |
commit | 56d5b48ee27c091a00e6aee4e6fa196634dea32b (patch) | |
tree | 988c94f8237c92fb60175d620286506b38f81820 /crypto | |
parent | Add validation to RSA public key constructor (diff) | |
download | BouncyCastle.NET-ed25519-56d5b48ee27c091a00e6aee4e6fa196634dea32b.tar.xz |
Port of latest encodings work from Java
Diffstat (limited to 'crypto')
-rw-r--r-- | crypto/src/crypto/encodings/OaepEncoding.cs | 60 | ||||
-rw-r--r-- | crypto/src/crypto/encodings/Pkcs1Encoding.cs | 132 | ||||
-rw-r--r-- | crypto/test/src/crypto/test/OAEPTest.cs | 43 | ||||
-rw-r--r-- | crypto/test/src/crypto/test/RSABlindedTest.cs | 8 | ||||
-rw-r--r-- | crypto/test/src/crypto/test/RsaTest.cs | 93 |
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) { |