diff --git a/Directory.Build.props b/Directory.Build.props index 0a0d59a5b4..a24f0ab2ac 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,7 +7,7 @@ 2.0.1 2.0.1 preview - 1.0.0-preview04 + 1.0.0-preview05 1.1.0-preview3 10.0 $([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../')) diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/changelog.md b/Microsoft.Azure.Cosmos.Encryption.Custom/changelog.md index a17680a9f8..58fcf1d790 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/changelog.md +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/changelog.md @@ -3,6 +3,11 @@ Preview features are treated as a separate branch and will not be included in th The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +### [1.0.0-preview05](https://www.nuget.org/packages/Microsoft.Azure.Cosmos.Encryption.Custom/1.0.0-preview05) - 2023-04-27 + +#### Fixes +- [#3809](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/3809) Adds api FetchDataEncryptionKeyWithoutRawKeyAsync and FetchDataEncryptionKey to get DEK without and with raw key respectively. + ### [1.0.0-preview04](https://www.nuget.org/packages/Microsoft.Azure.Cosmos.Encryption.Custom/1.0.0-preview04) - 2022-08-16 #### Fixes diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/CosmosDataEncryptionKeyProvider.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/CosmosDataEncryptionKeyProvider.cs index 1c8cffce8e..773845aa9e 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/CosmosDataEncryptionKeyProvider.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/CosmosDataEncryptionKeyProvider.cs @@ -166,11 +166,25 @@ public async Task InitializeAsync( this.container = containerResponse.Container; } + /// + public override async Task FetchDataEncryptionKeyWithoutRawKeyAsync( + string id, + string encryptionAlgorithm, + CancellationToken cancellationToken) + { + return await this.FetchDekAsync(id, encryptionAlgorithm, cancellationToken); + } + /// public override async Task FetchDataEncryptionKeyAsync( string id, string encryptionAlgorithm, CancellationToken cancellationToken) + { + return await this.FetchDekAsync(id, encryptionAlgorithm, cancellationToken, true); + } + + private async Task FetchDekAsync(string id, string encryptionAlgorithm, CancellationToken cancellationToken, bool withRawKey = false) { DataEncryptionKeyProperties dataEncryptionKeyProperties = await this.dataEncryptionKeyContainerCore.FetchDataEncryptionKeyPropertiesAsync( id, @@ -200,7 +214,8 @@ public override async Task FetchDataEncryptionKeyAsync( InMemoryRawDek inMemoryRawDek = await this.dataEncryptionKeyContainerCore.FetchUnwrappedAsync( dataEncryptionKeyProperties, diagnosticsContext: CosmosDiagnosticsContext.Create(null), - cancellationToken: cancellationToken); + cancellationToken: cancellationToken, + withRawKey); return inMemoryRawDek.DataEncryptionKey; } diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/CosmosEncryptor.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/CosmosEncryptor.cs index 3a325e77b2..462bd56a1f 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/CosmosEncryptor.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/CosmosEncryptor.cs @@ -35,14 +35,14 @@ public override async Task DecryptAsync( string encryptionAlgorithm, CancellationToken cancellationToken = default) { - DataEncryptionKey dek = await this.DataEncryptionKeyProvider.FetchDataEncryptionKeyAsync( + DataEncryptionKey dek = await this.DataEncryptionKeyProvider.FetchDataEncryptionKeyWithoutRawKeyAsync( dataEncryptionKeyId, encryptionAlgorithm, cancellationToken); if (dek == null) { - throw new InvalidOperationException($"Null {nameof(DataEncryptionKey)} returned from {nameof(this.DataEncryptionKeyProvider.FetchDataEncryptionKeyAsync)}."); + throw new InvalidOperationException($"Null {nameof(DataEncryptionKey)} returned from {nameof(this.DataEncryptionKeyProvider.FetchDataEncryptionKeyWithoutRawKeyAsync)}."); } return dek.DecryptData(cipherText); @@ -55,14 +55,14 @@ public override async Task EncryptAsync( string encryptionAlgorithm, CancellationToken cancellationToken = default) { - DataEncryptionKey dek = await this.DataEncryptionKeyProvider.FetchDataEncryptionKeyAsync( + DataEncryptionKey dek = await this.DataEncryptionKeyProvider.FetchDataEncryptionKeyWithoutRawKeyAsync( dataEncryptionKeyId, encryptionAlgorithm, cancellationToken); if (dek == null) { - throw new InvalidOperationException($"Null {nameof(DataEncryptionKey)} returned from {nameof(this.DataEncryptionKeyProvider.FetchDataEncryptionKeyAsync)}."); + throw new InvalidOperationException($"Null {nameof(DataEncryptionKey)} returned from {nameof(this.DataEncryptionKeyProvider.FetchDataEncryptionKeyWithoutRawKeyAsync)}."); } return dek.EncryptData(plainText); diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/DataEncryptionKeyContainerCore.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/DataEncryptionKeyContainerCore.cs index b5f5d340c9..68e5414275 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/DataEncryptionKeyContainerCore.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/DataEncryptionKeyContainerCore.cs @@ -335,6 +335,7 @@ internal async Task FetchUnWrappedMdeSupportedLegacyDekAsync( unwrapResult.DataEncryptionKey); return new MdeEncryptionAlgorithm( + unwrapResult.DataEncryptionKey, plaintextDataEncryptionKey, Data.Encryption.Cryptography.EncryptionType.Randomized); } @@ -378,13 +379,14 @@ internal async Task FetchUnWrappedLegacySupportedMdeDekAsync( internal async Task FetchUnwrappedAsync( DataEncryptionKeyProperties dekProperties, CosmosDiagnosticsContext diagnosticsContext, - CancellationToken cancellationToken) + CancellationToken cancellationToken, + bool withRawKey = false) { try { if (string.Equals(dekProperties.EncryptionAlgorithm, CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized)) { - DataEncryptionKey dek = this.InitMdeEncryptionAlgorithm(dekProperties); + DataEncryptionKey dek = this.InitMdeEncryptionAlgorithm(dekProperties, withRawKey); // TTL is not used since DEK is not cached. return new InMemoryRawDek(dek, TimeSpan.FromMilliseconds(0)); @@ -564,7 +566,7 @@ private async Task UnWrapDekMdeEncAlgoAsync( return unwrapResult; } - internal DataEncryptionKey InitMdeEncryptionAlgorithm(DataEncryptionKeyProperties dekProperties) + internal DataEncryptionKey InitMdeEncryptionAlgorithm(DataEncryptionKeyProperties dekProperties, bool withRawKey = false) { if (this.DekProvider.MdeKeyWrapProvider == null) { @@ -576,7 +578,8 @@ internal DataEncryptionKey InitMdeEncryptionAlgorithm(DataEncryptionKeyPropertie dekProperties, Data.Encryption.Cryptography.EncryptionType.Randomized, this.DekProvider.MdeKeyWrapProvider.EncryptionKeyStoreProvider, - this.DekProvider.PdekCacheTimeToLive); + this.DekProvider.PdekCacheTimeToLive, + withRawKey); } private async Task ReadResourceAsync( diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/DataEncryptionKeyProvider.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/DataEncryptionKeyProvider.cs index 502e14897b..c92df3fa60 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/DataEncryptionKeyProvider.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/DataEncryptionKeyProvider.cs @@ -14,7 +14,19 @@ namespace Microsoft.Azure.Cosmos.Encryption.Custom public abstract class DataEncryptionKeyProvider { /// - /// Retrieves the data encryption key for the given id. + /// Retrieves the data encryption key for the given id without rawkey. RawKey will be set to null. + /// + /// Identifier of the data encryption key. + /// Encryption algorithm that the retrieved key will be used with. + /// Token for request cancellation. + /// Data encryption key bytes. + public abstract Task FetchDataEncryptionKeyWithoutRawKeyAsync( + string id, + string encryptionAlgorithm, + CancellationToken cancellationToken); + + /// + /// Retrieves the data encryption key for the given id with RawKey value. /// /// Identifier of the data encryption key. /// Encryption algorithm that the retrieved key will be used with. diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/MdeServices/MdeEncryptionAlgorithm.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/MdeServices/MdeEncryptionAlgorithm.cs index d9144a5c4f..68d863114e 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/MdeServices/MdeEncryptionAlgorithm.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/MdeServices/MdeEncryptionAlgorithm.cs @@ -14,8 +14,10 @@ internal sealed class MdeEncryptionAlgorithm : DataEncryptionKey { private readonly AeadAes256CbcHmac256EncryptionAlgorithm mdeAeadAes256CbcHmac256EncryptionAlgorithm; + private readonly byte[] unwrapKey; + // unused for MDE Algorithm. - public override byte[] RawKey => null; + public override byte[] RawKey { get; } public override string EncryptionAlgorithm => CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized; @@ -32,7 +34,8 @@ public MdeEncryptionAlgorithm( DataEncryptionKeyProperties dekProperties, Data.Encryption.Cryptography.EncryptionType encryptionType, EncryptionKeyStoreProvider encryptionKeyStoreProvider, - TimeSpan? cacheTimeToLive) + TimeSpan? cacheTimeToLive, + bool withRawKey=false) { if (dekProperties == null) { @@ -49,36 +52,39 @@ public MdeEncryptionAlgorithm( dekProperties.EncryptionKeyWrapMetadata.Value, encryptionKeyStoreProvider); - ProtectedDataEncryptionKey protectedDataEncryptionKey; - if (cacheTimeToLive.HasValue) + if (!withRawKey) { - // no caching - if (cacheTimeToLive.Value == TimeSpan.Zero) - { - protectedDataEncryptionKey = new ProtectedDataEncryptionKey( + ProtectedDataEncryptionKey protectedDataEncryptionKey = cacheTimeToLive.HasValue && cacheTimeToLive.Value == TimeSpan.Zero + ? new ProtectedDataEncryptionKey( + dekProperties.Id, + keyEncryptionKey, + dekProperties.WrappedDataEncryptionKey) + : ProtectedDataEncryptionKey.GetOrCreate( dekProperties.Id, keyEncryptionKey, dekProperties.WrappedDataEncryptionKey); - } - else - { - protectedDataEncryptionKey = ProtectedDataEncryptionKey.GetOrCreate( - dekProperties.Id, - keyEncryptionKey, - dekProperties.WrappedDataEncryptionKey); - } + this.mdeAeadAes256CbcHmac256EncryptionAlgorithm = AeadAes256CbcHmac256EncryptionAlgorithm.GetOrCreate( + protectedDataEncryptionKey, + encryptionType); } else { - protectedDataEncryptionKey = ProtectedDataEncryptionKey.GetOrCreate( - dekProperties.Id, - keyEncryptionKey, - dekProperties.WrappedDataEncryptionKey); + byte[] rawKey = keyEncryptionKey.DecryptEncryptionKey(dekProperties.WrappedDataEncryptionKey); + PlaintextDataEncryptionKey plaintextDataEncryptionKey = cacheTimeToLive.HasValue && (cacheTimeToLive.Value == TimeSpan.Zero) + ? new PlaintextDataEncryptionKey( + dekProperties.Id, + rawKey) + : PlaintextDataEncryptionKey.GetOrCreate( + dekProperties.Id, + rawKey); + this.RawKey = rawKey; + this.mdeAeadAes256CbcHmac256EncryptionAlgorithm = AeadAes256CbcHmac256EncryptionAlgorithm.GetOrCreate( + plaintextDataEncryptionKey, + encryptionType); + } - this.mdeAeadAes256CbcHmac256EncryptionAlgorithm = AeadAes256CbcHmac256EncryptionAlgorithm.GetOrCreate( - protectedDataEncryptionKey, - encryptionType); + } /// @@ -90,9 +96,11 @@ public MdeEncryptionAlgorithm( /// Data Encryption Key /// Encryption type public MdeEncryptionAlgorithm( + byte[] rawkey, Data.Encryption.Cryptography.DataEncryptionKey dataEncryptionKey, Data.Encryption.Cryptography.EncryptionType encryptionType) { + this.RawKey = rawkey; this.mdeAeadAes256CbcHmac256EncryptionAlgorithm = AeadAes256CbcHmac256EncryptionAlgorithm.GetOrCreate( dataEncryptionKey, encryptionType); diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/EmulatorTests/LegacyEncryptionTests.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/EmulatorTests/LegacyEncryptionTests.cs index 27f4567732..2785f04815 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/EmulatorTests/LegacyEncryptionTests.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/EmulatorTests/LegacyEncryptionTests.cs @@ -1747,14 +1747,14 @@ public override async Task DecryptAsync( throw new InvalidOperationException($"Null {nameof(DataEncryptionKey)} returned."); } - DataEncryptionKey dek = await this.DataEncryptionKeyProvider.FetchDataEncryptionKeyAsync( + DataEncryptionKey dek = await this.DataEncryptionKeyProvider.FetchDataEncryptionKeyWithoutRawKeyAsync( dataEncryptionKeyId, encryptionAlgorithm, cancellationToken); if (dek == null) { - throw new InvalidOperationException($"Null {nameof(DataEncryptionKey)} returned from {nameof(this.DataEncryptionKeyProvider.FetchDataEncryptionKeyAsync)}."); + throw new InvalidOperationException($"Null {nameof(DataEncryptionKey)} returned from {nameof(this.DataEncryptionKeyProvider.FetchDataEncryptionKeyWithoutRawKeyAsync)}."); } return dek.DecryptData(cipherText); @@ -1766,7 +1766,7 @@ public override async Task EncryptAsync( string encryptionAlgorithm, CancellationToken cancellationToken = default) { - DataEncryptionKey dek = await this.DataEncryptionKeyProvider.FetchDataEncryptionKeyAsync( + DataEncryptionKey dek = await this.DataEncryptionKeyProvider.FetchDataEncryptionKeyWithoutRawKeyAsync( dataEncryptionKeyId, encryptionAlgorithm, cancellationToken); diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/EmulatorTests/MdeCustomEncryptionTests.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/EmulatorTests/MdeCustomEncryptionTests.cs index 3b2b8f8d97..e100268ea0 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/EmulatorTests/MdeCustomEncryptionTests.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/EmulatorTests/MdeCustomEncryptionTests.cs @@ -20,6 +20,7 @@ namespace Microsoft.Azure.Cosmos.Encryption.EmulatorTests using EncryptionKeyWrapMetadata = Custom.EncryptionKeyWrapMetadata; using DataEncryptionKey = Custom.DataEncryptionKey; using Newtonsoft.Json.Linq; + using System.Buffers.Text; [TestClass] public class MdeCustomEncryptionTests @@ -103,6 +104,47 @@ public async Task EncryptionCreateDek() Assert.AreEqual(dekProperties, readProperties); } + [TestMethod] + public async Task FetchDataEncryptionKeyWithRawKey() + { + CosmosDataEncryptionKeyProvider dekProvider = new CosmosDataEncryptionKeyProvider(new TestEncryptionKeyStoreProvider()); + await dekProvider.InitializeAsync(MdeCustomEncryptionTests.database, MdeCustomEncryptionTests.keyContainer.Id); + DataEncryptionKey k = await dekProvider.FetchDataEncryptionKeyAsync(dekProperties.Id, dekProperties.EncryptionAlgorithm, CancellationToken.None); + Assert.IsNotNull(k.RawKey); + } + + [TestMethod] + public async Task FetchDataEncryptionKeyWithoutRawKey() + { + CosmosDataEncryptionKeyProvider dekProvider = new CosmosDataEncryptionKeyProvider(new TestEncryptionKeyStoreProvider()); + await dekProvider.InitializeAsync(MdeCustomEncryptionTests.database, MdeCustomEncryptionTests.keyContainer.Id); + DataEncryptionKey k = await dekProvider.FetchDataEncryptionKeyWithoutRawKeyAsync(dekProperties.Id, dekProperties.EncryptionAlgorithm, CancellationToken.None); + Assert.IsNull(k.RawKey); + } + + [TestMethod] + [Obsolete] + public async Task FetchDataEncryptionKeyMdeDEKAndLegacyBasedAlgorithm() + { + CosmosDataEncryptionKeyProvider dekProvider = new CosmosDataEncryptionKeyProvider(new TestEncryptionKeyStoreProvider()); + await dekProvider.InitializeAsync(MdeCustomEncryptionTests.database, MdeCustomEncryptionTests.keyContainer.Id); + DataEncryptionKey k = await dekProvider.FetchDataEncryptionKeyAsync(dekProperties.Id, CosmosEncryptionAlgorithm.AEAes256CbcHmacSha256Randomized, CancellationToken.None); + Assert.IsNotNull(k.RawKey); + } + + [TestMethod] + [Obsolete] + public async Task FetchDataEncryptionKeyLegacyDEKAndMdeBasedAlgorithm() + { + string dekId = "legacyDEK"; + DataEncryptionKeyProperties dekProperties = await MdeCustomEncryptionTests.CreateDekAsync(MdeCustomEncryptionTests.dekProvider, dekId, CosmosEncryptionAlgorithm.AEAes256CbcHmacSha256Randomized); + // Use different DEK provider to avoid (unintentional) cache impact + CosmosDataEncryptionKeyProvider dekProvider = new CosmosDataEncryptionKeyProvider(new TestKeyWrapProvider(), new TestEncryptionKeyStoreProvider()); + await dekProvider.InitializeAsync(MdeCustomEncryptionTests.database, MdeCustomEncryptionTests.keyContainer.Id); + DataEncryptionKey k = await dekProvider.FetchDataEncryptionKeyAsync(dekProperties.Id, CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized, CancellationToken.None); + Assert.IsNotNull(k.RawKey); + } + [TestMethod] public async Task EncryptionRewrapDek() { @@ -2190,14 +2232,14 @@ public override async Task DecryptAsync( throw new InvalidOperationException($"Null {nameof(DataEncryptionKey)} returned."); } - DataEncryptionKey dek = await this.DataEncryptionKeyProvider.FetchDataEncryptionKeyAsync( + DataEncryptionKey dek = await this.DataEncryptionKeyProvider.FetchDataEncryptionKeyWithoutRawKeyAsync( dataEncryptionKeyId, encryptionAlgorithm, cancellationToken); if (dek == null) { - throw new InvalidOperationException($"Null {nameof(DataEncryptionKey)} returned from {nameof(this.DataEncryptionKeyProvider.FetchDataEncryptionKeyAsync)}."); + throw new InvalidOperationException($"Null {nameof(DataEncryptionKey)} returned from {nameof(this.DataEncryptionKeyProvider.FetchDataEncryptionKeyWithoutRawKeyAsync)}."); } return dek.DecryptData(cipherText); @@ -2209,7 +2251,7 @@ public override async Task EncryptAsync( string encryptionAlgorithm, CancellationToken cancellationToken = default) { - DataEncryptionKey dek = await this.DataEncryptionKeyProvider.FetchDataEncryptionKeyAsync( + DataEncryptionKey dek = await this.DataEncryptionKeyProvider.FetchDataEncryptionKeyWithoutRawKeyAsync( dataEncryptionKeyId, encryptionAlgorithm, cancellationToken); diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/CosmosEncryptorTests.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/CosmosEncryptorTests.cs index 747bf3e295..63c5b370d1 100644 --- a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/CosmosEncryptorTests.cs +++ b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/CosmosEncryptorTests.cs @@ -34,7 +34,7 @@ public static void ClassInitialize(TestContext testContext) CosmosEncryptorTests.mockDataEncryptionKeyProvider = new Mock(); CosmosEncryptorTests.mockDataEncryptionKeyProvider - .Setup(m => m.FetchDataEncryptionKeyAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(m => m.FetchDataEncryptionKeyWithoutRawKeyAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync((string dekId, string algo, CancellationToken cancellationToken) => dekId == CosmosEncryptorTests.dekId ? CosmosEncryptorTests.mockDataEncryptionKey.Object : null); @@ -82,7 +82,7 @@ public async Task ValidateEncryptDecrypt() Times.Once); CosmosEncryptorTests.mockDataEncryptionKeyProvider.Verify( - m => m.FetchDataEncryptionKeyAsync( + m => m.FetchDataEncryptionKeyWithoutRawKeyAsync( CosmosEncryptorTests.dekId, CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized, It.IsAny()), Times.Exactly(2));