diff --git a/src/Microsoft.AspNet.Security.DataProtection/Managed/ManagedAuthenticatedEncryptor.cs b/src/Microsoft.AspNet.Security.DataProtection/Managed/ManagedAuthenticatedEncryptor.cs index 8965539a29..6d43753665 100644 --- a/src/Microsoft.AspNet.Security.DataProtection/Managed/ManagedAuthenticatedEncryptor.cs +++ b/src/Microsoft.AspNet.Security.DataProtection/Managed/ManagedAuthenticatedEncryptor.cs @@ -49,7 +49,7 @@ namespace Microsoft.AspNet.Security.DataProtection.Managed CryptoUtil.Assert(KEY_MODIFIER_SIZE_IN_BYTES <= symmetricAlgorithmKeySizeInBytes && symmetricAlgorithmKeySizeInBytes <= Constants.MAX_STACKALLOC_BYTES, "KEY_MODIFIER_SIZE_IN_BYTES <= symmetricAlgorithmKeySizeInBytes && symmetricAlgorithmKeySizeInBytes <= Constants.MAX_STACKALLOC_BYTES"); - _genRandom = _genRandom ?? ManagedGenRandomImpl.Instance; + _genRandom = genRandom ?? ManagedGenRandomImpl.Instance; _keyDerivationKey = keyDerivationKey; // Validate that the symmetric algorithm has the properties we require @@ -302,7 +302,7 @@ namespace Microsoft.AspNet.Security.DataProtection.Managed // Step 1: Generate a random key modifier and IV for this operation. // Both will be equal to the block size of the block cipher algorithm. - byte[] keyModifier = _genRandom.GenRandom(_symmetricAlgorithmSubkeyLengthInBytes); + byte[] keyModifier = _genRandom.GenRandom(KEY_MODIFIER_SIZE_IN_BYTES); byte[] iv = _genRandom.GenRandom(_symmetricAlgorithmBlockSizeInBytes); // Step 2: Copy the key modifier and the IV to the output stream since they'll act as a header. diff --git a/test/Microsoft.AspNet.Security.DataProtection.Test/Managed/ManagedAuthenticatedEncryptorTests.cs b/test/Microsoft.AspNet.Security.DataProtection.Test/Managed/ManagedAuthenticatedEncryptorTests.cs new file mode 100644 index 0000000000..bed597f852 --- /dev/null +++ b/test/Microsoft.AspNet.Security.DataProtection.Test/Managed/ManagedAuthenticatedEncryptorTests.cs @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using Microsoft.AspNet.Security.DataProtection.Managed; +using Xunit; + +namespace Microsoft.AspNet.Security.DataProtection.Test.Managed +{ + public class ManagedAuthenticatedEncryptorTests + { + [Fact] + public void Encrypt_Decrypt_RoundTrips() + { + // Arrange + ProtectedMemoryBlob kdk = new ProtectedMemoryBlob(new byte[512 / 8]); + ManagedAuthenticatedEncryptor encryptor = new ManagedAuthenticatedEncryptor(kdk, + symmetricAlgorithmFactory: Aes.Create, + symmetricAlgorithmKeySizeInBytes: 256 / 8, + validationAlgorithmFactory: () => new HMACSHA256()); + ArraySegment plaintext = new ArraySegment(Encoding.UTF8.GetBytes("plaintext")); + ArraySegment aad = new ArraySegment(Encoding.UTF8.GetBytes("aad")); + + // Act + byte[] ciphertext = encryptor.Encrypt(plaintext, aad); + byte[] decipheredtext = encryptor.Decrypt(new ArraySegment(ciphertext), aad); + + // Assert + Assert.Equal(plaintext, decipheredtext); + } + + [Fact] + public void Encrypt_Decrypt_Tampering_Fails() + { + // Arrange + ProtectedMemoryBlob kdk = new ProtectedMemoryBlob(new byte[512 / 8]); + ManagedAuthenticatedEncryptor encryptor = new ManagedAuthenticatedEncryptor(kdk, + symmetricAlgorithmFactory: Aes.Create, + symmetricAlgorithmKeySizeInBytes: 256 / 8, + validationAlgorithmFactory: () => new HMACSHA256()); + ArraySegment plaintext = new ArraySegment(Encoding.UTF8.GetBytes("plaintext")); + ArraySegment aad = new ArraySegment(Encoding.UTF8.GetBytes("aad")); + byte[] validCiphertext = encryptor.Encrypt(plaintext, aad); + + // Act & assert - 1 + // Ciphertext is too short to be a valid payload + byte[] invalidCiphertext_tooShort = new byte[10]; + Assert.Throws(() => + { + encryptor.Decrypt(new ArraySegment(invalidCiphertext_tooShort), aad); + }); + + // Act & assert - 2 + // Ciphertext has been manipulated + byte[] invalidCiphertext_manipulated = (byte[])validCiphertext.Clone(); + invalidCiphertext_manipulated[0] ^= 0x01; + Assert.Throws(() => + { + encryptor.Decrypt(new ArraySegment(invalidCiphertext_manipulated), aad); + }); + + // Act & assert - 3 + // Ciphertext is too long + byte[] invalidCiphertext_tooLong = validCiphertext.Concat(new byte[] { 0 }).ToArray(); + Assert.Throws(() => + { + encryptor.Decrypt(new ArraySegment(invalidCiphertext_tooLong), aad); + }); + + // Act & assert - 4 + // AAD is incorrect + Assert.Throws(() => + { + encryptor.Decrypt(new ArraySegment(validCiphertext), new ArraySegment(Encoding.UTF8.GetBytes("different aad"))); + }); + } + + [Fact] + public void Encrypt_KnownKey() + { + // Arrange + ProtectedMemoryBlob kdk = new ProtectedMemoryBlob(Encoding.UTF8.GetBytes("master key")); + ManagedAuthenticatedEncryptor encryptor = new ManagedAuthenticatedEncryptor(kdk, + symmetricAlgorithmFactory: Aes.Create, + symmetricAlgorithmKeySizeInBytes: 256 / 8, + validationAlgorithmFactory: () => new HMACSHA256(), + genRandom: new SequentialGenRandom()); + ArraySegment plaintext = new ArraySegment(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 }, 2, 3); + ArraySegment aad = new ArraySegment(new byte[] { 7, 6, 5, 4, 3, 2, 1, 0 }, 1, 4); + + // Act + byte[] retVal = encryptor.Encrypt( + plaintext: plaintext, + additionalAuthenticatedData: aad); + + // Assert + + // retVal := 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F (keyModifier) + // | 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F (IV) + // | B7 EA 3E 32 58 93 A3 06 03 89 C6 66 03 63 08 4B (encryptedData) + // | 9D 8A 85 C7 0F BD 98 D8 7F 72 E7 72 3E B5 A6 26 (HMAC) + // | 6C 38 77 F7 66 19 A2 C9 2C BB AD DA E7 62 00 00 + + string retValAsString = Convert.ToBase64String(retVal); + Assert.Equal("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh+36j4yWJOjBgOJxmYDYwhLnYqFxw+9mNh/cudyPrWmJmw4d/dmGaLJLLut2udiAAA=", retValAsString); + } + } +} diff --git a/test/Microsoft.AspNet.Security.DataProtection.Test/Cng/SequentialGenRandom.cs b/test/Microsoft.AspNet.Security.DataProtection.Test/SequentialGenRandom.cs similarity index 51% rename from test/Microsoft.AspNet.Security.DataProtection.Test/Cng/SequentialGenRandom.cs rename to test/Microsoft.AspNet.Security.DataProtection.Test/SequentialGenRandom.cs index f995199dbb..59dcb6e9cb 100644 --- a/test/Microsoft.AspNet.Security.DataProtection.Test/Cng/SequentialGenRandom.cs +++ b/test/Microsoft.AspNet.Security.DataProtection.Test/SequentialGenRandom.cs @@ -3,16 +3,29 @@ using System; using Microsoft.AspNet.Security.DataProtection.Cng; +using Microsoft.AspNet.Security.DataProtection.Managed; -namespace Microsoft.AspNet.Security.DataProtection.Test.Cng +namespace Microsoft.AspNet.Security.DataProtection.Test { - internal unsafe class SequentialGenRandom : IBCryptGenRandom + internal unsafe class SequentialGenRandom : IBCryptGenRandom, IManagedGenRandom { + private byte _value; + + public byte[] GenRandom(int numBytes) + { + byte[] bytes = new byte[numBytes]; + for (int i = 0; i < bytes.Length; i++) + { + bytes[i] = _value++; + } + return bytes; + } + public void GenRandom(byte* pbBuffer, uint cbBuffer) { for (uint i = 0; i < cbBuffer; i++) { - pbBuffer[i] = (byte)i; + pbBuffer[i] = _value++; } } }