b64628de0b
Secrets-004: AesGcmEnvelopeCipher.Decrypt now wraps the ArgumentException AesGcm throws for a wrong-length nonce/tag/wrapped DEK as SecretDecryptionException (as Rewrap already did), so the resolver's decryption-failed audit fires. Secrets-007: new MasterKeySnapshot (key + KekId from one reading, zeroed on Dispose) and a default-implemented IMasterKeyProvider.GetSnapshot(); MasterKeyProviderBase overrides it to resolve the source once and derive the id from that same key. Encrypt/Decrypt/Rewrap take one snapshot per KEK and dispose it, so a key file swapped mid-operation can no longer stamp a row with one key's id and wrap it under another. Intermediate key buffers are zeroed. On-disk format unchanged: a golden row written by the 0.6.5 cipher still decrypts. Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
42 lines
1.8 KiB
C#
42 lines
1.8 KiB
C#
using System.Security.Cryptography;
|
|
|
|
namespace ZB.MOM.WW.Secrets.Abstractions;
|
|
|
|
/// <summary>
|
|
/// One consistent reading of a master key (KEK): the 32 key bytes together with the
|
|
/// <see cref="IMasterKeyProvider.KekId"/> that identifies <i>those</i> bytes. Obtained from
|
|
/// <see cref="IMasterKeyProvider.GetSnapshot"/> once per cipher operation, so the id stamped on a
|
|
/// row and the key that wraps it can never come from two different reads of a source that changed
|
|
/// in between (a key file replaced mid-operation).
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The snapshot owns its key buffer: <see cref="Dispose"/> zeroes it with
|
|
/// <see cref="CryptographicOperations.ZeroMemory"/>. Always dispose a snapshot
|
|
/// (<c>using var snapshot = provider.GetSnapshot();</c>) and never retain <see cref="Key"/> past it.
|
|
/// </remarks>
|
|
public sealed class MasterKeySnapshot : IDisposable
|
|
{
|
|
private readonly byte[] _key;
|
|
|
|
/// <summary>
|
|
/// Creates a snapshot that takes ownership of <paramref name="key"/> (it is zeroed on
|
|
/// <see cref="Dispose"/>; pass a buffer nothing else uses).
|
|
/// </summary>
|
|
/// <param name="kekId">The non-secret id of <paramref name="key"/>.</param>
|
|
/// <param name="key">The key bytes; ownership transfers to the snapshot.</param>
|
|
public MasterKeySnapshot(string kekId, byte[] key)
|
|
{
|
|
KekId = kekId ?? throw new ArgumentNullException(nameof(kekId));
|
|
_key = key ?? throw new ArgumentNullException(nameof(key));
|
|
}
|
|
|
|
/// <summary>The non-secret identifier of <see cref="Key"/>.</summary>
|
|
public string KekId { get; }
|
|
|
|
/// <summary>The key bytes. Valid only until <see cref="Dispose"/>, which zeroes them.</summary>
|
|
public ReadOnlyMemory<byte> Key => _key;
|
|
|
|
/// <summary>Zeroes the key buffer.</summary>
|
|
public void Dispose() => CryptographicOperations.ZeroMemory(_key);
|
|
}
|