diff --git a/src/Microsoft.AspNet.Security.DataProtection/Managed/ManagedAuthenticatedEncryptor.cs b/src/Microsoft.AspNet.Security.DataProtection/Managed/ManagedAuthenticatedEncryptor.cs index eedc30c5c5..8965539a29 100644 --- a/src/Microsoft.AspNet.Security.DataProtection/Managed/ManagedAuthenticatedEncryptor.cs +++ b/src/Microsoft.AspNet.Security.DataProtection/Managed/ManagedAuthenticatedEncryptor.cs @@ -219,7 +219,7 @@ namespace Microsoft.AspNet.Security.DataProtection.Managed try { _keyDerivationKey.WriteSecretIntoBuffer(new ArraySegment(decryptedKdk)); - DeriveKeysWithContextHeader( + ManagedSP800_108_CTR_HMACSHA512.DeriveKeysWithContextHeader( kdk: decryptedKdk, label: additionalAuthenticatedData, contextHeader: _contextHeader, @@ -285,14 +285,6 @@ namespace Microsoft.AspNet.Security.DataProtection.Managed } } - private static void DeriveKeysWithContextHeader(byte[] kdk, ArraySegment label, byte[] contextHeader, ArraySegment context, Func prfFactory, ArraySegment output) - { - byte[] combinedContext = new byte[checked(contextHeader.Length + context.Count)]; - Buffer.BlockCopy(contextHeader, 0, combinedContext, 0, contextHeader.Length); - Buffer.BlockCopy(context.Array, context.Offset, combinedContext, contextHeader.Length, context.Count); - ManagedSP800_108_CTR_HMACSHA512.DeriveKeys(kdk, label, new ArraySegment(combinedContext), prfFactory, output); - } - public void Dispose() { _keyDerivationKey.Dispose(); @@ -336,7 +328,7 @@ namespace Microsoft.AspNet.Security.DataProtection.Managed try { _keyDerivationKey.WriteSecretIntoBuffer(new ArraySegment(decryptedKdk)); - DeriveKeysWithContextHeader( + ManagedSP800_108_CTR_HMACSHA512.DeriveKeysWithContextHeader( kdk: decryptedKdk, label: additionalAuthenticatedData, contextHeader: _contextHeader, diff --git a/src/Microsoft.AspNet.Security.DataProtection/SP800_108/ManagedSP800_108_CTR_HMACSHA512.cs b/src/Microsoft.AspNet.Security.DataProtection/SP800_108/ManagedSP800_108_CTR_HMACSHA512.cs index 38dbe359e1..1ffa2e21f8 100644 --- a/src/Microsoft.AspNet.Security.DataProtection/SP800_108/ManagedSP800_108_CTR_HMACSHA512.cs +++ b/src/Microsoft.AspNet.Security.DataProtection/SP800_108/ManagedSP800_108_CTR_HMACSHA512.cs @@ -53,5 +53,13 @@ namespace Microsoft.AspNet.Security.DataProtection.SP800_108 } } } + + public static void DeriveKeysWithContextHeader(byte[] kdk, ArraySegment label, byte[] contextHeader, ArraySegment context, Func prfFactory, ArraySegment output) + { + byte[] combinedContext = new byte[checked(contextHeader.Length + context.Count)]; + Buffer.BlockCopy(contextHeader, 0, combinedContext, 0, contextHeader.Length); + Buffer.BlockCopy(context.Array, context.Offset, combinedContext, contextHeader.Length, context.Count); + DeriveKeys(kdk, label, new ArraySegment(combinedContext), prfFactory, output); + } } } diff --git a/src/Microsoft.AspNet.Security.DataProtection/SP800_108/SP800_108_CTR_HMACSHA512Util.cs b/src/Microsoft.AspNet.Security.DataProtection/SP800_108/SP800_108_CTR_HMACSHA512Util.cs index e87017a8f1..1f6787eb84 100644 --- a/src/Microsoft.AspNet.Security.DataProtection/SP800_108/SP800_108_CTR_HMACSHA512Util.cs +++ b/src/Microsoft.AspNet.Security.DataProtection/SP800_108/SP800_108_CTR_HMACSHA512Util.cs @@ -2,8 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using Microsoft.AspNet.Security.DataProtection.SafeHandles; -using Microsoft.Win32.SafeHandles; +using Microsoft.AspNet.Security.DataProtection.Cng; namespace Microsoft.AspNet.Security.DataProtection.SP800_108 { @@ -16,8 +15,6 @@ namespace Microsoft.AspNet.Security.DataProtection.SP800_108 /// internal unsafe static class SP800_108_CTR_HMACSHA512Util { - private static readonly bool _isWin8OrLater = GetIsRunningWin8OrLater(); - // Creates a provider with an empty key. public static ISP800_108_CTR_HMACSHA512Provider CreateEmptyProvider() { @@ -28,9 +25,14 @@ namespace Microsoft.AspNet.Security.DataProtection.SP800_108 // Creates a provider from the given key. public static ISP800_108_CTR_HMACSHA512Provider CreateProvider(byte* pbKdk, uint cbKdk) { - return (_isWin8OrLater) - ? (ISP800_108_CTR_HMACSHA512Provider)new Win8SP800_108_CTR_HMACSHA512Provider(pbKdk, cbKdk) - : (ISP800_108_CTR_HMACSHA512Provider)new Win7SP800_108_CTR_HMACSHA512Provider(pbKdk, cbKdk); + if (OSVersionUtil.IsBCryptOnWin8OrLaterAvailable()) + { + return new Win7SP800_108_CTR_HMACSHA512Provider(pbKdk, cbKdk); + } + else + { + return new Win8SP800_108_CTR_HMACSHA512Provider(pbKdk, cbKdk); + } } // Creates a provider from the given secret. @@ -57,37 +59,5 @@ namespace Microsoft.AspNet.Security.DataProtection.SP800_108 } } } - - private static bool GetIsRunningWin8OrLater() - { - // In priority order, our three implementations are Win8, Win7, and "other". - - const string BCRYPT_LIB = "bcrypt.dll"; - - SafeLibraryHandle bcryptLibHandle = null; - try - { - bcryptLibHandle = SafeLibraryHandle.Open(BCRYPT_LIB); - } - catch - { - // BCrypt not available? We'll fall back to managed code paths. - } - - if (bcryptLibHandle != null) - { - using (bcryptLibHandle) - { - if (bcryptLibHandle.DoesProcExist("BCryptKeyDerivation")) - { - // We're running on Win8+. - return true; - } - } - } - - // Not running on Win8+ - return false; - } } } diff --git a/src/Microsoft.AspNet.Security.DataProtection/SP800_108/Win8SP800_108_CTR_HMACSHA512Provider.cs b/src/Microsoft.AspNet.Security.DataProtection/SP800_108/Win8SP800_108_CTR_HMACSHA512Provider.cs index 2aa5d58b6b..30af954f1e 100644 --- a/src/Microsoft.AspNet.Security.DataProtection/SP800_108/Win8SP800_108_CTR_HMACSHA512Provider.cs +++ b/src/Microsoft.AspNet.Security.DataProtection/SP800_108/Win8SP800_108_CTR_HMACSHA512Provider.cs @@ -90,7 +90,7 @@ namespace Microsoft.AspNet.Security.DataProtection.SP800_108 { hashHandle.HashData(pbKdk, cbKdk, pbHashedKey, SHA512_DIGEST_SIZE_IN_BYTES); } - return CachedAlgorithmHandles.SP800_108_CTR_HMAC.GenerateSymmetricKey(pbKdk, cbKdk); + return CachedAlgorithmHandles.SP800_108_CTR_HMAC.GenerateSymmetricKey(pbHashedKey, SHA512_DIGEST_SIZE_IN_BYTES); } finally { diff --git a/src/Microsoft.AspNet.Security.DataProtection/SafeHandles/BCryptAlgorithmHandle.cs b/src/Microsoft.AspNet.Security.DataProtection/SafeHandles/BCryptAlgorithmHandle.cs index 2b72ae08d9..f8fe267f88 100644 --- a/src/Microsoft.AspNet.Security.DataProtection/SafeHandles/BCryptAlgorithmHandle.cs +++ b/src/Microsoft.AspNet.Security.DataProtection/SafeHandles/BCryptAlgorithmHandle.cs @@ -38,8 +38,6 @@ namespace Microsoft.AspNet.Security.DataProtection.SafeHandles public BCryptHashHandle CreateHmac(byte* pbKey, uint cbKey) { Debug.Assert(pbKey != null); - Debug.Assert(cbKey != 0); - return CreateHashImpl(pbKey, cbKey); } diff --git a/test/Microsoft.AspNet.Security.DataProtection.Test/Cng/CngAuthenticatedEncryptorBaseTests.cs b/test/Microsoft.AspNet.Security.DataProtection.Test/Cng/CngAuthenticatedEncryptorBaseTests.cs new file mode 100644 index 0000000000..603186a009 --- /dev/null +++ b/test/Microsoft.AspNet.Security.DataProtection.Test/Cng/CngAuthenticatedEncryptorBaseTests.cs @@ -0,0 +1,109 @@ +// 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 Microsoft.AspNet.Security.DataProtection.Cng; +using Moq; +using Xunit; + +namespace Microsoft.AspNet.Security.DataProtection.Test.Cng +{ + public unsafe class CngAuthenticatedEncryptorBaseTests + { + [Fact] + public void Decrypt_ForwardsArraySegment() + { + // Arrange + var ciphertext = new ArraySegment(new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04 }, 3, 2); + var aad = new ArraySegment(new byte[] { 0x10, 0x11, 0x12, 0x13, 0x14 }, 1, 4); + + var encryptorMock = new Mock(); + encryptorMock + .Setup(o => o.DecryptHook(It.IsAny(), 2, It.IsAny(), 4)) + .Returns((IntPtr pbCiphertext, uint cbCiphertext, IntPtr pbAdditionalAuthenticatedData, uint cbAdditionalAuthenticatedData) => + { + // ensure that pointers started at the right place + Assert.Equal((byte)0x03, *(byte*)pbCiphertext); + Assert.Equal((byte)0x11, *(byte*)pbAdditionalAuthenticatedData); + return new byte[] { 0x20, 0x21, 0x22 }; + }); + + // Act + var retVal = encryptorMock.Object.Decrypt(ciphertext, aad); + + // Assert + Assert.Equal(new byte[] { 0x20, 0x21, 0x22 }, retVal); + } + + [Fact] + public void Decrypt_HandlesEmptyAADPointerFixup() + { + // Arrange + var ciphertext = new ArraySegment(new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04 }, 3, 2); + var aad = new ArraySegment(new byte[0]); + + var encryptorMock = new Mock(); + encryptorMock + .Setup(o => o.DecryptHook(It.IsAny(), 2, It.IsAny(), 0)) + .Returns((IntPtr pbCiphertext, uint cbCiphertext, IntPtr pbAdditionalAuthenticatedData, uint cbAdditionalAuthenticatedData) => + { + // ensure that pointers started at the right place + Assert.Equal((byte)0x03, *(byte*)pbCiphertext); + Assert.NotEqual(IntPtr.Zero, pbAdditionalAuthenticatedData); // CNG will complain if this pointer is zero + return new byte[] { 0x20, 0x21, 0x22 }; + }); + + // Act + var retVal = encryptorMock.Object.Decrypt(ciphertext, aad); + + // Assert + Assert.Equal(new byte[] { 0x20, 0x21, 0x22 }, retVal); + } + + [Fact] + public void Decrypt_HandlesEmptyCiphertextPointerFixup() + { + // Arrange + var ciphertext = new ArraySegment(new byte[0]); + var aad = new ArraySegment(new byte[] { 0x10, 0x11, 0x12, 0x13, 0x14 }, 1, 4); + + var encryptorMock = new Mock(); + encryptorMock + .Setup(o => o.DecryptHook(It.IsAny(), 0, It.IsAny(), 4)) + .Returns((IntPtr pbCiphertext, uint cbCiphertext, IntPtr pbAdditionalAuthenticatedData, uint cbAdditionalAuthenticatedData) => + { + // ensure that pointers started at the right place + Assert.NotEqual(IntPtr.Zero, pbCiphertext); // CNG will complain if this pointer is zero + Assert.Equal((byte)0x11, *(byte*)pbAdditionalAuthenticatedData); + return new byte[] { 0x20, 0x21, 0x22 }; + }); + + // Act + var retVal = encryptorMock.Object.Decrypt(ciphertext, aad); + + // Assert + Assert.Equal(new byte[] { 0x20, 0x21, 0x22 }, retVal); + } + + internal abstract class MockableEncryptor : CngAuthenticatedEncryptorBase + { + public override void Dispose() + { + } + + public abstract byte[] DecryptHook(IntPtr pbCiphertext, uint cbCiphertext, IntPtr pbAdditionalAuthenticatedData, uint cbAdditionalAuthenticatedData); + + protected override sealed unsafe byte[] DecryptImpl(byte* pbCiphertext, uint cbCiphertext, byte* pbAdditionalAuthenticatedData, uint cbAdditionalAuthenticatedData) + { + return DecryptHook((IntPtr)pbCiphertext, cbCiphertext, (IntPtr)pbAdditionalAuthenticatedData, cbAdditionalAuthenticatedData); + } + + public abstract byte[] EncryptHook(IntPtr pbPlaintext, uint cbPlaintext, IntPtr pbAdditionalAuthenticatedData, uint cbAdditionalAuthenticatedData, uint cbPreBuffer, uint cbPostBuffer); + + protected override sealed unsafe byte[] EncryptImpl(byte* pbPlaintext, uint cbPlaintext, byte* pbAdditionalAuthenticatedData, uint cbAdditionalAuthenticatedData, uint cbPreBuffer, uint cbPostBuffer) + { + return EncryptHook((IntPtr)pbPlaintext, cbPlaintext, (IntPtr)pbAdditionalAuthenticatedData, cbAdditionalAuthenticatedData, cbPreBuffer, cbPostBuffer); + } + } + } +} diff --git a/test/Microsoft.AspNet.Security.DataProtection.Test/Microsoft.AspNet.Security.DataProtection.Test.kproj b/test/Microsoft.AspNet.Security.DataProtection.Test/Microsoft.AspNet.Security.DataProtection.Test.kproj index 34cf58a991..ea86cb2e90 100644 --- a/test/Microsoft.AspNet.Security.DataProtection.Test/Microsoft.AspNet.Security.DataProtection.Test.kproj +++ b/test/Microsoft.AspNet.Security.DataProtection.Test/Microsoft.AspNet.Security.DataProtection.Test.kproj @@ -22,8 +22,5 @@ 2.0 - - - \ No newline at end of file diff --git a/test/Microsoft.AspNet.Security.DataProtection.Test/PBKDF2/Pbkdf2Tests.cs b/test/Microsoft.AspNet.Security.DataProtection.Test/PBKDF2/Pbkdf2Tests.cs new file mode 100644 index 0000000000..501f759a89 --- /dev/null +++ b/test/Microsoft.AspNet.Security.DataProtection.Test/PBKDF2/Pbkdf2Tests.cs @@ -0,0 +1,66 @@ +// 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.Text; +using Microsoft.AspNet.Security.DataProtection.PBKDF2; +using Xunit; + +namespace Microsoft.AspNet.Security.DataProtection.Test.PBKDF2 +{ + public class Pbkdf2Tests + { + // The 'numBytesRequested' parameters below are chosen to exercise code paths where + // this value straddles the digest length of the PRF. We only use 5 iterations so + // that our unit tests are fast. + [Theory] + [InlineData("my-password", KeyDerivationPrf.Sha1, 5, 160 / 8 - 1, "efmxNcKD/U1urTEDGvsThlPnHA==")] + [InlineData("my-password", KeyDerivationPrf.Sha1, 5, 160 / 8 + 0, "efmxNcKD/U1urTEDGvsThlPnHDI=")] + [InlineData("my-password", KeyDerivationPrf.Sha1, 5, 160 / 8 + 1, "efmxNcKD/U1urTEDGvsThlPnHDLk")] + [InlineData("my-password", KeyDerivationPrf.Sha256, 5, 256 / 8 - 1, "JRNz8bPKS02EG1vf7eWjA64IeeI+TI8gBEwb1oVvRA==")] + [InlineData("my-password", KeyDerivationPrf.Sha256, 5, 256 / 8 + 0, "JRNz8bPKS02EG1vf7eWjA64IeeI+TI8gBEwb1oVvRLo=")] + [InlineData("my-password", KeyDerivationPrf.Sha256, 5, 256 / 8 + 1, "JRNz8bPKS02EG1vf7eWjA64IeeI+TI8gBEwb1oVvRLpk")] + [InlineData("my-password", KeyDerivationPrf.Sha512, 5, 512 / 8 - 1, "ZTallQJrFn0279xIzaiA1XqatVTGei+ZjKngA7bIMtKMDUw6YJeGUQpFG8iGTgN+ri3LNDktNbzwfcSyZmm9")] + [InlineData("my-password", KeyDerivationPrf.Sha512, 5, 512 / 8 + 0, "ZTallQJrFn0279xIzaiA1XqatVTGei+ZjKngA7bIMtKMDUw6YJeGUQpFG8iGTgN+ri3LNDktNbzwfcSyZmm90Q==")] + [InlineData("my-password", KeyDerivationPrf.Sha512, 5, 512 / 8 + 1, "ZTallQJrFn0279xIzaiA1XqatVTGei+ZjKngA7bIMtKMDUw6YJeGUQpFG8iGTgN+ri3LNDktNbzwfcSyZmm90Wk=")] + public void RunTest_Normal(string password, KeyDerivationPrf prf, int iterationCount, int numBytesRequested, string expectedValueAsBase64) + { + // Arrange + byte[] salt = new byte[256]; + for (int i = 0; i < salt.Length; i++) + { + salt[i] = (byte)i; + } + + // Act & assert - fully managed, Win7, and Win8 + TestProvider(password, salt, prf, iterationCount, numBytesRequested, expectedValueAsBase64); + TestProvider(password, salt, prf, iterationCount, numBytesRequested, expectedValueAsBase64); + TestProvider(password, salt, prf, iterationCount, numBytesRequested, expectedValueAsBase64); + } + + [Fact] + public void RunTest_WithLongPassword() + { + // Arrange + string password = new String('x', 50000); // 50,000 char password + byte[] salt = Encoding.UTF8.GetBytes("salt"); + const string expectedDerivedKeyBase64 = "Sc+V/c3fiZq5Z5qH3iavAiojTsW97FAp2eBNmCQAwCNzA8hfhFFYyQLIMK65qPnBFHOHXQPwAxNQNhaEAH9hzfiaNBSRJpF9V4rpl02d5ZpI6cZbsQFF7TJW7XJzQVpYoPDgJlg0xVmYLhn1E9qMtUVUuXsBjOOdd7K1M+ZI00c="; + const KeyDerivationPrf prf = KeyDerivationPrf.Sha256; + const int iterationCount = 5; + const int numBytesRequested = 128; + + // Act & assert - fully managed, Win7, and Win8 + TestProvider(password, salt, prf, iterationCount, numBytesRequested, expectedDerivedKeyBase64); + TestProvider(password, salt, prf, iterationCount, numBytesRequested, expectedDerivedKeyBase64); + TestProvider(password, salt, prf, iterationCount, numBytesRequested, expectedDerivedKeyBase64); + } + + private static void TestProvider(string password, byte[] salt, KeyDerivationPrf prf, int iterationCount, int numBytesRequested, string expectedDerivedKeyAsBase64) + where TProvider : IPbkdf2Provider, new() + { + byte[] derivedKey = new TProvider().DeriveKey(password, salt, prf, iterationCount, numBytesRequested); + Assert.Equal(numBytesRequested, derivedKey.Length); + Assert.Equal(expectedDerivedKeyAsBase64, Convert.ToBase64String(derivedKey)); + } + } +} diff --git a/test/Microsoft.AspNet.Security.DataProtection.Test/Properties/AssemblyInfo.cs b/test/Microsoft.AspNet.Security.DataProtection.Test/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..3f8188a594 --- /dev/null +++ b/test/Microsoft.AspNet.Security.DataProtection.Test/Properties/AssemblyInfo.cs @@ -0,0 +1,8 @@ +// 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.Runtime.CompilerServices; + +// for unit testing +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] diff --git a/test/Microsoft.AspNet.Security.DataProtection.Test/SP800_108/SP800_108Tests.cs b/test/Microsoft.AspNet.Security.DataProtection.Test/SP800_108/SP800_108Tests.cs new file mode 100644 index 0000000000..9feb26b816 --- /dev/null +++ b/test/Microsoft.AspNet.Security.DataProtection.Test/SP800_108/SP800_108Tests.cs @@ -0,0 +1,90 @@ +// 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.Security.Cryptography; +using System.Text; +using Microsoft.AspNet.Security.DataProtection.SP800_108; +using Xunit; + +namespace Microsoft.AspNet.Security.DataProtection.Test.SP800_108 +{ + public unsafe class SP800_108Tests + { + private delegate ISP800_108_CTR_HMACSHA512Provider ProviderFactory(byte* pbKdk, uint cbKdk); + + // The 'numBytesRequested' parameters below are chosen to exercise code paths where + // this value straddles the digest length of the PRF (which is hardcoded to HMACSHA512). + [Theory] + [InlineData(512 / 8 - 1, "V47WmHzPSkdC2vkLAomIjCzZlDOAetll3yJLcSvon7LJFjJpEN+KnSNp+gIpeydKMsENkflbrIZ/3s6GkEaH")] + [InlineData(512 / 8 + 0, "mVaFM4deXLl610CmnCteNzxgbM/VkmKznAlPauHcDBn0le06uOjAKLHx0LfoU2/Ttq9nd78Y6Nk6wArmdwJgJg==")] + [InlineData(512 / 8 + 1, "GaHPeqdUxriFpjRtkYQYWr5/iqneD/+hPhVJQt4rXblxSpB1UUqGqL00DMU/FJkX0iMCfqUjQXtXyfks+p++Ev4=")] + public void DeriveKeyWithContextHeader_Normal(int numDerivedBytes, string expectedDerivedSubkeyAsBase64) + { + // Arrange + byte[] kdk = Encoding.UTF8.GetBytes("kdk"); + byte[] label = Encoding.UTF8.GetBytes("label"); + byte[] contextHeader = Encoding.UTF8.GetBytes("contextHeader"); + byte[] context = Encoding.UTF8.GetBytes("context"); + + // Act & assert - managed, Win7, Win8 + TestManagedKeyDerivation(kdk, label, contextHeader, context, numDerivedBytes, expectedDerivedSubkeyAsBase64); + TestCngKeyDerivation((pbKdk, cbKdk) => new Win7SP800_108_CTR_HMACSHA512Provider(pbKdk, cbKdk), kdk, label, contextHeader, context, numDerivedBytes, expectedDerivedSubkeyAsBase64); + TestCngKeyDerivation((pbKdk, cbKdk) => new Win8SP800_108_CTR_HMACSHA512Provider(pbKdk, cbKdk), kdk, label, contextHeader, context, numDerivedBytes, expectedDerivedSubkeyAsBase64); + } + + // The 'numBytesRequested' parameters below are chosen to exercise code paths where + // this value straddles the digest length of the PRF (which is hardcoded to HMACSHA512). + [Theory] + [InlineData(512 / 8 - 1, "rt2hM6kkQ8hAXmkHx0TU4o3Q+S7fie6b3S1LAq107k++P9v8uSYA2G+WX3pJf9ZkpYrTKD7WUIoLkgA1R9lk")] + [InlineData(512 / 8 + 0, "RKiXmHSrWq5gkiRSyNZWNJrMR0jDyYHJMt9odOayRAE5wLSX2caINpQmfzTH7voJQi3tbn5MmD//dcspghfBiw==")] + [InlineData(512 / 8 + 1, "KedXO0zAIZ3AfnPqY1NnXxpC3HDHIxefG4bwD3g6nWYEc5+q7pjbam71Yqj0zgHMNC9Z7BX3wS1/tajFocRWZUk=")] + public void DeriveKeyWithContextHeader_LongKey(int numDerivedBytes, string expectedDerivedSubkeyAsBase64) + { + // Arrange + byte[] kdk = new byte[50000]; // CNG can't normally handle a 50,000 byte KDK, but we coerce it into working :) + for (int i = 0; i < kdk.Length; i++) + { + kdk[i] = (byte)i; + } + + byte[] label = Encoding.UTF8.GetBytes("label"); + byte[] contextHeader = Encoding.UTF8.GetBytes("contextHeader"); + byte[] context = Encoding.UTF8.GetBytes("context"); + + // Act & assert - managed, Win7, Win8 + TestManagedKeyDerivation(kdk, label, contextHeader, context, numDerivedBytes, expectedDerivedSubkeyAsBase64); + TestCngKeyDerivation((pbKdk, cbKdk) => new Win7SP800_108_CTR_HMACSHA512Provider(pbKdk, cbKdk), kdk, label, contextHeader, context, numDerivedBytes, expectedDerivedSubkeyAsBase64); + TestCngKeyDerivation((pbKdk, cbKdk) => new Win8SP800_108_CTR_HMACSHA512Provider(pbKdk, cbKdk), kdk, label, contextHeader, context, numDerivedBytes, expectedDerivedSubkeyAsBase64); + } + + private static void TestCngKeyDerivation(ProviderFactory factory, byte[] kdk, byte[] label, byte[] contextHeader, byte[] context, int numDerivedBytes, string expectedDerivedSubkeyAsBase64) + { + byte[] derivedSubkey = new byte[numDerivedBytes]; + + fixed (byte* pbKdk = kdk) + fixed (byte* pbLabel = label) + fixed (byte* pbContext = context) + fixed (byte* pbDerivedSubkey = derivedSubkey) + { + ISP800_108_CTR_HMACSHA512Provider provider = factory(pbKdk, (uint)kdk.Length); + provider.DeriveKeyWithContextHeader(pbLabel, (uint)label.Length, contextHeader, pbContext, (uint)context.Length, pbDerivedSubkey, (uint)derivedSubkey.Length); + } + + Assert.Equal(expectedDerivedSubkeyAsBase64, Convert.ToBase64String(derivedSubkey)); + } + + private static void TestManagedKeyDerivation(byte[] kdk, byte[] label, byte[] contextHeader, byte[] context, int numDerivedBytes, string expectedDerivedSubkeyAsBase64) + { + var labelSegment = new ArraySegment(new byte[label.Length + 10], 3, label.Length); + Buffer.BlockCopy(label, 0, labelSegment.Array, labelSegment.Offset, labelSegment.Count); + var contextSegment = new ArraySegment(new byte[context.Length + 10], 5, context.Length); + Buffer.BlockCopy(context, 0, contextSegment.Array, contextSegment.Offset, contextSegment.Count); + var derivedSubkeySegment = new ArraySegment(new byte[numDerivedBytes + 10], 4, numDerivedBytes); + + ManagedSP800_108_CTR_HMACSHA512.DeriveKeysWithContextHeader(kdk, labelSegment, contextHeader, contextSegment, + bytes => new HMACSHA512(bytes), derivedSubkeySegment); + Assert.Equal(expectedDerivedSubkeyAsBase64, Convert.ToBase64String(derivedSubkeySegment.AsStandaloneArray())); + } + } +}