From 675fa7908f88e612b1e5569ee7bcbb76a2c9965b Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Sun, 20 Sep 2026 11:18:53 -0400 Subject: [PATCH] KEM support for EnvelopedCms This implements ML-KEM and Composite ML-KEM API surface for EnvelopedCms. For the managed implementation, this implements ML-KEM entirely per RFC 9936. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/Security/Cryptography/Oids.cs | 14 + .../System/Runtime/CompilerServices/IUnion.cs | 7 +- .../CompilerServices/UnionAttribute.cs | 7 +- .../ref/System.Security.Cryptography.Pkcs.cs | 27 + .../src/Internal/Cryptography/DecryptorPal.cs | 2 +- .../Internal/Cryptography/EnvelopedCmsKey.cs | 25 + .../Cryptography/KemRecipientInfoPal.cs | 17 + .../Pal/AnyOS/ManagedPal.Decode.cs | 16 + .../Pal/AnyOS/ManagedPal.Decrypt.cs | 85 +- .../Pal/AnyOS/ManagedPal.Encrypt.cs | 20 +- .../Cryptography/Pal/AnyOS/ManagedPal.Kem.cs | 368 ++++++++ .../Pal/AnyOS/ManagedPal.KeyTrans.cs | 23 +- .../Windows/DecryptorPalWindows.Decrypt.cs | 24 +- .../src/Internal/Cryptography/PkcsHelpers.cs | 65 ++ .../System.Security.Cryptography.Pkcs.csproj | 28 + .../Security/Cryptography/NetStandardShims.cs | 18 + .../Pkcs/Asn1/CmsOriForKemOtherInfoAsn.xml | 19 + .../Pkcs/Asn1/CmsOriForKemOtherInfoAsn.xml.cs | 115 +++ .../Pkcs/Asn1/KemRecipientInfoAsn.xml | 31 + .../Pkcs/Asn1/KemRecipientInfoAsn.xml.cs | 156 ++++ .../Pkcs/Asn1/OtherRecipientInfoAsn.xml | 17 + .../Pkcs/Asn1/OtherRecipientInfoAsn.xml.cs | 98 ++ .../Pkcs/Asn1/RecipientInfoAsn.xml | 3 +- .../Pkcs/Asn1/RecipientInfoAsn.xml.cs | 18 + .../Cryptography/Pkcs/CmsRecipient.cs | 59 ++ .../Cryptography/Pkcs/EnvelopedCms.Kem.cs | 41 + .../Cryptography/Pkcs/EnvelopedCms.cs | 17 +- .../Cryptography/Pkcs/KemRecipientInfo.cs | 76 ++ .../Cryptography/Pkcs/RecipientInfo.cs | 6 + .../Cryptography/Pkcs/RecipientInfoType.cs | 3 + .../tests/EnvelopedCms/KemGeneralTests.cs | 98 ++ .../EnvelopedCms/KemNotSupportedTests.cs | 34 + .../tests/EnvelopedCms/KemTestDocuments.cs | 306 ++++++ .../tests/EnvelopedCms/MLKemDecryptTests.cs | 871 ++++++++++++++++++ .../tests/EnvelopedCms/MLKemEncryptTests.cs | 403 ++++++++ .../tests/Oids.cs | 7 + ...em.Security.Cryptography.Pkcs.Tests.csproj | 9 + 37 files changed, 3065 insertions(+), 68 deletions(-) create mode 100644 src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/EnvelopedCmsKey.cs create mode 100644 src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/KemRecipientInfoPal.cs create mode 100644 src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Kem.cs create mode 100644 src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/NetStandardShims.cs create mode 100644 src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/CmsOriForKemOtherInfoAsn.xml create mode 100644 src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/CmsOriForKemOtherInfoAsn.xml.cs create mode 100644 src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/KemRecipientInfoAsn.xml create mode 100644 src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/KemRecipientInfoAsn.xml.cs create mode 100644 src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/OtherRecipientInfoAsn.xml create mode 100644 src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/OtherRecipientInfoAsn.xml.cs create mode 100644 src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/EnvelopedCms.Kem.cs create mode 100644 src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/KemRecipientInfo.cs create mode 100644 src/libraries/System.Security.Cryptography.Pkcs/tests/EnvelopedCms/KemGeneralTests.cs create mode 100644 src/libraries/System.Security.Cryptography.Pkcs/tests/EnvelopedCms/KemNotSupportedTests.cs create mode 100644 src/libraries/System.Security.Cryptography.Pkcs/tests/EnvelopedCms/KemTestDocuments.cs create mode 100644 src/libraries/System.Security.Cryptography.Pkcs/tests/EnvelopedCms/MLKemDecryptTests.cs create mode 100644 src/libraries/System.Security.Cryptography.Pkcs/tests/EnvelopedCms/MLKemEncryptTests.cs diff --git a/src/libraries/Common/src/System/Security/Cryptography/Oids.cs b/src/libraries/Common/src/System/Security/Cryptography/Oids.cs index 30493b32eb8de8..6539489b107a64 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/Oids.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/Oids.cs @@ -55,9 +55,23 @@ internal static partial class Oids internal const string MsPkcs12MachineKeySet = "1.3.6.1.4.1.311.17.2"; // Key wrap algorithms + internal const string Aes128Wrap = "2.16.840.1.101.3.4.1.5"; + internal const string Aes192Wrap = "2.16.840.1.101.3.4.1.25"; + internal const string Aes256Wrap = "2.16.840.1.101.3.4.1.45"; internal const string CmsRc2Wrap = "1.2.840.113549.1.9.16.3.7"; internal const string Cms3DesWrap = "1.2.840.113549.1.9.16.3.6"; + // Key derivation algorithms + internal const string HkdfWithSha256 = "1.2.840.113549.1.9.16.3.28"; + internal const string HkdfWithSha384 = "1.2.840.113549.1.9.16.3.29"; + internal const string HkdfWithSha512 = "1.2.840.113549.1.9.16.3.30"; + internal const string HkdfWithSha3_256 = "1.2.840.113549.1.9.16.3.33"; + internal const string HkdfWithSha3_384 = "1.2.840.113549.1.9.16.3.34"; + internal const string HkdfWithSha3_512 = "1.2.840.113549.1.9.16.3.35"; + + // OtherRecipientInfo types + internal const string IdSmimeOriKem = "1.2.840.113549.1.9.16.13.3"; + // PKCS7 Content Types. internal const string Pkcs7Data = "1.2.840.113549.1.7.1"; internal const string Pkcs7Signed = "1.2.840.113549.1.7.2"; diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/IUnion.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/IUnion.cs index 71a88be48a6a33..00ed54f093931d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/IUnion.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/IUnion.cs @@ -15,7 +15,12 @@ namespace System.Runtime.CompilerServices /// /// /// - public interface IUnion +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + interface IUnion { /// /// Gets the value contained in the union, or if the union has no value. diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnionAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnionAttribute.cs index e1e6ea64ebfeee..141c5898dc4103 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnionAttribute.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/UnionAttribute.cs @@ -15,7 +15,12 @@ namespace System.Runtime.CompilerServices /// /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] - public sealed class UnionAttribute : Attribute +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + sealed class UnionAttribute : Attribute { } } diff --git a/src/libraries/System.Security.Cryptography.Pkcs/ref/System.Security.Cryptography.Pkcs.cs b/src/libraries/System.Security.Cryptography.Pkcs/ref/System.Security.Cryptography.Pkcs.cs index f687149f802229..6a98ceeba569f0 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/ref/System.Security.Cryptography.Pkcs.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/ref/System.Security.Cryptography.Pkcs.cs @@ -55,6 +55,10 @@ public CmsRecipient(System.Security.Cryptography.Pkcs.SubjectIdentifierType reci public CmsRecipient(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) { } public System.Security.Cryptography.X509Certificates.X509Certificate2 Certificate { get { throw null; } } public System.Security.Cryptography.Pkcs.SubjectIdentifierType RecipientIdentifierType { get { throw null; } } +#if NET11_0_OR_GREATER + public static System.Security.Cryptography.Pkcs.CmsRecipient CreateForKeyEncapsulation(System.Security.Cryptography.Pkcs.SubjectIdentifierType recipientIdentifierType, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, System.ReadOnlySpan userKeyingMaterial) { throw null; } + public static System.Security.Cryptography.Pkcs.CmsRecipient CreateForKeyEncapsulation(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, System.ReadOnlySpan userKeyingMaterial) { throw null; } +#endif } public sealed partial class CmsRecipientCollection : System.Collections.ICollection, System.Collections.IEnumerable { @@ -120,6 +124,11 @@ public EnvelopedCms(System.Security.Cryptography.Pkcs.ContentInfo contentInfo, S public int Version { get { throw null; } } public void Decode(byte[] encodedMessage) { } public void Decrypt() { } +#if NET11_0_OR_GREATER + [System.Diagnostics.CodeAnalysis.ExperimentalAttribute("SYSLIB5006", UrlFormat="https://aka.ms/dotnet-warnings/{0}")] + public void Decrypt(System.Security.Cryptography.Pkcs.KemRecipientInfo recipientInfo, System.Security.Cryptography.CompositeMLKem privateKey) { } + public void Decrypt(System.Security.Cryptography.Pkcs.KemRecipientInfo recipientInfo, System.Security.Cryptography.MLKem privateKey) { } +#endif public void Decrypt(System.Security.Cryptography.Pkcs.RecipientInfo recipientInfo) { } public void Decrypt(System.Security.Cryptography.Pkcs.RecipientInfo recipientInfo, System.Security.Cryptography.X509Certificates.X509Certificate2Collection extraStore) { } public void Decrypt(System.Security.Cryptography.X509Certificates.X509Certificate2Collection extraStore) { } @@ -138,6 +147,21 @@ internal KeyAgreeRecipientInfo() { } public override System.Security.Cryptography.Pkcs.SubjectIdentifier RecipientIdentifier { get { throw null; } } public override int Version { get { throw null; } } } +#if NET11_0_OR_GREATER + public sealed partial class KemRecipientInfo : System.Security.Cryptography.Pkcs.RecipientInfo + { + internal KemRecipientInfo() { } + public override byte[] EncryptedKey { get { throw null; } } + public System.Security.Cryptography.Pkcs.AlgorithmIdentifier KeyEncapsulationAlgorithm { get { throw null; } } + public System.ReadOnlyMemory KeyEncapsulationCiphertext { get { throw null; } } + public System.Security.Cryptography.Pkcs.AlgorithmIdentifier KeyDerivationAlgorithm { get { throw null; } } + public override System.Security.Cryptography.Pkcs.AlgorithmIdentifier KeyEncryptionAlgorithm { get { throw null; } } + public int KeyEncryptionKeyLengthInBytes { get { throw null; } } + public override System.Security.Cryptography.Pkcs.SubjectIdentifier RecipientIdentifier { get { throw null; } } + public System.ReadOnlyMemory? UserKeyingMaterial { get { throw null; } } + public override int Version { get { throw null; } } + } +#endif public sealed partial class KeyTransRecipientInfo : System.Security.Cryptography.Pkcs.RecipientInfo { internal KeyTransRecipientInfo() { } @@ -231,6 +255,9 @@ public enum RecipientInfoType Unknown = 0, KeyTransport = 1, KeyAgreement = 2, +#if NET11_0_OR_GREATER + KeyEncapsulation = 3, +#endif } public sealed partial class SignedCms { diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/DecryptorPal.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/DecryptorPal.cs index 6f13797dd9875b..9545cf1a0ebc6b 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/DecryptorPal.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/DecryptorPal.cs @@ -33,7 +33,7 @@ internal DecryptorPal(RecipientInfoCollection recipientInfos) public abstract ContentInfo? TryDecrypt( RecipientInfo recipientInfo, X509Certificate2? cert, - AsymmetricAlgorithm? privateKey, + EnvelopedCmsKey privateKey, X509Certificate2Collection originatorCerts, X509Certificate2Collection extraStore, out Exception? exception); diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/EnvelopedCmsKey.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/EnvelopedCmsKey.cs new file mode 100644 index 00000000000000..b09a876fefb52e --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/EnvelopedCmsKey.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Security.Cryptography; + +namespace Internal.Cryptography +{ + internal union EnvelopedCmsKey( + RSA, +#if NET11_0_OR_GREATER + MLKem, + CompositeMLKem, +#endif + EnvelopedCmsKey.None) + { + internal sealed record None + { + internal static None Instance { get; } = new None(); + + private None() + { + } + } + } +} diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/KemRecipientInfoPal.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/KemRecipientInfoPal.cs new file mode 100644 index 00000000000000..bd917c1b8bb480 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/KemRecipientInfoPal.cs @@ -0,0 +1,17 @@ +// 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.Security.Cryptography.Pkcs; + +namespace Internal.Cryptography +{ + internal abstract class KemRecipientInfoPal : RecipientInfoPal + { + internal abstract AlgorithmIdentifier KeyDerivationAlgorithm { get; } + internal abstract AlgorithmIdentifier KeyEncapsulationAlgorithm { get; } + internal abstract ReadOnlyMemory KeyEncapsulationCiphertext { get; } + internal abstract int KeyEncryptionKeyLengthInBytes { get; } + internal abstract ReadOnlyMemory? UserKeyingMaterial { get; } + } +} diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Decode.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Decode.cs index b21ecba4c41fb0..817611e3045979 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Decode.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Decode.cs @@ -68,6 +68,22 @@ public override DecryptorPal Decode( new KeyAgreeRecipientInfo(new ManagedKeyAgreePal(recipientInfo.Kari.Value, i))); } } + else if (recipientInfo.Ori.HasValue) + { +#if NET11_0_OR_GREATER + if (recipientInfo.Ori.Value.OriType == Oids.IdSmimeOriKem) + { + KemRecipientInfoAsn kemRecipientInfo = KemRecipientInfoAsn.Decode( + recipientInfo.Ori.Value.OriValue, + AsnEncodingRules.BER); + + recipientInfos.Add(new KemRecipientInfo(new ManagedKemRecipientInfoPal(kemRecipientInfo))); + continue; + } +#endif + + throw new CryptographicException(); + } else { Debug.Fail($"{nameof(RecipientInfoAsn)} deserialized with an unknown recipient type"); diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Decrypt.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Decrypt.cs index 6c43cb41e2ed68..d13140671bb90f 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Decrypt.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Decrypt.cs @@ -32,7 +32,7 @@ public ManagedDecryptorPal( public override unsafe ContentInfo? TryDecrypt( RecipientInfo recipientInfo, X509Certificate2? cert, - AsymmetricAlgorithm? privateKey, + EnvelopedCmsKey privateKey, X509Certificate2Collection originatorCerts, X509Certificate2Collection extraStore, out Exception? exception) @@ -40,45 +40,48 @@ public ManagedDecryptorPal( // When encryptedContent is null Windows seems to decrypt the CEK first, // then return a 0 byte answer. - Debug.Assert((cert != null) ^ (privateKey != null)); + Debug.Assert((cert is not null) ^ (privateKey is not EnvelopedCmsKey.None)); + + byte[]? cek; if (recipientInfo.Pal is ManagedKeyTransPal ktri) { - RSA? key = privateKey as RSA; + RSA? key = privateKey is RSA rsa ? rsa : null; - if (privateKey != null && key == null) + if (privateKey is not EnvelopedCmsKey.None && key is null) { exception = new CryptographicException(SR.Cryptography_Cms_Ktri_RSARequired); return null; } - byte[]? cek = ktri.DecryptCek(cert, key, out exception); - // Pin CEK to prevent it from getting copied during heap compaction. - fixed (byte* pinnedCek = cek) + cek = ktri.DecryptCek(cert, key, out exception); + } +#if NET11_0_OR_GREATER + else if (recipientInfo.Pal is ManagedKemRecipientInfoPal kemRecipientInfo) + { + if (privateKey is CompositeMLKem compositeMLKem) { - try - { - if (exception != null) - { - return null; - } - - return TryDecryptCore( - cek!, - _envelopedData.EncryptedContentInfo.ContentType, - _envelopedData.EncryptedContentInfo.EncryptedContent, - _envelopedData.EncryptedContentInfo.ContentEncryptionAlgorithm, - out exception); - } - finally - { - if (cek != null) - { - Array.Clear(cek, 0, cek.Length); - } - } + cek = kemRecipientInfo.DecryptCek(compositeMLKem, out exception); + } + else if (privateKey is MLKem mlKem) + { + cek = kemRecipientInfo.DecryptCek(mlKem, out exception); + } + else if (privateKey is EnvelopedCmsKey.None) + { + Debug.Assert(cert is not null); + cek = kemRecipientInfo.DecryptCek(cert, out exception); + } + else + { + exception = new CryptographicException( + SR.Cryptography_Cms_RecipientType_NotSupported, + recipientInfo.Type.ToString()); + + return null; } } +#endif else { exception = new CryptographicException( @@ -87,6 +90,32 @@ public ManagedDecryptorPal( return null; } + + // Pin CEK to prevent it from getting copied during heap compaction. + fixed (byte* pinnedCek = cek) + { + try + { + if (exception is not null) + { + return null; + } + + return TryDecryptCore( + cek!, + _envelopedData.EncryptedContentInfo.ContentType, + _envelopedData.EncryptedContentInfo.EncryptedContent, + _envelopedData.EncryptedContentInfo.ContentEncryptionAlgorithm, + out exception); + } + finally + { + if (cek is not null) + { + CryptographicOperations.ZeroMemory(cek); + } + } + } } public static ContentInfo? TryDecryptCore( diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Encrypt.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Encrypt.cs index f5036760a060bb..2f72e71b9429ab 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Encrypt.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Encrypt.cs @@ -51,7 +51,7 @@ public override unsafe byte[] Encrypt( } } - private byte[] Encrypt( + private static byte[] Encrypt( CmsRecipientCollection recipients, ContentInfo contentInfo, AlgorithmIdentifier contentEncryptionAlgorithm, @@ -102,12 +102,22 @@ private byte[] Encrypt( envelopedData.RecipientInfos = new RecipientInfoAsn[recipients.Count]; bool allRecipientsVersion0 = true; + bool hasOtherRecipientInfo = false; for (var i = 0; i < recipients.Count; i++) { CmsRecipient recipient = recipients[i]; bool v0Recipient; +#if NET11_0_OR_GREATER + if (PkcsHelpers.IsKeyEncapsulationAlgorithm(recipient.Certificate.GetKeyAlgorithm())) + { + envelopedData.RecipientInfos[i] = MakeKemRecipientInfo(cek, recipient); + hasOtherRecipientInfo = true; + continue; + } +#endif + envelopedData.RecipientInfos[i].Ktri = recipient.Certificate.GetKeyAlgorithm() switch { Oids.Rsa => MakeKtri(cek, recipient, out v0Recipient), @@ -126,7 +136,7 @@ private byte[] Encrypt( // v3 (RFC 3369): // * OriginatorInfo contains v2 attribute certificates (not supported) // * Any PWRI (password) recipients are present (not supported) - // * Any ORI (other) recipients are present (not supported) + // * Any ORI (other) recipients are present // v2 (RFC 2630): // * OriginatorInfo is present // * Any RecipientInfo has a non-zero version number @@ -135,7 +145,11 @@ private byte[] Encrypt( // v0 (RFC 2315): // * Anything not already matched - if (envelopedData.OriginatorInfo != null || + if (hasOtherRecipientInfo) + { + envelopedData.Version = 3; + } + else if (envelopedData.OriginatorInfo != null || !allRecipientsVersion0 || envelopedData.UnprotectedAttributes != null) { diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Kem.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Kem.cs new file mode 100644 index 00000000000000..94423cac8f2396 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Kem.cs @@ -0,0 +1,368 @@ +// 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.Diagnostics; +using System.Formats.Asn1; +using System.Security.Cryptography; +using System.Security.Cryptography.Asn1; +using System.Security.Cryptography.Pkcs; +using System.Security.Cryptography.Pkcs.Asn1; +using System.Security.Cryptography.X509Certificates; + +namespace Internal.Cryptography.Pal.AnyOS +{ + internal sealed partial class ManagedPkcsPal + { + private static readonly AlgorithmIdentifierAsn s_hkdfSha384Identifier = new() { Algorithm = Oids.HkdfWithSha384 }; + private static readonly AlgorithmIdentifierAsn s_aes256KwIdentifier = new() { Algorithm = Oids.Aes256Wrap }; + + private static RecipientInfoAsn MakeKemRecipientInfo(byte[] cek, CmsRecipient recipient) + { + KemRecipientInfoAsn kemRecipientInfo = MakeKemri(cek, recipient); + AsnWriter writer = new AsnWriter(AsnEncodingRules.DER); + kemRecipientInfo.Encode(writer); + + return new RecipientInfoAsn + { + Ori = new OtherRecipientInfoAsn + { + OriType = Oids.IdSmimeOriKem, + OriValue = writer.Encode(), + }, + }; + } + + private static KemRecipientInfoAsn MakeKemri(byte[] cek, CmsRecipient recipient) + { + if (cek.Length < ManagedKemRecipientInfoPal.Aes128KeySizeInBytes || + cek.Length % 8 != 0 || + cek.Length > ManagedKemRecipientInfoPal.Aes256KeySizeInBytes) + { + throw new CryptographicException(SR.Cryptography_Cms_InvalidSymmetricKey); + } + + KemRecipientInfoAsn kemri = default; + kemri.Rid = PkcsHelpers.MakeRecipientIdentifier(recipient); + + // KDF and AES-KW algorithm is not user selectable currently. Always use AES-256-KW with SHA-2-384 since it + // meets all requirements. + kemri.Kdf = s_hkdfSha384Identifier; + kemri.Wrap = s_aes256KwIdentifier; + kemri.KekLength = ManagedKemRecipientInfoPal.Aes256KeySizeInBytes; + kemri.Ukm = recipient.KeyEncapsulationUserKeyingMaterial; + + const int SharedSecretSize = 32; + Span sharedSecret = stackalloc byte[SharedSecretSize]; + byte[]? algorithmParameters = recipient.Certificate.GetKeyAlgorithmParameters(); + + try + { + string keyAlgorithm = recipient.Certificate.GetKeyAlgorithm(); + + if (PkcsHelpers.IsCompositeMLKemAlgorithm(keyAlgorithm)) + { + throw new PlatformNotSupportedException( + SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(CompositeMLKem))); + } + + switch (keyAlgorithm) + { + case Oids.MlKem512 or Oids.MlKem768 or Oids.MlKem1024 when algorithmParameters is null: + using (MLKem? key = recipient.Certificate.GetMLKemPublicKey()) + { + Debug.Assert(key is not null); + byte[] ciphertext = new byte[key.Algorithm.CiphertextSizeInBytes]; + Debug.Assert(key.Algorithm.SharedSecretSizeInBytes == SharedSecretSize); + + key.Encapsulate(ciphertext, sharedSecret); + kemri.Kemct = ciphertext; + kemri.Kem.Algorithm = keyAlgorithm; + } + break; + default: + throw new CryptographicException(SR.Cryptography_Cms_UnknownAlgorithm, keyAlgorithm); + } + + State3, ReadOnlySpan, int> encodeState = new(cek, sharedSecret, 0); + AsnWriter hkdfInfoWriter = ManagedKemRecipientInfoPal.EncodeKdfInfo( + kemri.Wrap, + kemri.KekLength, + kemri.Ukm); + + kemri.EncryptedKey = hkdfInfoWriter.Encode(encodeState, static (state, info) => + { + Span derivedKey = stackalloc byte[ManagedKemRecipientInfoPal.Aes256KeySizeInBytes]; + + try + { + HKDF.DeriveKey(HashAlgorithmName.SHA384, state.Item2, derivedKey, salt: [], info); + + using (Aes aes = Aes.Create()) + { + aes.SetKey(derivedKey); + return aes.EncryptKeyWrap(state.Item1); + } + } + finally + { + CryptographicOperations.ZeroMemory(derivedKey); + } + }); + } + finally + { + CryptographicOperations.ZeroMemory(sharedSecret); + } + + return kemri; + } + + private sealed class ManagedKemRecipientInfoPal : KemRecipientInfoPal + { + internal const int Aes128KeySizeInBytes = 128 / 8; + internal const int Aes192KeySizeInBytes = 192 / 8; + internal const int Aes256KeySizeInBytes = 256 / 8; + internal const int SharedSecretSizeInBytes = 32; + internal const int MinimumKeySizeInBytes = 24; + + private readonly KemRecipientInfoAsn _asn; + + internal ManagedKemRecipientInfoPal(KemRecipientInfoAsn asn) + { + _asn = asn; + } + + public override byte[] EncryptedKey => field ??= _asn.EncryptedKey.ToArray(); + + internal override AlgorithmIdentifier KeyDerivationAlgorithm => field ??= ToAlgorithmIdentifier(_asn.Kdf); + + internal override AlgorithmIdentifier KeyEncapsulationAlgorithm => field ??= ToAlgorithmIdentifier(_asn.Kem); + + internal override ReadOnlyMemory KeyEncapsulationCiphertext => _asn.Kemct; + + public override AlgorithmIdentifier KeyEncryptionAlgorithm => field ??= ToAlgorithmIdentifier(_asn.Wrap); + + internal override int KeyEncryptionKeyLengthInBytes => _asn.KekLength; + + public override SubjectIdentifier RecipientIdentifier => + field ??= new(_asn.Rid.IssuerAndSerialNumber, _asn.Rid.SubjectKeyIdentifier); + + internal override ReadOnlyMemory? UserKeyingMaterial => _asn.Ukm; + + public override int Version => _asn.Version; + +#pragma warning disable CA1822 // Instance member can be made static + internal byte[]? DecryptCek(CompositeMLKem privateKey, out Exception? exception) +#pragma warning restore CA1822 + { + _ = privateKey; + exception = new PlatformNotSupportedException(); + return null; + } + + internal byte[]? DecryptCek(X509Certificate2 cert, out Exception? exception) + { + string kemAlgorithm = _asn.Kem.Algorithm; + + if (PkcsHelpers.IsCompositeMLKemAlgorithm(kemAlgorithm)) + { + exception = new PlatformNotSupportedException( + SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(CompositeMLKem))); + + return null; + } + + if (PkcsHelpers.IsMLKemAlgorithm(kemAlgorithm)) + { + using (MLKem? certificatePrivateKey = cert.GetMLKemPrivateKey()) + { + if (certificatePrivateKey is null) + { + exception = new CryptographicException(SR.Cryptography_Cms_Signing_RequiresPrivateKey); + return null; + } + + return DecryptCek(certificatePrivateKey, out exception); + } + } + + exception = new CryptographicException(SR.Cryptography_Cms_UnknownAlgorithm, kemAlgorithm); + return null; + } + + internal byte[]? DecryptCek(MLKem privateKey, out Exception? exception) + { + exception = null; + + MLKemAlgorithm? encodedAlgorithm = KeyEncapsulationAlgorithm.Oid.Value switch + { + Oids.MlKem512 => MLKemAlgorithm.MLKem512, + Oids.MlKem768 => MLKemAlgorithm.MLKem768, + Oids.MlKem1024 => MLKemAlgorithm.MLKem1024, + _ => null, + }; + + // RFC 9936: Appendix A's KEM-ALGORITHMs are all `PARAMS ARE absent`. + if (encodedAlgorithm is null || + encodedAlgorithm != privateKey.Algorithm || + KeyEncapsulationAlgorithm.Parameters is not []) + { + exception = new CryptographicException(SR.Cryptography_Cms_UnknownAlgorithm); + return null; + } + + if (KeyEncapsulationCiphertext.Length != encodedAlgorithm.CiphertextSizeInBytes) + { + exception = new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + return null; + } + + // All ML-KEM and Composite-ML-KEM instances have a 256-bit shared secret. + // Since the decapulation implementations use precisely sized buffers an assert is enough here. + Debug.Assert(encodedAlgorithm.SharedSecretSizeInBytes == SharedSecretSizeInBytes); + + return DecryptCek( + privateKey, + static (privateKey, ciphertext, destination) => privateKey.Decapsulate(ciphertext, destination), + out exception); + } + + private byte[]? DecryptCek( + TState state, + Action, Span> decapsulator, + out Exception? exception) + { + // RFC 9629 section 3 "MUST be 0" + if (Version != 0) + { + exception = new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + return null; + } + + HashAlgorithmName? hkdfAlgorithm = KeyDerivationAlgorithm.Oid.Value switch + { + // There is no IETF-specified OID for HKDF with SHA-1 or MD5. + Oids.HkdfWithSha256 => HashAlgorithmName.SHA256, + Oids.HkdfWithSha384 => HashAlgorithmName.SHA384, + Oids.HkdfWithSha512 => HashAlgorithmName.SHA512, + Oids.HkdfWithSha3_256 => HashAlgorithmName.SHA3_256, + Oids.HkdfWithSha3_384 => HashAlgorithmName.SHA3_384, + Oids.HkdfWithSha3_512 => HashAlgorithmName.SHA3_512, + _ => null, + }; + + if (hkdfAlgorithm is null || KeyDerivationAlgorithm.Parameters is not []) + { + exception = new CryptographicException(SR.Cryptography_Cms_UnknownAlgorithm); + return null; + } + + // Validate the that OID of the AES-KW algorithm matches the key size. + int? aesKeySizeInBytes = KeyEncryptionAlgorithm.Oid.Value switch + { + Oids.Aes128Wrap => Aes128KeySizeInBytes, + Oids.Aes192Wrap => Aes192KeySizeInBytes, + Oids.Aes256Wrap => Aes256KeySizeInBytes, + _ => null, + }; + + // RFC 3565 2.3.2 explicitly requires params ARE absent for the key encryption algorithm. + if (aesKeySizeInBytes != KeyEncryptionKeyLengthInBytes || KeyEncryptionAlgorithm.Parameters is not []) + { + exception = new CryptographicException(SR.Cryptography_Cms_UnknownAlgorithm); + return null; + } + + if (_asn.EncryptedKey.Length % 8 != 0 || _asn.EncryptedKey.Length < MinimumKeySizeInBytes) + { + exception = new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + return null; + } + + Span sharedSecret = stackalloc byte[SharedSecretSizeInBytes]; + + try + { + decapsulator(state, KeyEncapsulationCiphertext.Span, sharedSecret); + + exception = null; + State3> encodeState = + new(this, hkdfAlgorithm.Value, sharedSecret); + + return EncodeKdfInfo(_asn.Wrap, _asn.KekLength, _asn.Ukm).Encode(encodeState, + static (state, info) => + { + // AES-256-KW is the largest supported key size. + const int MaxKeyEncryptionKeySize = 32; + Span derivedKey = stackalloc byte[MaxKeyEncryptionKeySize] + .Slice(0, state.Item1.KeyEncryptionKeyLengthInBytes); + + try + { + HKDF.DeriveKey(state.Item2, state.Item3, derivedKey, salt: [], info); + + using (Aes aes = Aes.Create()) + { + aes.SetKey(derivedKey); + return aes.DecryptKeyWrap(state.Item1._asn.EncryptedKey.Span); + } + } + finally + { + CryptographicOperations.ZeroMemory(derivedKey); + } + }); + } + catch (CryptographicException e) + { + exception = e; + return null; + } + finally + { + CryptographicOperations.ZeroMemory(sharedSecret); + } + } + + private static AlgorithmIdentifier ToAlgorithmIdentifier(AlgorithmIdentifierAsn algorithmIdentifier) + { + return new AlgorithmIdentifier(new Oid(algorithmIdentifier.Algorithm, null)) + { + Parameters = algorithmIdentifier.Parameters?.ToArray() ?? Array.Empty(), + }; + } + + internal static AsnWriter EncodeKdfInfo(in AlgorithmIdentifierAsn wrap, int kekLength, ReadOnlyMemory? ukm) + { + CmsOriForKemOtherInfoAsn kdfInfo = new() + { + Wrap = wrap, + KekLength = kekLength, + Ukm = ukm, + }; + + AsnWriter writer = new AsnWriter(AsnEncodingRules.DER); + kdfInfo.Encode(writer); + return writer; + } + } + } + + file readonly ref struct State3 + where T1 : allows ref struct + where T2 : allows ref struct + where T3 : allows ref struct + { + internal T1 Item1 { get; } + internal T2 Item2 { get; } + internal T3 Item3 { get; } + + internal State3(T1 item1, T2 item2, T3 item3) + { + Item1 = item1; + Item2 = item2; + Item3 = item3; + } + } +} diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.KeyTrans.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.KeyTrans.cs index 7ff3a3ecc65a27..efade28a0d0d45 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.KeyTrans.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.KeyTrans.cs @@ -105,36 +105,17 @@ internal ManagedKeyTransPal(KeyTransRecipientInfoAsn asn) } } - private KeyTransRecipientInfoAsn MakeKtri( + private static KeyTransRecipientInfoAsn MakeKtri( byte[] cek, CmsRecipient recipient, out bool v0Recipient) { KeyTransRecipientInfoAsn ktri = default; + ktri.Rid = PkcsHelpers.MakeRecipientIdentifier(recipient); if (recipient.RecipientIdentifierType == SubjectIdentifierType.SubjectKeyIdentifier) { ktri.Version = 2; - ktri.Rid.SubjectKeyIdentifier = GetSubjectKeyIdentifier(recipient.Certificate); - } - else if (recipient.RecipientIdentifierType == SubjectIdentifierType.IssuerAndSerialNumber) - { - byte[] serial = recipient.Certificate.GetSerialNumber(); - Array.Reverse(serial); - - IssuerAndSerialNumberAsn iasn = new IssuerAndSerialNumberAsn - { - Issuer = recipient.Certificate.IssuerName.RawData, - SerialNumber = serial, - }; - - ktri.Rid.IssuerAndSerialNumber = iasn; - } - else - { - throw new CryptographicException( - SR.Cryptography_Cms_Invalid_Subject_Identifier_Type, - recipient.RecipientIdentifierType.ToString()); } RSAEncryptionPadding padding = recipient.RSAEncryptionPadding ?? RSAEncryptionPadding.Pkcs1; diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/Windows/DecryptorPalWindows.Decrypt.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/Windows/DecryptorPalWindows.Decrypt.cs index 8e9848075f76a9..8d08f2a80d0d83 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/Windows/DecryptorPalWindows.Decrypt.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/Windows/DecryptorPalWindows.Decrypt.cs @@ -19,23 +19,22 @@ internal sealed partial class DecryptorPalWindows : DecryptorPal public sealed override unsafe ContentInfo? TryDecrypt( RecipientInfo recipientInfo, X509Certificate2? cert, - AsymmetricAlgorithm? privateKey, + EnvelopedCmsKey privateKey, X509Certificate2Collection originatorCerts, X509Certificate2Collection extraStore, out Exception? exception) { - Debug.Assert((cert != null) ^ (privateKey != null)); - - if (privateKey != null) +#if NET11_0_OR_GREATER + if (recipientInfo.Type == RecipientInfoType.KeyEncapsulation) { - RSA? key = privateKey as RSA; + throw new PlatformNotSupportedException(); + } +#endif - if (key == null) - { - exception = new CryptographicException(SR.Cryptography_Cms_Ktri_RSARequired); - return null; - } + Debug.Assert((cert is not null) ^ (privateKey is not EnvelopedCmsKey.None)); + if (privateKey is RSA key) + { ContentInfo contentInfo = _hCryptMsg.GetContentInfo(); byte[]? cek = AnyOS.ManagedPkcsPal.ManagedKeyTransPal.DecryptCekCore( cert, @@ -72,6 +71,11 @@ internal sealed partial class DecryptorPalWindows : DecryptorPal } } + if (privateKey is not EnvelopedCmsKey.None) + { + throw new PlatformNotSupportedException(); + } + Debug.Assert(recipientInfo != null); Debug.Assert(cert != null); Debug.Assert(originatorCerts != null); diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/PkcsHelpers.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/PkcsHelpers.cs index d2184523ec40f0..7882b4b143eefc 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/PkcsHelpers.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/PkcsHelpers.cs @@ -11,6 +11,7 @@ using System.Security.Cryptography.Asn1; using System.Security.Cryptography.Asn1.Pkcs7; using System.Security.Cryptography.Pkcs; +using System.Security.Cryptography.Pkcs.Asn1; using System.Security.Cryptography.X509Certificates; using System.Text; using X509IssuerSerial = System.Security.Cryptography.Xml.X509IssuerSerial; @@ -135,6 +136,16 @@ public static CmsRecipientCollection DeepCopy(this CmsRecipientCollection recipi X509Certificate2 certCopy = new X509Certificate2(originalCert.Handle); CmsRecipient recipientCopy; +#if NET11_0_OR_GREATER + if (recipient.KeyEncapsulationUserKeyingMaterial.HasValue) + { + recipientCopy = CmsRecipient.CreateForKeyEncapsulation( + recipient.RecipientIdentifierType, + certCopy, + recipient.KeyEncapsulationUserKeyingMaterial.Value.Span); + } + else +#endif if (recipient.RSAEncryptionPadding is null) { recipientCopy = new CmsRecipient(recipient.RecipientIdentifierType, certCopy); @@ -150,6 +161,60 @@ public static CmsRecipientCollection DeepCopy(this CmsRecipientCollection recipi return recipientsCopy; } + internal static RecipientIdentifierAsn MakeRecipientIdentifier(CmsRecipient recipient) + { + RecipientIdentifierAsn recipientIdentifier = default; + + if (recipient.RecipientIdentifierType == SubjectIdentifierType.SubjectKeyIdentifier) + { + recipientIdentifier.SubjectKeyIdentifier = + PkcsPal.Instance.GetSubjectKeyIdentifier(recipient.Certificate); + } + else if (recipient.RecipientIdentifierType == SubjectIdentifierType.IssuerAndSerialNumber) + { + byte[] serialNumber = recipient.Certificate.GetSerialNumber(); + Array.Reverse(serialNumber); + + recipientIdentifier.IssuerAndSerialNumber = new IssuerAndSerialNumberAsn + { + Issuer = recipient.Certificate.IssuerName.RawData, + SerialNumber = serialNumber, + }; + } + else + { + throw new CryptographicException( + SR.Cryptography_Cms_Invalid_Subject_Identifier_Type, + recipient.RecipientIdentifierType.ToString()); + } + + return recipientIdentifier; + } + +#if NET11_0_OR_GREATER + internal static bool IsMLKemAlgorithm(string? oid) => + oid is Oids.MlKem512 or + Oids.MlKem768 or + Oids.MlKem1024; + + internal static bool IsCompositeMLKemAlgorithm(string? oid) => + oid is Oids.MLKem768WithRsaOaep2048Sha3_256 or + Oids.MLKem768WithRsaOaep3072Sha3_256 or + Oids.MLKem768WithRsaOaep4096Sha3_256 or + Oids.MLKem768WithX25519Sha3_256 or + Oids.MLKem768WithECDiffieHellmanP256Sha3_256 or + Oids.MLKem768WithECDiffieHellmanP384Sha3_256 or + Oids.MLKem768WithECDiffieHellmanBrainpoolP256r1Sha3_256 or + Oids.MLKem1024WithRsaOaep3072Sha3_256 or + Oids.MLKem1024WithECDiffieHellmanP384Sha3_256 or + Oids.MLKem1024WithECDiffieHellmanBrainpoolP384r1Sha3_256 or + Oids.MLKem1024WithX448Sha3_256 or + Oids.MLKem1024WithECDiffieHellmanP521Sha3_256; + + internal static bool IsKeyEncapsulationAlgorithm(string? oid) => + IsMLKemAlgorithm(oid) || IsCompositeMLKemAlgorithm(oid); +#endif + public static X509Certificate2Collection GetStoreCertificates(StoreName storeName, StoreLocation storeLocation, bool openExistingOnly) { using (X509Store store = new X509Store(storeName, storeLocation)) diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj b/src/libraries/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj index a6d789e4ff0fc3..b45323d15b8092 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj @@ -68,6 +68,7 @@ System.Security.Cryptography.Pkcs.EnvelopedCms + @@ -179,6 +180,10 @@ System.Security.Cryptography.Pkcs.EnvelopedCms System\Security\Cryptography\Pkcs\Asn1\OtherKeyAttributeAsn.xml + + + System\Security\Cryptography\Pkcs\Asn1\OtherRecipientInfoAsn.xml + System\Security\Cryptography\Pkcs\Asn1\RecipientEncryptedKeyAsn.xml @@ -197,6 +202,28 @@ System.Security.Cryptography.Pkcs.EnvelopedCms + + + + + + + System\Security\Cryptography\Pkcs\Asn1\CmsOriForKemOtherInfoAsn.xml + + + System\Security\Cryptography\Pkcs\Asn1\KemRecipientInfoAsn.xml + + + + + + + + + + @@ -585,6 +612,7 @@ System.Security.Cryptography.Pkcs.EnvelopedCms System\Security\Cryptography\Pkcs\Asn1\SigningCertificateV2Asn.xml + diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/NetStandardShims.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/NetStandardShims.cs new file mode 100644 index 00000000000000..52c26cb9481a50 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/NetStandardShims.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; + +namespace System.Security.Cryptography +{ +#if !NET && !NETSTANDARD2_1_OR_GREATER + internal static class CryptographicOperations + { + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + internal static void ZeroMemory(Span buffer) + { + buffer.Clear(); + } + } +#endif +} diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/CmsOriForKemOtherInfoAsn.xml b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/CmsOriForKemOtherInfoAsn.xml new file mode 100644 index 00000000000000..b3dcea9f5f5bd1 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/CmsOriForKemOtherInfoAsn.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/CmsOriForKemOtherInfoAsn.xml.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/CmsOriForKemOtherInfoAsn.xml.cs new file mode 100644 index 00000000000000..6fa4ce82965f95 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/CmsOriForKemOtherInfoAsn.xml.cs @@ -0,0 +1,115 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma warning disable SA1028 // ignore whitespace warnings for generated code +using System; +using System.Formats.Asn1; +using System.Runtime.InteropServices; + +namespace System.Security.Cryptography.Pkcs.Asn1 +{ + [StructLayout(LayoutKind.Sequential)] + internal partial struct CmsOriForKemOtherInfoAsn + { + internal System.Security.Cryptography.Asn1.AlgorithmIdentifierAsn Wrap; + internal int KekLength; + internal ReadOnlyMemory? Ukm; + + internal readonly void Encode(AsnWriter writer) + { + Encode(writer, Asn1Tag.Sequence); + } + + internal readonly void Encode(AsnWriter writer, Asn1Tag tag) + { + writer.PushSequence(tag); + + Wrap.Encode(writer); + writer.WriteInteger(KekLength); + + if (Ukm.HasValue) + { + writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, 0)); + writer.WriteOctetString(Ukm.Value.Span); + writer.PopSequence(new Asn1Tag(TagClass.ContextSpecific, 0)); + } + + writer.PopSequence(tag); + } + + internal static CmsOriForKemOtherInfoAsn Decode(ReadOnlyMemory encoded, AsnEncodingRules ruleSet) + { + return Decode(Asn1Tag.Sequence, encoded, ruleSet); + } + + internal static CmsOriForKemOtherInfoAsn Decode(Asn1Tag expectedTag, ReadOnlyMemory encoded, AsnEncodingRules ruleSet) + { + try + { + ValueAsnReader reader = new ValueAsnReader(encoded.Span, ruleSet); + + DecodeCore(ref reader, expectedTag, encoded, out CmsOriForKemOtherInfoAsn decoded); + reader.ThrowIfNotEmpty(); + return decoded; + } + catch (AsnContentException e) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e); + } + } + + internal static void Decode(ref ValueAsnReader reader, ReadOnlyMemory rebind, out CmsOriForKemOtherInfoAsn decoded) + { + Decode(ref reader, Asn1Tag.Sequence, rebind, out decoded); + } + + internal static void Decode(ref ValueAsnReader reader, Asn1Tag expectedTag, ReadOnlyMemory rebind, out CmsOriForKemOtherInfoAsn decoded) + { + try + { + DecodeCore(ref reader, expectedTag, rebind, out decoded); + } + catch (AsnContentException e) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e); + } + } + + private static void DecodeCore(ref ValueAsnReader reader, Asn1Tag expectedTag, ReadOnlyMemory rebind, out CmsOriForKemOtherInfoAsn decoded) + { + decoded = default; + ValueAsnReader sequenceReader = reader.ReadSequence(expectedTag); + ValueAsnReader explicitReader; + ReadOnlySpan rebindSpan = rebind.Span; + int offset; + ReadOnlySpan tmpSpan; + + System.Security.Cryptography.Asn1.AlgorithmIdentifierAsn.Decode(ref sequenceReader, rebind, out decoded.Wrap); + + if (!sequenceReader.TryReadInt32(out decoded.KekLength)) + { + sequenceReader.ThrowIfNotEmpty(); + } + + + if (sequenceReader.HasData && sequenceReader.PeekTag().HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, 0))) + { + explicitReader = sequenceReader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, 0)); + + if (explicitReader.TryReadPrimitiveOctetString(out tmpSpan)) + { + decoded.Ukm = rebindSpan.Overlaps(tmpSpan, out offset) ? rebind.Slice(offset, tmpSpan.Length) : tmpSpan.ToArray(); + } + else + { + decoded.Ukm = explicitReader.ReadOctetString(); + } + + explicitReader.ThrowIfNotEmpty(); + } + + + sequenceReader.ThrowIfNotEmpty(); + } + } +} diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/KemRecipientInfoAsn.xml b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/KemRecipientInfoAsn.xml new file mode 100644 index 00000000000000..347fbb6f09378c --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/KemRecipientInfoAsn.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/KemRecipientInfoAsn.xml.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/KemRecipientInfoAsn.xml.cs new file mode 100644 index 00000000000000..19ca9ae56da984 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/KemRecipientInfoAsn.xml.cs @@ -0,0 +1,156 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma warning disable SA1028 // ignore whitespace warnings for generated code +using System; +using System.Formats.Asn1; +using System.Runtime.InteropServices; + +namespace System.Security.Cryptography.Pkcs.Asn1 +{ + [StructLayout(LayoutKind.Sequential)] + internal partial struct KemRecipientInfoAsn + { + internal int Version; + internal System.Security.Cryptography.Pkcs.Asn1.RecipientIdentifierAsn Rid; + internal System.Security.Cryptography.Asn1.AlgorithmIdentifierAsn Kem; + internal ReadOnlyMemory Kemct; + internal System.Security.Cryptography.Asn1.AlgorithmIdentifierAsn Kdf; + internal int KekLength; + internal ReadOnlyMemory? Ukm; + internal System.Security.Cryptography.Asn1.AlgorithmIdentifierAsn Wrap; + internal ReadOnlyMemory EncryptedKey; + + internal readonly void Encode(AsnWriter writer) + { + Encode(writer, Asn1Tag.Sequence); + } + + internal readonly void Encode(AsnWriter writer, Asn1Tag tag) + { + writer.PushSequence(tag); + + writer.WriteInteger(Version); + Rid.Encode(writer); + Kem.Encode(writer); + writer.WriteOctetString(Kemct.Span); + Kdf.Encode(writer); + writer.WriteInteger(KekLength); + + if (Ukm.HasValue) + { + writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, 0)); + writer.WriteOctetString(Ukm.Value.Span); + writer.PopSequence(new Asn1Tag(TagClass.ContextSpecific, 0)); + } + + Wrap.Encode(writer); + writer.WriteOctetString(EncryptedKey.Span); + writer.PopSequence(tag); + } + + internal static KemRecipientInfoAsn Decode(ReadOnlyMemory encoded, AsnEncodingRules ruleSet) + { + return Decode(Asn1Tag.Sequence, encoded, ruleSet); + } + + internal static KemRecipientInfoAsn Decode(Asn1Tag expectedTag, ReadOnlyMemory encoded, AsnEncodingRules ruleSet) + { + try + { + ValueAsnReader reader = new ValueAsnReader(encoded.Span, ruleSet); + + DecodeCore(ref reader, expectedTag, encoded, out KemRecipientInfoAsn decoded); + reader.ThrowIfNotEmpty(); + return decoded; + } + catch (AsnContentException e) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e); + } + } + + internal static void Decode(ref ValueAsnReader reader, ReadOnlyMemory rebind, out KemRecipientInfoAsn decoded) + { + Decode(ref reader, Asn1Tag.Sequence, rebind, out decoded); + } + + internal static void Decode(ref ValueAsnReader reader, Asn1Tag expectedTag, ReadOnlyMemory rebind, out KemRecipientInfoAsn decoded) + { + try + { + DecodeCore(ref reader, expectedTag, rebind, out decoded); + } + catch (AsnContentException e) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e); + } + } + + private static void DecodeCore(ref ValueAsnReader reader, Asn1Tag expectedTag, ReadOnlyMemory rebind, out KemRecipientInfoAsn decoded) + { + decoded = default; + ValueAsnReader sequenceReader = reader.ReadSequence(expectedTag); + ValueAsnReader explicitReader; + ReadOnlySpan rebindSpan = rebind.Span; + int offset; + ReadOnlySpan tmpSpan; + + + if (!sequenceReader.TryReadInt32(out decoded.Version)) + { + sequenceReader.ThrowIfNotEmpty(); + } + + System.Security.Cryptography.Pkcs.Asn1.RecipientIdentifierAsn.Decode(ref sequenceReader, rebind, out decoded.Rid); + System.Security.Cryptography.Asn1.AlgorithmIdentifierAsn.Decode(ref sequenceReader, rebind, out decoded.Kem); + + if (sequenceReader.TryReadPrimitiveOctetString(out tmpSpan)) + { + decoded.Kemct = rebindSpan.Overlaps(tmpSpan, out offset) ? rebind.Slice(offset, tmpSpan.Length) : tmpSpan.ToArray(); + } + else + { + decoded.Kemct = sequenceReader.ReadOctetString(); + } + + System.Security.Cryptography.Asn1.AlgorithmIdentifierAsn.Decode(ref sequenceReader, rebind, out decoded.Kdf); + + if (!sequenceReader.TryReadInt32(out decoded.KekLength)) + { + sequenceReader.ThrowIfNotEmpty(); + } + + + if (sequenceReader.HasData && sequenceReader.PeekTag().HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, 0))) + { + explicitReader = sequenceReader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, 0)); + + if (explicitReader.TryReadPrimitiveOctetString(out tmpSpan)) + { + decoded.Ukm = rebindSpan.Overlaps(tmpSpan, out offset) ? rebind.Slice(offset, tmpSpan.Length) : tmpSpan.ToArray(); + } + else + { + decoded.Ukm = explicitReader.ReadOctetString(); + } + + explicitReader.ThrowIfNotEmpty(); + } + + System.Security.Cryptography.Asn1.AlgorithmIdentifierAsn.Decode(ref sequenceReader, rebind, out decoded.Wrap); + + if (sequenceReader.TryReadPrimitiveOctetString(out tmpSpan)) + { + decoded.EncryptedKey = rebindSpan.Overlaps(tmpSpan, out offset) ? rebind.Slice(offset, tmpSpan.Length) : tmpSpan.ToArray(); + } + else + { + decoded.EncryptedKey = sequenceReader.ReadOctetString(); + } + + + sequenceReader.ThrowIfNotEmpty(); + } + } +} diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/OtherRecipientInfoAsn.xml b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/OtherRecipientInfoAsn.xml new file mode 100644 index 00000000000000..3aa01725ea0e05 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/OtherRecipientInfoAsn.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/OtherRecipientInfoAsn.xml.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/OtherRecipientInfoAsn.xml.cs new file mode 100644 index 00000000000000..7263c8b770734a --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/OtherRecipientInfoAsn.xml.cs @@ -0,0 +1,98 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma warning disable SA1028 // ignore whitespace warnings for generated code +using System; +using System.Formats.Asn1; +using System.Runtime.InteropServices; + +namespace System.Security.Cryptography.Pkcs.Asn1 +{ + [StructLayout(LayoutKind.Sequential)] + internal partial struct OtherRecipientInfoAsn + { + internal string OriType; + internal ReadOnlyMemory OriValue; + + internal readonly void Encode(AsnWriter writer) + { + Encode(writer, Asn1Tag.Sequence); + } + + internal readonly void Encode(AsnWriter writer, Asn1Tag tag) + { + writer.PushSequence(tag); + + try + { + writer.WriteObjectIdentifier(OriType); + } + catch (ArgumentException e) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e); + } + try + { + writer.WriteEncodedValue(OriValue.Span); + } + catch (ArgumentException e) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e); + } + writer.PopSequence(tag); + } + + internal static OtherRecipientInfoAsn Decode(ReadOnlyMemory encoded, AsnEncodingRules ruleSet) + { + return Decode(Asn1Tag.Sequence, encoded, ruleSet); + } + + internal static OtherRecipientInfoAsn Decode(Asn1Tag expectedTag, ReadOnlyMemory encoded, AsnEncodingRules ruleSet) + { + try + { + ValueAsnReader reader = new ValueAsnReader(encoded.Span, ruleSet); + + DecodeCore(ref reader, expectedTag, encoded, out OtherRecipientInfoAsn decoded); + reader.ThrowIfNotEmpty(); + return decoded; + } + catch (AsnContentException e) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e); + } + } + + internal static void Decode(ref ValueAsnReader reader, ReadOnlyMemory rebind, out OtherRecipientInfoAsn decoded) + { + Decode(ref reader, Asn1Tag.Sequence, rebind, out decoded); + } + + internal static void Decode(ref ValueAsnReader reader, Asn1Tag expectedTag, ReadOnlyMemory rebind, out OtherRecipientInfoAsn decoded) + { + try + { + DecodeCore(ref reader, expectedTag, rebind, out decoded); + } + catch (AsnContentException e) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e); + } + } + + private static void DecodeCore(ref ValueAsnReader reader, Asn1Tag expectedTag, ReadOnlyMemory rebind, out OtherRecipientInfoAsn decoded) + { + decoded = default; + ValueAsnReader sequenceReader = reader.ReadSequence(expectedTag); + ReadOnlySpan rebindSpan = rebind.Span; + int offset; + ReadOnlySpan tmpSpan; + + decoded.OriType = sequenceReader.ReadObjectIdentifier(); + tmpSpan = sequenceReader.ReadEncodedValue(); + decoded.OriValue = rebindSpan.Overlaps(tmpSpan, out offset) ? rebind.Slice(offset, tmpSpan.Length) : tmpSpan.ToArray(); + + sequenceReader.ThrowIfNotEmpty(); + } + } +} diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/RecipientInfoAsn.xml b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/RecipientInfoAsn.xml index c16ac770b55c70..3fb310ff05c9a3 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/RecipientInfoAsn.xml +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/RecipientInfoAsn.xml @@ -17,6 +17,7 @@ --> - diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/RecipientInfoAsn.xml.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/RecipientInfoAsn.xml.cs index 61d978cc1186ed..a91ff41f34c1bf 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/RecipientInfoAsn.xml.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/Asn1/RecipientInfoAsn.xml.cs @@ -26,6 +26,7 @@ static ValidateRecipientInfoAsn() ensureUniqueTag(Asn1Tag.Sequence, "Ktri"); ensureUniqueTag(new Asn1Tag(TagClass.ContextSpecific, 1), "Kari"); + ensureUniqueTag(new Asn1Tag(TagClass.ContextSpecific, 4), "Ori"); } [System.Runtime.CompilerServices.MethodImpl( @@ -40,6 +41,7 @@ internal partial struct RecipientInfoAsn { internal System.Security.Cryptography.Pkcs.Asn1.KeyTransRecipientInfoAsn? Ktri; internal System.Security.Cryptography.Pkcs.Asn1.KeyAgreeRecipientInfoAsn? Kari; + internal System.Security.Cryptography.Pkcs.Asn1.OtherRecipientInfoAsn? Ori; #if DEBUG static RecipientInfoAsn() @@ -70,6 +72,15 @@ internal readonly void Encode(AsnWriter writer) wroteValue = true; } + if (Ori.HasValue) + { + if (wroteValue) + throw new CryptographicException(); + + Ori.Value.Encode(writer, new Asn1Tag(TagClass.ContextSpecific, 4)); + wroteValue = true; + } + if (!wroteValue) { throw new CryptographicException(); @@ -122,6 +133,13 @@ private static void DecodeCore(ref ValueAsnReader reader, ReadOnlyMemory r System.Security.Cryptography.Pkcs.Asn1.KeyAgreeRecipientInfoAsn.Decode(ref reader, new Asn1Tag(TagClass.ContextSpecific, 1), rebind, out tmpKari); decoded.Kari = tmpKari; + } + else if (tag.HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, 4))) + { + System.Security.Cryptography.Pkcs.Asn1.OtherRecipientInfoAsn tmpOri; + System.Security.Cryptography.Pkcs.Asn1.OtherRecipientInfoAsn.Decode(ref reader, new Asn1Tag(TagClass.ContextSpecific, 4), rebind, out tmpOri); + decoded.Ori = tmpOri; + } else { diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsRecipient.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsRecipient.cs index 8169d5cce0a460..be734c71f1629d 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsRecipient.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsRecipient.cs @@ -5,6 +5,8 @@ using System.Diagnostics; using System.Security.Cryptography.X509Certificates; +using Internal.Cryptography; + namespace System.Security.Cryptography.Pkcs { public sealed class CmsRecipient @@ -72,6 +74,63 @@ public CmsRecipient(SubjectIdentifierType recipientIdentifierType, X509Certifica public SubjectIdentifierType RecipientIdentifierType { get; } public X509Certificate2 Certificate { get; } +#if NET11_0_OR_GREATER + internal ReadOnlyMemory? KeyEncapsulationUserKeyingMaterial { get; private set; } + + /// + /// Creates a recipient that uses key encapsulation. + /// + /// The recipient certificate. + /// The user keying material to include. + /// A recipient that uses key encapsulation. + /// + /// This method always includes the user keying material field, including when + /// is empty. Empty user keying material is distinct from + /// absent user keying material. To omit the field, use . + /// + public static CmsRecipient CreateForKeyEncapsulation( + X509Certificate2 certificate, + ReadOnlySpan userKeyingMaterial) => + new CmsRecipient(SubjectIdentifierType.IssuerAndSerialNumber, certificate, userKeyingMaterial); + + /// + /// Creates a recipient that uses key encapsulation. + /// + /// + /// One of the enumeration values that specifies how the recipient is identified. + /// + /// The recipient certificate. + /// The user keying material to include. + /// A recipient that uses key encapsulation. + /// + /// This method always includes the user keying material field, including when + /// is empty. Empty user keying material is distinct from + /// absent user keying material. To omit the field, use + /// . + /// + public static CmsRecipient CreateForKeyEncapsulation( + SubjectIdentifierType recipientIdentifierType, + X509Certificate2 certificate, + ReadOnlySpan userKeyingMaterial) => + new CmsRecipient(recipientIdentifierType, certificate, userKeyingMaterial); + + private CmsRecipient( + SubjectIdentifierType recipientIdentifierType, + X509Certificate2 certificate, + ReadOnlySpan userKeyingMaterial) + : this(recipientIdentifierType, certificate) + { + string keyAlgorithm = certificate.GetKeyAlgorithm(); + + if (!PkcsHelpers.IsKeyEncapsulationAlgorithm(keyAlgorithm)) + { + throw new CryptographicException(SR.Cryptography_Cms_UnknownAlgorithm, keyAlgorithm); + } + + KeyEncapsulationUserKeyingMaterial = userKeyingMaterial.ToArray(); + } +#endif + private static void ValidateRSACertificate(X509Certificate2 certificate) { switch (certificate.GetKeyAlgorithm()) diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/EnvelopedCms.Kem.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/EnvelopedCms.Kem.cs new file mode 100644 index 00000000000000..24195bae0e4589 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/EnvelopedCms.Kem.cs @@ -0,0 +1,41 @@ +// 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.Diagnostics.CodeAnalysis; + +using Internal.Cryptography; + +namespace System.Security.Cryptography.Pkcs +{ + public sealed partial class EnvelopedCms + { + /// + /// Decrypts the content using the specified recipient information and ML-KEM private key. + /// + /// The recipient information that identifies the encrypted key. + /// The private key to use for decapsulation. + public void Decrypt(KemRecipientInfo recipientInfo, MLKem privateKey) + { + ArgumentNullException.ThrowIfNull(recipientInfo); + ArgumentNullException.ThrowIfNull(privateKey); + + DecryptWithKey(recipientInfo, privateKey); + } + + /// + /// Decrypts the content using the specified recipient information and Composite ML-KEM private key. + /// + /// The recipient information that identifies the encrypted key. + /// The private key to use for decapsulation. + [Experimental(Experimentals.PostQuantumCryptographyDiagId, UrlFormat = Experimentals.SharedUrlFormat)] + public void Decrypt(KemRecipientInfo recipientInfo, CompositeMLKem privateKey) + { + ArgumentNullException.ThrowIfNull(recipientInfo); + ArgumentNullException.ThrowIfNull(privateKey); + + throw new PlatformNotSupportedException( + SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(CompositeMLKem))); + } + } +} diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/EnvelopedCms.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/EnvelopedCms.cs index 25f2278c331631..c15d367174fe22 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/EnvelopedCms.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/EnvelopedCms.cs @@ -9,7 +9,7 @@ namespace System.Security.Cryptography.Pkcs { - public sealed class EnvelopedCms + public sealed partial class EnvelopedCms { // // Constructors @@ -206,6 +206,19 @@ void Decrypt(RecipientInfo recipientInfo, AsymmetricAlgorithm? privateKey) { ArgumentNullException.ThrowIfNull(recipientInfo); + if (privateKey is not null and not RSA) + { + CheckStateForDecryption(); + throw new CryptographicException(SR.Cryptography_Cms_Ktri_RSARequired); + } + + DecryptWithKey( + recipientInfo, + privateKey is RSA rsa ? rsa : EnvelopedCmsKey.None.Instance); + } + + private void DecryptWithKey(RecipientInfo recipientInfo, EnvelopedCmsKey privateKey) + { CheckStateForDecryption(); X509Certificate2Collection extraStore = new X509Certificate2Collection(); @@ -248,7 +261,7 @@ private void DecryptContent(RecipientInfoCollection recipientInfos, X509Certific newContentInfo = _decryptorPal!.TryDecrypt( recipientInfo, cert, - null, + EnvelopedCmsKey.None.Instance, originatorCerts, extraStore, out exception); diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/KemRecipientInfo.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/KemRecipientInfo.cs new file mode 100644 index 00000000000000..1c039287f83b80 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/KemRecipientInfo.cs @@ -0,0 +1,76 @@ +// 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 Internal.Cryptography; + +namespace System.Security.Cryptography.Pkcs +{ + /// + /// Represents information about a key encapsulation recipient. + /// + public sealed class KemRecipientInfo : RecipientInfo + { + private AlgorithmIdentifier? _lazyKeyDerivationAlgorithm; + private AlgorithmIdentifier? _lazyKeyEncapsulationAlgorithm; + private AlgorithmIdentifier? _lazyKeyEncryptionAlgorithm; + private byte[]? _lazyEncryptedKey; + private SubjectIdentifier? _lazyRecipientIdentifier; + + internal KemRecipientInfo(KemRecipientInfoPal pal) + : base(RecipientInfoType.KeyEncapsulation, pal) + { + } + + /// + public override int Version => Pal.Version; + + /// + public override SubjectIdentifier RecipientIdentifier => + _lazyRecipientIdentifier ??= Pal.RecipientIdentifier; + + /// + public override AlgorithmIdentifier KeyEncryptionAlgorithm => + _lazyKeyEncryptionAlgorithm ??= Pal.KeyEncryptionAlgorithm; + + /// + public override byte[] EncryptedKey => _lazyEncryptedKey ??= Pal.EncryptedKey; + + /// + /// Gets the key encapsulation algorithm. + /// + /// The key encapsulation algorithm. + public AlgorithmIdentifier KeyEncapsulationAlgorithm => + _lazyKeyEncapsulationAlgorithm ??= Pal.KeyEncapsulationAlgorithm; + + /// + /// Gets the key encapsulation ciphertext. + /// + /// The key encapsulation ciphertext. + public ReadOnlyMemory KeyEncapsulationCiphertext => Pal.KeyEncapsulationCiphertext; + + /// + /// Gets the key derivation algorithm. + /// + /// The key derivation algorithm. + public AlgorithmIdentifier KeyDerivationAlgorithm => + _lazyKeyDerivationAlgorithm ??= Pal.KeyDerivationAlgorithm; + + /// + /// Gets the key-encryption key length, in bytes. + /// + /// The key-encryption key length, in bytes. + public int KeyEncryptionKeyLengthInBytes => Pal.KeyEncryptionKeyLengthInBytes; + + /// + /// Gets the optional user keying material. + /// + /// + /// The user keying material, or when the optional value is not present. + /// + public ReadOnlyMemory? UserKeyingMaterial => Pal.UserKeyingMaterial; + + private new KemRecipientInfoPal Pal => (KemRecipientInfoPal)base.Pal; + } +} diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/RecipientInfo.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/RecipientInfo.cs index dbafe0c2a4b22e..e625a4dc9c5662 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/RecipientInfo.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/RecipientInfo.cs @@ -23,6 +23,12 @@ internal RecipientInfo(RecipientInfoType type, RecipientInfoPal pal) Debug.Assert(pal is KeyAgreeRecipientInfoPal); break; +#if NET11_0_OR_GREATER + case RecipientInfoType.KeyEncapsulation: + Debug.Assert(pal is KemRecipientInfoPal); + break; +#endif + default: Debug.Fail($"Illegal recipientInfoType: {type}"); break; diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/RecipientInfoType.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/RecipientInfoType.cs index 0b4e419e546506..8a0624c479126f 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/RecipientInfoType.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/RecipientInfoType.cs @@ -11,5 +11,8 @@ public enum RecipientInfoType Unknown = 0, KeyTransport = 1, KeyAgreement = 2, +#if NET11_0_OR_GREATER + KeyEncapsulation = 3, +#endif } } diff --git a/src/libraries/System.Security.Cryptography.Pkcs/tests/EnvelopedCms/KemGeneralTests.cs b/src/libraries/System.Security.Cryptography.Pkcs/tests/EnvelopedCms/KemGeneralTests.cs new file mode 100644 index 00000000000000..bb8d40ab6177ee --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Pkcs/tests/EnvelopedCms/KemGeneralTests.cs @@ -0,0 +1,98 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +using TestOids = System.Security.Cryptography.Pkcs.Tests.Oids; +using X509IssuerSerial = System.Security.Cryptography.Xml.X509IssuerSerial; + +namespace System.Security.Cryptography.Pkcs.EnvelopedCmsTests.Tests +{ + [PlatformSpecific(~TestPlatforms.Windows)] + public static class KemGeneralTests + { + public static TheoryData MlKemDocuments { get; } = new TheoryData + { + { KemTestDocuments.MlKem512, TestOids.MLKem512, 768 }, + { KemTestDocuments.MlKem768, TestOids.MLKem768, 1088 }, + { KemTestDocuments.MlKem1024, TestOids.MLKem1024, 1568 }, + }; + + public static TheoryData UserKeyingMaterialDocuments { get; } = + new TheoryData + { + { KemTestDocuments.MlKem768, null }, + { KemTestDocuments.MlKem768EmptyUkm, [] }, + { KemTestDocuments.MlKem768NonEmptyUkm, [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] }, + }; + + [Fact] + public static void DecodeMlKem768() + { + KemRecipientInfo recipientInfo = Decode(KemTestDocuments.MlKem768); + Assert.Equal(RecipientInfoType.KeyEncapsulation, recipientInfo.Type); + Assert.Equal(0, recipientInfo.Version); + Assert.Equal(SubjectIdentifierType.IssuerAndSerialNumber, recipientInfo.RecipientIdentifier.Type); + X509IssuerSerial issuerSerial = Assert.IsType(recipientInfo.RecipientIdentifier.Value); + Assert.Equal("159FFE6F22FD5CC42C524DF6FD5E28D0DE38F34F", issuerSerial.SerialNumber); + Assert.Equal(TestOids.MLKem768, recipientInfo.KeyEncapsulationAlgorithm.Oid.Value); + Assert.Empty(recipientInfo.KeyEncapsulationAlgorithm.Parameters); + Assert.Equal(1088, recipientInfo.KeyEncapsulationCiphertext.Length); + Assert.Equal(TestOids.HkdfSha384, recipientInfo.KeyDerivationAlgorithm.Oid.Value); + Assert.Empty(recipientInfo.KeyDerivationAlgorithm.Parameters); + Assert.Equal(32, recipientInfo.KeyEncryptionKeyLengthInBytes); + Assert.Null(recipientInfo.UserKeyingMaterial); + Assert.Equal(TestOids.Aes256Wrap, recipientInfo.KeyEncryptionAlgorithm.Oid.Value); + Assert.Empty(recipientInfo.KeyEncryptionAlgorithm.Parameters); + Assert.Equal(40, recipientInfo.EncryptedKey.Length); + } + + [Theory] + [MemberData(nameof(MlKemDocuments))] + public static void DecodeMlKemParameterSet( + byte[] encodedMessage, + string expectedAlgorithm, + int expectedCiphertextLength) + { + KemRecipientInfo recipientInfo = Decode(encodedMessage); + + Assert.Equal(expectedAlgorithm, recipientInfo.KeyEncapsulationAlgorithm.Oid.Value); + Assert.Empty(recipientInfo.KeyEncapsulationAlgorithm.Parameters); + Assert.Equal(expectedCiphertextLength, recipientInfo.KeyEncapsulationCiphertext.Length); + } + + [Theory] + [MemberData(nameof(UserKeyingMaterialDocuments))] + public static void DecodeUserKeyingMaterial(byte[] encodedMessage, byte[]? expectedUserKeyingMaterial) + { + KemRecipientInfo recipientInfo = Decode(encodedMessage); + ReadOnlyMemory? actualUserKeyingMaterial = recipientInfo.UserKeyingMaterial; + + if (expectedUserKeyingMaterial is null) + { + Assert.Null(actualUserKeyingMaterial); + } + else + { + Assert.True(actualUserKeyingMaterial.HasValue); + AssertExtensions.SequenceEqual(expectedUserKeyingMaterial, actualUserKeyingMaterial.Value.Span); + } + } + + [Fact] + public static void DecodeOtherRecipientInfoWithUnknownOid() + { + EnvelopedCms cms = new EnvelopedCms(); + + Assert.Throws( + () => cms.Decode(KemTestDocuments.UnsupportedOtherRecipientInfo)); + } + + private static KemRecipientInfo Decode(byte[] encodedMessage) + { + EnvelopedCms cms = new EnvelopedCms(); + cms.Decode(encodedMessage); + return Assert.IsType(Assert.Single(cms.RecipientInfos)); + } + } +} diff --git a/src/libraries/System.Security.Cryptography.Pkcs/tests/EnvelopedCms/KemNotSupportedTests.cs b/src/libraries/System.Security.Cryptography.Pkcs/tests/EnvelopedCms/KemNotSupportedTests.cs new file mode 100644 index 00000000000000..b6b8b37336d60c --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Pkcs/tests/EnvelopedCms/KemNotSupportedTests.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Security.Cryptography.Tests; +using System.Security.Cryptography.X509Certificates; + +using Xunit; + +namespace System.Security.Cryptography.Pkcs.EnvelopedCmsTests.Tests +{ + [PlatformSpecific(~TestPlatforms.Windows)] + public static class KemNotSupportedTests + { + public static bool IsMLKemNotSupported => !MLKem.IsSupported; + + [ConditionalTheory(typeof(KemNotSupportedTests), nameof(IsMLKemNotSupported))] + [InlineData(false)] + [InlineData(true)] + public static void Encrypt_PlatformNotSupported(bool useFactory) + { + using (X509Certificate2 certificate = X509Certificate2.CreateFromPem( + MLKemTestData.IetfMlKem768CertificatePem)) + { + CmsRecipient recipient = useFactory ? + CmsRecipient.CreateForKeyEncapsulation(certificate, []) : + new CmsRecipient(certificate); + EnvelopedCms cms = new EnvelopedCms(new ContentInfo("hello world!"u8.ToArray())); + + Assert.Throws(() => cms.Encrypt(recipient)); + } + } + + } +} diff --git a/src/libraries/System.Security.Cryptography.Pkcs/tests/EnvelopedCms/KemTestDocuments.cs b/src/libraries/System.Security.Cryptography.Pkcs/tests/EnvelopedCms/KemTestDocuments.cs new file mode 100644 index 00000000000000..08ef3b26fe939d --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Pkcs/tests/EnvelopedCms/KemTestDocuments.cs @@ -0,0 +1,306 @@ +// 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.Formats.Asn1; + +using TestOids = System.Security.Cryptography.Pkcs.Tests.Oids; + +namespace System.Security.Cryptography.Pkcs.EnvelopedCmsTests.Tests +{ + internal static class KemTestDocuments + { + // ML-KEM-768, AES-256-KW, SHA-384 + internal static readonly byte[] MlKem768 = Convert.FromBase64String( + """ + MIIFRgYJKoZIhvcNAQcDoIIFNzCCBTMCAQMxggTupIIE6gYLKoZIhvcNAQkQDQMwggTZAgEAMDowIjENMAsGA1UEChMESUVURjER + MA8GA1UEAxMITEFNUFMgV0cCFBWf/m8i/VzELFJN9v1eKNDeOPNPMAsGCWCGSAFlAwQEAgSCBED8JbFiwSeYXPZ5kqKNInfiohfM + BjyULm1+hlIjTN9qK4dgvIpQ+jAdrHdJ1hOmItZ4LvGF6/P+oxq3+FSZSabfwBVuwGMS/YiUyxPWBO0UmqWThrs42i+IGU0hr5yg + Hkb6AGpPm/IdDi48TtM1PNBPl1QBBTaM2TAxb3Nn21a0EtB8bCmLAgnAjFC72YB4kp++HwTayJ6nuuim94VmuPuPOApEwIVhDECI + pToF5/M/a7x30qtgAXC1zcHsCidkzzO+ekqt8lWPzgUvtNmDJGu2kUZajZOqGYvBqT8mOyQpRBoISUFh+M5YEE2DMWyrRT4pjvvU + A5lwrHeSDdPiZAZ2QaOWE124nRf+gJG8DG2zJqqmaP4TscPb+bs+v+08Qf0aNjZwpLG6DqsUy066WMmOoygf/XlyxQFXJ6HxAsg0 + coD/tvE7rR9L0TiEkPjksPHxzocfxQK2tD22liMvemR2gO0qVjpIZAlIzq9iJ9J+sq5lWoaRHWsn+bLzpBiTulLE741zHJy4nCb8 + BAlCg+JOrh50ZQpjpHcoJ4X4NdLuQDOCp2utGnxxQI43cq+mLFYrLH9vetCktRtyKI5iDsGJMVkeIzUmh+IHunVcE1nTmiiTIkeb + bVGkGLyv+XI3x6QHRwQojjMer/bhoT8r129GYNMOAQzQm3FwpNLLBzrIj70Nm2cra3ntL/aMMPRI3CGlhMc405r3r1tbsfVcgqaN + sWJsj2PcUDLFKnYw1cyEvUZ1yF4MRsHgCaANCw9zYUQjxVnfeeXXc50hqOzAD/ZauzVUAdBRRpTB1EcDnI4RRFlukhx7fXvo2WUW + y0vEdj1fMw+bJhFTRQI54Zd0OS9tE8fm5K4tOaQTtCJeWMI4cYuVy+8+ZFrw1YGztRgq/umf7wBO3zGdAYqEhq3bg8ln8jipMD4w + TmQcmkGL1R1QxeJSXQCNZePKwIMaGGpiKTIZrEHASkf9XcbTtcL09FLKjHzo2FW56NPJnWg1cTdGCHT9VBcT06Vd6zmy3qjGCS7f + iSZs17lq4Uyc/lPYcx+0E1CJ4zMcAg9+jECV5cSUZ+HeTKpmbPOgL3rn9oWOGi0vXcJ8dtIfMRInueHqOOzDb9FbpMFDZ1AZMbGu + ChxxZtjVM9iw08mc/WIF2qa9tVIkEKX8ZQDuIz8e+C1HTawEFYZ1XBBqMTy0BfYpZhT1W/UJe5rgUDNJXXBv5LdPWhZPXFeomCC0 + 0O0bqioUVHqkzB7m0VtNHknEar2ks5pzigvo1Awd9dsgde1U4ZpRCIMAR7RD48rc8vTNSNF5LfvDSjJ7VZJREphog39ZmUhUuMl3 + ocTOXdNBwRx1xp1gY0OOgBXku1DRFNkmJm+OX4gO9z3vee0A4lHPPzt+wDG8F8msLs+FGPDWnUP1TWVyHLWWIT1VZ/ArvEP5eM7j + 0XH9CYIl/4Z1ncX60JtuO+dYoTANBgsqhkiG9w0BCRADHQIBIDALBglghkgBZQMEAS0EKBN/DHlGK/WvNzQ4kolGSwZMU/mRipHk + scRAfKSA/zSY3tJMuCsyUE8wPAYJKoZIhvcNAQcBMB0GCWCGSAFlAwQBKgQQRxfJmVCr9TJ5HqocaVzdSoAQdyh9tjmrLR1M2m/Q + AzYr7w== + """); + + // ML-KEM-768, AES-256-KW, SHA-384, empty UKM + internal static readonly byte[] MlKem768EmptyUkm = Convert.FromBase64String( + """ + MIIFSgYJKoZIhvcNAQcDoIIFOzCCBTcCAQMxggTypIIE7gYLKoZIhvcNAQkQDQMwggTdAgEAMDowIjENMAsGA1UEChMESUVURjERMA8GA1UEAxMITEFN + UFMgV0cCFBWf/m8i/VzELFJN9v1eKNDeOPNPMAsGCWCGSAFlAwQEAgSCBEDS7idu7u9Cc8d4VDHfBzjfTxyvlnNkFVQ5Li+phZ6AJalqurkgFq1m90jD + TBnQM8BKigOhuIflXsD8ccd5f1cd8S5vEZloqU+UanIOgApiKILDNvr9d4+aYFQ+ZSsJiHDyA+3MZlyN7f34tL1zQb5w6iBKqMA14Z6fDJsGhRz2VCNS + fk+yjhBSUnM2n4yADn6mM7L9hEjXNBNA+G2bgloq4lhsTolDNyFEFK1t2AFxf3Chjbz6VIkfA6bRfP/s2RW0HqbEvPbA89i5qVi+9m8wwxql5U8B26gb + MGVF+ZZFBPE0T73X3gEeidvXxXts4KtxcrlBqGjUxYpIfZfVvJ8tbtp/Ye/wfSuJMUwqWowuqrm9EjTo/r1l1q1ITBYZOnRHviK6/y8qHLjvuuxUH2vC + p2l4961l+DVxZCnWgs2mNOUQKoUzjQkaJEpRaUcZL8EpIQpx8o1BowPDUZp/CEsR8efFG1Ystcr3UMJghRZWNDaEZ8YLM8cGQxi4nMCtsZK+4QiAdSTH + to2NR/yIvViZg9EYiyAsE8Oog+MedPcsv+LZODxJ7G/GKrF2Xm7q1LIeZ3VTJ/Qv+PbwIauEUQQ0nlmVyt276jlSHoPrVWB0yQvY8pT5xuFSN3CgWzco + 2KjdWNNUJHxoZ2Ay9KVeF8leFYQpVadWQR3UrN9i/i2NnhdRjN67UDEJioSEz6tYSByF+fl686dLN+a6xPHjMfrlMPhZO2SSrwRQsfzwbdpgU1ilorqs + g5F42yzr/lyrr+fp4nE8GNJLpHvxQ05LGKgZWaCUBhB6R6jSVxl9P4gIhKABXHfXuzBaq2eL4tcaC1jlPWQOJj9cGNRfYKaPA+kKG3Ju9cycarnkSr+W + +6SCEXmjDgeJEypIbyPoP54ON7A7GJsvn7QGDJkjx5Zk9TOVY3i0ZuRqLIOXxoX+/CtI3i8GuqF7sQAEQaCUrdf9s70wROge/0OyymGOY0IbNp/I2PNO + cwYhM5bgMt5JmS/d6IV6qlzWH4r+JfRYn5QazFRAPf7n12OmlJLYj8mNoTYYYLFr04s0PpYJRRStGAKDPhPZiHDEPTrAZtu83oQrbmWWqzlIkruMpbxR + D0ntbq4jUMuDQ8E4qhRxxleRJNWH4mgXYOse6sAGRN4Xe0w0Cm4/QBxrl0U2qEq8Q6w2JWlvf+D6KkrEvqZ7jkwUEMPi3gHDhVUNGpfUzgdKSMx15hH1 + u8p2GoXnCeZUpbepJf1LWy2Tg2U4ORpbBKluv50fQ9LwuepQOpgg4oC5E17DBhKlCX+6X09iVH48mB6578wpR4h7wA6oq71GjUzC8Egfy+jua0mWX26k + mnEcZC2xktjvF0D5Ax5mMrYEJimC+7DCxwCgHus7kmOu47Dictux9phBIOA2emheWIlpFSBmWHV6Mg0ruo3eEkC/68ibPbQRjvuiQ3VYAwCeNnOXuxUz + lzANBgsqhkiG9w0BCRADHQIBIKACBAAwCwYJYIZIAWUDBAEtBCiZg6uggthcw9Fvy/cw74bLme+1UOA/tg6vw8ZMKbYuLzg7816ZJkNlMDwGCSqGSIb3 + DQEHATAdBglghkgBZQMEASoEEObo/sI+mCc6P080PBTxPsaAEF5MDg3WVOQxqpH/zVFnLSo= + """); + + // ML-KEM-768, AES-256-KW, SHA-384, UKM 01 02 03 04 05 06 07 08 + internal static readonly byte[] MlKem768NonEmptyUkm = Convert.FromBase64String( + """ + MIIFUgYJKoZIhvcNAQcDoIIFQzCCBT8CAQMxggT6pIIE9gYLKoZIhvcNAQkQDQMwggTlAgEAMDowIjENMAsGA1UEChMESUVURjERMA8GA1UEAxMITEFN + UFMgV0cCFBWf/m8i/VzELFJN9v1eKNDeOPNPMAsGCWCGSAFlAwQEAgSCBEDS7idu7u9Cc8d4VDHfBzjfTxyvlnNkFVQ5Li+phZ6AJalqurkgFq1m90jD + TBnQM8BKigOhuIflXsD8ccd5f1cd8S5vEZloqU+UanIOgApiKILDNvr9d4+aYFQ+ZSsJiHDyA+3MZlyN7f34tL1zQb5w6iBKqMA14Z6fDJsGhRz2VCNS + fk+yjhBSUnM2n4yADn6mM7L9hEjXNBNA+G2bgloq4lhsTolDNyFEFK1t2AFxf3Chjbz6VIkfA6bRfP/s2RW0HqbEvPbA89i5qVi+9m8wwxql5U8B26gb + MGVF+ZZFBPE0T73X3gEeidvXxXts4KtxcrlBqGjUxYpIfZfVvJ8tbtp/Ye/wfSuJMUwqWowuqrm9EjTo/r1l1q1ITBYZOnRHviK6/y8qHLjvuuxUH2vC + p2l4961l+DVxZCnWgs2mNOUQKoUzjQkaJEpRaUcZL8EpIQpx8o1BowPDUZp/CEsR8efFG1Ystcr3UMJghRZWNDaEZ8YLM8cGQxi4nMCtsZK+4QiAdSTH + to2NR/yIvViZg9EYiyAsE8Oog+MedPcsv+LZODxJ7G/GKrF2Xm7q1LIeZ3VTJ/Qv+PbwIauEUQQ0nlmVyt276jlSHoPrVWB0yQvY8pT5xuFSN3CgWzco + 2KjdWNNUJHxoZ2Ay9KVeF8leFYQpVadWQR3UrN9i/i2NnhdRjN67UDEJioSEz6tYSByF+fl686dLN+a6xPHjMfrlMPhZO2SSrwRQsfzwbdpgU1ilorqs + g5F42yzr/lyrr+fp4nE8GNJLpHvxQ05LGKgZWaCUBhB6R6jSVxl9P4gIhKABXHfXuzBaq2eL4tcaC1jlPWQOJj9cGNRfYKaPA+kKG3Ju9cycarnkSr+W + +6SCEXmjDgeJEypIbyPoP54ON7A7GJsvn7QGDJkjx5Zk9TOVY3i0ZuRqLIOXxoX+/CtI3i8GuqF7sQAEQaCUrdf9s70wROge/0OyymGOY0IbNp/I2PNO + cwYhM5bgMt5JmS/d6IV6qlzWH4r+JfRYn5QazFRAPf7n12OmlJLYj8mNoTYYYLFr04s0PpYJRRStGAKDPhPZiHDEPTrAZtu83oQrbmWWqzlIkruMpbxR + D0ntbq4jUMuDQ8E4qhRxxleRJNWH4mgXYOse6sAGRN4Xe0w0Cm4/QBxrl0U2qEq8Q6w2JWlvf+D6KkrEvqZ7jkwUEMPi3gHDhVUNGpfUzgdKSMx15hH1 + u8p2GoXnCeZUpbepJf1LWy2Tg2U4ORpbBKluv50fQ9LwuepQOpgg4oC5E17DBhKlCX+6X09iVH48mB6578wpR4h7wA6oq71GjUzC8Egfy+jua0mWX26k + mnEcZC2xktjvF0D5Ax5mMrYEJimC+7DCxwCgHus7kmOu47Dictux9phBIOA2emheWIlpFSBmWHV6Mg0ruo3eEkC/68ibPbQRjvuiQ3VYAwCeNnOXuxUz + lzANBgsqhkiG9w0BCRADHQIBIKAKBAgBAgMEBQYHCDALBglghkgBZQMEAS0EKCnEQSlS4Xl0Gbn5Sm50Bqxhqq5+WtrJFfPulTAG/JzgXVy2lSk9WXww + PAYJKoZIhvcNAQcBMB0GCWCGSAFlAwQBKgQQ5uj+wj6YJzo/TzQ8FPE+xoAQXkwODdZU5DGqkf/NUWctKg== + """); + + // ML-KEM-768, AES-256-KW, SHA-3-384 + internal static readonly byte[] MlKem768HkdfSha3_384 = Convert.FromBase64String( + """ + MIIFRgYJKoZIhvcNAQcDoIIFNzCCBTMCAQMxggTupIIE6gYLKoZIhvcNAQkQDQMwggTZAgEAMDowIjENMAsGA1UEChMESUVURjER + MA8GA1UEAxMITEFNUFMgV0cCFBWf/m8i/VzELFJN9v1eKNDeOPNPMAsGCWCGSAFlAwQEAgSCBEBUusDZ1LK5aav1+chVh5q4AZWM + 9unnLwK5qjwK/goc6O3q5EBGAUQcxTwixLFOWgCrqRo01fZ8TGpBdBN9EfttKQQ7JwUgVVGlUgMLmcGc+CO0Wxn1wyJY4moiRQk6 + xwy3ORMSj9TXluZNM7FRZOF70xx14fmvr9o7SlwOlBUfCxmwJR/+cLmz33mnopHf5mEeNGJ2YEzPitZQ2ns1vOFqmXOWEn+jkoWp + W6mi1UxizCfElF//Vh6Y07mSU8LZVmvEq9oi/29n0P7OSAh/NLbKqBT/ZqeW6TnYd8oifNzj6du+fkoK0o2CmLY5DgdK3A0C16b8 + oyJi9rJ4ZOpW1mQ9VzNN5lOyJv68tR3piOTZhJxwE0iYaYUqXhDk0N5Kyu8xe9gTx5+XTrEMIKDv/CcsBURPEdNGWUN3N1X8ziaq + WtUy8iHJUnvPH7faAKOwbqZb/VTVEMaGE1qzRNWaVpCOjnG4naUj/1T5vbv3dZzBqhiYWRFNYZf0uWw3ZaDDZ8NKO+uHH5ax1HcN + B2XI+SysNms/n/15C/rnfz/ebviGJwLRIHcOsYg3xL/3D+9QrGMvpTVmmEzEUZv+skmBdqrzkijK+w08dxSA0jehT69CGr94xIBh + lTmEe+IU97oTrP0mlzRgRGoYtoqjc/Q96+W9u+RSSUzImwqUS/sUUZmJRt4QYr/WTypwpGvmXZLVf1va4E81Put+EZ+8RZQ/lqAV + tV0Bd4nscanbEJAQ2zh2QYylk7BOuEunndn5ey1KClxanQqBNqT6Hw8oK+4EcDDbfes8a86oqGAa8sUXT3hoY929FwJNvDl2gNxt + 1Xa4YU3ZuQy5ChvK0HZwZMeN09W5mj/NMrqAXyW6qTFASilaUTkZJ/TL31xqyOF5bo0Og8UHgkC50SUWZf+H5VsnoFIOTYwLqTnD + wlVY37+LPhLbwVmFjD2q9Mcv4Iu0767Dl0FrbG4Tdrpqm7YjnCmxZGTyX/MjyMbiJGN8nJCyK40uUac40+/GcTaQZKDGjtOh66Gl + tq1dFZuKxEs+f3Md5Weh5tpr602guHvYVijfb/gVBBPR3P/F74PAgDOCq3vbONA2V4tVBnzqQK4DxMscAQrD42q/GXmjh1aI2ymF + S4IMmdkHotRSlU+R11NsH+U0kZoN1CynQR8ZMQRC4KBwL5AOnvzzxnlosRkUo8ZguX4Kc3sIVD/9eHp1iauuF7mjVmzdqjRnIzkG + 84SglkwafxrFDiOiDCVrLN5gyTDfM6DL5cZeRGR66lJdTOue8tTXGWzcrpw1HGfclcnc2d+FWCnnCEhIZVz8ll7uPP1gE+zWUP+f + F+rd0nfIjWjIa0FRnmKWVNua4uSH7j0SV4wbA6OSwNjE7SuVnqDMZdyHKAryi8tI/kZhfESD9DBDToLWJnmmCahr2JVSJ2GbBXZq + NqX2SK7bGzDk9zbK1pD4GdPbsjANBgsqhkiG9w0BCRADHAIBIDALBglghkgBZQMEAS0EKIV1CiYS1ilPY9ViYl12hbTik0f840dz + 4FUVDIK6qXJMevQWWl+CP6QwPAYJKoZIhvcNAQcBMB0GCWCGSAFlAwQBKgQQ0NoOCI+HY7B2CLV5cOOcVIAQNyxWNgIKUvs1xKFS + lmLmag== + """); + + // ML-KEM-512, AES-256-KW, SHA-384 + internal static readonly byte[] MlKem512 = Convert.FromBase64String( + """ + MIIEBgYJKoZIhvcNAQcDoIID9zCCA/MCAQMxggOupIIDqgYLKoZIhvcNAQkQDQMwggOZAgEAMDowIjENMAsGA1UEChMESUVURjERMA8GA1UEAxMITEFN + UFMgV0cCFBWf/m8i/VzELFJN9v1eKNDeOPNPMAsGCWCGSAFlAwQEAQSCAwAgJsff2m/wlp5YsRBMevkZRPaEmT7sP/z6EniNEBVBgCPlIpPFxJllmTBR + s3sbX6BP/jCCoY2FPWNeTxFr7F+geMlyWhZsTCuxV3ynLISQAbxhZzKWOzNohbtnKoVfdIK+J7TLSYoUWaGFKkPucDwYM7eBTEuQN7HbG0DMC94qiL9e + z3hsqagwy+ISFEs0PaP77r2WDejSps3zAJjKgtxxzJpkkcShoNmVYZMy4sOljW/fHl26RFo1O3A+dt4H0ePI7ixFzew/As6hnKyM0exL5DEz170QNp2D + vvHefqp77db2A/V4JmEAFvBIgtjKpO8KLriPO66pDcROjht+LHElcRAbbsn8YDZuIRBxqMgIgtsPntFODuVtXVgCRNbqSTBNiJ3tWoAXTdKQXBHwZQhO + tGz11IG3e5Wo3jiicTv/29AdAmnyB+GnitO/vfF2TmHNfGpzpv+XUNTd2PzfQ2kWmHj+w/b3huW2KtwFkPQkkMyhsgbss/BNkVt/4vgAZi5TgaZUwIxI + 1OzAE0kJzzkj+xvRdyhehTtin0La8nQsZ6AGqDhRTMCSV1LWHSxeL/QvcJ9U+azZWCkz+tndORHS6+RVTa89hKYxsChChx0LSHYhUv0YHsfLD745Isq+ + cDPRhETTtGkxyEPRVf0+0r9EghtCzenSNIG7nQ2FLPWV0ZaBbnrAq+yuIw6yLkl8mZ8Mg/bX7Rp1ii30mQT6Ak1mNdKdLNMUiBcDeao4kikTVhld+eMs + oq45hsHhilyMHPYGIOLa3FeUj0y3UIBmgLyEivbUNmkpNe2F0WhjvW06kjx+7PyHvUcoDDRaEwdOgkcmYCmcSgNfEHnFcELhBYE/Ih82nATClAaGbu6I + 0Rb2bbiiXhuD/lu6E0M2IdPKr8+cUSb5qGQKXZ2UZhSrWPkk3nzKYXjxjz5g9IIoOv6+jjQXPRaZzobnCIbolRQg+m2BxF9p6mLYKhvjaRh49665FITd + hOBG/U2HywWyzHKb7HbJ7qfAExANY5EAbp+ZeiUwDQYLKoZIhvcNAQkQAx0CASAwCwYJYIZIAWUDBAEtBChIJ3h/kLhBbEA35r1klvCT+BeKk3qsqAGt + WKGrgwOogcNQiLBhjHPgMDwGCSqGSIb3DQEHATAdBglghkgBZQMEASoEEMvgf+A0cmIqZqQB0Zh11eCAEEJd3TxgXLtl4mmIGuBQ8ZY= + """); + + // ML-KEM-1024, AES-256-KW, SHA-384 + internal static readonly byte[] MlKem1024 = Convert.FromBase64String( + """ + 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 + Lzi28I40hA== + """); + + // ML-KEM-768, AES-128-KW, SHA-384 + internal static readonly byte[] MlKem768Aes128Wrap = Convert.FromBase64String( + """ + MIIFRgYJKoZIhvcNAQcDoIIFNzCCBTMCAQMxggTupIIE6gYLKoZIhvcNAQkQDQMwggTZAgEAMDowIjENMAsGA1UEChMESUVURjERMA8GA1UEAxMITEFN + UFMgV0cCFBWf/m8i/VzELFJN9v1eKNDeOPNPMAsGCWCGSAFlAwQEAgSCBECzf4kq3fbJbx2BcfcyHlkz4qqU79m3WRrSicwtPttHPcjfDq9k6OcB6HiG + MjJnCuDLHvdDYO9tARwUihpMcEqjAuH07Gg8NHA9EedJqK7m99QzZTZ1TCK5q4NMW7+/PM6thDEhMLMoVp4UY5BvnmeGGdrGdYWOuubXOhWXOrZh6Tnt + RSqXoFTy1rXmZlTsCXMcDwHWT1/lJq/HoM1CeGW7Y3xukHCjdPJDryfT0KYV2LNRoo0RXjs0JUsUNETkA9hqDqWeMwtb9d7lv8+z+4cnCYn8ZrGAF6xE + lVYgd7cjSrt5cv1xQp6zB2Dcvreh3lng6EVPmsnOFJ0Yt99grCVhBHVG8hLjreEYf+NeAYWiHTlUsuiZdkNk0v3FgsDMqHGTm1EpNX6UnsMoOjPt9W7R + dJO5wfAYWgDbdm3OYiKewCHIrb2fnYZnidEg9eXKoXQB3Nc//U3UjEOPEpAkgXt9TFhC/etHJBPmV12PgGxgyvVWHewP82pIGS7axche78cVaQiozNAu + 08FJPoS99hrNvmScmeJKKLF5Vi0VNXABOj9QX2kXsbbWUoeMCrsGlfbIU5bWNKgIb+Cy65Tek7EInAIYNgpyVPQWoOuHpYhRle8DHkAIK7ZdGWMxSl5c + TZQurF3C45HGQXG8oeYznxeFp+ZYTJYI/hELB7JbhUkTxQajtiZSN/U4e0ZTOHVDYHdUWiRfWg2TAhVoNxLRdHHU3w7MW8oW7480XY8yWXBMg3O76ouQ + gNcjalboUr3kAMhIrVy6WBvQDpukEBZfuEznCh/+lLKErJXcAWub6iDormwo8eVNXQ3/PLuzE3MnxhPkvS4qsd5QZoalGwcg48vhFq8OoSJwDOVw5D+7 + YIAtLeX63dUI3o/QhkiA+ktBJQW0eAY0784kC6S5WGFddYMpURCPwCqDCFs1gp4VwS4ESvILjTwLZWP3Vw+ywOMhE4sCmEOsFxdWVsfY4lqLzQ/Yb2cT + aIBuvFlVBnKJWrKnd5d+cyiBvSmv58ttO/Ju5IeTAi8Mu2yB1l8rj5xIEKD+Ye1bDjXuQHL4DjtyMvl5IT/OT96wkrdIjAnH2tY1nGunxTvfxVM5Mhs0 + QslM2qPUGN1tI3vBJyMi+JTkmGKEo1SHFhAX/ZA/u9iirVZojgUFeHKSDbeGhwsoC4CDcl4KbwHooGrqMFHrGTaU/xkyrbR8SXJYVY8kkDxgYMLsCr9H + vUVK/ONMIxGizl9actSMGIANmmO96jrEf6tfR1teISrQ5CXw+/I7NonyCxrf8pQhLxTSBk4aRRXTNONkbWLP/WAzpn7KU7PTyEdPY+tBYYOs9sFo24jg + dwTVCPytBUrJWgV6s8p25u/NVJH9Cnsm+FvucAOs5i6hjzc3Htq3SPQsE0VZk+ejHcyYY2cFkMOqKgQQW/nRPnqnG1yywf7EU4b2VpaGxJWfJBrjvq2p + +zANBgsqhkiG9w0BCRADHQIBEDALBglghkgBZQMEAQUEKMo6XZ/lIQ45RbusiBlRtH1FxQsf16cwCgt9snA5rlJ+Dd9ywH0iZmgwPAYJKoZIhvcNAQcB + MB0GCWCGSAFlAwQBKgQQQGISYWm5CJ8g3mxcZIcRa4AQOXpyUHWC+BW8w5wJObZF4Q== + """); + + // ML-KEM-768, AES-192-KW, SHA-384 + internal static readonly byte[] MlKem768Aes192Wrap = Convert.FromBase64String( + """ + MIIFRgYJKoZIhvcNAQcDoIIFNzCCBTMCAQMxggTupIIE6gYLKoZIhvcNAQkQDQMwggTZAgEAMDowIjENMAsGA1UEChMESUVURjERMA8GA1UEAxMITEFN + UFMgV0cCFBWf/m8i/VzELFJN9v1eKNDeOPNPMAsGCWCGSAFlAwQEAgSCBECVAiQtk1GdCLmQvfhGg6ZvqlqN0uFcRuNo02cD+0fa01WKDegVoZm4Wuco + n+vjBTPcxNA8zFtGucrRJa+E+Q9mnk6Wyp6Oz4+yShnWqYAbuaTGKnkDeM/9BOWkz/wAe3yEu0TYUAzM2bbf0xKP05+31BCOxTLJwt2eeZAMfugod371 + pY91NvYfRATbMk/C/OEffjV9zNDMFtP/nAkYYN9bVpAlvEzlXXDp1VEOwx+5Fz6++XACp9Dyb6l86ei4nvgAunUK7uL6GifTl63l6OQi9dHS1NS5EMcR + JmjkxulwN9ddx/7ijOyFvTl0l7j7r2aAN4FYCleASRhDvTy2e70GaEtywf/fMK760qbrVsm+A3KZstJdzqK5jK0yuwXVoMYDQ0b2rls73LXEb/oU+uQ/ + FaJFC5tv8rdFHCzWf+mLSyBohzbNUjfRXwXNqFo7LTn9jiK0BbmLAktestMe1cdPPaj4yy/CBKANzuG7F0CQAxV4l+Fa1yN9Zzb2jwYE3JP6sFG5LJxi + Xvu1bbywUyye/0G3AW4XgTQVwliZPPR1xL/9bqO2k6Jo3ujhKq3wZv/HyBRiCOE3kImdDRaYLLU2rFQ8KwfSqATDc1bj74KrhoKBJkUieGDoC0JUuw7u + QYqgrQIDZ1Ws9IQadd/GFbWX71bFbi2iXpV47is+65N66TUh6y3nM38OUqgaK1cYsMWNXQ7+c8Gq4PVVqpqJr5foJOXosYRnj0nhOcgN2RBl7ZSHrUD0 + Fu3se1cJwwD9rA9IOGHnttSBhL9Hw/Oqtt2pYzFlh/nC6rPruPdxnsQLyWuSCwHiABefBSlM60ap95w1ZiyL2ZQlU7NvWupAMrQ+SwskzSJWEElhbcJj + foMgXOLI3Nvpbev8LSxpGBuAVqRoJzFr6NQ9PMEH4OStG0KZZRtW+UGE6bqoYMwjcoLhK9KQn3ITPiENwcstYNdE6zQKOYJpCmA4Sl1ipWpoiqtD5Va6 + ODmhEMtELD/e8Jx5taYFayVGhsf+XXNG2ukMfwtfMZBnAnaNjmXq7oaYIDmh3dA8zytgn9nd+mHRBs7CAVZfTWE/R4rhCRl2pRrCnZ/VYFQaZzvBCmRB + mIDbHb7XG8ATkSaHJVHC02Xb2UWEif6hdTGmuPvOSZFS9bnzhrjetWghdDBymkl6JzWngq/D53KpFFzGJxid/IKhyQO0C0uveC7mG/p4kL5twyojduN+ + o8gF/k6+13JCZHGb6GnQEvp7b0LaorxtrPMh93y984DGOXfudCKbgYacGHMUnI/PP9dGZFjqe76QsYiNjFzhFxpF/NUaX7Va/JA06BS/eipmCrkQ2r1+ + B70raq5uqQSGjpCONtQFBDaMw64K4TvGUFOZEN9r6MDcokbLxpS6xzZQTL4YbNDzLpPUnbmujjseRIyTqQ5WafaB3wJ2rE+ym5qO2n+wZXnHPnwPj6kj + 9DANBgsqhkiG9w0BCRADHQIBGDALBglghkgBZQMEARkEKDcTIG8TBt+MOI70Bv9GefYTrN9sU/OS4v0wsA7mmxKKbQ8F2HwlqB8wPAYJKoZIhvcNAQcB + MB0GCWCGSAFlAwQBKgQQeJZ58jieHWaTQSAKQzeOQIAQwEDESkVcdh3OXiJN1/Sp1w== + """); + + // ML-KEM-768, AES-256-KW, SHA-256 + internal static readonly byte[] MlKem768HkdfSha256 = Convert.FromBase64String( + """ + MIIFRgYJKoZIhvcNAQcDoIIFNzCCBTMCAQMxggTupIIE6gYLKoZIhvcNAQkQDQMwggTZAgEAMDowIjENMAsGA1UEChMESUVURjERMA8GA1UEAxMITEFN + UFMgV0cCFBWf/m8i/VzELFJN9v1eKNDeOPNPMAsGCWCGSAFlAwQEAgSCBECNRPA8lfLh0sGzIIGKprEQEhZbGsJobpZVOVqK5lr+whOQTgxQZCX7e2Cw + D/dsq5FhgUVqXPCUwaNJybiIt6IR7tFiDj49wZHJGQOfFYps8kuq74Zw31/tKWBZ/+pGPzNeRCOEVecKo+GXFxXcaKao1UVEK9OpofF54/SfgVf3B5W0 + 000va59s3kvsTwcCh5uls+m4eqnwVBEYskmYxYdASmdJ1eU5pWnYI6bto5XVUKxk16NZo0tkx9ltg4P8biXhNRXyTvz+ApInd4TeCVvvui6MNpUiJBfr + TrC2vxlPnCCYxtVQLpz100v94JwScxr5Pyws6vV4UHQjM9dB1v5WWmBObJ3IKksDn/lJYCFqu5Ipxk2Q43aQDGai5aSsjQ9ilysry7+NWciP3vA5Vb6A + v7HAL6Szekat+62wPYKkDhVa2cl4jRCEwvoIikzIgG7NZ29VTBLzDgq8sePTwQkmMVlMnC3W0AVd9ZOpZWc37mzlP50YXv2cLXAXlibHkbMGnJ8MLvDd + IIWOaFOuc93mez9AzLomnN+jBCvYXm1ROPOAV6icFWdEzrscDGF5+IUkwg+WiNtDXgcQHptd1v74GmKyyTwz555uVTg1sj6AMMtmUAELinHd6kZefbTX + 3m/4y9F7MrYadCL67rOHPy8WR2EssJ19+CvMc5lrYcdeL43T4T9eeBkK1rks7HDQN+1WEyZtOIANtGhTwDuM4zGbaGAZM4r0SfjYIPkWKC5UDVq1VAJV + +SWcx9fQwflJLGYUuR9+9j3P11pkSWRlmcPzQsLOexMZ20SZ3h12QYgbURQ7EcnITiIpqYluwZSjo6e2Mkh/2ZJKmbVS3VGnWDQOHweFzAzBUOod1+vk + 2xYcDq9DIl/Eg6JRBEHYBGqtDGcC5UTj+MYVWbso/l4ukazK3BBxC9afoLzPKMZcInKkeDFUmpwYIvG+xZ8nYWcLURd9CHxEmmD6Qq8wl0oFr7nYoH0U + IH68Ozg6Qu+UeFgM5lNQ0fgDxN6jG5PXY4LamEgTJglH934Yw6omuxJIEhNhsoUXZCVhQteTikeOlublL4LJkRN3xaxpuFCgoIk4N06ZXQqCNG25qg+8 + JuAsp1cSd+N1gCZUIbWZsTZ0Qy5iS4PmZUAIGOlXa9tR2Ikbpd3VfZEHGmXQCJ+eKXBB5PNhJSbN+JtdKm08pWvTJChSeTDypyN/rj+RGhSVYrihmjrj + PSDvoJ6ftkFf/7Ocecn2ahiuE/ht02dnOx3caKYWpwkJxYrUTU3Oyu716w1l3sijxu8e0TyXSNo+ZHSij3tJeAo1hJfuW5jeC4aIYr9ELXTgwSGCgO6w + CoOfUKK39BVwfF97wmKndOAWdrqSpQlKx39hgfdVVJWUuch8Pdynsj69YHJv8v3i56EbDjXEYbJ10hya6MX8TulqZM7cLA/LKvEwZnhMnAxOvgMyevED + KjANBgsqhkiG9w0BCRADHAIBIDALBglghkgBZQMEAS0EKMDcjLW9gX3FJV6xZAJwsTJSGyfUP/1tsWiWsmXEUPTTqjxfc34p8mgwPAYJKoZIhvcNAQcB + MB0GCWCGSAFlAwQBKgQQdpPJVWfDvj8v8b8Nlim6soAQDvTZn9duq5kXNusBcac6Kg== + """); + + // ML-KEM-768, AES-256-KW, SHA-512 + internal static readonly byte[] MlKem768HkdfSha512 = Convert.FromBase64String( + """ + MIIFRgYJKoZIhvcNAQcDoIIFNzCCBTMCAQMxggTupIIE6gYLKoZIhvcNAQkQDQMwggTZAgEAMDowIjENMAsGA1UEChMESUVURjERMA8GA1UEAxMITEFN + UFMgV0cCFBWf/m8i/VzELFJN9v1eKNDeOPNPMAsGCWCGSAFlAwQEAgSCBEDjmzQ33ytspmyfwtcFPL8r9rHpHkSnLnLLmgmOsN0m0ceGa8UexiWUTapU + amp9d4IAXXp4W8Wp1ebF8Y8gSUPQH7rssbAwJ+d9q6oiOD+kCVgvDbgrs6xvuxIxLfaapwKELuEqo+5aDBcBZ4z6KkRT9DMP36PAs2S7kuUjYCSZaBnq + VJWaCW8snoG75KKf1i4k3QjKYH1WDTnr2wjT02nq53fruUKeMzGEv6z2n/lByvAAvQMgTBk+RLk63862sA/uM+3I7+KNtPd5JwPwOUHY+jqmzOUUyHnJ + f/rX9/BabGZwC9OQYlzJp/wRdQapdzqBrGUj8el0Fv20TygvY8HCJ/euolpIl5FfoKQl4jYwojxG6qomLgeZ4TpXTHBqvRPYUYLOHx/vg6FyMF8yQZVM + hOmWM48rFaGfBALQH30WGRVPYSlnXC7nQZ3wgWZBfk9FtpYGu8WCapo2DKY7Hiltu3utfXYzDmO+qwbkUjn+2sqHbxVqmxcqTbHFwJGt2O+acFDpxCs8 + L/CXd+XmrAKWjIN3IQBj6d3xgKYtgcZbYXvpOZQX9TphI+0F5lpMaiEoNQIX5zQjQUER3UrtgJQ0QpMg2bNDxCuysIkWjQ8EYYB0GJlN3CxoO3SRlQ+I + MfRdkB4sfYbkezyNHOe9AAT+z95N10zXwyiHPdG/MS4kHrpOa+/2bb7RX7Bgq+83EDgoVrWte2jGTo0gXGNpPrRAjLVdFjBUUBA07rolYuq2G8p+1N6e + bcy6diXsEsRGD9moOsU18AdpXfv/LUCVkxj78ORWGs0K2S0Nz3FDtnk3B9SlG9wcH90UdQWc66zQZ2FKKNazy1oGDFH9LL16wNRCRkOiYEKhkggbWymc + nzNKR4s/nvhuIFULlgSkRVsshnDUP7RT63uZogLcSk4snEzB+6RKAAW3OdRvjJ/ak1nLPz5GYpzYqJdWVCvAqbEBHe+UocJWVm2Q7ZmRIGmo0frehCNn + iq3o+zz11Dc/QEuGsdmY+EX9vjcA50qfK6PlnWB2GaHVNzZxEz5AZ/AI+lAimupKI49O2y5hm9PnbqUmjevakBSMu9wFPUt02saqUK4QGWA0U1ECj+RM + lyQolbCNYMEx33XJQukMGHLZqJ7grlc8y9UBwPR61sXB5NwNrr7nr3KIogM39tuG5L/tWk4pekJ54rwCTt0iPaKHrrT9fuIcjiKmEelX5P+izSz6tUeR + zWLxd1e2VRHRoUe83yKlLg8bB6jvDIrlTWk8Ifn7P3guR+VMjdX+A4xrEI9/FUYOuaiE5uW7Y9ez5Cs+2wqmrDMvCWJgNW+AD0S56QNq8i/l+QZdoVBa + +H5IKwGV417frAUVyrd80NnLDdcyCszs3uJuIsIYe7ge/XMlabCM3yFnbDByDQ6qm4fNQXM+7OYaXmH3GdFsQ7rmBwfR+l8iO3fceXRD2POiQFwfIAzA + sDANBgsqhkiG9w0BCRADHgIBIDALBglghkgBZQMEAS0EKC/gnM/HKe7IkcAfIRT5AWHLsVDAbrW6micVFh+VhgjO75hCnB/cVdYwPAYJKoZIhvcNAQcB + MB0GCWCGSAFlAwQBKgQQR/xPkU2fMlFbRtKrVVd4IYAQ6jaUHoNRf8+kK/qU8/xbBw== + """); + + // ML-KEM-768, AES-256-KW, SHA-3-256 + internal static readonly byte[] MlKem768HkdfSha3_256 = Convert.FromBase64String( + """ + MIIFRgYJKoZIhvcNAQcDoIIFNzCCBTMCAQMxggTupIIE6gYLKoZIhvcNAQkQDQMwggTZAgEAMDowIjENMAsGA1UEChMESUVURjERMA8GA1UEAxMITEFN + UFMgV0cCFBWf/m8i/VzELFJN9v1eKNDeOPNPMAsGCWCGSAFlAwQEAgSCBEDAQnnJl3b/sLHdzYHnRUb3JjQBmdejzspdYu38pnvx64Aht+qIn9fgPTEn + 0Opmm8NIdUuPITTT5Tb/jSRHT+zXhpgRP4Bkx0DqWygtLcGEbvb4zWkjADMYWMMO9MpQVzrjmtoBSBbuOv5MFBhMnJY62ppGQPu415KJnY/O9fH0zDx2 + UFp36SArYk5HU9eHIjQjIycoA1tPyeG46pF2hebjpMVFeLgqKoPMI2R2YM762OHlH0WO5oEYlcij2dJbvx5dHmcLxB/wpYVKQxCgi5IOvdpJ1kgV9X2Y + XYMI+5f/2iO/Yn2p9eSKPU/lUbFRCdiChCfdBOm6MDCprux8sYnEurlrIJhh4BYG0nvXJ02m0nEs5qHdJZPmSr4Y5gValJJl/26vyRXbMhtLothjYHRW + o8GQ8yen2m8GxFK1h/7dM4VAWh/jS6NvW854BeAqLdAO4HKWw+GGP4z/+UBY31u/hVi1QpP/K4lcY093tpaovjSZG+nXC5mn2b33OxXH/5cwkMQsJHTC + NuR+FSH+mO+jVQqb3NZOkkYSavoxK7/OmcfYwJ8Ct3xzwZwd9IQKFZFNvc62lKg+ZHPQPXa8FoZ8HB7slq3bPWClB7ZudRjqloUw43wvqpYw3B+aGI1n + ZylXkiiftHXx6MqwTP5PuZoXgBb90PQU5WP628blMjyE3b/13wCbB+XhqXsMQulleFoK0umeD42jf1uQakVJE5VPfRJKC3aLl1t2Fz04GfmL53Y8/Yk7 + aNVtgohQxFre0skXiCB+2RU3iSmAtBMyaUOCImNjfiFrAI7Up7a00/eJPpwBEFIzmOsEXt7Rp9kiTzoGkvtU3q82tSgEXXjO3aX3y7W789ol8f/v9gxP + omqDODIdczR0m8o9jnOhSNFoSeeoXiSqizG138XWYqeoqjZXdL7KzZQmU1OWldOSTAAVa4e0MTqTSn90D8/PTB/eiAf+M6Tshzy7eQoVeGuTyRA8Sq5g + MvX3L3tauQ727Dh9gHd5KkQqGvwu4AloVOtHZ6NFgrXNJPOG4k+RnUxFVT4sLrxm5cjiijI/Exi+q6/ZTNiyI26BtUt4Rnlf4jFxNdkMG97UrsxvPTtM + pR/MLUOnxvfd64LENQODl3euvTOkQxAs855Mj//lRpMkFGWI2D4M/7DRVZSLURV0TmB/Yja+9ZFYylB+1aC6mHxyxpkYQ/+Tz7q9tCUQwOqp9bIma6Xz + AWZiWUI2qeuat7zmHxcI3BSekkmiKgcmHrC9v+VqHLOQvaFvqBT6IXUwGHA3cor7Z6JFtKeH90tg9C/hcYpIWHgDaJgePmx3SLt3LHsUrk5Y3fRB8XRL + 0BOjr+Y5j58FtE5MEFMvn+8PCXxptoowN+W5b0cZ2gdQ0ZxaGE9AnZ5f08d0hTnIkloL/hQHVIAZp2BBjD9w3NJ2oAbLdypyNb4CpjhZOpHgnlliz4jW + 3jANBgsqhkiG9w0BCRADHAIBIDALBglghkgBZQMEAS0EKAFfj/tBvXQBonKMpeVN9I9AtlNWtbYfLlHehhD9rVNhffuD8rgNaU4wPAYJKoZIhvcNAQcB + MB0GCWCGSAFlAwQBKgQQvVJmb/XOeXCm3OBO6nxytYAQ5/vyX3+NyfYsvkmpqWwH0w== + """); + + // ML-KEM-768, AES-256-KW, SHA-3-512 + internal static readonly byte[] MlKem768HkdfSha3_512 = Convert.FromBase64String( + """ + MIIFRgYJKoZIhvcNAQcDoIIFNzCCBTMCAQMxggTupIIE6gYLKoZIhvcNAQkQDQMwggTZAgEAMDowIjENMAsGA1UEChMESUVURjERMA8GA1UEAxMITEFN + UFMgV0cCFBWf/m8i/VzELFJN9v1eKNDeOPNPMAsGCWCGSAFlAwQEAgSCBEBET4kSY815ShaPBsu3iBqSzjcUhPDUSwNKdjKF9hw2p6vVknIh/8i+5eCX + kFBUgNdV4tONxfSKOVbUOqkALkKlUTv6GUCOAPA2/QSScP8lU6YaytdoTgqWS2qHsI3QM7xLGDBG/2XIpw4D/PF2iLPXcxspT2NrMfLPr7QtGkCmLew9 + RrkQv2gppehAfFSOj6dvtWry3K17sfyUYpMPCc9CaPeKAkRKYp9mnZwUqpI7SiRq+tY9ARObOmybLcrb4GQ5+8uxigN457Tm8FVThT2rVwdpI5m9cQsJ + 7HgqMKGlsf/xkwEp10Y8nfuQSx3IV2LyL1N0jBwpDPwBKNaXTL1mQIHX+HfjZE0w4JM5MOm8iURH6DSyQ+I7mKwdU2IFLW1HX5NFM0MkACnX1qWEvDTE + 2AxGvAOnS495NOypZ3aPXfl6+psWjm1pSJFuYEcpSHokKtRxvHmMCU7pcs7vwztKD5UQyeQqtNXwRTb6qjBLu6/HGTB9qWofmWHmiJm95SmPobezM9Xr + F7orOm2V+6LjFYZkpP2YeLmyO3ZRwUFLMT3SQ3yyUmZ26P8KBy5uIFBYCm5dsLQ5mQyDcmKrqO5X+qM9K+0Q9iVkH756pXwEmokkuzDkoaSubLpLB23i + YuMZQvq3etvOa+0HdG+zkBtmwYVnwPmMf41oAI9zheW1QXTr7dq2URCzRRfpNSol0Dwrsu790SnUhbqtc1k0HYwaHMtntVYejtnxwnbZ9vy3xVA3/ZWu + NC/8O1vrQXps6EGLcQCgFWv+DR2nHdq2w9M1tntoNcnK2FVGHaL82pfChjR5YjWbszLUs6d1EDd/7eEm879CPGTJBwY6qEIsnbJ/mB2EEHDOv+NrbwbD + 0AaxbHTui7FYF1uw6fJuM76/wDT1yHsXSnQVnwLYmDEwYFF6UZ+qCnbobY6VGs2HyKaVQeinPotkjXuqacKquPEBGiXNSFB7LmVhKseTbBSdqZZM/FYR + rhcJ1ThgaWKNJw5+cBRwW2euqdzkExj62G/Sz23gxdPvKt20PqhmnA06HbvA+DqdRc5Cc3yeCherx0kqrZac0Qgs/GKchq+D1z3SpWW3pm0Jk4m446kN + qToUkWmr+/7GYqSxSqx8oOBiA+PHhn31ZylhzOHuCYlC5RXCX/WyWjRupyktpqaTQ9AQAdfycIBGoBGksj+gmeap8/GP6DXDpOfY5KiHiMqydBIkMkpV + vetsl8TmMkVzEflvTvaMdZz+qY8t+MSidD+u/yqLQ/jTm1DvDr3zdKv8lwScr09GU1m0Bt9k9Pi2rmMgC6jHWbu3wSYw7RR1z7BAgF6S83DbjYENrqpm + 7qzqK0C1hXKmyXMta8ciadI0RSe4u+5KlJSEsaz85WIxtmGav8Mjz7oD+Bq4gp2YEVLyNVaGSgD0f2JxoZ2AZeunwCPq7xg6G3A0V74T9MR7ZKudyoD2 + wTANBgsqhkiG9w0BCRADHAIBIDALBglghkgBZQMEAS0EKBq1zc/H45zTXh0sCJhHGEYMG5mtQJ2pGTwyI79napyWuuBiAGpqp08wPAYJKoZIhvcNAQcB + MB0GCWCGSAFlAwQBKgQQ7OveAaBF3gHrD+N/WR3pD4AQiwLi7EFBmT8LZqhILJQmmQ== + """); + + internal static byte[] UnsupportedOtherRecipientInfo { get; } = BuildUnsupportedOtherRecipientInfo(); + + private static byte[] BuildUnsupportedOtherRecipientInfo() + { + AsnWriter writer = new AsnWriter(AsnEncodingRules.DER); + Asn1Tag context0 = new Asn1Tag(TagClass.ContextSpecific, 0); + Asn1Tag context4 = new Asn1Tag(TagClass.ContextSpecific, 4); + + writer.PushSequence(); + writer.WriteObjectIdentifier(TestOids.Pkcs7Enveloped); + writer.PushSequence(context0); + writer.PushSequence(); + writer.WriteInteger(3); + writer.PushSetOf(); + writer.PushSequence(context4); + writer.WriteObjectIdentifier("1.2.3.4"); + writer.WriteCharacterString(UniversalTagNumber.UTF8String, "other unsupported recipient type"); + writer.PopSequence(context4); + writer.PopSetOf(); + writer.PushSequence(); + writer.WriteObjectIdentifier(TestOids.Pkcs7Data); + writer.PushSequence(); + writer.WriteObjectIdentifier(TestOids.Aes256); + writer.WriteOctetString(new byte[16]); + writer.PopSequence(); + writer.PopSequence(); + writer.PopSequence(); + writer.PopSequence(context0); + writer.PopSequence(); + + return writer.Encode(); + } + } +} diff --git a/src/libraries/System.Security.Cryptography.Pkcs/tests/EnvelopedCms/MLKemDecryptTests.cs b/src/libraries/System.Security.Cryptography.Pkcs/tests/EnvelopedCms/MLKemDecryptTests.cs new file mode 100644 index 00000000000000..1ee3bd2d6ca674 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Pkcs/tests/EnvelopedCms/MLKemDecryptTests.cs @@ -0,0 +1,871 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Security.Cryptography.Tests; +using System.Security.Cryptography.X509Certificates; + +using Xunit; + +namespace System.Security.Cryptography.Pkcs.EnvelopedCmsTests.Tests +{ + [PlatformSpecific(~TestPlatforms.Windows)] + [ConditionalClass(typeof(MLKem), nameof(MLKem.IsSupported))] + public static class MLKemDecryptTests + { + public static TheoryData AesKeyWrapDocuments { get; } = new TheoryData + { + KemTestDocuments.MlKem768Aes128Wrap, + KemTestDocuments.MlKem768Aes192Wrap, + KemTestDocuments.MlKem768, + }; + + public static TheoryData HkdfDocuments { get; } = new TheoryData + { + KemTestDocuments.MlKem768HkdfSha256, + KemTestDocuments.MlKem768, + KemTestDocuments.MlKem768HkdfSha512, + KemTestDocuments.MlKem768HkdfSha3_256, + KemTestDocuments.MlKem768HkdfSha3_384, + KemTestDocuments.MlKem768HkdfSha3_512, + }; + + public static TheoryData MlKemParameterSetDocuments { get; } = new TheoryData + { + { KemTestDocuments.MlKem512, MLKemTestData.IetfMlKem512PrivateKeySeed }, + { KemTestDocuments.MlKem768, MLKemTestData.IetfMlKem768PrivateKeySeed }, + { KemTestDocuments.MlKem1024, MLKemTestData.IetfMlKem1024PrivateKeySeed }, + }; + + public static TheoryData UkmDocuments { get; } = new TheoryData + { + { KemTestDocuments.MlKem768, null }, + { KemTestDocuments.MlKem768EmptyUkm, [] }, + { KemTestDocuments.MlKem768NonEmptyUkm, [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] }, + }; + + [Theory] + [MemberData(nameof(AesKeyWrapDocuments))] + public static void DecryptAesKeyWrapAlgorithm(byte[] encodedMessage) + { + Decrypt(encodedMessage, MLKemTestData.IetfMlKem768PrivateKeySeed); + } + + [Theory] + [MemberData(nameof(HkdfDocuments))] + public static void DecryptHkdfAlgorithm(byte[] encodedMessage) + { + Decrypt(encodedMessage, MLKemTestData.IetfMlKem768PrivateKeySeed); + } + + [Theory] + [MemberData(nameof(MlKemParameterSetDocuments))] + public static void DecryptMlKemParameterSet(byte[] encodedMessage, byte[] privateKey) + { + Decrypt(encodedMessage, privateKey); + } + + [Theory] + [MemberData(nameof(UkmDocuments))] + public static void DecryptUserKeyingMaterial(byte[] encodedMessage, byte[]? expectedUkm) + { + EnvelopedCms cms = Decrypt(encodedMessage, MLKemTestData.IetfMlKem768PrivateKeySeed); + KemRecipientInfo recipientInfo = Assert.IsType(Assert.Single(cms.RecipientInfos)); + ReadOnlyMemory? actualUkm = recipientInfo.UserKeyingMaterial; + + if (expectedUkm is null) + { + Assert.Null(actualUkm); + } + else + { + Assert.True(actualUkm.HasValue); + AssertExtensions.SequenceEqual(expectedUkm, actualUkm.Value.Span); + } + } + + [Fact] + public static void DecryptWithCertificatePrivateKey() + { + using (X509Certificate2 certificate = X509Certificate2.CreateFromPem( + MLKemTestData.IetfMlKem768CertificatePem, + MLKemTestData.IetfMlKem768PrivateKeySeedPem)) + { + EnvelopedCms cms = new EnvelopedCms(); + cms.Decode(KemTestDocuments.MlKem768); + cms.Decrypt(new X509Certificate2Collection(certificate)); + + Assert.Equal("hello world!"u8.ToArray(), cms.ContentInfo.Content); + } + } + + [Fact] + public static void DecryptCompositeMLKemNotSupported() + { + EnvelopedCms cms = new EnvelopedCms(); + cms.Decode(KemTestDocuments.MlKem768); + + KemRecipientInfo recipientInfo = Assert.IsType(Assert.Single(cms.RecipientInfos)); + + using (TestCompositeMLKem key = new TestCompositeMLKem(CompositeMLKemAlgorithm.MLKem768WithRsaOaep2048)) + { + Assert.Throws(() => cms.Decrypt(recipientInfo, key)); + } + } + + [Fact] + public static void CompositeMLKemCertificateNotSupported() + { + // From https://github.com/lamps-wg/draft-composite-kem/blob/6f6c8a5601cfe8d66730841a413d209add7dc9ed/src/testvectors.json + const string Certificate = """ + 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 + """; + const string Document = """ + MIIFYAYJKoZIhvcNAQcDoIIFUTCCBU0CAQMxggUIpIIFBAYLKoZIhvcNAQkQDQMwggTzAgEAMFUwPTENMAsGA1UECgwESUVURjEO + MAwGA1UECwwFTEFNUFMxHDAaBgNVBAMME0NvbXBvc2l0ZSBNTC1LRU0gQ0ECFERwMfly9edO6lv6Wo3KYYgnaV+5MAoGCCsGAQUF + BwY6BIIEQPwlsWLBJ5hc9nmSoo0id+KiF8wGPJQubX6GUiNM32orh2C8ilD6MB2sd0nWE6Yi1ngu8YXr8/6jGrf4VJlJpt/AFW7A + YxL9iJTLE9YE7RSapZOGuzjaL4gZTSGvnKAeRvoAak+b8h0OLjxO0zU80E+XVAEFNozZMDFvc2fbVrQS0HxsKYsCCcCMULvZgHiS + n74fBNrInqe66Kb3hWa4+484CkTAhWEMQIilOgXn8z9rvHfSq2ABcLXNwewKJ2TPM756Sq3yVY/OBS+02YMka7aRRlqNk6oZi8Gp + PyY7JClEGghJQWH4zlgQTYMxbKtFPimO+9QDmXCsd5IN0+JkBnZBo5YTXbidF/6AkbwMbbMmqqZo/hOxw9v5uz6/7TxB/Ro2NnCk + sboOqxTLTrpYyY6jKB/9eXLFAVcnofECyDRygP+28TutH0vROISQ+OSw8fHOhx/FAra0PbaWIy96ZHaA7SpWOkhkCUjOr2In0n6y + rmVahpEdayf5svOkGJO6UsTvjXMcnLicJvwECUKD4k6uHnRlCmOkdygnhfg10u5AM4Kna60afHFAjjdyr6YsVissf2960KS1G3Io + jmIOwYkxWR4jNSaH4ge6dVwTWdOaKJMiR5ttUaQYvK/5cjfHpAdHBCiOMx6v9uGhPyvXb0Zg0w4BDNCbcXCk0ssHOsiPvQ2bZytr + ee0v9oww9EjcIaWExzjTmvevW1ux9VyCpo2xYmyPY9xQMsUqdjDVzIS9RnXIXgxGweAJoA0LD3NhRCPFWd955ddznSGo7MAP9lq7 + NVQB0FFGlMHURwOcjhFEWW6SHHt9e+jZZRbLS8R2PV8zD5smEVNFAjnhl3Q5L20Tx+bkri05pBO0Il5Ywjhxi5XL7z5kWvDVgbO1 + GCr+6Z/vAE7fMZ0BioSGrduDyWfyOKkwPjBOZByaQYvVHVDF4lJdAI1l48rAgxoYamIpMhmsQcBKR/1dxtO1wvT0UsqMfOjYVbno + 08mdaDVxN0YIdP1UFxPTpV3rObLeqMYJLt+JJmzXuWrhTJz+U9hzH7QTUInjMxwCD36MQJXlxJRn4d5MqmZs86Aveuf2hY4aLS9d + wnx20h8xEie54eo47MNv0VukwUNnUBkxsa4KHHFm2NUz2LDTyZz9YgXapr21UiQQpfxlAO4jPx74LUdNrAQVhnVcEGoxPLQF9ilm + FPVb9Ql7muBQM0ldcG/kt09aFk9cV6iYILTQ7RuqKhRUeqTMHubRW00eScRqvaSzmnOKC+jUDB312yB17VThmlEIgwBHtEPjytzy + 9M1I0Xkt+8NKMntVklESmGiDf1mZSFS4yXehxM5d00HBHHXGnWBjQ46AFeS7UNEU2SYmb45fiA73Pe957QDiUc8/O37AMbwXyawu + z4UY8NadQ/VNZXIctZYhPVVn8Cu8Q/l4zuPRcf0JgiX/hnWdxfrQm24751ihMA0GCyqGSIb3DQEJEAMdAgEgMAsGCWCGSAFlAwQB + LQQoE38MeUYr9a83NDiSiUZLBkxT+ZGKkeSxxEB8pID/NJje0ky4KzJQTzA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBBHF8mZ + UKv1MnkeqhxpXN1KgBB3KH22OastHUzab9ADNivv + """; + + using (X509Certificate2 certificate = + X509CertificateLoader.LoadCertificate(Convert.FromBase64String(Certificate))) + { + EnvelopedCms cms = new EnvelopedCms(new ContentInfo("hello world!"u8.ToArray())); + CmsRecipient recipient = CmsRecipient.CreateForKeyEncapsulation(certificate, []); + Assert.Throws(() => cms.Encrypt(recipient)); + + cms = new EnvelopedCms(); + cms.Decode(Convert.FromBase64String(Document)); + Assert.Throws( + () => cms.Decrypt(new X509Certificate2Collection(certificate))); + } + } + + [Fact] + public static void DecryptNullArguments() + { + EnvelopedCms cms = new EnvelopedCms(); + cms.Decode(KemTestDocuments.MlKem768); + + KemRecipientInfo recipientInfo = Assert.IsType(Assert.Single(cms.RecipientInfos)); + + using (MLKem mlKem = MLKem.ImportPkcs8PrivateKey(MLKemTestData.IetfMlKem768PrivateKeySeed)) + using (TestCompositeMLKem compositeMLKem = new TestCompositeMLKem( + CompositeMLKemAlgorithm.MLKem768WithRsaOaep2048)) + { + Assert.Throws(() => cms.Decrypt(null, mlKem)); + Assert.Throws(() => cms.Decrypt(recipientInfo, (MLKem)null)); + Assert.Throws(() => cms.Decrypt(null, compositeMLKem)); + Assert.Throws(() => cms.Decrypt(recipientInfo, (CompositeMLKem)null)); + } + } + + [Fact] + public static void DecryptWithCertificateWithoutPrivateKey() + { + EnvelopedCms cms = new EnvelopedCms(); + cms.Decode(KemTestDocuments.MlKem768); + + using (X509Certificate2 certificate = X509Certificate2.CreateFromPem( + MLKemTestData.IetfMlKem768CertificatePem)) + { + Assert.ThrowsAny( + () => cms.Decrypt(new X509Certificate2Collection(certificate))); + } + } + + [Fact] + public static void DecryptWithDisposedPrivateKey() + { + EnvelopedCms cms = new EnvelopedCms(); + cms.Decode(KemTestDocuments.MlKem768); + + KemRecipientInfo recipientInfo = Assert.IsType(Assert.Single(cms.RecipientInfos)); + MLKem privateKey = MLKem.ImportPkcs8PrivateKey(MLKemTestData.IetfMlKem768PrivateKeySeed); + privateKey.Dispose(); + + Assert.Throws(() => cms.Decrypt(recipientInfo, privateKey)); + } + + [Fact] + public static void DecryptWithEncapsulationOnlyKey() + { + EnvelopedCms cms = new EnvelopedCms(); + cms.Decode(KemTestDocuments.MlKem768); + + KemRecipientInfo recipientInfo = Assert.IsType(Assert.Single(cms.RecipientInfos)); + + using (MLKem publicKey = MLKem.ImportSubjectPublicKeyInfo(MLKemTestData.IetfMlKem768Spki)) + { + Assert.ThrowsAny(() => cms.Decrypt(recipientInfo, publicKey)); + } + } + + [Fact] + public static void DecryptBeforeDecode() + { + EnvelopedCms decodedCms = new EnvelopedCms(); + decodedCms.Decode(KemTestDocuments.MlKem768); + KemRecipientInfo recipientInfo = Assert.IsType(Assert.Single(decodedCms.RecipientInfos)); + + using (MLKem privateKey = MLKem.ImportPkcs8PrivateKey(MLKemTestData.IetfMlKem768PrivateKeySeed)) + { + EnvelopedCms cms = new EnvelopedCms(); + Assert.Throws(() => cms.Decrypt(recipientInfo, privateKey)); + } + } + + [Fact] + public static void DecryptAfterEncrypt() + { + EnvelopedCms decodedCms = new EnvelopedCms(); + decodedCms.Decode(KemTestDocuments.MlKem768); + KemRecipientInfo recipientInfo = Assert.IsType(Assert.Single(decodedCms.RecipientInfos)); + + using (X509Certificate2 certificate = X509Certificate2.CreateFromPem( + MLKemTestData.IetfMlKem768CertificatePem)) + using (MLKem privateKey = MLKem.ImportPkcs8PrivateKey(MLKemTestData.IetfMlKem768PrivateKeySeed)) + { + EnvelopedCms cms = new EnvelopedCms(new ContentInfo("hello world!"u8.ToArray())); + cms.Encrypt(new CmsRecipient(certificate)); + + Assert.ThrowsAny(() => cms.Decrypt(recipientInfo, privateKey)); + } + } + + [Fact] + public static void DecryptTwice() + { + EnvelopedCms cms = new EnvelopedCms(); + cms.Decode(KemTestDocuments.MlKem768); + KemRecipientInfo recipientInfo = Assert.IsType(Assert.Single(cms.RecipientInfos)); + + using (MLKem privateKey = MLKem.ImportPkcs8PrivateKey(MLKemTestData.IetfMlKem768PrivateKeySeed)) + { + cms.Decrypt(recipientInfo, privateKey); + Assert.ThrowsAny(() => cms.Decrypt(recipientInfo, privateKey)); + } + } + + [Fact] + public static void DecryptCorrectKeyAfterWrongKey() + { + EnvelopedCms cms = new EnvelopedCms(); + cms.Decode(KemTestDocuments.MlKem768); + KemRecipientInfo recipientInfo = Assert.IsType(Assert.Single(cms.RecipientInfos)); + + using (MLKem wrongKey = MLKem.GenerateKey(MLKemAlgorithm.MLKem768)) + { + Assert.ThrowsAny(() => cms.Decrypt(recipientInfo, wrongKey)); + } + + using (MLKem correctKey = MLKem.ImportPkcs8PrivateKey(MLKemTestData.IetfMlKem768PrivateKeySeed)) + { + cms.Decrypt(recipientInfo, correctKey); + } + + Assert.Equal("hello world!"u8.ToArray(), cms.ContentInfo.Content); + } + + [Fact] + public static void DecryptWrongPrivateKey() + { + EnvelopedCms cms = new EnvelopedCms(); + cms.Decode(KemTestDocuments.MlKem768); + + KemRecipientInfo recipientInfo = Assert.IsType(Assert.Single(cms.RecipientInfos)); + + using (MLKem privateKey = MLKem.GenerateKey(MLKemAlgorithm.MLKem768)) + { + Assert.ThrowsAny(() => cms.Decrypt(recipientInfo, privateKey)); + } + } + + [Fact] + public static void DecryptTamperedEncryptedKey() + { + const string Document = """ + MIIFRgYJKoZIhvcNAQcDoIIFNzCCBTMCAQMxggTupIIE6gYLKoZIhvcNAQkQDQMwggTZAgEAMDowIjENMAsGA1UEChMESUVURjERMA8GA1UEAxMITEFN + UFMgV0cCFBWf/m8i/VzELFJN9v1eKNDeOPNPMAsGCWCGSAFlAwQEAgSCBED8JbFiwSeYXPZ5kqKNInfiohfMBjyULm1+hlIjTN9qK4dgvIpQ+jAdrHdJ + 1hOmItZ4LvGF6/P+oxq3+FSZSabfwBVuwGMS/YiUyxPWBO0UmqWThrs42i+IGU0hr5ygHkb6AGpPm/IdDi48TtM1PNBPl1QBBTaM2TAxb3Nn21a0EtB8 + bCmLAgnAjFC72YB4kp++HwTayJ6nuuim94VmuPuPOApEwIVhDECIpToF5/M/a7x30qtgAXC1zcHsCidkzzO+ekqt8lWPzgUvtNmDJGu2kUZajZOqGYvB + qT8mOyQpRBoISUFh+M5YEE2DMWyrRT4pjvvUA5lwrHeSDdPiZAZ2QaOWE124nRf+gJG8DG2zJqqmaP4TscPb+bs+v+08Qf0aNjZwpLG6DqsUy066WMmO + oygf/XlyxQFXJ6HxAsg0coD/tvE7rR9L0TiEkPjksPHxzocfxQK2tD22liMvemR2gO0qVjpIZAlIzq9iJ9J+sq5lWoaRHWsn+bLzpBiTulLE741zHJy4 + nCb8BAlCg+JOrh50ZQpjpHcoJ4X4NdLuQDOCp2utGnxxQI43cq+mLFYrLH9vetCktRtyKI5iDsGJMVkeIzUmh+IHunVcE1nTmiiTIkebbVGkGLyv+XI3 + x6QHRwQojjMer/bhoT8r129GYNMOAQzQm3FwpNLLBzrIj70Nm2cra3ntL/aMMPRI3CGlhMc405r3r1tbsfVcgqaNsWJsj2PcUDLFKnYw1cyEvUZ1yF4M + RsHgCaANCw9zYUQjxVnfeeXXc50hqOzAD/ZauzVUAdBRRpTB1EcDnI4RRFlukhx7fXvo2WUWy0vEdj1fMw+bJhFTRQI54Zd0OS9tE8fm5K4tOaQTtCJe + WMI4cYuVy+8+ZFrw1YGztRgq/umf7wBO3zGdAYqEhq3bg8ln8jipMD4wTmQcmkGL1R1QxeJSXQCNZePKwIMaGGpiKTIZrEHASkf9XcbTtcL09FLKjHzo + 2FW56NPJnWg1cTdGCHT9VBcT06Vd6zmy3qjGCS7fiSZs17lq4Uyc/lPYcx+0E1CJ4zMcAg9+jECV5cSUZ+HeTKpmbPOgL3rn9oWOGi0vXcJ8dtIfMRIn + ueHqOOzDb9FbpMFDZ1AZMbGuChxxZtjVM9iw08mc/WIF2qa9tVIkEKX8ZQDuIz8e+C1HTawEFYZ1XBBqMTy0BfYpZhT1W/UJe5rgUDNJXXBv5LdPWhZP + XFeomCC00O0bqioUVHqkzB7m0VtNHknEar2ks5pzigvo1Awd9dsgde1U4ZpRCIMAR7RD48rc8vTNSNF5LfvDSjJ7VZJREphog39ZmUhUuMl3ocTOXdNB + wRx1xp1gY0OOgBXku1DRFNkmJm+OX4gO9z3vee0A4lHPPzt+wDG8F8msLs+FGPDWnUP1TWVyHLWWIT1VZ/ArvEP5eM7j0XH9CYIl/4Z1ncX60JtuO+dY + oTANBgsqhkiG9w0BCRADHQIBIDALBglghkgBZQMEAS0EKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwPAYJKoZIhvcNAQcB + MB0GCWCGSAFlAwQBKgQQRxfJmVCr9TJ5HqocaVzdSoAQdyh9tjmrLR1M2m/QAzYr7w== + """; + + EnvelopedCms cms = new EnvelopedCms(); + cms.Decode(Convert.FromBase64String(Document)); + + KemRecipientInfo recipientInfo = Assert.IsType(Assert.Single(cms.RecipientInfos)); + + using (MLKem privateKey = MLKem.ImportPkcs8PrivateKey(MLKemTestData.IetfMlKem768PrivateKeySeed)) + { + Assert.ThrowsAny(() => cms.Decrypt(recipientInfo, privateKey)); + } + } + + [Fact] + public static void DecryptInvalidVersion() + { + const string Document = """ + MIIFRgYJKoZIhvcNAQcDoIIFNzCCBTMCAQMxggTupIIE6gYLKoZIhvcNAQkQDQMwggTZAgEBMDowIjENMAsGA1UEChMESUVURjERMA8GA1UEAxMITEFN + UFMgV0cCFBWf/m8i/VzELFJN9v1eKNDeOPNPMAsGCWCGSAFlAwQEAgSCBED8JbFiwSeYXPZ5kqKNInfiohfMBjyULm1+hlIjTN9qK4dgvIpQ+jAdrHdJ + 1hOmItZ4LvGF6/P+oxq3+FSZSabfwBVuwGMS/YiUyxPWBO0UmqWThrs42i+IGU0hr5ygHkb6AGpPm/IdDi48TtM1PNBPl1QBBTaM2TAxb3Nn21a0EtB8 + bCmLAgnAjFC72YB4kp++HwTayJ6nuuim94VmuPuPOApEwIVhDECIpToF5/M/a7x30qtgAXC1zcHsCidkzzO+ekqt8lWPzgUvtNmDJGu2kUZajZOqGYvB + qT8mOyQpRBoISUFh+M5YEE2DMWyrRT4pjvvUA5lwrHeSDdPiZAZ2QaOWE124nRf+gJG8DG2zJqqmaP4TscPb+bs+v+08Qf0aNjZwpLG6DqsUy066WMmO + oygf/XlyxQFXJ6HxAsg0coD/tvE7rR9L0TiEkPjksPHxzocfxQK2tD22liMvemR2gO0qVjpIZAlIzq9iJ9J+sq5lWoaRHWsn+bLzpBiTulLE741zHJy4 + nCb8BAlCg+JOrh50ZQpjpHcoJ4X4NdLuQDOCp2utGnxxQI43cq+mLFYrLH9vetCktRtyKI5iDsGJMVkeIzUmh+IHunVcE1nTmiiTIkebbVGkGLyv+XI3 + x6QHRwQojjMer/bhoT8r129GYNMOAQzQm3FwpNLLBzrIj70Nm2cra3ntL/aMMPRI3CGlhMc405r3r1tbsfVcgqaNsWJsj2PcUDLFKnYw1cyEvUZ1yF4M + RsHgCaANCw9zYUQjxVnfeeXXc50hqOzAD/ZauzVUAdBRRpTB1EcDnI4RRFlukhx7fXvo2WUWy0vEdj1fMw+bJhFTRQI54Zd0OS9tE8fm5K4tOaQTtCJe + WMI4cYuVy+8+ZFrw1YGztRgq/umf7wBO3zGdAYqEhq3bg8ln8jipMD4wTmQcmkGL1R1QxeJSXQCNZePKwIMaGGpiKTIZrEHASkf9XcbTtcL09FLKjHzo + 2FW56NPJnWg1cTdGCHT9VBcT06Vd6zmy3qjGCS7fiSZs17lq4Uyc/lPYcx+0E1CJ4zMcAg9+jECV5cSUZ+HeTKpmbPOgL3rn9oWOGi0vXcJ8dtIfMRIn + ueHqOOzDb9FbpMFDZ1AZMbGuChxxZtjVM9iw08mc/WIF2qa9tVIkEKX8ZQDuIz8e+C1HTawEFYZ1XBBqMTy0BfYpZhT1W/UJe5rgUDNJXXBv5LdPWhZP + XFeomCC00O0bqioUVHqkzB7m0VtNHknEar2ks5pzigvo1Awd9dsgde1U4ZpRCIMAR7RD48rc8vTNSNF5LfvDSjJ7VZJREphog39ZmUhUuMl3ocTOXdNB + wRx1xp1gY0OOgBXku1DRFNkmJm+OX4gO9z3vee0A4lHPPzt+wDG8F8msLs+FGPDWnUP1TWVyHLWWIT1VZ/ArvEP5eM7j0XH9CYIl/4Z1ncX60JtuO+dY + oTANBgsqhkiG9w0BCRADHQIBIDALBglghkgBZQMEAS0EKBN/DHlGK/WvNzQ4kolGSwZMU/mRipHkscRAfKSA/zSY3tJMuCsyUE8wPAYJKoZIhvcNAQcB + MB0GCWCGSAFlAwQBKgQQRxfJmVCr9TJ5HqocaVzdSoAQdyh9tjmrLR1M2m/QAzYr7w== + """; + + KemRecipientInfo recipientInfo = AssertInvalidDocument(Document, MLKemAlgorithm.MLKem768); + Assert.Equal(1, recipientInfo.Version); + } + + [Fact] + public static void DecryptInvalidKemCiphertextLength() + { + const string Document = """ + MIIFRgYJKoZIhvcNAQcDoIIFNzCCBTMCAQMxggTupIIE6gYLKoZIhvcNAQkQDQMwggTZAgEAMDowIjENMAsGA1UEChMESUVURjERMA8GA1UEAxMITEFN + UFMgV0cCFBWf/m8i/VzELFJN9v1eKNDeOPNPMAsGCWCGSAFlAwQEAwSCBED8JbFiwSeYXPZ5kqKNInfiohfMBjyULm1+hlIjTN9qK4dgvIpQ+jAdrHdJ + 1hOmItZ4LvGF6/P+oxq3+FSZSabfwBVuwGMS/YiUyxPWBO0UmqWThrs42i+IGU0hr5ygHkb6AGpPm/IdDi48TtM1PNBPl1QBBTaM2TAxb3Nn21a0EtB8 + bCmLAgnAjFC72YB4kp++HwTayJ6nuuim94VmuPuPOApEwIVhDECIpToF5/M/a7x30qtgAXC1zcHsCidkzzO+ekqt8lWPzgUvtNmDJGu2kUZajZOqGYvB + qT8mOyQpRBoISUFh+M5YEE2DMWyrRT4pjvvUA5lwrHeSDdPiZAZ2QaOWE124nRf+gJG8DG2zJqqmaP4TscPb+bs+v+08Qf0aNjZwpLG6DqsUy066WMmO + oygf/XlyxQFXJ6HxAsg0coD/tvE7rR9L0TiEkPjksPHxzocfxQK2tD22liMvemR2gO0qVjpIZAlIzq9iJ9J+sq5lWoaRHWsn+bLzpBiTulLE741zHJy4 + nCb8BAlCg+JOrh50ZQpjpHcoJ4X4NdLuQDOCp2utGnxxQI43cq+mLFYrLH9vetCktRtyKI5iDsGJMVkeIzUmh+IHunVcE1nTmiiTIkebbVGkGLyv+XI3 + x6QHRwQojjMer/bhoT8r129GYNMOAQzQm3FwpNLLBzrIj70Nm2cra3ntL/aMMPRI3CGlhMc405r3r1tbsfVcgqaNsWJsj2PcUDLFKnYw1cyEvUZ1yF4M + RsHgCaANCw9zYUQjxVnfeeXXc50hqOzAD/ZauzVUAdBRRpTB1EcDnI4RRFlukhx7fXvo2WUWy0vEdj1fMw+bJhFTRQI54Zd0OS9tE8fm5K4tOaQTtCJe + WMI4cYuVy+8+ZFrw1YGztRgq/umf7wBO3zGdAYqEhq3bg8ln8jipMD4wTmQcmkGL1R1QxeJSXQCNZePKwIMaGGpiKTIZrEHASkf9XcbTtcL09FLKjHzo + 2FW56NPJnWg1cTdGCHT9VBcT06Vd6zmy3qjGCS7fiSZs17lq4Uyc/lPYcx+0E1CJ4zMcAg9+jECV5cSUZ+HeTKpmbPOgL3rn9oWOGi0vXcJ8dtIfMRIn + ueHqOOzDb9FbpMFDZ1AZMbGuChxxZtjVM9iw08mc/WIF2qa9tVIkEKX8ZQDuIz8e+C1HTawEFYZ1XBBqMTy0BfYpZhT1W/UJe5rgUDNJXXBv5LdPWhZP + XFeomCC00O0bqioUVHqkzB7m0VtNHknEar2ks5pzigvo1Awd9dsgde1U4ZpRCIMAR7RD48rc8vTNSNF5LfvDSjJ7VZJREphog39ZmUhUuMl3ocTOXdNB + wRx1xp1gY0OOgBXku1DRFNkmJm+OX4gO9z3vee0A4lHPPzt+wDG8F8msLs+FGPDWnUP1TWVyHLWWIT1VZ/ArvEP5eM7j0XH9CYIl/4Z1ncX60JtuO+dY + oTANBgsqhkiG9w0BCRADHQIBIDALBglghkgBZQMEAS0EKBN/DHlGK/WvNzQ4kolGSwZMU/mRipHkscRAfKSA/zSY3tJMuCsyUE8wPAYJKoZIhvcNAQcB + MB0GCWCGSAFlAwQBKgQQRxfJmVCr9TJ5HqocaVzdSoAQdyh9tjmrLR1M2m/QAzYr7w== + """; + + AssertInvalidDocument(Document, MLKemAlgorithm.MLKem1024); + } + + [Fact] + public static void DecryptInvalidAesKeyWrapLength() + { + const string Document = """ + MIIFNQYJKoZIhvcNAQcDoIIFJjCCBSICAQMxggTdpIIE2QYLKoZIhvcNAQkQDQMwggTIAgEAMDowIjENMAsGA1UEChMESUVURjERMA8GA1UEAxMITEFN + UFMgV0cCFBWf/m8i/VzELFJN9v1eKNDeOPNPMAsGCWCGSAFlAwQEAgSCBED8JbFiwSeYXPZ5kqKNInfiohfMBjyULm1+hlIjTN9qK4dgvIpQ+jAdrHdJ + 1hOmItZ4LvGF6/P+oxq3+FSZSabfwBVuwGMS/YiUyxPWBO0UmqWThrs42i+IGU0hr5ygHkb6AGpPm/IdDi48TtM1PNBPl1QBBTaM2TAxb3Nn21a0EtB8 + bCmLAgnAjFC72YB4kp++HwTayJ6nuuim94VmuPuPOApEwIVhDECIpToF5/M/a7x30qtgAXC1zcHsCidkzzO+ekqt8lWPzgUvtNmDJGu2kUZajZOqGYvB + qT8mOyQpRBoISUFh+M5YEE2DMWyrRT4pjvvUA5lwrHeSDdPiZAZ2QaOWE124nRf+gJG8DG2zJqqmaP4TscPb+bs+v+08Qf0aNjZwpLG6DqsUy066WMmO + oygf/XlyxQFXJ6HxAsg0coD/tvE7rR9L0TiEkPjksPHxzocfxQK2tD22liMvemR2gO0qVjpIZAlIzq9iJ9J+sq5lWoaRHWsn+bLzpBiTulLE741zHJy4 + nCb8BAlCg+JOrh50ZQpjpHcoJ4X4NdLuQDOCp2utGnxxQI43cq+mLFYrLH9vetCktRtyKI5iDsGJMVkeIzUmh+IHunVcE1nTmiiTIkebbVGkGLyv+XI3 + x6QHRwQojjMer/bhoT8r129GYNMOAQzQm3FwpNLLBzrIj70Nm2cra3ntL/aMMPRI3CGlhMc405r3r1tbsfVcgqaNsWJsj2PcUDLFKnYw1cyEvUZ1yF4M + RsHgCaANCw9zYUQjxVnfeeXXc50hqOzAD/ZauzVUAdBRRpTB1EcDnI4RRFlukhx7fXvo2WUWy0vEdj1fMw+bJhFTRQI54Zd0OS9tE8fm5K4tOaQTtCJe + WMI4cYuVy+8+ZFrw1YGztRgq/umf7wBO3zGdAYqEhq3bg8ln8jipMD4wTmQcmkGL1R1QxeJSXQCNZePKwIMaGGpiKTIZrEHASkf9XcbTtcL09FLKjHzo + 2FW56NPJnWg1cTdGCHT9VBcT06Vd6zmy3qjGCS7fiSZs17lq4Uyc/lPYcx+0E1CJ4zMcAg9+jECV5cSUZ+HeTKpmbPOgL3rn9oWOGi0vXcJ8dtIfMRIn + ueHqOOzDb9FbpMFDZ1AZMbGuChxxZtjVM9iw08mc/WIF2qa9tVIkEKX8ZQDuIz8e+C1HTawEFYZ1XBBqMTy0BfYpZhT1W/UJe5rgUDNJXXBv5LdPWhZP + XFeomCC00O0bqioUVHqkzB7m0VtNHknEar2ks5pzigvo1Awd9dsgde1U4ZpRCIMAR7RD48rc8vTNSNF5LfvDSjJ7VZJREphog39ZmUhUuMl3ocTOXdNB + wRx1xp1gY0OOgBXku1DRFNkmJm+OX4gO9z3vee0A4lHPPzt+wDG8F8msLs+FGPDWnUP1TWVyHLWWIT1VZ/ArvEP5eM7j0XH9CYIl/4Z1ncX60JtuO+dY + oTANBgsqhkiG9w0BCRADHQIBIDALBglghkgBZQMEAS0EFwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDwGCSqGSIb3DQEHATAdBglghkgBZQMEASoEEEcX + yZlQq/UyeR6qHGlc3UqAEHcofbY5qy0dTNpv0AM2K+8= + """; + + AssertInvalidDocument(Document, MLKemAlgorithm.MLKem768); + } + + [Fact] + public static void DecryptAesKeyWrapLengthTooShort() + { + const string Document = """ + MIIFLgYJKoZIhvcNAQcDoIIFHzCCBRsCAQMxggTWpIIE0gYLKoZIhvcNAQkQDQMwggTBAgEAMDowIjENMAsGA1UEChMESUVURjERMA8GA1UEAxMITEFN + UFMgV0cCFBWf/m8i/VzELFJN9v1eKNDeOPNPMAsGCWCGSAFlAwQEAgSCBED8JbFiwSeYXPZ5kqKNInfiohfMBjyULm1+hlIjTN9qK4dgvIpQ+jAdrHdJ + 1hOmItZ4LvGF6/P+oxq3+FSZSabfwBVuwGMS/YiUyxPWBO0UmqWThrs42i+IGU0hr5ygHkb6AGpPm/IdDi48TtM1PNBPl1QBBTaM2TAxb3Nn21a0EtB8 + bCmLAgnAjFC72YB4kp++HwTayJ6nuuim94VmuPuPOApEwIVhDECIpToF5/M/a7x30qtgAXC1zcHsCidkzzO+ekqt8lWPzgUvtNmDJGu2kUZajZOqGYvB + qT8mOyQpRBoISUFh+M5YEE2DMWyrRT4pjvvUA5lwrHeSDdPiZAZ2QaOWE124nRf+gJG8DG2zJqqmaP4TscPb+bs+v+08Qf0aNjZwpLG6DqsUy066WMmO + oygf/XlyxQFXJ6HxAsg0coD/tvE7rR9L0TiEkPjksPHxzocfxQK2tD22liMvemR2gO0qVjpIZAlIzq9iJ9J+sq5lWoaRHWsn+bLzpBiTulLE741zHJy4 + nCb8BAlCg+JOrh50ZQpjpHcoJ4X4NdLuQDOCp2utGnxxQI43cq+mLFYrLH9vetCktRtyKI5iDsGJMVkeIzUmh+IHunVcE1nTmiiTIkebbVGkGLyv+XI3 + x6QHRwQojjMer/bhoT8r129GYNMOAQzQm3FwpNLLBzrIj70Nm2cra3ntL/aMMPRI3CGlhMc405r3r1tbsfVcgqaNsWJsj2PcUDLFKnYw1cyEvUZ1yF4M + RsHgCaANCw9zYUQjxVnfeeXXc50hqOzAD/ZauzVUAdBRRpTB1EcDnI4RRFlukhx7fXvo2WUWy0vEdj1fMw+bJhFTRQI54Zd0OS9tE8fm5K4tOaQTtCJe + WMI4cYuVy+8+ZFrw1YGztRgq/umf7wBO3zGdAYqEhq3bg8ln8jipMD4wTmQcmkGL1R1QxeJSXQCNZePKwIMaGGpiKTIZrEHASkf9XcbTtcL09FLKjHzo + 2FW56NPJnWg1cTdGCHT9VBcT06Vd6zmy3qjGCS7fiSZs17lq4Uyc/lPYcx+0E1CJ4zMcAg9+jECV5cSUZ+HeTKpmbPOgL3rn9oWOGi0vXcJ8dtIfMRIn + ueHqOOzDb9FbpMFDZ1AZMbGuChxxZtjVM9iw08mc/WIF2qa9tVIkEKX8ZQDuIz8e+C1HTawEFYZ1XBBqMTy0BfYpZhT1W/UJe5rgUDNJXXBv5LdPWhZP + XFeomCC00O0bqioUVHqkzB7m0VtNHknEar2ks5pzigvo1Awd9dsgde1U4ZpRCIMAR7RD48rc8vTNSNF5LfvDSjJ7VZJREphog39ZmUhUuMl3ocTOXdNB + wRx1xp1gY0OOgBXku1DRFNkmJm+OX4gO9z3vee0A4lHPPzt+wDG8F8msLs+FGPDWnUP1TWVyHLWWIT1VZ/ArvEP5eM7j0XH9CYIl/4Z1ncX60JtuO+dY + oTANBgsqhkiG9w0BCRADHQIBIDALBglghkgBZQMEAS0EEAAAAAAAAAAAAAAAAAAAAAAwPAYJKoZIhvcNAQcBMB0GCWCGSAFlAwQBKgQQRxfJmVCr9TJ5 + HqocaVzdSoAQdyh9tjmrLR1M2m/QAzYr7w== + """; + + AssertInvalidDocument(Document, MLKemAlgorithm.MLKem768); + } + + [Fact] + public static void DecryptAesKeyWrapLengthNotAligned() + { + const string Document = """ + MIIFNwYJKoZIhvcNAQcDoIIFKDCCBSQCAQMxggTfpIIE2wYLKoZIhvcNAQkQDQMwggTKAgEAMDowIjENMAsGA1UEChMESUVURjERMA8GA1UEAxMITEFN + UFMgV0cCFBWf/m8i/VzELFJN9v1eKNDeOPNPMAsGCWCGSAFlAwQEAgSCBED8JbFiwSeYXPZ5kqKNInfiohfMBjyULm1+hlIjTN9qK4dgvIpQ+jAdrHdJ + 1hOmItZ4LvGF6/P+oxq3+FSZSabfwBVuwGMS/YiUyxPWBO0UmqWThrs42i+IGU0hr5ygHkb6AGpPm/IdDi48TtM1PNBPl1QBBTaM2TAxb3Nn21a0EtB8 + bCmLAgnAjFC72YB4kp++HwTayJ6nuuim94VmuPuPOApEwIVhDECIpToF5/M/a7x30qtgAXC1zcHsCidkzzO+ekqt8lWPzgUvtNmDJGu2kUZajZOqGYvB + qT8mOyQpRBoISUFh+M5YEE2DMWyrRT4pjvvUA5lwrHeSDdPiZAZ2QaOWE124nRf+gJG8DG2zJqqmaP4TscPb+bs+v+08Qf0aNjZwpLG6DqsUy066WMmO + oygf/XlyxQFXJ6HxAsg0coD/tvE7rR9L0TiEkPjksPHxzocfxQK2tD22liMvemR2gO0qVjpIZAlIzq9iJ9J+sq5lWoaRHWsn+bLzpBiTulLE741zHJy4 + nCb8BAlCg+JOrh50ZQpjpHcoJ4X4NdLuQDOCp2utGnxxQI43cq+mLFYrLH9vetCktRtyKI5iDsGJMVkeIzUmh+IHunVcE1nTmiiTIkebbVGkGLyv+XI3 + x6QHRwQojjMer/bhoT8r129GYNMOAQzQm3FwpNLLBzrIj70Nm2cra3ntL/aMMPRI3CGlhMc405r3r1tbsfVcgqaNsWJsj2PcUDLFKnYw1cyEvUZ1yF4M + RsHgCaANCw9zYUQjxVnfeeXXc50hqOzAD/ZauzVUAdBRRpTB1EcDnI4RRFlukhx7fXvo2WUWy0vEdj1fMw+bJhFTRQI54Zd0OS9tE8fm5K4tOaQTtCJe + WMI4cYuVy+8+ZFrw1YGztRgq/umf7wBO3zGdAYqEhq3bg8ln8jipMD4wTmQcmkGL1R1QxeJSXQCNZePKwIMaGGpiKTIZrEHASkf9XcbTtcL09FLKjHzo + 2FW56NPJnWg1cTdGCHT9VBcT06Vd6zmy3qjGCS7fiSZs17lq4Uyc/lPYcx+0E1CJ4zMcAg9+jECV5cSUZ+HeTKpmbPOgL3rn9oWOGi0vXcJ8dtIfMRIn + ueHqOOzDb9FbpMFDZ1AZMbGuChxxZtjVM9iw08mc/WIF2qa9tVIkEKX8ZQDuIz8e+C1HTawEFYZ1XBBqMTy0BfYpZhT1W/UJe5rgUDNJXXBv5LdPWhZP + XFeomCC00O0bqioUVHqkzB7m0VtNHknEar2ks5pzigvo1Awd9dsgde1U4ZpRCIMAR7RD48rc8vTNSNF5LfvDSjJ7VZJREphog39ZmUhUuMl3ocTOXdNB + wRx1xp1gY0OOgBXku1DRFNkmJm+OX4gO9z3vee0A4lHPPzt+wDG8F8msLs+FGPDWnUP1TWVyHLWWIT1VZ/ArvEP5eM7j0XH9CYIl/4Z1ncX60JtuO+dY + oTANBgsqhkiG9w0BCRADHQIBIDALBglghkgBZQMEAS0EGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwPAYJKoZIhvcNAQcBMB0GCWCGSAFlAwQBKgQQ + RxfJmVCr9TJ5HqocaVzdSoAQdyh9tjmrLR1M2m/QAzYr7w== + """; + + AssertInvalidDocument(Document, MLKemAlgorithm.MLKem768); + } + + [Fact] + public static void DecryptAesKeyWrapOidDoesNotMatchKekLength() + { + const string Document = """ + MIIFRgYJKoZIhvcNAQcDoIIFNzCCBTMCAQMxggTupIIE6gYLKoZIhvcNAQkQDQMwggTZAgEAMDowIjENMAsGA1UEChMESUVURjERMA8GA1UEAxMITEFN + UFMgV0cCFBWf/m8i/VzELFJN9v1eKNDeOPNPMAsGCWCGSAFlAwQEAgSCBED8JbFiwSeYXPZ5kqKNInfiohfMBjyULm1+hlIjTN9qK4dgvIpQ+jAdrHdJ + 1hOmItZ4LvGF6/P+oxq3+FSZSabfwBVuwGMS/YiUyxPWBO0UmqWThrs42i+IGU0hr5ygHkb6AGpPm/IdDi48TtM1PNBPl1QBBTaM2TAxb3Nn21a0EtB8 + bCmLAgnAjFC72YB4kp++HwTayJ6nuuim94VmuPuPOApEwIVhDECIpToF5/M/a7x30qtgAXC1zcHsCidkzzO+ekqt8lWPzgUvtNmDJGu2kUZajZOqGYvB + qT8mOyQpRBoISUFh+M5YEE2DMWyrRT4pjvvUA5lwrHeSDdPiZAZ2QaOWE124nRf+gJG8DG2zJqqmaP4TscPb+bs+v+08Qf0aNjZwpLG6DqsUy066WMmO + oygf/XlyxQFXJ6HxAsg0coD/tvE7rR9L0TiEkPjksPHxzocfxQK2tD22liMvemR2gO0qVjpIZAlIzq9iJ9J+sq5lWoaRHWsn+bLzpBiTulLE741zHJy4 + nCb8BAlCg+JOrh50ZQpjpHcoJ4X4NdLuQDOCp2utGnxxQI43cq+mLFYrLH9vetCktRtyKI5iDsGJMVkeIzUmh+IHunVcE1nTmiiTIkebbVGkGLyv+XI3 + x6QHRwQojjMer/bhoT8r129GYNMOAQzQm3FwpNLLBzrIj70Nm2cra3ntL/aMMPRI3CGlhMc405r3r1tbsfVcgqaNsWJsj2PcUDLFKnYw1cyEvUZ1yF4M + RsHgCaANCw9zYUQjxVnfeeXXc50hqOzAD/ZauzVUAdBRRpTB1EcDnI4RRFlukhx7fXvo2WUWy0vEdj1fMw+bJhFTRQI54Zd0OS9tE8fm5K4tOaQTtCJe + WMI4cYuVy+8+ZFrw1YGztRgq/umf7wBO3zGdAYqEhq3bg8ln8jipMD4wTmQcmkGL1R1QxeJSXQCNZePKwIMaGGpiKTIZrEHASkf9XcbTtcL09FLKjHzo + 2FW56NPJnWg1cTdGCHT9VBcT06Vd6zmy3qjGCS7fiSZs17lq4Uyc/lPYcx+0E1CJ4zMcAg9+jECV5cSUZ+HeTKpmbPOgL3rn9oWOGi0vXcJ8dtIfMRIn + ueHqOOzDb9FbpMFDZ1AZMbGuChxxZtjVM9iw08mc/WIF2qa9tVIkEKX8ZQDuIz8e+C1HTawEFYZ1XBBqMTy0BfYpZhT1W/UJe5rgUDNJXXBv5LdPWhZP + XFeomCC00O0bqioUVHqkzB7m0VtNHknEar2ks5pzigvo1Awd9dsgde1U4ZpRCIMAR7RD48rc8vTNSNF5LfvDSjJ7VZJREphog39ZmUhUuMl3ocTOXdNB + wRx1xp1gY0OOgBXku1DRFNkmJm+OX4gO9z3vee0A4lHPPzt+wDG8F8msLs+FGPDWnUP1TWVyHLWWIT1VZ/ArvEP5eM7j0XH9CYIl/4Z1ncX60JtuO+dY + oTANBgsqhkiG9w0BCRADHQIBIDALBglghkgBZQMEAQUEKBN/DHlGK/WvNzQ4kolGSwZMU/mRipHkscRAfKSA/zSY3tJMuCsyUE8wPAYJKoZIhvcNAQcB + MB0GCWCGSAFlAwQBKgQQRxfJmVCr9TJ5HqocaVzdSoAQdyh9tjmrLR1M2m/QAzYr7w== + """; + + AssertInvalidDocument(Document, MLKemAlgorithm.MLKem768); + } + + [Fact] + public static void DecryptUnknownKdf() + { + const string Document = """ + MIIFPgYJKoZIhvcNAQcDoIIFLzCCBSsCAQMxggTmpIIE4gYLKoZIhvcNAQkQDQMwggTRAgEAMDowIjENMAsGA1UEChMESUVURjERMA8GA1UEAxMITEFN + UFMgV0cCFBWf/m8i/VzELFJN9v1eKNDeOPNPMAsGCWCGSAFlAwQEAgSCBED8JbFiwSeYXPZ5kqKNInfiohfMBjyULm1+hlIjTN9qK4dgvIpQ+jAdrHdJ + 1hOmItZ4LvGF6/P+oxq3+FSZSabfwBVuwGMS/YiUyxPWBO0UmqWThrs42i+IGU0hr5ygHkb6AGpPm/IdDi48TtM1PNBPl1QBBTaM2TAxb3Nn21a0EtB8 + bCmLAgnAjFC72YB4kp++HwTayJ6nuuim94VmuPuPOApEwIVhDECIpToF5/M/a7x30qtgAXC1zcHsCidkzzO+ekqt8lWPzgUvtNmDJGu2kUZajZOqGYvB + qT8mOyQpRBoISUFh+M5YEE2DMWyrRT4pjvvUA5lwrHeSDdPiZAZ2QaOWE124nRf+gJG8DG2zJqqmaP4TscPb+bs+v+08Qf0aNjZwpLG6DqsUy066WMmO + oygf/XlyxQFXJ6HxAsg0coD/tvE7rR9L0TiEkPjksPHxzocfxQK2tD22liMvemR2gO0qVjpIZAlIzq9iJ9J+sq5lWoaRHWsn+bLzpBiTulLE741zHJy4 + nCb8BAlCg+JOrh50ZQpjpHcoJ4X4NdLuQDOCp2utGnxxQI43cq+mLFYrLH9vetCktRtyKI5iDsGJMVkeIzUmh+IHunVcE1nTmiiTIkebbVGkGLyv+XI3 + x6QHRwQojjMer/bhoT8r129GYNMOAQzQm3FwpNLLBzrIj70Nm2cra3ntL/aMMPRI3CGlhMc405r3r1tbsfVcgqaNsWJsj2PcUDLFKnYw1cyEvUZ1yF4M + RsHgCaANCw9zYUQjxVnfeeXXc50hqOzAD/ZauzVUAdBRRpTB1EcDnI4RRFlukhx7fXvo2WUWy0vEdj1fMw+bJhFTRQI54Zd0OS9tE8fm5K4tOaQTtCJe + WMI4cYuVy+8+ZFrw1YGztRgq/umf7wBO3zGdAYqEhq3bg8ln8jipMD4wTmQcmkGL1R1QxeJSXQCNZePKwIMaGGpiKTIZrEHASkf9XcbTtcL09FLKjHzo + 2FW56NPJnWg1cTdGCHT9VBcT06Vd6zmy3qjGCS7fiSZs17lq4Uyc/lPYcx+0E1CJ4zMcAg9+jECV5cSUZ+HeTKpmbPOgL3rn9oWOGi0vXcJ8dtIfMRIn + ueHqOOzDb9FbpMFDZ1AZMbGuChxxZtjVM9iw08mc/WIF2qa9tVIkEKX8ZQDuIz8e+C1HTawEFYZ1XBBqMTy0BfYpZhT1W/UJe5rgUDNJXXBv5LdPWhZP + XFeomCC00O0bqioUVHqkzB7m0VtNHknEar2ks5pzigvo1Awd9dsgde1U4ZpRCIMAR7RD48rc8vTNSNF5LfvDSjJ7VZJREphog39ZmUhUuMl3ocTOXdNB + wRx1xp1gY0OOgBXku1DRFNkmJm+OX4gO9z3vee0A4lHPPzt+wDG8F8msLs+FGPDWnUP1TWVyHLWWIT1VZ/ArvEP5eM7j0XH9CYIl/4Z1ncX60JtuO+dY + oTAFBgMqAwQCASAwCwYJYIZIAWUDBAEtBCgTfwx5Riv1rzc0OJKJRksGTFP5kYqR5LHEQHykgP80mN7STLgrMlBPMDwGCSqGSIb3DQEHATAdBglghkgB + ZQMEASoEEEcXyZlQq/UyeR6qHGlc3UqAEHcofbY5qy0dTNpv0AM2K+8= + """; + + AssertInvalidDocument(Document, MLKemAlgorithm.MLKem768); + } + + [Fact] + public static void DecryptUnknownKem() + { + const string Document = """ + MIIFQAYJKoZIhvcNAQcDoIIFMTCCBS0CAQMxggTopIIE5AYLKoZIhvcNAQkQDQMwggTTAgEAMDowIjENMAsGA1UEChMESUVURjERMA8GA1UEAxMITEFN + UFMgV0cCFBWf/m8i/VzELFJN9v1eKNDeOPNPMAUGAyoDBQSCBED8JbFiwSeYXPZ5kqKNInfiohfMBjyULm1+hlIjTN9qK4dgvIpQ+jAdrHdJ1hOmItZ4 + LvGF6/P+oxq3+FSZSabfwBVuwGMS/YiUyxPWBO0UmqWThrs42i+IGU0hr5ygHkb6AGpPm/IdDi48TtM1PNBPl1QBBTaM2TAxb3Nn21a0EtB8bCmLAgnA + jFC72YB4kp++HwTayJ6nuuim94VmuPuPOApEwIVhDECIpToF5/M/a7x30qtgAXC1zcHsCidkzzO+ekqt8lWPzgUvtNmDJGu2kUZajZOqGYvBqT8mOyQp + RBoISUFh+M5YEE2DMWyrRT4pjvvUA5lwrHeSDdPiZAZ2QaOWE124nRf+gJG8DG2zJqqmaP4TscPb+bs+v+08Qf0aNjZwpLG6DqsUy066WMmOoygf/Xly + xQFXJ6HxAsg0coD/tvE7rR9L0TiEkPjksPHxzocfxQK2tD22liMvemR2gO0qVjpIZAlIzq9iJ9J+sq5lWoaRHWsn+bLzpBiTulLE741zHJy4nCb8BAlC + g+JOrh50ZQpjpHcoJ4X4NdLuQDOCp2utGnxxQI43cq+mLFYrLH9vetCktRtyKI5iDsGJMVkeIzUmh+IHunVcE1nTmiiTIkebbVGkGLyv+XI3x6QHRwQo + jjMer/bhoT8r129GYNMOAQzQm3FwpNLLBzrIj70Nm2cra3ntL/aMMPRI3CGlhMc405r3r1tbsfVcgqaNsWJsj2PcUDLFKnYw1cyEvUZ1yF4MRsHgCaAN + Cw9zYUQjxVnfeeXXc50hqOzAD/ZauzVUAdBRRpTB1EcDnI4RRFlukhx7fXvo2WUWy0vEdj1fMw+bJhFTRQI54Zd0OS9tE8fm5K4tOaQTtCJeWMI4cYuV + y+8+ZFrw1YGztRgq/umf7wBO3zGdAYqEhq3bg8ln8jipMD4wTmQcmkGL1R1QxeJSXQCNZePKwIMaGGpiKTIZrEHASkf9XcbTtcL09FLKjHzo2FW56NPJ + nWg1cTdGCHT9VBcT06Vd6zmy3qjGCS7fiSZs17lq4Uyc/lPYcx+0E1CJ4zMcAg9+jECV5cSUZ+HeTKpmbPOgL3rn9oWOGi0vXcJ8dtIfMRInueHqOOzD + b9FbpMFDZ1AZMbGuChxxZtjVM9iw08mc/WIF2qa9tVIkEKX8ZQDuIz8e+C1HTawEFYZ1XBBqMTy0BfYpZhT1W/UJe5rgUDNJXXBv5LdPWhZPXFeomCC0 + 0O0bqioUVHqkzB7m0VtNHknEar2ks5pzigvo1Awd9dsgde1U4ZpRCIMAR7RD48rc8vTNSNF5LfvDSjJ7VZJREphog39ZmUhUuMl3ocTOXdNBwRx1xp1g + Y0OOgBXku1DRFNkmJm+OX4gO9z3vee0A4lHPPzt+wDG8F8msLs+FGPDWnUP1TWVyHLWWIT1VZ/ArvEP5eM7j0XH9CYIl/4Z1ncX60JtuO+dYoTANBgsq + hkiG9w0BCRADHQIBIDALBglghkgBZQMEAS0EKBN/DHlGK/WvNzQ4kolGSwZMU/mRipHkscRAfKSA/zSY3tJMuCsyUE8wPAYJKoZIhvcNAQcBMB0GCWCG + SAFlAwQBKgQQRxfJmVCr9TJ5HqocaVzdSoAQdyh9tjmrLR1M2m/QAzYr7w== + """; + + AssertInvalidDocument(Document, MLKemAlgorithm.MLKem768); + } + + [Fact] + public static void DecryptKemAlgorithmParameters() + { + const string Document = """ + MIIFSAYJKoZIhvcNAQcDoIIFOTCCBTUCAQMxggTwpIIE7AYLKoZIhvcNAQkQDQMwggTbAgEAMDowIjENMAsGA1UEChMESUVURjERMA8GA1UEAxMITEFN + UFMgV0cCFBWf/m8i/VzELFJN9v1eKNDeOPNPMA0GCWCGSAFlAwQEAgUABIIEQPwlsWLBJ5hc9nmSoo0id+KiF8wGPJQubX6GUiNM32orh2C8ilD6MB2s + d0nWE6Yi1ngu8YXr8/6jGrf4VJlJpt/AFW7AYxL9iJTLE9YE7RSapZOGuzjaL4gZTSGvnKAeRvoAak+b8h0OLjxO0zU80E+XVAEFNozZMDFvc2fbVrQS + 0HxsKYsCCcCMULvZgHiSn74fBNrInqe66Kb3hWa4+484CkTAhWEMQIilOgXn8z9rvHfSq2ABcLXNwewKJ2TPM756Sq3yVY/OBS+02YMka7aRRlqNk6oZ + i8GpPyY7JClEGghJQWH4zlgQTYMxbKtFPimO+9QDmXCsd5IN0+JkBnZBo5YTXbidF/6AkbwMbbMmqqZo/hOxw9v5uz6/7TxB/Ro2NnCksboOqxTLTrpY + yY6jKB/9eXLFAVcnofECyDRygP+28TutH0vROISQ+OSw8fHOhx/FAra0PbaWIy96ZHaA7SpWOkhkCUjOr2In0n6yrmVahpEdayf5svOkGJO6UsTvjXMc + nLicJvwECUKD4k6uHnRlCmOkdygnhfg10u5AM4Kna60afHFAjjdyr6YsVissf2960KS1G3IojmIOwYkxWR4jNSaH4ge6dVwTWdOaKJMiR5ttUaQYvK/5 + cjfHpAdHBCiOMx6v9uGhPyvXb0Zg0w4BDNCbcXCk0ssHOsiPvQ2bZytree0v9oww9EjcIaWExzjTmvevW1ux9VyCpo2xYmyPY9xQMsUqdjDVzIS9RnXI + XgxGweAJoA0LD3NhRCPFWd955ddznSGo7MAP9lq7NVQB0FFGlMHURwOcjhFEWW6SHHt9e+jZZRbLS8R2PV8zD5smEVNFAjnhl3Q5L20Tx+bkri05pBO0 + Il5Ywjhxi5XL7z5kWvDVgbO1GCr+6Z/vAE7fMZ0BioSGrduDyWfyOKkwPjBOZByaQYvVHVDF4lJdAI1l48rAgxoYamIpMhmsQcBKR/1dxtO1wvT0UsqM + fOjYVbno08mdaDVxN0YIdP1UFxPTpV3rObLeqMYJLt+JJmzXuWrhTJz+U9hzH7QTUInjMxwCD36MQJXlxJRn4d5MqmZs86Aveuf2hY4aLS9dwnx20h8x + Eie54eo47MNv0VukwUNnUBkxsa4KHHFm2NUz2LDTyZz9YgXapr21UiQQpfxlAO4jPx74LUdNrAQVhnVcEGoxPLQF9ilmFPVb9Ql7muBQM0ldcG/kt09a + Fk9cV6iYILTQ7RuqKhRUeqTMHubRW00eScRqvaSzmnOKC+jUDB312yB17VThmlEIgwBHtEPjytzy9M1I0Xkt+8NKMntVklESmGiDf1mZSFS4yXehxM5d + 00HBHHXGnWBjQ46AFeS7UNEU2SYmb45fiA73Pe957QDiUc8/O37AMbwXyawuz4UY8NadQ/VNZXIctZYhPVVn8Cu8Q/l4zuPRcf0JgiX/hnWdxfrQm247 + 51ihMA0GCyqGSIb3DQEJEAMdAgEgMAsGCWCGSAFlAwQBLQQoE38MeUYr9a83NDiSiUZLBkxT+ZGKkeSxxEB8pID/NJje0ky4KzJQTzA8BgkqhkiG9w0B + BwEwHQYJYIZIAWUDBAEqBBBHF8mZUKv1MnkeqhxpXN1KgBB3KH22OastHUzab9ADNivv + """; + + KemRecipientInfo recipientInfo = AssertInvalidDocument(Document, MLKemAlgorithm.MLKem768); + Assert.NotEmpty(recipientInfo.KeyEncapsulationAlgorithm.Parameters); + } + + [Fact] + public static void DecryptKdfAlgorithmParameters() + { + const string Document = """ + MIIFSAYJKoZIhvcNAQcDoIIFOTCCBTUCAQMxggTwpIIE7AYLKoZIhvcNAQkQDQMwggTbAgEAMDowIjENMAsGA1UEChMESUVURjERMA8GA1UEAxMITEFN + UFMgV0cCFBWf/m8i/VzELFJN9v1eKNDeOPNPMAsGCWCGSAFlAwQEAgSCBED8JbFiwSeYXPZ5kqKNInfiohfMBjyULm1+hlIjTN9qK4dgvIpQ+jAdrHdJ + 1hOmItZ4LvGF6/P+oxq3+FSZSabfwBVuwGMS/YiUyxPWBO0UmqWThrs42i+IGU0hr5ygHkb6AGpPm/IdDi48TtM1PNBPl1QBBTaM2TAxb3Nn21a0EtB8 + bCmLAgnAjFC72YB4kp++HwTayJ6nuuim94VmuPuPOApEwIVhDECIpToF5/M/a7x30qtgAXC1zcHsCidkzzO+ekqt8lWPzgUvtNmDJGu2kUZajZOqGYvB + qT8mOyQpRBoISUFh+M5YEE2DMWyrRT4pjvvUA5lwrHeSDdPiZAZ2QaOWE124nRf+gJG8DG2zJqqmaP4TscPb+bs+v+08Qf0aNjZwpLG6DqsUy066WMmO + oygf/XlyxQFXJ6HxAsg0coD/tvE7rR9L0TiEkPjksPHxzocfxQK2tD22liMvemR2gO0qVjpIZAlIzq9iJ9J+sq5lWoaRHWsn+bLzpBiTulLE741zHJy4 + nCb8BAlCg+JOrh50ZQpjpHcoJ4X4NdLuQDOCp2utGnxxQI43cq+mLFYrLH9vetCktRtyKI5iDsGJMVkeIzUmh+IHunVcE1nTmiiTIkebbVGkGLyv+XI3 + x6QHRwQojjMer/bhoT8r129GYNMOAQzQm3FwpNLLBzrIj70Nm2cra3ntL/aMMPRI3CGlhMc405r3r1tbsfVcgqaNsWJsj2PcUDLFKnYw1cyEvUZ1yF4M + RsHgCaANCw9zYUQjxVnfeeXXc50hqOzAD/ZauzVUAdBRRpTB1EcDnI4RRFlukhx7fXvo2WUWy0vEdj1fMw+bJhFTRQI54Zd0OS9tE8fm5K4tOaQTtCJe + WMI4cYuVy+8+ZFrw1YGztRgq/umf7wBO3zGdAYqEhq3bg8ln8jipMD4wTmQcmkGL1R1QxeJSXQCNZePKwIMaGGpiKTIZrEHASkf9XcbTtcL09FLKjHzo + 2FW56NPJnWg1cTdGCHT9VBcT06Vd6zmy3qjGCS7fiSZs17lq4Uyc/lPYcx+0E1CJ4zMcAg9+jECV5cSUZ+HeTKpmbPOgL3rn9oWOGi0vXcJ8dtIfMRIn + ueHqOOzDb9FbpMFDZ1AZMbGuChxxZtjVM9iw08mc/WIF2qa9tVIkEKX8ZQDuIz8e+C1HTawEFYZ1XBBqMTy0BfYpZhT1W/UJe5rgUDNJXXBv5LdPWhZP + XFeomCC00O0bqioUVHqkzB7m0VtNHknEar2ks5pzigvo1Awd9dsgde1U4ZpRCIMAR7RD48rc8vTNSNF5LfvDSjJ7VZJREphog39ZmUhUuMl3ocTOXdNB + wRx1xp1gY0OOgBXku1DRFNkmJm+OX4gO9z3vee0A4lHPPzt+wDG8F8msLs+FGPDWnUP1TWVyHLWWIT1VZ/ArvEP5eM7j0XH9CYIl/4Z1ncX60JtuO+dY + oTAPBgsqhkiG9w0BCRADHQUAAgEgMAsGCWCGSAFlAwQBLQQoE38MeUYr9a83NDiSiUZLBkxT+ZGKkeSxxEB8pID/NJje0ky4KzJQTzA8BgkqhkiG9w0B + BwEwHQYJYIZIAWUDBAEqBBBHF8mZUKv1MnkeqhxpXN1KgBB3KH22OastHUzab9ADNivv + """; + + KemRecipientInfo recipientInfo = AssertInvalidDocument(Document, MLKemAlgorithm.MLKem768); + Assert.NotEmpty(recipientInfo.KeyDerivationAlgorithm.Parameters); + } + + [Fact] + public static void DecryptAesKeyWrapAlgorithmParameters() + { + const string Document = """ + MIIFSAYJKoZIhvcNAQcDoIIFOTCCBTUCAQMxggTwpIIE7AYLKoZIhvcNAQkQDQMwggTbAgEAMDowIjENMAsGA1UEChMESUVURjERMA8GA1UEAxMITEFN + UFMgV0cCFBWf/m8i/VzELFJN9v1eKNDeOPNPMAsGCWCGSAFlAwQEAgSCBED8JbFiwSeYXPZ5kqKNInfiohfMBjyULm1+hlIjTN9qK4dgvIpQ+jAdrHdJ + 1hOmItZ4LvGF6/P+oxq3+FSZSabfwBVuwGMS/YiUyxPWBO0UmqWThrs42i+IGU0hr5ygHkb6AGpPm/IdDi48TtM1PNBPl1QBBTaM2TAxb3Nn21a0EtB8 + bCmLAgnAjFC72YB4kp++HwTayJ6nuuim94VmuPuPOApEwIVhDECIpToF5/M/a7x30qtgAXC1zcHsCidkzzO+ekqt8lWPzgUvtNmDJGu2kUZajZOqGYvB + qT8mOyQpRBoISUFh+M5YEE2DMWyrRT4pjvvUA5lwrHeSDdPiZAZ2QaOWE124nRf+gJG8DG2zJqqmaP4TscPb+bs+v+08Qf0aNjZwpLG6DqsUy066WMmO + oygf/XlyxQFXJ6HxAsg0coD/tvE7rR9L0TiEkPjksPHxzocfxQK2tD22liMvemR2gO0qVjpIZAlIzq9iJ9J+sq5lWoaRHWsn+bLzpBiTulLE741zHJy4 + nCb8BAlCg+JOrh50ZQpjpHcoJ4X4NdLuQDOCp2utGnxxQI43cq+mLFYrLH9vetCktRtyKI5iDsGJMVkeIzUmh+IHunVcE1nTmiiTIkebbVGkGLyv+XI3 + x6QHRwQojjMer/bhoT8r129GYNMOAQzQm3FwpNLLBzrIj70Nm2cra3ntL/aMMPRI3CGlhMc405r3r1tbsfVcgqaNsWJsj2PcUDLFKnYw1cyEvUZ1yF4M + RsHgCaANCw9zYUQjxVnfeeXXc50hqOzAD/ZauzVUAdBRRpTB1EcDnI4RRFlukhx7fXvo2WUWy0vEdj1fMw+bJhFTRQI54Zd0OS9tE8fm5K4tOaQTtCJe + WMI4cYuVy+8+ZFrw1YGztRgq/umf7wBO3zGdAYqEhq3bg8ln8jipMD4wTmQcmkGL1R1QxeJSXQCNZePKwIMaGGpiKTIZrEHASkf9XcbTtcL09FLKjHzo + 2FW56NPJnWg1cTdGCHT9VBcT06Vd6zmy3qjGCS7fiSZs17lq4Uyc/lPYcx+0E1CJ4zMcAg9+jECV5cSUZ+HeTKpmbPOgL3rn9oWOGi0vXcJ8dtIfMRIn + ueHqOOzDb9FbpMFDZ1AZMbGuChxxZtjVM9iw08mc/WIF2qa9tVIkEKX8ZQDuIz8e+C1HTawEFYZ1XBBqMTy0BfYpZhT1W/UJe5rgUDNJXXBv5LdPWhZP + XFeomCC00O0bqioUVHqkzB7m0VtNHknEar2ks5pzigvo1Awd9dsgde1U4ZpRCIMAR7RD48rc8vTNSNF5LfvDSjJ7VZJREphog39ZmUhUuMl3ocTOXdNB + wRx1xp1gY0OOgBXku1DRFNkmJm+OX4gO9z3vee0A4lHPPzt+wDG8F8msLs+FGPDWnUP1TWVyHLWWIT1VZ/ArvEP5eM7j0XH9CYIl/4Z1ncX60JtuO+dY + oTANBgsqhkiG9w0BCRADHQIBIDANBglghkgBZQMEAS0FAAQoE38MeUYr9a83NDiSiUZLBkxT+ZGKkeSxxEB8pID/NJje0ky4KzJQTzA8BgkqhkiG9w0B + BwEwHQYJYIZIAWUDBAEqBBBHF8mZUKv1MnkeqhxpXN1KgBB3KH22OastHUzab9ADNivv + """; + + KemRecipientInfo recipientInfo = AssertInvalidDocument(Document, MLKemAlgorithm.MLKem768); + Assert.NotEmpty(recipientInfo.KeyEncryptionAlgorithm.Parameters); + } + + [Fact] + public static void DecryptUnknownAesKeyWrap() + { + const string Document = """ + MIIFQAYJKoZIhvcNAQcDoIIFMTCCBS0CAQMxggTopIIE5AYLKoZIhvcNAQkQDQMwggTTAgEAMDowIjENMAsGA1UEChMESUVURjERMA8GA1UEAxMITEFN + UFMgV0cCFBWf/m8i/VzELFJN9v1eKNDeOPNPMAsGCWCGSAFlAwQEAgSCBED8JbFiwSeYXPZ5kqKNInfiohfMBjyULm1+hlIjTN9qK4dgvIpQ+jAdrHdJ + 1hOmItZ4LvGF6/P+oxq3+FSZSabfwBVuwGMS/YiUyxPWBO0UmqWThrs42i+IGU0hr5ygHkb6AGpPm/IdDi48TtM1PNBPl1QBBTaM2TAxb3Nn21a0EtB8 + bCmLAgnAjFC72YB4kp++HwTayJ6nuuim94VmuPuPOApEwIVhDECIpToF5/M/a7x30qtgAXC1zcHsCidkzzO+ekqt8lWPzgUvtNmDJGu2kUZajZOqGYvB + qT8mOyQpRBoISUFh+M5YEE2DMWyrRT4pjvvUA5lwrHeSDdPiZAZ2QaOWE124nRf+gJG8DG2zJqqmaP4TscPb+bs+v+08Qf0aNjZwpLG6DqsUy066WMmO + oygf/XlyxQFXJ6HxAsg0coD/tvE7rR9L0TiEkPjksPHxzocfxQK2tD22liMvemR2gO0qVjpIZAlIzq9iJ9J+sq5lWoaRHWsn+bLzpBiTulLE741zHJy4 + nCb8BAlCg+JOrh50ZQpjpHcoJ4X4NdLuQDOCp2utGnxxQI43cq+mLFYrLH9vetCktRtyKI5iDsGJMVkeIzUmh+IHunVcE1nTmiiTIkebbVGkGLyv+XI3 + x6QHRwQojjMer/bhoT8r129GYNMOAQzQm3FwpNLLBzrIj70Nm2cra3ntL/aMMPRI3CGlhMc405r3r1tbsfVcgqaNsWJsj2PcUDLFKnYw1cyEvUZ1yF4M + RsHgCaANCw9zYUQjxVnfeeXXc50hqOzAD/ZauzVUAdBRRpTB1EcDnI4RRFlukhx7fXvo2WUWy0vEdj1fMw+bJhFTRQI54Zd0OS9tE8fm5K4tOaQTtCJe + WMI4cYuVy+8+ZFrw1YGztRgq/umf7wBO3zGdAYqEhq3bg8ln8jipMD4wTmQcmkGL1R1QxeJSXQCNZePKwIMaGGpiKTIZrEHASkf9XcbTtcL09FLKjHzo + 2FW56NPJnWg1cTdGCHT9VBcT06Vd6zmy3qjGCS7fiSZs17lq4Uyc/lPYcx+0E1CJ4zMcAg9+jECV5cSUZ+HeTKpmbPOgL3rn9oWOGi0vXcJ8dtIfMRIn + ueHqOOzDb9FbpMFDZ1AZMbGuChxxZtjVM9iw08mc/WIF2qa9tVIkEKX8ZQDuIz8e+C1HTawEFYZ1XBBqMTy0BfYpZhT1W/UJe5rgUDNJXXBv5LdPWhZP + XFeomCC00O0bqioUVHqkzB7m0VtNHknEar2ks5pzigvo1Awd9dsgde1U4ZpRCIMAR7RD48rc8vTNSNF5LfvDSjJ7VZJREphog39ZmUhUuMl3ocTOXdNB + wRx1xp1gY0OOgBXku1DRFNkmJm+OX4gO9z3vee0A4lHPPzt+wDG8F8msLs+FGPDWnUP1TWVyHLWWIT1VZ/ArvEP5eM7j0XH9CYIl/4Z1ncX60JtuO+dY + oTANBgsqhkiG9w0BCRADHQIBIDAFBgMqAwYEKBN/DHlGK/WvNzQ4kolGSwZMU/mRipHkscRAfKSA/zSY3tJMuCsyUE8wPAYJKoZIhvcNAQcBMB0GCWCG + SAFlAwQBKgQQRxfJmVCr9TJ5HqocaVzdSoAQdyh9tjmrLR1M2m/QAzYr7w== + """; + + AssertInvalidDocument(Document, MLKemAlgorithm.MLKem768); + } + + [Fact] + public static void DecryptKemAlgorithmDoesNotMatchPrivateKeyAlgorithm() + { + AssertInvalidDocument(KemTestDocuments.MlKem768, MLKemAlgorithm.MLKem512); + } + + private static KemRecipientInfo AssertInvalidDocument(string document, MLKemAlgorithm algorithm) => + AssertInvalidDocument(Convert.FromBase64String(document), algorithm); + + private static KemRecipientInfo AssertInvalidDocument(byte[] document, MLKemAlgorithm algorithm) + { + EnvelopedCms cms = new EnvelopedCms(); + cms.Decode(document); + + KemRecipientInfo recipientInfo = Assert.IsType(Assert.Single(cms.RecipientInfos)); + + using (ValidationMLKem privateKey = new ValidationMLKem(algorithm)) + { + Assert.Throws(() => cms.Decrypt(recipientInfo, privateKey)); + } + + return recipientInfo; + } + + private static EnvelopedCms Decrypt(byte[] encodedMessage, byte[] privateKey) + { + EnvelopedCms cms = new EnvelopedCms(); + cms.Decode(encodedMessage); + + KemRecipientInfo recipientInfo = Assert.IsType(Assert.Single(cms.RecipientInfos)); + + using (MLKem mlKem = MLKem.ImportPkcs8PrivateKey(privateKey)) + { + cms.Decrypt(recipientInfo, mlKem); + } + + Assert.Equal("hello world!"u8.ToArray(), cms.ContentInfo.Content); + return cms; + } + + private sealed class ValidationMLKem : MLKem + { + internal ValidationMLKem(MLKemAlgorithm algorithm) + : base(algorithm) + { + } + + protected override void DecapsulateCore(ReadOnlySpan ciphertext, Span sharedSecret) => + Assert.Fail("Decapsulation should not be attempted."); + + protected override void Dispose(bool disposing) + { + } + + protected override void EncapsulateCore(Span ciphertext, Span sharedSecret) => + throw new NotSupportedException(); + + protected override void ExportDecapsulationKeyCore(Span destination) => + throw new NotSupportedException(); + + protected override void ExportEncapsulationKeyCore(Span destination) => + throw new NotSupportedException(); + + protected override void ExportPrivateSeedCore(Span destination) => + throw new NotSupportedException(); + + protected override bool TryExportPkcs8PrivateKeyCore(Span destination, out int bytesWritten) => + throw new NotSupportedException(); + } + + private sealed class TestCompositeMLKem : CompositeMLKem + { + internal TestCompositeMLKem(CompositeMLKemAlgorithm algorithm) + : base(algorithm) + { + } + + protected override void DecapsulateCore(ReadOnlySpan ciphertext, Span sharedSecret) => + throw new NotSupportedException(); + + protected override void Dispose(bool disposing) + { + } + + protected override void EncapsulateCore(Span ciphertext, Span sharedSecret) => + throw new NotSupportedException(); + + protected override int ExportDecapsulationKeyCore(Span destination) => + throw new NotSupportedException(); + + protected override int ExportEncapsulationKeyCore(Span destination) => + throw new NotSupportedException(); + + protected override bool TryExportPkcs8PrivateKeyCore(Span destination, out int bytesWritten) => + throw new NotSupportedException(); + } + } + + [PlatformSpecific(~TestPlatforms.Windows)] + public static class KemCustomImplementationTests + { + [Fact] + public static void Decrypt_CustomMLKemInstance() + { + // Even if MLKem.IsSupported returns false, a custom implementation of ML-KEM should work. + EnvelopedCms cms = new EnvelopedCms(); + cms.Decode(KemTestDocuments.MlKem768); + + KemRecipientInfo recipientInfo = Assert.IsType(Assert.Single(cms.RecipientInfos)); + + using (MockMLKem privateKey = new MockMLKem()) + { + cms.Decrypt(recipientInfo, privateKey); + } + + Assert.Equal("hello world!"u8.ToArray(), cms.ContentInfo.Content); + } + + private sealed class MockMLKem : MLKem + { + private static readonly byte[] s_expectedCiphertext = Convert.FromBase64String( + """ + /CWxYsEnmFz2eZKijSJ34qIXzAY8lC5tfoZSI0zfaiuHYLyKUPowHax3SdYTpiLWeC7xhevz/qMat/hUmUmm38AVbsBjEv2IlMsT + 1gTtFJqlk4a7ONoviBlNIa+coB5G+gBqT5vyHQ4uPE7TNTzQT5dUAQU2jNkwMW9zZ9tWtBLQfGwpiwIJwIxQu9mAeJKfvh8E2sie + p7ropveFZrj7jzgKRMCFYQxAiKU6BefzP2u8d9KrYAFwtc3B7AonZM8zvnpKrfJVj84FL7TZgyRrtpFGWo2TqhmLwak/JjskKUQa + CElBYfjOWBBNgzFsq0U+KY771AOZcKx3kg3T4mQGdkGjlhNduJ0X/oCRvAxtsyaqpmj+E7HD2/m7Pr/tPEH9GjY2cKSxug6rFMtO + uljJjqMoH/15csUBVyeh8QLINHKA/7bxO60fS9E4hJD45LDx8c6HH8UCtrQ9tpYjL3pkdoDtKlY6SGQJSM6vYifSfrKuZVqGkR1r + J/my86QYk7pSxO+NcxycuJwm/AQJQoPiTq4edGUKY6R3KCeF+DXS7kAzgqdrrRp8cUCON3KvpixWKyx/b3rQpLUbciiOYg7BiTFZ + HiM1JofiB7p1XBNZ05ookyJHm21RpBi8r/lyN8ekB0cEKI4zHq/24aE/K9dvRmDTDgEM0JtxcKTSywc6yI+9DZtnK2t57S/2jDD0 + SNwhpYTHONOa969bW7H1XIKmjbFibI9j3FAyxSp2MNXMhL1GdcheDEbB4AmgDQsPc2FEI8VZ33nl13OdIajswA/2Wrs1VAHQUUaU + wdRHA5yOEURZbpIce3176NllFstLxHY9XzMPmyYRU0UCOeGXdDkvbRPH5uSuLTmkE7QiXljCOHGLlcvvPmRa8NWBs7UYKv7pn+8A + Tt8xnQGKhIat24PJZ/I4qTA+ME5kHJpBi9UdUMXiUl0AjWXjysCDGhhqYikyGaxBwEpH/V3G07XC9PRSyox86NhVuejTyZ1oNXE3 + Rgh0/VQXE9OlXes5st6oxgku34kmbNe5auFMnP5T2HMftBNQieMzHAIPfoxAleXElGfh3kyqZmzzoC965/aFjhotL13CfHbSHzES + J7nh6jjsw2/RW6TBQ2dQGTGxrgoccWbY1TPYsNPJnP1iBdqmvbVSJBCl/GUA7iM/HvgtR02sBBWGdVwQajE8tAX2KWYU9Vv1CXua + 4FAzSV1wb+S3T1oWT1xXqJggtNDtG6oqFFR6pMwe5tFbTR5JxGq9pLOac4oL6NQMHfXbIHXtVOGaUQiDAEe0Q+PK3PL0zUjReS37 + w0oye1WSURKYaIN/WZlIVLjJd6HEzl3TQcEcdcadYGNDjoAV5LtQ0RTZJiZvjl+IDvc973ntAOJRzz87fsAxvBfJrC7PhRjw1p1D + 9U1lchy1liE9VWfwK7xD+XjO49Fx/QmCJf+GdZ3F+tCbbjvnWKE= + """); + + private static readonly byte[] s_sharedSecret = + Convert.FromBase64String("pTct2UThy1KN636LnF86ahCWlweRAKmeYDORxVk4NFs="); + + internal MockMLKem() + : base(MLKemAlgorithm.MLKem768) + { + } + + protected override void DecapsulateCore(ReadOnlySpan ciphertext, Span sharedSecret) + { + Assert.Equal(s_expectedCiphertext, ciphertext.ToArray()); + s_sharedSecret.CopyTo(sharedSecret); + } + + protected override void Dispose(bool disposing) + { + } + + protected override void EncapsulateCore(Span ciphertext, Span sharedSecret) => + throw new NotSupportedException(); + + protected override void ExportDecapsulationKeyCore(Span destination) => + throw new NotSupportedException(); + + protected override void ExportEncapsulationKeyCore(Span destination) => + throw new NotSupportedException(); + + protected override void ExportPrivateSeedCore(Span destination) => + throw new NotSupportedException(); + + protected override bool TryExportPkcs8PrivateKeyCore(Span destination, out int bytesWritten) => + throw new NotSupportedException(); + } + } +} diff --git a/src/libraries/System.Security.Cryptography.Pkcs/tests/EnvelopedCms/MLKemEncryptTests.cs b/src/libraries/System.Security.Cryptography.Pkcs/tests/EnvelopedCms/MLKemEncryptTests.cs new file mode 100644 index 00000000000000..ffb7780d75e44e --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Pkcs/tests/EnvelopedCms/MLKemEncryptTests.cs @@ -0,0 +1,403 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Security.Cryptography.Tests; +using System.Security.Cryptography.X509Certificates; + +using Xunit; + +using TestCertificates = System.Security.Cryptography.Pkcs.Tests.Certificates; +using TestOids = System.Security.Cryptography.Pkcs.Tests.Oids; + +namespace System.Security.Cryptography.Pkcs.EnvelopedCmsTests.Tests +{ + [PlatformSpecific(~TestPlatforms.Windows)] + [ConditionalClass(typeof(MLKem), nameof(MLKem.IsSupported))] + public static class MLKemEncryptTests + { + public static TheoryData MlKemParameterSets { get; } = + new TheoryData + { + { + MLKemTestData.IetfMlKem512CertificatePem, + MLKemTestData.IetfMlKem512PrivateKeySeed, + TestOids.MLKem512, + 768 + }, + { + MLKemTestData.IetfMlKem768CertificatePem, + MLKemTestData.IetfMlKem768PrivateKeySeed, + TestOids.MLKem768, + 1088 + }, + { + MLKemTestData.IetfMlKem1024CertificatePem, + MLKemTestData.IetfMlKem1024PrivateKeySeed, + TestOids.MLKem1024, + 1568 + }, + }; + + [Theory] + [InlineData(TestOids.Aes128, 16)] + [InlineData(TestOids.Aes192, 24)] + [InlineData(TestOids.Aes256, 32)] + public static void EncryptAndDecryptContentEncryptionAlgorithm( + string contentEncryptionAlgorithm, + int contentEncryptionKeyLength) + { + using (X509Certificate2 certificate = X509Certificate2.CreateFromPem( + MLKemTestData.IetfMlKem768CertificatePem)) + { + CmsRecipient recipient = new CmsRecipient(certificate); + + EncryptAndDecrypt( + recipient, + MLKemTestData.IetfMlKem768PrivateKeySeed, + TestOids.MLKem768, + 1088, + SubjectIdentifierType.IssuerAndSerialNumber, + expectedUkm: null, + contentEncryptionAlgorithm, + contentEncryptionKeyLength); + } + } + + [Theory] + [MemberData(nameof(MlKemParameterSets))] + public static void EncryptAndDecryptMlKemParameterSet( + string certificatePem, + byte[] privateKey, + string expectedKemAlgorithm, + int expectedCiphertextLength) + { + using (X509Certificate2 certificate = X509Certificate2.CreateFromPem(certificatePem)) + { + CmsRecipient recipient = new CmsRecipient(certificate); + + EncryptAndDecrypt( + recipient, + privateKey, + expectedKemAlgorithm, + expectedCiphertextLength, + SubjectIdentifierType.IssuerAndSerialNumber, + expectedUkm: null, + TestOids.Aes256, + contentEncryptionKeyLength: 32); + } + } + + [Fact] + public static void EncryptAndDecryptSubjectKeyIdentifier() + { + using (X509Certificate2 certificate = X509Certificate2.CreateFromPem( + MLKemTestData.IetfMlKem768CertificatePem)) + { + CmsRecipient recipient = new CmsRecipient(SubjectIdentifierType.SubjectKeyIdentifier, certificate); + + EncryptAndDecrypt( + recipient, + MLKemTestData.IetfMlKem768PrivateKeySeed, + TestOids.MLKem768, + 1088, + SubjectIdentifierType.SubjectKeyIdentifier, + expectedUkm: null, + TestOids.Aes256, + contentEncryptionKeyLength: 32); + } + } + + [Fact] + public static void EncryptAndDecryptFactoryWithEmptyUkm() + { + using (X509Certificate2 certificate = X509Certificate2.CreateFromPem( + MLKemTestData.IetfMlKem768CertificatePem)) + { + CmsRecipient recipient = CmsRecipient.CreateForKeyEncapsulation(certificate, []); + + EncryptAndDecrypt( + recipient, + MLKemTestData.IetfMlKem768PrivateKeySeed, + TestOids.MLKem768, + 1088, + SubjectIdentifierType.IssuerAndSerialNumber, + expectedUkm: [], + TestOids.Aes256, + contentEncryptionKeyLength: 32); + } + } + + [Fact] + public static void EncryptAndDecryptFactoryWithUkmAndSubjectKeyIdentifier() + { + byte[] userKeyingMaterial = [1, 2, 3, 4, 5]; + + using (X509Certificate2 certificate = X509Certificate2.CreateFromPem( + MLKemTestData.IetfMlKem768CertificatePem)) + { + CmsRecipient recipient = CmsRecipient.CreateForKeyEncapsulation( + SubjectIdentifierType.SubjectKeyIdentifier, + certificate, + userKeyingMaterial); + + EncryptAndDecrypt( + recipient, + MLKemTestData.IetfMlKem768PrivateKeySeed, + TestOids.MLKem768, + 1088, + SubjectIdentifierType.SubjectKeyIdentifier, + userKeyingMaterial, + TestOids.Aes256, + contentEncryptionKeyLength: 32); + } + } + + [Fact] + public static void CreateForKeyEncapsulationCopiesUkm() + { + byte[] userKeyingMaterial = [1, 2, 3, 4, 5]; + byte[] expectedUkm = userKeyingMaterial.AsSpan().ToArray(); + + using (X509Certificate2 certificate = X509Certificate2.CreateFromPem( + MLKemTestData.IetfMlKem768CertificatePem)) + { + CmsRecipient recipient = CmsRecipient.CreateForKeyEncapsulation(certificate, userKeyingMaterial); + userKeyingMaterial.AsSpan().Fill(0xFF); + + EncryptAndDecrypt( + recipient, + MLKemTestData.IetfMlKem768PrivateKeySeed, + TestOids.MLKem768, + 1088, + SubjectIdentifierType.IssuerAndSerialNumber, + expectedUkm, + TestOids.Aes256, + contentEncryptionKeyLength: 32); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void CreateForKeyEncapsulationRejectsNonKemCertificate(bool specifyRecipientIdentifier) + { + using (X509Certificate2 certificate = TestCertificates.RSAKeyTransfer1.GetCertificate()) + { + if (specifyRecipientIdentifier) + { + Assert.Throws(() => + CmsRecipient.CreateForKeyEncapsulation( + SubjectIdentifierType.SubjectKeyIdentifier, + certificate, + [])); + } + else + { + Assert.Throws(() => + CmsRecipient.CreateForKeyEncapsulation(certificate, [])); + } + } + } + + [Fact] + public static void CreateForKeyEncapsulationNullCertificate() + { + byte[] userKeyingMaterial = []; + + Assert.Throws(() => + CmsRecipient.CreateForKeyEncapsulation(null, userKeyingMaterial)); + + Assert.Throws(() => + CmsRecipient.CreateForKeyEncapsulation( + SubjectIdentifierType.SubjectKeyIdentifier, + null, + userKeyingMaterial)); + } + + [Fact] + public static void EncryptInvalidContentEncryptionKeySize() + { + // DES has a 56-bit key which can't be wrapped with AES-KW because AES-KW can only wrap inputs that are + // AES block-size aligned. + using (X509Certificate2 certificate = X509Certificate2.CreateFromPem( + MLKemTestData.IetfMlKem768CertificatePem)) + { + EnvelopedCms cms = new EnvelopedCms( + new ContentInfo("hello world!"u8.ToArray()), + new AlgorithmIdentifier(new Oid(TestOids.Des))); + + Assert.Throws(() => cms.Encrypt(new CmsRecipient(certificate))); + } + } + + [Fact] + public static void EncryptAndDecryptMixedRsaAndMlKemRecipients() + { + byte[] content = "hello world!"u8.ToArray(); + + using (X509Certificate2 mlKemCertificate = X509Certificate2.CreateFromPem( + MLKemTestData.IetfMlKem768CertificatePem)) + using (X509Certificate2 rsaCertificate = TestCertificates.RSAKeyTransfer1.GetCertificate()) + { + CmsRecipientCollection recipients = new CmsRecipientCollection + { + new CmsRecipient(rsaCertificate), + new CmsRecipient(mlKemCertificate), + }; + + EnvelopedCms cms = new EnvelopedCms(new ContentInfo(content)); + cms.Encrypt(recipients); + byte[] encoded = cms.Encode(); + + cms = new EnvelopedCms(); + cms.Decode(encoded); + + Assert.Equal(2, cms.RecipientInfos.Count); + KemRecipientInfo? kemRecipientInfo = null; + KeyTransRecipientInfo? keyTransRecipientInfo = null; + + foreach (RecipientInfo recipientInfo in cms.RecipientInfos) + { + if (recipientInfo is KemRecipientInfo kem) + { + kemRecipientInfo = kem; + } + else if (recipientInfo is KeyTransRecipientInfo keyTrans) + { + keyTransRecipientInfo = keyTrans; + } + } + + Assert.NotNull(kemRecipientInfo); + Assert.NotNull(keyTransRecipientInfo); + + using (MLKem privateKey = MLKem.ImportPkcs8PrivateKey(MLKemTestData.IetfMlKem768PrivateKeySeed)) + { + cms.Decrypt(kemRecipientInfo, privateKey); + } + + Assert.Equal(content, cms.ContentInfo.Content); + + using (X509Certificate2 privateRsaCert = TestCertificates.RSAKeyTransfer1.TryGetCertificateWithPrivateKey()) + { + if (privateRsaCert is null) + { + return; + } + + cms = new EnvelopedCms(); + cms.Decode(encoded); + keyTransRecipientInfo = null; + + foreach (RecipientInfo recipientInfo in cms.RecipientInfos) + { + if (recipientInfo is KeyTransRecipientInfo keyTrans) + { + keyTransRecipientInfo = keyTrans; + break; + } + } + + Assert.NotNull(keyTransRecipientInfo); + + using (RSA privateKey = privateRsaCert.GetRSAPrivateKey()) + { + cms.Decrypt(keyTransRecipientInfo, privateKey); + } + } + + Assert.Equal(content, cms.ContentInfo.Content); + } + } + + [Fact] + public static void EncryptMultipleMlKemRecipientsUseDistinctCiphertexts() + { + byte[] content = "hello world!"u8.ToArray(); + + using (X509Certificate2 certificate = X509Certificate2.CreateFromPem( + MLKemTestData.IetfMlKem768CertificatePem)) + { + CmsRecipientCollection recipients = new CmsRecipientCollection + { + new CmsRecipient(certificate), + new CmsRecipient(certificate), + }; + + EnvelopedCms cms = new EnvelopedCms(new ContentInfo(content)); + cms.Encrypt(recipients); + byte[] encoded = cms.Encode(); + + cms = new EnvelopedCms(); + cms.Decode(encoded); + + Assert.Equal(2, cms.RecipientInfos.Count); + KemRecipientInfo first = Assert.IsType(cms.RecipientInfos[0]); + KemRecipientInfo second = Assert.IsType(cms.RecipientInfos[1]); + Assert.False(first.KeyEncapsulationCiphertext.Span.SequenceEqual(second.KeyEncapsulationCiphertext.Span)); + + using (MLKem privateKey = MLKem.ImportPkcs8PrivateKey(MLKemTestData.IetfMlKem768PrivateKeySeed)) + { + cms.Decrypt(first, privateKey); + } + + Assert.Equal(content, cms.ContentInfo.Content); + } + } + + private static void EncryptAndDecrypt( + CmsRecipient recipient, + byte[] privateKey, + string expectedKemAlgorithm, + int expectedCiphertextLength, + SubjectIdentifierType expectedRecipientIdentifierType, + byte[]? expectedUkm, + string contentEncryptionAlgorithm, + int contentEncryptionKeyLength) + { + byte[] content = "hello world!"u8.ToArray(); + EnvelopedCms cms = new EnvelopedCms( + new ContentInfo(content), + new AlgorithmIdentifier(new Oid(contentEncryptionAlgorithm))); + + cms.Encrypt(recipient); + byte[] encoded = cms.Encode(); + + cms = new EnvelopedCms(); + cms.Decode(encoded); + + Assert.Equal(3, cms.Version); + Assert.Equal(contentEncryptionAlgorithm, cms.ContentEncryptionAlgorithm.Oid.Value); + + KemRecipientInfo recipientInfo = Assert.IsType(Assert.Single(cms.RecipientInfos)); + Assert.Equal(0, recipientInfo.Version); + Assert.Equal(expectedRecipientIdentifierType, recipientInfo.RecipientIdentifier.Type); + Assert.Equal(expectedKemAlgorithm, recipientInfo.KeyEncapsulationAlgorithm.Oid.Value); + Assert.Empty(recipientInfo.KeyEncapsulationAlgorithm.Parameters); + Assert.Equal(expectedCiphertextLength, recipientInfo.KeyEncapsulationCiphertext.Length); + Assert.Equal(TestOids.HkdfSha384, recipientInfo.KeyDerivationAlgorithm.Oid.Value); + Assert.Empty(recipientInfo.KeyDerivationAlgorithm.Parameters); + Assert.Equal(32, recipientInfo.KeyEncryptionKeyLengthInBytes); + Assert.Equal(TestOids.Aes256Wrap, recipientInfo.KeyEncryptionAlgorithm.Oid.Value); + Assert.Empty(recipientInfo.KeyEncryptionAlgorithm.Parameters); + Assert.Equal(contentEncryptionKeyLength + 8, recipientInfo.EncryptedKey.Length); + + if (expectedUkm is null) + { + Assert.Null(recipientInfo.UserKeyingMaterial); + } + else + { + Assert.True(recipientInfo.UserKeyingMaterial.HasValue); + AssertExtensions.SequenceEqual(expectedUkm, recipientInfo.UserKeyingMaterial.Value.Span); + } + + using (MLKem mlKem = MLKem.ImportPkcs8PrivateKey(privateKey)) + { + cms.Decrypt(recipientInfo, mlKem); + } + + Assert.Equal(content, cms.ContentInfo.Content); + } + } +} diff --git a/src/libraries/System.Security.Cryptography.Pkcs/tests/Oids.cs b/src/libraries/System.Security.Cryptography.Pkcs/tests/Oids.cs index 665307c3e52b7b..6188436d29e31b 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/tests/Oids.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/tests/Oids.cs @@ -30,6 +30,9 @@ internal static class Oids public const string MLDsa44 = "2.16.840.1.101.3.4.3.17"; public const string MLDsa65 = "2.16.840.1.101.3.4.3.18"; public const string MLDsa87 = "2.16.840.1.101.3.4.3.19"; + public const string MLKem512 = "2.16.840.1.101.3.4.4.1"; + public const string MLKem768 = "2.16.840.1.101.3.4.4.2"; + public const string MLKem1024 = "2.16.840.1.101.3.4.4.3"; public const string SlhDsaSha2_128s = "2.16.840.1.101.3.4.3.20"; public const string SlhDsaSha2_128f = "2.16.840.1.101.3.4.3.21"; public const string SlhDsaSha2_192s = "2.16.840.1.101.3.4.3.22"; @@ -55,9 +58,13 @@ internal static class Oids // Key wrap algorithms + public const string Aes256Wrap = "2.16.840.1.101.3.4.1.45"; public const string CmsRc2Wrap = "1.2.840.113549.1.9.16.3.7"; public const string Cms3DesWrap = "1.2.840.113549.1.9.16.3.6"; + // Key derivation algorithms + public const string HkdfSha384 = "1.2.840.113549.1.9.16.3.29"; + // PKCS7 Content Types. public const string Pkcs7Data = "1.2.840.113549.1.7.1"; public const string Pkcs7Signed = "1.2.840.113549.1.7.2"; diff --git a/src/libraries/System.Security.Cryptography.Pkcs/tests/System.Security.Cryptography.Pkcs.Tests.csproj b/src/libraries/System.Security.Cryptography.Pkcs/tests/System.Security.Cryptography.Pkcs.Tests.csproj index ee52068c8a26ce..99e42dde0ae1d3 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/tests/System.Security.Cryptography.Pkcs.Tests.csproj +++ b/src/libraries/System.Security.Cryptography.Pkcs/tests/System.Security.Cryptography.Pkcs.Tests.csproj @@ -48,6 +48,15 @@ + + + + + + + +