Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace System.Security.Cryptography.Tests
public sealed class CompositeMLKemTestVector
{
private readonly byte[] _encapsulationKey;
private readonly byte[] _certificate;
private readonly byte[] _decapsulationKey;
private readonly byte[] _pkcs8;
private readonly byte[] _ciphertext;
Expand All @@ -16,6 +17,7 @@ public sealed class CompositeMLKemTestVector

internal string Id { get; }
internal CompositeMLKemAlgorithm Algorithm { get; }
internal ReadOnlySpan<byte> Certificate => _certificate;
internal ReadOnlySpan<byte> EncapsulationKey => _encapsulationKey;
internal ReadOnlySpan<byte> DecapsulationKey => _decapsulationKey;
internal ReadOnlySpan<byte> Pkcs8 => _pkcs8;
Expand All @@ -40,8 +42,9 @@ internal CompositeMLKemTestVector(
_pkcs8 = Convert.FromBase64String(pkcs8);
_ciphertext = Convert.FromBase64String(ciphertext);
_sharedSecret = Convert.FromBase64String(sharedSecret);
_certificate = Convert.FromBase64String(certificate);

AsnReader reader = new AsnReader(Convert.FromBase64String(certificate), AsnEncodingRules.DER);
AsnReader reader = new AsnReader(_certificate, AsnEncodingRules.DER);
AsnReader certificateReader = reader.ReadSequence();
AsnReader tbsCertificate = certificateReader.ReadSequence();
tbsCertificate.ReadEncodedValue(); // Version
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,20 @@ private static KemRecipientInfoAsn MakeKemri(byte[] cek, CmsRecipient recipient)
{
string keyAlgorithm = recipient.Certificate.GetKeyAlgorithm();

if (PkcsHelpers.IsCompositeMLKemAlgorithm(keyAlgorithm))
{
throw new PlatformNotSupportedException(
SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(CompositeMLKem)));
}

switch (keyAlgorithm)
{
case string alg when PkcsHelpers.IsCompositeMLKemAlgorithm(alg) && algorithmParameters is null:
using (CompositeMLKem? key = recipient.Certificate.GetCompositeMLKemPublicKey())
{
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 = alg;
}
break;
case Oids.MlKem512 or Oids.MlKem768 or Oids.MlKem1024 when algorithmParameters is null:
using (MLKem? key = recipient.Certificate.GetMLKemPublicKey())
{
Expand Down Expand Up @@ -152,13 +158,49 @@ internal ManagedKemRecipientInfoPal(KemRecipientInfoAsn asn)

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;
exception = null;

CompositeMLKemAlgorithm? encodedAlgorithm = KeyEncapsulationAlgorithm.Oid.Value switch
{
Oids.MLKem768WithRsaOaep2048Sha3_256 => CompositeMLKemAlgorithm.MLKem768WithRsaOaep2048,
Oids.MLKem768WithRsaOaep3072Sha3_256 => CompositeMLKemAlgorithm.MLKem768WithRsaOaep3072,
Oids.MLKem768WithRsaOaep4096Sha3_256 => CompositeMLKemAlgorithm.MLKem768WithRsaOaep4096,
Oids.MLKem768WithX25519Sha3_256 => CompositeMLKemAlgorithm.MLKem768WithX25519,
Oids.MLKem768WithECDiffieHellmanP256Sha3_256 => CompositeMLKemAlgorithm.MLKem768WithECDiffieHellmanP256,
Oids.MLKem768WithECDiffieHellmanP384Sha3_256 => CompositeMLKemAlgorithm.MLKem768WithECDiffieHellmanP384,
Oids.MLKem768WithECDiffieHellmanBrainpoolP256r1Sha3_256 => CompositeMLKemAlgorithm.MLKem768WithECDiffieHellmanBrainpoolP256r1,
Oids.MLKem1024WithRsaOaep3072Sha3_256 => CompositeMLKemAlgorithm.MLKem1024WithRsaOaep3072,
Oids.MLKem1024WithECDiffieHellmanP384Sha3_256 => CompositeMLKemAlgorithm.MLKem1024WithECDiffieHellmanP384,
Oids.MLKem1024WithECDiffieHellmanBrainpoolP384r1Sha3_256 => CompositeMLKemAlgorithm.MLKem1024WithECDiffieHellmanBrainpoolP384r1,
Oids.MLKem1024WithX448Sha3_256 => CompositeMLKemAlgorithm.MLKem1024WithX448,
Oids.MLKem1024WithECDiffieHellmanP521Sha3_256 => CompositeMLKemAlgorithm.MLKem1024WithECDiffieHellmanP521,
_ => null,
};

// https://datatracker.ietf.org/doc/html/draft-ietf-lamps-cms-composite-kem-03#section-2.1
// requires the parameters to be 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;
}

Debug.Assert(encodedAlgorithm.SharedSecretSizeInBytes == SharedSecretSizeInBytes);

return DecryptCek(
privateKey,
static (privateKey, ciphertext, destination) => privateKey.Decapsulate(ciphertext, destination),
out exception);
}

internal byte[]? DecryptCek(X509Certificate2 cert, out Exception? exception)
Expand All @@ -167,10 +209,28 @@ internal ManagedKemRecipientInfoPal(KemRecipientInfoAsn asn)

if (PkcsHelpers.IsCompositeMLKemAlgorithm(kemAlgorithm))
{
exception = new PlatformNotSupportedException(
SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(CompositeMLKem)));
CompositeMLKem? certificatePrivateKey;

return null;
try
{
certificatePrivateKey = cert.GetCompositeMLKemPrivateKey();
}
catch (PlatformNotSupportedException e)
{
exception = e;
return null;
}

using (certificatePrivateKey)
{
if (certificatePrivateKey is null)
{
exception = new CryptographicException(SR.Cryptography_Cms_Signing_RequiresPrivateKey);
return null;
}

return DecryptCek(certificatePrivateKey, out exception);
}
}

if (PkcsHelpers.IsMLKemAlgorithm(kemAlgorithm))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ public void Decrypt(KemRecipientInfo recipientInfo, CompositeMLKem privateKey)
ArgumentNullException.ThrowIfNull(recipientInfo);
ArgumentNullException.ThrowIfNull(privateKey);

throw new PlatformNotSupportedException(
SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(CompositeMLKem)));
DecryptWithKey(recipientInfo, privateKey);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Formats.Asn1;
using System.Linq;
using System.Security.Cryptography.Asn1;
using System.Security.Cryptography.Asn1.Pkcs7;
using System.Security.Cryptography.Pkcs.Asn1;
using System.Security.Cryptography.Tests;
using System.Security.Cryptography.X509Certificates;

using TestOids = System.Security.Cryptography.Pkcs.Tests.Oids;

namespace System.Security.Cryptography.Pkcs.EnvelopedCmsTests.Tests
{
internal static class CompositeMLKemCmsTestData
{
private const string IdSmimeOriKem = "1.2.840.113549.1.9.16.13.3";

internal static CompositeMLKemTestVector X25519Vector { get; } =
GetVector(CompositeMLKemAlgorithm.MLKem768WithX25519);

internal static CompositeMLKemTestVector P256Vector { get; } =
GetVector(CompositeMLKemAlgorithm.MLKem768WithECDiffieHellmanP256);

internal static byte[] BuildEnvelopedData(
CompositeMLKemTestVector vector,
HashAlgorithmName kdfAlgorithm = default,
string kdfOid = null,
string wrapOid = null,
int kekLength = 32,
byte[] ukm = null,
int version = 0,
string kemOid = null,
byte[] kemCiphertext = null,
int? encryptedKeyLength = null,
bool includeKemParameters = false,
bool includeKdfParameters = false,
bool includeWrapParameters = false)
{
kdfAlgorithm = kdfAlgorithm == default ? HashAlgorithmName.SHA384 : kdfAlgorithm;
kdfOid ??= TestOids.HkdfSha384;
wrapOid ??= TestOids.Aes256Wrap;

using (X509Certificate2 certificate = X509CertificateLoader.LoadCertificate(vector.Certificate))
{
kemOid ??= certificate.GetKeyAlgorithm();
}

byte[] contentEncryptionKey =
[
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
];
byte[] iv =
[
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
];
byte[] keyEncryptionKey = new byte[kekLength];
byte[] encryptedKey;
AlgorithmIdentifierAsn wrap = CreateAlgorithmIdentifier(wrapOid, includeWrapParameters);
ReadOnlyMemory<byte>? userKeyingMaterial = null;

if (ukm is not null)
{
userKeyingMaterial = ukm;
}

CmsOriForKemOtherInfoAsn kdfInfo = new CmsOriForKemOtherInfoAsn
{
Wrap = wrap,
KekLength = kekLength,
Ukm = userKeyingMaterial,
};

HKDF.DeriveKey(
kdfAlgorithm,
vector.SharedSecret,
keyEncryptionKey,
salt: [],
Encode(kdfInfo));

using (Aes aes = Aes.Create())
{
aes.SetKey(keyEncryptionKey);
encryptedKey = aes.EncryptKeyWrap(contentEncryptionKey);
}

if (encryptedKeyLength.HasValue)
{
encryptedKey = new byte[encryptedKeyLength.Value];
}

byte[] encryptedContent;

using (Aes aes = Aes.Create())
{
aes.SetKey(contentEncryptionKey);
encryptedContent = aes.EncryptCbc("hello world!"u8, iv, PaddingMode.PKCS7);
}

KemRecipientInfoAsn kemRecipientInfo = new KemRecipientInfoAsn
{
Version = version,
Rid = new RecipientIdentifierAsn
{
SubjectKeyIdentifier = new byte[] { 1, 2, 3 },
},
Kem = CreateAlgorithmIdentifier(kemOid, includeKemParameters),
Kemct = kemCiphertext ?? vector.Ciphertext.ToArray(),
Kdf = CreateAlgorithmIdentifier(kdfOid, includeKdfParameters),
KekLength = kekLength,
Ukm = userKeyingMaterial,
Wrap = wrap,
EncryptedKey = encryptedKey,
};
RecipientInfoAsn recipientInfo = new RecipientInfoAsn
{
Ori = new OtherRecipientInfoAsn
{
OriType = IdSmimeOriKem,
OriValue = Encode(kemRecipientInfo),
},
};
EnvelopedDataAsn envelopedData = new EnvelopedDataAsn
{
Version = 3,
RecipientInfos = [recipientInfo],
EncryptedContentInfo = new EncryptedContentInfoAsn
{
ContentType = TestOids.Pkcs7Data,
ContentEncryptionAlgorithm = new AlgorithmIdentifierAsn
{
Algorithm = TestOids.Aes256,
Parameters = EncodeOctetString(iv),
},
EncryptedContent = encryptedContent,
},
};
ContentInfoAsn contentInfo = new ContentInfoAsn
{
ContentType = TestOids.Pkcs7Enveloped,
Content = Encode(envelopedData),
};

return Encode(contentInfo);
}

private static CompositeMLKemTestVector GetVector(CompositeMLKemAlgorithm algorithm) =>
CompositeMLKemTestData.AllIetfVectors.Single(vector => vector.Algorithm == algorithm);

private static AlgorithmIdentifierAsn CreateAlgorithmIdentifier(string oid, bool includeNullParameters)
{
ReadOnlyMemory<byte>? parameters = null;

if (includeNullParameters)
{
parameters = new byte[] { 0x05, 0x00 };
}

return new AlgorithmIdentifierAsn
{
Algorithm = oid,
Parameters = parameters,
};
}

private static byte[] Encode(CmsOriForKemOtherInfoAsn value)
{
AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
value.Encode(writer);
return writer.Encode();
}

private static byte[] Encode(ContentInfoAsn value)
{
AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
value.Encode(writer);
return writer.Encode();
}

private static byte[] Encode(EnvelopedDataAsn value)
{
AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
value.Encode(writer);
return writer.Encode();
}

private static byte[] Encode(KemRecipientInfoAsn value)
{
AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
value.Encode(writer);
return writer.Encode();
}

private static byte[] EncodeOctetString(byte[] value)
{
AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
writer.WriteOctetString(value);
return writer.Encode();
}
}
}
Loading
Loading