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 @@
+
+
+
+
+
+
+
+