dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.42k stars 4.75k forks source link

Add key wrapping algorithms requested by Key Vault #108332

Open blowdart opened 1 month ago

blowdart commented 1 month ago

@herveyw-msft to expand on what they need.

dotnet-policy-service[bot] commented 1 month ago

Tagging subscribers to this area: @dotnet/area-system-security, @bartonjs, @vcsjones See info in area-owners.md if you want to be subscribed.

jackrichins commented 1 week ago

We need the CKM_AES_KEY_WRAP_PAD mechanism, RFC5649, with support for key sizes 128, 192, and 256 key encryption keys and wrapped keys. For completeness, you may want to support 384 and 512 bit keys CKM_AES_KEY_WRAP.

Our end-to-end scenario is a three key hierarchy: Key encryption key - in an HSM Resource level key - wrapped by the key encryption key, in memory on a service machine. Gets persisted in service metadata wrapped by the KEK. Data encryption key - wrapped by the resource level key, in memory while encrypting/decrypting data on the service machine.

Just for context of roadmap (not a requirement today), eventually we'd like the plaintext version of the resource level key and perhaps even the data encryption key to only exist in Keyguard. But that is a future requirement. For now, we simply want the wrap operations.

This is for teams implementing encryption at rest in Azure with key encryption keys in Key Vault or Managed HSM, though this is a standard pattern referred to as Envelope Encryption used by other cloud vendors as well. As NIST and NSA are recommending AES keys to be post-quantum safe rather than RSA keys as has been used in the past, we expect more teams to need this. Our team is building a library that would use this wrapping mechanism to help teams adopt AES key wrapping for encryption at rest with Azure Key Vault and Managed HSM.

jackrichins commented 4 days ago

Regarding which version beyond the next version of .Net,

I have no data to justify backporting to prior versions yet, but because of CNSA 2.0 timelines most services/applications should switch to AES-KW instead of RSA key wrap algorithms by 2030. So any version of .Net likely to still be in significant usage by 2030 will likely request backporting.

vcsjones commented 4 days ago

The API that I had in mind is basically this.

In an ideal, no-backport required scenario:

public abstract partial class Aes {
    public static int GetCiphertextLengthKeyWrapPadded(int plaintextLength);

    public byte[] EncryptKeyWrapPadded(byte[] plaintext);
    public byte[] EncryptKeyWrapPadded(System.ReadOnlySpan<byte> plaintext);
    public int EncryptKeyWrapPadded(ReadOnlySpan<byte> plaintext, Span<byte> destination);
    public bool TryEncryptKeyWrapPadded(ReadOnlySpan<byte> plaintext, Span<byte> destination, out int bytesWritten);
    protected virtual bool TryEncryptKeyWrapPaddedCore(ReadOnlySpan<byte> plaintext, Span<byte> destination, out int bytesWritten);
}

However, if we need to outbox this, we can't have instance members on Aes any more since that would not work with a type forward.

That would also mean we have no implementation point for user-derived types to override. Instead, we would implement it in terms of ICryptoTransform.TransformBlock calls in down level platforms. "In box" will still be able to do a better implementation since it can use the lite ciphers.

public static class AesKeyWrapExtensions {
    // We can't do extensions on types, so making this an extension is the next best option.
    public static int GetCiphertextLengthKeyWrapPadded(this Aes aes, int plaintextLength);

    public static byte[] EncryptKeyWrapPadded(this Aes aes, byte[] plaintext);
    public static byte[] EncryptKeyWrapPadded(this Aes aes, System.ReadOnlySpan<byte> plaintext);
    public static int EncryptKeyWrapPadded(this Aes aes, ReadOnlySpan<byte> plaintext, Span<byte> destination);
    public static bool TryEncryptKeyWrapPadded(this Aes aes, ReadOnlySpan<byte> plaintext, Span<byte> destination, out int bytesWritten);
}

Making this somewhat complicated is the fact that the algorithm instance of Aes has mode and padding associated with it. To implement AES-KW in terms of TransformBlock, we would need Aes at a minimum to have its mode in ECB. This makes the API shape a little rough but doable.

However I would also suggest that if down level is a real possibility, making the "right" decision early on is better than having two different AES-KW APIs later.