diff --git a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.AesKeyWrap.cs b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.AesKeyWrap.cs new file mode 100644 index 00000000000000..0c802506deb07f --- /dev/null +++ b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.AesKeyWrap.cs @@ -0,0 +1,84 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Swift; +using System.Security.Cryptography; +using System.Security.Cryptography.Apple; +using Swift.Runtime; + +#pragma warning disable CS3016 // Arrays as attribute arguments are not CLS Compliant + +internal static partial class Interop +{ + internal static partial class AppleCrypto + { + internal static unsafe int AesKeyWrapEncrypt( + ReadOnlySpan key, + ReadOnlySpan plaintext, + Span ciphertext) + { + fixed (byte* keyPtr = key) + fixed (byte* plaintextPtr = plaintext) + fixed (byte* ciphertextPtr = ciphertext) + { + int written = AppleCryptoNative_AesKeyWrapEncrypt( + new UnsafeBufferPointer(keyPtr, key.Length), + new UnsafeBufferPointer(plaintextPtr, plaintext.Length), + new UnsafeMutableBufferPointer(ciphertextPtr, ciphertext.Length), + out SwiftError error); + + if (error.Value != null) + { + CryptographicOperations.ZeroMemory(ciphertext); + throw new CryptographicException(); + } + + return written; + } + } + + internal static unsafe int AesKeyWrapDecrypt( + ReadOnlySpan key, + ReadOnlySpan ciphertext, + Span plaintext) + { + fixed (byte* keyPtr = key) + fixed (byte* ciphertextPtr = ciphertext) + fixed (byte* plaintextPtr = plaintext) + { + int written = AppleCryptoNative_AesKeyWrapDecrypt( + new UnsafeBufferPointer(keyPtr, key.Length), + new UnsafeBufferPointer(ciphertextPtr, ciphertext.Length), + new UnsafeMutableBufferPointer(plaintextPtr, plaintext.Length), + out SwiftError error); + + if (error.Value != null) + { + CryptographicOperations.ZeroMemory(plaintext); + throw new CryptographicException(); + } + + return written; + } + } + + [LibraryImport(Libraries.AppleCryptoNative)] + [UnmanagedCallConv(CallConvs = [ typeof(CallConvSwift) ])] + private static unsafe partial int AppleCryptoNative_AesKeyWrapEncrypt( + UnsafeBufferPointer key, + UnsafeBufferPointer plaintext, + UnsafeMutableBufferPointer ciphertext, + out SwiftError error); + + [LibraryImport(Libraries.AppleCryptoNative)] + [UnmanagedCallConv(CallConvs = [ typeof(CallConvSwift) ])] + private static unsafe partial int AppleCryptoNative_AesKeyWrapDecrypt( + UnsafeBufferPointer key, + UnsafeBufferPointer ciphertext, + UnsafeMutableBufferPointer plaintext, + out SwiftError error); + } +} diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.Cipher.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.Cipher.cs index 1a90ae1c95a371..fe75f3ffa0e5c8 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.Cipher.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.EVP.Cipher.cs @@ -258,6 +258,9 @@ internal static void EvpCipherSetCcmTagLength(SafeEvpCipherCtxHandle ctx, int ta [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpAes128Ccm")] internal static partial IntPtr EvpAes128Ccm(); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpAes128Wrap")] + internal static partial IntPtr EvpAes128Wrap(); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpAes128WrapPad")] internal static partial IntPtr EvpAes128WrapPad(); @@ -279,6 +282,9 @@ internal static void EvpCipherSetCcmTagLength(SafeEvpCipherCtxHandle ctx, int ta [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpAes192Ccm")] internal static partial IntPtr EvpAes192Ccm(); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpAes192Wrap")] + internal static partial IntPtr EvpAes192Wrap(); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpAes192WrapPad")] internal static partial IntPtr EvpAes192WrapPad(); @@ -300,6 +306,9 @@ internal static void EvpCipherSetCcmTagLength(SafeEvpCipherCtxHandle ctx, int ta [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpAes256Ccm")] internal static partial IntPtr EvpAes256Ccm(); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpAes256Wrap")] + internal static partial IntPtr EvpAes256Wrap(); + [LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EvpAes256WrapPad")] internal static partial IntPtr EvpAes256WrapPad(); diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/KeyWrapPaddedTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/KeyWrapPaddedTests.cs new file mode 100644 index 00000000000000..9fb48407a4fbfd --- /dev/null +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/KeyWrapPaddedTests.cs @@ -0,0 +1,915 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using Test.Cryptography; +using Xunit; + +namespace System.Security.Cryptography.Encryption.Aes.Tests +{ + using Aes = System.Security.Cryptography.Aes; + + [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] + public sealed class KeyWrapPaddedTests_AesCreate_KeyProp : KeyWrapPaddedTests + { + protected override Aes CreateKey(byte[] key) + { + Aes aes = Aes.Create(); + aes.Key = key; + return aes; + } + } + + [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] + public sealed class KeyWrapPaddedTests_AesCreate_SetKey : KeyWrapPaddedTests + { + protected override Aes CreateKey(byte[] key) + { + Aes aes = Aes.Create(); + aes.SetKey(key); + return aes; + } + } + + [SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")] + public static class KeyWrapPaddedTests_AesCryptoServiceProvider + { + [Fact] + public static void NotValidForAesCsp() + { + byte[] input = new byte[16]; + + using (Aes aes = new AesCryptoServiceProvider()) + { + Assert.Throws(() => aes.EncryptKeyWrapPadded(input)); + Assert.Throws(() => aes.DecryptKeyWrapPadded(input)); + } + } + } + + [PlatformSpecific(TestPlatforms.Windows)] + public sealed class KeyWrapPaddedTests_AesCng : KeyWrapPaddedTests + { + protected override Aes CreateKey(byte[] key) + { + Aes aes = new AesCng(); + aes.Key = key; + return aes; + } + } + + public static class KeyWrapPaddedContractTests + { + [Theory] + [InlineData(1, 16)] + [InlineData(5, 16)] + [InlineData(8, 16)] + [InlineData(9, 24)] + [InlineData(15, 24)] + [InlineData(16, 24)] + [InlineData(17, 32)] + public static void VerifyGetPaddedLength(int inputLength, int expectedLength) + { + Assert.Equal(expectedLength, Aes.GetKeyWrapPaddedLength(inputLength)); + } + + [Fact] + public static void VerifyGetPaddedLength_Random() + { + int value = Random.Shared.Next(0x7FFF_FFF1); + int actual = Aes.GetKeyWrapPaddedLength(value); + + // Rather than repeat the formula of `(value + 7) / 8 * 8 + 8`, let's prove it by deconstruction. + int minus8 = actual - 8; + int minus16 = actual - 16; + + if (value <= minus16 || value > minus8) + { + Assert.Fail($"Expected {value} to be in the range ({minus16}, {minus8}] for padded length {actual}"); + } + } + + [Fact] + public static void GetPaddedLength_TooLarge() + { + int i = int.MaxValue; + + for (; i >= 0x7FFF_FFF1; i--) + { + AssertExtensions.Throws( + "plaintextLengthInBytes", + () => Aes.GetKeyWrapPaddedLength(i)); + } + + Assert.Equal(0x7FFF_FFF8, Aes.GetKeyWrapPaddedLength(i)); + } + + [Fact] + public static void GetPaddedLengthNeedsPositiveInput() + { + foreach (int len in new int[] { 0, -1, -2, int.MinValue }) + { + AssertExtensions.Throws( + "plaintextLengthInBytes", + () => Aes.GetKeyWrapPaddedLength(len)); + } + } + + [Fact] + public static void NeverCalledWithEmpty() + { + using (TestAes key = new TestAes()) + { + byte[] output = new byte[24]; + + AssertExtensions.Throws( + "plaintext", + () => key.EncryptKeyWrapPadded(null)); + + AssertExtensions.Throws( + "plaintext", + () => key.EncryptKeyWrapPadded(Array.Empty())); + + AssertExtensions.Throws( + "plaintext", + () => key.EncryptKeyWrapPadded(ReadOnlySpan.Empty)); + + AssertExtensions.Throws( + "plaintext", + () => key.EncryptKeyWrapPadded(ReadOnlySpan.Empty, output)); + + AssertExtensions.Throws( + "ciphertext", + () => key.DecryptKeyWrapPadded(null)); + + AssertExtensions.Throws( + "ciphertext", + () => key.DecryptKeyWrapPadded(ReadOnlySpan.Empty, output)); + + AssertExtensions.Throws( + "ciphertext", + () => key.DecryptKeyWrapPadded(ReadOnlySpan.Empty)); + + AssertExtensions.Throws( + "ciphertext", + () => key.DecryptKeyWrapPadded(ReadOnlySpan.Empty, output)); + + AssertExtensions.Throws( + "ciphertext", + () => key.TryDecryptKeyWrapPadded(ReadOnlySpan.Empty, output, out _)); + + Assert.Equal(0, key.DecryptKeyWrapPaddedCallCount); + } + } + + [Fact] + public static void DecryptNeverCalledWithPartialBlocks() + { + byte[] input = new byte[33]; + + using (TestAes key = new TestAes()) + { + key.DecryptOverride = (source, destination) => source.Length - 15; + + Assert.ThrowsAny(() => key.DecryptKeyWrapPadded(Array.Empty())); + Assert.Equal(0, key.DecryptKeyWrapPaddedCallCount); + + int expectedCallCount = 0; + + for (int i = input.Length; i > 0; i--) + { + if (i % 8 == 0 && i > 8) + { + // Assert.NoThrow + key.DecryptKeyWrapPadded(new ReadOnlySpan(input, 0, i)); + expectedCallCount++; + } + else + { + AssertExtensions.Throws( + "ciphertext", + () => key.DecryptKeyWrapPadded(new ReadOnlySpan(input, 0, i))); + } + + Assert.Equal(expectedCallCount, key.DecryptKeyWrapPaddedCallCount); + } + } + } + + [Fact] + public static void DecryptMustReportMinimumLength() + { + using (TestAes key = new TestAes()) + { + key.DecryptOverride = (source, destination) => source.Length - 16; + byte[] input = new byte[32]; + + Assert.Throws(() => key.DecryptKeyWrapPadded(input)); + Assert.Equal(1, key.DecryptKeyWrapPaddedCallCount); + + key.DecryptOverride = (source, destination) => source.Length - 15; + byte[] ret = key.DecryptKeyWrapPadded(input); + Assert.Equal(17, ret.Length); + Assert.Equal(2, key.DecryptKeyWrapPaddedCallCount); + } + } + + [Fact] + public static void ExcessiveLengthFromDecrypt() + { + using (TestAes key = new TestAes()) + { + byte[] input = new byte[32]; + byte[] output = new byte[32]; + int retLen = input.Length; + int expectedCallCount = 0; + key.DecryptOverride = (source, destination) => retLen; + + for (; retLen > input.Length - 8; retLen--) + { + Assert.Throws(() => key.DecryptKeyWrapPadded(input)); + Assert.Equal(++expectedCallCount, key.DecryptKeyWrapPaddedCallCount); + + Assert.Throws(() => key.DecryptKeyWrapPadded(new ReadOnlySpan(input))); + Assert.Equal(++expectedCallCount, key.DecryptKeyWrapPaddedCallCount); + + Assert.Throws(() => key.DecryptKeyWrapPadded(input, output)); + Assert.Equal(++expectedCallCount, key.DecryptKeyWrapPaddedCallCount); + + Assert.Throws(() => key.TryDecryptKeyWrapPadded(input, output, out _)); + Assert.Equal(++expectedCallCount, key.DecryptKeyWrapPaddedCallCount); + } + + byte[] ret = key.DecryptKeyWrapPadded(input); + Assert.Equal(++expectedCallCount, key.DecryptKeyWrapPaddedCallCount); + Assert.Equal(retLen, ret.Length); + } + } + + [Fact] + public static void EncryptAlwaysSeesSource() + { + using (TestAes key = new TestAes()) + { + byte[] input = new byte[32]; + int callLen = input.Length; + + key.EncryptOverride = + (source, destination) => + { + AssertExtensions.TrueExpression(source.Overlaps(input)); + }; + + for (; callLen > 0; callLen--) + { + key.EncryptKeyWrapPadded(input.AsSpan(0, callLen)); + } + } + } + + [Fact] + public static void EncryptNeverSeesInexactDestination() + { + using (TestAes key = new TestAes()) + { + byte[] input = new byte[16]; + byte[] output = new byte[32]; + int expectedCallCount = 0; + + key.EncryptOverride = + (source, destination) => + { + Assert.Equal(Aes.GetKeyWrapPaddedLength(source.Length), destination.Length); + AssertExtensions.TrueExpression(destination.Overlaps(output, out int offset)); + Assert.Equal(0, offset); + }; + + int correctLength = Aes.GetKeyWrapPaddedLength(input.Length); + + for (int i = 0; i <= output.Length; i++) + { + if (i == correctLength) + { + // Assert.NoThrow + key.EncryptKeyWrapPadded(input, output.AsSpan(0, i)); + Assert.Equal(++expectedCallCount, key.EncryptKeyWrapPaddedCallCount); + } + else + { + AssertExtensions.Throws( + "destination", + () => key.EncryptKeyWrapPadded(input, output.AsSpan(0, i))); + Assert.Equal(expectedCallCount, key.EncryptKeyWrapPaddedCallCount); + } + } + + Assert.Equal(1, key.EncryptKeyWrapPaddedCallCount); + } + } + + [Fact] + public static void DecryptNeverSeesSmallDestination() + { + using (TestAes key = new TestAes()) + { + byte[] input = new byte[32]; + byte[] output = new byte[32]; + int callLen = output.Length; + int expectedCallCount = 0; + + key.DecryptOverride = + (source, destination) => + { + int maxOutput = source.Length - 8; + Assert.Equal(maxOutput, destination.Length); + + if (callLen >= maxOutput) + { + AssertExtensions.TrueExpression(destination.Overlaps(output, out int offset)); + Assert.Equal(0, offset); + } + else + { + AssertExtensions.FalseExpression(destination.Overlaps(output)); + } + + return source.Length - 15; + }; + + for (; callLen > input.Length - 16; callLen--) + { + key.DecryptKeyWrapPadded(input, output.AsSpan(0, callLen)); + Assert.Equal(++expectedCallCount, key.DecryptKeyWrapPaddedCallCount); + + AssertExtensions.TrueExpression(key.TryDecryptKeyWrapPadded(input, output.AsSpan(0, callLen), out _)); + Assert.Equal(++expectedCallCount, key.DecryptKeyWrapPaddedCallCount); + } + + // Now that callLen is too short, we should get an ArgumentException with no increase in call count. + AssertExtensions.Throws( + "destination", + () => key.DecryptKeyWrapPadded(input, output.AsSpan(0, callLen))); + + Assert.Equal(expectedCallCount, key.DecryptKeyWrapPaddedCallCount); + + // TryDecrypt doesn't throw, but also doesn't call the virtual + AssertExtensions.FalseExpression(key.TryDecryptKeyWrapPadded(input, output.AsSpan(0, callLen), out _)); + Assert.Equal(expectedCallCount, key.DecryptKeyWrapPaddedCallCount); + } + } + + [Fact] + public static void BaseClassForcesZeroPadding() + { + // To make it permissible for a derived class to perform the unwrap in a different buffer, + // and only copy over bytesWritten bytes to the destination, the base class forces any + // extra bytes in destination to be zeroed out. + // This test shows that works. + + using (TestAes key = new TestAes()) + { + ReadOnlySpan input = stackalloc byte[24]; + Span output = stackalloc byte[32]; + int retLen = 0; + int outputOffset = 0; + + int minOutput = input.Length - 15; + int maxOutput = input.Length - 8; + + const byte PreFill = 0xEE; + const byte CallFill = 0xCC; + + key.DecryptOverride = + (source, destination) => + { + destination.Fill(CallFill); + return retLen; + }; + + for (int outputLen = minOutput; outputLen <= output.Length; outputLen++) + { + outputOffset = (output.Length - outputLen + 1) / 2; + Span destination = output.Slice(outputOffset, outputLen); + ReadOnlySpan trimmedDest = destination; + + if (trimmedDest.Length > maxOutput) + { + trimmedDest = trimmedDest.Slice(0, maxOutput); + } + + ReadOnlySpan preDest = output.Slice(0, outputOffset); + ReadOnlySpan postDest = output.Slice(outputOffset + trimmedDest.Length); + + for (retLen = minOutput; retLen <= int.Min(outputLen, maxOutput); retLen++) + { + output.Fill(PreFill); + int ret = key.DecryptKeyWrapPadded(input, destination); + Assert.Equal(retLen, ret); + + ReadOnlySpan padding = trimmedDest.Slice(retLen); + ReadOnlySpan answer = trimmedDest.Slice(0, retLen); + + AssertExtensions.TrueExpression(padding.IndexOfAnyExcept((byte)0) == -1); + AssertExtensions.TrueExpression(answer.IndexOfAnyExcept(CallFill) == -1); + AssertExtensions.TrueExpression(preDest.IndexOfAnyExcept(PreFill) == -1); + AssertExtensions.TrueExpression(postDest.IndexOfAnyExcept(PreFill) == -1); + } + } + } + } + + [Fact] + public static void DecryptCallsVirtualWhenDestinationIsPlausible() + { + using (TestAes key = new TestAes()) + { + byte[] input = new byte[24]; + byte[] output = new byte[32]; + int retLen = input.Length - 9; + int maxOutput = input.Length - 8; + int expectedCallCount = 0; + + const byte CallFill = 0xDD; + const byte PreFill = 0xB5; + + key.DecryptOverride = + (source, destination) => + { + destination[0..^1].Fill(CallFill); + destination[^1] = 0; + return destination.Length - 1; + }; + + for (int outputLen = output.Length; outputLen > input.Length - 16; outputLen--) + { + int outputOffset = (output.Length - outputLen + 1) / 2; + int trimmedLen = int.Min(maxOutput, outputLen); + Span destination = output.AsSpan(outputOffset, outputLen); + + ReadOnlySpan preDest = output.AsSpan(0, outputOffset); + ReadOnlySpan postDest = output.AsSpan(outputOffset + trimmedLen); + + if (outputLen >= retLen) + { + Array.Fill(output, PreFill); + int ret = key.DecryptKeyWrapPadded(input, destination); + Assert.Equal(++expectedCallCount, key.DecryptKeyWrapPaddedCallCount); + + ReadOnlySpan answer = destination.Slice(0, retLen); + ReadOnlySpan padding = destination.Slice(retLen, trimmedLen - retLen); + + AssertExtensions.TrueExpression(padding.IndexOfAnyExcept((byte)0) == -1); + AssertExtensions.TrueExpression(answer.IndexOfAnyExcept(CallFill) == -1); + AssertExtensions.TrueExpression(preDest.IndexOfAnyExcept(PreFill) == -1); + AssertExtensions.TrueExpression(postDest.IndexOfAnyExcept(PreFill) == -1); + + Array.Fill(output, PreFill); + AssertExtensions.TrueExpression(key.TryDecryptKeyWrapPadded(input, destination, out ret)); + Assert.Equal(++expectedCallCount, key.DecryptKeyWrapPaddedCallCount); + + AssertExtensions.TrueExpression(padding.IndexOfAnyExcept((byte)0) == -1); + AssertExtensions.TrueExpression(answer.IndexOfAnyExcept(CallFill) == -1); + AssertExtensions.TrueExpression(preDest.IndexOfAnyExcept(PreFill) == -1); + AssertExtensions.TrueExpression(postDest.IndexOfAnyExcept(PreFill) == -1); + } + else + { + Array.Fill(output, PreFill); + + AssertExtensions.Throws( + "destination", + () => key.DecryptKeyWrapPadded(input, output.AsSpan(outputOffset, outputLen))); + + Assert.Equal(++expectedCallCount, key.DecryptKeyWrapPaddedCallCount); + AssertExtensions.TrueExpression(output.IndexOfAnyExcept(PreFill) == -1); + + Array.Fill(output, PreFill); + AssertExtensions.FalseExpression(key.TryDecryptKeyWrapPadded(input, destination, out int ret)); + Assert.Equal(++expectedCallCount, key.DecryptKeyWrapPaddedCallCount); + AssertExtensions.TrueExpression(output.IndexOfAnyExcept(PreFill) == -1); + Assert.Equal(0, ret); + } + } + } + } + + [Fact] + public static void NoOverlapForEncrypt() + { + byte[] buffer = new byte[32]; + + using (TestAes key = new TestAes()) + { + AssertExtensions.Throws( + () => key.EncryptKeyWrapPadded(buffer.AsSpan(15, 8), buffer.AsSpan(0, 16))); + + Assert.Equal(0, key.EncryptKeyWrapPaddedCallCount); + + key.EncryptOverride = (source, destination) => { }; + + // Adjacent is OK + key.EncryptKeyWrapPadded(buffer.AsSpan(16, 8), buffer.AsSpan(0, 16)); + Assert.Equal(1, key.EncryptKeyWrapPaddedCallCount); + } + } + + [Fact] + public static void NoOverlapForDecrypt() + { + byte[] buffer = new byte[32]; + + using (TestAes key = new TestAes()) + { + AssertExtensions.Throws( + () => key.DecryptKeyWrapPadded(buffer.AsSpan(0, 16), buffer.AsSpan(15, 8))); + + AssertExtensions.Throws( + () => key.TryDecryptKeyWrapPadded(buffer.AsSpan(0, 16), buffer.AsSpan(15, 8), out _)); + + Assert.Equal(0, key.EncryptKeyWrapPaddedCallCount); + + key.DecryptOverride = (source, destination) => destination.Length - 1; + + // Adjacent is OK + key.DecryptKeyWrapPadded(buffer.AsSpan(0, 16), buffer.AsSpan(16, 8)); + Assert.Equal(1, key.DecryptKeyWrapPaddedCallCount); + + AssertExtensions.TrueExpression(key.TryDecryptKeyWrapPadded(buffer.AsSpan(0, 16), buffer.AsSpan(16, 8), out _)); + Assert.Equal(2, key.DecryptKeyWrapPaddedCallCount); + } + } + + private class TestAes : Aes + { + public delegate void EncryptCallback(ReadOnlySpan source, Span destination); + public delegate int DecryptCallback(ReadOnlySpan source, Span destination); + + public int EncryptKeyWrapPaddedCallCount { get; private set; } + public int DecryptKeyWrapPaddedCallCount { get; private set; } + public EncryptCallback EncryptOverride { get; set; } + public DecryptCallback DecryptOverride { get; set; } + + public override void GenerateIV() + { + } + + public override void GenerateKey() + { + } + + public override ICryptoTransform CreateDecryptor(byte[] rgbKey, byte[]? rgbIV) + { + Assert.Fail("CreateDecryptor should never be called"); + return null; + } + + public override ICryptoTransform CreateEncryptor(byte[] rgbKey, byte[]? rgbIV) + { + Assert.Fail("CreateEncryptor should never be called"); + return null; + } + + protected override void EncryptKeyWrapPaddedCore(ReadOnlySpan source, Span destination) + { + EncryptKeyWrapPaddedCallCount++; + + if (EncryptOverride is not null) + { + EncryptOverride(source, destination); + } + else + { + Assert.Fail("Unexpected call to EncryptKeyWrapPaddedCore"); + } + } + + protected override int DecryptKeyWrapPaddedCore(ReadOnlySpan source, Span destination) + { + DecryptKeyWrapPaddedCallCount++; + + if (DecryptOverride is not null) + { + return DecryptOverride(source, destination); + } + + Assert.Fail("Unexpected call to EncryptKeyWrapPaddedCore"); + return -1; + } + } + } + + public abstract class KeyWrapPaddedTests + { + protected abstract Aes CreateKey(byte[] key); + + [Theory] + [MemberData(nameof(KnownAnswerTests))] + public void VerifyKnownAnswer(KnownAnswerTest kat) + { + using (Aes key = CreateKey(kat.Key)) + { + VerifyWrap(key, kat.Plaintext, kat.Ciphertext); + VerifyUnwrap(key, kat.Ciphertext, kat.Plaintext); + } + } + + [Theory] + [InlineData(128, 1, 16)] + [InlineData(128, 96, 103)] + [InlineData(192, 1, 16)] + [InlineData(192, 96, 103)] + [InlineData(256, 1, 16)] + [InlineData(256, 96, 103)] + public void VerifyRoundtrip(int kekSize, int ptMin, int ptMax) + { + byte[] kek = new byte[kekSize / 8]; + RandomNumberGenerator.Fill(kek); + + using (Aes key = CreateKey(kek)) + { + for (int i = ptMin; i <= ptMax; i++) + { + // Round plaintext up to the nearest multiple of 8, + // and add the 8 bytes for the IV semi-block. + int expectedSize = (i + 7) / 8 * 8 + 8; + + byte[] plaintext = new byte[i]; + RandomNumberGenerator.Fill(plaintext); + byte[] ciphertext = key.EncryptKeyWrapPadded(plaintext); + Assert.Equal(expectedSize, ciphertext.Length); + + VerifyUnwrap(key, ciphertext, plaintext); + VerifyWrap(key, plaintext, ciphertext); + } + } + } + + [Fact] + public void UnwrapBadIV_SingleBlock() + { + // At the end of unwrap, the header block will have the incorrect value A65959A7. + byte[] kek = "9FC9E4BA68CA3EC8BAC82B02223EADDAAA1A67350E12510D0016083095B32BBC".HexToByteArray(); + byte[] ciphertext = "1B2DE25B6990AA8B74087499294ECB39".HexToByteArray(); + + VerifyUnwrapFails(kek, ciphertext); + } + + [Fact] + public void UnwrapBadIV_MultiBlock() + { + // At the end of unwrap, the header block will have the incorrect value A65959A7. + byte[] kek = "B1D18A0296DF025443EE1677ED783FB6C137A98814E09FE1".HexToByteArray(); + byte[] ciphertext = ( + "67A3F00F801" + + "A1CDAFF2D324C7AC393EB97938556FA8D54C5DB303F9EBB6321B84BCED6DD3A80EC98B3047110" + + "89A8EF9ADADA14A3ADD324E55BEFB6A5598ABB90A40CA8F36CB175498FAA3BDC11FDAC1113042" + + "E3229B790FA4BD0240830933FA9D0C8255CD271D5B7C301DDF85F098C62").HexToByteArray(); + + VerifyUnwrapFails(kek, ciphertext); + } + + [Fact] + public void UnwrapLengthTooBig_Single() + { + // At the end of unwrap, the length segment will report 8 bytes more than the original input was, + // which requires reading beyond the end of the processed buffer. + byte[] kek = "3D7C64D35E1CEC5BBEA04867073F5E9F6DB671B28EA325215FA6DA3B1B561F48".HexToByteArray(); + byte[] ciphertext = "9EDBCAFD999E7A1CEEC4529DC192797E".HexToByteArray(); + + VerifyUnwrapFails(kek, ciphertext); + } + + [Fact] + public void UnwrapLengthTooBig_MultiBlock() + { + // At the end of unwrap, the length segment will report 8 bytes more than the original input was, + // which requires reading beyond the end of the processed buffer. + byte[] kek = "3D7C64D35E1CEC5BBEA04867073F5E9F6DB671B28EA325215FA6DA3B1B561F48".HexToByteArray(); + byte[] ciphertext = "1A91401A927296BEF253F857C6124B20A2FEFB580FF472F5".HexToByteArray(); + + VerifyUnwrapFails(kek, ciphertext); + } + + [Fact] + public void UnwrapLengthZero_Single() + { + // At the end of unwrap, the length segment will report zero, which is always invalid. + byte[] kek = "B870467A475D675AEE893430A09FD77F".HexToByteArray(); + byte[] ciphertext = "024D1848259597D20FFDCE39BC3E461D".HexToByteArray(); + + VerifyUnwrapFails(kek, ciphertext); + } + + [Fact] + public void UnwrapLengthZero_MultiBlock() + { + // At the end of unwrap, the length segment will report zero, which is always invalid. + byte[] kek = "DA536B4D274173D0DAD5DBB8FF21F6E27AC8BB6F9E12F51A".HexToByteArray(); + byte[] ciphertext = "29288FFE637F4CCBF3D44FEAB22300C67796C14AAFB682E3".HexToByteArray(); + + VerifyUnwrapFails(kek, ciphertext); + } + + [Fact] + public void UnwrapBadPadding_Single() + { + // At the end of unwrap, some of the "padding" bytes will be non-zero. + byte[] kek = "838E662F79DC11058ED1EC27928DE835119BAC751B689A1DFC09011BD634842E".HexToByteArray(); + byte[] ciphertext = "E9D57F431E9A9A2878E6629A890E4C3E".HexToByteArray(); + + VerifyUnwrapFails(kek, ciphertext); + } + + [Fact] + public void UnwrapBadPadding_MultiBlock() + { + // At the end of unwrap, some of the "padding" bytes will be non-zero. + byte[] kek = "6BA88BFEA55ECE448898BFEE524244B965C5EB3CADA463E0".HexToByteArray(); + byte[] ciphertext = "852CA39B8A1DE2FD2EF10DA6F01AF860F1DF6E16F0593E85".HexToByteArray(); + + VerifyUnwrapFails(kek, ciphertext); + } + + [Fact] + public void UnwrapLengthTooShort() + { + // At the end of unwrap, this length will report 8 less than the original input was, + // which means the ciphertext should have had one block fewer than it does. + // + // Subtracting 8 from the length of a single-block ciphertext is either zero (already covered), + // or "extremely large" (already covered), so there is not a single-block variant of this. + byte[] kek = "7F0B9A269A182935200F4D92FFE291F94D132D9FBCB8982F".HexToByteArray(); + byte[] ciphertext = "9C21E8325D1D7406DE94B2009D3E67152EE6C7DBC0E5B911".HexToByteArray(); + + VerifyUnwrapFails(kek, ciphertext); + } + + private static void VerifyWrap(Aes key, byte[] plaintext, byte[] ciphertext) + { + // EncryptKeyWrapPadded(byte[]) + byte[] wrapped = key.EncryptKeyWrapPadded(plaintext); + AssertExtensions.SequenceEqual(ciphertext, wrapped); + + // EncryptKeyWrapPadded(ReadOnlySpan) + wrapped = key.EncryptKeyWrapPadded(new ReadOnlySpan(plaintext)); + AssertExtensions.SequenceEqual(ciphertext, wrapped); + + // void EncryptKeyWrapPadded(ReadOnlySpan, Span) + Array.Clear(wrapped); + key.EncryptKeyWrapPadded(plaintext, wrapped.AsSpan()); + AssertExtensions.SequenceEqual(ciphertext, wrapped); + } + + private static void VerifyUnwrap(Aes key, byte[] ciphertext, byte[] plaintext) + { + // DecryptKeyWrapPadded(byte[]) + byte[] unwrapped = key.DecryptKeyWrapPadded(ciphertext); + AssertExtensions.SequenceEqual(plaintext, unwrapped); + + // DecryptKeyWrapPadded(ReadOnlySpan) + unwrapped = key.DecryptKeyWrapPadded(new ReadOnlySpan(ciphertext)); + AssertExtensions.SequenceEqual(plaintext, unwrapped); + + byte[] tooBig = new byte[ciphertext.Length]; + tooBig.AsSpan().Fill(0xFF); + int maxOutput = ciphertext.Length - 8; + int minOutput = maxOutput - 7; + int expectedPadding = maxOutput - plaintext.Length; + ReadOnlySpan paddingSlice = tooBig.AsSpan(plaintext.Length, expectedPadding); + ReadOnlySpan untouchedSlice = tooBig.AsSpan(maxOutput); + + // The tooBig buffer will be composed of [the right answer][padding][bytes that are not touched] + // Our slice point in this loop is always within [bytes that are not touched], so we expect all + // padding bytes to be written (as 0) and all untouched bytes to remain 0xFF. + for (int i = tooBig.Length; i >= maxOutput; i--) + { + Span targetSlice = tooBig.AsSpan(0, i); + int written = key.DecryptKeyWrapPadded(ciphertext, targetSlice); + ReadOnlySpan answerSlice = targetSlice.Slice(0, written); + // SequenceEqual will also check that `written` is correct + AssertExtensions.SequenceEqual(plaintext, answerSlice); + + // Since `written` is correct, paddingSlice and untouchedSlice are sliced correctly. + AssertExtensions.TrueExpression(paddingSlice.IndexOfAnyExcept((byte)0) == -1); + AssertExtensions.TrueExpression(untouchedSlice.IndexOfAnyExcept((byte)0xFF) == -1); + + // Repeat with TryDecryptKeyWrapPadded + tooBig.AsSpan().Fill(0xFF); + AssertExtensions.TrueExpression(key.TryDecryptKeyWrapPadded(ciphertext, targetSlice, out written)); + answerSlice = targetSlice.Slice(0, written); + // SequenceEqual will also check that `written` is correct + AssertExtensions.SequenceEqual(plaintext, answerSlice); + + // Since `written` is correct, paddingSlice and untouchedSlice are sliced correctly. + AssertExtensions.TrueExpression(paddingSlice.IndexOfAnyExcept((byte)0) == -1); + AssertExtensions.TrueExpression(untouchedSlice.IndexOfAnyExcept((byte)0xFF) == -1); + } + + // In this loop, the input buffer is plausibly big enough, but not guaranteed big enough, + // so the implementation is going to use rented space to compute the unwrap. + // + // Any surplus bytes should be set to 0 (as the padding), and we can still assert that + // the untouched range is 0xFF, but this loop never even sees it. + for (int i = maxOutput - 1; i >= plaintext.Length; i--) + { + Span targetSlice = tooBig.AsSpan(0, i); + untouchedSlice = targetSlice.Slice(i); + + int written = key.DecryptKeyWrapPadded(ciphertext, targetSlice); + ReadOnlySpan answerSlice = targetSlice.Slice(0, written); + // SequenceEqual will also check that `written` is correct + AssertExtensions.SequenceEqual(plaintext, answerSlice); + + paddingSlice = targetSlice.Slice(written); + AssertExtensions.TrueExpression(paddingSlice.IndexOfAnyExcept((byte)0) == -1); + AssertExtensions.TrueExpression(untouchedSlice.IndexOfAnyExcept((byte)0xFF) == -1); + + // Repeat with TryDecryptKeyWrapPadded + tooBig.AsSpan().Fill(0xFF); + AssertExtensions.TrueExpression(key.TryDecryptKeyWrapPadded(ciphertext, targetSlice, out written)); + answerSlice = targetSlice.Slice(0, written); + // SequenceEqual will also check that `written` is correct + AssertExtensions.SequenceEqual(plaintext, answerSlice); + + // Since `written` is correct, paddingSlice and untouchedSlice are still sliced correctly. + AssertExtensions.TrueExpression(paddingSlice.IndexOfAnyExcept((byte)0) == -1); + AssertExtensions.TrueExpression(untouchedSlice.IndexOfAnyExcept((byte)0xFF) == -1); + } + + tooBig.AsSpan().Fill(0xFF); + + // targetSlice is now too small to hold the plaintext, but that can only be determined after + // running the algorithm. + // In this case, we should never touch the destination buffer. + for (int i = plaintext.Length - 1; i >= minOutput; i--) + { + AssertExtensions.Throws( + "destination", + () => key.DecryptKeyWrapPadded(ciphertext, tooBig.AsSpan(0, i))); + + AssertExtensions.TrueExpression(tooBig.IndexOfAnyExcept((byte)0xFF) == -1); + + AssertExtensions.FalseExpression( + key.TryDecryptKeyWrapPadded(ciphertext, tooBig.AsSpan(0, i), out int written)); + Assert.Equal(0, written); + + AssertExtensions.TrueExpression(tooBig.IndexOfAnyExcept((byte)0xFF) == -1); + } + } + + private void VerifyUnwrapFails(byte[] kek, byte[] ciphertext) + { + using (Aes key = CreateKey(kek)) + { + byte[] dest = new byte[ciphertext.Length]; + + Assert.ThrowsAny(() => key.DecryptKeyWrapPadded(ciphertext)); + Assert.ThrowsAny(() => key.DecryptKeyWrapPadded(new ReadOnlySpan(ciphertext))); + Assert.ThrowsAny(() => key.DecryptKeyWrapPadded(ciphertext, dest)); + Assert.ThrowsAny(() => key.TryDecryptKeyWrapPadded(ciphertext, dest, out _)); + } + } + + public static IEnumerable KnownAnswerTests { get; } = + [ + new object[] + { + new KnownAnswerTest( + "RFC 5649 Example 1", + "5840df6e29b02af1ab493b705bf16ea1ae8338f4dcc176a8".HexToByteArray(), + "c37b7e6492584340bed12207808941155068f738".HexToByteArray(), + "138bdeaa9b8fa7fc61f97742e72248ee5ae6ae5360d1ae6a5f54f373fa543b6a".HexToByteArray()) + }, + + new object[] + { + new KnownAnswerTest( + "RFC 5649 Example 2", + "5840df6e29b02af1ab493b705bf16ea1ae8338f4dcc176a8".HexToByteArray(), + "466f7250617369".HexToByteArray(), + "afbeb0f07dfbf5419200f2ccb50bb24f".HexToByteArray()) + }, + ]; + + public struct KnownAnswerTest + { + public string Name { get; } + public byte[] Key { get; } + public byte[] Plaintext { get; } + public byte[] Ciphertext { get; } + + public KnownAnswerTest(string name, byte[] key, byte[] plaintext, byte[] ciphertext) + { + Name = name; + Key = key; + Plaintext = plaintext; + Ciphertext = ciphertext; + } + + public override string ToString() + { + return Name; + } + } + } +} diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/KeyWrapTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/KeyWrapTests.cs index 41877050462cb2..48003d756bfc3c 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/KeyWrapTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/KeyWrapTests.cs @@ -37,12 +37,19 @@ public static class KeyWrapTests_AesCryptoServiceProvider [Fact] public static void NotValidForAesCsp() { - byte[] input = new byte[16]; + byte[] plaintext = new byte[16]; + byte[] ciphertext = new byte[24]; using (Aes aes = new AesCryptoServiceProvider()) { - Assert.Throws(() => aes.EncryptKeyWrapPadded(input)); - Assert.Throws(() => aes.DecryptKeyWrapPadded(input)); + Assert.Throws(() => aes.EncryptKeyWrap(plaintext)); + Assert.Throws(() => aes.EncryptKeyWrap(new ReadOnlySpan(plaintext))); + Assert.Throws(() => aes.EncryptKeyWrap(plaintext, ciphertext)); + + Assert.Throws(() => aes.DecryptKeyWrap(ciphertext)); + Assert.Throws(() => aes.DecryptKeyWrap(new ReadOnlySpan(ciphertext))); + Assert.Throws(() => aes.DecryptKeyWrap(ciphertext, plaintext)); + Assert.Throws(() => aes.TryDecryptKeyWrap(ciphertext, plaintext, out _)); } } } @@ -61,36 +68,23 @@ protected override Aes CreateKey(byte[] key) public static class KeyWrapContractTests { [Theory] - [InlineData(1, 16)] - [InlineData(5, 16)] - [InlineData(8, 16)] - [InlineData(9, 24)] - [InlineData(15, 24)] [InlineData(16, 24)] - [InlineData(17, 32)] - public static void VerifyGetPaddedLength(int inputLength, int expectedLength) + [InlineData(0x7FFF_FFF0, 0x7FFF_FFF8)] + public static void VerifyGetLength(int inputLength, int expectedLength) { - Assert.Equal(expectedLength, Aes.GetKeyWrapPaddedLength(inputLength)); + Assert.Equal(expectedLength, Aes.GetKeyWrapLength(inputLength)); } [Fact] - public static void VerifyGetPaddedLength_Random() + public static void VerifyGetLength_Random() { - int value = Random.Shared.Next(0x7FFF_FFF1); - int actual = Aes.GetKeyWrapPaddedLength(value); - - // Rather than repeat the formula of `(value + 7) / 8 * 8 + 8`, let's prove it by deconstruction. - int minus8 = actual - 8; - int minus16 = actual - 16; - - if (value <= minus16 || value > minus8) - { - Assert.Fail($"Expected {value} to be in the range ({minus16}, {minus8}] for padded length {actual}"); - } + int value = Random.Shared.Next(16, 0x7FFF_FFF1) & ~0b111; + int actual = Aes.GetKeyWrapLength(value); + Assert.Equal(value + 8, actual); } [Fact] - public static void GetPaddedLength_TooLarge() + public static void GetLength_TooLarge() { int i = int.MaxValue; @@ -98,21 +92,34 @@ public static void GetPaddedLength_TooLarge() { AssertExtensions.Throws( "plaintextLengthInBytes", - () => Aes.GetKeyWrapPaddedLength(i)); + () => Aes.GetKeyWrapLength(i)); } - Assert.Equal(0x7FFF_FFF8, Aes.GetKeyWrapPaddedLength(i)); + Assert.Equal(0x7FFF_FFF8, Aes.GetKeyWrapLength(i)); } - [Fact] - public static void GetPaddedLengthNeedsPositiveInput() + [Theory] + [InlineData(15)] + [InlineData(8)] + [InlineData(0)] + [InlineData(-1)] + [InlineData(int.MinValue)] + public static void GetLengthTooSmall(int len) { - foreach (int len in new int[] { 0, -1, -2, int.MinValue }) - { - AssertExtensions.Throws( - "plaintextLengthInBytes", - () => Aes.GetKeyWrapPaddedLength(len)); - } + AssertExtensions.Throws( + "plaintextLengthInBytes", + () => Aes.GetKeyWrapLength(len)); + } + + [Theory] + [InlineData(17)] + [InlineData(19)] + [InlineData(25)] + public static void GetLengthNotAlignedToMultiple(int len) + { + AssertExtensions.Throws( + "plaintextLengthInBytes", + () => Aes.GetKeyWrapLength(len)); } [Fact] @@ -122,127 +129,201 @@ public static void NeverCalledWithEmpty() { byte[] output = new byte[24]; - AssertExtensions.Throws( + AssertExtensions.Throws( "plaintext", - () => key.EncryptKeyWrapPadded(null)); + () => key.EncryptKeyWrap(null)); AssertExtensions.Throws( "plaintext", - () => key.EncryptKeyWrapPadded(Array.Empty())); + () => key.EncryptKeyWrap(Array.Empty())); AssertExtensions.Throws( "plaintext", - () => key.EncryptKeyWrapPadded(ReadOnlySpan.Empty)); + () => key.EncryptKeyWrap(ReadOnlySpan.Empty)); AssertExtensions.Throws( "plaintext", - () => key.EncryptKeyWrapPadded(ReadOnlySpan.Empty, output)); + () => key.EncryptKeyWrap(ReadOnlySpan.Empty, output)); AssertExtensions.Throws( "ciphertext", - () => key.DecryptKeyWrapPadded(null)); + () => key.DecryptKeyWrap(null)); AssertExtensions.Throws( "ciphertext", - () => key.DecryptKeyWrapPadded(ReadOnlySpan.Empty, output)); + () => key.DecryptKeyWrap(Array.Empty())); AssertExtensions.Throws( "ciphertext", - () => key.DecryptKeyWrapPadded(ReadOnlySpan.Empty)); + () => key.DecryptKeyWrap(ReadOnlySpan.Empty)); AssertExtensions.Throws( "ciphertext", - () => key.DecryptKeyWrapPadded(ReadOnlySpan.Empty, output)); + () => key.DecryptKeyWrap(ReadOnlySpan.Empty, output)); AssertExtensions.Throws( "ciphertext", - () => key.TryDecryptKeyWrapPadded(ReadOnlySpan.Empty, output, out _)); + () => key.TryDecryptKeyWrap(ReadOnlySpan.Empty, output, out _)); - Assert.Equal(0, key.DecryptKeyWrapPaddedCallCount); + Assert.Equal(0, key.DecryptKeyWrapCallCount); } } [Fact] public static void DecryptNeverCalledWithPartialBlocks() { - byte[] input = new byte[33]; + byte[] input = new byte[129]; + byte[] buffer = new byte[input.Length]; using (TestAes key = new TestAes()) { - key.DecryptOverride = (source, destination) => source.Length - 15; + key.DecryptOverride = (source, destination) => source.Length - 8; - Assert.ThrowsAny(() => key.DecryptKeyWrapPadded(Array.Empty())); - Assert.Equal(0, key.DecryptKeyWrapPaddedCallCount); + Assert.ThrowsAny(() => key.DecryptKeyWrap(Array.Empty())); + Assert.Equal(0, key.DecryptKeyWrapCallCount); int expectedCallCount = 0; + const int MinCiphertextLength = 24; - for (int i = input.Length; i > 0; i--) + for (int i = input.Length; i >= 0; i--) { - if (i % 8 == 0 && i > 8) + if (i % 8 == 0 && i >= MinCiphertextLength) { // Assert.NoThrow - key.DecryptKeyWrapPadded(new ReadOnlySpan(input, 0, i)); + key.DecryptKeyWrap(new ReadOnlySpan(input, 0, i)); expectedCallCount++; } else { AssertExtensions.Throws( "ciphertext", - () => key.DecryptKeyWrapPadded(new ReadOnlySpan(input, 0, i))); + () => key.DecryptKeyWrap(new ReadOnlySpan(input, 0, i))); + + AssertExtensions.Throws( + "ciphertext", + () => key.TryDecryptKeyWrap(new ReadOnlySpan(input, 0, i), buffer, out _)); } - Assert.Equal(expectedCallCount, key.DecryptKeyWrapPaddedCallCount); + Assert.Equal(expectedCallCount, key.DecryptKeyWrapCallCount); } } } [Fact] - public static void DecryptMustReportMinimumLength() + public static void EncryptNeverCalledWithPartialBlocks() { + byte[] input = new byte[32]; + byte[] buffer = new byte[input.Length + 8]; + using (TestAes key = new TestAes()) { - key.DecryptOverride = (source, destination) => source.Length - 16; - byte[] input = new byte[32]; + key.EncryptOverride = (source, destination) => {}; - Assert.Throws(() => key.DecryptKeyWrapPadded(input)); - Assert.Equal(1, key.DecryptKeyWrapPaddedCallCount); - key.DecryptOverride = (source, destination) => source.Length - 15; - byte[] ret = key.DecryptKeyWrapPadded(input); - Assert.Equal(17, ret.Length); - Assert.Equal(2, key.DecryptKeyWrapPaddedCallCount); + Assert.Throws(() => key.EncryptKeyWrap(new byte[15])); + Assert.Throws(() => key.EncryptKeyWrap(new ReadOnlySpan(new byte[15]))); + Assert.Throws(() => key.EncryptKeyWrap(new ReadOnlySpan(new byte[15]), buffer)); + Assert.Equal(0, key.EncryptKeyWrapCallCount); + + int expectedCallCount = 0; + const int MinPlaintextLength = 16; + + for (int i = input.Length; i >= 0; i--) + { + if (i % 8 == 0 && i >= MinPlaintextLength) + { + // Assert.NoThrow + key.EncryptKeyWrap(new ReadOnlySpan(input, 0, i)); + expectedCallCount++; + } + else + { + AssertExtensions.Throws( + "plaintext", + () => key.EncryptKeyWrap(new ReadOnlySpan(input, 0, i))); + + AssertExtensions.Throws( + "plaintext", + () => key.EncryptKeyWrap(new ReadOnlySpan(input, 0, i), buffer)); + } + + Assert.Equal(expectedCallCount, key.EncryptKeyWrapCallCount); + } } } [Fact] - public static void ExcessiveLengthFromDecrypt() + public static void DecryptMustReportCorrectLength() { using (TestAes key = new TestAes()) { byte[] input = new byte[32]; - byte[] output = new byte[32]; - int retLen = input.Length; + byte[] output = new byte[24]; int expectedCallCount = 0; - key.DecryptOverride = (source, destination) => retLen; - for (; retLen > input.Length - 8; retLen--) + foreach (int reportedLength in new[] { 23, 25 }) { - Assert.Throws(() => key.DecryptKeyWrapPadded(input)); - Assert.Equal(++expectedCallCount, key.DecryptKeyWrapPaddedCallCount); + key.DecryptOverride = + (source, destination) => + { + destination.Fill(0xDD); + return reportedLength; + }; + + Assert.Throws(() => key.DecryptKeyWrap(input)); + Assert.Equal(++expectedCallCount, key.DecryptKeyWrapCallCount); + + Assert.Throws( + () => key.DecryptKeyWrap(new ReadOnlySpan(input))); - Assert.Throws(() => key.DecryptKeyWrapPadded(new ReadOnlySpan(input))); - Assert.Equal(++expectedCallCount, key.DecryptKeyWrapPaddedCallCount); + Assert.Equal(++expectedCallCount, key.DecryptKeyWrapCallCount); - Assert.Throws(() => key.DecryptKeyWrapPadded(input, output)); - Assert.Equal(++expectedCallCount, key.DecryptKeyWrapPaddedCallCount); + output.AsSpan().Fill(0xFF); + Assert.Throws(() => key.DecryptKeyWrap(input, output)); + Assert.Equal(++expectedCallCount, key.DecryptKeyWrapCallCount); + AssertExtensions.TrueExpression(output.IndexOfAnyExcept((byte)0) == -1); - Assert.Throws(() => key.TryDecryptKeyWrapPadded(input, output, out _)); - Assert.Equal(++expectedCallCount, key.DecryptKeyWrapPaddedCallCount); + output.AsSpan().Fill(0xFF); + Assert.Throws(() => key.TryDecryptKeyWrap(input, output, out _)); + Assert.Equal(++expectedCallCount, key.DecryptKeyWrapCallCount); + AssertExtensions.TrueExpression(output.IndexOfAnyExcept((byte)0) == -1); } - byte[] ret = key.DecryptKeyWrapPadded(input); - Assert.Equal(++expectedCallCount, key.DecryptKeyWrapPaddedCallCount); - Assert.Equal(retLen, ret.Length); + key.DecryptOverride = (source, destination) => source.Length - 8; + byte[] ret = key.DecryptKeyWrap(input); + Assert.Equal(24, ret.Length); + Assert.Equal(++expectedCallCount, key.DecryptKeyWrapCallCount); + } + } + + [Fact] + public static void DecryptClearsDestinationWhenVirtualThrows() + { + byte[] input = new byte[24]; + byte[] output = new byte[24]; + const int OutputLength = 16; + const byte PreFill = 0xB5; + + using (TestAes key = new TestAes()) + { + key.DecryptOverride = + (source, destination) => + { + destination.Fill(0xDD); + throw new CryptographicException(); + }; + + Array.Fill(output, PreFill); + Assert.Throws(() => key.DecryptKeyWrap(input, output)); + Assert.Equal(1, key.DecryptKeyWrapCallCount); + AssertExtensions.TrueExpression(output.AsSpan(0, OutputLength).IndexOfAnyExcept((byte)0) == -1); + AssertExtensions.TrueExpression(output.AsSpan(OutputLength).IndexOfAnyExcept(PreFill) == -1); + + Array.Fill(output, PreFill); + Assert.Throws(() => key.TryDecryptKeyWrap(input, output, out _)); + Assert.Equal(2, key.DecryptKeyWrapCallCount); + AssertExtensions.TrueExpression(output.AsSpan(0, OutputLength).IndexOfAnyExcept((byte)0) == -1); + AssertExtensions.TrueExpression(output.AsSpan(OutputLength).IndexOfAnyExcept(PreFill) == -1); } } @@ -251,7 +332,7 @@ public static void EncryptAlwaysSeesSource() { using (TestAes key = new TestAes()) { - byte[] input = new byte[32]; + byte[] input = new byte[64]; int callLen = input.Length; key.EncryptOverride = @@ -260,9 +341,9 @@ public static void EncryptAlwaysSeesSource() AssertExtensions.TrueExpression(source.Overlaps(input)); }; - for (; callLen > 0; callLen--) + for (; callLen >= 16; callLen -= 8) { - key.EncryptKeyWrapPadded(input.AsSpan(0, callLen)); + key.EncryptKeyWrap(input.AsSpan(0, callLen)); } } } @@ -279,31 +360,31 @@ public static void EncryptNeverSeesInexactDestination() key.EncryptOverride = (source, destination) => { - Assert.Equal(Aes.GetKeyWrapPaddedLength(source.Length), destination.Length); + Assert.Equal(Aes.GetKeyWrapLength(source.Length), destination.Length); AssertExtensions.TrueExpression(destination.Overlaps(output, out int offset)); Assert.Equal(0, offset); }; - int correctLength = Aes.GetKeyWrapPaddedLength(input.Length); + int correctLength = Aes.GetKeyWrapLength(input.Length); for (int i = 0; i <= output.Length; i++) { if (i == correctLength) { // Assert.NoThrow - key.EncryptKeyWrapPadded(input, output.AsSpan(0, i)); - Assert.Equal(++expectedCallCount, key.EncryptKeyWrapPaddedCallCount); + key.EncryptKeyWrap(input, output.AsSpan(0, i)); + Assert.Equal(++expectedCallCount, key.EncryptKeyWrapCallCount); } else { AssertExtensions.Throws( "destination", - () => key.EncryptKeyWrapPadded(input, output.AsSpan(0, i))); - Assert.Equal(expectedCallCount, key.EncryptKeyWrapPaddedCallCount); + () => key.EncryptKeyWrap(input, output.AsSpan(0, i))); + Assert.Equal(expectedCallCount, key.EncryptKeyWrapCallCount); } } - Assert.Equal(1, key.EncryptKeyWrapPaddedCallCount); + Assert.Equal(1, key.EncryptKeyWrapCallCount); } } @@ -312,121 +393,54 @@ public static void DecryptNeverSeesSmallDestination() { using (TestAes key = new TestAes()) { - byte[] input = new byte[32]; - byte[] output = new byte[32]; + byte[] input = new byte[64]; + byte[] output = new byte[64]; int callLen = output.Length; int expectedCallCount = 0; key.DecryptOverride = (source, destination) => { - int maxOutput = source.Length - 8; - Assert.Equal(maxOutput, destination.Length); - - if (callLen >= maxOutput) - { - AssertExtensions.TrueExpression(destination.Overlaps(output, out int offset)); - Assert.Equal(0, offset); - } - else - { - AssertExtensions.FalseExpression(destination.Overlaps(output)); - } - - return source.Length - 15; + // Since decrypt is unpadded we never need to rent a destination buffer, so we should always + // be decrypting into the caller supplied buffer. + int outputLength = source.Length - 8; + Assert.Equal(outputLength, destination.Length); + AssertExtensions.TrueExpression(destination.Overlaps(output, out int offset)); + Assert.Equal(0, offset); + return source.Length - 8; }; - for (; callLen > input.Length - 16; callLen--) + for (; callLen >= input.Length - 8; callLen--) { - key.DecryptKeyWrapPadded(input, output.AsSpan(0, callLen)); - Assert.Equal(++expectedCallCount, key.DecryptKeyWrapPaddedCallCount); + key.DecryptKeyWrap(input, output.AsSpan(0, callLen)); + Assert.Equal(++expectedCallCount, key.DecryptKeyWrapCallCount); - AssertExtensions.TrueExpression(key.TryDecryptKeyWrapPadded(input, output.AsSpan(0, callLen), out _)); - Assert.Equal(++expectedCallCount, key.DecryptKeyWrapPaddedCallCount); + AssertExtensions.TrueExpression(key.TryDecryptKeyWrap(input, output.AsSpan(0, callLen), out _)); + Assert.Equal(++expectedCallCount, key.DecryptKeyWrapCallCount); } // Now that callLen is too short, we should get an ArgumentException with no increase in call count. AssertExtensions.Throws( "destination", - () => key.DecryptKeyWrapPadded(input, output.AsSpan(0, callLen))); + () => key.DecryptKeyWrap(input, output.AsSpan(0, callLen))); - Assert.Equal(expectedCallCount, key.DecryptKeyWrapPaddedCallCount); + Assert.Equal(expectedCallCount, key.DecryptKeyWrapCallCount); + AssertExtensions.TrueExpression(expectedCallCount > 0); // TryDecrypt doesn't throw, but also doesn't call the virtual - AssertExtensions.FalseExpression(key.TryDecryptKeyWrapPadded(input, output.AsSpan(0, callLen), out _)); - Assert.Equal(expectedCallCount, key.DecryptKeyWrapPaddedCallCount); - } - } - - [Fact] - public static void BaseClassForcesZeroPadding() - { - // To make it permissible for a derived class to perform the unwrap in a different buffer, - // and only copy over bytesWritten bytes to the destination, the base class forces any - // extra bytes in destination to be zeroed out. - // This test shows that works. - - using (TestAes key = new TestAes()) - { - ReadOnlySpan input = stackalloc byte[24]; - Span output = stackalloc byte[32]; - int retLen = 0; - int outputOffset = 0; - - int minOutput = input.Length - 15; - int maxOutput = input.Length - 8; - - const byte PreFill = 0xEE; - const byte CallFill = 0xCC; - - key.DecryptOverride = - (source, destination) => - { - destination.Fill(CallFill); - return retLen; - }; - - for (int outputLen = minOutput; outputLen <= output.Length; outputLen++) - { - outputOffset = (output.Length - outputLen + 1) / 2; - Span destination = output.Slice(outputOffset, outputLen); - ReadOnlySpan trimmedDest = destination; - - if (trimmedDest.Length > maxOutput) - { - trimmedDest = trimmedDest.Slice(0, maxOutput); - } - - ReadOnlySpan preDest = output.Slice(0, outputOffset); - ReadOnlySpan postDest = output.Slice(outputOffset + trimmedDest.Length); - - for (retLen = minOutput; retLen <= int.Min(outputLen, maxOutput); retLen++) - { - output.Fill(PreFill); - int ret = key.DecryptKeyWrapPadded(input, destination); - Assert.Equal(retLen, ret); - - ReadOnlySpan padding = trimmedDest.Slice(retLen); - ReadOnlySpan answer = trimmedDest.Slice(0, retLen); - - AssertExtensions.TrueExpression(padding.IndexOfAnyExcept((byte)0) == -1); - AssertExtensions.TrueExpression(answer.IndexOfAnyExcept(CallFill) == -1); - AssertExtensions.TrueExpression(preDest.IndexOfAnyExcept(PreFill) == -1); - AssertExtensions.TrueExpression(postDest.IndexOfAnyExcept(PreFill) == -1); - } - } + AssertExtensions.FalseExpression(key.TryDecryptKeyWrap(input, output.AsSpan(0, callLen), out _)); + Assert.Equal(expectedCallCount, key.DecryptKeyWrapCallCount); } } [Fact] - public static void DecryptCallsVirtualWhenDestinationIsPlausible() + public static void DecryptCallsVirtualWhenDestinationIsBigEnough() { using (TestAes key = new TestAes()) { byte[] input = new byte[24]; byte[] output = new byte[32]; - int retLen = input.Length - 9; - int maxOutput = input.Length - 8; + int retLen = input.Length - 8; int expectedCallCount = 0; const byte CallFill = 0xDD; @@ -435,15 +449,14 @@ public static void DecryptCallsVirtualWhenDestinationIsPlausible() key.DecryptOverride = (source, destination) => { - destination[0..^1].Fill(CallFill); - destination[^1] = 0; - return destination.Length - 1; + destination.Fill(CallFill); + return destination.Length; }; - for (int outputLen = output.Length; outputLen > input.Length - 16; outputLen--) + for (int outputLen = output.Length; outputLen >= 0; outputLen--) { int outputOffset = (output.Length - outputLen + 1) / 2; - int trimmedLen = int.Min(maxOutput, outputLen); + int trimmedLen = int.Min(retLen, outputLen); Span destination = output.AsSpan(outputOffset, outputLen); ReadOnlySpan preDest = output.AsSpan(0, outputOffset); @@ -452,22 +465,19 @@ public static void DecryptCallsVirtualWhenDestinationIsPlausible() if (outputLen >= retLen) { Array.Fill(output, PreFill); - int ret = key.DecryptKeyWrapPadded(input, destination); - Assert.Equal(++expectedCallCount, key.DecryptKeyWrapPaddedCallCount); + int ret = key.DecryptKeyWrap(input, destination); + Assert.Equal(++expectedCallCount, key.DecryptKeyWrapCallCount); ReadOnlySpan answer = destination.Slice(0, retLen); - ReadOnlySpan padding = destination.Slice(retLen, trimmedLen - retLen); - AssertExtensions.TrueExpression(padding.IndexOfAnyExcept((byte)0) == -1); AssertExtensions.TrueExpression(answer.IndexOfAnyExcept(CallFill) == -1); AssertExtensions.TrueExpression(preDest.IndexOfAnyExcept(PreFill) == -1); AssertExtensions.TrueExpression(postDest.IndexOfAnyExcept(PreFill) == -1); Array.Fill(output, PreFill); - AssertExtensions.TrueExpression(key.TryDecryptKeyWrapPadded(input, destination, out ret)); - Assert.Equal(++expectedCallCount, key.DecryptKeyWrapPaddedCallCount); + AssertExtensions.TrueExpression(key.TryDecryptKeyWrap(input, destination, out ret)); + Assert.Equal(++expectedCallCount, key.DecryptKeyWrapCallCount); - AssertExtensions.TrueExpression(padding.IndexOfAnyExcept((byte)0) == -1); AssertExtensions.TrueExpression(answer.IndexOfAnyExcept(CallFill) == -1); AssertExtensions.TrueExpression(preDest.IndexOfAnyExcept(PreFill) == -1); AssertExtensions.TrueExpression(postDest.IndexOfAnyExcept(PreFill) == -1); @@ -478,14 +488,14 @@ public static void DecryptCallsVirtualWhenDestinationIsPlausible() AssertExtensions.Throws( "destination", - () => key.DecryptKeyWrapPadded(input, output.AsSpan(outputOffset, outputLen))); + () => key.DecryptKeyWrap(input, output.AsSpan(outputOffset, outputLen))); - Assert.Equal(++expectedCallCount, key.DecryptKeyWrapPaddedCallCount); + Assert.Equal(expectedCallCount, key.DecryptKeyWrapCallCount); AssertExtensions.TrueExpression(output.IndexOfAnyExcept(PreFill) == -1); Array.Fill(output, PreFill); - AssertExtensions.FalseExpression(key.TryDecryptKeyWrapPadded(input, destination, out int ret)); - Assert.Equal(++expectedCallCount, key.DecryptKeyWrapPaddedCallCount); + AssertExtensions.FalseExpression(key.TryDecryptKeyWrap(input, destination, out int ret)); + Assert.Equal(expectedCallCount, key.DecryptKeyWrapCallCount); AssertExtensions.TrueExpression(output.IndexOfAnyExcept(PreFill) == -1); Assert.Equal(0, ret); } @@ -496,46 +506,46 @@ public static void DecryptCallsVirtualWhenDestinationIsPlausible() [Fact] public static void NoOverlapForEncrypt() { - byte[] buffer = new byte[32]; + byte[] buffer = new byte[40]; using (TestAes key = new TestAes()) { AssertExtensions.Throws( - () => key.EncryptKeyWrapPadded(buffer.AsSpan(15, 8), buffer.AsSpan(0, 16))); + () => key.EncryptKeyWrap(buffer.AsSpan(24, 16), buffer.AsSpan(1, 24))); - Assert.Equal(0, key.EncryptKeyWrapPaddedCallCount); + Assert.Equal(0, key.EncryptKeyWrapCallCount); key.EncryptOverride = (source, destination) => { }; // Adjacent is OK - key.EncryptKeyWrapPadded(buffer.AsSpan(16, 8), buffer.AsSpan(0, 16)); - Assert.Equal(1, key.EncryptKeyWrapPaddedCallCount); + key.EncryptKeyWrap(buffer.AsSpan(24, 16), buffer.AsSpan(0, 24)); + Assert.Equal(1, key.EncryptKeyWrapCallCount); } } [Fact] public static void NoOverlapForDecrypt() { - byte[] buffer = new byte[32]; + byte[] buffer = new byte[40]; using (TestAes key = new TestAes()) { AssertExtensions.Throws( - () => key.DecryptKeyWrapPadded(buffer.AsSpan(0, 16), buffer.AsSpan(15, 8))); + () => key.DecryptKeyWrap(buffer.AsSpan(0, 24), buffer.AsSpan(23, 16))); AssertExtensions.Throws( - () => key.TryDecryptKeyWrapPadded(buffer.AsSpan(0, 16), buffer.AsSpan(15, 8), out _)); + () => key.TryDecryptKeyWrap(buffer.AsSpan(0, 24), buffer.AsSpan(23, 16), out _)); - Assert.Equal(0, key.EncryptKeyWrapPaddedCallCount); + Assert.Equal(0, key.DecryptKeyWrapCallCount); - key.DecryptOverride = (source, destination) => destination.Length - 1; + key.DecryptOverride = (source, destination) => source.Length - 8; // Adjacent is OK - key.DecryptKeyWrapPadded(buffer.AsSpan(0, 16), buffer.AsSpan(16, 8)); - Assert.Equal(1, key.DecryptKeyWrapPaddedCallCount); + key.DecryptKeyWrap(buffer.AsSpan(0, 24), buffer.AsSpan(24, 16)); + Assert.Equal(1, key.DecryptKeyWrapCallCount); - AssertExtensions.TrueExpression(key.TryDecryptKeyWrapPadded(buffer.AsSpan(0, 16), buffer.AsSpan(16, 8), out _)); - Assert.Equal(2, key.DecryptKeyWrapPaddedCallCount); + AssertExtensions.TrueExpression(key.TryDecryptKeyWrap(buffer.AsSpan(0, 24), buffer.AsSpan(24, 16), out _)); + Assert.Equal(2, key.DecryptKeyWrapCallCount); } } @@ -544,8 +554,8 @@ private class TestAes : Aes public delegate void EncryptCallback(ReadOnlySpan source, Span destination); public delegate int DecryptCallback(ReadOnlySpan source, Span destination); - public int EncryptKeyWrapPaddedCallCount { get; private set; } - public int DecryptKeyWrapPaddedCallCount { get; private set; } + public int EncryptKeyWrapCallCount { get; private set; } + public int DecryptKeyWrapCallCount { get; private set; } public EncryptCallback EncryptOverride { get; set; } public DecryptCallback DecryptOverride { get; set; } @@ -569,9 +579,9 @@ public override ICryptoTransform CreateEncryptor(byte[] rgbKey, byte[]? rgbIV) return null; } - protected override void EncryptKeyWrapPaddedCore(ReadOnlySpan source, Span destination) + protected override void EncryptKeyWrapCore(ReadOnlySpan source, Span destination) { - EncryptKeyWrapPaddedCallCount++; + EncryptKeyWrapCallCount++; if (EncryptOverride is not null) { @@ -579,20 +589,20 @@ protected override void EncryptKeyWrapPaddedCore(ReadOnlySpan source, Span } else { - Assert.Fail("Unexpected call to EncryptKeyWrapPaddedCore"); + Assert.Fail("Unexpected call to EncryptKeyWrapCore"); } } - protected override int DecryptKeyWrapPaddedCore(ReadOnlySpan source, Span destination) + protected override int DecryptKeyWrapCore(ReadOnlySpan source, Span destination) { - DecryptKeyWrapPaddedCallCount++; + DecryptKeyWrapCallCount++; if (DecryptOverride is not null) { return DecryptOverride(source, destination); } - Assert.Fail("Unexpected call to EncryptKeyWrapPaddedCore"); + Assert.Fail("Unexpected call to EncryptKeyWrapCore"); return -1; } } @@ -614,247 +624,100 @@ public void VerifyKnownAnswer(KnownAnswerTest kat) } [Theory] - [InlineData(128, 1, 16)] - [InlineData(128, 96, 103)] - [InlineData(192, 1, 16)] - [InlineData(192, 96, 103)] - [InlineData(256, 1, 16)] - [InlineData(256, 96, 103)] - public void VerifyRoundtrip(int kekSize, int ptMin, int ptMax) + [InlineData(128, 16)] + [InlineData(128, 96)] + [InlineData(128, 128)] + [InlineData(192, 16)] + [InlineData(192, 96)] + [InlineData(192, 128)] + [InlineData(256, 16)] + [InlineData(256, 96)] + [InlineData(256, 128)] + public void VerifyRoundtrip(int kekSize, int plaintextSize) { byte[] kek = new byte[kekSize / 8]; RandomNumberGenerator.Fill(kek); using (Aes key = CreateKey(kek)) { - for (int i = ptMin; i <= ptMax; i++) - { - // Round plaintext up to the nearest multiple of 8, - // and add the 8 bytes for the IV semi-block. - int expectedSize = (i + 7) / 8 * 8 + 8; + int expectedSize = plaintextSize + 8; - byte[] plaintext = new byte[i]; - RandomNumberGenerator.Fill(plaintext); - byte[] ciphertext = key.EncryptKeyWrapPadded(plaintext); - Assert.Equal(expectedSize, ciphertext.Length); + byte[] plaintext = new byte[plaintextSize]; + RandomNumberGenerator.Fill(plaintext); + byte[] ciphertext = key.EncryptKeyWrap(plaintext); + Assert.Equal(expectedSize, ciphertext.Length); - VerifyUnwrap(key, ciphertext, plaintext); - VerifyWrap(key, plaintext, ciphertext); - } + VerifyUnwrap(key, ciphertext, plaintext); + VerifyWrap(key, plaintext, ciphertext); } } - [Fact] - public void UnwrapBadIV_SingleBlock() - { - // At the end of unwrap, the header block will have the incorrect value A65959A7. - byte[] kek = "9FC9E4BA68CA3EC8BAC82B02223EADDAAA1A67350E12510D0016083095B32BBC".HexToByteArray(); - byte[] ciphertext = "1B2DE25B6990AA8B74087499294ECB39".HexToByteArray(); - - VerifyUnwrapFails(kek, ciphertext); - } - - [Fact] - public void UnwrapBadIV_MultiBlock() - { - // At the end of unwrap, the header block will have the incorrect value A65959A7. - byte[] kek = "B1D18A0296DF025443EE1677ED783FB6C137A98814E09FE1".HexToByteArray(); - byte[] ciphertext = ( - "67A3F00F801" + - "A1CDAFF2D324C7AC393EB97938556FA8D54C5DB303F9EBB6321B84BCED6DD3A80EC98B3047110" + - "89A8EF9ADADA14A3ADD324E55BEFB6A5598ABB90A40CA8F36CB175498FAA3BDC11FDAC1113042" + - "E3229B790FA4BD0240830933FA9D0C8255CD271D5B7C301DDF85F098C62").HexToByteArray(); - - VerifyUnwrapFails(kek, ciphertext); - } - - [Fact] - public void UnwrapLengthTooBig_Single() - { - // At the end of unwrap, the length segment will report 8 bytes more than the original input was, - // which requires reading beyond the end of the processed buffer. - byte[] kek = "3D7C64D35E1CEC5BBEA04867073F5E9F6DB671B28EA325215FA6DA3B1B561F48".HexToByteArray(); - byte[] ciphertext = "9EDBCAFD999E7A1CEEC4529DC192797E".HexToByteArray(); - - VerifyUnwrapFails(kek, ciphertext); - } - - [Fact] - public void UnwrapLengthTooBig_MultiBlock() - { - // At the end of unwrap, the length segment will report 8 bytes more than the original input was, - // which requires reading beyond the end of the processed buffer. - byte[] kek = "3D7C64D35E1CEC5BBEA04867073F5E9F6DB671B28EA325215FA6DA3B1B561F48".HexToByteArray(); - byte[] ciphertext = "1A91401A927296BEF253F857C6124B20A2FEFB580FF472F5".HexToByteArray(); - - VerifyUnwrapFails(kek, ciphertext); - } - - [Fact] - public void UnwrapLengthZero_Single() - { - // At the end of unwrap, the length segment will report zero, which is always invalid. - byte[] kek = "B870467A475D675AEE893430A09FD77F".HexToByteArray(); - byte[] ciphertext = "024D1848259597D20FFDCE39BC3E461D".HexToByteArray(); - - VerifyUnwrapFails(kek, ciphertext); - } - - [Fact] - public void UnwrapLengthZero_MultiBlock() - { - // At the end of unwrap, the length segment will report zero, which is always invalid. - byte[] kek = "DA536B4D274173D0DAD5DBB8FF21F6E27AC8BB6F9E12F51A".HexToByteArray(); - byte[] ciphertext = "29288FFE637F4CCBF3D44FEAB22300C67796C14AAFB682E3".HexToByteArray(); - - VerifyUnwrapFails(kek, ciphertext); - } - - [Fact] - public void UnwrapBadPadding_Single() - { - // At the end of unwrap, some of the "padding" bytes will be non-zero. - byte[] kek = "838E662F79DC11058ED1EC27928DE835119BAC751B689A1DFC09011BD634842E".HexToByteArray(); - byte[] ciphertext = "E9D57F431E9A9A2878E6629A890E4C3E".HexToByteArray(); - - VerifyUnwrapFails(kek, ciphertext); - } - - [Fact] - public void UnwrapBadPadding_MultiBlock() + [Theory] + [MemberData(nameof(KnownAnswerTests))] + public void RejectsTamperedCiphertext(KnownAnswerTest kat) { - // At the end of unwrap, some of the "padding" bytes will be non-zero. - byte[] kek = "6BA88BFEA55ECE448898BFEE524244B965C5EB3CADA463E0".HexToByteArray(); - byte[] ciphertext = "852CA39B8A1DE2FD2EF10DA6F01AF860F1DF6E16F0593E85".HexToByteArray(); + byte[] tampered = (byte[])kat.Ciphertext.Clone(); + const byte TamperedBit = 1 << 2; - VerifyUnwrapFails(kek, ciphertext); + for (int i = 0; i < tampered.Length; i++) + { + tampered[i] ^= TamperedBit; + VerifyUnwrapFails(kat.Key, tampered); + tampered[i] ^= TamperedBit; // Put the tampered bit back so only one bit is tampered at a time. + } } - [Fact] - public void UnwrapLengthTooShort() + [Theory] + [InlineData("079E449C7E8504B8D559EDA0387724C78820C1E93F4F9716")] + [InlineData("BAB95D7021F1196EE8BC5146D20167F58362B46EED49CB9E")] + public void RejectsIncorrectInitialValue(string ciphertextHex) { - // At the end of unwrap, this length will report 8 less than the original input was, - // which means the ciphertext should have had one block fewer than it does. - // - // Subtracting 8 from the length of a single-block ciphertext is either zero (already covered), - // or "extremely large" (already covered), so there is not a single-block variant of this. - byte[] kek = "7F0B9A269A182935200F4D92FFE291F94D132D9FBCB8982F".HexToByteArray(); - byte[] ciphertext = "9C21E8325D1D7406DE94B2009D3E67152EE6C7DBC0E5B911".HexToByteArray(); + // Each of these chosen ciphertexts produces a recovered IV that is off by a single bit, one in the top 32-bit + // half and the other in the lower 32-bit half (A7A6A6A6A6A6A6A6 and A6A6A6A6A7A6A6A6, respectively). + byte[] kek = "000102030405060708090A0B0C0D0E0F".HexToByteArray(); + byte[] ciphertext = ciphertextHex.HexToByteArray(); VerifyUnwrapFails(kek, ciphertext); } private static void VerifyWrap(Aes key, byte[] plaintext, byte[] ciphertext) { - // EncryptKeyWrapPadded(byte[]) - byte[] wrapped = key.EncryptKeyWrapPadded(plaintext); + // EncryptKeyWrap(byte[]) + byte[] wrapped = key.EncryptKeyWrap(plaintext); AssertExtensions.SequenceEqual(ciphertext, wrapped); - // EncryptKeyWrapPadded(ReadOnlySpan) - wrapped = key.EncryptKeyWrapPadded(new ReadOnlySpan(plaintext)); + // EncryptKeyWrap(ReadOnlySpan) + wrapped = key.EncryptKeyWrap(new ReadOnlySpan(plaintext)); AssertExtensions.SequenceEqual(ciphertext, wrapped); - // void EncryptKeyWrapPadded(ReadOnlySpan, Span) + // void EncryptKeyWrap(ReadOnlySpan, Span) Array.Clear(wrapped); - key.EncryptKeyWrapPadded(plaintext, wrapped.AsSpan()); + key.EncryptKeyWrap(plaintext, wrapped.AsSpan()); AssertExtensions.SequenceEqual(ciphertext, wrapped); } private static void VerifyUnwrap(Aes key, byte[] ciphertext, byte[] plaintext) { - // DecryptKeyWrapPadded(byte[]) - byte[] unwrapped = key.DecryptKeyWrapPadded(ciphertext); + // DecryptKeyWrap(byte[]) + byte[] unwrapped = key.DecryptKeyWrap(ciphertext); AssertExtensions.SequenceEqual(plaintext, unwrapped); - // DecryptKeyWrapPadded(ReadOnlySpan) - unwrapped = key.DecryptKeyWrapPadded(new ReadOnlySpan(ciphertext)); + // DecryptKeyWrap(ReadOnlySpan) + unwrapped = key.DecryptKeyWrap(new ReadOnlySpan(ciphertext)); AssertExtensions.SequenceEqual(plaintext, unwrapped); - byte[] tooBig = new byte[ciphertext.Length]; - tooBig.AsSpan().Fill(0xFF); - int maxOutput = ciphertext.Length - 8; - int minOutput = maxOutput - 7; - int expectedPadding = maxOutput - plaintext.Length; - ReadOnlySpan paddingSlice = tooBig.AsSpan(plaintext.Length, expectedPadding); - ReadOnlySpan untouchedSlice = tooBig.AsSpan(maxOutput); - - // The tooBig buffer will be composed of [the right answer][padding][bytes that are not touched] - // Our slice point in this loop is always within [bytes that are not touched], so we expect all - // padding bytes to be written (as 0) and all untouched bytes to remain 0xFF. - for (int i = tooBig.Length; i >= maxOutput; i--) - { - Span targetSlice = tooBig.AsSpan(0, i); - int written = key.DecryptKeyWrapPadded(ciphertext, targetSlice); - ReadOnlySpan answerSlice = targetSlice.Slice(0, written); - // SequenceEqual will also check that `written` is correct - AssertExtensions.SequenceEqual(plaintext, answerSlice); - - // Since `written` is correct, paddingSlice and untouchedSlice are sliced correctly. - AssertExtensions.TrueExpression(paddingSlice.IndexOfAnyExcept((byte)0) == -1); - AssertExtensions.TrueExpression(untouchedSlice.IndexOfAnyExcept((byte)0xFF) == -1); - - // Repeat with TryDecryptKeyWrapPadded - tooBig.AsSpan().Fill(0xFF); - AssertExtensions.TrueExpression(key.TryDecryptKeyWrapPadded(ciphertext, targetSlice, out written)); - answerSlice = targetSlice.Slice(0, written); - // SequenceEqual will also check that `written` is correct - AssertExtensions.SequenceEqual(plaintext, answerSlice); - - // Since `written` is correct, paddingSlice and untouchedSlice are sliced correctly. - AssertExtensions.TrueExpression(paddingSlice.IndexOfAnyExcept((byte)0) == -1); - AssertExtensions.TrueExpression(untouchedSlice.IndexOfAnyExcept((byte)0xFF) == -1); - } - - // In this loop, the input buffer is plausibly big enough, but not guaranteed big enough, - // so the implementation is going to use rented space to compute the unwrap. - // - // Any surplus bytes should be set to 0 (as the padding), and we can still assert that - // the untouched range is 0xFF, but this loop never even sees it. - for (int i = maxOutput - 1; i >= plaintext.Length; i--) - { - Span targetSlice = tooBig.AsSpan(0, i); - untouchedSlice = targetSlice.Slice(i); - - int written = key.DecryptKeyWrapPadded(ciphertext, targetSlice); - ReadOnlySpan answerSlice = targetSlice.Slice(0, written); - // SequenceEqual will also check that `written` is correct - AssertExtensions.SequenceEqual(plaintext, answerSlice); - - paddingSlice = targetSlice.Slice(written); - AssertExtensions.TrueExpression(paddingSlice.IndexOfAnyExcept((byte)0) == -1); - AssertExtensions.TrueExpression(untouchedSlice.IndexOfAnyExcept((byte)0xFF) == -1); - - // Repeat with TryDecryptKeyWrapPadded - tooBig.AsSpan().Fill(0xFF); - AssertExtensions.TrueExpression(key.TryDecryptKeyWrapPadded(ciphertext, targetSlice, out written)); - answerSlice = targetSlice.Slice(0, written); - // SequenceEqual will also check that `written` is correct - AssertExtensions.SequenceEqual(plaintext, answerSlice); - - // Since `written` is correct, paddingSlice and untouchedSlice are still sliced correctly. - AssertExtensions.TrueExpression(paddingSlice.IndexOfAnyExcept((byte)0) == -1); - AssertExtensions.TrueExpression(untouchedSlice.IndexOfAnyExcept((byte)0xFF) == -1); - } - - tooBig.AsSpan().Fill(0xFF); - - // targetSlice is now too small to hold the plaintext, but that can only be determined after - // running the algorithm. - // In this case, we should never touch the destination buffer. - for (int i = plaintext.Length - 1; i >= minOutput; i--) - { - AssertExtensions.Throws( - "destination", - () => key.DecryptKeyWrapPadded(ciphertext, tooBig.AsSpan(0, i))); - - AssertExtensions.TrueExpression(tooBig.IndexOfAnyExcept((byte)0xFF) == -1); - - AssertExtensions.FalseExpression( - key.TryDecryptKeyWrapPadded(ciphertext, tooBig.AsSpan(0, i), out int written)); - Assert.Equal(0, written); + // DecryptKeyWrap(ReadOnlySpan, Span) + Array.Clear(unwrapped); + int written = key.DecryptKeyWrap(new ReadOnlySpan(ciphertext), unwrapped); + Assert.Equal(unwrapped.Length, written); + AssertExtensions.SequenceEqual(plaintext, unwrapped); - AssertExtensions.TrueExpression(tooBig.IndexOfAnyExcept((byte)0xFF) == -1); - } + // TryDecryptKeyWrap(ReadOnlySpan, Span, out int) + Array.Clear(unwrapped); + bool result = key.TryDecryptKeyWrap(new ReadOnlySpan(ciphertext), unwrapped, out written); + AssertExtensions.TrueExpression(result); + Assert.Equal(unwrapped.Length, written); + AssertExtensions.SequenceEqual(plaintext, unwrapped); } private void VerifyUnwrapFails(byte[] kek, byte[] ciphertext) @@ -862,11 +725,21 @@ private void VerifyUnwrapFails(byte[] kek, byte[] ciphertext) using (Aes key = CreateKey(kek)) { byte[] dest = new byte[ciphertext.Length]; + int plaintextLength = ciphertext.Length - 8; + const byte PreFill = 0xB5; + + Assert.ThrowsAny(() => key.DecryptKeyWrap(ciphertext)); + Assert.ThrowsAny(() => key.DecryptKeyWrap(new ReadOnlySpan(ciphertext))); + + Array.Fill(dest, PreFill); + Assert.ThrowsAny(() => key.DecryptKeyWrap(ciphertext, dest)); + AssertExtensions.TrueExpression(dest.AsSpan(0, plaintextLength).IndexOfAnyExcept((byte)0) == -1); + AssertExtensions.TrueExpression(dest.AsSpan(plaintextLength).IndexOfAnyExcept(PreFill) == -1); - Assert.ThrowsAny(() => key.DecryptKeyWrapPadded(ciphertext)); - Assert.ThrowsAny(() => key.DecryptKeyWrapPadded(new ReadOnlySpan(ciphertext))); - Assert.ThrowsAny(() => key.DecryptKeyWrapPadded(ciphertext, dest)); - Assert.ThrowsAny(() => key.TryDecryptKeyWrapPadded(ciphertext, dest, out _)); + Array.Fill(dest, PreFill); + Assert.ThrowsAny(() => key.TryDecryptKeyWrap(ciphertext, dest, out _)); + AssertExtensions.TrueExpression(dest.AsSpan(0, plaintextLength).IndexOfAnyExcept((byte)0) == -1); + AssertExtensions.TrueExpression(dest.AsSpan(plaintextLength).IndexOfAnyExcept(PreFill) == -1); } } @@ -875,19 +748,55 @@ private void VerifyUnwrapFails(byte[] kek, byte[] ciphertext) new object[] { new KnownAnswerTest( - "RFC 5649 Example 1", - "5840df6e29b02af1ab493b705bf16ea1ae8338f4dcc176a8".HexToByteArray(), - "c37b7e6492584340bed12207808941155068f738".HexToByteArray(), - "138bdeaa9b8fa7fc61f97742e72248ee5ae6ae5360d1ae6a5f54f373fa543b6a".HexToByteArray()) + "RFC 3394 4.1", + "000102030405060708090A0B0C0D0E0F".HexToByteArray(), + "00112233445566778899AABBCCDDEEFF".HexToByteArray(), + "1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5".HexToByteArray()) + }, + + new object[] + { + new KnownAnswerTest( + "RFC 3394 4.2", + "000102030405060708090A0B0C0D0E0F1011121314151617".HexToByteArray(), + "00112233445566778899AABBCCDDEEFF".HexToByteArray(), + "96778B25AE6CA435F92B5B97C050AED2468AB8A17AD84E5D".HexToByteArray()) + }, + + new object[] + { + new KnownAnswerTest( + "RFC 3394 4.3", + "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F".HexToByteArray(), + "00112233445566778899AABBCCDDEEFF".HexToByteArray(), + "64E8C3F9CE0F5BA263E9777905818A2A93C8191E7D6E8AE7".HexToByteArray()) + }, + + new object[] + { + new KnownAnswerTest( + "RFC 3394 4.4", + "000102030405060708090A0B0C0D0E0F1011121314151617".HexToByteArray(), + "00112233445566778899AABBCCDDEEFF0001020304050607".HexToByteArray(), + "031D33264E15D33268F24EC260743EDCE1C6C7DDEE725A936BA814915C6762D2".HexToByteArray()) + }, + + new object[] + { + new KnownAnswerTest( + "RFC 3394 4.5", + "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F".HexToByteArray(), + "00112233445566778899AABBCCDDEEFF0001020304050607".HexToByteArray(), + "A8F9BC1612C68B3FF6E6F4FBE30E71E4769C8B80A32CB8958CD5D17D6B254DA1".HexToByteArray()) }, new object[] { new KnownAnswerTest( - "RFC 5649 Example 2", - "5840df6e29b02af1ab493b705bf16ea1ae8338f4dcc176a8".HexToByteArray(), - "466f7250617369".HexToByteArray(), - "afbeb0f07dfbf5419200f2ccb50bb24f".HexToByteArray()) + "RFC 3394 4.6", + "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F".HexToByteArray(), + "00112233445566778899AABBCCDDEEFF000102030405060708090A0B0C0D0E0F".HexToByteArray(), + "28C9F404C4B810F4CBCCB35CFB87F8263F5786E2D80ED326CBC7F0E71A99F43BFB988B9B7A02DD21".HexToByteArray()) }, ]; diff --git a/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs b/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs index 4a177bcaa21fa3..9e9e74d7baf34f 100644 --- a/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs +++ b/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs @@ -52,15 +52,25 @@ protected Aes() { } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("The default algorithm implementations might be removed, use strong type references like 'RSA.Create()' instead.")] [System.ObsoleteAttribute("Cryptographic factory methods accepting an algorithm name are obsolete. Use the parameterless Create factory method on the algorithm type instead.", DiagnosticId="SYSLIB0045", UrlFormat="https://aka.ms/dotnet-warnings/{0}")] public static new System.Security.Cryptography.Aes? Create(string algorithmName) { throw null; } + public byte[] DecryptKeyWrap(byte[] ciphertext) { throw null; } + public byte[] DecryptKeyWrap(System.ReadOnlySpan ciphertext) { throw null; } + public int DecryptKeyWrap(System.ReadOnlySpan ciphertext, System.Span destination) { throw null; } + protected virtual int DecryptKeyWrapCore(System.ReadOnlySpan source, System.Span destination) { throw null; } public byte[] DecryptKeyWrapPadded(byte[] ciphertext) { throw null; } public byte[] DecryptKeyWrapPadded(System.ReadOnlySpan ciphertext) { throw null; } public int DecryptKeyWrapPadded(System.ReadOnlySpan ciphertext, System.Span destination) { throw null; } protected virtual int DecryptKeyWrapPaddedCore(System.ReadOnlySpan source, System.Span destination) { throw null; } + public byte[] EncryptKeyWrap(byte[] plaintext) { throw null; } + public byte[] EncryptKeyWrap(System.ReadOnlySpan plaintext) { throw null; } + public void EncryptKeyWrap(System.ReadOnlySpan plaintext, System.Span destination) { } + protected virtual void EncryptKeyWrapCore(System.ReadOnlySpan source, System.Span destination) { } public byte[] EncryptKeyWrapPadded(byte[] plaintext) { throw null; } public byte[] EncryptKeyWrapPadded(System.ReadOnlySpan plaintext) { throw null; } public void EncryptKeyWrapPadded(System.ReadOnlySpan plaintext, System.Span destination) { } protected virtual void EncryptKeyWrapPaddedCore(System.ReadOnlySpan source, System.Span destination) { } + public static int GetKeyWrapLength(int plaintextLengthInBytes) { throw null; } public static int GetKeyWrapPaddedLength(int plaintextLengthInBytes) { throw null; } + public bool TryDecryptKeyWrap(System.ReadOnlySpan ciphertext, System.Span destination, out int bytesWritten) { throw null; } public bool TryDecryptKeyWrapPadded(System.ReadOnlySpan ciphertext, System.Span destination, out int bytesWritten) { throw null; } } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] diff --git a/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx b/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx index 2d4865137731a8..1fdd5a3e8257e5 100644 --- a/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx +++ b/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx @@ -573,12 +573,18 @@ The key is too small for the requested operation. + + The ciphertext is not a valid length for the Key Wrap algorithm. + The ciphertext did not successfully decrypt with the instance key. The ciphertext is not a valid length for the Key Wrap with Padding Unwrap algorithm. + + The plaintext is not a valid length for the Key Wrap algorithm. + The specified key is not the correct size for the indicated algorithm. diff --git a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj index 0d1a2027654947..8bd54b9fd5a488 100644 --- a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj +++ b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj @@ -1342,6 +1342,8 @@ Link="Common\Interop\OSX\Swift.Runtime\UnsafeBufferPointer.cs" /> + + /// Computes the output length of the IETF RFC 3394 AES Key Wrap Algorithm + /// for the specified plaintext length. + /// + /// + /// The length of the plaintext to be wrapped, in bytes. + /// + /// + /// The length of the key wrap for the specified plaintext. + /// + /// + /// + /// is less than 16 or is not a multiple of 8. + /// + /// -or- + /// + /// represents a plaintext length + /// that, when wrapped, has a length that cannot be represented as a signed + /// 32-bit integer. + /// + /// + public static int GetKeyWrapLength(int plaintextLengthInBytes) + { + const int MaxSupportedValue = 0x7FFF_FFF0; + const int MinSupportedValue = 16; // RFC 3394 requires at least two 64-bit blocks. + + if (plaintextLengthInBytes < MinSupportedValue || (plaintextLengthInBytes % 8) != 0) + { + throw new ArgumentOutOfRangeException( + nameof(plaintextLengthInBytes), + SR.Cryptography_KeyWrap_Plaintext_InvalidLength); + } + + if (plaintextLengthInBytes > MaxSupportedValue) + { + throw new ArgumentOutOfRangeException( + nameof(plaintextLengthInBytes), + SR.Cryptography_PlaintextTooLarge); + } + + return checked(plaintextLengthInBytes + 8); + } + + /// + /// + /// is . + /// + public byte[] EncryptKeyWrap(byte[] plaintext) + { + ArgumentNullException.ThrowIfNull(plaintext); + return EncryptKeyWrap(new ReadOnlySpan(plaintext)); + } + + /// + /// Wraps a key using the IETF RFC 3394 AES Key Wrap algorithm. + /// + /// The data to wrap. + /// The wrapped data. + /// + /// has a length that is less than 16 bytes or is not a multiple of 8 bytes. + /// + /// An error occurred during the cryptographic operation. + public byte[] EncryptKeyWrap(ReadOnlySpan plaintext) + { + int outputLength = GetKeyWrapCiphertextLength(plaintext); + byte[] output = new byte[outputLength]; + EncryptKeyWrapCore(plaintext, output); + return output; + } + + /// + /// Wraps a key using the IETF RFC 3394 AES Key Wrap algorithm, + /// writing the result to a specified buffer. + /// + /// The data to wrap. + /// The buffer to receive the wrapped data. + /// + /// + /// has a length that is less than 16 bytes or is not a multiple of 8 bytes. + /// + /// -or- + /// + /// is not precisely sized to the value returned by + /// for the plaintext length. + /// + /// + /// + /// and overlap. + /// -or- + /// An error occurred during the cryptographic operation. + /// + /// + public void EncryptKeyWrap(ReadOnlySpan plaintext, Span destination) + { + int requiredLength = GetKeyWrapCiphertextLength(plaintext); + + if (destination.Length != requiredLength) + { + throw new ArgumentException( + SR.Format(SR.Argument_DestinationImprecise, requiredLength), + nameof(destination)); + } + + if (plaintext.Overlaps(destination)) + { + throw new CryptographicException(SR.Cryptography_OverlappingBuffers); + } + + EncryptKeyWrapCore(plaintext, destination); + } + + /// + /// + /// is . + /// + public byte[] DecryptKeyWrap(byte[] ciphertext) + { + ArgumentNullException.ThrowIfNull(ciphertext); + return DecryptKeyWrap(new ReadOnlySpan(ciphertext)); + } + + /// + /// Unwraps a key that was wrapped using the IETF RFC 3394 AES Key Wrap algorithm. + /// + /// The data to unwrap. + /// The unwrapped key. + /// + /// has a length that is less than 24 bytes or is not a multiple of 8 bytes. + /// + /// + /// The unwrap algorithm failed to unwrap the ciphertext. + /// -or- + /// An error occurred during the cryptographic operation. + /// + public byte[] DecryptKeyWrap(ReadOnlySpan ciphertext) + { + int outputLength = GetKeyWrapPlaintextLength(ciphertext); + byte[] output = new byte[outputLength]; + int written; + + try + { + written = DecryptKeyWrapCore(ciphertext, output); + } + catch + { + CryptographicOperations.ZeroMemory(output); + throw; + } + + if (written != outputLength) + { + // The virtual implementation did not write the amount required. + CryptographicOperations.ZeroMemory(output); + throw new CryptographicException(); + } + + return output; + } + + /// + /// Unwraps a key that was wrapped using the IETF RFC 3394 AES Key Wrap algorithm, + /// writing the result to a specified buffer. + /// + /// The data to unwrap. + /// The buffer to receive the unwrapped key. + /// The number of bytes written to . + /// + /// + /// has a length that is less than 24 bytes or is not a multiple of 8 bytes. + /// + /// -or- + /// + /// is too short to receive the unwrapped key. + /// + /// + /// + /// and overlap. + /// -or- + /// The unwrap algorithm failed to unwrap the ciphertext. + /// -or- + /// An error occurred during the cryptographic operation. + /// + public int DecryptKeyWrap(ReadOnlySpan ciphertext, Span destination) + { + if (TryDecryptKeyWrap(ciphertext, destination, out int bytesWritten)) + { + return bytesWritten; + } + + throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination)); + } + + /// + /// Attempts to unwrap a key that was wrapped using the IETF RFC 3394 AES Key Wrap algorithm. + /// + /// The data to unwrap. + /// The buffer to receive the unwrapped key. + /// + /// When this method returns, contains the number of bytes written to . + /// This parameter is treated as uninitialized. + /// + /// + /// if is long enough to receive the unwrapped key; + /// otherwise, . + /// + /// + /// has a length that is less than 24 bytes or is not a multiple of 8 bytes. + /// + /// + /// and overlap. + /// -or- + /// The unwrap algorithm failed to unwrap the ciphertext. + /// -or- + /// An error occurred during the cryptographic operation. + /// + public bool TryDecryptKeyWrap(ReadOnlySpan ciphertext, Span destination, out int bytesWritten) + { + int requiredLength = GetKeyWrapPlaintextLength(ciphertext); + + if (destination.Length < requiredLength) + { + bytesWritten = 0; + return false; + } + + destination = destination.Slice(0, requiredLength); + + if (ciphertext.Overlaps(destination)) + { + throw new CryptographicException(SR.Cryptography_OverlappingBuffers); + } + + int written; + + try + { + written = DecryptKeyWrapCore(ciphertext, destination); + } + catch + { + CryptographicOperations.ZeroMemory(destination); + throw; + } + + if (written != requiredLength) + { + // Even though this method returns an int indicating how much was written, we validate that the + // virtual implementation did the right thing instead of blindly passing it through. + CryptographicOperations.ZeroMemory(destination); + throw new CryptographicException(); + } + + bytesWritten = written; + return true; + } + + /// + /// Unwraps a key that was wrapped using the IETF RFC 3394 AES Key Wrap algorithm. + /// + /// The data to unwrap. + /// The buffer to receive the unwrapped key. + /// The number of bytes in the unwrapped key. + /// + /// The unwrap algorithm failed to unwrap the ciphertext. + /// -or- + /// An error occurred during the cryptographic operation. + /// + /// + /// + /// When called by the base class, + /// is pre-validated to be at least 24 bytes long and a multiple of 8 bytes. + /// + /// + /// When called by the base class, + /// will always be exactly 8 bytes shorter than , + /// so any valid value will always fit. + /// + /// + protected virtual int DecryptKeyWrapCore(ReadOnlySpan source, Span destination) + { + ulong a0 = Rfc3394Unwrap( + source, + destination, + this, + static (self, source, destination) => self.DecryptEcb(source, destination, PaddingMode.None)); + + // Check that a0 is equal to 0xA6A6A6A6A6A6A6A6UL using 32-bit branchless checks only. + uint hiCheck = (uint)(a0 >> 32) ^ 0xA6A6A6A6U; + uint loCheck = (uint)a0 ^ 0xA6A6A6A6U; + + if ((hiCheck | loCheck) != 0) + { + throw new CryptographicException(SR.Cryptography_KeyWrap_DecryptFailed); + } + + return source.Length - 8; + } + + /// + /// Wraps a key using the IETF RFC 3394 AES Key Wrap algorithm, + /// writing the result to a specified buffer. + /// + /// The data to wrap. + /// The buffer to receive the wrapped data. + /// An error occurred during the cryptographic operation. + /// + /// + /// When called by the base class, + /// is pre-validated to be at least 16 bytes long and a multiple of 8 bytes. + /// + /// + /// When called by the base class, + /// is pre-validated to be exactly the length returned by + /// for the given input. + /// + /// + protected virtual void EncryptKeyWrapCore(ReadOnlySpan source, Span destination) + { + const ulong DefaultInitialValue = 0xA6A6A6A6A6A6A6A6UL; + Rfc3394Wrap( + DefaultInitialValue, + source, + destination, + this, + static (self, source, destination) => self.EncryptEcb(source, destination, PaddingMode.None)); + } + /// /// Computes the output length of the IETF RFC 5649 AES Key Wrap with Padding /// Algorithm for the specified plaintext length. @@ -429,6 +757,7 @@ protected virtual unsafe void EncryptKeyWrapPaddedCore(ReadOnlySpan source this, static (instance, source, destination) => instance.EncryptEcb(source, destination, PaddingMode.None)); } + private protected void EncryptKeyWrapPaddedCore( ReadOnlySpan source, Span destination, @@ -489,7 +818,7 @@ private void Rfc3394Wrap( { Debug.Assert(source.Length % 8 == 0); Debug.Assert(source.Length >= 16); - Debug.Assert(destination.Length == GetKeyWrapPaddedLength(source.Length)); + Debug.Assert(destination.Length == source.Length + 8); Span B = stackalloc byte[16]; Span A = B.Slice(0, 8); @@ -556,6 +885,44 @@ private ulong Rfc3394Unwrap( return BinaryPrimitives.ReadUInt64BigEndian(A); } + private static int GetKeyWrapCiphertextLength(ReadOnlySpan plaintext) + { + const int MaxSupportedValue = 0x7FFF_FFF0; + const int MinSupportedValue = 16; // RFC 3394 requires at least two 64-bit blocks. + int plaintextLengthInBytes = plaintext.Length; + + if (plaintextLengthInBytes < MinSupportedValue || (plaintextLengthInBytes % 8) != 0) + { + throw new ArgumentException( + SR.Cryptography_KeyWrap_Plaintext_InvalidLength, + nameof(plaintext)); + } + + if (plaintextLengthInBytes > MaxSupportedValue) + { + throw new ArgumentException( + SR.Cryptography_PlaintextTooLarge, + nameof(plaintext)); + } + + return checked(plaintextLengthInBytes + 8); + } + + private static int GetKeyWrapPlaintextLength(ReadOnlySpan ciphertext) + { + const int MinSupportedValue = 24; // The minimum plaintext is 16 bytes, plus 8 bytes for the check register. + int ciphertextLengthInBytes = ciphertext.Length; + + if (ciphertextLengthInBytes < MinSupportedValue || (ciphertextLengthInBytes % 8) != 0) + { + throw new ArgumentException( + SR.Cryptography_KeyWrap_Ciphertext_InvalidLength, + nameof(ciphertext)); + } + + return checked(ciphertextLengthInBytes - 8); + } + private static readonly KeySizes[] s_legalBlockSizes = { new KeySizes(128, 128, 0) }; private static readonly KeySizes[] s_legalKeySizes = { new KeySizes(128, 256, 64) }; } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.Apple.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.Apple.cs index f6c99e32ed9160..3a29fd4e74d40d 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.Apple.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.Apple.cs @@ -7,6 +7,12 @@ namespace System.Security.Cryptography { internal sealed partial class AesImplementation { + private static readonly bool s_hasCryptoKitKeyWrap = + OperatingSystem.IsMacOS() || + OperatingSystem.IsIOSVersionAtLeast(15) || + OperatingSystem.IsTvOSVersionAtLeast(15) || + OperatingSystem.IsMacCatalystVersionAtLeast(15); + private static UniversalCryptoTransform CreateTransformCore( CipherMode cipherMode, PaddingMode paddingMode, @@ -50,6 +56,69 @@ private static AppleCCCryptorLite CreateLiteCipher( paddingSize); } + protected override void EncryptKeyWrapCore(ReadOnlySpan source, Span destination) + { + if (!s_hasCryptoKitKeyWrap) + { + base.EncryptKeyWrapCore(source, destination); + return; + } + + FixedMemoryKeyBox keyBox = GetKey(); + bool addedRef = false; + + try + { + keyBox.DangerousAddRef(ref addedRef); + int written = Interop.AppleCrypto.AesKeyWrapEncrypt(keyBox.DangerousKeySpan, source, destination); + + if (written != destination.Length) + { + Debug.Fail($"CryptoKit wrote {written} bytes; expected {destination.Length}."); + throw new CryptographicException(); + } + } + finally + { + if (addedRef) + { + keyBox.DangerousRelease(); + } + } + } + + protected override int DecryptKeyWrapCore(ReadOnlySpan source, Span destination) + { + if (!s_hasCryptoKitKeyWrap) + { + return base.DecryptKeyWrapCore(source, destination); + } + + FixedMemoryKeyBox keyBox = GetKey(); + bool addedRef = false; + + try + { + keyBox.DangerousAddRef(ref addedRef); + int written = Interop.AppleCrypto.AesKeyWrapDecrypt(keyBox.DangerousKeySpan, source, destination); + + if (written != destination.Length) + { + Debug.Fail($"CryptoKit wrote {written} bytes; expected {destination.Length}."); + throw new CryptographicException(); + } + + return written; + } + finally + { + if (addedRef) + { + keyBox.DangerousRelease(); + } + } + } + protected override void EncryptKeyWrapPaddedCore(ReadOnlySpan source, Span destination) { Debug.Assert(destination.Length == GetKeyWrapPaddedLength(source.Length)); diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs index 38c622efb2b1ce..c696d514c479be 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.OpenSsl.cs @@ -41,33 +41,57 @@ private static OpenSslCipherLite CreateLiteCipher( protected override void EncryptKeyWrapPaddedCore(ReadOnlySpan source, Span destination) { - int written = KeyWrap(source, destination, enc: 1); + int written = KeyWrap(source, destination, enc: 1, padded: true); Debug.Assert(written == destination.Length); } protected override int DecryptKeyWrapPaddedCore(ReadOnlySpan source, Span destination) { - return KeyWrap(source, destination, enc: 0); + return KeyWrap(source, destination, enc: 0, padded: true); } - private int KeyWrap(ReadOnlySpan source, Span destination, int enc) + protected override void EncryptKeyWrapCore(ReadOnlySpan source, Span destination) + { + int written = KeyWrap(source, destination, enc: 1, padded: false); + + if (written != destination.Length) + { + Debug.Fail($"OpenSSL wrote {written} bytes; expected {destination.Length}."); + throw new CryptographicException(); + } + } + + protected override int DecryptKeyWrapCore(ReadOnlySpan source, Span destination) + { + int written = KeyWrap(source, destination, enc: 0, padded: false); + + if (written != destination.Length) + { + Debug.Fail($"OpenSSL wrote {written} bytes; expected {destination.Length}."); + throw new CryptographicException(); + } + + return written; + } + + private int KeyWrap(ReadOnlySpan source, Span destination, int enc, bool padded) { Debug.Assert(enc is 0 or 1); SafeEvpCipherCtxHandle ctx = GetKey().UseKey( - state: enc, - static (enc, key) => + state: (Enc: enc, Padded: padded), + static (state, key) => { int keySizeInBits = key.Length * 8; - IntPtr algorithm = GetKeyWrapAlgorithm(keySizeInBits); + IntPtr algorithm = GetKeyWrapAlgorithm(keySizeInBits, state.Padded); SafeEvpCipherCtxHandle ctx = Interop.Crypto.EvpCipherCreate( algorithm, ref MemoryMarshal.GetReference(key), key.Length * 8, ref MemoryMarshal.GetReference(ReadOnlySpan.Empty), - enc); + state.Enc); if (ctx.IsInvalid) { @@ -82,7 +106,7 @@ ref MemoryMarshal.GetReference(ReadOnlySpan.Empty), using (ctx) { - // OpenSSL AES-KWP requires that the destination be at least as large as the source length plus the block size. + // OpenSSL AES key wrap requires that the destination be at least as large as the source length plus the block size. const int AesBlockSizeBytes = 16; using (CryptoPoolLease lease = CryptoPoolLease.RentConditionally( checked(source.Length + AesBlockSizeBytes), @@ -152,12 +176,15 @@ private static IntPtr GetAlgorithm(int keySize, int feedback, CipherMode cipherM new CryptographicException(SR.Cryptography_InvalidKeySize)), }; - private static IntPtr GetKeyWrapAlgorithm(int keySize) => - keySize switch + private static IntPtr GetKeyWrapAlgorithm(int keySize, bool padded) => + (keySize, padded) switch { - 128 => Interop.Crypto.EvpAes128WrapPad(), - 192 => Interop.Crypto.EvpAes192WrapPad(), - 256 => Interop.Crypto.EvpAes256WrapPad(), + (128, false) => Interop.Crypto.EvpAes128Wrap(), + (128, true) => Interop.Crypto.EvpAes128WrapPad(), + (192, false) => Interop.Crypto.EvpAes192Wrap(), + (192, true) => Interop.Crypto.EvpAes192WrapPad(), + (256, false) => Interop.Crypto.EvpAes256Wrap(), + (256, true) => Interop.Crypto.EvpAes256WrapPad(), _ => throw new CryptographicException(SR.Cryptography_InvalidKeySize), }; } diff --git a/src/libraries/System.Security.Cryptography/tests/ShimHelpers.cs b/src/libraries/System.Security.Cryptography/tests/ShimHelpers.cs index d60d3c21363a20..03ae0ace0e5353 100644 --- a/src/libraries/System.Security.Cryptography/tests/ShimHelpers.cs +++ b/src/libraries/System.Security.Cryptography/tests/ShimHelpers.cs @@ -55,6 +55,8 @@ public static void VerifyAllBaseMembersOverridden(Type shimType) "TryDecryptCbcCore", "TryEncryptCfbCore", "TryDecryptCfbCore", + "EncryptKeyWrapCore", + "DecryptKeyWrapCore", "EncryptKeyWrapPaddedCore", "DecryptKeyWrapPaddedCore", }; diff --git a/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj b/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj index dcc43dd74d4bcc..0111b5943403bc 100644 --- a/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj +++ b/src/libraries/System.Security.Cryptography/tests/System.Security.Cryptography.Tests.csproj @@ -290,6 +290,8 @@ Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\AES\DecryptorReusability.cs" /> + , + plaintext: UnsafeBufferPointer, + ciphertext: UnsafeMutableBufferPointer +) throws -> Int32 { + let result = try AES.KeyWrap.wrap( + SymmetricKey(data: plaintext), + using: SymmetricKey(data: key)) + + return Int32(result.copyBytes(to: ciphertext)) +} + +@_silgen_name("AppleCryptoNative_AesKeyWrapDecrypt") +@available(macOS 12.0, iOS 15.0, tvOS 15.0, macCatalyst 15.0, *) +public func AppleCryptoNative_AesKeyWrapDecrypt( + key: UnsafeBufferPointer, + ciphertext: UnsafeBufferPointer, + plaintext: UnsafeMutableBufferPointer +) throws -> Int32 { + let result = try AES.KeyWrap.unwrap( + ciphertext, + using: SymmetricKey(data: key)) + + return result.withUnsafeBytes { + Int32($0.copyBytes(to: plaintext)) + } +} + @_silgen_name("AppleCryptoNative_IsAuthenticationFailure") public func AppleCryptoNative_IsAuthenticationFailure(error: Error) -> Bool { if let error = error as? CryptoKitError { diff --git a/src/native/libs/System.Security.Cryptography.Native/entrypoints.c b/src/native/libs/System.Security.Cryptography.Native/entrypoints.c index 1ac15294492ded..f086723428540b 100644 --- a/src/native/libs/System.Security.Cryptography.Native/entrypoints.c +++ b/src/native/libs/System.Security.Cryptography.Native/entrypoints.c @@ -105,6 +105,7 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_EvpAes128Cfb8) DllImportEntry(CryptoNative_EvpAes128Ecb) DllImportEntry(CryptoNative_EvpAes128Gcm) + DllImportEntry(CryptoNative_EvpAes128Wrap) DllImportEntry(CryptoNative_EvpAes128WrapPad) DllImportEntry(CryptoNative_EvpAes192Cbc) DllImportEntry(CryptoNative_EvpAes192Ccm) @@ -112,6 +113,7 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_EvpAes192Cfb8) DllImportEntry(CryptoNative_EvpAes192Ecb) DllImportEntry(CryptoNative_EvpAes192Gcm) + DllImportEntry(CryptoNative_EvpAes192Wrap) DllImportEntry(CryptoNative_EvpAes192WrapPad) DllImportEntry(CryptoNative_EvpAes256Cbc) DllImportEntry(CryptoNative_EvpAes256Ccm) @@ -119,6 +121,7 @@ static const Entry s_cryptoNative[] = DllImportEntry(CryptoNative_EvpAes256Cfb8) DllImportEntry(CryptoNative_EvpAes256Ecb) DllImportEntry(CryptoNative_EvpAes256Gcm) + DllImportEntry(CryptoNative_EvpAes256Wrap) DllImportEntry(CryptoNative_EvpAes256WrapPad) DllImportEntry(CryptoNative_EvpChaCha20Poly1305) DllImportEntry(CryptoNative_EvpCipherCreate2) diff --git a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h index f2f05870ed857d..26f263052fbc8e 100644 --- a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h +++ b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h @@ -463,6 +463,7 @@ extern bool g_libSslUses32BitTime; REQUIRED_FUNCTION(EVP_aes_128_cfb8) \ REQUIRED_FUNCTION(EVP_aes_128_ecb) \ REQUIRED_FUNCTION(EVP_aes_128_gcm) \ + REQUIRED_FUNCTION(EVP_aes_128_wrap) \ REQUIRED_FUNCTION(EVP_aes_128_wrap_pad) \ REQUIRED_FUNCTION(EVP_aes_192_cbc) \ REQUIRED_FUNCTION(EVP_aes_192_ccm) \ @@ -470,6 +471,7 @@ extern bool g_libSslUses32BitTime; REQUIRED_FUNCTION(EVP_aes_192_cfb8) \ REQUIRED_FUNCTION(EVP_aes_192_ecb) \ REQUIRED_FUNCTION(EVP_aes_192_gcm) \ + REQUIRED_FUNCTION(EVP_aes_192_wrap) \ REQUIRED_FUNCTION(EVP_aes_192_wrap_pad) \ REQUIRED_FUNCTION(EVP_aes_256_cbc) \ REQUIRED_FUNCTION(EVP_aes_256_ccm) \ @@ -477,6 +479,7 @@ extern bool g_libSslUses32BitTime; REQUIRED_FUNCTION(EVP_aes_256_cfb8) \ REQUIRED_FUNCTION(EVP_aes_256_ecb) \ REQUIRED_FUNCTION(EVP_aes_256_gcm) \ + REQUIRED_FUNCTION(EVP_aes_256_wrap) \ REQUIRED_FUNCTION(EVP_aes_256_wrap_pad) \ LIGHTUP_FUNCTION(EVP_chacha20_poly1305) \ REQUIRED_FUNCTION(EVP_CIPHER_CTX_ctrl) \ @@ -1062,6 +1065,7 @@ extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr; #define EVP_aes_128_ecb EVP_aes_128_ecb_ptr #define EVP_aes_128_gcm EVP_aes_128_gcm_ptr #define EVP_aes_128_ccm EVP_aes_128_ccm_ptr +#define EVP_aes_128_wrap EVP_aes_128_wrap_ptr #define EVP_aes_128_wrap_pad EVP_aes_128_wrap_pad_ptr #define EVP_aes_192_cbc EVP_aes_192_cbc_ptr #define EVP_aes_192_cfb8 EVP_aes_192_cfb8_ptr @@ -1069,6 +1073,7 @@ extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr; #define EVP_aes_192_ecb EVP_aes_192_ecb_ptr #define EVP_aes_192_gcm EVP_aes_192_gcm_ptr #define EVP_aes_192_ccm EVP_aes_192_ccm_ptr +#define EVP_aes_192_wrap EVP_aes_192_wrap_ptr #define EVP_aes_192_wrap_pad EVP_aes_192_wrap_pad_ptr #define EVP_aes_256_cbc EVP_aes_256_cbc_ptr #define EVP_aes_256_cfb8 EVP_aes_256_cfb8_ptr @@ -1076,6 +1081,7 @@ extern TYPEOF(OPENSSL_gmtime)* OPENSSL_gmtime_ptr; #define EVP_aes_256_ecb EVP_aes_256_ecb_ptr #define EVP_aes_256_gcm EVP_aes_256_gcm_ptr #define EVP_aes_256_ccm EVP_aes_256_ccm_ptr +#define EVP_aes_256_wrap EVP_aes_256_wrap_ptr #define EVP_aes_256_wrap_pad EVP_aes_256_wrap_pad_ptr #define EVP_chacha20_poly1305 EVP_chacha20_poly1305_ptr #define EVP_CIPHER_CTX_ctrl EVP_CIPHER_CTX_ctrl_ptr diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_cipher.c b/src/native/libs/System.Security.Cryptography.Native/pal_evp_cipher.c index 17562f965e6fda..ea34be3da131c4 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_cipher.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_cipher.c @@ -286,6 +286,12 @@ const EVP_CIPHER* CryptoNative_EvpAes128WrapPad(void) return EVP_aes_128_wrap_pad(); } +const EVP_CIPHER* CryptoNative_EvpAes128Wrap(void) +{ + // No error queue impact. + return EVP_aes_128_wrap(); +} + const EVP_CIPHER* CryptoNative_EvpAes192Ecb(void) { // No error queue impact. @@ -328,6 +334,12 @@ const EVP_CIPHER* CryptoNative_EvpAes192WrapPad(void) return EVP_aes_192_wrap_pad(); } +const EVP_CIPHER* CryptoNative_EvpAes192Wrap(void) +{ + // No error queue impact. + return EVP_aes_192_wrap(); +} + const EVP_CIPHER* CryptoNative_EvpAes256Ecb(void) { // No error queue impact. @@ -370,6 +382,12 @@ const EVP_CIPHER* CryptoNative_EvpAes256WrapPad(void) return EVP_aes_256_wrap_pad(); } +const EVP_CIPHER* CryptoNative_EvpAes256Wrap(void) +{ + // No error queue impact. + return EVP_aes_256_wrap(); +} + const EVP_CIPHER* CryptoNative_EvpDesEcb(void) { // No error queue impact. diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_cipher.h b/src/native/libs/System.Security.Cryptography.Native/pal_evp_cipher.h index 90410d86e7c059..61c5b547cb5717 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_cipher.h +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_cipher.h @@ -156,6 +156,14 @@ Direct shim to EVP_aes_128_ccm. */ PALEXPORT const EVP_CIPHER* CryptoNative_EvpAes128Ccm(void); +/* +Function: +EvpAes128Wrap + +Direct shim to EVP_aes_128_wrap. +*/ +PALEXPORT const EVP_CIPHER* CryptoNative_EvpAes128Wrap(void); + /* Function: EvpAes128WrapPad @@ -212,6 +220,14 @@ Direct shim to EVP_aes_192_ccm. */ PALEXPORT const EVP_CIPHER* CryptoNative_EvpAes192Ccm(void); +/* +Function: +EvpAes192Wrap + +Direct shim to EVP_aes_192_wrap. +*/ +PALEXPORT const EVP_CIPHER* CryptoNative_EvpAes192Wrap(void); + /* Function: EvpAes192WrapPad @@ -268,6 +284,14 @@ Direct shim to EVP_aes_256_ccm. */ PALEXPORT const EVP_CIPHER* CryptoNative_EvpAes256Ccm(void); +/* +Function: +EvpAes256Wrap + +Direct shim to EVP_aes_256_wrap. +*/ +PALEXPORT const EVP_CIPHER* CryptoNative_EvpAes256Wrap(void); + /* Function: EvpAes256WrapPad