dotnet / dotnet/runtime

[API Proposal]: Configuration for EnvelopedCms KEM KDF and KW

Open
#133,024 4 comments 0 reactions 0 assignees View on GitHub
api-suggestion area-System.Security
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

### Background and motivation

In https://github.com/dotnet/runtime/issues/130883 we approved adding support for KEMs such as ML-KEM and Composite-ML-KEM to EnvelopedCms.

KEMs in Enveloped CMS use two additional algorithms to use a KEM's shared secret. The first is HKDF, where the shared secret + some info is used to create an AES-KW key. That AES-KW is in-turn used to encrypt (wrap) the Content Encryption Key.

The HKDF and AES-KW algorithms are flexible - the RFC does give some guidance on using similar security levels of algorithms (i.e. use AES-256 with ML-KEM 1024, not AES-128) but as long as the combination is legal it is supported.

While our CMS can decode using the right algorithm parameters (again, as long as the combination is permitted) we don't offer flexibility in what algorithms are used. We should allow a CMS recipient to specify which algorithms are used in the wrapping ceremony.

### API Proposal

```csharp
namespace System.Security.Cryptography.Pkcs;

public sealed class CmsKemKeyWrapAlgorithm : IEquatable
{
public static CmsKemKeyWrapAlgorithm Aes128KeyWrap { get; }
public static CmsKemKeyWrapAlgorithm Aes192KeyWrap { get; }
public static CmsKemKeyWrapAlgorithm Aes256KeyWrap { get; }

internal CmsKemKeyWrapAlgorithm(); // Not publicly instantiatable

public string Oid { get; }

public static CmsKemKeyWrapAlgorithm FromOid(string oid);
public static bool TryFromOid(string oid, [NotNullWhen(true)] out CmsKemKeyWrapAlgorithm? algorithm);

public static bool operator ==(CmsKemKeyWrapAlgorithm left, CmsKemKeyWrapAlgorithm right);
public static bool operator !=(CmsKemKeyWrapAlgorithm left, CmsKemKeyWrapAlgorithm right);
}

public sealed class CmsKeyEncapsulationOptions
{
// Defaults to existing behavior.
public HashAlgorithmName KeyDerivationAlgorithm { get; set; } = HashAlgorithmName.SHA384;
public CmsKemKeyWrapAlgorithm KeyEncryptionAlgorithm { get; set; } = CmsKemKeyWrapAlgorithm.Aes256KeyWrap;
}

public partial class CmsRecipient
{
// With UKM
public static CmsRecipient CreateForKeyEncapsulation(
X509Certificate2 certificate,
ReadOnlySpan userKeyingMaterial,
CmsKeyEncapsulationOptions options);

// With UKM
public static CmsRecipient CreateForKeyEncapsulation(
SubjectIdentifierType recipientIdentifierType,
X509Certificate2 certificate,
ReadOnlySpan userKeyingMaterial,
CmsKeyEncapsulationOptions options);

// Without UKM
public static CmsRecipient CreateForKeyEncapsulation(
X509Certificate2 certificate,
CmsKeyEncapsulationOptions options);

// Without UKM
public static CmsRecipient CreateForKeyEncapsulation(
SubjectIdentifierType recipientIdentifierType,
X509Certificate2 certificate,
CmsKeyEncapsulationOptions options);
}
```

### API Usage

```csharp
CmsRecipient recipient = CmsRecipient.CreateForKeyEncapsulation(
cert,
"my keying material"u8,
new CmsKeyEncapsulationOptions {
KeyDerivationAlgorithm = HashAlgorithmName.SHA256,
KeyEncryptionAlgorithm = CmsKemKeyWrapAlgorithm.Aes128KeyWrap,
});
```

### Alternative Designs

`CmsKemKeyWrapAlgorithm` as-proposed is scoped to being only for this specific use case - it is not intended to be "any" key wrap algorithm identifier. It is also a class. It could be an enum to make things simpler. I made it a class in case there is ever a future wrap algorithm that can't be well represented by an enum (e.g. requires some kind of parameters, so then the `CmsKemKeyWrapAlgorithm` could have a factory method with whatever parameters are needed)

User Keying Material remains a parameter, not an option. UKM makes a distinction between "empty" and "missing", so we cannot put it on the options class since a ReadOnlySpan does not have a state that can distinguish between "empty" and "missing" (pointing at null-ref is a poor solution, in my opinion). So it remains a parameter that is either present or not.

We don't have to introduce an options type - we can also just add more overloads. I did options though on the possible anticipation of adding something additional.

### Risks

_No response_

Contributor guide

Open the contributing guide

Research direction

Start with the approved issue #130883 and the existing CmsRecipient key-encapsulation entry points in System.Security.Cryptography.Pkcs. Compare the proposed CmsKemKeyWrapAlgorithm and CmsKeyEncapsulationOptions surface with current EnvelopedCms behavior; the payload names no implementation files or tests, and done depends on the API design being agreed and implemented while preserving existing defaults.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
cryptography
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.