From 8897cd0b6b5261b33f0cd64b636c7f609860d72e Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Wed, 20 Dec 2023 16:18:27 -0600 Subject: [PATCH 01/43] Added method to DaprClient and GRPC implementation to call cryptography proto endpoints Signed-off-by: Whit Waldo --- src/Dapr.Client/DaprClient.cs | 22 ++++++++++++++++++- src/Dapr.Client/DaprClientGrpc.cs | 36 ++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/Dapr.Client/DaprClient.cs b/src/Dapr.Client/DaprClient.cs index 361ac54bc..952e6360a 100644 --- a/src/Dapr.Client/DaprClient.cs +++ b/src/Dapr.Client/DaprClient.cs @@ -3,7 +3,7 @@ // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -939,6 +939,26 @@ public abstract Task UnsubscribeConfiguration( string id, CancellationToken cancellationToken = default); + /// + /// Encrypts an array of bytes using the Dapr Cryptography functionality. + /// + /// The bytes of the plaintext value to encrypt. + /// A that can be used to cancel the operation. + /// The array of encrypted bytes. + public abstract IAsyncEnumerable EncryptAsync( + byte[] plainTextBytes, + CancellationToken cancellationToken = default); + + /// + /// Decrypts an array of bytes using the Dapr Cryptography functionality. + /// + /// The array of bytes to decrypt. + /// A that can be used to cancel the operation. + /// The array of plaintext bytes. + public abstract IAsyncEnumerable DecryptAsync( + byte[] cipherTextBytes, + CancellationToken cancellationToken = default); + /// /// Attempt to lock the given resourceId with response indicating success. /// diff --git a/src/Dapr.Client/DaprClientGrpc.cs b/src/Dapr.Client/DaprClientGrpc.cs index 75df09323..537f39c03 100644 --- a/src/Dapr.Client/DaprClientGrpc.cs +++ b/src/Dapr.Client/DaprClientGrpc.cs @@ -3,7 +3,7 @@ // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -11,6 +11,9 @@ // limitations under the License. // ------------------------------------------------------------------------ +using System.Runtime.CompilerServices; +using System.Text; + namespace Dapr.Client { using System; @@ -1373,6 +1376,37 @@ public override async Task UnsubscribeConfigur var resp = await client.UnsubscribeConfigurationAsync(request, options); return new UnsubscribeConfigurationResponse(resp.Ok, resp.Message); } + + #endregion + + #region Cryptography + + /// + public override async IAsyncEnumerable EncryptAsync(byte[] plainTextBytes, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + var options = CreateCallOptions(headers: null, cancellationToken); + var resp = client.EncryptAlpha1(options); + + await foreach (var result in resp.ResponseStream.ReadAllAsync(cancellationToken)) + { + yield return result.ToByteArray(); + } + } + + /// + public override async IAsyncEnumerable DecryptAsync(byte[] cipherTextBytes, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + Autogenerated.DecryptRequest request = new Autogenerated.DecryptRequest() { }; + var options = CreateCallOptions(headers: null, cancellationToken); + + var resp = client.DecryptAlpha1(options); + + await foreach (var result in resp.ResponseStream.ReadAllAsync(cancellationToken)) + { + yield return result.ToByteArray(); + } + } + #endregion #region Distributed Lock API From b98926e09f02be9118df8ccc567878f4328558e6 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Fri, 22 Dec 2023 01:19:45 -0600 Subject: [PATCH 02/43] First pass at implementing all exposed Cryptography methods on Go interface Signed-off-by: Whit Waldo --- src/Dapr.Client/DaprClient.cs | 182 +++++++++++++++++++++- src/Dapr.Client/DaprClientGrpc.cs | 249 ++++++++++++++++++++++++++++-- 2 files changed, 418 insertions(+), 13 deletions(-) diff --git a/src/Dapr.Client/DaprClient.cs b/src/Dapr.Client/DaprClient.cs index 952e6360a..0f5cbbebb 100644 --- a/src/Dapr.Client/DaprClient.cs +++ b/src/Dapr.Client/DaprClient.cs @@ -20,6 +20,7 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Dapr.Client.Autogen.Grpc.v1; using Google.Protobuf; using Grpc.Core; using Grpc.Core.Interceptors; @@ -939,24 +940,197 @@ public abstract Task UnsubscribeConfiguration( string id, CancellationToken cancellationToken = default); + /// + /// Retrieves the value of the specified key from the vault. + /// + /// The name of the vault resource used by the operation. + /// The name of the key to retrieve the value of. + /// The format to use for the key result. + /// A that can be used to cancel the operation. + /// The name (and possibly version as name/version) of the key and its public key. + [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public abstract Task<(string Name, string PublicKey)> GetKeyAsync(string vaultResourceName, string keyName, SubtleGetKeyRequest.Types.KeyFormat keyFormat, + CancellationToken cancellationToken = default); + /// /// Encrypts an array of bytes using the Dapr Cryptography functionality. /// + /// The name of the vault resource used by the operation. /// The bytes of the plaintext value to encrypt. + /// The name of the algorithm that should be used to perform the encryption. + /// The name of the key used to perform the encryption operation. + /// The bytes comprising the nonce. + /// Any associated data when using AEAD ciphers. /// A that can be used to cancel the operation. /// The array of encrypted bytes. - public abstract IAsyncEnumerable EncryptAsync( + [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public abstract Task<(byte[] CipherTextBytes, byte[] AuthenticationTag)> EncryptAsync( + string vaultResourceName, byte[] plainTextBytes, + string algorithm, + string keyName, + byte[] nonce, + byte[] associatedData, CancellationToken cancellationToken = default); - + + /// + /// Encrypts an array of bytes using the Dapr Cryptography functionality. + /// + /// The name of the vault resource used by the operation. + /// The bytes of the plaintext value to encrypt. + /// The name of the algorithm that should be used to perform the encryption. + /// The name of the key used to perform the encryption operation. + /// The bytes comprising the nonce. + /// A that can be used to cancel the operation. + /// The array of encrypted bytes. + [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public async Task<(byte[] CipherTextBytes, byte[] AuthenticationTag)> EncryptAsync( + string vaultResourceName, + byte[] plainTextBytes, + string algorithm, + string keyName, + byte[] nonce, + CancellationToken cancellationToken = default) => + await EncryptAsync(vaultResourceName, plainTextBytes, algorithm, keyName, nonce, Array.Empty(), + cancellationToken); + /// /// Decrypts an array of bytes using the Dapr Cryptography functionality. /// + /// The name of the vault the key is retrieved from for the decryption operation. /// The array of bytes to decrypt. /// A that can be used to cancel the operation. + /// The algorithm to use to perform the decryption operation. + /// The name of the key used for the decryption. + /// The nonce value used. + /// + /// Any associated data when using AEAD ciphers. /// The array of plaintext bytes. - public abstract IAsyncEnumerable DecryptAsync( - byte[] cipherTextBytes, + [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public abstract Task DecryptAsync(string vaultResourceName, byte[] cipherTextBytes, + string algorithm, string keyName, byte[] nonce, byte[] tag, byte[] associatedData, + CancellationToken cancellationToken = default); + + /// + /// Decrypts an array of bytes using the Dapr Cryptography functionality. + /// + /// The name of the vault the key is retrieved from for the decryption operation. + /// The array of bytes to decrypt. + /// A that can be used to cancel the operation. + /// The algorithm to use to perform the decryption operation. + /// The name of the key used for the decryption. + /// The nonce value used. + /// + /// The array of plaintext bytes. + [Obsolete( + "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public async Task DecryptAsync(string vaultResourceName, byte[] cipherTextBytes, + string algorithm, string keyName, byte[] nonce, byte[] tag, CancellationToken cancellationToken = default) => + await DecryptAsync(vaultResourceName, cipherTextBytes, algorithm, keyName, nonce, tag, Array.Empty(), cancellationToken); + + /// + /// Wraps the plaintext key using another. + /// + /// The name of the vault to retrieve the key from. + /// The plaintext bytes comprising the key to wrap. + /// The name of the key used to wrap the value. + /// The algorithm to use to perform the wrap operation. + /// The none used. + /// Any associated data when using AEAD ciphers. + /// A that can be used to cancel the operation. + /// The bytes comprising the wrapped plain-text key and the authentication tag, if applicable. + [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public abstract Task<(byte[] WrappedKey, byte[] AuthenticationTag)> WrapKeyAsync(string vaultResourceName, byte[] plainTextKey, string keyName, string algorithm, byte[] nonce, byte[] associatedData, + CancellationToken cancellationToken = default); + + /// + /// Wraps the plaintext key using another. + /// + /// The name of the vault to retrieve the key from. + /// The plaintext bytes comprising the key to wrap. + /// The name of the key used to wrap the value. + /// The algorithm to use to perform the wrap operation. + /// The none used. + /// A that can be used to cancel the operation. + /// The bytes comprising the unwrapped key and the authentication tag, if applicable. + [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public async Task<(byte[] WrappedKey, byte[] AuthenticationTag)> WrapKeyAsync(string vaultResourceName, byte[] plainTextKey, string keyName, string algorithm, + byte[] nonce, CancellationToken cancellationToken = default) => await WrapKeyAsync(vaultResourceName, plainTextKey, + keyName, algorithm, nonce, Array.Empty(), cancellationToken); + + /// + /// Used to unwrap the specified key. + /// + /// The name of the vault to retrieve the key from. + /// The byte comprising the wrapped key. + /// The algorithm to use in unwrapping the key. + /// The name of the key used to unwrap the wrapped key bytes. + /// The nonce value. + /// The bytes comprising the authentication tag. + /// Any associated data when using AEAD ciphers. + /// A that can be used to cancel the operation. + /// The bytes comprising the unwrapped key. + [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public abstract Task UnwrapKeyAsync(string vaultResourceName, byte[] wrappedKey, string algorithm, string keyName, byte[] nonce, byte[] tag, byte[] associatedData, + CancellationToken cancellationToken = default); + + /// + /// Used to unwrap the specified key. + /// + /// The name of the vault to retrieve the key from. + /// The byte comprising the wrapped key. + /// The algorithm to use in unwrapping the key. + /// The name of the key used to unwrap the wrapped key bytes. + /// The nonce value. + /// The bytes comprising the authentication tag. + /// A that can be used to cancel the operation. + /// The bytes comprising the unwrapped key. + [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public async Task UnwrapKeyAsync(string vaultResourceName, byte[] wrappedKey, string algorithm, string keyName, + byte[] nonce, byte[] tag, + CancellationToken cancellationToken = default) => await UnwrapKeyAsync(vaultResourceName, + wrappedKey, algorithm, keyName, nonce, Array.Empty(), Array.Empty(), cancellationToken); + + /// + /// Used to unwrap the specified key. + /// + /// The name of the vault to retrieve the key from. + /// The byte comprising the wrapped key. + /// The algorithm to use in unwrapping the key. + /// The name of the key used to unwrap the wrapped key bytes. + /// The nonce value. + /// A that can be used to cancel the operation. + /// The bytes comprising the unwrapped key. + [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public async Task UnwrapKeyAsync(string vaultResourceName, byte[] wrappedKey, string algorithm, string keyName, + byte[] nonce, CancellationToken cancellationToken = default) => await UnwrapKeyAsync(vaultResourceName, + wrappedKey, algorithm, keyName, nonce, Array.Empty(), Array.Empty(), cancellationToken); + + /// + /// Creates a signature of a digest value. + /// + /// The name of the vault to retrieve the key from. + /// The digest value to create the signature for. + /// The algorithm used to create the signature. + /// The name of the key used. + /// A that can be used to cancel the operation. + /// The bytes comprising the signature. + [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public abstract Task SignAsync(string vaultResourceName, byte[] digest, string algorithm, string keyName, + CancellationToken cancellationToken = default); + + /// + /// Validates a signature. + /// + /// The name of the vault to retrieve the key from. + /// The digest to validate the signature with. + /// The signature to validate. + /// The algorithm to validate the signature with. + /// The name of the key used. + /// A that can be used to cancel the operation. + /// True if the signature verification is successful; otherwise false. + [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public abstract Task VerifyAsync(string vaultResourceName, byte[] digest, byte[] signature, string algorithm, string keyName, CancellationToken cancellationToken = default); /// diff --git a/src/Dapr.Client/DaprClientGrpc.cs b/src/Dapr.Client/DaprClientGrpc.cs index 537f39c03..dac6c8aab 100644 --- a/src/Dapr.Client/DaprClientGrpc.cs +++ b/src/Dapr.Client/DaprClientGrpc.cs @@ -1382,31 +1382,262 @@ public override async Task UnsubscribeConfigur #region Cryptography /// - public override async IAsyncEnumerable EncryptAsync(byte[] plainTextBytes, [EnumeratorCancellation] CancellationToken cancellationToken = default) + [Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public override async Task<(string Name, string PublicKey)> GetKeyAsync(string vaultResourceName, string keyName, Autogenerated.SubtleGetKeyRequest.Types.KeyFormat keyFormat, + CancellationToken cancellationToken = default) { + ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); + ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); + + var envelope = new Autogenerated.SubtleGetKeyRequest() + { + ComponentName = vaultResourceName, Format = keyFormat, Name = keyName + }; + + var options = CreateCallOptions(headers: null, cancellationToken); + Autogenerated.SubtleGetKeyResponse response; + + try + { + response = await client.SubtleGetKeyAlpha1Async(envelope, options); + } + catch (RpcException ex) + { + throw new DaprException( + "Cryptography operation failed: the Dapr endpoint indicated a failure. See InnerException for details", ex); + } + + return (response.Name, response.PublicKey); + } + + /// + [Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public override async Task<(byte[] CipherTextBytes, byte[] AuthenticationTag)> EncryptAsync(string vaultResourceName, byte[] plainTextBytes, string algorithm, + string keyName, byte[] nonce, byte[] associatedData, CancellationToken cancellationToken = default) + { + ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); + ArgumentVerifier.ThrowIfNullOrEmpty(algorithm, nameof(algorithm)); + ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); + + var envelope = new Autogenerated.SubtleEncryptRequest + { + ComponentName = vaultResourceName, + Algorithm = algorithm, + KeyName = keyName, + Nonce = ByteString.CopyFrom(nonce), + Plaintext = ByteString.CopyFrom(plainTextBytes), + AssociatedData = ByteString.CopyFrom(associatedData) + }; + + var options = CreateCallOptions(headers: null, cancellationToken); + Autogenerated.SubtleEncryptResponse response; + + try + { + response = await client.SubtleEncryptAlpha1Async(envelope, options); + } + catch (RpcException ex) + { + throw new DaprException( + "Cryptography operation failed: the Dapr endpoint indicated a failure. See InnerException for details", + ex); + } + + return (response.Ciphertext.ToByteArray(), response.Tag.ToByteArray() ?? Array.Empty()); + } + + /// + [Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public override async Task DecryptAsync(string vaultResourceName, byte[] cipherTextBytes, string algorithm, string keyName, byte[] nonce, byte[] tag, + byte[] associatedData, CancellationToken cancellationToken = default) + { + ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); + ArgumentVerifier.ThrowIfNullOrEmpty(algorithm, nameof(algorithm)); + ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); + + var envelope = new Autogenerated.SubtleDecryptRequest + { + ComponentName = vaultResourceName, + Algorithm = algorithm, + KeyName = keyName, + Nonce = ByteString.CopyFrom(nonce), + Ciphertext = ByteString.CopyFrom(cipherTextBytes), + AssociatedData = ByteString.CopyFrom(associatedData), + Tag = ByteString.CopyFrom(tag) + }; + var options = CreateCallOptions(headers: null, cancellationToken); - var resp = client.EncryptAlpha1(options); + Autogenerated.SubtleDecryptResponse response; - await foreach (var result in resp.ResponseStream.ReadAllAsync(cancellationToken)) + try + { + response = await client.SubtleDecryptAlpha1Async(envelope, options); + } + catch (RpcException ex) { - yield return result.ToByteArray(); + throw new DaprException( + "Cryptography operation failed: the Dapr endpoint included a failure. See InnerException for details", ex); } + + return response.Plaintext.ToByteArray(); } /// - public override async IAsyncEnumerable DecryptAsync(byte[] cipherTextBytes, [EnumeratorCancellation] CancellationToken cancellationToken = default) + [Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public override async Task<(byte[] WrappedKey, byte[] AuthenticationTag)> WrapKeyAsync(string vaultResourceName, byte[] plainTextKey, string keyName, + string algorithm, byte[] nonce, byte[] associatedData, CancellationToken cancellationToken = default) + { + ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); + ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); + ArgumentVerifier.ThrowIfNullOrEmpty(algorithm, nameof(algorithm)); + + var envelope = new Autogenerated.SubtleWrapKeyRequest + { + ComponentName = vaultResourceName, + Algorithm = algorithm, + KeyName = keyName, + Nonce = ByteString.CopyFrom(nonce), + PlaintextKey = ByteString.CopyFrom(plainTextKey), + AssociatedData = ByteString.CopyFrom(associatedData) + }; + + var options = CreateCallOptions(headers: null, cancellationToken); + Autogenerated.SubtleWrapKeyResponse response; + + try + { + response = await client.SubtleWrapKeyAlpha1Async(envelope, options); + } + catch (RpcException ex) + { + throw new DaprException( + "Cryptography operation failed: the Dapr endpoint included a failure. See InnerException for details", + ex); + } + + return (response.WrappedKey.ToByteArray(), response.Tag.ToByteArray() ?? Array.Empty()); + } + + /// + [Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public override async Task UnwrapKeyAsync(string vaultResourceName, byte[] wrappedKey, string algorithm, + string keyName, byte[] nonce, byte[] tag, byte[] associatedData, CancellationToken cancellationToken = default) + { + ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); + ArgumentVerifier.ThrowIfNullOrEmpty(algorithm, nameof(algorithm)); + ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); + + var envelope = new Autogenerated.SubtleUnwrapKeyRequest + { + ComponentName = vaultResourceName, + WrappedKey = ByteString.CopyFrom(wrappedKey), + AssociatedData = ByteString.CopyFrom(associatedData), + Algorithm = algorithm, + KeyName = keyName, + Nonce = ByteString.CopyFrom(nonce), + Tag = ByteString.CopyFrom(tag) + }; + + var options = CreateCallOptions(headers: null, cancellationToken); + Autogenerated.SubtleUnwrapKeyResponse response; + + try + { + response = await client.SubtleUnwrapKeyAlpha1Async(envelope, options); + } + catch (RpcException ex) + { + throw new DaprException( + "Cryptography operation failed: the Dapr endpoint included a failure. See InnerException for details", + ex); + } + + return response.PlaintextKey.ToByteArray(); + } + + /// + [Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public override async Task SignAsync(string vaultResourceName, byte[] digest, string algorithm, string keyName, CancellationToken cancellationToken = default) { - Autogenerated.DecryptRequest request = new Autogenerated.DecryptRequest() { }; + ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); + ArgumentVerifier.ThrowIfNullOrEmpty(algorithm, nameof(algorithm)); + ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); + + var envelope = new Autogenerated.SubtleSignRequest + { + ComponentName = vaultResourceName, + Digest = ByteString.CopyFrom(digest), + Algorithm = algorithm, + KeyName = keyName + }; + var options = CreateCallOptions(headers: null, cancellationToken); + Autogenerated.SubtleSignResponse response; - var resp = client.DecryptAlpha1(options); + try + { + response = await client.SubtleSignAlpha1Async(envelope, options); + } + catch (RpcException ex) + { + throw new DaprException( + "Cryptography operation failed: the Dapr endpoint included a failure. See InnerException for details", + ex); + } + + return response.Signature.ToByteArray(); + } - await foreach (var result in resp.ResponseStream.ReadAllAsync(cancellationToken)) + /// + [Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public override async Task VerifyAsync(string vaultResourceName, byte[] digest, byte[] signature, + string algorithm, string keyName, CancellationToken cancellationToken = default) + { + ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); + ArgumentVerifier.ThrowIfNullOrEmpty(algorithm, nameof(algorithm)); + ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); + + var envelope = new Autogenerated.SubtleVerifyRequest { - yield return result.ToByteArray(); + ComponentName = vaultResourceName, + Algorithm = algorithm, + KeyName = keyName, + Signature = ByteString.CopyFrom(signature), + Digest = ByteString.CopyFrom(digest) + }; + + var options = CreateCallOptions(headers: null, cancellationToken); + Autogenerated.SubtleVerifyResponse response; + + try + { + response = await client.SubtleVerifyAlpha1Async(envelope, options); + } + catch (RpcException ex) + { + throw new DaprException( + "Cryptography operation failed: the Dapr endpoint included a failure. See InnerException for details", + ex); } + + return response.Valid; } + + ///// + //public override async IAsyncEnumerable DecryptAsync(byte[] cipherTextBytes, [EnumeratorCancellation] CancellationToken cancellationToken = default) + //{ + // Autogenerated.DecryptRequest request = new Autogenerated.DecryptRequest() { }; + // var options = CreateCallOptions(headers: null, cancellationToken); + + // var resp = client.DecryptAlpha1(options); + + // await foreach (var result in resp.ResponseStream.ReadAllAsync(cancellationToken)) + // { + // yield return result.ToByteArray(); + // } + //} + #endregion #region Distributed Lock API From e391735533c74b482978b4682bc9649bfb1e1f0a Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Fri, 22 Dec 2023 03:31:45 -0600 Subject: [PATCH 03/43] Added examples for Cryptography block Signed-off-by: Whit Waldo --- all.sln | 11 ++++ .../Components/azurekeyvault.yaml | 25 +++++++++ .../Components/env-secretstore.yaml | 7 +++ .../Client/Cryptography/Cryptography.csproj | 19 +++++++ examples/Client/Cryptography/Example.cs | 9 ++++ .../Examples/EncryptDecryptExample.cs | 39 ++++++++++++++ .../Examples/SignVerifyExample.cs | 32 +++++++++++ .../Examples/WrapUnwrapKeyExample.cs | 53 +++++++++++++++++++ examples/Client/Cryptography/Program.cs | 35 ++++++++++++ examples/Client/Cryptography/README.md | 33 ++++++++++++ 10 files changed, 263 insertions(+) create mode 100644 examples/Client/Cryptography/Components/azurekeyvault.yaml create mode 100644 examples/Client/Cryptography/Components/env-secretstore.yaml create mode 100644 examples/Client/Cryptography/Cryptography.csproj create mode 100644 examples/Client/Cryptography/Example.cs create mode 100644 examples/Client/Cryptography/Examples/EncryptDecryptExample.cs create mode 100644 examples/Client/Cryptography/Examples/SignVerifyExample.cs create mode 100644 examples/Client/Cryptography/Examples/WrapUnwrapKeyExample.cs create mode 100644 examples/Client/Cryptography/Program.cs create mode 100644 examples/Client/Cryptography/README.md diff --git a/all.sln b/all.sln index 47fc9098c..bf0b87352 100644 --- a/all.sln +++ b/all.sln @@ -104,6 +104,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BulkPublishEventExample", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowUnitTest", "examples\Workflow\WorkflowUnitTest\WorkflowUnitTest.csproj", "{8CA09061-2BEF-4506-A763-07062D2BD6AC}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cryptography", "examples\Client\Cryptography\Cryptography.csproj", "{C74FBA78-13E8-407F-A173-4555AEE41FF3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -248,6 +250,14 @@ Global {DDC41278-FB60-403A-B969-2AEBD7C2D83C}.Release|Any CPU.Build.0 = Release|Any CPU {8CA09061-2BEF-4506-A763-07062D2BD6AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8CA09061-2BEF-4506-A763-07062D2BD6AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C74FBA78-13E8-407F-A173-4555AEE41FF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C74FBA78-13E8-407F-A173-4555AEE41FF3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C74FBA78-13E8-407F-A173-4555AEE41FF3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C74FBA78-13E8-407F-A173-4555AEE41FF3}.Release|Any CPU.Build.0 = Release|Any CPU + {D1786E2B-CAA0-4B2D-A974-9845EB9E420F}.Debug|Any CPU.ActiveCfg = Debug + {D1786E2B-CAA0-4B2D-A974-9845EB9E420F}.Debug|Any CPU.Build.0 = Debug + {D1786E2B-CAA0-4B2D-A974-9845EB9E420F}.Release|Any CPU.ActiveCfg = Release + {D1786E2B-CAA0-4B2D-A974-9845EB9E420F}.Release|Any CPU.Build.0 = Release EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -293,6 +303,7 @@ Global {4A175C27-EAFE-47E7-90F6-873B37863656} = {0EF6EA64-D7C3-420D-9890-EAE8D54A57E6} {DDC41278-FB60-403A-B969-2AEBD7C2D83C} = {0EF6EA64-D7C3-420D-9890-EAE8D54A57E6} {8CA09061-2BEF-4506-A763-07062D2BD6AC} = {BF3ED6BF-ADF3-4D25-8E89-02FB8D945CA9} + {C74FBA78-13E8-407F-A173-4555AEE41FF3} = {A7F41094-8648-446B-AECD-DCC2CC871F73} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {65220BF2-EAE1-4CB2-AA58-EBE80768CB40} diff --git a/examples/Client/Cryptography/Components/azurekeyvault.yaml b/examples/Client/Cryptography/Components/azurekeyvault.yaml new file mode 100644 index 000000000..5932e0bc8 --- /dev/null +++ b/examples/Client/Cryptography/Components/azurekeyvault.yaml @@ -0,0 +1,25 @@ +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: azurekeyvault +spec: + type: crypto.azure.keyvault + metadata: + - name: vaultName + value: "" + - name: azureEnvironment + value: AZUREPUBLICCLOUD + - name: azureTenantId + secretKeyRef: + name: read_azure_tenant_id + key: read_azure_tenant_id + - name: azureClientId + secretKeyRef: + name: read_azure_client_id + key: read_azure_client_id + - name: azureClientSecret + secretKeyRef: + name: read_azure_client_secret + key: read_azure_client_secret +auth: + secureStore: envvar-secret-store \ No newline at end of file diff --git a/examples/Client/Cryptography/Components/env-secretstore.yaml b/examples/Client/Cryptography/Components/env-secretstore.yaml new file mode 100644 index 000000000..fb191414d --- /dev/null +++ b/examples/Client/Cryptography/Components/env-secretstore.yaml @@ -0,0 +1,7 @@ +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: envvar-secret-store +spec: + type: secretstores.local.env + version: v1 \ No newline at end of file diff --git a/examples/Client/Cryptography/Cryptography.csproj b/examples/Client/Cryptography/Cryptography.csproj new file mode 100644 index 000000000..2f08102a6 --- /dev/null +++ b/examples/Client/Cryptography/Cryptography.csproj @@ -0,0 +1,19 @@ + + + + Exe + net8.0 + enable + enable + latest + + + + + + + + + + + \ No newline at end of file diff --git a/examples/Client/Cryptography/Example.cs b/examples/Client/Cryptography/Example.cs new file mode 100644 index 000000000..13fa261a0 --- /dev/null +++ b/examples/Client/Cryptography/Example.cs @@ -0,0 +1,9 @@ +namespace Cryptography +{ + internal abstract class Example + { + public abstract string DisplayName { get; } + + public abstract Task RunAsync(CancellationToken cancellationToken); + } +} diff --git a/examples/Client/Cryptography/Examples/EncryptDecryptExample.cs b/examples/Client/Cryptography/Examples/EncryptDecryptExample.cs new file mode 100644 index 000000000..13535f92a --- /dev/null +++ b/examples/Client/Cryptography/Examples/EncryptDecryptExample.cs @@ -0,0 +1,39 @@ +using System.Text; +using Dapr.Client; + +namespace Cryptography.Examples +{ + internal class EncryptDecryptExample : Example + { + public override string DisplayName => "Using Cryptography to encrypt and decrypt a string"; + + public override async Task RunAsync(CancellationToken cancellationToken) + { + using var client = new DaprClientBuilder().Build(); + + const string componentName = "azurekeyvault"; //Change this to match the name of the component containing your vault + const string keyName = "myKey"; //Change this to match the name of the key in your Vault + const string algorithm = "RSA"; //The algorithm used should match the type of key used. + + var nonceBytes = "This in our nonce value"u8.ToArray(); + + const string plaintextStr = "This is the value we're going to encrypt today"; + Console.WriteLine($"Original string value: '{plaintextStr}'"); + + //Encrypt the string + var plaintextBytes = Encoding.UTF8.GetBytes(plaintextStr); +#pragma warning disable CS0618 // Type or member is obsolete + var encryptedBytesResult = await client.EncryptAsync(componentName, plaintextBytes, algorithm, keyName, + nonceBytes, cancellationToken); +#pragma warning restore CS0618 // Type or member is obsolete + + //Decrypt the string +#pragma warning disable CS0618 // Type or member is obsolete + var decryptedBytes = await client.DecryptAsync(componentName, encryptedBytesResult.CipherTextBytes, + algorithm, keyName, + nonceBytes, Array.Empty(), cancellationToken); +#pragma warning restore CS0618 // Type or member is obsolete + Console.WriteLine($"Decrypted string: '{Encoding.UTF8.GetString(decryptedBytes)}'"); + } + } +} diff --git a/examples/Client/Cryptography/Examples/SignVerifyExample.cs b/examples/Client/Cryptography/Examples/SignVerifyExample.cs new file mode 100644 index 000000000..f7209e597 --- /dev/null +++ b/examples/Client/Cryptography/Examples/SignVerifyExample.cs @@ -0,0 +1,32 @@ +using Dapr.Client; + +namespace Cryptography.Examples +{ + // This isn't yet implemented in the API, so it cannot yet be tested + // internal class SignVerifyExample : Example + // { + // public override string DisplayName => "Using Cryptography to sign a digest and verify that signature"; + + // public override async Task RunAsync(CancellationToken cancellationToken) + // { + // using var client = new DaprClientBuilder().Build(); + + // const string componentName = "azurekeyvault"; + // const string keyName = "mykey"; // Change this to match the name of the key in your Vault + // const string algorithm = "RSA"; //The algorithm should match the key being used + + // var digestBytes = "This is our starting value we'll build the signature for"u8.ToArray(); + + //#pragma warning disable CS0618 // Type or member is obsolete + // var signature = await client.SignAsync(componentName, digestBytes, algorithm, keyName, cancellationToken); + //#pragma warning restore CS0618 // Type or member is obsolete + // Console.WriteLine($"Signature: '{Convert.ToBase64String(signature)}'"); + + //#pragma warning disable CS0618 // Type or member is obsolete + // var verification = await client.VerifyAsync(componentName, digestBytes, signature, algorithm, keyName, + // cancellationToken); + //#pragma warning restore CS0618 // Type or member is obsolete + // Console.WriteLine($"Verification: {(verification ? "Success": "Failed")}"); + // } + // } +} diff --git a/examples/Client/Cryptography/Examples/WrapUnwrapKeyExample.cs b/examples/Client/Cryptography/Examples/WrapUnwrapKeyExample.cs new file mode 100644 index 000000000..35953030f --- /dev/null +++ b/examples/Client/Cryptography/Examples/WrapUnwrapKeyExample.cs @@ -0,0 +1,53 @@ +using System.Security.Cryptography; +using System.Text; +using Dapr.Client; + +namespace Cryptography.Examples +{ + // This isn't yet implemented in the API, so it cannot yet be tested + // internal class WrapUnwrapKeyExample : Example + // { + // public override string DisplayName => "Using Cryptography to retrieve, wrap and unwrap a given key"; + + // public override async Task RunAsync(CancellationToken cancellationToken) + // { + // using var client = new DaprClientBuilder().Build(); + + // const string componentName = "azurekeyvault"; // Change this to match the name of the component containing your vault + // const string keyName = "mykey"; // Change this to match the name of the key in your Vault + + // var nonceBytes = "This is a nonce"u8.ToArray(); + + // //Generate a new private key for our purposes here + // var privateKeyBytes = new List(); + // using (var rsa = new RSACryptoServiceProvider(2048)) + // { + // try + // { + // var privateKey = rsa.ExportEncryptedPkcs8PrivateKey("password", + // new PbeParameters(PbeEncryptionAlgorithm.Aes256Cbc, HashAlgorithmName.SHA256, 5)); + + // privateKeyBytes.AddRange(privateKey); + // } + // finally + // { + // rsa.PersistKeyInCsp = false; + // } + // } + + // //Wrap the key + // var wrappedKeyResult = + //#pragma warning disable CS0618 // Type or member is obsolete + // await client.WrapKeyAsync(componentName, privateKeyBytes.ToArray(), keyName, "RSA", nonceBytes, cancellationToken); + //#pragma warning restore CS0618 // Type or member is obsolete + // Console.WriteLine($"Wrapped key bytes: '{Convert.ToBase64String(wrappedKeyResult.WrappedKey)}'"); + + // //Unwrap the key + //#pragma warning disable CS0618 // Type or member is obsolete + // var unwrappedKey = await client.UnwrapKeyAsync(componentName, wrappedKeyResult.WrappedKey, "RSA", keyName, + // nonceBytes, Array.Empty(), cancellationToken); + //#pragma warning restore CS0618 // Type or member is obsolete + // Console.WriteLine($"Unwrapped key value:"); + // } + // } +} diff --git a/examples/Client/Cryptography/Program.cs b/examples/Client/Cryptography/Program.cs new file mode 100644 index 000000000..46427deb5 --- /dev/null +++ b/examples/Client/Cryptography/Program.cs @@ -0,0 +1,35 @@ +using Cryptography; +using Cryptography.Examples; + +namespace Samples.Client +{ + class Program + { + private static readonly Example[] Examples = new Example[] + { + new EncryptDecryptExample() + //new SignVerifyExample() - API not implemented yet + //new WrapUnwrapKeyExample() - API not implemented yet + }; + + static async Task Main(string[] args) + { + if (args.Length > 0 && int.TryParse(args[0], out var index) && index >= 0 && index < Examples.Length) + { + var cts = new CancellationTokenSource(); + Console.CancelKeyPress += (object? sender, ConsoleCancelEventArgs e) => cts.Cancel(); + + await Examples[index].RunAsync(cts.Token); + return 0; + } + + Console.WriteLine("Hello, please choose a sample to run:"); + for (var i = 0; i < Examples.Length; i++) + { + Console.WriteLine($"{i}: {Examples[i].DisplayName}"); + } + Console.WriteLine(); + return 1; + } + } +} diff --git a/examples/Client/Cryptography/README.md b/examples/Client/Cryptography/README.md new file mode 100644 index 000000000..8b602ecca --- /dev/null +++ b/examples/Client/Cryptography/README.md @@ -0,0 +1,33 @@ +# Dapr .NET SDK Cryptography example + +## Prerequisites + +- [.NET 8+](https://dotnet.microsoft.com/download) installed +- [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli) +- [Initialized Dapr environment](https://docs.dapr.io/getting-started/installation) +- [Dapr .NET SDK](https://docs.dapr.io/developing-applications/sdks/dotnet/) + +## Running the example + +To run the sample locally, run this command in the DaprClient directory: + +```sh +dapr run --resources-path ./Components --app-id DaprClient -- dotnet run +``` + +Running the following command will output a list of the samples included: + +```sh +dapr run --resources-path ./Components --app-id DaprClient -- dotnet run +``` + +Press Ctrl+C to exit, and then run the command again and provide a sample number to run the samples. + +For example, run this command to run the first sample from the list produced earlier (the 0th example): + +```sh +dapr run --resources-path ./Components --app-id DaprClient -- dotnet run 0 +``` + +## Encryption/Decryption with strings +See [EncryptStringExample.cs](./EncryptStringExample.cs) for an example of using `DaprClient` for basic string-based encryption and decryption operations as performed against UTF-8 encoded byte arrays. \ No newline at end of file From 43dd9028dadece10a3b26e325ba4df59f198638d Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Fri, 22 Dec 2023 03:55:20 -0600 Subject: [PATCH 04/43] Added missing copyright statements Signed-off-by: Whit Waldo --- examples/Client/Cryptography/Example.cs | 15 ++++++++++++++- .../Examples/EncryptDecryptExample.cs | 15 ++++++++++++++- .../Cryptography/Examples/SignVerifyExample.cs | 15 ++++++++++++++- .../Cryptography/Examples/WrapUnwrapKeyExample.cs | 15 ++++++++++++++- examples/Client/Cryptography/Program.cs | 15 ++++++++++++++- 5 files changed, 70 insertions(+), 5 deletions(-) diff --git a/examples/Client/Cryptography/Example.cs b/examples/Client/Cryptography/Example.cs index 13fa261a0..2c2d41626 100644 --- a/examples/Client/Cryptography/Example.cs +++ b/examples/Client/Cryptography/Example.cs @@ -1,4 +1,17 @@ -namespace Cryptography +// ------------------------------------------------------------------------ +// Copyright 2023 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +namespace Cryptography { internal abstract class Example { diff --git a/examples/Client/Cryptography/Examples/EncryptDecryptExample.cs b/examples/Client/Cryptography/Examples/EncryptDecryptExample.cs index 13535f92a..f0a0af66d 100644 --- a/examples/Client/Cryptography/Examples/EncryptDecryptExample.cs +++ b/examples/Client/Cryptography/Examples/EncryptDecryptExample.cs @@ -1,4 +1,17 @@ -using System.Text; +// ------------------------------------------------------------------------ +// Copyright 2023 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using System.Text; using Dapr.Client; namespace Cryptography.Examples diff --git a/examples/Client/Cryptography/Examples/SignVerifyExample.cs b/examples/Client/Cryptography/Examples/SignVerifyExample.cs index f7209e597..3af94e903 100644 --- a/examples/Client/Cryptography/Examples/SignVerifyExample.cs +++ b/examples/Client/Cryptography/Examples/SignVerifyExample.cs @@ -1,4 +1,17 @@ -using Dapr.Client; +// ------------------------------------------------------------------------ +// Copyright 2023 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using Dapr.Client; namespace Cryptography.Examples { diff --git a/examples/Client/Cryptography/Examples/WrapUnwrapKeyExample.cs b/examples/Client/Cryptography/Examples/WrapUnwrapKeyExample.cs index 35953030f..339e15791 100644 --- a/examples/Client/Cryptography/Examples/WrapUnwrapKeyExample.cs +++ b/examples/Client/Cryptography/Examples/WrapUnwrapKeyExample.cs @@ -1,4 +1,17 @@ -using System.Security.Cryptography; +// ------------------------------------------------------------------------ +// Copyright 2023 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using System.Security.Cryptography; using System.Text; using Dapr.Client; diff --git a/examples/Client/Cryptography/Program.cs b/examples/Client/Cryptography/Program.cs index 46427deb5..2981b3280 100644 --- a/examples/Client/Cryptography/Program.cs +++ b/examples/Client/Cryptography/Program.cs @@ -1,4 +1,17 @@ -using Cryptography; +// ------------------------------------------------------------------------ +// Copyright 2023 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using Cryptography; using Cryptography.Examples; namespace Samples.Client From 9c5ef09861570ea25f0dc05a66cd15663fcb65c2 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Fri, 22 Dec 2023 17:47:12 -0600 Subject: [PATCH 05/43] Updated to properly support Crypto API this time Signed-off-by: Whit Waldo --- src/Dapr.Client/CryptographyEnums.cs | 63 ++++ src/Dapr.Client/DaprClient.cs | 391 ++++++++++++--------- src/Dapr.Client/DaprClientGrpc.cs | 506 +++++++++++++++++---------- src/Dapr.Client/EnumExtensions.cs | 24 ++ 4 files changed, 623 insertions(+), 361 deletions(-) create mode 100644 src/Dapr.Client/CryptographyEnums.cs create mode 100644 src/Dapr.Client/EnumExtensions.cs diff --git a/src/Dapr.Client/CryptographyEnums.cs b/src/Dapr.Client/CryptographyEnums.cs new file mode 100644 index 000000000..6611390ff --- /dev/null +++ b/src/Dapr.Client/CryptographyEnums.cs @@ -0,0 +1,63 @@ +using System.Runtime.Serialization; + +namespace Dapr.Client +{ + /// + /// The cipher used for data encryption operations. + /// + public enum DataEncryptionCipher + { + /// + /// The default data encryption cipher used, this represents AES GCM. + /// + [EnumMember(Value = "aes-gcm")] + AesGcm, + /// + /// Represents the ChaCha20-Poly1305 data encryption cipher. + /// + [EnumMember(Value = "chacha20-poly1305")] + ChaCha20Poly1305 + }; + + /// + /// The algorithm used for key wrapping cryptographic operations. + /// + public enum KeyWrapAlgorithm + { + /// + /// Represents the AES key wrap algorithm. + /// + [EnumMember(Value="AES")] + Aes, + /// + /// An alias for the AES key wrap algorithm. + /// + [EnumMember(Value="AES")] + A256kw, + /// + /// Represents the AES 128 CBC key wrap algorithm. + /// + [EnumMember(Value="A128CBC")] + A128cbc, + /// + /// Represents the AES 192 CBC key wrap algorithm. + /// + [EnumMember(Value="A192CBC")] + A192cbc, + /// + /// Represents the AES 256 CBC key wrap algorithm. + /// + [EnumMember(Value="A256CBC")] + A256cbc, + /// + /// Represents the RSA key wrap algorithm. + /// + [EnumMember(Value="RSA")] + Rsa, + /// + /// An alias for the RSA key wrap algorithm. + /// + [EnumMember(Value="RSA")] + RsaOaep256 //Alias for RSA + } +} diff --git a/src/Dapr.Client/DaprClient.cs b/src/Dapr.Client/DaprClient.cs index 0f5cbbebb..691c6a616 100644 --- a/src/Dapr.Client/DaprClient.cs +++ b/src/Dapr.Client/DaprClient.cs @@ -940,199 +940,250 @@ public abstract Task UnsubscribeConfiguration( string id, CancellationToken cancellationToken = default); - /// - /// Retrieves the value of the specified key from the vault. - /// - /// The name of the vault resource used by the operation. - /// The name of the key to retrieve the value of. - /// The format to use for the key result. - /// A that can be used to cancel the operation. - /// The name (and possibly version as name/version) of the key and its public key. - [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public abstract Task<(string Name, string PublicKey)> GetKeyAsync(string vaultResourceName, string keyName, SubtleGetKeyRequest.Types.KeyFormat keyFormat, - CancellationToken cancellationToken = default); + #region Cryptography /// - /// Encrypts an array of bytes using the Dapr Cryptography functionality. + /// Encrypts an array of bytes using the Dapr Cryptography encryption functionality. /// /// The name of the vault resource used by the operation. /// The bytes of the plaintext value to encrypt. - /// The name of the algorithm that should be used to perform the encryption. - /// The name of the key used to perform the encryption operation. - /// The bytes comprising the nonce. - /// Any associated data when using AEAD ciphers. + /// The name of the algorithm used to wrap the encryption key. + /// The name of the key to use from the Vault for the encryption operation. + /// The name of the cipher to use for the encryption operation. /// A that can be used to cancel the operation. - /// The array of encrypted bytes. + /// An array of encrypted bytes. [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public abstract Task<(byte[] CipherTextBytes, byte[] AuthenticationTag)> EncryptAsync( - string vaultResourceName, - byte[] plainTextBytes, - string algorithm, - string keyName, - byte[] nonce, - byte[] associatedData, + public abstract Task EncryptAsync(string vaultResourceName, byte[] plainTextBytes, + KeyWrapAlgorithm algorithm, string keyName, DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, CancellationToken cancellationToken = default); /// - /// Encrypts an array of bytes using the Dapr Cryptography functionality. + /// Encrypts an array of bytes using the Dapr Cryptography encryption functionality. /// /// The name of the vault resource used by the operation. /// The bytes of the plaintext value to encrypt. - /// The name of the algorithm that should be used to perform the encryption. - /// The name of the key used to perform the encryption operation. - /// The bytes comprising the nonce. - /// A that can be used to cancel the operation. - /// The array of encrypted bytes. - [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public async Task<(byte[] CipherTextBytes, byte[] AuthenticationTag)> EncryptAsync( - string vaultResourceName, - byte[] plainTextBytes, - string algorithm, - string keyName, - byte[] nonce, - CancellationToken cancellationToken = default) => - await EncryptAsync(vaultResourceName, plainTextBytes, algorithm, keyName, nonce, Array.Empty(), - cancellationToken); - - /// - /// Decrypts an array of bytes using the Dapr Cryptography functionality. - /// - /// The name of the vault the key is retrieved from for the decryption operation. - /// The array of bytes to decrypt. - /// A that can be used to cancel the operation. - /// The algorithm to use to perform the decryption operation. - /// The name of the key used for the decryption. - /// The nonce value used. - /// - /// Any associated data when using AEAD ciphers. - /// The array of plaintext bytes. - [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public abstract Task DecryptAsync(string vaultResourceName, byte[] cipherTextBytes, - string algorithm, string keyName, byte[] nonce, byte[] tag, byte[] associatedData, - CancellationToken cancellationToken = default); - - /// - /// Decrypts an array of bytes using the Dapr Cryptography functionality. - /// - /// The name of the vault the key is retrieved from for the decryption operation. - /// The array of bytes to decrypt. - /// A that can be used to cancel the operation. - /// The algorithm to use to perform the decryption operation. - /// The name of the key used for the decryption. - /// The nonce value used. - /// - /// The array of plaintext bytes. - [Obsolete( - "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public async Task DecryptAsync(string vaultResourceName, byte[] cipherTextBytes, - string algorithm, string keyName, byte[] nonce, byte[] tag, CancellationToken cancellationToken = default) => - await DecryptAsync(vaultResourceName, cipherTextBytes, algorithm, keyName, nonce, tag, Array.Empty(), cancellationToken); - - /// - /// Wraps the plaintext key using another. - /// - /// The name of the vault to retrieve the key from. - /// The plaintext bytes comprising the key to wrap. - /// The name of the key used to wrap the value. - /// The algorithm to use to perform the wrap operation. - /// The none used. - /// Any associated data when using AEAD ciphers. + /// The name of the algorithm used to wrap the encryption key. + /// The name of the key to use from the Vault for the encryption operation. + /// The name (and optionally version) of the decryption key to specify should be used. + /// The name of the cipher to use for the encryption operation. /// A that can be used to cancel the operation. - /// The bytes comprising the wrapped plain-text key and the authentication tag, if applicable. + /// An array of encrypted bytes. [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public abstract Task<(byte[] WrappedKey, byte[] AuthenticationTag)> WrapKeyAsync(string vaultResourceName, byte[] plainTextKey, string keyName, string algorithm, byte[] nonce, byte[] associatedData, + public abstract Task EncryptAsync(string vaultResourceName, byte[] plainTextBytes, + KeyWrapAlgorithm algorithm, string keyName, string decryptionKeyName, DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, CancellationToken cancellationToken = default); /// - /// Wraps the plaintext key using another. + /// Decrypts the specified cipher text bytes using the Dapr Cryptography encryption functionality. /// - /// The name of the vault to retrieve the key from. - /// The plaintext bytes comprising the key to wrap. - /// The name of the key used to wrap the value. - /// The algorithm to use to perform the wrap operation. - /// The none used. - /// A that can be used to cancel the operation. - /// The bytes comprising the unwrapped key and the authentication tag, if applicable. - [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public async Task<(byte[] WrappedKey, byte[] AuthenticationTag)> WrapKeyAsync(string vaultResourceName, byte[] plainTextKey, string keyName, string algorithm, - byte[] nonce, CancellationToken cancellationToken = default) => await WrapKeyAsync(vaultResourceName, plainTextKey, - keyName, algorithm, nonce, Array.Empty(), cancellationToken); - - /// - /// Used to unwrap the specified key. - /// - /// The name of the vault to retrieve the key from. - /// The byte comprising the wrapped key. - /// The algorithm to use in unwrapping the key. - /// The name of the key used to unwrap the wrapped key bytes. - /// The nonce value. - /// The bytes comprising the authentication tag. - /// Any associated data when using AEAD ciphers. - /// A that can be used to cancel the operation. - /// The bytes comprising the unwrapped key. - [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public abstract Task UnwrapKeyAsync(string vaultResourceName, byte[] wrappedKey, string algorithm, string keyName, byte[] nonce, byte[] tag, byte[] associatedData, - CancellationToken cancellationToken = default); - - /// - /// Used to unwrap the specified key. - /// - /// The name of the vault to retrieve the key from. - /// The byte comprising the wrapped key. - /// The algorithm to use in unwrapping the key. - /// The name of the key used to unwrap the wrapped key bytes. - /// The nonce value. - /// The bytes comprising the authentication tag. - /// A that can be used to cancel the operation. - /// The bytes comprising the unwrapped key. - [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public async Task UnwrapKeyAsync(string vaultResourceName, byte[] wrappedKey, string algorithm, string keyName, - byte[] nonce, byte[] tag, - CancellationToken cancellationToken = default) => await UnwrapKeyAsync(vaultResourceName, - wrappedKey, algorithm, keyName, nonce, Array.Empty(), Array.Empty(), cancellationToken); - - /// - /// Used to unwrap the specified key. - /// - /// The name of the vault to retrieve the key from. - /// The byte comprising the wrapped key. - /// The algorithm to use in unwrapping the key. - /// The name of the key used to unwrap the wrapped key bytes. - /// The nonce value. - /// A that can be used to cancel the operation. - /// The bytes comprising the unwrapped key. - [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public async Task UnwrapKeyAsync(string vaultResourceName, byte[] wrappedKey, string algorithm, string keyName, - byte[] nonce, CancellationToken cancellationToken = default) => await UnwrapKeyAsync(vaultResourceName, - wrappedKey, algorithm, keyName, nonce, Array.Empty(), Array.Empty(), cancellationToken); - - /// - /// Creates a signature of a digest value. - /// - /// The name of the vault to retrieve the key from. - /// The digest value to create the signature for. - /// The algorithm used to create the signature. - /// The name of the key used. - /// A that can be used to cancel the operation. - /// The bytes comprising the signature. - [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public abstract Task SignAsync(string vaultResourceName, byte[] digest, string algorithm, string keyName, - CancellationToken cancellationToken = default); - - /// - /// Validates a signature. - /// - /// The name of the vault to retrieve the key from. - /// The digest to validate the signature with. - /// The signature to validate. - /// The algorithm to validate the signature with. - /// The name of the key used. + /// The name of the vault resource used by the operation. + /// The byte of the cipher text value to decrypt. + /// The name of the key to use from the Vault for the decryption operation. /// A that can be used to cancel the operation. - /// True if the signature verification is successful; otherwise false. + /// An array of decrypted bytes. [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public abstract Task VerifyAsync(string vaultResourceName, byte[] digest, byte[] signature, string algorithm, string keyName, + public abstract Task DecryptAsync(string vaultResourceName, byte[] cipherTextBytes, string keyName, CancellationToken cancellationToken = default); + #endregion + + #region Cryptography - Subtle API + + ///// + ///// Retrieves the value of the specified key from the vault. + ///// + ///// The name of the vault resource used by the operation. + ///// The name of the key to retrieve the value of. + ///// The format to use for the key result. + ///// A that can be used to cancel the operation. + ///// The name (and possibly version as name/version) of the key and its public key. + //[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + //public abstract Task<(string Name, string PublicKey)> GetKeyAsync(string vaultResourceName, string keyName, SubtleGetKeyRequest.Types.KeyFormat keyFormat, + // CancellationToken cancellationToken = default); + + ///// + ///// Encrypts an array of bytes using the Dapr Cryptography functionality. + ///// + ///// The name of the vault resource used by the operation. + ///// The bytes of the plaintext value to encrypt. + ///// The name of the algorithm that should be used to perform the encryption. + ///// The name of the key used to perform the encryption operation. + ///// The bytes comprising the nonce. + ///// Any associated data when using AEAD ciphers. + ///// A that can be used to cancel the operation. + ///// The array of encrypted bytes. + //[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + //public abstract Task<(byte[] CipherTextBytes, byte[] AuthenticationTag)> EncryptAsync( + // string vaultResourceName, + // byte[] plainTextBytes, + // string algorithm, + // string keyName, + // byte[] nonce, + // byte[] associatedData, + // CancellationToken cancellationToken = default); + + ///// + ///// Encrypts an array of bytes using the Dapr Cryptography functionality. + ///// + ///// The name of the vault resource used by the operation. + ///// The bytes of the plaintext value to encrypt. + ///// The name of the algorithm that should be used to perform the encryption. + ///// The name of the key used to perform the encryption operation. + ///// The bytes comprising the nonce. + ///// A that can be used to cancel the operation. + ///// The array of encrypted bytes. + //[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + //public async Task<(byte[] CipherTextBytes, byte[] AuthenticationTag)> EncryptAsync( + // string vaultResourceName, + // byte[] plainTextBytes, + // string algorithm, + // string keyName, + // byte[] nonce, + // CancellationToken cancellationToken = default) => + // await EncryptAsync(vaultResourceName, plainTextBytes, algorithm, keyName, nonce, Array.Empty(), + // cancellationToken); + + ///// + ///// Decrypts an array of bytes using the Dapr Cryptography functionality. + ///// + ///// The name of the vault the key is retrieved from for the decryption operation. + ///// The array of bytes to decrypt. + ///// A that can be used to cancel the operation. + ///// The algorithm to use to perform the decryption operation. + ///// The name of the key used for the decryption. + ///// The nonce value used. + ///// + ///// Any associated data when using AEAD ciphers. + ///// The array of plaintext bytes. + //[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + //public abstract Task DecryptAsync(string vaultResourceName, byte[] cipherTextBytes, + // string algorithm, string keyName, byte[] nonce, byte[] tag, byte[] associatedData, + // CancellationToken cancellationToken = default); + + ///// + ///// Decrypts an array of bytes using the Dapr Cryptography functionality. + ///// + ///// The name of the vault the key is retrieved from for the decryption operation. + ///// The array of bytes to decrypt. + ///// A that can be used to cancel the operation. + ///// The algorithm to use to perform the decryption operation. + ///// The name of the key used for the decryption. + ///// The nonce value used. + ///// + ///// The array of plaintext bytes. + //[Obsolete( + // "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + //public async Task DecryptAsync(string vaultResourceName, byte[] cipherTextBytes, + // string algorithm, string keyName, byte[] nonce, byte[] tag, CancellationToken cancellationToken = default) => + // await DecryptAsync(vaultResourceName, cipherTextBytes, algorithm, keyName, nonce, tag, Array.Empty(), cancellationToken); + + ///// + ///// Wraps the plaintext key using another. + ///// + ///// The name of the vault to retrieve the key from. + ///// The plaintext bytes comprising the key to wrap. + ///// The name of the key used to wrap the value. + ///// The algorithm to use to perform the wrap operation. + ///// The none used. + ///// Any associated data when using AEAD ciphers. + ///// A that can be used to cancel the operation. + ///// The bytes comprising the wrapped plain-text key and the authentication tag, if applicable. + //[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + //public abstract Task<(byte[] WrappedKey, byte[] AuthenticationTag)> WrapKeyAsync(string vaultResourceName, byte[] plainTextKey, string keyName, string algorithm, byte[] nonce, byte[] associatedData, + // CancellationToken cancellationToken = default); + + ///// + ///// Wraps the plaintext key using another. + ///// + ///// The name of the vault to retrieve the key from. + ///// The plaintext bytes comprising the key to wrap. + ///// The name of the key used to wrap the value. + ///// The algorithm to use to perform the wrap operation. + ///// The none used. + ///// A that can be used to cancel the operation. + ///// The bytes comprising the unwrapped key and the authentication tag, if applicable. + //[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + //public async Task<(byte[] WrappedKey, byte[] AuthenticationTag)> WrapKeyAsync(string vaultResourceName, byte[] plainTextKey, string keyName, string algorithm, + // byte[] nonce, CancellationToken cancellationToken = default) => await WrapKeyAsync(vaultResourceName, plainTextKey, + // keyName, algorithm, nonce, Array.Empty(), cancellationToken); + + ///// + ///// Used to unwrap the specified key. + ///// + ///// The name of the vault to retrieve the key from. + ///// The byte comprising the wrapped key. + ///// The algorithm to use in unwrapping the key. + ///// The name of the key used to unwrap the wrapped key bytes. + ///// The nonce value. + ///// The bytes comprising the authentication tag. + ///// Any associated data when using AEAD ciphers. + ///// A that can be used to cancel the operation. + ///// The bytes comprising the unwrapped key. + //[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + //public abstract Task UnwrapKeyAsync(string vaultResourceName, byte[] wrappedKey, string algorithm, string keyName, byte[] nonce, byte[] tag, byte[] associatedData, + // CancellationToken cancellationToken = default); + + ///// + ///// Used to unwrap the specified key. + ///// + ///// The name of the vault to retrieve the key from. + ///// The byte comprising the wrapped key. + ///// The algorithm to use in unwrapping the key. + ///// The name of the key used to unwrap the wrapped key bytes. + ///// The nonce value. + ///// The bytes comprising the authentication tag. + ///// A that can be used to cancel the operation. + ///// The bytes comprising the unwrapped key. + //[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + //public async Task UnwrapKeyAsync(string vaultResourceName, byte[] wrappedKey, string algorithm, string keyName, + // byte[] nonce, byte[] tag, + // CancellationToken cancellationToken = default) => await UnwrapKeyAsync(vaultResourceName, + // wrappedKey, algorithm, keyName, nonce, Array.Empty(), Array.Empty(), cancellationToken); + + ///// + ///// Used to unwrap the specified key. + ///// + ///// The name of the vault to retrieve the key from. + ///// The byte comprising the wrapped key. + ///// The algorithm to use in unwrapping the key. + ///// The name of the key used to unwrap the wrapped key bytes. + ///// The nonce value. + ///// A that can be used to cancel the operation. + ///// The bytes comprising the unwrapped key. + //[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + //public async Task UnwrapKeyAsync(string vaultResourceName, byte[] wrappedKey, string algorithm, string keyName, + // byte[] nonce, CancellationToken cancellationToken = default) => await UnwrapKeyAsync(vaultResourceName, + // wrappedKey, algorithm, keyName, nonce, Array.Empty(), Array.Empty(), cancellationToken); + + ///// + ///// Creates a signature of a digest value. + ///// + ///// The name of the vault to retrieve the key from. + ///// The digest value to create the signature for. + ///// The algorithm used to create the signature. + ///// The name of the key used. + ///// A that can be used to cancel the operation. + ///// The bytes comprising the signature. + //[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + //public abstract Task SignAsync(string vaultResourceName, byte[] digest, string algorithm, string keyName, + // CancellationToken cancellationToken = default); + + ///// + ///// Validates a signature. + ///// + ///// The name of the vault to retrieve the key from. + ///// The digest to validate the signature with. + ///// The signature to validate. + ///// The algorithm to validate the signature with. + ///// The name of the key used. + ///// A that can be used to cancel the operation. + ///// True if the signature verification is successful; otherwise false. + //[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + //public abstract Task VerifyAsync(string vaultResourceName, byte[] digest, byte[] signature, string algorithm, string keyName, + // CancellationToken cancellationToken = default); + + #endregion + /// /// Attempt to lock the given resourceId with response indicating success. /// diff --git a/src/Dapr.Client/DaprClientGrpc.cs b/src/Dapr.Client/DaprClientGrpc.cs index dac6c8aab..13789a77c 100644 --- a/src/Dapr.Client/DaprClientGrpc.cs +++ b/src/Dapr.Client/DaprClientGrpc.cs @@ -1381,263 +1381,387 @@ public override async Task UnsubscribeConfigur #region Cryptography - /// - [Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override async Task<(string Name, string PublicKey)> GetKeyAsync(string vaultResourceName, string keyName, Autogenerated.SubtleGetKeyRequest.Types.KeyFormat keyFormat, - CancellationToken cancellationToken = default) + private async Task EncryptAsync(string vaultResourceName, byte[] plainTextBytes, + KeyWrapAlgorithm algorithm, string keyName, DataEncryptionCipher dataEncryptionCipher, + string decryptionKeyName, bool omitDecryptionKey, CancellationToken cancellationToken = default) { ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); - var envelope = new Autogenerated.SubtleGetKeyRequest() + + var encryptRequestOptions = new Autogenerated.EncryptRequestOptions { - ComponentName = vaultResourceName, Format = keyFormat, Name = keyName + ComponentName = vaultResourceName, + DataEncryptionCipher = dataEncryptionCipher.GetValueFromEnumMember(), + KeyName = keyName, + KeyWrapAlgorithm = algorithm.GetValueFromEnumMember(), + OmitDecryptionKeyName = omitDecryptionKey }; - var options = CreateCallOptions(headers: null, cancellationToken); - Autogenerated.SubtleGetKeyResponse response; - - try - { - response = await client.SubtleGetKeyAlpha1Async(envelope, options); - } - catch (RpcException ex) + if (!omitDecryptionKey) { - throw new DaprException( - "Cryptography operation failed: the Dapr endpoint indicated a failure. See InnerException for details", ex); + ArgumentVerifier.ThrowIfNullOrEmpty(decryptionKeyName, nameof(decryptionKeyName)); + encryptRequestOptions.DecryptionKeyName = decryptionKeyName; } + + var options = CreateCallOptions(headers: null, cancellationToken); + var duplexStream = client.EncryptAlpha1(options); - return (response.Name, response.PublicKey); - } + //Start with passing the metadata about the encryption request itself in the first message + await duplexStream.RequestStream.WriteAsync( + new Autogenerated.EncryptRequest {Options = encryptRequestOptions}, cancellationToken); - /// - [Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override async Task<(byte[] CipherTextBytes, byte[] AuthenticationTag)> EncryptAsync(string vaultResourceName, byte[] plainTextBytes, string algorithm, - string keyName, byte[] nonce, byte[] associatedData, CancellationToken cancellationToken = default) - { - ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); - ArgumentVerifier.ThrowIfNullOrEmpty(algorithm, nameof(algorithm)); - ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); + //Send the plaintext bytes in 4 kb blocks in subsequent messages + const int blockSize = 4 * 1024; // 4 kb + var blockCount = (int)Math.Ceiling((double)plainTextBytes.Length / blockSize); - var envelope = new Autogenerated.SubtleEncryptRequest + for (var a = 0; a < blockCount; a++) { - ComponentName = vaultResourceName, - Algorithm = algorithm, - KeyName = keyName, - Nonce = ByteString.CopyFrom(nonce), - Plaintext = ByteString.CopyFrom(plainTextBytes), - AssociatedData = ByteString.CopyFrom(associatedData) - }; + var offset = a * blockSize; + var count = Math.Min(blockSize, plainTextBytes.Length - offset); + + await duplexStream.RequestStream.WriteAsync(new Autogenerated.EncryptRequest + { + Payload = new Autogenerated.StreamPayload + { + Data = ByteString.CopyFrom(plainTextBytes, offset, count), + Seq = (ulong)a + } + }, cancellationToken); + } - var options = CreateCallOptions(headers: null, cancellationToken); - Autogenerated.SubtleEncryptResponse response; + //Send the completion message + await duplexStream.RequestStream.CompleteAsync(); - try - { - response = await client.SubtleEncryptAlpha1Async(envelope, options); - } - catch (RpcException ex) + //Set up how the response is handled since we know it's of finite length + var returnedBytes = new List(); + await foreach (var encryptResponse in duplexStream.ResponseStream.ReadAllAsync(cancellationToken).ConfigureAwait(false)) { - throw new DaprException( - "Cryptography operation failed: the Dapr endpoint indicated a failure. See InnerException for details", - ex); + returnedBytes.AddRange(encryptResponse.Payload.Data.ToByteArray()); } - return (response.Ciphertext.ToByteArray(), response.Tag.ToByteArray() ?? Array.Empty()); + return returnedBytes.ToArray(); } - /// - [Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override async Task DecryptAsync(string vaultResourceName, byte[] cipherTextBytes, string algorithm, string keyName, byte[] nonce, byte[] tag, - byte[] associatedData, CancellationToken cancellationToken = default) + /// + [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public override Task EncryptAsync( + string vaultResourceName, byte[] plainTextBytes, KeyWrapAlgorithm algorithm, + string keyName, DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, + CancellationToken cancellationToken = default) => EncryptAsync(vaultResourceName, plainTextBytes, + algorithm, keyName, dataEncryptionCipher, string.Empty, true, cancellationToken); + + /// + [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public override Task EncryptAsync( + string vaultResourceName, byte[] plainTextBytes, KeyWrapAlgorithm algorithm, + string keyName, string decryptionKeyName, + DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, + CancellationToken cancellationToken = default) => EncryptAsync(vaultResourceName, plainTextBytes, + algorithm, keyName, dataEncryptionCipher, + decryptionKeyName, false, cancellationToken); + + /// + [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public override async Task DecryptAsync(string vaultResourceName, byte[] cipherTextBytes, string keyName, CancellationToken cancellationToken = default) { ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); - ArgumentVerifier.ThrowIfNullOrEmpty(algorithm, nameof(algorithm)); ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); - var envelope = new Autogenerated.SubtleDecryptRequest + var decryptRequestOptions = new Autogenerated.DecryptRequestOptions { - ComponentName = vaultResourceName, - Algorithm = algorithm, - KeyName = keyName, - Nonce = ByteString.CopyFrom(nonce), - Ciphertext = ByteString.CopyFrom(cipherTextBytes), - AssociatedData = ByteString.CopyFrom(associatedData), - Tag = ByteString.CopyFrom(tag) + ComponentName = vaultResourceName, KeyName = keyName }; var options = CreateCallOptions(headers: null, cancellationToken); - Autogenerated.SubtleDecryptResponse response; + var duplexStream = client.DecryptAlpha1(options); - try + //Start with passing the metadata about the decryption request itself in the first message + await duplexStream.RequestStream.WriteAsync(new Autogenerated.DecryptRequest { - response = await client.SubtleDecryptAlpha1Async(envelope, options); + Options = decryptRequestOptions + }, cancellationToken); + + //Send the cipher text bytes in 4 kb blocks in subsequent messages + const int blockSize = 4 * 1024; // 4 kb + var blockCount = (int)Math.Ceiling((double)cipherTextBytes.Length / blockSize); + + for (var a = 0; a < blockCount; a++) + { + var offset = a * blockSize; + var count = Math.Min(blockSize, cipherTextBytes.Length - offset); + + await duplexStream.RequestStream.WriteAsync( + new Autogenerated.DecryptRequest + { + Payload = new Autogenerated.StreamPayload + { + Data = ByteString.CopyFrom(cipherTextBytes, offset, count), Seq = (ulong)a + } + }, cancellationToken); } - catch (RpcException ex) + + //Send the completion message + await duplexStream.RequestStream.CompleteAsync(); + + //Set up how the response is handled since we know it's of finite length + var returnedBytes = new List(); + await foreach (var decryptedResponse in duplexStream.ResponseStream.ReadAllAsync(cancellationToken) + .ConfigureAwait(false)) { - throw new DaprException( - "Cryptography operation failed: the Dapr endpoint included a failure. See InnerException for details", ex); + returnedBytes.AddRange(decryptedResponse.Payload.Data.ToByteArray()); } - return response.Plaintext.ToByteArray(); + return returnedBytes.ToArray(); } + + #region Subtle Crypto Implementation - /// - [Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override async Task<(byte[] WrappedKey, byte[] AuthenticationTag)> WrapKeyAsync(string vaultResourceName, byte[] plainTextKey, string keyName, - string algorithm, byte[] nonce, byte[] associatedData, CancellationToken cancellationToken = default) - { - ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); - ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); - ArgumentVerifier.ThrowIfNullOrEmpty(algorithm, nameof(algorithm)); + ///// + //[Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + //public override async Task<(string Name, string PublicKey)> GetKeyAsync(string vaultResourceName, string keyName, Autogenerated.SubtleGetKeyRequest.Types.KeyFormat keyFormat, + // CancellationToken cancellationToken = default) + //{ + // ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); + // ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); - var envelope = new Autogenerated.SubtleWrapKeyRequest - { - ComponentName = vaultResourceName, - Algorithm = algorithm, - KeyName = keyName, - Nonce = ByteString.CopyFrom(nonce), - PlaintextKey = ByteString.CopyFrom(plainTextKey), - AssociatedData = ByteString.CopyFrom(associatedData) - }; + // var envelope = new Autogenerated.SubtleGetKeyRequest() + // { + // ComponentName = vaultResourceName, Format = keyFormat, Name = keyName + // }; - var options = CreateCallOptions(headers: null, cancellationToken); - Autogenerated.SubtleWrapKeyResponse response; + // var options = CreateCallOptions(headers: null, cancellationToken); + // Autogenerated.SubtleGetKeyResponse response; - try - { - response = await client.SubtleWrapKeyAlpha1Async(envelope, options); - } - catch (RpcException ex) - { - throw new DaprException( - "Cryptography operation failed: the Dapr endpoint included a failure. See InnerException for details", - ex); - } + // try + // { + // response = await client.SubtleGetKeyAlpha1Async(envelope, options); + // } + // catch (RpcException ex) + // { + // throw new DaprException( + // "Cryptography operation failed: the Dapr endpoint indicated a failure. See InnerException for details", ex); + // } - return (response.WrappedKey.ToByteArray(), response.Tag.ToByteArray() ?? Array.Empty()); - } + // return (response.Name, response.PublicKey); + //} - /// - [Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override async Task UnwrapKeyAsync(string vaultResourceName, byte[] wrappedKey, string algorithm, - string keyName, byte[] nonce, byte[] tag, byte[] associatedData, CancellationToken cancellationToken = default) - { - ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); - ArgumentVerifier.ThrowIfNullOrEmpty(algorithm, nameof(algorithm)); - ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); + ///// + //[Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + //public override async Task<(byte[] CipherTextBytes, byte[] AuthenticationTag)> EncryptAsync(string vaultResourceName, byte[] plainTextBytes, string algorithm, + // string keyName, byte[] nonce, byte[] associatedData, CancellationToken cancellationToken = default) + //{ + // ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); + // ArgumentVerifier.ThrowIfNullOrEmpty(algorithm, nameof(algorithm)); + // ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); - var envelope = new Autogenerated.SubtleUnwrapKeyRequest - { - ComponentName = vaultResourceName, - WrappedKey = ByteString.CopyFrom(wrappedKey), - AssociatedData = ByteString.CopyFrom(associatedData), - Algorithm = algorithm, - KeyName = keyName, - Nonce = ByteString.CopyFrom(nonce), - Tag = ByteString.CopyFrom(tag) - }; + // var envelope = new Autogenerated.SubtleEncryptRequest + // { + // ComponentName = vaultResourceName, + // Algorithm = algorithm, + // KeyName = keyName, + // Nonce = ByteString.CopyFrom(nonce), + // Plaintext = ByteString.CopyFrom(plainTextBytes), + // AssociatedData = ByteString.CopyFrom(associatedData) + // }; - var options = CreateCallOptions(headers: null, cancellationToken); - Autogenerated.SubtleUnwrapKeyResponse response; + // var options = CreateCallOptions(headers: null, cancellationToken); + // Autogenerated.SubtleEncryptResponse response; - try - { - response = await client.SubtleUnwrapKeyAlpha1Async(envelope, options); - } - catch (RpcException ex) - { - throw new DaprException( - "Cryptography operation failed: the Dapr endpoint included a failure. See InnerException for details", - ex); - } + // try + // { + // response = await client.SubtleEncryptAlpha1Async(envelope, options); + // } + // catch (RpcException ex) + // { + // throw new DaprException( + // "Cryptography operation failed: the Dapr endpoint indicated a failure. See InnerException for details", + // ex); + // } - return response.PlaintextKey.ToByteArray(); - } + // return (response.Ciphertext.ToByteArray(), response.Tag.ToByteArray() ?? Array.Empty()); + //} - /// - [Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override async Task SignAsync(string vaultResourceName, byte[] digest, string algorithm, string keyName, CancellationToken cancellationToken = default) - { - ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); - ArgumentVerifier.ThrowIfNullOrEmpty(algorithm, nameof(algorithm)); - ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); + ///// + //[Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + //public override async Task DecryptAsync(string vaultResourceName, byte[] cipherTextBytes, string algorithm, string keyName, byte[] nonce, byte[] tag, + // byte[] associatedData, CancellationToken cancellationToken = default) + //{ + // ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); + // ArgumentVerifier.ThrowIfNullOrEmpty(algorithm, nameof(algorithm)); + // ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); - var envelope = new Autogenerated.SubtleSignRequest - { - ComponentName = vaultResourceName, - Digest = ByteString.CopyFrom(digest), - Algorithm = algorithm, - KeyName = keyName - }; + // var envelope = new Autogenerated.SubtleDecryptRequest + // { + // ComponentName = vaultResourceName, + // Algorithm = algorithm, + // KeyName = keyName, + // Nonce = ByteString.CopyFrom(nonce), + // Ciphertext = ByteString.CopyFrom(cipherTextBytes), + // AssociatedData = ByteString.CopyFrom(associatedData), + // Tag = ByteString.CopyFrom(tag) + // }; - var options = CreateCallOptions(headers: null, cancellationToken); - Autogenerated.SubtleSignResponse response; + // var options = CreateCallOptions(headers: null, cancellationToken); + // Autogenerated.SubtleDecryptResponse response; - try - { - response = await client.SubtleSignAlpha1Async(envelope, options); - } - catch (RpcException ex) - { - throw new DaprException( - "Cryptography operation failed: the Dapr endpoint included a failure. See InnerException for details", - ex); - } + // try + // { + // response = await client.SubtleDecryptAlpha1Async(envelope, options); + // } + // catch (RpcException ex) + // { + // throw new DaprException( + // "Cryptography operation failed: the Dapr endpoint included a failure. See InnerException for details", ex); + // } - return response.Signature.ToByteArray(); - } + // return response.Plaintext.ToByteArray(); + //} - /// - [Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override async Task VerifyAsync(string vaultResourceName, byte[] digest, byte[] signature, - string algorithm, string keyName, CancellationToken cancellationToken = default) - { - ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); - ArgumentVerifier.ThrowIfNullOrEmpty(algorithm, nameof(algorithm)); - ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); + ///// + //[Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + //public override async Task<(byte[] WrappedKey, byte[] AuthenticationTag)> WrapKeyAsync(string vaultResourceName, byte[] plainTextKey, string keyName, + // string algorithm, byte[] nonce, byte[] associatedData, CancellationToken cancellationToken = default) + //{ + // ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); + // ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); + // ArgumentVerifier.ThrowIfNullOrEmpty(algorithm, nameof(algorithm)); - var envelope = new Autogenerated.SubtleVerifyRequest - { - ComponentName = vaultResourceName, - Algorithm = algorithm, - KeyName = keyName, - Signature = ByteString.CopyFrom(signature), - Digest = ByteString.CopyFrom(digest) - }; + // var envelope = new Autogenerated.SubtleWrapKeyRequest + // { + // ComponentName = vaultResourceName, + // Algorithm = algorithm, + // KeyName = keyName, + // Nonce = ByteString.CopyFrom(nonce), + // PlaintextKey = ByteString.CopyFrom(plainTextKey), + // AssociatedData = ByteString.CopyFrom(associatedData) + // }; - var options = CreateCallOptions(headers: null, cancellationToken); - Autogenerated.SubtleVerifyResponse response; + // var options = CreateCallOptions(headers: null, cancellationToken); + // Autogenerated.SubtleWrapKeyResponse response; - try - { - response = await client.SubtleVerifyAlpha1Async(envelope, options); - } - catch (RpcException ex) - { - throw new DaprException( - "Cryptography operation failed: the Dapr endpoint included a failure. See InnerException for details", - ex); - } + // try + // { + // response = await client.SubtleWrapKeyAlpha1Async(envelope, options); + // } + // catch (RpcException ex) + // { + // throw new DaprException( + // "Cryptography operation failed: the Dapr endpoint included a failure. See InnerException for details", + // ex); + // } - return response.Valid; - } + // return (response.WrappedKey.ToByteArray(), response.Tag.ToByteArray() ?? Array.Empty()); + //} + ///// + //[Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + //public override async Task UnwrapKeyAsync(string vaultResourceName, byte[] wrappedKey, string algorithm, + // string keyName, byte[] nonce, byte[] tag, byte[] associatedData, CancellationToken cancellationToken = default) + //{ + // ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); + // ArgumentVerifier.ThrowIfNullOrEmpty(algorithm, nameof(algorithm)); + // ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); + + // var envelope = new Autogenerated.SubtleUnwrapKeyRequest + // { + // ComponentName = vaultResourceName, + // WrappedKey = ByteString.CopyFrom(wrappedKey), + // AssociatedData = ByteString.CopyFrom(associatedData), + // Algorithm = algorithm, + // KeyName = keyName, + // Nonce = ByteString.CopyFrom(nonce), + // Tag = ByteString.CopyFrom(tag) + // }; + + // var options = CreateCallOptions(headers: null, cancellationToken); + // Autogenerated.SubtleUnwrapKeyResponse response; + + // try + // { + // response = await client.SubtleUnwrapKeyAlpha1Async(envelope, options); + // } + // catch (RpcException ex) + // { + // throw new DaprException( + // "Cryptography operation failed: the Dapr endpoint included a failure. See InnerException for details", + // ex); + // } + + // return response.PlaintextKey.ToByteArray(); + //} ///// - //public override async IAsyncEnumerable DecryptAsync(byte[] cipherTextBytes, [EnumeratorCancellation] CancellationToken cancellationToken = default) + //[Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + //public override async Task SignAsync(string vaultResourceName, byte[] digest, string algorithm, string keyName, CancellationToken cancellationToken = default) //{ - // Autogenerated.DecryptRequest request = new Autogenerated.DecryptRequest() { }; + // ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); + // ArgumentVerifier.ThrowIfNullOrEmpty(algorithm, nameof(algorithm)); + // ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); + + // var envelope = new Autogenerated.SubtleSignRequest + // { + // ComponentName = vaultResourceName, + // Digest = ByteString.CopyFrom(digest), + // Algorithm = algorithm, + // KeyName = keyName + // }; + // var options = CreateCallOptions(headers: null, cancellationToken); + // Autogenerated.SubtleSignResponse response; + + // try + // { + // response = await client.SubtleSignAlpha1Async(envelope, options); + // } + // catch (RpcException ex) + // { + // throw new DaprException( + // "Cryptography operation failed: the Dapr endpoint included a failure. See InnerException for details", + // ex); + // } + + // return response.Signature.ToByteArray(); + //} + + ///// + //[Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + //public override async Task VerifyAsync(string vaultResourceName, byte[] digest, byte[] signature, + // string algorithm, string keyName, CancellationToken cancellationToken = default) + //{ + // ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); + // ArgumentVerifier.ThrowIfNullOrEmpty(algorithm, nameof(algorithm)); + // ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); + + // var envelope = new Autogenerated.SubtleVerifyRequest + // { + // ComponentName = vaultResourceName, + // Algorithm = algorithm, + // KeyName = keyName, + // Signature = ByteString.CopyFrom(signature), + // Digest = ByteString.CopyFrom(digest) + // }; - // var resp = client.DecryptAlpha1(options); + // var options = CreateCallOptions(headers: null, cancellationToken); + // Autogenerated.SubtleVerifyResponse response; - // await foreach (var result in resp.ResponseStream.ReadAllAsync(cancellationToken)) + // try // { - // yield return result.ToByteArray(); + // response = await client.SubtleVerifyAlpha1Async(envelope, options); // } + // catch (RpcException ex) + // { + // throw new DaprException( + // "Cryptography operation failed: the Dapr endpoint included a failure. See InnerException for details", + // ex); + // } + + // return response.Valid; //} + #endregion + + #endregion #region Distributed Lock API diff --git a/src/Dapr.Client/EnumExtensions.cs b/src/Dapr.Client/EnumExtensions.cs new file mode 100644 index 000000000..ab5f6afd6 --- /dev/null +++ b/src/Dapr.Client/EnumExtensions.cs @@ -0,0 +1,24 @@ +using System; +using System.Runtime.Serialization; + +namespace Dapr.Client +{ + internal static class EnumExtensions + { + /// + /// Reads the value of an enum out of the attached attribute. + /// + /// The enum. + /// The value of the enum to pull the value for. + /// + public static string GetValueFromEnumMember(this T value) where T : Enum + { + var memberInfo = typeof(T).GetMember(value.ToString()); + if (memberInfo.Length <= 0) + return value.ToString(); + + var attributes = memberInfo[0].GetCustomAttributes(typeof(EnumMemberAttribute), false); + return attributes.Length > 0 ? ((EnumMemberAttribute)attributes[0]).Value : value.ToString(); + } + } +} From 14f23df123dcc49e52299b8fe545f86914473d07 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Fri, 22 Dec 2023 17:51:54 -0600 Subject: [PATCH 06/43] Added copyright statements Signed-off-by: Whit Waldo --- src/Dapr.Client/CryptographyEnums.cs | 15 ++++++++++++++- src/Dapr.Client/EnumExtensions.cs | 15 ++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/Dapr.Client/CryptographyEnums.cs b/src/Dapr.Client/CryptographyEnums.cs index 6611390ff..1f136e338 100644 --- a/src/Dapr.Client/CryptographyEnums.cs +++ b/src/Dapr.Client/CryptographyEnums.cs @@ -1,4 +1,17 @@ -using System.Runtime.Serialization; +// ------------------------------------------------------------------------ +// Copyright 2023 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using System.Runtime.Serialization; namespace Dapr.Client { diff --git a/src/Dapr.Client/EnumExtensions.cs b/src/Dapr.Client/EnumExtensions.cs index ab5f6afd6..05cc7c4ee 100644 --- a/src/Dapr.Client/EnumExtensions.cs +++ b/src/Dapr.Client/EnumExtensions.cs @@ -1,4 +1,17 @@ -using System; +// ------------------------------------------------------------------------ +// Copyright 2023 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using System; using System.Runtime.Serialization; namespace Dapr.Client From dee30d19d9c9e014cde6314ab6bdc5db6c44581c Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Fri, 22 Dec 2023 17:52:36 -0600 Subject: [PATCH 07/43] Removed deprecated examples as the subtle APIs are presently disabled Signed-off-by: Whit Waldo --- .../Examples/SignVerifyExample.cs | 45 ------------- .../Examples/WrapUnwrapKeyExample.cs | 66 ------------------- 2 files changed, 111 deletions(-) delete mode 100644 examples/Client/Cryptography/Examples/SignVerifyExample.cs delete mode 100644 examples/Client/Cryptography/Examples/WrapUnwrapKeyExample.cs diff --git a/examples/Client/Cryptography/Examples/SignVerifyExample.cs b/examples/Client/Cryptography/Examples/SignVerifyExample.cs deleted file mode 100644 index 3af94e903..000000000 --- a/examples/Client/Cryptography/Examples/SignVerifyExample.cs +++ /dev/null @@ -1,45 +0,0 @@ -// ------------------------------------------------------------------------ -// Copyright 2023 The Dapr Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ------------------------------------------------------------------------ - -using Dapr.Client; - -namespace Cryptography.Examples -{ - // This isn't yet implemented in the API, so it cannot yet be tested - // internal class SignVerifyExample : Example - // { - // public override string DisplayName => "Using Cryptography to sign a digest and verify that signature"; - - // public override async Task RunAsync(CancellationToken cancellationToken) - // { - // using var client = new DaprClientBuilder().Build(); - - // const string componentName = "azurekeyvault"; - // const string keyName = "mykey"; // Change this to match the name of the key in your Vault - // const string algorithm = "RSA"; //The algorithm should match the key being used - - // var digestBytes = "This is our starting value we'll build the signature for"u8.ToArray(); - - //#pragma warning disable CS0618 // Type or member is obsolete - // var signature = await client.SignAsync(componentName, digestBytes, algorithm, keyName, cancellationToken); - //#pragma warning restore CS0618 // Type or member is obsolete - // Console.WriteLine($"Signature: '{Convert.ToBase64String(signature)}'"); - - //#pragma warning disable CS0618 // Type or member is obsolete - // var verification = await client.VerifyAsync(componentName, digestBytes, signature, algorithm, keyName, - // cancellationToken); - //#pragma warning restore CS0618 // Type or member is obsolete - // Console.WriteLine($"Verification: {(verification ? "Success": "Failed")}"); - // } - // } -} diff --git a/examples/Client/Cryptography/Examples/WrapUnwrapKeyExample.cs b/examples/Client/Cryptography/Examples/WrapUnwrapKeyExample.cs deleted file mode 100644 index 339e15791..000000000 --- a/examples/Client/Cryptography/Examples/WrapUnwrapKeyExample.cs +++ /dev/null @@ -1,66 +0,0 @@ -// ------------------------------------------------------------------------ -// Copyright 2023 The Dapr Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ------------------------------------------------------------------------ - -using System.Security.Cryptography; -using System.Text; -using Dapr.Client; - -namespace Cryptography.Examples -{ - // This isn't yet implemented in the API, so it cannot yet be tested - // internal class WrapUnwrapKeyExample : Example - // { - // public override string DisplayName => "Using Cryptography to retrieve, wrap and unwrap a given key"; - - // public override async Task RunAsync(CancellationToken cancellationToken) - // { - // using var client = new DaprClientBuilder().Build(); - - // const string componentName = "azurekeyvault"; // Change this to match the name of the component containing your vault - // const string keyName = "mykey"; // Change this to match the name of the key in your Vault - - // var nonceBytes = "This is a nonce"u8.ToArray(); - - // //Generate a new private key for our purposes here - // var privateKeyBytes = new List(); - // using (var rsa = new RSACryptoServiceProvider(2048)) - // { - // try - // { - // var privateKey = rsa.ExportEncryptedPkcs8PrivateKey("password", - // new PbeParameters(PbeEncryptionAlgorithm.Aes256Cbc, HashAlgorithmName.SHA256, 5)); - - // privateKeyBytes.AddRange(privateKey); - // } - // finally - // { - // rsa.PersistKeyInCsp = false; - // } - // } - - // //Wrap the key - // var wrappedKeyResult = - //#pragma warning disable CS0618 // Type or member is obsolete - // await client.WrapKeyAsync(componentName, privateKeyBytes.ToArray(), keyName, "RSA", nonceBytes, cancellationToken); - //#pragma warning restore CS0618 // Type or member is obsolete - // Console.WriteLine($"Wrapped key bytes: '{Convert.ToBase64String(wrappedKeyResult.WrappedKey)}'"); - - // //Unwrap the key - //#pragma warning disable CS0618 // Type or member is obsolete - // var unwrappedKey = await client.UnwrapKeyAsync(componentName, wrappedKeyResult.WrappedKey, "RSA", keyName, - // nonceBytes, Array.Empty(), cancellationToken); - //#pragma warning restore CS0618 // Type or member is obsolete - // Console.WriteLine($"Unwrapped key value:"); - // } - // } -} From 37640bb51239421b8327085a6883216122610c0a Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Fri, 22 Dec 2023 17:53:04 -0600 Subject: [PATCH 08/43] Updated example to reflect new API shape Signed-off-by: Whit Waldo --- .../Examples/EncryptDecryptExample.cs | 16 ++++++---------- examples/Client/Cryptography/Program.cs | 4 +--- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/examples/Client/Cryptography/Examples/EncryptDecryptExample.cs b/examples/Client/Cryptography/Examples/EncryptDecryptExample.cs index f0a0af66d..1933bf22a 100644 --- a/examples/Client/Cryptography/Examples/EncryptDecryptExample.cs +++ b/examples/Client/Cryptography/Examples/EncryptDecryptExample.cs @@ -3,7 +3,7 @@ // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -26,25 +26,21 @@ public override async Task RunAsync(CancellationToken cancellationToken) const string componentName = "azurekeyvault"; //Change this to match the name of the component containing your vault const string keyName = "myKey"; //Change this to match the name of the key in your Vault - const string algorithm = "RSA"; //The algorithm used should match the type of key used. - - var nonceBytes = "This in our nonce value"u8.ToArray(); - + + const string plaintextStr = "This is the value we're going to encrypt today"; Console.WriteLine($"Original string value: '{plaintextStr}'"); //Encrypt the string var plaintextBytes = Encoding.UTF8.GetBytes(plaintextStr); #pragma warning disable CS0618 // Type or member is obsolete - var encryptedBytesResult = await client.EncryptAsync(componentName, plaintextBytes, algorithm, keyName, - nonceBytes, cancellationToken); + var encryptedBytesResult = await client.EncryptAsync(componentName, plaintextBytes, KeyWrapAlgorithm.Rsa, keyName, DataEncryptionCipher.AesGcm, + cancellationToken); #pragma warning restore CS0618 // Type or member is obsolete //Decrypt the string #pragma warning disable CS0618 // Type or member is obsolete - var decryptedBytes = await client.DecryptAsync(componentName, encryptedBytesResult.CipherTextBytes, - algorithm, keyName, - nonceBytes, Array.Empty(), cancellationToken); + var decryptedBytes = await client.DecryptAsync(componentName, encryptedBytesResult, keyName, cancellationToken); #pragma warning restore CS0618 // Type or member is obsolete Console.WriteLine($"Decrypted string: '{Encoding.UTF8.GetString(decryptedBytes)}'"); } diff --git a/examples/Client/Cryptography/Program.cs b/examples/Client/Cryptography/Program.cs index 2981b3280..65dd5473d 100644 --- a/examples/Client/Cryptography/Program.cs +++ b/examples/Client/Cryptography/Program.cs @@ -3,7 +3,7 @@ // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -21,8 +21,6 @@ class Program private static readonly Example[] Examples = new Example[] { new EncryptDecryptExample() - //new SignVerifyExample() - API not implemented yet - //new WrapUnwrapKeyExample() - API not implemented yet }; static async Task Main(string[] args) From c398780ece79bf3bdeefb027e06323e75fdbd294 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Fri, 22 Dec 2023 18:31:39 -0600 Subject: [PATCH 09/43] Updated example and readme Signed-off-by: Whit Waldo --- .../Examples/EncryptDecryptExample.cs | 2 + examples/Client/Cryptography/README.md | 45 ++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/examples/Client/Cryptography/Examples/EncryptDecryptExample.cs b/examples/Client/Cryptography/Examples/EncryptDecryptExample.cs index 1933bf22a..a8663626a 100644 --- a/examples/Client/Cryptography/Examples/EncryptDecryptExample.cs +++ b/examples/Client/Cryptography/Examples/EncryptDecryptExample.cs @@ -38,6 +38,8 @@ public override async Task RunAsync(CancellationToken cancellationToken) cancellationToken); #pragma warning restore CS0618 // Type or member is obsolete + Console.WriteLine($"Encrypted bytes: '{Convert.ToBase64String(encryptedBytesResult)}'"); + //Decrypt the string #pragma warning disable CS0618 // Type or member is obsolete var decryptedBytes = await client.DecryptAsync(componentName, encryptedBytesResult, keyName, cancellationToken); diff --git a/examples/Client/Cryptography/README.md b/examples/Client/Cryptography/README.md index 8b602ecca..21d524a63 100644 --- a/examples/Client/Cryptography/README.md +++ b/examples/Client/Cryptography/README.md @@ -6,6 +6,49 @@ - [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli) - [Initialized Dapr environment](https://docs.dapr.io/getting-started/installation) - [Dapr .NET SDK](https://docs.dapr.io/developing-applications/sdks/dotnet/) +- [Azure Key Vault instance](https://learn.microsoft.com/en-us/azure/key-vault/general/quick-create-portal) +- [Entra Service Principal](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app) + +### Service Principal/Environment Variables Setup +In your Azure portal, open Microsoft Entra ID and click `App Registrations`. Click the button at the top to create a new registration. Select a name for your service principal +and click register, noting this name for later. + +Once the registration is completed, open it from the list and select Certificates & Secrets from the left navigation. Select "Client secrets" from the page body (middle column) +and click the button to add a new client secret giving it an optional description and changing the expiry date as you desire. Click Add to create the secret. Record the secret +value it shows you - it will not be shown to you again without creating another client secret. + +Click Overview from the left navigation and record the "Application (client) ID" and the "Directory (tenant) ID" values. + +On your computer (assuming Windows), open your start menu and type "Environment Variables". An option should appear named "Edit the system environment variables". Select this +and your System Properties window will open. Click the "Environment Variables" button in the bottom and said window will appear. Click the "New..." button under System variables +to add the requisite service principal values to your environment variables. You can change these names as to want by updating the `./Components/azurekeyvault.yaml` names, but for now +configure as follows: + +| Variable Name | Value | +|--|--| +| read_azure_client_id | Paste the value from your app registration overview for "Application (client) ID" | +| read_azure_client_secret | Paste the value of the client secret you generated for your app registration | +| read_azure_tenant_id | Paste the valeu from your app registration overview for "Directory (tenant) ID" | + +Click OK to save your environment variables and to close your System Properties window. You may need to close restart your command line tool for it to recognize the new values. + +### Azure Key Vault Setup + +This example is implemented using the Azure Key Vault and will not work without it. Assuming you have a Key Vault instance configured, ensure that +you have the `Key Vault Crypto Officer` role assigned to yourself as you'll need to in order to generate a new key in the instance. After selecting Keys +under the Objects header, click the `Generate/Import` button at the top of the instance panel. + +Under options, select `Generate` and name your key. This example is pre-configured to assume a key name of 'mykey', but feel free to change this. The other default +options are fine for our purposes, so click Create at the bottom and if you've got the appropriate roles, it will show up in the list of Keys. + +Update your `./Components/azurekeyvault.yaml` file with the name of your Key Vault under `vaultName` where it currently reads "changeMe". This sample assumes authentication +via a service principal, so you might also need to set this up. + +Back in the Azure Portal, assign at least the `Key Vault Crypto User` role to the service principal you previously created in the last step. Do this by clicking +`Access Control (IAM)` from the left navigation, clicking "Add" from the top and clicking "Add Role Assignment". Select `Key Vault Crypto User` from the list and click the Next +button. Ensuring that the "User, group or service principal" option is selected, click the "Select members" link and search for the name of the app registration you created. Click +Add to add this service principal to the list of members for the new role assignment and click Review + Assign twice to assign the role. This will take effect within a few seconds +or minutes. This step ensures that while Dapr can authenticate as your service principal, that it also has permission to access and use the key in your Key Vault. ## Running the example @@ -30,4 +73,4 @@ dapr run --resources-path ./Components --app-id DaprClient -- dotnet run 0 ``` ## Encryption/Decryption with strings -See [EncryptStringExample.cs](./EncryptStringExample.cs) for an example of using `DaprClient` for basic string-based encryption and decryption operations as performed against UTF-8 encoded byte arrays. \ No newline at end of file +See [EncryptDecryptExample.cs](./EncryptDecryptExample.cs) for an example of using `DaprClient` for basic string-based encryption and decryption operations as performed against UTF-8 encoded byte arrays. \ No newline at end of file From 6e93dd54557f21e9585f43e077815e13cdafacc0 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Fri, 22 Dec 2023 19:29:40 -0600 Subject: [PATCH 10/43] Added overloads for encrypting/decrypting streams instead of just fixed byte arrays. Added example demonstrating the same encrypting a file via a FileStream and decrypting from a MemoryStream. Signed-off-by: Whit Waldo --- .../Client/Cryptography/Cryptography.csproj | 6 + .../EncryptDecryptFileStreamExample.cs | 59 +++++++ ...mple.cs => EncryptDecryptStringExample.cs} | 2 +- examples/Client/Cryptography/Program.cs | 3 +- examples/Client/Cryptography/README.md | 4 +- examples/Client/Cryptography/file.txt | 26 +++ src/Dapr.Client/DaprClient.cs | 49 +++++- src/Dapr.Client/DaprClientGrpc.cs | 156 +++++++++++++++++- 8 files changed, 292 insertions(+), 13 deletions(-) create mode 100644 examples/Client/Cryptography/Examples/EncryptDecryptFileStreamExample.cs rename examples/Client/Cryptography/Examples/{EncryptDecryptExample.cs => EncryptDecryptStringExample.cs} (97%) create mode 100644 examples/Client/Cryptography/file.txt diff --git a/examples/Client/Cryptography/Cryptography.csproj b/examples/Client/Cryptography/Cryptography.csproj index 2f08102a6..1b44e1d89 100644 --- a/examples/Client/Cryptography/Cryptography.csproj +++ b/examples/Client/Cryptography/Cryptography.csproj @@ -16,4 +16,10 @@ + + + PreserveNewest + + + \ No newline at end of file diff --git a/examples/Client/Cryptography/Examples/EncryptDecryptFileStreamExample.cs b/examples/Client/Cryptography/Examples/EncryptDecryptFileStreamExample.cs new file mode 100644 index 000000000..632f7d566 --- /dev/null +++ b/examples/Client/Cryptography/Examples/EncryptDecryptFileStreamExample.cs @@ -0,0 +1,59 @@ +// ------------------------------------------------------------------------ +// Copyright 2023 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +using Dapr.Client; + +namespace Cryptography.Examples +{ + internal class EncryptDecryptFileStreamExample : Example + { + public override string DisplayName => "Use Cryptography to encrypt and decrypt a file"; + public override async Task RunAsync(CancellationToken cancellationToken) + { + using var client = new DaprClientBuilder().Build(); + + const string componentName = "azurekeyvault"; // Change this to match the name of the component containing your vault + const string keyName = "myKey"; + + // The name of the file we're using as an example + const string fileName = "file.txt"; + + Console.WriteLine("Original file contents:"); + foreach (var line in await File.ReadAllLinesAsync(fileName, cancellationToken)) + { + Console.WriteLine(line); + } + Console.WriteLine(); + + //Encrypt the file + await using var encryptFs = new FileStream(fileName, FileMode.Open); +#pragma warning disable CS0618 // Type or member is obsolete + var encryptedBytesResult = await client.EncryptAsync(componentName, encryptFs, KeyWrapAlgorithm.Rsa, keyName, + DataEncryptionCipher.AesGcm, cancellationToken); +#pragma warning restore CS0618 // Type or member is obsolete + Console.WriteLine($"Encrypted bytes: '{Convert.ToBase64String(encryptedBytesResult)}'"); + Console.WriteLine(); + + //Decrypt the temp file from a memory stream this time instead of a file + await using var ms = new MemoryStream(encryptedBytesResult); +#pragma warning disable CS0618 // Type or member is obsolete + var decryptedBytes = await client.DecryptAsync(componentName, ms, keyName, cancellationToken); +#pragma warning restore CS0618 // Type or member is obsolete + + Console.WriteLine("Decrypted value:"); + await using var decryptedMs = new MemoryStream(decryptedBytes); + using var sr = new StreamReader(decryptedMs); + Console.WriteLine(await sr.ReadToEndAsync(cancellationToken)); + } + } +} diff --git a/examples/Client/Cryptography/Examples/EncryptDecryptExample.cs b/examples/Client/Cryptography/Examples/EncryptDecryptStringExample.cs similarity index 97% rename from examples/Client/Cryptography/Examples/EncryptDecryptExample.cs rename to examples/Client/Cryptography/Examples/EncryptDecryptStringExample.cs index a8663626a..5cd99516a 100644 --- a/examples/Client/Cryptography/Examples/EncryptDecryptExample.cs +++ b/examples/Client/Cryptography/Examples/EncryptDecryptStringExample.cs @@ -16,7 +16,7 @@ namespace Cryptography.Examples { - internal class EncryptDecryptExample : Example + internal class EncryptDecryptStringExample : Example { public override string DisplayName => "Using Cryptography to encrypt and decrypt a string"; diff --git a/examples/Client/Cryptography/Program.cs b/examples/Client/Cryptography/Program.cs index 65dd5473d..74e3c7f48 100644 --- a/examples/Client/Cryptography/Program.cs +++ b/examples/Client/Cryptography/Program.cs @@ -20,7 +20,8 @@ class Program { private static readonly Example[] Examples = new Example[] { - new EncryptDecryptExample() + new EncryptDecryptStringExample(), + new EncryptDecryptFileStreamExample() }; static async Task Main(string[] args) diff --git a/examples/Client/Cryptography/README.md b/examples/Client/Cryptography/README.md index 21d524a63..84c2f8248 100644 --- a/examples/Client/Cryptography/README.md +++ b/examples/Client/Cryptography/README.md @@ -38,8 +38,8 @@ This example is implemented using the Azure Key Vault and will not work without you have the `Key Vault Crypto Officer` role assigned to yourself as you'll need to in order to generate a new key in the instance. After selecting Keys under the Objects header, click the `Generate/Import` button at the top of the instance panel. -Under options, select `Generate` and name your key. This example is pre-configured to assume a key name of 'mykey', but feel free to change this. The other default -options are fine for our purposes, so click Create at the bottom and if you've got the appropriate roles, it will show up in the list of Keys. +Under options, select `Generate` and name your key. This example is pre-configured to assume a key name of 'myKey', but feel free to change this (but also update the name in the example +you wish to run). The other default options are fine for our purposes, so click Create at the bottom and if you've got the appropriate roles, it will show up in the list of Keys. Update your `./Components/azurekeyvault.yaml` file with the name of your Key Vault under `vaultName` where it currently reads "changeMe". This sample assumes authentication via a service principal, so you might also need to set this up. diff --git a/examples/Client/Cryptography/file.txt b/examples/Client/Cryptography/file.txt new file mode 100644 index 000000000..9e8638939 --- /dev/null +++ b/examples/Client/Cryptography/file.txt @@ -0,0 +1,26 @@ +# The Road Not Taken +## By Robert Lee Frost + +Two roads diverged in a yellow wood, +And sorry I could not travel both +And be one traveler, long I stood +And looked down one as far as I could +To where it bent in the undergrowth; + +Then took the other, as just as fair +And having perhaps the better claim, +Because it was grassy and wanted wear; +Though as for that, the passing there +Had worn them really about the same, + +And both that morning equally lay +In leaves no step had trodden black +Oh, I kept the first for another day! +Yet knowing how way leads on to way, +I doubted if I should ever come back. + +I shall be telling this with a sigh +Somewhere ages and ages hence: +Two roads diverged in a wood, and I, +I took the one less traveled by, +And that has made all the difference. \ No newline at end of file diff --git a/src/Dapr.Client/DaprClient.cs b/src/Dapr.Client/DaprClient.cs index 691c6a616..b0b39fb64 100644 --- a/src/Dapr.Client/DaprClient.cs +++ b/src/Dapr.Client/DaprClient.cs @@ -13,6 +13,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; @@ -20,7 +21,6 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using Dapr.Client.Autogen.Grpc.v1; using Google.Protobuf; using Grpc.Core; using Grpc.Core.Interceptors; @@ -973,18 +973,61 @@ public abstract Task EncryptAsync(string vaultResourceName, byte[] plain KeyWrapAlgorithm algorithm, string keyName, string decryptionKeyName, DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, CancellationToken cancellationToken = default); + /// + /// Encrypts an array of bytes using the Dapr Cryptography encryption functionality. + /// + /// The name of the vault resource used by the operation. + /// The stream containing the bytes of the plaintext value to encrypt. + /// The name of the algorithm used to wrap the encryption key. + /// The name of the key to use from the Vault for the encryption operation. + /// The name of the cipher to use for the encryption operation. + /// A that can be used to cancel the operation. + /// An array of encrypted bytes. + [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public abstract Task EncryptAsync(string vaultResourceName, Stream plainTextStream, + KeyWrapAlgorithm algorithm, string keyName, DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, + CancellationToken cancellationToken = default); + + /// + /// Encrypts an array of bytes using the Dapr Cryptography encryption functionality. + /// + /// The name of the vault resource used by the operation. + /// The stream containing the bytes of the plaintext value to encrypt. + /// The name of the algorithm used to wrap the encryption key. + /// The name of the key to use from the Vault for the encryption operation. + /// The name (and optionally version) of the decryption key to specify should be used. + /// The name of the cipher to use for the encryption operation. + /// A that can be used to cancel the operation. + /// An array of encrypted bytes. + [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public abstract Task EncryptAsync(string vaultResourceName, Stream plainTextStream, + KeyWrapAlgorithm algorithm, string keyName, string decryptionKeyName, DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, + CancellationToken cancellationToken = default); + /// /// Decrypts the specified cipher text bytes using the Dapr Cryptography encryption functionality. /// /// The name of the vault resource used by the operation. - /// The byte of the cipher text value to decrypt. + /// The bytes of the cipher text value to decrypt. /// The name of the key to use from the Vault for the decryption operation. /// A that can be used to cancel the operation. /// An array of decrypted bytes. [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] public abstract Task DecryptAsync(string vaultResourceName, byte[] cipherTextBytes, string keyName, CancellationToken cancellationToken = default); - + + /// + /// Decrypts the specified cipher text bytes using the Dapr Cryptography encryption functionality. + /// + /// The name of the vault resource used by the operation. + /// The stream containing the bytes of the cipher text value to decrypt. + /// The name of the key to use from the Vault for the decryption operation. + /// A that can be used to cancel the operation. + /// An array of decrypted bytes. + [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public abstract Task DecryptAsync(string vaultResourceName, Stream cipherTextStream, string keyName, + CancellationToken cancellationToken = default); + #endregion #region Cryptography - Subtle API diff --git a/src/Dapr.Client/DaprClientGrpc.cs b/src/Dapr.Client/DaprClientGrpc.cs index 13789a77c..0e760c9b8 100644 --- a/src/Dapr.Client/DaprClientGrpc.cs +++ b/src/Dapr.Client/DaprClientGrpc.cs @@ -11,8 +11,7 @@ // limitations under the License. // ------------------------------------------------------------------------ -using System.Runtime.CompilerServices; -using System.Text; +using System.IO; namespace Dapr.Client { @@ -1381,7 +1380,91 @@ public override async Task UnsubscribeConfigur #region Cryptography - private async Task EncryptAsync(string vaultResourceName, byte[] plainTextBytes, + private async Task EncryptStreamAsync(string vaultResourceName, Stream stream, + KeyWrapAlgorithm algorithm, string keyName, DataEncryptionCipher dataEncryptionCipher, + string decryptionKeyName, bool omitDecryptionKey, CancellationToken cancellationToken = default) + { + ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); + ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); + + + var encryptRequestOptions = new Autogenerated.EncryptRequestOptions + { + ComponentName = vaultResourceName, + DataEncryptionCipher = dataEncryptionCipher.GetValueFromEnumMember(), + KeyName = keyName, + KeyWrapAlgorithm = algorithm.GetValueFromEnumMember(), + OmitDecryptionKeyName = omitDecryptionKey + }; + + if (!omitDecryptionKey) + { + ArgumentVerifier.ThrowIfNullOrEmpty(decryptionKeyName, nameof(decryptionKeyName)); + encryptRequestOptions.DecryptionKeyName = decryptionKeyName; + } + + var options = CreateCallOptions(headers: null, cancellationToken); + var duplexStream = client.EncryptAlpha1(options); + + //Start with passing the metadata about the encryption request itself in the first message + await duplexStream.RequestStream.WriteAsync( + new Autogenerated.EncryptRequest { Options = encryptRequestOptions }, cancellationToken); + + //Send the plaintext bytes in 4 kb blocks in subsequent messages + const int blockSize = 4 * 1024; // 4 kb + await using (var bufferedStream = new BufferedStream(stream, blockSize)) + { + var buffer = new byte[blockSize]; + int bytesRead; + ulong sequenceNumber = 0; + + while ((bytesRead = await bufferedStream.ReadAsync(buffer, 0, blockSize, cancellationToken)) != 0) + { + await duplexStream.RequestStream.WriteAsync(new Autogenerated.EncryptRequest + { + Payload = new Autogenerated.StreamPayload + { + Data = ByteString.CopyFrom(buffer, 0, bytesRead), Seq = sequenceNumber + } + }, cancellationToken); + + //Increment the sequence number + sequenceNumber++; + } + } + + //Send the completion message + await duplexStream.RequestStream.CompleteAsync(); + + //Set up how the response is handled since we know it's of finite length + var returnedBytes = new List(); + await foreach (var encryptResponse in duplexStream.ResponseStream.ReadAllAsync(cancellationToken).ConfigureAwait(false)) + { + returnedBytes.AddRange(encryptResponse.Payload.Data.ToByteArray()); + } + + return returnedBytes.ToArray(); + } + + /// + [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public override Task EncryptAsync( + string vaultResourceName, Stream plainTextStream, KeyWrapAlgorithm algorithm, + string keyName, DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, + CancellationToken cancellationToken = default) => EncryptStreamAsync(vaultResourceName, plainTextStream, + algorithm, keyName, dataEncryptionCipher, string.Empty, true, cancellationToken); + + /// + [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public override Task EncryptAsync( + string vaultResourceName, Stream plainTextStream, KeyWrapAlgorithm algorithm, + string keyName, string decryptionKeyName, + DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, + CancellationToken cancellationToken = default) => EncryptStreamAsync(vaultResourceName, plainTextStream, + algorithm, keyName, dataEncryptionCipher, + decryptionKeyName, false, cancellationToken); + + private async Task EncryptByteArrayAsync(string vaultResourceName, byte[] plainTextBytes, KeyWrapAlgorithm algorithm, string keyName, DataEncryptionCipher dataEncryptionCipher, string decryptionKeyName, bool omitDecryptionKey, CancellationToken cancellationToken = default) { @@ -1448,7 +1531,7 @@ await duplexStream.RequestStream.WriteAsync(new Autogenerated.EncryptRequest public override Task EncryptAsync( string vaultResourceName, byte[] plainTextBytes, KeyWrapAlgorithm algorithm, string keyName, DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, - CancellationToken cancellationToken = default) => EncryptAsync(vaultResourceName, plainTextBytes, + CancellationToken cancellationToken = default) => EncryptByteArrayAsync(vaultResourceName, plainTextBytes, algorithm, keyName, dataEncryptionCipher, string.Empty, true, cancellationToken); /// @@ -1457,7 +1540,7 @@ public override Task EncryptAsync( string vaultResourceName, byte[] plainTextBytes, KeyWrapAlgorithm algorithm, string keyName, string decryptionKeyName, DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, - CancellationToken cancellationToken = default) => EncryptAsync(vaultResourceName, plainTextBytes, + CancellationToken cancellationToken = default) => EncryptByteArrayAsync(vaultResourceName, plainTextBytes, algorithm, keyName, dataEncryptionCipher, decryptionKeyName, false, cancellationToken); @@ -1514,7 +1597,68 @@ await duplexStream.RequestStream.WriteAsync( return returnedBytes.ToArray(); } - + + /// + [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public override async Task DecryptAsync(string vaultResourceName, Stream cipherTextStream, string keyName, CancellationToken cancellationToken = default) + { + ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); + ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); + + var decryptRequestOptions = new Autogenerated.DecryptRequestOptions + { + ComponentName = vaultResourceName, + KeyName = keyName + }; + + var options = CreateCallOptions(headers: null, cancellationToken); + var duplexStream = client.DecryptAlpha1(options); + + //Start with passing the metadata about the decryption request itself in the first message + await duplexStream.RequestStream.WriteAsync(new Autogenerated.DecryptRequest + { + Options = decryptRequestOptions + }, cancellationToken); + + //Send the cipher text bytes in 4 kb blocks in subsequent messages + const int blockSize = 4 * 1024; // 4 kb + + await using (var bufferedStream = new BufferedStream(cipherTextStream, blockSize)) + { + var buffer = new byte[blockSize]; + int bytesRead; + ulong sequenceNumber = 0; + + while ((bytesRead = await bufferedStream.ReadAsync(buffer, 0, blockSize, cancellationToken)) != 0) + { + await duplexStream.RequestStream.WriteAsync( + new Autogenerated.DecryptRequest + { + Payload = new Autogenerated.StreamPayload + { + Data = ByteString.CopyFrom(buffer, 0, bytesRead), Seq = sequenceNumber + } + }, cancellationToken); + + //Increment the sequence number + sequenceNumber++; + } + } + + //Send the completion message + await duplexStream.RequestStream.CompleteAsync(); + + //Set up how the response is handled since we know it's of finite length + var returnedBytes = new List(); + await foreach (var decryptedResponse in duplexStream.ResponseStream.ReadAllAsync(cancellationToken) + .ConfigureAwait(false)) + { + returnedBytes.AddRange(decryptedResponse.Payload.Data.ToByteArray()); + } + + return returnedBytes.ToArray(); + } + #region Subtle Crypto Implementation ///// From 2913fcfaafdc513dd6a9b2eb0a521c751e31a604 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Sat, 23 Dec 2023 02:42:50 -0600 Subject: [PATCH 11/43] Added some unit tests to pair with the implementation Signed-off-by: Whit Waldo --- test/Dapr.Client.Test/CryptographyApiTest.cs | 97 ++++++++++++++++++++ test/Dapr.Client.Test/EnumExtensionTest.cs | 38 ++++++++ 2 files changed, 135 insertions(+) create mode 100644 test/Dapr.Client.Test/CryptographyApiTest.cs create mode 100644 test/Dapr.Client.Test/EnumExtensionTest.cs diff --git a/test/Dapr.Client.Test/CryptographyApiTest.cs b/test/Dapr.Client.Test/CryptographyApiTest.cs new file mode 100644 index 000000000..e9edd59e1 --- /dev/null +++ b/test/Dapr.Client.Test/CryptographyApiTest.cs @@ -0,0 +1,97 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Moq; +using Xunit; +#pragma warning disable CS0618 // Type or member is obsolete + +namespace Dapr.Client.Test +{ + public class CryptographyApiTest + { + [Fact] + public async Task EncryptAsync_ByteArray_VaultResourceName_ArgumentVerifierException() + { + var client = new DaprClientBuilder().Build(); + const string vaultResourceName = ""; + //Get response and validate + await Assert.ThrowsAsync(async () => await client.EncryptAsync(vaultResourceName, + Array.Empty(), KeyWrapAlgorithm.Rsa, "MyKey", DataEncryptionCipher.AesGcm, + CancellationToken.None)); + } + + [Fact] + public async Task EncryptAsync_ByteArray_KeyName_ArgumentVerifierException() + { + var client = new DaprClientBuilder().Build(); + const string keyName = ""; + //Get response and validate + await Assert.ThrowsAsync(async () => await client.EncryptAsync("myVault", + Array.Empty(), KeyWrapAlgorithm.Rsa, keyName, DataEncryptionCipher.AesGcm, + CancellationToken.None)); + } + + [Fact] + public async Task EncryptAsync_Stream_VaultResourceName_ArgumentVerifierException() + { + var client = new DaprClientBuilder().Build(); + const string vaultResourceName = ""; + //Get response and validate + await Assert.ThrowsAsync(async () => await client.EncryptAsync(vaultResourceName, + new MemoryStream(), KeyWrapAlgorithm.Rsa, "MyKey", DataEncryptionCipher.AesGcm, + CancellationToken.None)); + } + + [Fact] + public async Task EncryptAsync_Stream_KeyName_ArgumentVerifierException() + { + var client = new DaprClientBuilder().Build(); + const string keyName = ""; + //Get response and validate + await Assert.ThrowsAsync(async () => await client.EncryptAsync("myVault", + new MemoryStream(), KeyWrapAlgorithm.Rsa, keyName, DataEncryptionCipher.AesGcm, + CancellationToken.None)); + } + + [Fact] + public async Task DecryptAsync_ByteArray_VaultResourceName_ArgumentVerifierException() + { + var client = new DaprClientBuilder().Build(); + const string vaultResourceName = ""; + //Get response and validate + await Assert.ThrowsAsync(async () => await client.DecryptAsync(vaultResourceName, + Array.Empty(), "myKey", CancellationToken.None)); + } + + [Fact] + public async Task DecryptAsync_ByteArray_KeyName_ArgumentVerifierException() + { + var client = new DaprClientBuilder().Build(); + const string keyName = ""; + //Get response and validate + await Assert.ThrowsAsync(async () => await client.DecryptAsync("myVault", + Array.Empty(), keyName, CancellationToken.None)); + } + + [Fact] + public async Task DecryptAsync_Stream_VaultResourceName_ArgumentVerifierException() + { + var client = new DaprClientBuilder().Build(); + const string vaultResourceName = ""; + //Get response and validate + await Assert.ThrowsAsync(async () => await client.DecryptAsync(vaultResourceName, + new MemoryStream(), "MyKey", CancellationToken.None)); + } + + [Fact] + public async Task DecryptAsync_Stream_KeyName_ArgumentVerifierException() + { + var client = new DaprClientBuilder().Build(); + const string keyName = ""; + //Get response and validate + await Assert.ThrowsAsync(async () => await client.DecryptAsync("myVault", + new MemoryStream(), keyName, CancellationToken.None)); + } + } +} diff --git a/test/Dapr.Client.Test/EnumExtensionTest.cs b/test/Dapr.Client.Test/EnumExtensionTest.cs new file mode 100644 index 000000000..be78c3861 --- /dev/null +++ b/test/Dapr.Client.Test/EnumExtensionTest.cs @@ -0,0 +1,38 @@ +using System.Runtime.Serialization; +using Xunit; + +namespace Dapr.Client.Test +{ + public class EnumExtensionTest + { + [Fact] + public void GetValueFromEnumMember_RedResolvesAsExpected() + { + var value = TestEnum.Red.GetValueFromEnumMember(); + Assert.Equal("red", value); + } + + [Fact] + public void GetValueFromEnumMember_YellowResolvesAsExpected() + { + var value = TestEnum.Yellow.GetValueFromEnumMember(); + Assert.Equal("YELLOW", value); + } + + [Fact] + public void GetValueFromEnumMember_BlueResolvesAsExpected() + { + var value = TestEnum.Blue.GetValueFromEnumMember(); + Assert.Equal("Blue", value); + } + } + + public enum TestEnum + { + [EnumMember(Value="red")] + Red, + [EnumMember(Value="YELLOW")] + Yellow, + Blue + } +} From 89dfda3dc68ca886aeb8e45e4a0e91638f1f9f87 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Tue, 2 Jan 2024 17:25:45 -0600 Subject: [PATCH 12/43] Added null check for the stream argument Signed-off-by: Whit Waldo --- src/Dapr.Client/DaprClientGrpc.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dapr.Client/DaprClientGrpc.cs b/src/Dapr.Client/DaprClientGrpc.cs index 0e760c9b8..29db3c758 100644 --- a/src/Dapr.Client/DaprClientGrpc.cs +++ b/src/Dapr.Client/DaprClientGrpc.cs @@ -1386,7 +1386,7 @@ private async Task EncryptStreamAsync(string vaultResourceName, Stream s { ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); - + ArgumentVerifier.ThrowIfNull(stream, nameof(stream)); var encryptRequestOptions = new Autogenerated.EncryptRequestOptions { From 3f5a489ca728664728ec7737b84a5c3d4b6bfe5a Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Tue, 2 Jan 2024 17:29:11 -0600 Subject: [PATCH 13/43] Changed case of the arguments as they should read "plaintext" and not "plainText" Signed-off-by: Whit Waldo --- src/Dapr.Client/DaprClient.cs | 8 ++++---- src/Dapr.Client/DaprClientGrpc.cs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Dapr.Client/DaprClient.cs b/src/Dapr.Client/DaprClient.cs index b0b39fb64..788d90f47 100644 --- a/src/Dapr.Client/DaprClient.cs +++ b/src/Dapr.Client/DaprClient.cs @@ -946,14 +946,14 @@ public abstract Task UnsubscribeConfiguration( /// Encrypts an array of bytes using the Dapr Cryptography encryption functionality. /// /// The name of the vault resource used by the operation. - /// The bytes of the plaintext value to encrypt. + /// The bytes of the plaintext value to encrypt. /// The name of the algorithm used to wrap the encryption key. /// The name of the key to use from the Vault for the encryption operation. /// The name of the cipher to use for the encryption operation. /// A that can be used to cancel the operation. /// An array of encrypted bytes. [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public abstract Task EncryptAsync(string vaultResourceName, byte[] plainTextBytes, + public abstract Task EncryptAsync(string vaultResourceName, byte[] plaintextBytes, KeyWrapAlgorithm algorithm, string keyName, DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, CancellationToken cancellationToken = default); @@ -961,7 +961,7 @@ public abstract Task EncryptAsync(string vaultResourceName, byte[] plain /// Encrypts an array of bytes using the Dapr Cryptography encryption functionality. /// /// The name of the vault resource used by the operation. - /// The bytes of the plaintext value to encrypt. + /// The bytes of the plaintext value to encrypt. /// The name of the algorithm used to wrap the encryption key. /// The name of the key to use from the Vault for the encryption operation. /// The name (and optionally version) of the decryption key to specify should be used. @@ -969,7 +969,7 @@ public abstract Task EncryptAsync(string vaultResourceName, byte[] plain /// A that can be used to cancel the operation. /// An array of encrypted bytes. [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public abstract Task EncryptAsync(string vaultResourceName, byte[] plainTextBytes, + public abstract Task EncryptAsync(string vaultResourceName, byte[] plaintextBytes, KeyWrapAlgorithm algorithm, string keyName, string decryptionKeyName, DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, CancellationToken cancellationToken = default); diff --git a/src/Dapr.Client/DaprClientGrpc.cs b/src/Dapr.Client/DaprClientGrpc.cs index 29db3c758..1b35cc619 100644 --- a/src/Dapr.Client/DaprClientGrpc.cs +++ b/src/Dapr.Client/DaprClientGrpc.cs @@ -1529,18 +1529,18 @@ await duplexStream.RequestStream.WriteAsync(new Autogenerated.EncryptRequest /// [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] public override Task EncryptAsync( - string vaultResourceName, byte[] plainTextBytes, KeyWrapAlgorithm algorithm, + string vaultResourceName, byte[] plaintextBytes, KeyWrapAlgorithm algorithm, string keyName, DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, - CancellationToken cancellationToken = default) => EncryptByteArrayAsync(vaultResourceName, plainTextBytes, + CancellationToken cancellationToken = default) => EncryptByteArrayAsync(vaultResourceName, plaintextBytes, algorithm, keyName, dataEncryptionCipher, string.Empty, true, cancellationToken); /// [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] public override Task EncryptAsync( - string vaultResourceName, byte[] plainTextBytes, KeyWrapAlgorithm algorithm, + string vaultResourceName, byte[] plaintextBytes, KeyWrapAlgorithm algorithm, string keyName, string decryptionKeyName, DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, - CancellationToken cancellationToken = default) => EncryptByteArrayAsync(vaultResourceName, plainTextBytes, + CancellationToken cancellationToken = default) => EncryptByteArrayAsync(vaultResourceName, plaintextBytes, algorithm, keyName, dataEncryptionCipher, decryptionKeyName, false, cancellationToken); From f70622732051a6f96b3811172a2e27378993f055 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Tue, 2 Jan 2024 17:42:10 -0600 Subject: [PATCH 14/43] Reduced number of encryption implementations by just wrapping byte array into memory stream Signed-off-by: Whit Waldo --- src/Dapr.Client/DaprClientGrpc.cs | 68 ++----------------------------- 1 file changed, 3 insertions(+), 65 deletions(-) diff --git a/src/Dapr.Client/DaprClientGrpc.cs b/src/Dapr.Client/DaprClientGrpc.cs index 1b35cc619..9ec1ebb73 100644 --- a/src/Dapr.Client/DaprClientGrpc.cs +++ b/src/Dapr.Client/DaprClientGrpc.cs @@ -1463,75 +1463,13 @@ public override Task EncryptAsync( CancellationToken cancellationToken = default) => EncryptStreamAsync(vaultResourceName, plainTextStream, algorithm, keyName, dataEncryptionCipher, decryptionKeyName, false, cancellationToken); - - private async Task EncryptByteArrayAsync(string vaultResourceName, byte[] plainTextBytes, - KeyWrapAlgorithm algorithm, string keyName, DataEncryptionCipher dataEncryptionCipher, - string decryptionKeyName, bool omitDecryptionKey, CancellationToken cancellationToken = default) - { - ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); - ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); - - - var encryptRequestOptions = new Autogenerated.EncryptRequestOptions - { - ComponentName = vaultResourceName, - DataEncryptionCipher = dataEncryptionCipher.GetValueFromEnumMember(), - KeyName = keyName, - KeyWrapAlgorithm = algorithm.GetValueFromEnumMember(), - OmitDecryptionKeyName = omitDecryptionKey - }; - - if (!omitDecryptionKey) - { - ArgumentVerifier.ThrowIfNullOrEmpty(decryptionKeyName, nameof(decryptionKeyName)); - encryptRequestOptions.DecryptionKeyName = decryptionKeyName; - } - - var options = CreateCallOptions(headers: null, cancellationToken); - var duplexStream = client.EncryptAlpha1(options); - - //Start with passing the metadata about the encryption request itself in the first message - await duplexStream.RequestStream.WriteAsync( - new Autogenerated.EncryptRequest {Options = encryptRequestOptions}, cancellationToken); - - //Send the plaintext bytes in 4 kb blocks in subsequent messages - const int blockSize = 4 * 1024; // 4 kb - var blockCount = (int)Math.Ceiling((double)plainTextBytes.Length / blockSize); - - for (var a = 0; a < blockCount; a++) - { - var offset = a * blockSize; - var count = Math.Min(blockSize, plainTextBytes.Length - offset); - - await duplexStream.RequestStream.WriteAsync(new Autogenerated.EncryptRequest - { - Payload = new Autogenerated.StreamPayload - { - Data = ByteString.CopyFrom(plainTextBytes, offset, count), - Seq = (ulong)a - } - }, cancellationToken); - } - - //Send the completion message - await duplexStream.RequestStream.CompleteAsync(); - - //Set up how the response is handled since we know it's of finite length - var returnedBytes = new List(); - await foreach (var encryptResponse in duplexStream.ResponseStream.ReadAllAsync(cancellationToken).ConfigureAwait(false)) - { - returnedBytes.AddRange(encryptResponse.Payload.Data.ToByteArray()); - } - - return returnedBytes.ToArray(); - } - + /// [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] public override Task EncryptAsync( string vaultResourceName, byte[] plaintextBytes, KeyWrapAlgorithm algorithm, string keyName, DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, - CancellationToken cancellationToken = default) => EncryptByteArrayAsync(vaultResourceName, plaintextBytes, + CancellationToken cancellationToken = default) => EncryptStreamAsync(vaultResourceName, new MemoryStream(plaintextBytes), algorithm, keyName, dataEncryptionCipher, string.Empty, true, cancellationToken); /// @@ -1540,7 +1478,7 @@ public override Task EncryptAsync( string vaultResourceName, byte[] plaintextBytes, KeyWrapAlgorithm algorithm, string keyName, string decryptionKeyName, DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, - CancellationToken cancellationToken = default) => EncryptByteArrayAsync(vaultResourceName, plaintextBytes, + CancellationToken cancellationToken = default) => EncryptStreamAsync(vaultResourceName, new MemoryStream(plaintextBytes), algorithm, keyName, dataEncryptionCipher, decryptionKeyName, false, cancellationToken); From 735b079d4fd83dddc82a4bec92c5b555994b3eb8 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Tue, 2 Jan 2024 17:58:36 -0600 Subject: [PATCH 15/43] Constrainted returned member types per review suggestion Signed-off-by: Whit Waldo --- src/Dapr.Client/EnumExtensions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Dapr.Client/EnumExtensions.cs b/src/Dapr.Client/EnumExtensions.cs index 05cc7c4ee..6b058ca77 100644 --- a/src/Dapr.Client/EnumExtensions.cs +++ b/src/Dapr.Client/EnumExtensions.cs @@ -12,6 +12,7 @@ // ------------------------------------------------------------------------ using System; +using System.Reflection; using System.Runtime.Serialization; namespace Dapr.Client @@ -26,7 +27,7 @@ internal static class EnumExtensions /// public static string GetValueFromEnumMember(this T value) where T : Enum { - var memberInfo = typeof(T).GetMember(value.ToString()); + var memberInfo = typeof(T).GetMember(value.ToString(), BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly); if (memberInfo.Length <= 0) return value.ToString(); From 38b9e045877f4282c486ed6c741ac6b1174bf480 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Tue, 2 Jan 2024 18:09:09 -0600 Subject: [PATCH 16/43] Updated methods to use ReadOnlyMemory instead of byte[] - updated implementations to use low-allocation spans where possible (though ToArray is necessary to wrap with MemoryStream). Signed-off-by: Whit Waldo --- src/Dapr.Client/DaprClient.cs | 12 ++++++------ src/Dapr.Client/DaprClientGrpc.cs | 25 +++++++++++++------------ 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/Dapr.Client/DaprClient.cs b/src/Dapr.Client/DaprClient.cs index 788d90f47..cff3c16a1 100644 --- a/src/Dapr.Client/DaprClient.cs +++ b/src/Dapr.Client/DaprClient.cs @@ -953,7 +953,7 @@ public abstract Task UnsubscribeConfiguration( /// A that can be used to cancel the operation. /// An array of encrypted bytes. [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public abstract Task EncryptAsync(string vaultResourceName, byte[] plaintextBytes, + public abstract Task> EncryptAsync(string vaultResourceName, ReadOnlyMemory plaintextBytes, KeyWrapAlgorithm algorithm, string keyName, DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, CancellationToken cancellationToken = default); @@ -969,7 +969,7 @@ public abstract Task EncryptAsync(string vaultResourceName, byte[] plain /// A that can be used to cancel the operation. /// An array of encrypted bytes. [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public abstract Task EncryptAsync(string vaultResourceName, byte[] plaintextBytes, + public abstract Task> EncryptAsync(string vaultResourceName, ReadOnlyMemory plaintextBytes, KeyWrapAlgorithm algorithm, string keyName, string decryptionKeyName, DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, CancellationToken cancellationToken = default); @@ -984,7 +984,7 @@ public abstract Task EncryptAsync(string vaultResourceName, byte[] plain /// A that can be used to cancel the operation. /// An array of encrypted bytes. [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public abstract Task EncryptAsync(string vaultResourceName, Stream plainTextStream, + public abstract Task> EncryptAsync(string vaultResourceName, Stream plainTextStream, KeyWrapAlgorithm algorithm, string keyName, DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, CancellationToken cancellationToken = default); @@ -1000,7 +1000,7 @@ public abstract Task EncryptAsync(string vaultResourceName, Stream plain /// A that can be used to cancel the operation. /// An array of encrypted bytes. [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public abstract Task EncryptAsync(string vaultResourceName, Stream plainTextStream, + public abstract Task> EncryptAsync(string vaultResourceName, Stream plainTextStream, KeyWrapAlgorithm algorithm, string keyName, string decryptionKeyName, DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, CancellationToken cancellationToken = default); @@ -1013,7 +1013,7 @@ public abstract Task EncryptAsync(string vaultResourceName, Stream plain /// A that can be used to cancel the operation. /// An array of decrypted bytes. [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public abstract Task DecryptAsync(string vaultResourceName, byte[] cipherTextBytes, string keyName, + public abstract Task> DecryptAsync(string vaultResourceName, ReadOnlyMemory cipherTextBytes, string keyName, CancellationToken cancellationToken = default); /// @@ -1025,7 +1025,7 @@ public abstract Task DecryptAsync(string vaultResourceName, byte[] ciphe /// A that can be used to cancel the operation. /// An array of decrypted bytes. [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public abstract Task DecryptAsync(string vaultResourceName, Stream cipherTextStream, string keyName, + public abstract Task> DecryptAsync(string vaultResourceName, Stream cipherTextStream, string keyName, CancellationToken cancellationToken = default); #endregion diff --git a/src/Dapr.Client/DaprClientGrpc.cs b/src/Dapr.Client/DaprClientGrpc.cs index 9ec1ebb73..ef99ea998 100644 --- a/src/Dapr.Client/DaprClientGrpc.cs +++ b/src/Dapr.Client/DaprClientGrpc.cs @@ -1380,7 +1380,7 @@ public override async Task UnsubscribeConfigur #region Cryptography - private async Task EncryptStreamAsync(string vaultResourceName, Stream stream, + private async Task> EncryptStreamAsync(string vaultResourceName, Stream stream, KeyWrapAlgorithm algorithm, string keyName, DataEncryptionCipher dataEncryptionCipher, string decryptionKeyName, bool omitDecryptionKey, CancellationToken cancellationToken = default) { @@ -1448,7 +1448,7 @@ await duplexStream.RequestStream.WriteAsync(new Autogenerated.EncryptRequest /// [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override Task EncryptAsync( + public override Task> EncryptAsync( string vaultResourceName, Stream plainTextStream, KeyWrapAlgorithm algorithm, string keyName, DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, CancellationToken cancellationToken = default) => EncryptStreamAsync(vaultResourceName, plainTextStream, @@ -1456,7 +1456,7 @@ public override Task EncryptAsync( /// [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override Task EncryptAsync( + public override Task> EncryptAsync( string vaultResourceName, Stream plainTextStream, KeyWrapAlgorithm algorithm, string keyName, string decryptionKeyName, DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, @@ -1466,25 +1466,25 @@ public override Task EncryptAsync( /// [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override Task EncryptAsync( - string vaultResourceName, byte[] plaintextBytes, KeyWrapAlgorithm algorithm, + public override Task> EncryptAsync( + string vaultResourceName, ReadOnlyMemory plaintextBytes, KeyWrapAlgorithm algorithm, string keyName, DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, - CancellationToken cancellationToken = default) => EncryptStreamAsync(vaultResourceName, new MemoryStream(plaintextBytes), + CancellationToken cancellationToken = default) => EncryptStreamAsync(vaultResourceName, new MemoryStream(plaintextBytes.ToArray()), algorithm, keyName, dataEncryptionCipher, string.Empty, true, cancellationToken); /// [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override Task EncryptAsync( - string vaultResourceName, byte[] plaintextBytes, KeyWrapAlgorithm algorithm, + public override Task> EncryptAsync( + string vaultResourceName, ReadOnlyMemory plaintextBytes, KeyWrapAlgorithm algorithm, string keyName, string decryptionKeyName, DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, - CancellationToken cancellationToken = default) => EncryptStreamAsync(vaultResourceName, new MemoryStream(plaintextBytes), + CancellationToken cancellationToken = default) => EncryptStreamAsync(vaultResourceName, new MemoryStream(plaintextBytes.ToArray()), algorithm, keyName, dataEncryptionCipher, decryptionKeyName, false, cancellationToken); /// [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override async Task DecryptAsync(string vaultResourceName, byte[] cipherTextBytes, string keyName, CancellationToken cancellationToken = default) + public override async Task> DecryptAsync(string vaultResourceName, ReadOnlyMemory cipherTextBytes, string keyName, CancellationToken cancellationToken = default) { ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); @@ -1517,7 +1517,8 @@ await duplexStream.RequestStream.WriteAsync( { Payload = new Autogenerated.StreamPayload { - Data = ByteString.CopyFrom(cipherTextBytes, offset, count), Seq = (ulong)a + Data = ByteString.CopyFrom(cipherTextBytes.Span), + Seq = (ulong)a } }, cancellationToken); } @@ -1538,7 +1539,7 @@ await duplexStream.RequestStream.WriteAsync( /// [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override async Task DecryptAsync(string vaultResourceName, Stream cipherTextStream, string keyName, CancellationToken cancellationToken = default) + public override async Task> DecryptAsync(string vaultResourceName, Stream cipherTextStream, string keyName, CancellationToken cancellationToken = default) { ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); From 8a80a863081b159d34a50e1c1b748aa5974dc7b1 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Tue, 2 Jan 2024 18:59:41 -0600 Subject: [PATCH 17/43] Updated to use encryption/decryption options instead of lots of method overload variations. Simplified gRPC implementation to use fewer methods. Applied argument name updates applied previously (plainText -> plaintext). Signed-off-by: Whit Waldo --- src/Dapr.Client/CryptographyOptions.cs | 52 +++++++++ src/Dapr.Client/DaprClient.cs | 60 +++------- src/Dapr.Client/DaprClientGrpc.cs | 151 +++++++------------------ 3 files changed, 106 insertions(+), 157 deletions(-) create mode 100644 src/Dapr.Client/CryptographyOptions.cs diff --git a/src/Dapr.Client/CryptographyOptions.cs b/src/Dapr.Client/CryptographyOptions.cs new file mode 100644 index 000000000..bee394d54 --- /dev/null +++ b/src/Dapr.Client/CryptographyOptions.cs @@ -0,0 +1,52 @@ +#nullable enable +namespace Dapr.Client +{ + /// + /// A collection of options used to configure how encryption cryptographic operations are performed. + /// + public class EncryptionOptions + { + /// + /// Creates a new instance of the . + /// + /// + public EncryptionOptions(KeyWrapAlgorithm keyWrapAlgorithm) + { + KeyWrapAlgorithm = keyWrapAlgorithm; + } + + /// + /// The name of the algorithm used to wrap the encryption key. + /// + public KeyWrapAlgorithm KeyWrapAlgorithm { get; set; } + + /// + /// The size of the block in bytes used to send data to the sidecar for cryptography operations. + /// + /// + /// This defaults to 4KB and generally should not exceed 64KB. + /// + public uint StreamingBlockSizeInBytes { get; set; } = 4 * 1024; + + /// + /// The optional name (and optionally a version) of the key specified to use during decryption. + /// + public string? DecryptionKeyName { get; set; } = null; + + /// + /// The name of the cipher to use for the encryption operation. + /// + public DataEncryptionCipher EncryptionCipher { get; set; } = DataEncryptionCipher.AesGcm; + } + + /// + /// A collection fo options used to configure how decryption cryptographic operations are performed. + /// + public class DecryptionOptions + { + /// + /// The size of the block in bytes used to send data to the sidecar for cryptography operations. + /// + public uint StreamingBlockSizeInBytes { get; set; } = 4 * 1024; + } +} diff --git a/src/Dapr.Client/DaprClient.cs b/src/Dapr.Client/DaprClient.cs index cff3c16a1..a8ed12a48 100644 --- a/src/Dapr.Client/DaprClient.cs +++ b/src/Dapr.Client/DaprClient.cs @@ -947,85 +947,53 @@ public abstract Task UnsubscribeConfiguration( /// /// The name of the vault resource used by the operation. /// The bytes of the plaintext value to encrypt. - /// The name of the algorithm used to wrap the encryption key. /// The name of the key to use from the Vault for the encryption operation. - /// The name of the cipher to use for the encryption operation. + /// Options informing how the encryption operation should be configured. /// A that can be used to cancel the operation. /// An array of encrypted bytes. [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public abstract Task> EncryptAsync(string vaultResourceName, ReadOnlyMemory plaintextBytes, - KeyWrapAlgorithm algorithm, string keyName, DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, + public abstract Task> EncryptAsync(string vaultResourceName, + ReadOnlyMemory plaintextBytes, string keyName, EncryptionOptions encryptionOptions, CancellationToken cancellationToken = default); - - /// - /// Encrypts an array of bytes using the Dapr Cryptography encryption functionality. - /// - /// The name of the vault resource used by the operation. - /// The bytes of the plaintext value to encrypt. - /// The name of the algorithm used to wrap the encryption key. - /// The name of the key to use from the Vault for the encryption operation. - /// The name (and optionally version) of the decryption key to specify should be used. - /// The name of the cipher to use for the encryption operation. - /// A that can be used to cancel the operation. - /// An array of encrypted bytes. - [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public abstract Task> EncryptAsync(string vaultResourceName, ReadOnlyMemory plaintextBytes, - KeyWrapAlgorithm algorithm, string keyName, string decryptionKeyName, DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, - CancellationToken cancellationToken = default); - - /// - /// Encrypts an array of bytes using the Dapr Cryptography encryption functionality. - /// - /// The name of the vault resource used by the operation. - /// The stream containing the bytes of the plaintext value to encrypt. - /// The name of the algorithm used to wrap the encryption key. - /// The name of the key to use from the Vault for the encryption operation. - /// The name of the cipher to use for the encryption operation. - /// A that can be used to cancel the operation. - /// An array of encrypted bytes. - [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public abstract Task> EncryptAsync(string vaultResourceName, Stream plainTextStream, - KeyWrapAlgorithm algorithm, string keyName, DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, - CancellationToken cancellationToken = default); - + /// /// Encrypts an array of bytes using the Dapr Cryptography encryption functionality. /// /// The name of the vault resource used by the operation. - /// The stream containing the bytes of the plaintext value to encrypt. - /// The name of the algorithm used to wrap the encryption key. + /// The stream containing the bytes of the plaintext value to encrypt. /// The name of the key to use from the Vault for the encryption operation. - /// The name (and optionally version) of the decryption key to specify should be used. - /// The name of the cipher to use for the encryption operation. + /// /// A that can be used to cancel the operation. /// An array of encrypted bytes. [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public abstract Task> EncryptAsync(string vaultResourceName, Stream plainTextStream, - KeyWrapAlgorithm algorithm, string keyName, string decryptionKeyName, DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, + public abstract Task> EncryptAsync(string vaultResourceName, Stream plaintextStream, + string keyName, EncryptionOptions encryptionOptions, CancellationToken cancellationToken = default); /// /// Decrypts the specified cipher text bytes using the Dapr Cryptography encryption functionality. /// /// The name of the vault resource used by the operation. - /// The bytes of the cipher text value to decrypt. + /// The bytes of the cipher text value to decrypt. /// The name of the key to use from the Vault for the decryption operation. + /// Options informing how the decryption operation should be configured. /// A that can be used to cancel the operation. /// An array of decrypted bytes. [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public abstract Task> DecryptAsync(string vaultResourceName, ReadOnlyMemory cipherTextBytes, string keyName, + public abstract Task> DecryptAsync(string vaultResourceName, ReadOnlyMemory ciphertextBytes, string keyName, DecryptionOptions options, CancellationToken cancellationToken = default); /// /// Decrypts the specified cipher text bytes using the Dapr Cryptography encryption functionality. /// /// The name of the vault resource used by the operation. - /// The stream containing the bytes of the cipher text value to decrypt. + /// The stream containing the bytes of the cipher text value to decrypt. /// The name of the key to use from the Vault for the decryption operation. + /// Options informing how the decryption operation should be configured. /// A that can be used to cancel the operation. /// An array of decrypted bytes. [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public abstract Task> DecryptAsync(string vaultResourceName, Stream cipherTextStream, string keyName, + public abstract Task> DecryptAsync(string vaultResourceName, Stream ciphertextStream, string keyName, DecryptionOptions options, CancellationToken cancellationToken = default); #endregion diff --git a/src/Dapr.Client/DaprClientGrpc.cs b/src/Dapr.Client/DaprClientGrpc.cs index ef99ea998..d86fb3672 100644 --- a/src/Dapr.Client/DaprClientGrpc.cs +++ b/src/Dapr.Client/DaprClientGrpc.cs @@ -12,6 +12,7 @@ // ------------------------------------------------------------------------ using System.IO; +using System.Net.Security; namespace Dapr.Client { @@ -1380,27 +1381,37 @@ public override async Task UnsubscribeConfigur #region Cryptography - private async Task> EncryptStreamAsync(string vaultResourceName, Stream stream, - KeyWrapAlgorithm algorithm, string keyName, DataEncryptionCipher dataEncryptionCipher, - string decryptionKeyName, bool omitDecryptionKey, CancellationToken cancellationToken = default) + /// + [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public override Task> EncryptAsync(string vaultResourceName, ReadOnlyMemory plaintextBytes, string keyName, EncryptionOptions encryptionOptions, + CancellationToken cancellationToken = default) => + EncryptAsync(vaultResourceName, new MemoryStream(plaintextBytes.ToArray()), keyName, encryptionOptions, cancellationToken); + + /// + [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public override async Task> EncryptAsync(string vaultResourceName, Stream plaintextStream, + string keyName, EncryptionOptions encryptionOptions, CancellationToken cancellationToken = default) { ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); - ArgumentVerifier.ThrowIfNull(stream, nameof(stream)); + ArgumentVerifier.ThrowIfNull(plaintextStream, nameof(plaintextStream)); + ArgumentVerifier.ThrowIfNull(encryptionOptions, nameof(encryptionOptions)); + + var shouldOmitDecryptionKeyName = string.IsNullOrWhiteSpace(encryptionOptions.DecryptionKeyName); //Whitespace isn't likely a valid key name either var encryptRequestOptions = new Autogenerated.EncryptRequestOptions { ComponentName = vaultResourceName, - DataEncryptionCipher = dataEncryptionCipher.GetValueFromEnumMember(), + DataEncryptionCipher = encryptionOptions.EncryptionCipher.GetValueFromEnumMember(), KeyName = keyName, - KeyWrapAlgorithm = algorithm.GetValueFromEnumMember(), - OmitDecryptionKeyName = omitDecryptionKey + KeyWrapAlgorithm = encryptionOptions.KeyWrapAlgorithm.GetValueFromEnumMember(), + OmitDecryptionKeyName = shouldOmitDecryptionKeyName }; - if (!omitDecryptionKey) + if (!shouldOmitDecryptionKeyName) { - ArgumentVerifier.ThrowIfNullOrEmpty(decryptionKeyName, nameof(decryptionKeyName)); - encryptRequestOptions.DecryptionKeyName = decryptionKeyName; + ArgumentVerifier.ThrowIfNullOrEmpty(encryptionOptions.DecryptionKeyName, nameof(encryptionOptions.DecryptionKeyName)); + encryptRequestOptions.DecryptionKeyName = encryptRequestOptions.DecryptionKeyName; } var options = CreateCallOptions(headers: null, cancellationToken); @@ -1411,20 +1422,20 @@ await duplexStream.RequestStream.WriteAsync( new Autogenerated.EncryptRequest { Options = encryptRequestOptions }, cancellationToken); //Send the plaintext bytes in 4 kb blocks in subsequent messages - const int blockSize = 4 * 1024; // 4 kb - await using (var bufferedStream = new BufferedStream(stream, blockSize)) + await using (var bufferedStream = new BufferedStream(plaintextStream, (int)encryptionOptions.StreamingBlockSizeInBytes)) { - var buffer = new byte[blockSize]; + var buffer = new byte[encryptionOptions.StreamingBlockSizeInBytes]; int bytesRead; ulong sequenceNumber = 0; - while ((bytesRead = await bufferedStream.ReadAsync(buffer, 0, blockSize, cancellationToken)) != 0) + while ((bytesRead = await bufferedStream.ReadAsync(buffer, 0, (int)encryptionOptions.StreamingBlockSizeInBytes, cancellationToken)) != 0) { await duplexStream.RequestStream.WriteAsync(new Autogenerated.EncryptRequest { Payload = new Autogenerated.StreamPayload { - Data = ByteString.CopyFrom(buffer, 0, bytesRead), Seq = sequenceNumber + Data = ByteString.CopyFrom(buffer, 0, bytesRead), + Seq = sequenceNumber } }, cancellationToken); @@ -1445,104 +1456,23 @@ await duplexStream.RequestStream.WriteAsync(new Autogenerated.EncryptRequest return returnedBytes.ToArray(); } - - /// - [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override Task> EncryptAsync( - string vaultResourceName, Stream plainTextStream, KeyWrapAlgorithm algorithm, - string keyName, DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, - CancellationToken cancellationToken = default) => EncryptStreamAsync(vaultResourceName, plainTextStream, - algorithm, keyName, dataEncryptionCipher, string.Empty, true, cancellationToken); - - /// - [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override Task> EncryptAsync( - string vaultResourceName, Stream plainTextStream, KeyWrapAlgorithm algorithm, - string keyName, string decryptionKeyName, - DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, - CancellationToken cancellationToken = default) => EncryptStreamAsync(vaultResourceName, plainTextStream, - algorithm, keyName, dataEncryptionCipher, - decryptionKeyName, false, cancellationToken); /// [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override Task> EncryptAsync( - string vaultResourceName, ReadOnlyMemory plaintextBytes, KeyWrapAlgorithm algorithm, - string keyName, DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, - CancellationToken cancellationToken = default) => EncryptStreamAsync(vaultResourceName, new MemoryStream(plaintextBytes.ToArray()), - algorithm, keyName, dataEncryptionCipher, string.Empty, true, cancellationToken); - - /// - [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override Task> EncryptAsync( - string vaultResourceName, ReadOnlyMemory plaintextBytes, KeyWrapAlgorithm algorithm, - string keyName, string decryptionKeyName, - DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm, - CancellationToken cancellationToken = default) => EncryptStreamAsync(vaultResourceName, new MemoryStream(plaintextBytes.ToArray()), - algorithm, keyName, dataEncryptionCipher, - decryptionKeyName, false, cancellationToken); + public override async Task> DecryptAsync(string vaultResourceName, + ReadOnlyMemory ciphertextBytes, string keyName, DecryptionOptions decryptionOptions, + CancellationToken cancellationToken = default) => + await DecryptAsync(vaultResourceName, new MemoryStream(ciphertextBytes.ToArray()), keyName, + decryptionOptions, cancellationToken); /// [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override async Task> DecryptAsync(string vaultResourceName, ReadOnlyMemory cipherTextBytes, string keyName, CancellationToken cancellationToken = default) - { - ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); - ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); - - var decryptRequestOptions = new Autogenerated.DecryptRequestOptions - { - ComponentName = vaultResourceName, KeyName = keyName - }; - - var options = CreateCallOptions(headers: null, cancellationToken); - var duplexStream = client.DecryptAlpha1(options); - - //Start with passing the metadata about the decryption request itself in the first message - await duplexStream.RequestStream.WriteAsync(new Autogenerated.DecryptRequest - { - Options = decryptRequestOptions - }, cancellationToken); - - //Send the cipher text bytes in 4 kb blocks in subsequent messages - const int blockSize = 4 * 1024; // 4 kb - var blockCount = (int)Math.Ceiling((double)cipherTextBytes.Length / blockSize); - - for (var a = 0; a < blockCount; a++) - { - var offset = a * blockSize; - var count = Math.Min(blockSize, cipherTextBytes.Length - offset); - - await duplexStream.RequestStream.WriteAsync( - new Autogenerated.DecryptRequest - { - Payload = new Autogenerated.StreamPayload - { - Data = ByteString.CopyFrom(cipherTextBytes.Span), - Seq = (ulong)a - } - }, cancellationToken); - } - - //Send the completion message - await duplexStream.RequestStream.CompleteAsync(); - - //Set up how the response is handled since we know it's of finite length - var returnedBytes = new List(); - await foreach (var decryptedResponse in duplexStream.ResponseStream.ReadAllAsync(cancellationToken) - .ConfigureAwait(false)) - { - returnedBytes.AddRange(decryptedResponse.Payload.Data.ToByteArray()); - } - - return returnedBytes.ToArray(); - } - - /// - [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override async Task> DecryptAsync(string vaultResourceName, Stream cipherTextStream, string keyName, CancellationToken cancellationToken = default) + public override async Task> DecryptAsync(string vaultResourceName, Stream ciphertextStream, string keyName, DecryptionOptions decryptionOptions, CancellationToken cancellationToken = default) { ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); + ArgumentVerifier.ThrowIfNull(decryptionOptions, nameof(decryptionOptions)); + ArgumentVerifier.ThrowIfNull(ciphertextStream, nameof(ciphertextStream)); var decryptRequestOptions = new Autogenerated.DecryptRequestOptions { @@ -1559,23 +1489,22 @@ await duplexStream.RequestStream.WriteAsync(new Autogenerated.DecryptRequest Options = decryptRequestOptions }, cancellationToken); - //Send the cipher text bytes in 4 kb blocks in subsequent messages - const int blockSize = 4 * 1024; // 4 kb - - await using (var bufferedStream = new BufferedStream(cipherTextStream, blockSize)) + //Send the cipher text bytes in configurably-sized blocks in subsequent messages + await using (var bufferedStream = new BufferedStream(ciphertextStream, (int)decryptionOptions.StreamingBlockSizeInBytes)) { - var buffer = new byte[blockSize]; + var buffer = new byte[(int)decryptionOptions.StreamingBlockSizeInBytes]; int bytesRead; ulong sequenceNumber = 0; - while ((bytesRead = await bufferedStream.ReadAsync(buffer, 0, blockSize, cancellationToken)) != 0) + while ((bytesRead = await bufferedStream.ReadAsync(buffer, 0, (int)decryptionOptions.StreamingBlockSizeInBytes, cancellationToken)) != 0) { await duplexStream.RequestStream.WriteAsync( new Autogenerated.DecryptRequest { Payload = new Autogenerated.StreamPayload { - Data = ByteString.CopyFrom(buffer, 0, bytesRead), Seq = sequenceNumber + Data = ByteString.CopyFrom(buffer, 0, bytesRead), + Seq = sequenceNumber } }, cancellationToken); From 337961eb237dbb688efd8fe1c558d439c0a43d22 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Tue, 2 Jan 2024 19:02:39 -0600 Subject: [PATCH 18/43] Updated tests Signed-off-by: Whit Waldo --- test/Dapr.Client.Test/CryptographyApiTest.cs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/test/Dapr.Client.Test/CryptographyApiTest.cs b/test/Dapr.Client.Test/CryptographyApiTest.cs index e9edd59e1..67174de30 100644 --- a/test/Dapr.Client.Test/CryptographyApiTest.cs +++ b/test/Dapr.Client.Test/CryptographyApiTest.cs @@ -17,7 +17,7 @@ public async Task EncryptAsync_ByteArray_VaultResourceName_ArgumentVerifierExcep const string vaultResourceName = ""; //Get response and validate await Assert.ThrowsAsync(async () => await client.EncryptAsync(vaultResourceName, - Array.Empty(), KeyWrapAlgorithm.Rsa, "MyKey", DataEncryptionCipher.AesGcm, + (ReadOnlyMemory)Array.Empty(), "MyKey", new EncryptionOptions(KeyWrapAlgorithm.Rsa), CancellationToken.None)); } @@ -27,9 +27,8 @@ public async Task EncryptAsync_ByteArray_KeyName_ArgumentVerifierException() var client = new DaprClientBuilder().Build(); const string keyName = ""; //Get response and validate - await Assert.ThrowsAsync(async () => await client.EncryptAsync("myVault", - Array.Empty(), KeyWrapAlgorithm.Rsa, keyName, DataEncryptionCipher.AesGcm, - CancellationToken.None)); + await Assert.ThrowsAsync(async () => await client.EncryptAsync( "myVault", + (ReadOnlyMemory) Array.Empty(), keyName, new EncryptionOptions(KeyWrapAlgorithm.Rsa), CancellationToken.None)); } [Fact] @@ -39,7 +38,7 @@ public async Task EncryptAsync_Stream_VaultResourceName_ArgumentVerifierExceptio const string vaultResourceName = ""; //Get response and validate await Assert.ThrowsAsync(async () => await client.EncryptAsync(vaultResourceName, - new MemoryStream(), KeyWrapAlgorithm.Rsa, "MyKey", DataEncryptionCipher.AesGcm, + new MemoryStream(), "MyKey", new EncryptionOptions(KeyWrapAlgorithm.Rsa), CancellationToken.None)); } @@ -50,7 +49,7 @@ public async Task EncryptAsync_Stream_KeyName_ArgumentVerifierException() const string keyName = ""; //Get response and validate await Assert.ThrowsAsync(async () => await client.EncryptAsync("myVault", - new MemoryStream(), KeyWrapAlgorithm.Rsa, keyName, DataEncryptionCipher.AesGcm, + (Stream) new MemoryStream(), keyName, new EncryptionOptions(KeyWrapAlgorithm.Rsa), CancellationToken.None)); } @@ -61,7 +60,7 @@ public async Task DecryptAsync_ByteArray_VaultResourceName_ArgumentVerifierExcep const string vaultResourceName = ""; //Get response and validate await Assert.ThrowsAsync(async () => await client.DecryptAsync(vaultResourceName, - Array.Empty(), "myKey", CancellationToken.None)); + Array.Empty(), "myKey", new DecryptionOptions(), CancellationToken.None)); } [Fact] @@ -71,7 +70,7 @@ public async Task DecryptAsync_ByteArray_KeyName_ArgumentVerifierException() const string keyName = ""; //Get response and validate await Assert.ThrowsAsync(async () => await client.DecryptAsync("myVault", - Array.Empty(), keyName, CancellationToken.None)); + Array.Empty(), keyName, new DecryptionOptions(), CancellationToken.None)); } [Fact] @@ -81,7 +80,7 @@ public async Task DecryptAsync_Stream_VaultResourceName_ArgumentVerifierExceptio const string vaultResourceName = ""; //Get response and validate await Assert.ThrowsAsync(async () => await client.DecryptAsync(vaultResourceName, - new MemoryStream(), "MyKey", CancellationToken.None)); + new MemoryStream(), "MyKey", new DecryptionOptions(), CancellationToken.None)); } [Fact] @@ -91,7 +90,7 @@ public async Task DecryptAsync_Stream_KeyName_ArgumentVerifierException() const string keyName = ""; //Get response and validate await Assert.ThrowsAsync(async () => await client.DecryptAsync("myVault", - new MemoryStream(), keyName, CancellationToken.None)); + new MemoryStream(), keyName, new DecryptionOptions(), CancellationToken.None)); } } } From a75603f93bbbd5162edaa4f0b6b4abc53951f407 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Tue, 2 Jan 2024 19:08:28 -0600 Subject: [PATCH 19/43] Removed unused reference Signed-off-by: Whit Waldo --- test/Dapr.Client.Test/CryptographyApiTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/test/Dapr.Client.Test/CryptographyApiTest.cs b/test/Dapr.Client.Test/CryptographyApiTest.cs index 67174de30..a7d57a096 100644 --- a/test/Dapr.Client.Test/CryptographyApiTest.cs +++ b/test/Dapr.Client.Test/CryptographyApiTest.cs @@ -2,7 +2,6 @@ using System.IO; using System.Threading; using System.Threading.Tasks; -using Moq; using Xunit; #pragma warning disable CS0618 // Type or member is obsolete From 86315b40541578f537d6111f3ed82bc04084d402 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Tue, 2 Jan 2024 19:09:50 -0600 Subject: [PATCH 20/43] Updated examples to reflect new method shapes. Downgraded package to .net 6 instead of .net 8 per review suggestion. Signed-off-by: Whit Waldo --- examples/Client/Cryptography/Cryptography.csproj | 2 +- .../Examples/EncryptDecryptFileStreamExample.cs | 14 ++++++++------ .../Examples/EncryptDecryptStringExample.cs | 8 ++++---- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/examples/Client/Cryptography/Cryptography.csproj b/examples/Client/Cryptography/Cryptography.csproj index 1b44e1d89..525c38562 100644 --- a/examples/Client/Cryptography/Cryptography.csproj +++ b/examples/Client/Cryptography/Cryptography.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net6.0 enable enable latest diff --git a/examples/Client/Cryptography/Examples/EncryptDecryptFileStreamExample.cs b/examples/Client/Cryptography/Examples/EncryptDecryptFileStreamExample.cs index 632f7d566..b0ca0fa29 100644 --- a/examples/Client/Cryptography/Examples/EncryptDecryptFileStreamExample.cs +++ b/examples/Client/Cryptography/Examples/EncryptDecryptFileStreamExample.cs @@ -38,20 +38,22 @@ public override async Task RunAsync(CancellationToken cancellationToken) //Encrypt the file await using var encryptFs = new FileStream(fileName, FileMode.Open); #pragma warning disable CS0618 // Type or member is obsolete - var encryptedBytesResult = await client.EncryptAsync(componentName, encryptFs, KeyWrapAlgorithm.Rsa, keyName, - DataEncryptionCipher.AesGcm, cancellationToken); + var encryptedBytesResult = await client.EncryptAsync(componentName, encryptFs, keyName, new EncryptionOptions(KeyWrapAlgorithm.Rsa) + { + EncryptionCipher = DataEncryptionCipher.AesGcm + }, cancellationToken); #pragma warning restore CS0618 // Type or member is obsolete - Console.WriteLine($"Encrypted bytes: '{Convert.ToBase64String(encryptedBytesResult)}'"); + Console.WriteLine($"Encrypted bytes: '{Convert.ToBase64String(encryptedBytesResult.Span)}'"); Console.WriteLine(); //Decrypt the temp file from a memory stream this time instead of a file - await using var ms = new MemoryStream(encryptedBytesResult); + await using var ms = new MemoryStream(encryptedBytesResult.ToArray()); #pragma warning disable CS0618 // Type or member is obsolete - var decryptedBytes = await client.DecryptAsync(componentName, ms, keyName, cancellationToken); + var decryptedBytes = await client.DecryptAsync(componentName, ms, keyName, new DecryptionOptions(), cancellationToken); #pragma warning restore CS0618 // Type or member is obsolete Console.WriteLine("Decrypted value:"); - await using var decryptedMs = new MemoryStream(decryptedBytes); + await using var decryptedMs = new MemoryStream(decryptedBytes.ToArray()); using var sr = new StreamReader(decryptedMs); Console.WriteLine(await sr.ReadToEndAsync(cancellationToken)); } diff --git a/examples/Client/Cryptography/Examples/EncryptDecryptStringExample.cs b/examples/Client/Cryptography/Examples/EncryptDecryptStringExample.cs index 5cd99516a..937b91073 100644 --- a/examples/Client/Cryptography/Examples/EncryptDecryptStringExample.cs +++ b/examples/Client/Cryptography/Examples/EncryptDecryptStringExample.cs @@ -34,17 +34,17 @@ public override async Task RunAsync(CancellationToken cancellationToken) //Encrypt the string var plaintextBytes = Encoding.UTF8.GetBytes(plaintextStr); #pragma warning disable CS0618 // Type or member is obsolete - var encryptedBytesResult = await client.EncryptAsync(componentName, plaintextBytes, KeyWrapAlgorithm.Rsa, keyName, DataEncryptionCipher.AesGcm, + var encryptedBytesResult = await client.EncryptAsync(componentName, plaintextBytes, keyName, new EncryptionOptions(KeyWrapAlgorithm.Rsa), cancellationToken); #pragma warning restore CS0618 // Type or member is obsolete - Console.WriteLine($"Encrypted bytes: '{Convert.ToBase64String(encryptedBytesResult)}'"); + Console.WriteLine($"Encrypted bytes: '{Convert.ToBase64String(encryptedBytesResult.Span)}'"); //Decrypt the string #pragma warning disable CS0618 // Type or member is obsolete - var decryptedBytes = await client.DecryptAsync(componentName, encryptedBytesResult, keyName, cancellationToken); + var decryptedBytes = await client.DecryptAsync(componentName, encryptedBytesResult, keyName, new DecryptionOptions(), cancellationToken); #pragma warning restore CS0618 // Type or member is obsolete - Console.WriteLine($"Decrypted string: '{Encoding.UTF8.GetString(decryptedBytes)}'"); + Console.WriteLine($"Decrypted string: '{Encoding.UTF8.GetString(decryptedBytes.ToArray())}'"); } } } From c2e59bc2f5628b093a7fb32fe1a05af8241aac70 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Tue, 2 Jan 2024 19:29:01 -0600 Subject: [PATCH 21/43] Updated to reflect non-aliased values per review suggestion Signed-off-by: Whit Waldo --- src/Dapr.Client/CryptographyEnums.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Dapr.Client/CryptographyEnums.cs b/src/Dapr.Client/CryptographyEnums.cs index 1f136e338..f5955b389 100644 --- a/src/Dapr.Client/CryptographyEnums.cs +++ b/src/Dapr.Client/CryptographyEnums.cs @@ -40,12 +40,12 @@ public enum KeyWrapAlgorithm /// /// Represents the AES key wrap algorithm. /// - [EnumMember(Value="AES")] + [EnumMember(Value="A256KW")] Aes, /// /// An alias for the AES key wrap algorithm. /// - [EnumMember(Value="AES")] + [EnumMember(Value="A256KW")] A256kw, /// /// Represents the AES 128 CBC key wrap algorithm. @@ -65,12 +65,12 @@ public enum KeyWrapAlgorithm /// /// Represents the RSA key wrap algorithm. /// - [EnumMember(Value="RSA")] + [EnumMember(Value= "RSA-OAEP-256")] Rsa, /// /// An alias for the RSA key wrap algorithm. /// - [EnumMember(Value="RSA")] + [EnumMember(Value= "RSA-OAEP-256")] RsaOaep256 //Alias for RSA } } From bdc7ffde19022009ff8df13a5caf0647a638d928 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Tue, 2 Jan 2024 19:56:54 -0600 Subject: [PATCH 22/43] Update to ensure that both send/receive streams run at the same time instead of sequentially. Signed-off-by: Whit Waldo --- src/Dapr.Client/DaprClientGrpc.cs | 87 +++++++++++++++++++++---------- 1 file changed, 59 insertions(+), 28 deletions(-) diff --git a/src/Dapr.Client/DaprClientGrpc.cs b/src/Dapr.Client/DaprClientGrpc.cs index d86fb3672..71363a3f0 100644 --- a/src/Dapr.Client/DaprClientGrpc.cs +++ b/src/Dapr.Client/DaprClientGrpc.cs @@ -11,13 +11,11 @@ // limitations under the License. // ------------------------------------------------------------------------ -using System.IO; -using System.Net.Security; - namespace Dapr.Client { using System; using System.Collections.Generic; + using System.IO; using System.Linq; using System.Net.Http; using System.Net.Http.Json; @@ -1417,24 +1415,39 @@ public override async Task> EncryptAsync(string vaultResour var options = CreateCallOptions(headers: null, cancellationToken); var duplexStream = client.EncryptAlpha1(options); + //Stream the data to the sidecar asynchronously + var sendStreamedData = SendEncryptedStreamAsync(plaintextStream, + (int)encryptionOptions.StreamingBlockSizeInBytes, duplexStream, encryptRequestOptions, + cancellationToken); + + //At the same time, retrieve and buffer the response + var retrieveStreamedData = RetrieveEncryptedBufferedStreamAsync(duplexStream, cancellationToken); + + //Do both operations at the same time and wait on each to complete + var result = await Task.WhenAll(sendStreamedData, retrieveStreamedData); + return result.Last(); //Only care about the buffered response back from the sidecar + } + + private async Task> SendEncryptedStreamAsync(Stream plaintextStream, int streamingBlockSizeInBytes, AsyncDuplexStreamingCall duplexStream, Autogenerated.EncryptRequestOptions encryptRequestOptions, CancellationToken cancellationToken) + { //Start with passing the metadata about the encryption request itself in the first message await duplexStream.RequestStream.WriteAsync( new Autogenerated.EncryptRequest { Options = encryptRequestOptions }, cancellationToken); - //Send the plaintext bytes in 4 kb blocks in subsequent messages - await using (var bufferedStream = new BufferedStream(plaintextStream, (int)encryptionOptions.StreamingBlockSizeInBytes)) + //Send the plaintext bytes in blocks in subsequent messages + await using (var bufferedStream = new BufferedStream(plaintextStream, streamingBlockSizeInBytes)) { - var buffer = new byte[encryptionOptions.StreamingBlockSizeInBytes]; + var buffer = new byte[streamingBlockSizeInBytes]; int bytesRead; ulong sequenceNumber = 0; - while ((bytesRead = await bufferedStream.ReadAsync(buffer, 0, (int)encryptionOptions.StreamingBlockSizeInBytes, cancellationToken)) != 0) + while ((bytesRead = await bufferedStream.ReadAsync(buffer, 0, streamingBlockSizeInBytes, cancellationToken)) != 0) { await duplexStream.RequestStream.WriteAsync(new Autogenerated.EncryptRequest { Payload = new Autogenerated.StreamPayload { - Data = ByteString.CopyFrom(buffer, 0, bytesRead), + Data = ByteString.CopyFrom(buffer, 0, bytesRead), Seq = sequenceNumber } }, cancellationToken); @@ -1443,10 +1456,14 @@ await duplexStream.RequestStream.WriteAsync(new Autogenerated.EncryptRequest sequenceNumber++; } } - + //Send the completion message await duplexStream.RequestStream.CompleteAsync(); + return new ReadOnlyMemory(); + } + private async Task> RetrieveEncryptedBufferedStreamAsync(AsyncDuplexStreamingCall duplexStream, CancellationToken cancellationToken) + { //Set up how the response is handled since we know it's of finite length var returnedBytes = new List(); await foreach (var encryptResponse in duplexStream.ResponseStream.ReadAllAsync(cancellationToken).ConfigureAwait(false)) @@ -1483,30 +1500,41 @@ public override async Task> DecryptAsync(string vaultResour var options = CreateCallOptions(headers: null, cancellationToken); var duplexStream = client.DecryptAlpha1(options); + //Stream the data to the sidecar asynchronously + var sendStreamedData = SendDecryptedStreamAsync(ciphertextStream, + (int)decryptionOptions.StreamingBlockSizeInBytes, duplexStream, decryptRequestOptions, + cancellationToken); + + var retrieveStreamedData = RetrieveDecryptedBufferedStreamAsync(duplexStream, cancellationToken); + + //Do both operations at the same time and wait on each to complete + var result = await Task.WhenAll(sendStreamedData, retrieveStreamedData); + return result.Last(); //Only care about the buffered response back from the sidecar + } + + private async Task> SendDecryptedStreamAsync(Stream ciphertextStream, int streamingBlockSizeInBytes, AsyncDuplexStreamingCall duplexStream, Autogenerated.DecryptRequestOptions decryptRequestOptions, CancellationToken cancellationToken) + { //Start with passing the metadata about the decryption request itself in the first message - await duplexStream.RequestStream.WriteAsync(new Autogenerated.DecryptRequest - { - Options = decryptRequestOptions - }, cancellationToken); + await duplexStream.RequestStream.WriteAsync( + new Autogenerated.DecryptRequest { Options = decryptRequestOptions }, cancellationToken); - //Send the cipher text bytes in configurably-sized blocks in subsequent messages - await using (var bufferedStream = new BufferedStream(ciphertextStream, (int)decryptionOptions.StreamingBlockSizeInBytes)) + //Send the plaintext bytes in blocks in subsequent messages + await using (var bufferedStream = new BufferedStream(ciphertextStream, streamingBlockSizeInBytes)) { - var buffer = new byte[(int)decryptionOptions.StreamingBlockSizeInBytes]; + var buffer = new byte[streamingBlockSizeInBytes]; int bytesRead; ulong sequenceNumber = 0; - while ((bytesRead = await bufferedStream.ReadAsync(buffer, 0, (int)decryptionOptions.StreamingBlockSizeInBytes, cancellationToken)) != 0) + while ((bytesRead = await bufferedStream.ReadAsync(buffer, 0, streamingBlockSizeInBytes, cancellationToken)) != 0) { - await duplexStream.RequestStream.WriteAsync( - new Autogenerated.DecryptRequest + await duplexStream.RequestStream.WriteAsync(new Autogenerated.DecryptRequest + { + Payload = new Autogenerated.StreamPayload { - Payload = new Autogenerated.StreamPayload - { - Data = ByteString.CopyFrom(buffer, 0, bytesRead), - Seq = sequenceNumber - } - }, cancellationToken); + Data = ByteString.CopyFrom(buffer, 0, bytesRead), + Seq = sequenceNumber + } + }, cancellationToken); //Increment the sequence number sequenceNumber++; @@ -1515,13 +1543,16 @@ await duplexStream.RequestStream.WriteAsync( //Send the completion message await duplexStream.RequestStream.CompleteAsync(); + return new ReadOnlyMemory(); + } + private async Task> RetrieveDecryptedBufferedStreamAsync(AsyncDuplexStreamingCall duplexStream, CancellationToken cancellationToken) + { //Set up how the response is handled since we know it's of finite length var returnedBytes = new List(); - await foreach (var decryptedResponse in duplexStream.ResponseStream.ReadAllAsync(cancellationToken) - .ConfigureAwait(false)) + await foreach (var decryptResponse in duplexStream.ResponseStream.ReadAllAsync(cancellationToken).ConfigureAwait(false)) { - returnedBytes.AddRange(decryptedResponse.Payload.Data.ToByteArray()); + returnedBytes.AddRange(decryptResponse.Payload.Data.ToByteArray()); } return returnedBytes.ToArray(); From 705cf77aec4b398703a3cb429ac0e0faf2b566cb Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Wed, 3 Jan 2024 02:55:01 -0600 Subject: [PATCH 23/43] Updated to support streamed results in addition to fixed byte arrays. Refactored implementation to minimize duplicative code. Signed-off-by: Whit Waldo --- src/Dapr.Client/DaprClient.cs | 39 ++++- src/Dapr.Client/DaprClientGrpc.cs | 267 ++++++++++++++++++++++-------- 2 files changed, 229 insertions(+), 77 deletions(-) diff --git a/src/Dapr.Client/DaprClient.cs b/src/Dapr.Client/DaprClient.cs index a8ed12a48..36955fe31 100644 --- a/src/Dapr.Client/DaprClient.cs +++ b/src/Dapr.Client/DaprClient.cs @@ -955,14 +955,14 @@ public abstract Task UnsubscribeConfiguration( public abstract Task> EncryptAsync(string vaultResourceName, ReadOnlyMemory plaintextBytes, string keyName, EncryptionOptions encryptionOptions, CancellationToken cancellationToken = default); - + /// - /// Encrypts an array of bytes using the Dapr Cryptography encryption functionality. + /// Encrypts a stream using the Dapr Cryptography encryption functionality. /// /// The name of the vault resource used by the operation. /// The stream containing the bytes of the plaintext value to encrypt. /// The name of the key to use from the Vault for the encryption operation. - /// + /// Options informing how the encryption operation should be configured. /// A that can be used to cancel the operation. /// An array of encrypted bytes. [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] @@ -971,10 +971,23 @@ public abstract Task> EncryptAsync(string vaultResourceName CancellationToken cancellationToken = default); /// - /// Decrypts the specified cipher text bytes using the Dapr Cryptography encryption functionality. + /// Encrypts a stream using the Dapr Cryptography encryption functionality. /// /// The name of the vault resource used by the operation. - /// The bytes of the cipher text value to decrypt. + /// The stream containing the bytes of the plaintext value to encrypt. + /// The name of the key to use from the Vault for the encryption operation. + /// Options informing how the encryption operation should be configured. + /// A that can be used to cancel the operation. + /// An array of encrypted bytes. + [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public abstract IAsyncEnumerable EncryptStreamAsync(string vaultResourceName, Stream plaintextStream, string keyName, + EncryptionOptions encryptionOptions, CancellationToken cancellationToken = default); + + /// + /// Decrypts the specified ciphertext bytes using the Dapr Cryptography encryption functionality. + /// + /// The name of the vault resource used by the operation. + /// The bytes of the ciphertext value to decrypt. /// The name of the key to use from the Vault for the decryption operation. /// Options informing how the decryption operation should be configured. /// A that can be used to cancel the operation. @@ -984,10 +997,10 @@ public abstract Task> DecryptAsync(string vaultResourceName CancellationToken cancellationToken = default); /// - /// Decrypts the specified cipher text bytes using the Dapr Cryptography encryption functionality. + /// Decrypts the specified ciphertext bytes using the Dapr Cryptography encryption functionality. /// /// The name of the vault resource used by the operation. - /// The stream containing the bytes of the cipher text value to decrypt. + /// The stream containing the bytes of the ciphertext value to decrypt. /// The name of the key to use from the Vault for the decryption operation. /// Options informing how the decryption operation should be configured. /// A that can be used to cancel the operation. @@ -996,6 +1009,18 @@ public abstract Task> DecryptAsync(string vaultResourceName public abstract Task> DecryptAsync(string vaultResourceName, Stream ciphertextStream, string keyName, DecryptionOptions options, CancellationToken cancellationToken = default); + /// + /// Decrypts the specified stream of ciphertext using the Dapr Cryptography encryption functionality. + /// + /// The name of the vault resource used by the operation. + /// The stream containing the bytes of the ciphertext value to decrypt. + /// The name of the key to use from the Vault for the decryption operation. + /// Options informing how the decryption operation should be configured. + /// A that can be used to cancel the operation. + /// An asynchronously enumerable array of decrypted bytes. + public abstract IAsyncEnumerable DecryptStreamAsync(string vaultResourceName, Stream ciphertextStream, + string keyName, DecryptionOptions options, CancellationToken cancellationToken = default); + #endregion #region Cryptography - Subtle API diff --git a/src/Dapr.Client/DaprClientGrpc.cs b/src/Dapr.Client/DaprClientGrpc.cs index 71363a3f0..471cd9635 100644 --- a/src/Dapr.Client/DaprClientGrpc.cs +++ b/src/Dapr.Client/DaprClientGrpc.cs @@ -19,6 +19,7 @@ namespace Dapr.Client using System.Linq; using System.Net.Http; using System.Net.Http.Json; + using System.Runtime.CompilerServices; using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -1379,6 +1380,57 @@ public override async Task UnsubscribeConfigur #region Cryptography + /// + [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public override async Task> EncryptAsync(string vaultResourceName, Stream plaintextStream, string keyName, + EncryptionOptions encryptionOptions, CancellationToken cancellationToken = default) + { + ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); + ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); + ArgumentVerifier.ThrowIfNull(plaintextStream, nameof(plaintextStream)); + ArgumentVerifier.ThrowIfNull(encryptionOptions, nameof(encryptionOptions)); + + var shouldOmitDecryptionKeyName = string.IsNullOrWhiteSpace(encryptionOptions.DecryptionKeyName); //Whitespace isn't likely a valid key name either + + var encryptRequestOptions = new Autogenerated.EncryptRequestOptions + { + ComponentName = vaultResourceName, + DataEncryptionCipher = encryptionOptions.EncryptionCipher.GetValueFromEnumMember(), + KeyName = keyName, + KeyWrapAlgorithm = encryptionOptions.KeyWrapAlgorithm.GetValueFromEnumMember(), + OmitDecryptionKeyName = shouldOmitDecryptionKeyName + }; + + if (!shouldOmitDecryptionKeyName) + { + ArgumentVerifier.ThrowIfNullOrEmpty(encryptionOptions.DecryptionKeyName, nameof(encryptionOptions.DecryptionKeyName)); + encryptRequestOptions.DecryptionKeyName = encryptRequestOptions.DecryptionKeyName; + } + + var options = CreateCallOptions(headers: null, cancellationToken); + var duplexStream = client.EncryptAlpha1(options); + + //Run both operations at the same time, but return the output of the streaming values coming from the operation + var tasks = new Task>[] + { + //Stream the plaintext data to the sidecar in chunks + Task.FromResult(SendPlaintextStreamAsync(plaintextStream, + (int)encryptionOptions.StreamingBlockSizeInBytes, duplexStream, encryptRequestOptions, + cancellationToken)), + //At the same time, retrieve the encrypted response from the sidecar + Task.FromResult(RetrieveEncryptedStreamAsync(duplexStream, cancellationToken)) + }; + + //Return only the buffered result of the `RetrieveEncryptedStreamAsync` method + var bufferedResult = new List(); + await foreach (var item in tasks[1].Result.WithCancellation(cancellationToken)) + { + bufferedResult.AddRange(item); + } + + return bufferedResult.ToArray(); + } + /// [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] public override Task> EncryptAsync(string vaultResourceName, ReadOnlyMemory plaintextBytes, string keyName, EncryptionOptions encryptionOptions, @@ -1387,8 +1439,8 @@ public override Task> EncryptAsync(string vaultResourceName /// [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override async Task> EncryptAsync(string vaultResourceName, Stream plaintextStream, - string keyName, EncryptionOptions encryptionOptions, CancellationToken cancellationToken = default) + public override async IAsyncEnumerable EncryptStreamAsync(string vaultResourceName, Stream plaintextStream, + string keyName, EncryptionOptions encryptionOptions, [EnumeratorCancellation] CancellationToken cancellationToken = default) { ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); @@ -1415,24 +1467,34 @@ public override async Task> EncryptAsync(string vaultResour var options = CreateCallOptions(headers: null, cancellationToken); var duplexStream = client.EncryptAlpha1(options); - //Stream the data to the sidecar asynchronously - var sendStreamedData = SendEncryptedStreamAsync(plaintextStream, - (int)encryptionOptions.StreamingBlockSizeInBytes, duplexStream, encryptRequestOptions, - cancellationToken); - - //At the same time, retrieve and buffer the response - var retrieveStreamedData = RetrieveEncryptedBufferedStreamAsync(duplexStream, cancellationToken); - - //Do both operations at the same time and wait on each to complete - var result = await Task.WhenAll(sendStreamedData, retrieveStreamedData); - return result.Last(); //Only care about the buffered response back from the sidecar + //Run both operations at the same time, but return the output of the streaming values coming from the operation + var tasks = new Task>[] + { + //Stream the plaintext data to the sidecar in chunks + Task.FromResult(SendPlaintextStreamAsync(plaintextStream, (int)encryptionOptions.StreamingBlockSizeInBytes, duplexStream, encryptRequestOptions, cancellationToken)), + //At the same time, retrieve the encrypted response from the sidecar + Task.FromResult(RetrieveEncryptedStreamAsync(duplexStream, cancellationToken)) + }; + + //Return only the result of the `RetrieveEncryptedStreamAsync` method + await foreach (var item in tasks[1].Result.WithCancellation(cancellationToken)) + { + yield return item; + } } - - private async Task> SendEncryptedStreamAsync(Stream plaintextStream, int streamingBlockSizeInBytes, AsyncDuplexStreamingCall duplexStream, Autogenerated.EncryptRequestOptions encryptRequestOptions, CancellationToken cancellationToken) + + /// + /// Sends the plaintext bytes in chunks to the sidecar to be encrypted. + /// + private async IAsyncEnumerable SendPlaintextStreamAsync(Stream plaintextStream, + int streamingBlockSizeInBytes, + AsyncDuplexStreamingCall duplexStream, + Autogenerated.EncryptRequestOptions encryptRequestOptions, + [EnumeratorCancellation] CancellationToken cancellationToken) { //Start with passing the metadata about the encryption request itself in the first message await duplexStream.RequestStream.WriteAsync( - new Autogenerated.EncryptRequest { Options = encryptRequestOptions }, cancellationToken); + new Autogenerated.EncryptRequest {Options = encryptRequestOptions}, cancellationToken); //Send the plaintext bytes in blocks in subsequent messages await using (var bufferedStream = new BufferedStream(plaintextStream, streamingBlockSizeInBytes)) @@ -1441,90 +1503,105 @@ await duplexStream.RequestStream.WriteAsync( int bytesRead; ulong sequenceNumber = 0; - while ((bytesRead = await bufferedStream.ReadAsync(buffer, 0, streamingBlockSizeInBytes, cancellationToken)) != 0) + while ((bytesRead = + await bufferedStream.ReadAsync(buffer, 0, streamingBlockSizeInBytes, cancellationToken)) != + 0) { - await duplexStream.RequestStream.WriteAsync(new Autogenerated.EncryptRequest - { - Payload = new Autogenerated.StreamPayload + await duplexStream.RequestStream.WriteAsync( + new Autogenerated.EncryptRequest { - Data = ByteString.CopyFrom(buffer, 0, bytesRead), - Seq = sequenceNumber - } - }, cancellationToken); + Payload = new Autogenerated.StreamPayload + { + Data = ByteString.CopyFrom(buffer, 0, bytesRead), Seq = sequenceNumber + } + }, cancellationToken); //Increment the sequence number sequenceNumber++; + + //Yield an empty byte array to satisfy the return type though it's unused + yield return Array.Empty(); } } //Send the completion message await duplexStream.RequestStream.CompleteAsync(); - return new ReadOnlyMemory(); + yield break; //End the async enumeration } - private async Task> RetrieveEncryptedBufferedStreamAsync(AsyncDuplexStreamingCall duplexStream, CancellationToken cancellationToken) + /// + /// Retrieves the encrypted bytes from the encryption operation on the sidecar and returns as an enumerable stream. + /// + private async IAsyncEnumerable RetrieveEncryptedStreamAsync(AsyncDuplexStreamingCall duplexStream, [EnumeratorCancellation] CancellationToken cancellationToken) { - //Set up how the response is handled since we know it's of finite length - var returnedBytes = new List(); - await foreach (var encryptResponse in duplexStream.ResponseStream.ReadAllAsync(cancellationToken).ConfigureAwait(false)) + await foreach (var encryptResponse in duplexStream.ResponseStream.ReadAllAsync(cancellationToken) + .ConfigureAwait(false)) { - returnedBytes.AddRange(encryptResponse.Payload.Data.ToByteArray()); + yield return encryptResponse.Payload.Data.ToByteArray(); } - - return returnedBytes.ToArray(); } - /// - [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override async Task> DecryptAsync(string vaultResourceName, - ReadOnlyMemory ciphertextBytes, string keyName, DecryptionOptions decryptionOptions, - CancellationToken cancellationToken = default) => - await DecryptAsync(vaultResourceName, new MemoryStream(ciphertextBytes.ToArray()), keyName, - decryptionOptions, cancellationToken); - - /// - [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override async Task> DecryptAsync(string vaultResourceName, Stream ciphertextStream, string keyName, DecryptionOptions decryptionOptions, CancellationToken cancellationToken = default) + /// + /// Decrypts the specified stream of ciphertext using the Dapr Cryptography encryption functionality. + /// + /// The name of the vault resource used by the operation. + /// The stream containing the bytes of the ciphertext value to decrypt. + /// The name of the key to use from the Vault for the decryption operation. + /// Options informing how the decryption operation should be configured. + /// A that can be used to cancel the operation. + /// An asynchronously enumerable array of decrypted bytes. + public override async IAsyncEnumerable DecryptStreamAsync(string vaultResourceName, Stream ciphertextStream, string keyName, + DecryptionOptions decryptionOptions, [EnumeratorCancellation] CancellationToken cancellationToken = default) { ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); - ArgumentVerifier.ThrowIfNull(decryptionOptions, nameof(decryptionOptions)); ArgumentVerifier.ThrowIfNull(ciphertextStream, nameof(ciphertextStream)); + ArgumentVerifier.ThrowIfNull(decryptionOptions, nameof(decryptionOptions)); var decryptRequestOptions = new Autogenerated.DecryptRequestOptions { - ComponentName = vaultResourceName, - KeyName = keyName + ComponentName = vaultResourceName, KeyName = keyName }; var options = CreateCallOptions(headers: null, cancellationToken); var duplexStream = client.DecryptAlpha1(options); - //Stream the data to the sidecar asynchronously - var sendStreamedData = SendDecryptedStreamAsync(ciphertextStream, - (int)decryptionOptions.StreamingBlockSizeInBytes, duplexStream, decryptRequestOptions, - cancellationToken); - - var retrieveStreamedData = RetrieveDecryptedBufferedStreamAsync(duplexStream, cancellationToken); - - //Do both operations at the same time and wait on each to complete - var result = await Task.WhenAll(sendStreamedData, retrieveStreamedData); - return result.Last(); //Only care about the buffered response back from the sidecar + //Run both operations at the same time, but return the output of the streaming values coming from the operation + var tasks = new Task>[] + { + //Stream the plaintext data to the sidecar in chunks + Task.FromResult(SendCiphertextStreamAsync(ciphertextStream, (int)decryptionOptions.StreamingBlockSizeInBytes, duplexStream, decryptRequestOptions, cancellationToken)), + //At the same time, retrieve the encrypted response from the sidecar + Task.FromResult(RetrieveDecryptedStreamAsync(duplexStream, cancellationToken)) + }; + + //Return only the result of the `RetrieveEncryptedStreamAsync` method + await foreach (var item in tasks[1].Result.WithCancellation(cancellationToken)) + { + yield return item; + } } - private async Task> SendDecryptedStreamAsync(Stream ciphertextStream, int streamingBlockSizeInBytes, AsyncDuplexStreamingCall duplexStream, Autogenerated.DecryptRequestOptions decryptRequestOptions, CancellationToken cancellationToken) + /// + /// Sends the ciphertext bytes in chunks to the sidecar to be decrypted. + /// + private async IAsyncEnumerable SendCiphertextStreamAsync(Stream ciphertextStream, + int streamingBlockSizeInBytes, + AsyncDuplexStreamingCall duplexStream, + Autogenerated.DecryptRequestOptions decryptRequestOptions, + [EnumeratorCancellation] CancellationToken cancellationToken) { //Start with passing the metadata about the decryption request itself in the first message await duplexStream.RequestStream.WriteAsync( new Autogenerated.DecryptRequest { Options = decryptRequestOptions }, cancellationToken); - - //Send the plaintext bytes in blocks in subsequent messages + + //Send the ciphertext bytes in blocks in subsequent messages await using (var bufferedStream = new BufferedStream(ciphertextStream, streamingBlockSizeInBytes)) { var buffer = new byte[streamingBlockSizeInBytes]; int bytesRead; ulong sequenceNumber = 0; - + while ((bytesRead = await bufferedStream.ReadAsync(buffer, 0, streamingBlockSizeInBytes, cancellationToken)) != 0) { await duplexStream.RequestStream.WriteAsync(new Autogenerated.DecryptRequest @@ -1535,29 +1612,79 @@ await duplexStream.RequestStream.WriteAsync(new Autogenerated.DecryptRequest Seq = sequenceNumber } }, cancellationToken); - + //Increment the sequence number sequenceNumber++; + + //Yield an empty byte array to satisfy the return type through it's unused + yield return Array.Empty(); } } - + //Send the completion message await duplexStream.RequestStream.CompleteAsync(); - return new ReadOnlyMemory(); + yield break; // End the async enumeration } - private async Task> RetrieveDecryptedBufferedStreamAsync(AsyncDuplexStreamingCall duplexStream, CancellationToken cancellationToken) + /// + /// Retrieves the decrypted bytes from the decryption operation on the sidecar and returns as an enumerable stream. + /// + private async IAsyncEnumerable RetrieveDecryptedStreamAsync( + AsyncDuplexStreamingCall duplexStream, + [EnumeratorCancellation] CancellationToken cancellationToken) { - //Set up how the response is handled since we know it's of finite length - var returnedBytes = new List(); - await foreach (var decryptResponse in duplexStream.ResponseStream.ReadAllAsync(cancellationToken).ConfigureAwait(false)) + await foreach (var decryptResponse in duplexStream.ResponseStream.ReadAllAsync(cancellationToken) + .ConfigureAwait(false)) { - returnedBytes.AddRange(decryptResponse.Payload.Data.ToByteArray()); + yield return decryptResponse.Payload.Data.ToByteArray(); } - - return returnedBytes.ToArray(); } + + /// + [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public override async Task> DecryptAsync(string vaultResourceName, + ReadOnlyMemory ciphertextBytes, string keyName, DecryptionOptions decryptionOptions, + CancellationToken cancellationToken = default) => + await DecryptAsync(vaultResourceName, new MemoryStream(ciphertextBytes.ToArray()), keyName, + decryptionOptions, cancellationToken); + + /// + [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public override async Task> DecryptAsync(string vaultResourceName, Stream ciphertextStream, string keyName, DecryptionOptions decryptionOptions, CancellationToken cancellationToken = default) + { + ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); + ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); + ArgumentVerifier.ThrowIfNull(decryptionOptions, nameof(decryptionOptions)); + ArgumentVerifier.ThrowIfNull(ciphertextStream, nameof(ciphertextStream)); + + var decryptRequestOptions = new Autogenerated.DecryptRequestOptions + { + ComponentName = vaultResourceName, + KeyName = keyName + }; + + var options = CreateCallOptions(headers: null, cancellationToken); + var duplexStream = client.DecryptAlpha1(options); + //Run both operation at the same time, but return the output of the streaming values coming from the operation + var tasks = new Task>[] + { + Task.FromResult(SendCiphertextStreamAsync(ciphertextStream, + (int)decryptionOptions.StreamingBlockSizeInBytes, duplexStream, decryptRequestOptions, + cancellationToken)), + Task.FromResult(RetrieveDecryptedStreamAsync(duplexStream, cancellationToken)) + }; + + //Return only the buffered result of the `RetrieveDecryptStreamAsync` method + var bufferedResult = new List(); + await foreach(var item in tasks[1].Result.WithCancellation(cancellationToken)) + { + bufferedResult.AddRange(item); + } + + return bufferedResult.ToArray(); + } + #region Subtle Crypto Implementation ///// From 6b6d709223380e56a5c6d4adc63291dcf9570472 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Wed, 3 Jan 2024 02:58:34 -0600 Subject: [PATCH 24/43] Updated example to fix compile issue Signed-off-by: Whit Waldo --- .../Cryptography/Examples/EncryptDecryptFileStreamExample.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/Client/Cryptography/Examples/EncryptDecryptFileStreamExample.cs b/examples/Client/Cryptography/Examples/EncryptDecryptFileStreamExample.cs index b0ca0fa29..ba244637d 100644 --- a/examples/Client/Cryptography/Examples/EncryptDecryptFileStreamExample.cs +++ b/examples/Client/Cryptography/Examples/EncryptDecryptFileStreamExample.cs @@ -35,7 +35,7 @@ public override async Task RunAsync(CancellationToken cancellationToken) } Console.WriteLine(); - //Encrypt the file + //Encrypt from a file stream await using var encryptFs = new FileStream(fileName, FileMode.Open); #pragma warning disable CS0618 // Type or member is obsolete var encryptedBytesResult = await client.EncryptAsync(componentName, encryptFs, keyName, new EncryptionOptions(KeyWrapAlgorithm.Rsa) @@ -55,7 +55,7 @@ public override async Task RunAsync(CancellationToken cancellationToken) Console.WriteLine("Decrypted value:"); await using var decryptedMs = new MemoryStream(decryptedBytes.ToArray()); using var sr = new StreamReader(decryptedMs); - Console.WriteLine(await sr.ReadToEndAsync(cancellationToken)); + Console.WriteLine(await sr.ReadToEndAsync()); } } } From 3c0bcdc5d35f4a1f3ab14f347744015efcfb8091 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Wed, 3 Jan 2024 14:20:18 -0600 Subject: [PATCH 25/43] Removed encrypt/decrypt methods that accepted streams and returned ReadOnlyMemory. Marked implementations that use this on the gRPC class as private instead. Signed-off-by: Whit Waldo --- src/Dapr.Client/DaprClient.cs | 27 --------------------------- src/Dapr.Client/DaprClientGrpc.cs | 8 ++------ 2 files changed, 2 insertions(+), 33 deletions(-) diff --git a/src/Dapr.Client/DaprClient.cs b/src/Dapr.Client/DaprClient.cs index 36955fe31..98060a7ef 100644 --- a/src/Dapr.Client/DaprClient.cs +++ b/src/Dapr.Client/DaprClient.cs @@ -956,20 +956,6 @@ public abstract Task> EncryptAsync(string vaultResourceName ReadOnlyMemory plaintextBytes, string keyName, EncryptionOptions encryptionOptions, CancellationToken cancellationToken = default); - /// - /// Encrypts a stream using the Dapr Cryptography encryption functionality. - /// - /// The name of the vault resource used by the operation. - /// The stream containing the bytes of the plaintext value to encrypt. - /// The name of the key to use from the Vault for the encryption operation. - /// Options informing how the encryption operation should be configured. - /// A that can be used to cancel the operation. - /// An array of encrypted bytes. - [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public abstract Task> EncryptAsync(string vaultResourceName, Stream plaintextStream, - string keyName, EncryptionOptions encryptionOptions, - CancellationToken cancellationToken = default); - /// /// Encrypts a stream using the Dapr Cryptography encryption functionality. /// @@ -996,19 +982,6 @@ public abstract IAsyncEnumerable EncryptStreamAsync(string vaultResource public abstract Task> DecryptAsync(string vaultResourceName, ReadOnlyMemory ciphertextBytes, string keyName, DecryptionOptions options, CancellationToken cancellationToken = default); - /// - /// Decrypts the specified ciphertext bytes using the Dapr Cryptography encryption functionality. - /// - /// The name of the vault resource used by the operation. - /// The stream containing the bytes of the ciphertext value to decrypt. - /// The name of the key to use from the Vault for the decryption operation. - /// Options informing how the decryption operation should be configured. - /// A that can be used to cancel the operation. - /// An array of decrypted bytes. - [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public abstract Task> DecryptAsync(string vaultResourceName, Stream ciphertextStream, string keyName, DecryptionOptions options, - CancellationToken cancellationToken = default); - /// /// Decrypts the specified stream of ciphertext using the Dapr Cryptography encryption functionality. /// diff --git a/src/Dapr.Client/DaprClientGrpc.cs b/src/Dapr.Client/DaprClientGrpc.cs index 471cd9635..c369c0faa 100644 --- a/src/Dapr.Client/DaprClientGrpc.cs +++ b/src/Dapr.Client/DaprClientGrpc.cs @@ -1380,9 +1380,7 @@ public override async Task UnsubscribeConfigur #region Cryptography - /// - [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override async Task> EncryptAsync(string vaultResourceName, Stream plaintextStream, string keyName, + private async Task> EncryptAsync(string vaultResourceName, Stream plaintextStream, string keyName, EncryptionOptions encryptionOptions, CancellationToken cancellationToken = default) { ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); @@ -1648,9 +1646,7 @@ public override async Task> DecryptAsync(string vaultResour await DecryptAsync(vaultResourceName, new MemoryStream(ciphertextBytes.ToArray()), keyName, decryptionOptions, cancellationToken); - /// - [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override async Task> DecryptAsync(string vaultResourceName, Stream ciphertextStream, string keyName, DecryptionOptions decryptionOptions, CancellationToken cancellationToken = default) + private async Task> DecryptAsync(string vaultResourceName, Stream ciphertextStream, string keyName, DecryptionOptions decryptionOptions, CancellationToken cancellationToken = default) { ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); From ba618128668d48883efc60988090ec18953ef91a Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Wed, 3 Jan 2024 14:48:49 -0600 Subject: [PATCH 26/43] Added missing Obsolete attributes on Encrypt/Decrypt methods. Added overloads on decrypt methods that do not require a DecryptionOptions to be passed in. Signed-off-by: Whit Waldo --- src/Dapr.Client/DaprClient.cs | 28 ++++++++++++++++++++++++++++ src/Dapr.Client/DaprClientGrpc.cs | 26 +++++++++++++++++--------- 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/Dapr.Client/DaprClient.cs b/src/Dapr.Client/DaprClient.cs index 98060a7ef..e28830707 100644 --- a/src/Dapr.Client/DaprClient.cs +++ b/src/Dapr.Client/DaprClient.cs @@ -982,6 +982,19 @@ public abstract IAsyncEnumerable EncryptStreamAsync(string vaultResource public abstract Task> DecryptAsync(string vaultResourceName, ReadOnlyMemory ciphertextBytes, string keyName, DecryptionOptions options, CancellationToken cancellationToken = default); + /// + /// Decrypts the specified ciphertext bytes using the Dapr Cryptography encryption functionality. + /// + /// The name of the vault resource used by the operation. + /// The bytes of the ciphertext value to decrypt. + /// The name of the key to use from the Vault for the decryption operation. + /// A that can be used to cancel the operation. + /// An array of decrypted bytes. + [Obsolete( + "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public abstract Task> DecryptAsync(string vaultResourceName, + ReadOnlyMemory ciphertextBytes, string keyName, CancellationToken cancellationToken = default); + /// /// Decrypts the specified stream of ciphertext using the Dapr Cryptography encryption functionality. /// @@ -991,9 +1004,24 @@ public abstract Task> DecryptAsync(string vaultResourceName /// Options informing how the decryption operation should be configured. /// A that can be used to cancel the operation. /// An asynchronously enumerable array of decrypted bytes. + [Obsolete( + "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] public abstract IAsyncEnumerable DecryptStreamAsync(string vaultResourceName, Stream ciphertextStream, string keyName, DecryptionOptions options, CancellationToken cancellationToken = default); + /// + /// Decrypts the specified stream of ciphertext using the Dapr Cryptography encryption functionality. + /// + /// The name of the vault resource used by the operation. + /// The stream containing the bytes of the ciphertext value to decrypt. + /// The name of the key to use from the Vault for the decryption operation. + /// A that can be used to cancel the operation. + /// An asynchronously enumerable array of decrypted bytes. + [Obsolete( + "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public abstract IAsyncEnumerable DecryptStreamAsync(string vaultResourceName, Stream ciphertextStream, + string keyName, CancellationToken cancellationToken = default); + #endregion #region Cryptography - Subtle API diff --git a/src/Dapr.Client/DaprClientGrpc.cs b/src/Dapr.Client/DaprClientGrpc.cs index c369c0faa..0b1d212cf 100644 --- a/src/Dapr.Client/DaprClientGrpc.cs +++ b/src/Dapr.Client/DaprClientGrpc.cs @@ -1539,15 +1539,9 @@ private async IAsyncEnumerable RetrieveEncryptedStreamAsync(AsyncDuplexS } } - /// - /// Decrypts the specified stream of ciphertext using the Dapr Cryptography encryption functionality. - /// - /// The name of the vault resource used by the operation. - /// The stream containing the bytes of the ciphertext value to decrypt. - /// The name of the key to use from the Vault for the decryption operation. - /// Options informing how the decryption operation should be configured. - /// A that can be used to cancel the operation. - /// An asynchronously enumerable array of decrypted bytes. + /// + [Obsolete( + "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] public override async IAsyncEnumerable DecryptStreamAsync(string vaultResourceName, Stream ciphertextStream, string keyName, DecryptionOptions decryptionOptions, [EnumeratorCancellation] CancellationToken cancellationToken = default) { @@ -1580,6 +1574,13 @@ public override async IAsyncEnumerable DecryptStreamAsync(string vaultRe } } + /// + [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public override IAsyncEnumerable DecryptStreamAsync(string vaultResourceName, + Stream ciphertextStream, string keyName, CancellationToken cancellationToken = default) => + DecryptStreamAsync(vaultResourceName, ciphertextStream, keyName, new DecryptionOptions(), + cancellationToken); + /// /// Sends the ciphertext bytes in chunks to the sidecar to be decrypted. /// @@ -1646,6 +1647,13 @@ public override async Task> DecryptAsync(string vaultResour await DecryptAsync(vaultResourceName, new MemoryStream(ciphertextBytes.ToArray()), keyName, decryptionOptions, cancellationToken); + /// + [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + public override async Task> DecryptAsync(string vaultResourceName, + ReadOnlyMemory ciphertextBytes, string keyName, CancellationToken cancellationToken = default) => + await DecryptAsync(vaultResourceName, new MemoryStream(ciphertextBytes.ToArray()), keyName, + new DecryptionOptions(), cancellationToken); + private async Task> DecryptAsync(string vaultResourceName, Stream ciphertextStream, string keyName, DecryptionOptions decryptionOptions, CancellationToken cancellationToken = default) { ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); From 1d0764b006191a72749125df2e1a8e9c32d44d13 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Wed, 3 Jan 2024 15:12:27 -0600 Subject: [PATCH 27/43] Updated encrypt/decrypt options so the streaming block size no longer uses a uint. Added validation in its place to ensure the value provided is never less than or equal to 0. Signed-off-by: Whit Waldo --- src/Dapr.Client/CryptographyOptions.cs | 27 +++++++++++++++++++++++--- src/Dapr.Client/DaprClientGrpc.cs | 8 ++++---- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/Dapr.Client/CryptographyOptions.cs b/src/Dapr.Client/CryptographyOptions.cs index bee394d54..58d91ca43 100644 --- a/src/Dapr.Client/CryptographyOptions.cs +++ b/src/Dapr.Client/CryptographyOptions.cs @@ -1,4 +1,6 @@ #nullable enable +using System; + namespace Dapr.Client { /// @@ -20,14 +22,23 @@ public EncryptionOptions(KeyWrapAlgorithm keyWrapAlgorithm) /// public KeyWrapAlgorithm KeyWrapAlgorithm { get; set; } + private int streamingBlockSizeInBytes = 4 * 1024; // 4 KB /// /// The size of the block in bytes used to send data to the sidecar for cryptography operations. /// /// /// This defaults to 4KB and generally should not exceed 64KB. /// - public uint StreamingBlockSizeInBytes { get; set; } = 4 * 1024; - + public int StreamingBlockSizeInBytes + { + get => streamingBlockSizeInBytes; + set + { + ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(value, 0, nameof(value)); + streamingBlockSizeInBytes = value; + } + } + /// /// The optional name (and optionally a version) of the key specified to use during decryption. /// @@ -44,9 +55,19 @@ public EncryptionOptions(KeyWrapAlgorithm keyWrapAlgorithm) /// public class DecryptionOptions { + private int streamingBlockSizeInBytes = 4 * 1024; // 4KB /// /// The size of the block in bytes used to send data to the sidecar for cryptography operations. /// - public uint StreamingBlockSizeInBytes { get; set; } = 4 * 1024; + public int StreamingBlockSizeInBytes + { + get => streamingBlockSizeInBytes; + set + { + ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(value, 0, nameof(value)); + + streamingBlockSizeInBytes = value; + } + } } } diff --git a/src/Dapr.Client/DaprClientGrpc.cs b/src/Dapr.Client/DaprClientGrpc.cs index 0b1d212cf..141b7dc70 100644 --- a/src/Dapr.Client/DaprClientGrpc.cs +++ b/src/Dapr.Client/DaprClientGrpc.cs @@ -1413,7 +1413,7 @@ private async Task> EncryptAsync(string vaultResourceName, { //Stream the plaintext data to the sidecar in chunks Task.FromResult(SendPlaintextStreamAsync(plaintextStream, - (int)encryptionOptions.StreamingBlockSizeInBytes, duplexStream, encryptRequestOptions, + encryptionOptions.StreamingBlockSizeInBytes, duplexStream, encryptRequestOptions, cancellationToken)), //At the same time, retrieve the encrypted response from the sidecar Task.FromResult(RetrieveEncryptedStreamAsync(duplexStream, cancellationToken)) @@ -1469,7 +1469,7 @@ public override async IAsyncEnumerable EncryptStreamAsync(string vaultRe var tasks = new Task>[] { //Stream the plaintext data to the sidecar in chunks - Task.FromResult(SendPlaintextStreamAsync(plaintextStream, (int)encryptionOptions.StreamingBlockSizeInBytes, duplexStream, encryptRequestOptions, cancellationToken)), + Task.FromResult(SendPlaintextStreamAsync(plaintextStream, encryptionOptions.StreamingBlockSizeInBytes, duplexStream, encryptRequestOptions, cancellationToken)), //At the same time, retrieve the encrypted response from the sidecar Task.FromResult(RetrieveEncryptedStreamAsync(duplexStream, cancellationToken)) }; @@ -1562,7 +1562,7 @@ public override async IAsyncEnumerable DecryptStreamAsync(string vaultRe var tasks = new Task>[] { //Stream the plaintext data to the sidecar in chunks - Task.FromResult(SendCiphertextStreamAsync(ciphertextStream, (int)decryptionOptions.StreamingBlockSizeInBytes, duplexStream, decryptRequestOptions, cancellationToken)), + Task.FromResult(SendCiphertextStreamAsync(ciphertextStream, decryptionOptions.StreamingBlockSizeInBytes, duplexStream, decryptRequestOptions, cancellationToken)), //At the same time, retrieve the encrypted response from the sidecar Task.FromResult(RetrieveDecryptedStreamAsync(duplexStream, cancellationToken)) }; @@ -1674,7 +1674,7 @@ private async Task> DecryptAsync(string vaultResourceName, var tasks = new Task>[] { Task.FromResult(SendCiphertextStreamAsync(ciphertextStream, - (int)decryptionOptions.StreamingBlockSizeInBytes, duplexStream, decryptRequestOptions, + decryptionOptions.StreamingBlockSizeInBytes, duplexStream, decryptRequestOptions, cancellationToken)), Task.FromResult(RetrieveDecryptedStreamAsync(duplexStream, cancellationToken)) }; From 6e827e10d1cefa0f91a48e390a9114851c0aa590 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Wed, 3 Jan 2024 15:43:15 -0600 Subject: [PATCH 28/43] Updated how validation works in the options to accommodate lack of the shorter variation in .NET 6 Signed-off-by: Whit Waldo --- src/Dapr.Client/CryptographyOptions.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Dapr.Client/CryptographyOptions.cs b/src/Dapr.Client/CryptographyOptions.cs index 58d91ca43..ae94a8f2f 100644 --- a/src/Dapr.Client/CryptographyOptions.cs +++ b/src/Dapr.Client/CryptographyOptions.cs @@ -34,7 +34,11 @@ public int StreamingBlockSizeInBytes get => streamingBlockSizeInBytes; set { - ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(value, 0, nameof(value)); + if (value <= 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + streamingBlockSizeInBytes = value; } } @@ -64,7 +68,10 @@ public int StreamingBlockSizeInBytes get => streamingBlockSizeInBytes; set { - ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(value, 0, nameof(value)); + if (value <= 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } streamingBlockSizeInBytes = value; } From ea3992a43c69c164266febd63119be6588a2b66e Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Wed, 3 Jan 2024 15:43:41 -0600 Subject: [PATCH 29/43] Updated names of encrypt/decrypt streaming methods so everything uses just EncryptAsync or DecryptAsync Signed-off-by: Whit Waldo --- src/Dapr.Client/DaprClient.cs | 6 +++--- src/Dapr.Client/DaprClientGrpc.cs | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Dapr.Client/DaprClient.cs b/src/Dapr.Client/DaprClient.cs index e28830707..6c15bf69a 100644 --- a/src/Dapr.Client/DaprClient.cs +++ b/src/Dapr.Client/DaprClient.cs @@ -966,7 +966,7 @@ public abstract Task> EncryptAsync(string vaultResourceName /// A that can be used to cancel the operation. /// An array of encrypted bytes. [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public abstract IAsyncEnumerable EncryptStreamAsync(string vaultResourceName, Stream plaintextStream, string keyName, + public abstract IAsyncEnumerable EncryptAsync(string vaultResourceName, Stream plaintextStream, string keyName, EncryptionOptions encryptionOptions, CancellationToken cancellationToken = default); /// @@ -1006,7 +1006,7 @@ public abstract Task> DecryptAsync(string vaultResourceName /// An asynchronously enumerable array of decrypted bytes. [Obsolete( "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public abstract IAsyncEnumerable DecryptStreamAsync(string vaultResourceName, Stream ciphertextStream, + public abstract IAsyncEnumerable DecryptAsync(string vaultResourceName, Stream ciphertextStream, string keyName, DecryptionOptions options, CancellationToken cancellationToken = default); /// @@ -1019,7 +1019,7 @@ public abstract IAsyncEnumerable DecryptStreamAsync(string vaultResource /// An asynchronously enumerable array of decrypted bytes. [Obsolete( "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public abstract IAsyncEnumerable DecryptStreamAsync(string vaultResourceName, Stream ciphertextStream, + public abstract IAsyncEnumerable DecryptAsync(string vaultResourceName, Stream ciphertextStream, string keyName, CancellationToken cancellationToken = default); #endregion diff --git a/src/Dapr.Client/DaprClientGrpc.cs b/src/Dapr.Client/DaprClientGrpc.cs index 141b7dc70..b5efac361 100644 --- a/src/Dapr.Client/DaprClientGrpc.cs +++ b/src/Dapr.Client/DaprClientGrpc.cs @@ -1380,7 +1380,7 @@ public override async Task UnsubscribeConfigur #region Cryptography - private async Task> EncryptAsync(string vaultResourceName, Stream plaintextStream, string keyName, + private async Task> EncryptDataAsync(string vaultResourceName, Stream plaintextStream, string keyName, EncryptionOptions encryptionOptions, CancellationToken cancellationToken = default) { ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); @@ -1433,11 +1433,11 @@ private async Task> EncryptAsync(string vaultResourceName, [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] public override Task> EncryptAsync(string vaultResourceName, ReadOnlyMemory plaintextBytes, string keyName, EncryptionOptions encryptionOptions, CancellationToken cancellationToken = default) => - EncryptAsync(vaultResourceName, new MemoryStream(plaintextBytes.ToArray()), keyName, encryptionOptions, cancellationToken); + EncryptDataAsync(vaultResourceName, new MemoryStream(plaintextBytes.ToArray()), keyName, encryptionOptions, cancellationToken); /// [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override async IAsyncEnumerable EncryptStreamAsync(string vaultResourceName, Stream plaintextStream, + public override async IAsyncEnumerable EncryptAsync(string vaultResourceName, Stream plaintextStream, string keyName, EncryptionOptions encryptionOptions, [EnumeratorCancellation] CancellationToken cancellationToken = default) { ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); @@ -1542,7 +1542,7 @@ private async IAsyncEnumerable RetrieveEncryptedStreamAsync(AsyncDuplexS /// [Obsolete( "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override async IAsyncEnumerable DecryptStreamAsync(string vaultResourceName, Stream ciphertextStream, string keyName, + public override async IAsyncEnumerable DecryptAsync(string vaultResourceName, Stream ciphertextStream, string keyName, DecryptionOptions decryptionOptions, [EnumeratorCancellation] CancellationToken cancellationToken = default) { ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); @@ -1576,9 +1576,9 @@ public override async IAsyncEnumerable DecryptStreamAsync(string vaultRe /// [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override IAsyncEnumerable DecryptStreamAsync(string vaultResourceName, + public override IAsyncEnumerable DecryptAsync(string vaultResourceName, Stream ciphertextStream, string keyName, CancellationToken cancellationToken = default) => - DecryptStreamAsync(vaultResourceName, ciphertextStream, keyName, new DecryptionOptions(), + DecryptAsync(vaultResourceName, ciphertextStream, keyName, new DecryptionOptions(), cancellationToken); /// @@ -1644,17 +1644,17 @@ private async IAsyncEnumerable RetrieveDecryptedStreamAsync( public override async Task> DecryptAsync(string vaultResourceName, ReadOnlyMemory ciphertextBytes, string keyName, DecryptionOptions decryptionOptions, CancellationToken cancellationToken = default) => - await DecryptAsync(vaultResourceName, new MemoryStream(ciphertextBytes.ToArray()), keyName, + await DecryptDataAsync(vaultResourceName, new MemoryStream(ciphertextBytes.ToArray()), keyName, decryptionOptions, cancellationToken); /// [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] public override async Task> DecryptAsync(string vaultResourceName, ReadOnlyMemory ciphertextBytes, string keyName, CancellationToken cancellationToken = default) => - await DecryptAsync(vaultResourceName, new MemoryStream(ciphertextBytes.ToArray()), keyName, + await DecryptDataAsync(vaultResourceName, new MemoryStream(ciphertextBytes.ToArray()), keyName, new DecryptionOptions(), cancellationToken); - private async Task> DecryptAsync(string vaultResourceName, Stream ciphertextStream, string keyName, DecryptionOptions decryptionOptions, CancellationToken cancellationToken = default) + private async Task> DecryptDataAsync(string vaultResourceName, Stream ciphertextStream, string keyName, DecryptionOptions decryptionOptions, CancellationToken cancellationToken = default) { ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); From 50bf98140934c32f54307a4be9fcf41181f83009 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Wed, 3 Jan 2024 16:04:26 -0600 Subject: [PATCH 30/43] Fixed regression that would have prevented data from being sent entirely to the sidecar. Also simplified operation per suggestion in review. Signed-off-by: Whit Waldo --- src/Dapr.Client/DaprClientGrpc.cs | 56 +++++++++++++++---------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/Dapr.Client/DaprClientGrpc.cs b/src/Dapr.Client/DaprClientGrpc.cs index b5efac361..5ffb35fc9 100644 --- a/src/Dapr.Client/DaprClientGrpc.cs +++ b/src/Dapr.Client/DaprClientGrpc.cs @@ -1409,19 +1409,18 @@ private async Task> EncryptDataAsync(string vaultResourceNa var duplexStream = client.EncryptAlpha1(options); //Run both operations at the same time, but return the output of the streaming values coming from the operation - var tasks = new Task>[] - { + var receiveResult = Task.FromResult(RetrieveEncryptedStreamAsync(duplexStream, cancellationToken)); + await Task.WhenAll( //Stream the plaintext data to the sidecar in chunks Task.FromResult(SendPlaintextStreamAsync(plaintextStream, encryptionOptions.StreamingBlockSizeInBytes, duplexStream, encryptRequestOptions, cancellationToken)), //At the same time, retrieve the encrypted response from the sidecar - Task.FromResult(RetrieveEncryptedStreamAsync(duplexStream, cancellationToken)) - }; - + receiveResult); + //Return only the buffered result of the `RetrieveEncryptedStreamAsync` method var bufferedResult = new List(); - await foreach (var item in tasks[1].Result.WithCancellation(cancellationToken)) + await foreach (var item in receiveResult.Result.WithCancellation(cancellationToken)) { bufferedResult.AddRange(item); } @@ -1466,16 +1465,16 @@ public override async IAsyncEnumerable EncryptAsync(string vaultResource var duplexStream = client.EncryptAlpha1(options); //Run both operations at the same time, but return the output of the streaming values coming from the operation - var tasks = new Task>[] - { + var receiveResult = Task.FromResult(RetrieveEncryptedStreamAsync(duplexStream, cancellationToken)); + await Task.WhenAll( //Stream the plaintext data to the sidecar in chunks - Task.FromResult(SendPlaintextStreamAsync(plaintextStream, encryptionOptions.StreamingBlockSizeInBytes, duplexStream, encryptRequestOptions, cancellationToken)), + Task.FromResult(SendPlaintextStreamAsync(plaintextStream, encryptionOptions.StreamingBlockSizeInBytes, + duplexStream, encryptRequestOptions, cancellationToken)), //At the same time, retrieve the encrypted response from the sidecar - Task.FromResult(RetrieveEncryptedStreamAsync(duplexStream, cancellationToken)) - }; + receiveResult); //Return only the result of the `RetrieveEncryptedStreamAsync` method - await foreach (var item in tasks[1].Result.WithCancellation(cancellationToken)) + await foreach (var item in receiveResult.Result.WithCancellation(cancellationToken)) { yield return item; } @@ -1559,16 +1558,17 @@ public override async IAsyncEnumerable DecryptAsync(string vaultResource var duplexStream = client.DecryptAlpha1(options); //Run both operations at the same time, but return the output of the streaming values coming from the operation - var tasks = new Task>[] - { - //Stream the plaintext data to the sidecar in chunks - Task.FromResult(SendCiphertextStreamAsync(ciphertextStream, decryptionOptions.StreamingBlockSizeInBytes, duplexStream, decryptRequestOptions, cancellationToken)), - //At the same time, retrieve the encrypted response from the sidecar - Task.FromResult(RetrieveDecryptedStreamAsync(duplexStream, cancellationToken)) - }; + var receiveResult = Task.FromResult(RetrieveDecryptedStreamAsync(duplexStream, cancellationToken)); + await Task.WhenAll( + //Stream the ciphertext data to the sidecar in chunks + Task.FromResult(SendCiphertextStreamAsync(ciphertextStream, decryptionOptions.StreamingBlockSizeInBytes, + duplexStream, decryptRequestOptions, cancellationToken)), + //At the same time, retrieve the decrypted response from the sidecar + receiveResult + ); //Return only the result of the `RetrieveEncryptedStreamAsync` method - await foreach (var item in tasks[1].Result.WithCancellation(cancellationToken)) + await foreach (var item in receiveResult.Result.WithCancellation(cancellationToken)) { yield return item; } @@ -1671,17 +1671,17 @@ private async Task> DecryptDataAsync(string vaultResourceNa var duplexStream = client.DecryptAlpha1(options); //Run both operation at the same time, but return the output of the streaming values coming from the operation - var tasks = new Task>[] - { - Task.FromResult(SendCiphertextStreamAsync(ciphertextStream, - decryptionOptions.StreamingBlockSizeInBytes, duplexStream, decryptRequestOptions, - cancellationToken)), - Task.FromResult(RetrieveDecryptedStreamAsync(duplexStream, cancellationToken)) - }; + var receiveResult = Task.FromResult(RetrieveDecryptedStreamAsync(duplexStream, cancellationToken)); + await Task.WhenAll( + //Stream the ciphertext data to the sidecar in chunks + Task.FromResult(SendCiphertextStreamAsync(ciphertextStream, decryptionOptions.StreamingBlockSizeInBytes, + duplexStream, decryptRequestOptions, cancellationToken)), + //At the same time, retrieve the encrypted response from the sidecar + receiveResult); //Return only the buffered result of the `RetrieveDecryptStreamAsync` method var bufferedResult = new List(); - await foreach(var item in tasks[1].Result.WithCancellation(cancellationToken)) + await foreach(var item in receiveResult.Result.WithCancellation(cancellationToken)) { bufferedResult.AddRange(item); } From 0c25a56629151e031602614f0c54100c1df1c7fc Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Wed, 3 Jan 2024 16:23:46 -0600 Subject: [PATCH 31/43] Updated examples to reflect changed API Signed-off-by: Whit Waldo --- .../EncryptDecryptFileStreamExample.cs | 50 ++++++++++++------- .../Examples/EncryptDecryptStringExample.cs | 5 +- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/examples/Client/Cryptography/Examples/EncryptDecryptFileStreamExample.cs b/examples/Client/Cryptography/Examples/EncryptDecryptFileStreamExample.cs index ba244637d..a34ba6545 100644 --- a/examples/Client/Cryptography/Examples/EncryptDecryptFileStreamExample.cs +++ b/examples/Client/Cryptography/Examples/EncryptDecryptFileStreamExample.cs @@ -12,6 +12,7 @@ // ------------------------------------------------------------------------ using Dapr.Client; +#pragma warning disable CS0618 // Type or member is obsolete namespace Cryptography.Examples { @@ -35,27 +36,40 @@ public override async Task RunAsync(CancellationToken cancellationToken) } Console.WriteLine(); - //Encrypt from a file stream + //Encrypt from a file stream and buffer the resulting bytes to an in-memory List await using var encryptFs = new FileStream(fileName, FileMode.Open); -#pragma warning disable CS0618 // Type or member is obsolete - var encryptedBytesResult = await client.EncryptAsync(componentName, encryptFs, keyName, new EncryptionOptions(KeyWrapAlgorithm.Rsa) + var bufferedEncBytes = new List(); + await foreach (var bytes in client.EncryptAsync(componentName, encryptFs, keyName, + new EncryptionOptions(KeyWrapAlgorithm.Rsa), cancellationToken)) { - EncryptionCipher = DataEncryptionCipher.AesGcm - }, cancellationToken); -#pragma warning restore CS0618 // Type or member is obsolete - Console.WriteLine($"Encrypted bytes: '{Convert.ToBase64String(encryptedBytesResult.Span)}'"); + bufferedEncBytes.AddRange(bytes); + } + + var encryptedBytesArr = bufferedEncBytes.ToArray(); + Console.WriteLine($"Encrypted bytes: {Convert.ToBase64String(encryptedBytesArr)}"); Console.WriteLine(); - - //Decrypt the temp file from a memory stream this time instead of a file - await using var ms = new MemoryStream(encryptedBytesResult.ToArray()); -#pragma warning disable CS0618 // Type or member is obsolete - var decryptedBytes = await client.DecryptAsync(componentName, ms, keyName, new DecryptionOptions(), cancellationToken); -#pragma warning restore CS0618 // Type or member is obsolete - - Console.WriteLine("Decrypted value:"); - await using var decryptedMs = new MemoryStream(decryptedBytes.ToArray()); - using var sr = new StreamReader(decryptedMs); - Console.WriteLine(await sr.ReadToEndAsync()); + + //Decrypt the bytes from a memory stream back into a file (via stream) + + //We'll write to a temporary file via a FileStream + var tempDecryptedFile = Path.GetTempFileName(); + await using var decryptFs = new FileStream(tempDecryptedFile, FileMode.Create); + + //We'll decrypt the bytes from a MemoryStream + await using var encryptedMs = new MemoryStream(encryptedBytesArr); + await foreach (var result in client.DecryptAsync(componentName, encryptedMs, keyName, cancellationToken)) + { + decryptFs.Write(result); + } + decryptFs.Close(); + + //Let's confirm the value as written to the file + var decryptedValue = await File.ReadAllTextAsync(tempDecryptedFile, cancellationToken); + Console.WriteLine($"Decrypted value: "); + Console.WriteLine(decryptedValue); + + //And some cleanup to delete our temp file + File.Delete(tempDecryptedFile); } } } diff --git a/examples/Client/Cryptography/Examples/EncryptDecryptStringExample.cs b/examples/Client/Cryptography/Examples/EncryptDecryptStringExample.cs index 937b91073..a37ca1b8b 100644 --- a/examples/Client/Cryptography/Examples/EncryptDecryptStringExample.cs +++ b/examples/Client/Cryptography/Examples/EncryptDecryptStringExample.cs @@ -13,6 +13,7 @@ using System.Text; using Dapr.Client; +#pragma warning disable CS0618 // Type or member is obsolete namespace Cryptography.Examples { @@ -33,17 +34,13 @@ public override async Task RunAsync(CancellationToken cancellationToken) //Encrypt the string var plaintextBytes = Encoding.UTF8.GetBytes(plaintextStr); -#pragma warning disable CS0618 // Type or member is obsolete var encryptedBytesResult = await client.EncryptAsync(componentName, plaintextBytes, keyName, new EncryptionOptions(KeyWrapAlgorithm.Rsa), cancellationToken); -#pragma warning restore CS0618 // Type or member is obsolete Console.WriteLine($"Encrypted bytes: '{Convert.ToBase64String(encryptedBytesResult.Span)}'"); //Decrypt the string -#pragma warning disable CS0618 // Type or member is obsolete var decryptedBytes = await client.DecryptAsync(componentName, encryptedBytesResult, keyName, new DecryptionOptions(), cancellationToken); -#pragma warning restore CS0618 // Type or member is obsolete Console.WriteLine($"Decrypted string: '{Encoding.UTF8.GetString(decryptedBytes.ToArray())}'"); } } From 5bb55fea577fe88d5e85210d921bcc239fac0454 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Wed, 3 Jan 2024 16:30:02 -0600 Subject: [PATCH 32/43] Updated so IAsyncEnumerable methods (encrypt and decrypt) return IAsyncEnumerable> instead of IAsyncEnumerable. Signed-off-by: Whit Waldo --- src/Dapr.Client/DaprClient.cs | 6 +++--- src/Dapr.Client/DaprClientGrpc.cs | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Dapr.Client/DaprClient.cs b/src/Dapr.Client/DaprClient.cs index 6c15bf69a..738e1dc76 100644 --- a/src/Dapr.Client/DaprClient.cs +++ b/src/Dapr.Client/DaprClient.cs @@ -966,7 +966,7 @@ public abstract Task> EncryptAsync(string vaultResourceName /// A that can be used to cancel the operation. /// An array of encrypted bytes. [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public abstract IAsyncEnumerable EncryptAsync(string vaultResourceName, Stream plaintextStream, string keyName, + public abstract IAsyncEnumerable> EncryptAsync(string vaultResourceName, Stream plaintextStream, string keyName, EncryptionOptions encryptionOptions, CancellationToken cancellationToken = default); /// @@ -1006,7 +1006,7 @@ public abstract Task> DecryptAsync(string vaultResourceName /// An asynchronously enumerable array of decrypted bytes. [Obsolete( "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public abstract IAsyncEnumerable DecryptAsync(string vaultResourceName, Stream ciphertextStream, + public abstract IAsyncEnumerable> DecryptAsync(string vaultResourceName, Stream ciphertextStream, string keyName, DecryptionOptions options, CancellationToken cancellationToken = default); /// @@ -1019,7 +1019,7 @@ public abstract IAsyncEnumerable DecryptAsync(string vaultResourceName, /// An asynchronously enumerable array of decrypted bytes. [Obsolete( "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public abstract IAsyncEnumerable DecryptAsync(string vaultResourceName, Stream ciphertextStream, + public abstract IAsyncEnumerable> DecryptAsync(string vaultResourceName, Stream ciphertextStream, string keyName, CancellationToken cancellationToken = default); #endregion diff --git a/src/Dapr.Client/DaprClientGrpc.cs b/src/Dapr.Client/DaprClientGrpc.cs index 5ffb35fc9..dfc685c8e 100644 --- a/src/Dapr.Client/DaprClientGrpc.cs +++ b/src/Dapr.Client/DaprClientGrpc.cs @@ -1436,7 +1436,7 @@ public override Task> EncryptAsync(string vaultResourceName /// [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override async IAsyncEnumerable EncryptAsync(string vaultResourceName, Stream plaintextStream, + public override async IAsyncEnumerable> EncryptAsync(string vaultResourceName, Stream plaintextStream, string keyName, EncryptionOptions encryptionOptions, [EnumeratorCancellation] CancellationToken cancellationToken = default) { ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); @@ -1541,7 +1541,7 @@ private async IAsyncEnumerable RetrieveEncryptedStreamAsync(AsyncDuplexS /// [Obsolete( "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override async IAsyncEnumerable DecryptAsync(string vaultResourceName, Stream ciphertextStream, string keyName, + public override async IAsyncEnumerable> DecryptAsync(string vaultResourceName, Stream ciphertextStream, string keyName, DecryptionOptions decryptionOptions, [EnumeratorCancellation] CancellationToken cancellationToken = default) { ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); @@ -1576,7 +1576,7 @@ await Task.WhenAll( /// [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override IAsyncEnumerable DecryptAsync(string vaultResourceName, + public override IAsyncEnumerable> DecryptAsync(string vaultResourceName, Stream ciphertextStream, string keyName, CancellationToken cancellationToken = default) => DecryptAsync(vaultResourceName, ciphertextStream, keyName, new DecryptionOptions(), cancellationToken); @@ -1584,7 +1584,7 @@ public override IAsyncEnumerable DecryptAsync(string vaultResourceName, /// /// Sends the ciphertext bytes in chunks to the sidecar to be decrypted. /// - private async IAsyncEnumerable SendCiphertextStreamAsync(Stream ciphertextStream, + private async IAsyncEnumerable> SendCiphertextStreamAsync(Stream ciphertextStream, int streamingBlockSizeInBytes, AsyncDuplexStreamingCall duplexStream, Autogenerated.DecryptRequestOptions decryptRequestOptions, From 4ed3562d7e2ac6d40c7aeeae1789f3f2b39fe08e Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Wed, 3 Jan 2024 16:36:34 -0600 Subject: [PATCH 33/43] Updated example to reflect change from IAsyncEnumerable to IAsyncEnumerable> Signed-off-by: Whit Waldo --- .../EncryptDecryptFileStreamExample.cs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/examples/Client/Cryptography/Examples/EncryptDecryptFileStreamExample.cs b/examples/Client/Cryptography/Examples/EncryptDecryptFileStreamExample.cs index a34ba6545..9aea16998 100644 --- a/examples/Client/Cryptography/Examples/EncryptDecryptFileStreamExample.cs +++ b/examples/Client/Cryptography/Examples/EncryptDecryptFileStreamExample.cs @@ -11,6 +11,7 @@ // limitations under the License. // ------------------------------------------------------------------------ +using System.Buffers; using Dapr.Client; #pragma warning disable CS0618 // Type or member is obsolete @@ -36,30 +37,28 @@ public override async Task RunAsync(CancellationToken cancellationToken) } Console.WriteLine(); - //Encrypt from a file stream and buffer the resulting bytes to an in-memory List + //Encrypt from a file stream and buffer the resulting bytes to an in-memory buffer await using var encryptFs = new FileStream(fileName, FileMode.Open); - var bufferedEncBytes = new List(); + + var bufferedEncryptedBytes = new ArrayBufferWriter(); await foreach (var bytes in client.EncryptAsync(componentName, encryptFs, keyName, new EncryptionOptions(KeyWrapAlgorithm.Rsa), cancellationToken)) { - bufferedEncBytes.AddRange(bytes); + bufferedEncryptedBytes.Write(bytes.Span); } - var encryptedBytesArr = bufferedEncBytes.ToArray(); - Console.WriteLine($"Encrypted bytes: {Convert.ToBase64String(encryptedBytesArr)}"); + Console.WriteLine($"Encrypted bytes: {Convert.ToBase64String(bufferedEncryptedBytes.GetSpan())}"); Console.WriteLine(); - //Decrypt the bytes from a memory stream back into a file (via stream) - //We'll write to a temporary file via a FileStream var tempDecryptedFile = Path.GetTempFileName(); await using var decryptFs = new FileStream(tempDecryptedFile, FileMode.Create); - //We'll decrypt the bytes from a MemoryStream - await using var encryptedMs = new MemoryStream(encryptedBytesArr); + //We'll stream the decrypted bytes from a MemoryStream into the above temporary file + await using var encryptedMs = new MemoryStream(bufferedEncryptedBytes.WrittenMemory.ToArray()); await foreach (var result in client.DecryptAsync(componentName, encryptedMs, keyName, cancellationToken)) { - decryptFs.Write(result); + decryptFs.Write(result.Span); } decryptFs.Close(); From 878b8e7c627867601f408069df39f826f9dd4823 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Wed, 3 Jan 2024 16:52:40 -0600 Subject: [PATCH 34/43] Avoiding allocation by using MemoryMarshal instead of .ToArray() to create MemoryStream from ReadOnlyMemory. Signed-off-by: Whit Waldo --- src/Dapr.Client/DaprClientGrpc.cs | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/Dapr.Client/DaprClientGrpc.cs b/src/Dapr.Client/DaprClientGrpc.cs index dfc685c8e..ecb5f6c15 100644 --- a/src/Dapr.Client/DaprClientGrpc.cs +++ b/src/Dapr.Client/DaprClientGrpc.cs @@ -20,6 +20,7 @@ namespace Dapr.Client using System.Net.Http; using System.Net.Http.Json; using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -1430,9 +1431,17 @@ await Task.WhenAll( /// [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override Task> EncryptAsync(string vaultResourceName, ReadOnlyMemory plaintextBytes, string keyName, EncryptionOptions encryptionOptions, - CancellationToken cancellationToken = default) => - EncryptDataAsync(vaultResourceName, new MemoryStream(plaintextBytes.ToArray()), keyName, encryptionOptions, cancellationToken); + public override async Task> EncryptAsync(string vaultResourceName, ReadOnlyMemory plaintextBytes, string keyName, EncryptionOptions encryptionOptions, + CancellationToken cancellationToken = default) + { + if (MemoryMarshal.TryGetArray(plaintextBytes, out var plaintextSegment) && plaintextSegment.Array != null) + { + return await EncryptDataAsync(vaultResourceName, new MemoryStream(plaintextSegment.Array), keyName, + encryptionOptions, cancellationToken); + } + + throw new ArgumentException("Unable to read bytes", nameof(plaintextBytes)); + } /// [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] @@ -1643,15 +1652,22 @@ private async IAsyncEnumerable RetrieveDecryptedStreamAsync( [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] public override async Task> DecryptAsync(string vaultResourceName, ReadOnlyMemory ciphertextBytes, string keyName, DecryptionOptions decryptionOptions, - CancellationToken cancellationToken = default) => - await DecryptDataAsync(vaultResourceName, new MemoryStream(ciphertextBytes.ToArray()), keyName, - decryptionOptions, cancellationToken); + CancellationToken cancellationToken = default) + { + if (MemoryMarshal.TryGetArray(ciphertextBytes, out var ciphertextSegment) && ciphertextSegment.Array != null) + { + return await DecryptDataAsync(vaultResourceName, new MemoryStream(ciphertextSegment.Array), keyName, + decryptionOptions, cancellationToken); + } + + throw new ArgumentException("Unable to read bytes", nameof(ciphertextBytes)); + } /// [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] public override async Task> DecryptAsync(string vaultResourceName, ReadOnlyMemory ciphertextBytes, string keyName, CancellationToken cancellationToken = default) => - await DecryptDataAsync(vaultResourceName, new MemoryStream(ciphertextBytes.ToArray()), keyName, + await DecryptAsync(vaultResourceName, ciphertextBytes, keyName, new DecryptionOptions(), cancellationToken); private async Task> DecryptDataAsync(string vaultResourceName, Stream ciphertextStream, string keyName, DecryptionOptions decryptionOptions, CancellationToken cancellationToken = default) From 7c83888cdb5edacc51377b01e6d64ff6713f673c Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Thu, 4 Jan 2024 13:22:31 -0600 Subject: [PATCH 35/43] Performance updates to minimize unnecessary byte array copies and eliminate unnecessary allocations. Signed-off-by: Whit Waldo --- src/Dapr.Client/DaprClientGrpc.cs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/Dapr.Client/DaprClientGrpc.cs b/src/Dapr.Client/DaprClientGrpc.cs index ecb5f6c15..02991174d 100644 --- a/src/Dapr.Client/DaprClientGrpc.cs +++ b/src/Dapr.Client/DaprClientGrpc.cs @@ -14,6 +14,7 @@ namespace Dapr.Client { using System; + using System.Buffers; using System.Collections.Generic; using System.IO; using System.Linq; @@ -1420,13 +1421,13 @@ await Task.WhenAll( receiveResult); //Return only the buffered result of the `RetrieveEncryptedStreamAsync` method - var bufferedResult = new List(); + var bufferedResult = new ArrayBufferWriter(); await foreach (var item in receiveResult.Result.WithCancellation(cancellationToken)) { - bufferedResult.AddRange(item); + bufferedResult.Write(item.Span); } - return bufferedResult.ToArray(); + return bufferedResult.WrittenMemory; } /// @@ -1538,12 +1539,12 @@ await duplexStream.RequestStream.WriteAsync( /// /// Retrieves the encrypted bytes from the encryption operation on the sidecar and returns as an enumerable stream. /// - private async IAsyncEnumerable RetrieveEncryptedStreamAsync(AsyncDuplexStreamingCall duplexStream, [EnumeratorCancellation] CancellationToken cancellationToken) + private async IAsyncEnumerable> RetrieveEncryptedStreamAsync(AsyncDuplexStreamingCall duplexStream, [EnumeratorCancellation] CancellationToken cancellationToken) { await foreach (var encryptResponse in duplexStream.ResponseStream.ReadAllAsync(cancellationToken) .ConfigureAwait(false)) { - yield return encryptResponse.Payload.Data.ToByteArray(); + yield return encryptResponse.Payload.Data.Memory; } } @@ -1637,14 +1638,14 @@ await duplexStream.RequestStream.WriteAsync(new Autogenerated.DecryptRequest /// /// Retrieves the decrypted bytes from the decryption operation on the sidecar and returns as an enumerable stream. /// - private async IAsyncEnumerable RetrieveDecryptedStreamAsync( + private async IAsyncEnumerable> RetrieveDecryptedStreamAsync( AsyncDuplexStreamingCall duplexStream, [EnumeratorCancellation] CancellationToken cancellationToken) { await foreach (var decryptResponse in duplexStream.ResponseStream.ReadAllAsync(cancellationToken) .ConfigureAwait(false)) { - yield return decryptResponse.Payload.Data.ToByteArray(); + yield return decryptResponse.Payload.Data.Memory; } } @@ -1696,13 +1697,13 @@ await Task.WhenAll( receiveResult); //Return only the buffered result of the `RetrieveDecryptStreamAsync` method - var bufferedResult = new List(); + var bufferedResult = new ArrayBufferWriter(); await foreach(var item in receiveResult.Result.WithCancellation(cancellationToken)) { - bufferedResult.AddRange(item); + bufferedResult.Write(item.Span); } - return bufferedResult.ToArray(); + return bufferedResult.WrittenMemory; } #region Subtle Crypto Implementation From 17bf79f2e261950d26612bd52bdb2abca3729485 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Thu, 4 Jan 2024 13:26:28 -0600 Subject: [PATCH 36/43] Removed unnecessary return from SendPlaintextStreamAsync and SendCiphertextStreamAsync methods Signed-off-by: Whit Waldo --- src/Dapr.Client/DaprClientGrpc.cs | 32 ++++++++++++------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/src/Dapr.Client/DaprClientGrpc.cs b/src/Dapr.Client/DaprClientGrpc.cs index 02991174d..6ad2dc7ba 100644 --- a/src/Dapr.Client/DaprClientGrpc.cs +++ b/src/Dapr.Client/DaprClientGrpc.cs @@ -1414,9 +1414,9 @@ private async Task> EncryptDataAsync(string vaultResourceNa var receiveResult = Task.FromResult(RetrieveEncryptedStreamAsync(duplexStream, cancellationToken)); await Task.WhenAll( //Stream the plaintext data to the sidecar in chunks - Task.FromResult(SendPlaintextStreamAsync(plaintextStream, + SendPlaintextStreamAsync(plaintextStream, encryptionOptions.StreamingBlockSizeInBytes, duplexStream, encryptRequestOptions, - cancellationToken)), + cancellationToken), //At the same time, retrieve the encrypted response from the sidecar receiveResult); @@ -1478,8 +1478,8 @@ public override async IAsyncEnumerable> EncryptAsync(string var receiveResult = Task.FromResult(RetrieveEncryptedStreamAsync(duplexStream, cancellationToken)); await Task.WhenAll( //Stream the plaintext data to the sidecar in chunks - Task.FromResult(SendPlaintextStreamAsync(plaintextStream, encryptionOptions.StreamingBlockSizeInBytes, - duplexStream, encryptRequestOptions, cancellationToken)), + SendPlaintextStreamAsync(plaintextStream, encryptionOptions.StreamingBlockSizeInBytes, + duplexStream, encryptRequestOptions, cancellationToken), //At the same time, retrieve the encrypted response from the sidecar receiveResult); @@ -1493,11 +1493,11 @@ await Task.WhenAll( /// /// Sends the plaintext bytes in chunks to the sidecar to be encrypted. /// - private async IAsyncEnumerable SendPlaintextStreamAsync(Stream plaintextStream, + private async Task SendPlaintextStreamAsync(Stream plaintextStream, int streamingBlockSizeInBytes, AsyncDuplexStreamingCall duplexStream, Autogenerated.EncryptRequestOptions encryptRequestOptions, - [EnumeratorCancellation] CancellationToken cancellationToken) + CancellationToken cancellationToken) { //Start with passing the metadata about the encryption request itself in the first message await duplexStream.RequestStream.WriteAsync( @@ -1525,15 +1525,11 @@ await duplexStream.RequestStream.WriteAsync( //Increment the sequence number sequenceNumber++; - - //Yield an empty byte array to satisfy the return type though it's unused - yield return Array.Empty(); } } //Send the completion message await duplexStream.RequestStream.CompleteAsync(); - yield break; //End the async enumeration } /// @@ -1571,8 +1567,8 @@ public override async IAsyncEnumerable> DecryptAsync(string var receiveResult = Task.FromResult(RetrieveDecryptedStreamAsync(duplexStream, cancellationToken)); await Task.WhenAll( //Stream the ciphertext data to the sidecar in chunks - Task.FromResult(SendCiphertextStreamAsync(ciphertextStream, decryptionOptions.StreamingBlockSizeInBytes, - duplexStream, decryptRequestOptions, cancellationToken)), + SendCiphertextStreamAsync(ciphertextStream, decryptionOptions.StreamingBlockSizeInBytes, + duplexStream, decryptRequestOptions, cancellationToken), //At the same time, retrieve the decrypted response from the sidecar receiveResult ); @@ -1594,11 +1590,11 @@ public override IAsyncEnumerable> DecryptAsync(string vault /// /// Sends the ciphertext bytes in chunks to the sidecar to be decrypted. /// - private async IAsyncEnumerable> SendCiphertextStreamAsync(Stream ciphertextStream, + private async Task SendCiphertextStreamAsync(Stream ciphertextStream, int streamingBlockSizeInBytes, AsyncDuplexStreamingCall duplexStream, Autogenerated.DecryptRequestOptions decryptRequestOptions, - [EnumeratorCancellation] CancellationToken cancellationToken) + CancellationToken cancellationToken) { //Start with passing the metadata about the decryption request itself in the first message await duplexStream.RequestStream.WriteAsync( @@ -1624,15 +1620,11 @@ await duplexStream.RequestStream.WriteAsync(new Autogenerated.DecryptRequest //Increment the sequence number sequenceNumber++; - - //Yield an empty byte array to satisfy the return type through it's unused - yield return Array.Empty(); } } //Send the completion message await duplexStream.RequestStream.CompleteAsync(); - yield break; // End the async enumeration } /// @@ -1691,8 +1683,8 @@ private async Task> DecryptDataAsync(string vaultResourceNa var receiveResult = Task.FromResult(RetrieveDecryptedStreamAsync(duplexStream, cancellationToken)); await Task.WhenAll( //Stream the ciphertext data to the sidecar in chunks - Task.FromResult(SendCiphertextStreamAsync(ciphertextStream, decryptionOptions.StreamingBlockSizeInBytes, - duplexStream, decryptRequestOptions, cancellationToken)), + SendCiphertextStreamAsync(ciphertextStream, decryptionOptions.StreamingBlockSizeInBytes, + duplexStream, decryptRequestOptions, cancellationToken), //At the same time, retrieve the encrypted response from the sidecar receiveResult); From 770b94af1efbdfa119f0cce0a6aedd8e27a1edc1 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Thu, 4 Jan 2024 13:43:16 -0600 Subject: [PATCH 37/43] Updated exception text to be more specific as to what's wrong with the input value. Signed-off-by: Whit Waldo --- src/Dapr.Client/DaprClientGrpc.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Dapr.Client/DaprClientGrpc.cs b/src/Dapr.Client/DaprClientGrpc.cs index 6ad2dc7ba..5f689bb5e 100644 --- a/src/Dapr.Client/DaprClientGrpc.cs +++ b/src/Dapr.Client/DaprClientGrpc.cs @@ -1432,7 +1432,8 @@ await Task.WhenAll( /// [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override async Task> EncryptAsync(string vaultResourceName, ReadOnlyMemory plaintextBytes, string keyName, EncryptionOptions encryptionOptions, + public override async Task> EncryptAsync(string vaultResourceName, + ReadOnlyMemory plaintextBytes, string keyName, EncryptionOptions encryptionOptions, CancellationToken cancellationToken = default) { if (MemoryMarshal.TryGetArray(plaintextBytes, out var plaintextSegment) && plaintextSegment.Array != null) @@ -1441,7 +1442,7 @@ public override async Task> EncryptAsync(string vaultResour encryptionOptions, cancellationToken); } - throw new ArgumentException("Unable to read bytes", nameof(plaintextBytes)); + throw new ArgumentException("The input instance doesn't have a valid underlying data store.", nameof(plaintextBytes)); } /// @@ -1653,7 +1654,7 @@ public override async Task> DecryptAsync(string vaultResour decryptionOptions, cancellationToken); } - throw new ArgumentException("Unable to read bytes", nameof(ciphertextBytes)); + throw new ArgumentException("The input instance doesn't have a valid underlying data store", nameof(ciphertextBytes)); } /// From d5bcebb5cca35c7d2e2f10c0537fdf3fae4af095 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Thu, 4 Jan 2024 13:55:26 -0600 Subject: [PATCH 38/43] Minor tweak to prefer using using a Memory Signed-off-by: Whit Waldo --- src/Dapr.Client/DaprClient.cs | 3 +-- src/Dapr.Client/DaprClientGrpc.cs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Dapr.Client/DaprClient.cs b/src/Dapr.Client/DaprClient.cs index 738e1dc76..f608f8886 100644 --- a/src/Dapr.Client/DaprClient.cs +++ b/src/Dapr.Client/DaprClient.cs @@ -990,8 +990,7 @@ public abstract Task> DecryptAsync(string vaultResourceName /// The name of the key to use from the Vault for the decryption operation. /// A that can be used to cancel the operation. /// An array of decrypted bytes. - [Obsolete( - "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] public abstract Task> DecryptAsync(string vaultResourceName, ReadOnlyMemory ciphertextBytes, string keyName, CancellationToken cancellationToken = default); diff --git a/src/Dapr.Client/DaprClientGrpc.cs b/src/Dapr.Client/DaprClientGrpc.cs index 5f689bb5e..1e695b572 100644 --- a/src/Dapr.Client/DaprClientGrpc.cs +++ b/src/Dapr.Client/DaprClientGrpc.cs @@ -1512,7 +1512,7 @@ await duplexStream.RequestStream.WriteAsync( ulong sequenceNumber = 0; while ((bytesRead = - await bufferedStream.ReadAsync(buffer, 0, streamingBlockSizeInBytes, cancellationToken)) != + await bufferedStream.ReadAsync(buffer.AsMemory(0, streamingBlockSizeInBytes), cancellationToken)) != 0) { await duplexStream.RequestStream.WriteAsync( @@ -1608,7 +1608,7 @@ await duplexStream.RequestStream.WriteAsync( int bytesRead; ulong sequenceNumber = 0; - while ((bytesRead = await bufferedStream.ReadAsync(buffer, 0, streamingBlockSizeInBytes, cancellationToken)) != 0) + while ((bytesRead = await bufferedStream.ReadAsync(buffer.AsMemory(0, streamingBlockSizeInBytes), cancellationToken)) != 0) { await duplexStream.RequestStream.WriteAsync(new Autogenerated.DecryptRequest { From 095405e1ed8745eb3258c5c74217745ed845bf8e Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Thu, 4 Jan 2024 14:21:07 -0600 Subject: [PATCH 39/43] Deduplicated some of the Decrypt methods, simplifying the implementation Signed-off-by: Whit Waldo --- src/Dapr.Client/DaprClientGrpc.cs | 55 ++++++++----------------------- 1 file changed, 13 insertions(+), 42 deletions(-) diff --git a/src/Dapr.Client/DaprClientGrpc.cs b/src/Dapr.Client/DaprClientGrpc.cs index 1e695b572..82040bc4f 100644 --- a/src/Dapr.Client/DaprClientGrpc.cs +++ b/src/Dapr.Client/DaprClientGrpc.cs @@ -1546,8 +1546,7 @@ private async IAsyncEnumerable> RetrieveEncryptedStreamAsyn } /// - [Obsolete( - "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] + [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] public override async IAsyncEnumerable> DecryptAsync(string vaultResourceName, Stream ciphertextStream, string keyName, DecryptionOptions decryptionOptions, [EnumeratorCancellation] CancellationToken cancellationToken = default) { @@ -1558,7 +1557,8 @@ public override async IAsyncEnumerable> DecryptAsync(string var decryptRequestOptions = new Autogenerated.DecryptRequestOptions { - ComponentName = vaultResourceName, KeyName = keyName + ComponentName = vaultResourceName, + KeyName = keyName }; var options = CreateCallOptions(headers: null, cancellationToken); @@ -1607,7 +1607,7 @@ await duplexStream.RequestStream.WriteAsync( var buffer = new byte[streamingBlockSizeInBytes]; int bytesRead; ulong sequenceNumber = 0; - + while ((bytesRead = await bufferedStream.ReadAsync(buffer.AsMemory(0, streamingBlockSizeInBytes), cancellationToken)) != 0) { await duplexStream.RequestStream.WriteAsync(new Autogenerated.DecryptRequest @@ -1618,7 +1618,7 @@ await duplexStream.RequestStream.WriteAsync(new Autogenerated.DecryptRequest Seq = sequenceNumber } }, cancellationToken); - + //Increment the sequence number sequenceNumber++; } @@ -1650,8 +1650,14 @@ public override async Task> DecryptAsync(string vaultResour { if (MemoryMarshal.TryGetArray(ciphertextBytes, out var ciphertextSegment) && ciphertextSegment.Array != null) { - return await DecryptDataAsync(vaultResourceName, new MemoryStream(ciphertextSegment.Array), keyName, - decryptionOptions, cancellationToken); + var bufferedResult = new ArrayBufferWriter(); + await foreach (var item in DecryptAsync(vaultResourceName, new MemoryStream(ciphertextSegment.Array), + keyName, decryptionOptions, cancellationToken)) + { + bufferedResult.Write(item.Span); + } + + return bufferedResult.WrittenMemory; } throw new ArgumentException("The input instance doesn't have a valid underlying data store", nameof(ciphertextBytes)); @@ -1663,41 +1669,6 @@ public override async Task> DecryptAsync(string vaultResour ReadOnlyMemory ciphertextBytes, string keyName, CancellationToken cancellationToken = default) => await DecryptAsync(vaultResourceName, ciphertextBytes, keyName, new DecryptionOptions(), cancellationToken); - - private async Task> DecryptDataAsync(string vaultResourceName, Stream ciphertextStream, string keyName, DecryptionOptions decryptionOptions, CancellationToken cancellationToken = default) - { - ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); - ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); - ArgumentVerifier.ThrowIfNull(decryptionOptions, nameof(decryptionOptions)); - ArgumentVerifier.ThrowIfNull(ciphertextStream, nameof(ciphertextStream)); - - var decryptRequestOptions = new Autogenerated.DecryptRequestOptions - { - ComponentName = vaultResourceName, - KeyName = keyName - }; - - var options = CreateCallOptions(headers: null, cancellationToken); - var duplexStream = client.DecryptAlpha1(options); - - //Run both operation at the same time, but return the output of the streaming values coming from the operation - var receiveResult = Task.FromResult(RetrieveDecryptedStreamAsync(duplexStream, cancellationToken)); - await Task.WhenAll( - //Stream the ciphertext data to the sidecar in chunks - SendCiphertextStreamAsync(ciphertextStream, decryptionOptions.StreamingBlockSizeInBytes, - duplexStream, decryptRequestOptions, cancellationToken), - //At the same time, retrieve the encrypted response from the sidecar - receiveResult); - - //Return only the buffered result of the `RetrieveDecryptStreamAsync` method - var bufferedResult = new ArrayBufferWriter(); - await foreach(var item in receiveResult.Result.WithCancellation(cancellationToken)) - { - bufferedResult.Write(item.Span); - } - - return bufferedResult.WrittenMemory; - } #region Subtle Crypto Implementation From 27366a43faec9fc71e8a07382a19d0e0b40f1afa Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Thu, 4 Jan 2024 14:23:44 -0600 Subject: [PATCH 40/43] Eliminated duplicate encryption method, simplifying implementation Signed-off-by: Whit Waldo --- src/Dapr.Client/DaprClientGrpc.cs | 58 +++++-------------------------- 1 file changed, 8 insertions(+), 50 deletions(-) diff --git a/src/Dapr.Client/DaprClientGrpc.cs b/src/Dapr.Client/DaprClientGrpc.cs index 82040bc4f..2c2513bf0 100644 --- a/src/Dapr.Client/DaprClientGrpc.cs +++ b/src/Dapr.Client/DaprClientGrpc.cs @@ -1382,54 +1382,6 @@ public override async Task UnsubscribeConfigur #region Cryptography - private async Task> EncryptDataAsync(string vaultResourceName, Stream plaintextStream, string keyName, - EncryptionOptions encryptionOptions, CancellationToken cancellationToken = default) - { - ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); - ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); - ArgumentVerifier.ThrowIfNull(plaintextStream, nameof(plaintextStream)); - ArgumentVerifier.ThrowIfNull(encryptionOptions, nameof(encryptionOptions)); - - var shouldOmitDecryptionKeyName = string.IsNullOrWhiteSpace(encryptionOptions.DecryptionKeyName); //Whitespace isn't likely a valid key name either - - var encryptRequestOptions = new Autogenerated.EncryptRequestOptions - { - ComponentName = vaultResourceName, - DataEncryptionCipher = encryptionOptions.EncryptionCipher.GetValueFromEnumMember(), - KeyName = keyName, - KeyWrapAlgorithm = encryptionOptions.KeyWrapAlgorithm.GetValueFromEnumMember(), - OmitDecryptionKeyName = shouldOmitDecryptionKeyName - }; - - if (!shouldOmitDecryptionKeyName) - { - ArgumentVerifier.ThrowIfNullOrEmpty(encryptionOptions.DecryptionKeyName, nameof(encryptionOptions.DecryptionKeyName)); - encryptRequestOptions.DecryptionKeyName = encryptRequestOptions.DecryptionKeyName; - } - - var options = CreateCallOptions(headers: null, cancellationToken); - var duplexStream = client.EncryptAlpha1(options); - - //Run both operations at the same time, but return the output of the streaming values coming from the operation - var receiveResult = Task.FromResult(RetrieveEncryptedStreamAsync(duplexStream, cancellationToken)); - await Task.WhenAll( - //Stream the plaintext data to the sidecar in chunks - SendPlaintextStreamAsync(plaintextStream, - encryptionOptions.StreamingBlockSizeInBytes, duplexStream, encryptRequestOptions, - cancellationToken), - //At the same time, retrieve the encrypted response from the sidecar - receiveResult); - - //Return only the buffered result of the `RetrieveEncryptedStreamAsync` method - var bufferedResult = new ArrayBufferWriter(); - await foreach (var item in receiveResult.Result.WithCancellation(cancellationToken)) - { - bufferedResult.Write(item.Span); - } - - return bufferedResult.WrittenMemory; - } - /// [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] public override async Task> EncryptAsync(string vaultResourceName, @@ -1438,8 +1390,14 @@ public override async Task> EncryptAsync(string vaultResour { if (MemoryMarshal.TryGetArray(plaintextBytes, out var plaintextSegment) && plaintextSegment.Array != null) { - return await EncryptDataAsync(vaultResourceName, new MemoryStream(plaintextSegment.Array), keyName, - encryptionOptions, cancellationToken); + var bufferedResult = new ArrayBufferWriter(); + await foreach (var item in EncryptAsync(vaultResourceName, new MemoryStream(plaintextSegment.Array), + keyName, encryptionOptions, cancellationToken)) + { + bufferedResult.Write(item.Span); + } + + return bufferedResult.WrittenMemory; } throw new ArgumentException("The input instance doesn't have a valid underlying data store.", nameof(plaintextBytes)); From ff670ee683196ff7d59cac19d85d0c4e99da8df8 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Thu, 4 Jan 2024 16:50:20 -0600 Subject: [PATCH 41/43] Updated to eliminate an unnecessary `await` and `async foreach`. Signed-off-by: Whit Waldo --- src/Dapr.Client/DaprClient.cs | 6 ++-- src/Dapr.Client/DaprClientGrpc.cs | 52 ++++++++++++++----------------- 2 files changed, 26 insertions(+), 32 deletions(-) diff --git a/src/Dapr.Client/DaprClient.cs b/src/Dapr.Client/DaprClient.cs index f608f8886..20c37d9e7 100644 --- a/src/Dapr.Client/DaprClient.cs +++ b/src/Dapr.Client/DaprClient.cs @@ -966,7 +966,7 @@ public abstract Task> EncryptAsync(string vaultResourceName /// A that can be used to cancel the operation. /// An array of encrypted bytes. [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public abstract IAsyncEnumerable> EncryptAsync(string vaultResourceName, Stream plaintextStream, string keyName, + public abstract Task>> EncryptAsync(string vaultResourceName, Stream plaintextStream, string keyName, EncryptionOptions encryptionOptions, CancellationToken cancellationToken = default); /// @@ -1005,7 +1005,7 @@ public abstract Task> DecryptAsync(string vaultResourceName /// An asynchronously enumerable array of decrypted bytes. [Obsolete( "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public abstract IAsyncEnumerable> DecryptAsync(string vaultResourceName, Stream ciphertextStream, + public abstract Task>> DecryptAsync(string vaultResourceName, Stream ciphertextStream, string keyName, DecryptionOptions options, CancellationToken cancellationToken = default); /// @@ -1018,7 +1018,7 @@ public abstract IAsyncEnumerable> DecryptAsync(string vault /// An asynchronously enumerable array of decrypted bytes. [Obsolete( "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public abstract IAsyncEnumerable> DecryptAsync(string vaultResourceName, Stream ciphertextStream, + public abstract Task>> DecryptAsync(string vaultResourceName, Stream ciphertextStream, string keyName, CancellationToken cancellationToken = default); #endregion diff --git a/src/Dapr.Client/DaprClientGrpc.cs b/src/Dapr.Client/DaprClientGrpc.cs index 2c2513bf0..9c99b9eee 100644 --- a/src/Dapr.Client/DaprClientGrpc.cs +++ b/src/Dapr.Client/DaprClientGrpc.cs @@ -1390,13 +1390,16 @@ public override async Task> EncryptAsync(string vaultResour { if (MemoryMarshal.TryGetArray(plaintextBytes, out var plaintextSegment) && plaintextSegment.Array != null) { + var encryptionResult = await EncryptAsync(vaultResourceName, new MemoryStream(plaintextSegment.Array), keyName, encryptionOptions, + cancellationToken); + var bufferedResult = new ArrayBufferWriter(); - await foreach (var item in EncryptAsync(vaultResourceName, new MemoryStream(plaintextSegment.Array), - keyName, encryptionOptions, cancellationToken)) + + await foreach (var item in encryptionResult.WithCancellation(cancellationToken)) { bufferedResult.Write(item.Span); } - + return bufferedResult.WrittenMemory; } @@ -1405,8 +1408,8 @@ public override async Task> EncryptAsync(string vaultResour /// [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override async IAsyncEnumerable> EncryptAsync(string vaultResourceName, Stream plaintextStream, - string keyName, EncryptionOptions encryptionOptions, [EnumeratorCancellation] CancellationToken cancellationToken = default) + public override async Task>> EncryptAsync(string vaultResourceName, Stream plaintextStream, + string keyName, EncryptionOptions encryptionOptions, CancellationToken cancellationToken = default) { ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); @@ -1434,19 +1437,13 @@ public override async IAsyncEnumerable> EncryptAsync(string var duplexStream = client.EncryptAlpha1(options); //Run both operations at the same time, but return the output of the streaming values coming from the operation - var receiveResult = Task.FromResult(RetrieveEncryptedStreamAsync(duplexStream, cancellationToken)); - await Task.WhenAll( + var receiveResult = Task.FromResult(RetrieveEncryptedStreamAsync(duplexStream, cancellationToken)); + return await Task.WhenAll( //Stream the plaintext data to the sidecar in chunks SendPlaintextStreamAsync(plaintextStream, encryptionOptions.StreamingBlockSizeInBytes, duplexStream, encryptRequestOptions, cancellationToken), //At the same time, retrieve the encrypted response from the sidecar - receiveResult); - - //Return only the result of the `RetrieveEncryptedStreamAsync` method - await foreach (var item in receiveResult.Result.WithCancellation(cancellationToken)) - { - yield return item; - } + receiveResult).ContinueWith(_ => receiveResult.Result, cancellationToken); } /// @@ -1505,8 +1502,8 @@ private async IAsyncEnumerable> RetrieveEncryptedStreamAsyn /// [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override async IAsyncEnumerable> DecryptAsync(string vaultResourceName, Stream ciphertextStream, string keyName, - DecryptionOptions decryptionOptions, [EnumeratorCancellation] CancellationToken cancellationToken = default) + public override async Task>> DecryptAsync(string vaultResourceName, Stream ciphertextStream, string keyName, + DecryptionOptions decryptionOptions, CancellationToken cancellationToken = default) { ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName)); ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName)); @@ -1524,24 +1521,19 @@ public override async IAsyncEnumerable> DecryptAsync(string //Run both operations at the same time, but return the output of the streaming values coming from the operation var receiveResult = Task.FromResult(RetrieveDecryptedStreamAsync(duplexStream, cancellationToken)); - await Task.WhenAll( + return await Task.WhenAll( //Stream the ciphertext data to the sidecar in chunks SendCiphertextStreamAsync(ciphertextStream, decryptionOptions.StreamingBlockSizeInBytes, duplexStream, decryptRequestOptions, cancellationToken), //At the same time, retrieve the decrypted response from the sidecar - receiveResult - ); - - //Return only the result of the `RetrieveEncryptedStreamAsync` method - await foreach (var item in receiveResult.Result.WithCancellation(cancellationToken)) - { - yield return item; - } + receiveResult) + //Return only the result of the `RetrieveEncryptedStreamAsync` method + .ContinueWith(t => receiveResult.Result, cancellationToken); } /// [Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")] - public override IAsyncEnumerable> DecryptAsync(string vaultResourceName, + public override Task>> DecryptAsync(string vaultResourceName, Stream ciphertextStream, string keyName, CancellationToken cancellationToken = default) => DecryptAsync(vaultResourceName, ciphertextStream, keyName, new DecryptionOptions(), cancellationToken); @@ -1608,11 +1600,13 @@ public override async Task> DecryptAsync(string vaultResour { if (MemoryMarshal.TryGetArray(ciphertextBytes, out var ciphertextSegment) && ciphertextSegment.Array != null) { + var decryptionResult = await DecryptAsync(vaultResourceName, new MemoryStream(ciphertextSegment.Array), + keyName, decryptionOptions, cancellationToken); + var bufferedResult = new ArrayBufferWriter(); - await foreach (var item in DecryptAsync(vaultResourceName, new MemoryStream(ciphertextSegment.Array), - keyName, decryptionOptions, cancellationToken)) + await foreach (var item in decryptionResult.WithCancellation(cancellationToken)) { - bufferedResult.Write(item.Span); + bufferedResult.Write(item.Span); } return bufferedResult.WrittenMemory; From 05ebc8e033923d9981cc5f91d97cb6e9a239e77a Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Thu, 4 Jan 2024 16:52:36 -0600 Subject: [PATCH 42/43] Updated stream example to reflect the changes to the API shape Signed-off-by: Whit Waldo --- .../Examples/EncryptDecryptFileStreamExample.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/Client/Cryptography/Examples/EncryptDecryptFileStreamExample.cs b/examples/Client/Cryptography/Examples/EncryptDecryptFileStreamExample.cs index 9aea16998..aa9c404a7 100644 --- a/examples/Client/Cryptography/Examples/EncryptDecryptFileStreamExample.cs +++ b/examples/Client/Cryptography/Examples/EncryptDecryptFileStreamExample.cs @@ -41,12 +41,13 @@ public override async Task RunAsync(CancellationToken cancellationToken) await using var encryptFs = new FileStream(fileName, FileMode.Open); var bufferedEncryptedBytes = new ArrayBufferWriter(); - await foreach (var bytes in client.EncryptAsync(componentName, encryptFs, keyName, + await foreach (var bytes in (await client.EncryptAsync(componentName, encryptFs, keyName, new EncryptionOptions(KeyWrapAlgorithm.Rsa), cancellationToken)) + .WithCancellation(cancellationToken)) { bufferedEncryptedBytes.Write(bytes.Span); } - + Console.WriteLine($"Encrypted bytes: {Convert.ToBase64String(bufferedEncryptedBytes.GetSpan())}"); Console.WriteLine(); @@ -56,10 +57,12 @@ public override async Task RunAsync(CancellationToken cancellationToken) //We'll stream the decrypted bytes from a MemoryStream into the above temporary file await using var encryptedMs = new MemoryStream(bufferedEncryptedBytes.WrittenMemory.ToArray()); - await foreach (var result in client.DecryptAsync(componentName, encryptedMs, keyName, cancellationToken)) + await foreach (var result in (await client.DecryptAsync(componentName, encryptedMs, keyName, + cancellationToken)).WithCancellation(cancellationToken)) { decryptFs.Write(result.Span); } + decryptFs.Close(); //Let's confirm the value as written to the file From 48bd5414ccd090007ffb8f06fe5ae293174abf61 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Mon, 22 Jan 2024 07:49:48 -0600 Subject: [PATCH 43/43] Added notes about operations with stream-based data Signed-off-by: Whit Waldo --- examples/Client/Cryptography/README.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/examples/Client/Cryptography/README.md b/examples/Client/Cryptography/README.md index 84c2f8248..c0c884369 100644 --- a/examples/Client/Cryptography/README.md +++ b/examples/Client/Cryptography/README.md @@ -73,4 +73,20 @@ dapr run --resources-path ./Components --app-id DaprClient -- dotnet run 0 ``` ## Encryption/Decryption with strings -See [EncryptDecryptExample.cs](./EncryptDecryptExample.cs) for an example of using `DaprClient` for basic string-based encryption and decryption operations as performed against UTF-8 encoded byte arrays. \ No newline at end of file +See [EncryptDecryptStringExample.cs](./EncryptDecryptStringExample.cs) for an example of using `DaprClient` for basic +string-based encryption and decryption operations as performed against UTF-8 encoded byte arrays. + +## Encryption/Decryption with streams +See [EncryptDecryptFileStreamExample.cs](./EncryptDecryptFileStreamExample.cs) for an example of using `DaprClient` +to perform an encrypt and decrypt operation against a stream of data. In the example, we stream a local file to the +sidecar to encrypt and write the result (as it's streamed back) to an in-memory buffer. Once the operation fully +completes, we perform the decrypt operation against this in-memory buffer and write the decrypted result back out to a +temporary file. + +In either operation, rather than load the entire stream into memory and send all at once to the +sidecar as we do in the other string-based example (as this might cause you to run out of memory either on the +node the app is running on or do the same to the sidecar itself), this example instead breaks the input stream into +more manageable 4KB chunks (a value you can override via the `EncryptionOptions` or `DecryptionOptions` parameters +respectively up to 64KB. Further, rather than waiting for the entire stream to send to the sidecar before the +encryption operation proceeds, it immediately works to process the sidecar response, continuing to minimize resource +usage.