diff --git a/src/libraries/Common/src/Interop/Windows/BCrypt/BCryptAeadHandleCache.cs b/src/libraries/Common/src/Interop/Windows/BCrypt/BCryptAeadHandleCache.cs index 60941688533ba6..10333f03f775c0 100644 --- a/src/libraries/Common/src/Interop/Windows/BCrypt/BCryptAeadHandleCache.cs +++ b/src/libraries/Common/src/Interop/Windows/BCrypt/BCryptAeadHandleCache.cs @@ -10,15 +10,17 @@ namespace Internal.Cryptography { internal static class BCryptAeadHandleCache { - private static SafeAlgorithmHandle? s_aesCcm; private static SafeAlgorithmHandle? s_aesGcm; +#if NET + private static SafeAlgorithmHandle? s_aesCcm; private static SafeAlgorithmHandle? s_chaCha20Poly1305; - internal static SafeAlgorithmHandle AesCcm => GetCachedAlgorithmHandle(ref s_aesCcm, Cng.BCRYPT_AES_ALGORITHM, Cng.BCRYPT_CHAIN_MODE_CCM); - internal static SafeAlgorithmHandle AesGcm => GetCachedAlgorithmHandle(ref s_aesGcm, Cng.BCRYPT_AES_ALGORITHM, Cng.BCRYPT_CHAIN_MODE_GCM); - internal static bool IsChaCha20Poly1305Supported { get; } = OperatingSystem.IsWindowsVersionAtLeast(10, 0, 20142); internal static SafeAlgorithmHandle ChaCha20Poly1305 => GetCachedAlgorithmHandle(ref s_chaCha20Poly1305, Cng.BCRYPT_CHACHA20_POLY1305_ALGORITHM); + internal static SafeAlgorithmHandle AesCcm => GetCachedAlgorithmHandle(ref s_aesCcm, Cng.BCRYPT_AES_ALGORITHM, Cng.BCRYPT_CHAIN_MODE_CCM); +#endif + + internal static SafeAlgorithmHandle AesGcm => GetCachedAlgorithmHandle(ref s_aesGcm, Cng.BCRYPT_AES_ALGORITHM, Cng.BCRYPT_CHAIN_MODE_GCM); private static SafeAlgorithmHandle GetCachedAlgorithmHandle(ref SafeAlgorithmHandle? handle, string algId, string? chainingMode = null) { diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AeadCommon.Windows.cs b/src/libraries/Common/src/System/Security/Cryptography/AeadCommon.Windows.cs similarity index 100% rename from src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AeadCommon.Windows.cs rename to src/libraries/Common/src/System/Security/Cryptography/AeadCommon.Windows.cs diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesAEAD.cs b/src/libraries/Common/src/System/Security/Cryptography/AesAEAD.cs similarity index 57% rename from src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesAEAD.cs rename to src/libraries/Common/src/System/Security/Cryptography/AesAEAD.cs index 26322fe82265a6..7d02a66b6d08f0 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesAEAD.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/AesAEAD.cs @@ -1,15 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Internal.Cryptography; - namespace System.Security.Cryptography { - internal static partial class AesAEAD + internal static class AesAEAD { - public static void CheckKeySize(int keySizeInBytes) + internal static void CheckKeySize(int keySizeInBytes) { - if (keySizeInBytes != (128 / 8) && keySizeInBytes != (192 / 8) && keySizeInBytes != (256 / 8)) + if (keySizeInBytes is not (128 / 8 or 192 / 8 or 256 / 8)) { throw new CryptographicException(SR.Cryptography_InvalidKeySize); } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesGcm.Windows.cs b/src/libraries/Common/src/System/Security/Cryptography/AesGcm.Windows.cs similarity index 68% rename from src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesGcm.Windows.cs rename to src/libraries/Common/src/System/Security/Cryptography/AesGcm.Windows.cs index 305fccb16188e6..b6866563a07dbe 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesGcm.Windows.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/AesGcm.Windows.cs @@ -4,43 +4,46 @@ using System.Diagnostics.CodeAnalysis; using Internal.Cryptography; using Internal.NativeCrypto; +using System.Runtime.InteropServices; namespace System.Security.Cryptography { public partial class AesGcm { private SafeKeyHandle _keyHandle; + private static readonly KeySizes s_tagByteSizes = new KeySizes(12, 16, 1); - public static bool IsSupported => true; - public static KeySizes TagByteSizes { get; } = new KeySizes(12, 16, 1); + public static partial bool IsSupported => true; + + public static partial KeySizes TagByteSizes => s_tagByteSizes; [MemberNotNull(nameof(_keyHandle))] - private void ImportKey(ReadOnlySpan key) + private partial void ImportKey(ReadOnlySpan key) { _keyHandle = Interop.BCrypt.BCryptImportKey(BCryptAeadHandleCache.AesGcm, key); } - private void EncryptCore( + private partial void EncryptCore( ReadOnlySpan nonce, ReadOnlySpan plaintext, Span ciphertext, Span tag, - ReadOnlySpan associatedData = default) + ReadOnlySpan associatedData) { AeadCommon.Encrypt(_keyHandle, nonce, associatedData, plaintext, ciphertext, tag); } - private void DecryptCore( + private partial void DecryptCore( ReadOnlySpan nonce, ReadOnlySpan ciphertext, ReadOnlySpan tag, Span plaintext, - ReadOnlySpan associatedData = default) + ReadOnlySpan associatedData) { AeadCommon.Decrypt(_keyHandle, nonce, associatedData, ciphertext, tag, plaintext, clearPlaintextOnFailure: true); } - public void Dispose() + public partial void Dispose() { _keyHandle.Dispose(); } diff --git a/src/libraries/Common/src/System/Security/Cryptography/AesGcm.cs b/src/libraries/Common/src/System/Security/Cryptography/AesGcm.cs new file mode 100644 index 00000000000000..c29307afd1aef4 --- /dev/null +++ b/src/libraries/Common/src/System/Security/Cryptography/AesGcm.cs @@ -0,0 +1,353 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Runtime.Versioning; +using Internal.Cryptography; + +namespace System.Security.Cryptography +{ + /// + /// Represents an Advanced Encryption Standard (AES) key to be used with the Galois/Counter Mode (GCM) mode of operation. + /// + public sealed partial class AesGcm : IDisposable + { + private const int NonceSize = 12; + + /// + /// Gets the nonce sizes, in bytes, supported by this instance. + /// + /// + /// The nonce sizes supported by this instance: 12 bytes (96 bits). + /// + public static KeySizes NonceByteSizes { get; } = new KeySizes(NonceSize, NonceSize, 1); + + /// + /// Gets the size of the tag, in bytes. + /// + /// + /// The size of the tag that must be used for encryption or decryption, or if the + /// tag size is unspecified. + /// + public int? TagSizeInBytes { get; } + + /// + /// Gets the tag sizes, in bytes, supported by this instance. + /// + /// + /// The tag sizes supported by this instance: 12, 13, 14, 15, or 16 bytes (96, 104, 112, 120, or 128 bits). + /// + public static partial KeySizes TagByteSizes { get; } + + /// + /// Gets a value that indicates whether the algorithm is supported on the current platform. + /// + /// + /// if the algorithm is supported; otherwise, . + /// + public static partial bool IsSupported { get; } + + /// + /// Initializes a new instance of the class with a provided key and required tag size. + /// + /// The secret key to use for this instance. + /// The size of the tag, in bytes, that encryption and decryption must use. + /// + /// The parameter length is other than 16, 24, or 32 bytes (128, 192, or 256 bits). + /// + /// + /// The parameter is an unsupported tag size indicated by + /// . + /// + /// + /// The current platform does not support AES-GCM. + /// + /// + /// The parameter is used to indicate that the tag parameter in Encrypt + /// or Decrypt must be exactly this size. Indicating the required tag size prevents issues where callers + /// of Decrypt may supply a tag as input and that input is truncated to an unexpected size. + /// + public AesGcm(ReadOnlySpan key, int tagSizeInBytes) + { + ThrowIfNotSupported(); + + AesAEAD.CheckKeySize(key.Length); + + if (!tagSizeInBytes.IsLegalSize(TagByteSizes)) + { + throw new ArgumentException(SR.Cryptography_InvalidTagLength, nameof(tagSizeInBytes)); + } + + TagSizeInBytes = tagSizeInBytes; + ImportKey(key); + } + + /// + /// Initializes a new instance of the class with a provided key and required tag size. + /// + /// The secret key to use for this instance. + /// The size of the tag, in bytes, that encryption and decryption must use. + /// The parameter is null. + /// + /// The parameter length is other than 16, 24, or 32 bytes (128, 192, or 256 bits). + /// + /// + /// The parameter is an unsupported tag size indicated by + /// . + /// + /// + /// The current platform does not support AES-GCM. + /// + /// + /// The parameter is used to indicate that the tag parameter in Encrypt + /// or Decrypt must be exactly this size. Indicating the required tag size prevents issues where callers + /// of Decrypt may supply a tag as input and that input is truncated to an unexpected size. + /// + public AesGcm(byte[] key, int tagSizeInBytes) + : this(new ReadOnlySpan(key ?? throw new ArgumentNullException(nameof(key))), tagSizeInBytes) + { + } + + private partial void ImportKey(ReadOnlySpan key); + + private partial void DecryptCore( + ReadOnlySpan nonce, + ReadOnlySpan ciphertext, + ReadOnlySpan tag, + Span plaintext, + ReadOnlySpan associatedData); + + private partial void EncryptCore( + ReadOnlySpan nonce, + ReadOnlySpan plaintext, + Span ciphertext, + Span tag, + ReadOnlySpan associatedData); + + /// + /// Encrypts the plaintext into the ciphertext destination buffer and generates the authentication tag + /// into a separate buffer. + /// + /// + /// The nonce associated with this message, which should be a unique value for every operation with the + /// same key. + /// + /// The content to encrypt. + /// The byte array to receive the encrypted contents. + /// The byte array to receive the generated authentication tag. + /// + /// Extra data associated with this message, which must also be provided during decryption. + /// + /// + /// The security guarantees of the AES-GCM algorithm mode require that the same nonce value is never used + /// twice with the same key. + /// + /// + /// The parameter and the do not have the same length. + /// -or- + /// The parameter length is not permitted by . + /// -or- + /// The parameter length is not permitted by . + /// + /// + /// The , , , or + /// parameter is . + /// + /// The encryption operation failed. + public void Encrypt(byte[] nonce, byte[] plaintext, byte[] ciphertext, byte[] tag, byte[]? associatedData = null) + { +#if NET + ArgumentNullException.ThrowIfNull(nonce); + ArgumentNullException.ThrowIfNull(plaintext); + ArgumentNullException.ThrowIfNull(ciphertext); + ArgumentNullException.ThrowIfNull(tag); +#else + if (nonce is null) + throw new ArgumentNullException(nameof(nonce)); + + if (plaintext is null) + throw new ArgumentNullException(nameof(plaintext)); + + if (ciphertext is null) + throw new ArgumentNullException(nameof(ciphertext)); + + if (tag is null) + throw new ArgumentNullException(nameof(tag)); +#endif + + Encrypt((ReadOnlySpan)nonce, plaintext, ciphertext, tag, associatedData); + } + + + /// + /// Encrypts the plaintext into the ciphertext destination buffer and generates the authentication tag + /// into a separate buffer. + /// + /// + /// The nonce associated with this message, which should be a unique value for every operation with the + /// same key. + /// + /// The content to encrypt. + /// The byte array to receive the encrypted contents. + /// The byte array to receive the generated authentication tag. + /// + /// Extra data associated with this message, which must also be provided during decryption. + /// + /// + /// The security guarantees of the AES-GCM algorithm mode require that the same nonce value is never used + /// twice with the same key. + /// + /// + /// The parameter and the do not have the same length. + /// -or- + /// The parameter length is not permitted by . + /// -or- + /// The parameter length is not permitted by . + /// + /// The encryption operation failed. + public void Encrypt( + ReadOnlySpan nonce, + ReadOnlySpan plaintext, + Span ciphertext, + Span tag, + ReadOnlySpan associatedData = default) + { + CheckParameters(plaintext, ciphertext, nonce, tag); + EncryptCore(nonce, plaintext, ciphertext, tag, associatedData); + } + + /// + /// Decrypts the ciphertext into the provided destination buffer if the authentication tag can be validated. + /// + /// + /// The nonce associated with this message, which must match the value provided during encryption + /// + /// The encrypted content to decrypt. + /// The authentication tag produced for this message during encryption. + /// The byte array to receive the decrypted contents. + /// + /// Extra data associated with this message, which must match the value provided during encryption + /// + /// + /// If tag cannot be validated (using the key, nonce, ciphertext, and + /// associatedData values), then plaintext is cleared. + /// + /// + /// The parameter and the do not have the same length. + /// -or- + /// The parameter length is not permitted by . + /// -or- + /// The parameter length is not permitted by . + /// + /// + /// The , , , or + /// parameter is . + /// + /// + /// The decryption operation failed. Prior to .NET 8, indicates the tag value could not be verified. + /// + /// The tag value could not be verified. + public void Decrypt(byte[] nonce, byte[] ciphertext, byte[] tag, byte[] plaintext, byte[]? associatedData = null) + { +#if NET + ArgumentNullException.ThrowIfNull(nonce); + ArgumentNullException.ThrowIfNull(ciphertext); + ArgumentNullException.ThrowIfNull(tag); + ArgumentNullException.ThrowIfNull(plaintext); +#else + if (nonce is null) + throw new ArgumentNullException(nameof(nonce)); + + if (ciphertext is null) + throw new ArgumentNullException(nameof(ciphertext)); + + if (tag is null) + throw new ArgumentNullException(nameof(tag)); + + if (plaintext is null) + throw new ArgumentNullException(nameof(plaintext)); +#endif + + Decrypt((ReadOnlySpan)nonce, ciphertext, tag, plaintext, associatedData); + } + + /// + /// Decrypts the ciphertext into the provided destination buffer if the authentication tag can be validated. + /// + /// + /// The nonce associated with this message, which must match the value provided during encryption + /// + /// The encrypted content to decrypt. + /// The authentication tag produced for this message during encryption. + /// The byte span to receive the decrypted contents. + /// + /// Extra data associated with this message, which must match the value provided during encryption + /// + /// + /// If tag cannot be validated (using the key, nonce, ciphertext, and + /// associatedData values), then plaintext is cleared. + /// + /// + /// The parameter and the do not have the same length. + /// -or- + /// The parameter length is not permitted by . + /// -or- + /// The parameter length is not permitted by . + /// + /// + /// The decryption operation failed. Prior to .NET 8, indicates the tag value could not be verified. + /// + /// The tag value could not be verified. + public void Decrypt( + ReadOnlySpan nonce, + ReadOnlySpan ciphertext, + ReadOnlySpan tag, + Span plaintext, + ReadOnlySpan associatedData = default) + { + CheckParameters(plaintext, ciphertext, nonce, tag); + DecryptCore(nonce, ciphertext, tag, plaintext, associatedData); + } + + /// + /// Releases the resources used by the current instance of the class. + /// + public partial void Dispose(); + + private void CheckParameters( + ReadOnlySpan plaintext, + ReadOnlySpan ciphertext, + ReadOnlySpan nonce, + ReadOnlySpan tag) + { + if (plaintext.Length != ciphertext.Length) + throw new ArgumentException(SR.Cryptography_PlaintextCiphertextLengthMismatch); + + if (!nonce.Length.IsLegalSize(NonceByteSizes)) + throw new ArgumentException(SR.Cryptography_InvalidNonceLength, nameof(nonce)); + + if (TagSizeInBytes is int tagSizeInBytes) + { + // constructor promise + Debug.Assert(tagSizeInBytes.IsLegalSize(TagByteSizes)); + + if (tag.Length != tagSizeInBytes) + { + throw new ArgumentException(SR.Format(SR.Cryptography_IncorrectTagLength, tagSizeInBytes), nameof(tag)); + } + } + else if (!tag.Length.IsLegalSize(TagByteSizes)) + { + throw new ArgumentException(SR.Cryptography_InvalidTagLength, nameof(tag)); + } + } + + private static void ThrowIfNotSupported() + { + if (!IsSupported) + { + throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(AesGcm))); + } + } + } +} diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AuthenticationTagMismatchException.cs b/src/libraries/Common/src/System/Security/Cryptography/AuthenticationTagMismatchException.cs similarity index 100% rename from src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AuthenticationTagMismatchException.cs rename to src/libraries/Common/src/System/Security/Cryptography/AuthenticationTagMismatchException.cs diff --git a/src/libraries/System.Security.Cryptography/tests/AesGcmTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AesGcmTests.cs similarity index 97% rename from src/libraries/System.Security.Cryptography/tests/AesGcmTests.cs rename to src/libraries/Common/tests/System/Security/Cryptography/AesGcmTests.cs index d8fe8367106fc7..0bfe2677a0ef74 100644 --- a/src/libraries/System.Security.Cryptography/tests/AesGcmTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AesGcmTests.cs @@ -18,15 +18,15 @@ public class AesGcmTests : CommonAEADTests public static void EncryptTamperAADDecrypt(int dataLength, int additionalDataLength) { byte[] additionalData = new byte[additionalDataLength]; - RandomNumberGenerator.Fill(additionalData); + FillRandom(additionalData); byte[] plaintext = Enumerable.Range(1, dataLength).Select((x) => (byte)x).ToArray(); byte[] ciphertext = new byte[dataLength]; byte[] key = new byte[16]; byte[] nonce = new byte[AesGcm.NonceByteSizes.MinSize]; byte[] tag = new byte[AesGcm.TagByteSizes.MinSize]; - RandomNumberGenerator.Fill(key); - RandomNumberGenerator.Fill(nonce); + FillRandom(key); + FillRandom(nonce); using (var aesGcm = new AesGcm(key, tag.Length)) { @@ -50,10 +50,12 @@ public static void EncryptTamperAADDecrypt(int dataLength, int additionalDataLen public static void InvalidKeyLength(int keyLength) { byte[] key = new byte[keyLength]; +#if NET #pragma warning disable SYSLIB0053 Assert.Throws(() => new AesGcm(key)); Assert.Throws(() => new AesGcm(key.AsSpan())); #pragma warning restore SYSLIB0053 +#endif Assert.Throws(() => new AesGcm(key, AesGcm.TagByteSizes.MinSize)); Assert.Throws(() => new AesGcm(key.AsSpan(), AesGcm.TagByteSizes.MinSize)); } @@ -68,8 +70,8 @@ public static void InvalidNonceSize(int nonceSize) byte[] key = new byte[16]; byte[] nonce = new byte[nonceSize]; byte[] tag = new byte[AesGcm.TagByteSizes.MinSize]; - RandomNumberGenerator.Fill(key); - RandomNumberGenerator.Fill(nonce); + FillRandom(key); + FillRandom(nonce); using (var aesGcm = new AesGcm(key, AesGcm.TagByteSizes.MinSize)) { @@ -87,8 +89,8 @@ public static void ValidNonceSize(int nonceSize) byte[] key = new byte[16]; byte[] nonce = new byte[nonceSize]; byte[] tag = new byte[AesGcm.TagByteSizes.MinSize]; - RandomNumberGenerator.Fill(key); - RandomNumberGenerator.Fill(nonce); + FillRandom(key); + FillRandom(nonce); using (var aesGcm = new AesGcm(key, AesGcm.TagByteSizes.MinSize)) { @@ -100,6 +102,7 @@ public static void ValidNonceSize(int nonceSize) } } +#if NET [Theory] [MemberData(nameof(GetInvalidTagSizes))] public static void InvalidTagSizeForUnspecifiedRequiredTag(int tagSize) @@ -110,8 +113,8 @@ public static void InvalidTagSizeForUnspecifiedRequiredTag(int tagSize) byte[] key = new byte[16]; byte[] nonce = new byte[12]; byte[] tag = new byte[tagSize]; - RandomNumberGenerator.Fill(key); - RandomNumberGenerator.Fill(nonce); + FillRandom(key); + FillRandom(nonce); #pragma warning disable SYSLIB0053 using (var aesGcm = new AesGcm(key)) @@ -121,6 +124,7 @@ public static void InvalidTagSizeForUnspecifiedRequiredTag(int tagSize) Assert.Throws("tag", () => aesGcm.Decrypt(nonce, ciphertext, tag, plaintext)); } } +#endif [Theory] [MemberData(nameof(GetInvalidTagSizes))] @@ -141,8 +145,8 @@ public static void ValidTagSize(int tagSize) byte[] key = new byte[16]; byte[] nonce = new byte[12]; byte[] tag = new byte[tagSize]; - RandomNumberGenerator.Fill(key); - RandomNumberGenerator.Fill(nonce); + FillRandom(key); + FillRandom(nonce); using (var aesGcm = new AesGcm(key, tagSize)) { @@ -254,9 +258,11 @@ public static void PlaintextAndCiphertextSizeDiffer(int ptLen, int ctLen) [Fact] public static void NullKey() { +#if NET #pragma warning disable SYSLIB0053 Assert.Throws(() => new AesGcm((byte[])null)); #pragma warning restore SYSLIB0053 +#endif Assert.Throws(() => new AesGcm((byte[])null, AesGcm.TagByteSizes.MinSize)); } @@ -328,7 +334,7 @@ public static void InplaceEncryptDecrypt() byte[] originalPlaintext = new byte[] { 1, 2, 8, 12, 16, 99, 0 }; byte[] data = (byte[])originalPlaintext.Clone(); byte[] tag = new byte[16]; - RandomNumberGenerator.Fill(nonce); + FillRandom(nonce); using (var aesGcm = new AesGcm(key, tag.Length)) { @@ -348,7 +354,7 @@ public static void InplaceEncryptTamperTagDecrypt() byte[] originalPlaintext = new byte[] { 1, 2, 8, 12, 16, 99, 0 }; byte[] data = (byte[])originalPlaintext.Clone(); byte[] tag = new byte[16]; - RandomNumberGenerator.Fill(nonce); + FillRandom(nonce); using (var aesGcm = new AesGcm(key, tag.Length)) { @@ -363,6 +369,7 @@ public static void InplaceEncryptTamperTagDecrypt() } } +#if NET [Theory] [MemberData(nameof(GetNistGcmTestCases))] public static void AesGcmNistTestsUnspecifiedTagSize(AEADTest testCase) @@ -398,6 +405,7 @@ public static void AesGcmNistTestsUnspecifiedTagSize(AEADTest testCase) } } } +#endif [Theory] [MemberData(nameof(GetNistGcmTestCases))] @@ -445,7 +453,7 @@ public static void AesGcmNistTestsTamperTag(AEADTest testCase) tag[0] ^= 1; byte[] plaintext = new byte[testCase.Plaintext.Length]; - RandomNumberGenerator.Fill(plaintext); + FillRandom(plaintext); Assert.Throws( () => aesGcm.Decrypt(testCase.Nonce, ciphertext, tag, plaintext, testCase.AssociatedData)); Assert.Equal(new byte[plaintext.Length], plaintext); @@ -471,7 +479,9 @@ public static void AesGcmNistTestsTamperCiphertext(AEADTest testCase) ciphertext[0] ^= 1; - byte[] plaintext = RandomNumberGenerator.GetBytes(testCase.Plaintext.Length); + byte[] plaintext = new byte[testCase.Plaintext.Length]; + FillRandom(plaintext); + Assert.Throws( () => aesGcm.Decrypt(testCase.Nonce, ciphertext, tag, plaintext, testCase.AssociatedData)); AssertExtensions.FilledWith(0, plaintext); @@ -962,6 +972,20 @@ private static IEnumerable GetNistTests() Tag = "4ce8aff15debc1b23c50665b9c".HexToByteArray(), }, }; + + private static void FillRandom(Span destination) + { +#if NET + RandomNumberGenerator.Fill(destination); +#else + using (RandomNumberGenerator generator = RandomNumberGenerator.Create()) + { + byte[] bytes = new byte[destination.Length]; + generator.GetBytes(bytes); + bytes.AsSpan().CopyTo(destination); + } +#endif + } } public class AesGcmIsSupportedTests @@ -971,12 +995,24 @@ public class AesGcmIsSupportedTests [ConditionalFact(nameof(RuntimeSaysIsNotSupported))] public static void CtorThrowsPNSEIfNotSupported() { - byte[] key = RandomNumberGenerator.GetBytes(256 / 8); + byte[] key; +#if NET + key = RandomNumberGenerator.GetBytes(256 / 8); +#else + key = new byte[256 / 8]; + + using (RandomNumberGenerator generator = RandomNumberGenerator.Create()) + { + generator.GetBytes(key); + } +#endif +#if NET #pragma warning disable SYSLIB0053 Assert.Throws(() => new AesGcm(key)); Assert.Throws(() => new AesGcm(key.AsSpan())); #pragma warning restore SYSLIB0053 +#endif Assert.Throws(() => new AesGcm(key, AesGcm.TagByteSizes.MinSize)); Assert.Throws(() => new AesGcm(key.AsSpan(), AesGcm.TagByteSizes.MinSize)); diff --git a/src/libraries/System.Security.Cryptography/tests/CommonAEADTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/CommonAEADTests.cs similarity index 100% rename from src/libraries/System.Security.Cryptography/tests/CommonAEADTests.cs rename to src/libraries/Common/tests/System/Security/Cryptography/CommonAEADTests.cs diff --git a/src/libraries/Microsoft.Bcl.Cryptography/src/Microsoft.Bcl.Cryptography.Forwards.cs b/src/libraries/Microsoft.Bcl.Cryptography/src/Microsoft.Bcl.Cryptography.Forwards.cs index b7cb4dae61d56a..198e623ef8709d 100644 --- a/src/libraries/Microsoft.Bcl.Cryptography/src/Microsoft.Bcl.Cryptography.Forwards.cs +++ b/src/libraries/Microsoft.Bcl.Cryptography/src/Microsoft.Bcl.Cryptography.Forwards.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. #if NET8_0_OR_GREATER +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Security.Cryptography.AesGcm))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Security.Cryptography.AuthenticationTagMismatchException))] [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Security.Cryptography.SP800108HmacCounterKdf))] #endif #if NET9_0_OR_GREATER diff --git a/src/libraries/Microsoft.Bcl.Cryptography/src/Microsoft.Bcl.Cryptography.csproj b/src/libraries/Microsoft.Bcl.Cryptography/src/Microsoft.Bcl.Cryptography.csproj index 85bc820fa08289..973f529be3106f 100644 --- a/src/libraries/Microsoft.Bcl.Cryptography/src/Microsoft.Bcl.Cryptography.csproj +++ b/src/libraries/Microsoft.Bcl.Cryptography/src/Microsoft.Bcl.Cryptography.csproj @@ -14,7 +14,7 @@ true true - + @@ -136,13 +136,13 @@ Common\System\Security\Cryptography\Asn1\Pbkdf2SaltChoice.xml.cs Common\System\Security\Cryptography\Asn1\Pbkdf2SaltChoice.xml - + Common\System\Security\Cryptography\Asn1\PrivateKeyInfoAsn.xml Common\System\Security\Cryptography\Asn1\PrivateKeyInfoAsn.xml.cs Common\System\Security\Cryptography\Asn1\PrivateKeyInfoAsn.xml - + Common\System\Security\Cryptography\Asn1\Rc2CbcParameters.xml @@ -255,7 +255,7 @@ + Link="Common\Interop\Windows\Crypt32\Interop.CryptQueryObject_IntPtr_out.cs" /> + + + + + + + + + + + + + + diff --git a/src/libraries/Microsoft.Bcl.Cryptography/src/PACKAGE.md b/src/libraries/Microsoft.Bcl.Cryptography/src/PACKAGE.md index 215d29e162c4b6..ebbb68160c47de 100644 --- a/src/libraries/Microsoft.Bcl.Cryptography/src/PACKAGE.md +++ b/src/libraries/Microsoft.Bcl.Cryptography/src/PACKAGE.md @@ -28,7 +28,9 @@ internal static class Program The main types provided by this library are: +* `System.Security.Cryptography.AesGcm` * `System.Security.Cryptography.SP800108HmacCounterKdf` +* `System.Security.Cryptography.X509Certificates.X509CertificateLoader` ## Additional Documentation diff --git a/src/libraries/Microsoft.Bcl.Cryptography/src/Resources/Strings.resx b/src/libraries/Microsoft.Bcl.Cryptography/src/Resources/Strings.resx index 02e47cb969f047..0a9c247f690c41 100644 --- a/src/libraries/Microsoft.Bcl.Cryptography/src/Resources/Strings.resx +++ b/src/libraries/Microsoft.Bcl.Cryptography/src/Resources/Strings.resx @@ -84,12 +84,27 @@ Algorithm '{0}' is not supported on this platform. + + The computed authentication tag did not match the input authentication tag. + ASN1 corrupted data. The hash algorithm name cannot be null or empty. + + The size of the specified tag does not match the expected size of {0}. + + + Specified key is not a valid size for this algorithm. + + + The specified tag is not a valid size for this algorithm. + + + The specified nonce is not a valid size for this algorithm. + Key is not a valid public or private key. @@ -102,6 +117,9 @@ The EncryptedPrivateKeyInfo structure was decoded but was not successfully interpreted, the password may be incorrect. + + Plaintext and ciphertext must have the same length. + The algorithm identified by '{0}' is unknown, not valid for the requested usage, or was not handled. diff --git a/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/Helpers.cs b/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/Helpers.cs index 0d2af61b8d6347..c51de135ddd9b4 100644 --- a/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/Helpers.cs +++ b/src/libraries/Microsoft.Bcl.Cryptography/src/System/Security/Cryptography/Helpers.cs @@ -5,11 +5,27 @@ using System.Diagnostics; using System.Formats.Asn1; using System.Security.Cryptography; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace Internal.Cryptography { internal static partial class Helpers { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe ref readonly byte GetNonNullPinnableReference(ReadOnlySpan buffer) + { + // Based on the internal implementation from MemoryMarshal. + return ref buffer.Length != 0 ? ref MemoryMarshal.GetReference(buffer) : ref Unsafe.AsRef((void*)1); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe ref byte GetNonNullPinnableReference(Span buffer) + { + // Based on the internal implementation from MemoryMarshal. + return ref buffer.Length != 0 ? ref MemoryMarshal.GetReference(buffer) : ref Unsafe.AsRef((void*)1); + } + internal static ReadOnlyMemory DecodeOctetStringAsMemory(ReadOnlyMemory encodedOctetString) { try diff --git a/src/libraries/Microsoft.Bcl.Cryptography/tests/Microsoft.Bcl.Cryptography.Tests.csproj b/src/libraries/Microsoft.Bcl.Cryptography/tests/Microsoft.Bcl.Cryptography.Tests.csproj index be85e50d295cbd..1c47527480b27a 100644 --- a/src/libraries/Microsoft.Bcl.Cryptography/tests/Microsoft.Bcl.Cryptography.Tests.csproj +++ b/src/libraries/Microsoft.Bcl.Cryptography/tests/Microsoft.Bcl.Cryptography.Tests.csproj @@ -5,11 +5,15 @@ + Link="Common\System\IO\MemoryMappedFiles\MemoryMappedFileMemoryManager.cs" /> + + Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml.cs Common\System\Security\Cryptography\Asn1\Pkcs7\EncryptedDataAsn.xml + + + - @@ -404,7 +409,6 @@ - @@ -1704,6 +1708,10 @@ Link="Common\Microsoft\Win32\SafeHandles\SafeCryptMsgHandle.cs" /> + + - - diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesGcm.Android.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesGcm.Android.cs index 4b66635d3757f2..551ef7e32ca1ff 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesGcm.Android.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesGcm.Android.cs @@ -10,12 +10,13 @@ namespace System.Security.Cryptography public sealed partial class AesGcm { private SafeEvpCipherCtxHandle _ctxHandle; + private static readonly KeySizes s_tagByteSizes = new KeySizes(12, 16, 1); - public static bool IsSupported => true; - public static KeySizes TagByteSizes { get; } = new KeySizes(12, 16, 1); + public static partial bool IsSupported => true; + public static partial KeySizes TagByteSizes => s_tagByteSizes; [MemberNotNull(nameof(_ctxHandle))] - private void ImportKey(ReadOnlySpan key) + private partial void ImportKey(ReadOnlySpan key) { // Convert key length to bits. _ctxHandle = Interop.Crypto.EvpCipherCreatePartial(GetCipher(key.Length * 8)); @@ -29,12 +30,12 @@ private void ImportKey(ReadOnlySpan key) Interop.Crypto.CipherSetNonceLength(_ctxHandle, NonceSize); } - private void EncryptCore( + private partial void EncryptCore( ReadOnlySpan nonce, ReadOnlySpan plaintext, Span ciphertext, Span tag, - ReadOnlySpan associatedData = default) + ReadOnlySpan associatedData) { if (!Interop.Crypto.CipherSetTagLength(_ctxHandle, tag.Length)) @@ -107,7 +108,7 @@ private void EncryptCore( } } - private void DecryptCore( + private partial void DecryptCore( ReadOnlySpan nonce, ReadOnlySpan ciphertext, ReadOnlySpan tag, @@ -180,7 +181,7 @@ private static IntPtr GetCipher(int keySizeInBits) }; } - public void Dispose() + public partial void Dispose() { _ctxHandle.Dispose(); } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesGcm.Apple.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesGcm.Apple.cs index d09e55eafb89eb..b2621493ae2ec4 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesGcm.Apple.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesGcm.Apple.cs @@ -11,21 +11,27 @@ public sealed partial class AesGcm { private FixedMemoryKeyBox _keyBox; + // CryptoKit only supports 16 byte tags. + private static readonly KeySizes s_tagByteSizes = new KeySizes(16, 16, 1); + // CryptoKit added AES.GCM in macOS 10.15, which is lower than our minimum target for macOS/MacCatalyst. On iOS/tvOS, it was added in 13.0. - public static bool IsSupported => OperatingSystem.IsMacOS() || OperatingSystem.IsMacCatalyst() || OperatingSystem.IsIOSVersionAtLeast(13) || OperatingSystem.IsTvOSVersionAtLeast(13); + public static partial bool IsSupported => + OperatingSystem.IsMacOS() || + OperatingSystem.IsMacCatalyst() || + OperatingSystem.IsIOSVersionAtLeast(13) || + OperatingSystem.IsTvOSVersionAtLeast(13); - // CryptoKit only supports 16 byte tags. - public static KeySizes TagByteSizes { get; } = new KeySizes(16, 16, 1); + public static partial KeySizes TagByteSizes => s_tagByteSizes; [MemberNotNull(nameof(_keyBox))] - private void ImportKey(ReadOnlySpan key) + private partial void ImportKey(ReadOnlySpan key) { // We should only be calling this in the constructor, so there shouldn't be a previous key. Debug.Assert(_keyBox is null); _keyBox = new FixedMemoryKeyBox(key); } - private void EncryptCore( + private partial void EncryptCore( ReadOnlySpan nonce, ReadOnlySpan plaintext, Span ciphertext, @@ -54,7 +60,7 @@ private void EncryptCore( } } - private void DecryptCore( + private partial void DecryptCore( ReadOnlySpan nonce, ReadOnlySpan ciphertext, ReadOnlySpan tag, @@ -83,6 +89,6 @@ private void DecryptCore( } } - public void Dispose() => _keyBox.Dispose(); + public partial void Dispose() => _keyBox.Dispose(); } } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesGcm.NotSupported.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesGcm.NotSupported.cs index 4fdf9a9d8e0dfa..6764bc4aa26fc5 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesGcm.NotSupported.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesGcm.NotSupported.cs @@ -7,39 +7,39 @@ namespace System.Security.Cryptography { public partial class AesGcm { - public static bool IsSupported => false; - public static KeySizes TagByteSizes { get; } = new KeySizes(12, 16, 1); + public static partial bool IsSupported => false; + public static partial KeySizes TagByteSizes => new KeySizes(12, 16, 1); #pragma warning disable CA1822, IDE0060 - private void ImportKey(ReadOnlySpan key) + private partial void ImportKey(ReadOnlySpan key) { Debug.Fail("Instance ctor should fail before we reach this point."); throw new NotImplementedException(); } - private void EncryptCore( + private partial void EncryptCore( ReadOnlySpan nonce, ReadOnlySpan plaintext, Span ciphertext, Span tag, - ReadOnlySpan associatedData = default) + ReadOnlySpan associatedData) { Debug.Fail("Instance ctor should fail before we reach this point."); throw new NotImplementedException(); } - private void DecryptCore( + private partial void DecryptCore( ReadOnlySpan nonce, ReadOnlySpan ciphertext, ReadOnlySpan tag, Span plaintext, - ReadOnlySpan associatedData = default) + ReadOnlySpan associatedData) { Debug.Fail("Instance ctor should fail before we reach this point."); throw new NotImplementedException(); } - public void Dispose() + public partial void Dispose() { Debug.Fail("Instance ctor should fail before we reach this point."); // no-op diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesGcm.OpenSsl.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesGcm.OpenSsl.cs index 7cc251b0610d47..731d294ae9da42 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesGcm.OpenSsl.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesGcm.OpenSsl.cs @@ -10,12 +10,14 @@ namespace System.Security.Cryptography public sealed partial class AesGcm { private SafeEvpCipherCtxHandle _ctxHandle; + private static readonly KeySizes s_tagByteSizes = new KeySizes(12, 16, 1); + private static readonly bool s_isSupported = Interop.OpenSslNoInit.OpenSslIsAvailable; - public static bool IsSupported { get; } = Interop.OpenSslNoInit.OpenSslIsAvailable; - public static KeySizes TagByteSizes { get; } = new KeySizes(12, 16, 1); + public static partial bool IsSupported => s_isSupported; + public static partial KeySizes TagByteSizes => s_tagByteSizes; [MemberNotNull(nameof(_ctxHandle))] - private void ImportKey(ReadOnlySpan key) + private partial void ImportKey(ReadOnlySpan key) { _ctxHandle = Interop.Crypto.EvpCipherCreatePartial(GetCipher(key.Length * 8)); @@ -28,12 +30,12 @@ private void ImportKey(ReadOnlySpan key) Interop.Crypto.EvpCipherSetGcmNonceLength(_ctxHandle, NonceSize); } - private void EncryptCore( + private partial void EncryptCore( ReadOnlySpan nonce, ReadOnlySpan plaintext, Span ciphertext, Span tag, - ReadOnlySpan associatedData = default) + ReadOnlySpan associatedData) { Interop.Crypto.EvpCipherSetKeyAndIV( _ctxHandle, @@ -73,7 +75,7 @@ private void EncryptCore( Interop.Crypto.EvpCipherGetGcmTag(_ctxHandle, tag); } - private void DecryptCore( + private partial void DecryptCore( ReadOnlySpan nonce, ReadOnlySpan ciphertext, ReadOnlySpan tag, @@ -132,7 +134,7 @@ private static IntPtr GetCipher(int keySizeInBits) } } - public void Dispose() + public partial void Dispose() { _ctxHandle.Dispose(); } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesGcm.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesGcm.cs index 6135892934140d..b076a145fc99af 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesGcm.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesGcm.cs @@ -1,9 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics; using System.Runtime.Versioning; -using Internal.Cryptography; namespace System.Security.Cryptography { @@ -12,11 +10,8 @@ namespace System.Security.Cryptography [UnsupportedOSPlatform("tvos")] [SupportedOSPlatform("ios13.0")] [SupportedOSPlatform("tvos13.0")] - public sealed partial class AesGcm : IDisposable + public sealed partial class AesGcm { - private const int NonceSize = 12; - public static KeySizes NonceByteSizes { get; } = new KeySizes(NonceSize, NonceSize, 1); - [Obsolete(Obsoletions.AesGcmTagConstructorMessage, DiagnosticId = Obsoletions.AesGcmTagConstructorDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] public AesGcm(ReadOnlySpan key) { @@ -31,153 +26,5 @@ public AesGcm(byte[] key) : this(new ReadOnlySpan(key ?? throw new ArgumentNullException(nameof(key)))) { } - - /// - /// Initializes a new instance of the class with a provided key and required tag size. - /// - /// The secret key to use for this instance. - /// The size of the tag, in bytes, that encryption and decryption must use. - /// - /// The parameter length is other than 16, 24, or 32 bytes (128, 192, or 256 bits). - /// - /// - /// The parameter is an unsupported tag size indicated by - /// . - /// - /// - /// The current platform does not support AES-GCM. - /// - /// - /// The parameter is used to indicate that the tag parameter in Encrypt - /// or Decrypt must be exactly this size. Indicating the required tag size prevents issues where callers - /// of Decrypt may supply a tag as input and that input is truncated to an unexpected size. - /// - public AesGcm(ReadOnlySpan key, int tagSizeInBytes) - { - ThrowIfNotSupported(); - - AesAEAD.CheckKeySize(key.Length); - - if (!tagSizeInBytes.IsLegalSize(TagByteSizes)) - { - throw new ArgumentException(SR.Cryptography_InvalidTagLength, nameof(tagSizeInBytes)); - } - - TagSizeInBytes = tagSizeInBytes; - ImportKey(key); - } - - /// - /// Initializes a new instance of the class with a provided key and required tag size. - /// - /// The secret key to use for this instance. - /// The size of the tag, in bytes, that encryption and decryption must use. - /// The parameter is null. - /// - /// The parameter length is other than 16, 24, or 32 bytes (128, 192, or 256 bits). - /// - /// - /// The parameter is an unsupported tag size indicated by - /// . - /// - /// - /// The current platform does not support AES-GCM. - /// - /// - /// The parameter is used to indicate that the tag parameter in Encrypt - /// or Decrypt must be exactly this size. Indicating the required tag size prevents issues where callers - /// of Decrypt may supply a tag as input and that input is truncated to an unexpected size. - /// - public AesGcm(byte[] key, int tagSizeInBytes) - : this(new ReadOnlySpan(key ?? throw new ArgumentNullException(nameof(key))), tagSizeInBytes) - { - } - - /// - /// Gets the size of the tag, in bytes. - /// - /// - /// The size of the tag that must be used for encryption or decryption, or if the - /// tag size is unspecified. - /// - public int? TagSizeInBytes { get; } - - public void Encrypt(byte[] nonce, byte[] plaintext, byte[] ciphertext, byte[] tag, byte[]? associatedData = null) - { - ArgumentNullException.ThrowIfNull(nonce); - ArgumentNullException.ThrowIfNull(plaintext); - ArgumentNullException.ThrowIfNull(ciphertext); - ArgumentNullException.ThrowIfNull(tag); - - Encrypt((ReadOnlySpan)nonce, plaintext, ciphertext, tag, associatedData); - } - - public void Encrypt( - ReadOnlySpan nonce, - ReadOnlySpan plaintext, - Span ciphertext, - Span tag, - ReadOnlySpan associatedData = default) - { - CheckParameters(plaintext, ciphertext, nonce, tag); - EncryptCore(nonce, plaintext, ciphertext, tag, associatedData); - } - - public void Decrypt(byte[] nonce, byte[] ciphertext, byte[] tag, byte[] plaintext, byte[]? associatedData = null) - { - ArgumentNullException.ThrowIfNull(nonce); - ArgumentNullException.ThrowIfNull(ciphertext); - ArgumentNullException.ThrowIfNull(tag); - ArgumentNullException.ThrowIfNull(plaintext); - - Decrypt((ReadOnlySpan)nonce, ciphertext, tag, plaintext, associatedData); - } - - public void Decrypt( - ReadOnlySpan nonce, - ReadOnlySpan ciphertext, - ReadOnlySpan tag, - Span plaintext, - ReadOnlySpan associatedData = default) - { - CheckParameters(plaintext, ciphertext, nonce, tag); - DecryptCore(nonce, ciphertext, tag, plaintext, associatedData); - } - - private void CheckParameters( - ReadOnlySpan plaintext, - ReadOnlySpan ciphertext, - ReadOnlySpan nonce, - ReadOnlySpan tag) - { - if (plaintext.Length != ciphertext.Length) - throw new ArgumentException(SR.Cryptography_PlaintextCiphertextLengthMismatch); - - if (!nonce.Length.IsLegalSize(NonceByteSizes)) - throw new ArgumentException(SR.Cryptography_InvalidNonceLength, nameof(nonce)); - - if (TagSizeInBytes is int tagSizeInBytes) - { - // constructor promise - Debug.Assert(tagSizeInBytes.IsLegalSize(TagByteSizes)); - - if (tag.Length != tagSizeInBytes) - { - throw new ArgumentException(SR.Format(SR.Cryptography_IncorrectTagLength, tagSizeInBytes), nameof(tag)); - } - } - else if (!tag.Length.IsLegalSize(TagByteSizes)) - { - throw new ArgumentException(SR.Cryptography_InvalidTagLength, nameof(tag)); - } - } - - private static void ThrowIfNotSupported() - { - if (!IsSupported) - { - throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(AesGcm))); - } - } } } 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 9ec855e32bd341..a03f173a7c5e3a 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 @@ -217,8 +217,12 @@ Link="CommonTest\System\Security\Cryptography\509Certificates\X509CertificateLoaderPkcs12Tests.cs" /> + + - @@ -242,7 +245,6 @@ -