Add Dapr.Cryptography package + fix for large files (#1527)

* Implementation of the new crypto client

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
Co-authored-by: Christopher Watford <83599748+watfordsuzy@users.noreply.github.com>
This commit is contained in:
Whit Waldo 2025-05-02 15:39:12 -05:00 committed by GitHub
parent 6d92113788
commit 49992f4a86
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
49 changed files with 2016 additions and 107 deletions

39
all.sln
View File

@ -111,8 +111,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapr.Actors.Generators.Test
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapr.E2E.Test.Actors.Generators", "test\Dapr.E2E.Test.Actors.Generators\Dapr.E2E.Test.Actors.Generators.csproj", "{B5CDB0DC-B26D-48F1-B934-FE5C1C991940}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cryptography", "examples\Client\Cryptography\Cryptography.csproj", "{C74FBA78-13E8-407F-A173-4555AEE41FF3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Protos", "src\Dapr.Protos\Dapr.Protos.csproj", "{DFBABB04-50E9-42F6-B470-310E1B545638}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Common", "src\Dapr.Common\Dapr.Common.csproj", "{B445B19C-A925-4873-8CB7-8317898B6970}"
@ -143,8 +141,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Messaging.Test", "test
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Messaging", "src\Dapr.Messaging\Dapr.Messaging.csproj", "{0EAE36A1-B578-4F13-A113-7A477ECA1BDA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StreamingSubscriptionExample", "examples\Client\PublishSubscribe\StreamingSubscriptionExample\StreamingSubscriptionExample.csproj", "{290D1278-F613-4DF3-9DF5-F37E38CDC363}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Jobs", "src\Dapr.Jobs\Dapr.Jobs.csproj", "{C8BB6A85-A7EA-40C0-893D-F36F317829B3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Jobs.Test", "test\Dapr.Jobs.Test\Dapr.Jobs.Test.csproj", "{BF9828E9-5597-4D42-AA6E-6E6C12214204}"
@ -169,6 +165,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Actors.Analyzers.Test"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Analyzers.Common", "test\Dapr.Analyzers.Common\Dapr.Analyzers.Common.csproj", "{7E23E229-6823-4D84-AF3A-AE14CEAEF52A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Cryptography", "src\Dapr.Cryptography\Dapr.Cryptography.csproj", "{160EFFA0-F6B9-49E4-B62B-68C0D53DB425}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Cryptography.Test", "test\Dapr.Cryptography.Test\Dapr.Cryptography.Test.csproj", "{B508EBD6-0F14-480C-A446-45A09052733B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StreamingSubscriptionExample", "examples\Messaging\StreamingSubscriptionExample\StreamingSubscriptionExample.csproj", "{E070F694-335D-4D96-8951-F41D0A5F2A8B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Cryptography", "Cryptography", "{6843B5B3-9E95-4022-B792-8A1DE6BFEFEC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cryptography", "examples\Cryptography\Cryptography.csproj", "{097D5F6F-D26F-4BFB-9074-FA52577EB442}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Messaging", "Messaging", "{442E80E5-8040-4123-B88A-26FD36BA95D9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -445,6 +453,22 @@ Global
{7E23E229-6823-4D84-AF3A-AE14CEAEF52A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7E23E229-6823-4D84-AF3A-AE14CEAEF52A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7E23E229-6823-4D84-AF3A-AE14CEAEF52A}.Release|Any CPU.Build.0 = Release|Any CPU
{160EFFA0-F6B9-49E4-B62B-68C0D53DB425}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{160EFFA0-F6B9-49E4-B62B-68C0D53DB425}.Debug|Any CPU.Build.0 = Debug|Any CPU
{160EFFA0-F6B9-49E4-B62B-68C0D53DB425}.Release|Any CPU.ActiveCfg = Release|Any CPU
{160EFFA0-F6B9-49E4-B62B-68C0D53DB425}.Release|Any CPU.Build.0 = Release|Any CPU
{B508EBD6-0F14-480C-A446-45A09052733B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B508EBD6-0F14-480C-A446-45A09052733B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B508EBD6-0F14-480C-A446-45A09052733B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B508EBD6-0F14-480C-A446-45A09052733B}.Release|Any CPU.Build.0 = Release|Any CPU
{E070F694-335D-4D96-8951-F41D0A5F2A8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E070F694-335D-4D96-8951-F41D0A5F2A8B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E070F694-335D-4D96-8951-F41D0A5F2A8B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E070F694-335D-4D96-8951-F41D0A5F2A8B}.Release|Any CPU.Build.0 = Release|Any CPU
{097D5F6F-D26F-4BFB-9074-FA52577EB442}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{097D5F6F-D26F-4BFB-9074-FA52577EB442}.Debug|Any CPU.Build.0 = Debug|Any CPU
{097D5F6F-D26F-4BFB-9074-FA52577EB442}.Release|Any CPU.ActiveCfg = Release|Any CPU
{097D5F6F-D26F-4BFB-9074-FA52577EB442}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -497,7 +521,6 @@ Global
{7C06FE2D-6C62-48F5-A505-F0D715C554DE} = {7592AFA4-426B-42F3-AE82-957C86814482}
{AF89083D-4715-42E6-93E9-38497D12A8A6} = {DD020B34-460F-455F-8D17-CF4A949F100B}
{B5CDB0DC-B26D-48F1-B934-FE5C1C991940} = {DD020B34-460F-455F-8D17-CF4A949F100B}
{C74FBA78-13E8-407F-A173-4555AEE41FF3} = {A7F41094-8648-446B-AECD-DCC2CC871F73}
{DFBABB04-50E9-42F6-B470-310E1B545638} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
{B445B19C-A925-4873-8CB7-8317898B6970} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
{CDB47863-BEBD-4841-A807-46D868962521} = {DD020B34-460F-455F-8D17-CF4A949F100B}
@ -525,6 +548,12 @@ Global
{E49C822C-E921-48DF-897B-3E603CA596D2} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
{A2C0F203-11FF-4B7F-A94F-B9FD873573FE} = {DD020B34-460F-455F-8D17-CF4A949F100B}
{7E23E229-6823-4D84-AF3A-AE14CEAEF52A} = {DD020B34-460F-455F-8D17-CF4A949F100B}
{160EFFA0-F6B9-49E4-B62B-68C0D53DB425} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
{B508EBD6-0F14-480C-A446-45A09052733B} = {DD020B34-460F-455F-8D17-CF4A949F100B}
{6843B5B3-9E95-4022-B792-8A1DE6BFEFEC} = {D687DDC4-66C5-4667-9E3A-FD8B78ECAA78}
{097D5F6F-D26F-4BFB-9074-FA52577EB442} = {6843B5B3-9E95-4022-B792-8A1DE6BFEFEC}
{442E80E5-8040-4123-B88A-26FD36BA95D9} = {D687DDC4-66C5-4667-9E3A-FD8B78ECAA78}
{E070F694-335D-4D96-8951-F41D0A5F2A8B} = {442E80E5-8040-4123-B88A-26FD36BA95D9}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {65220BF2-EAE1-4CB2-AA58-EBE80768CB40}

View File

@ -77,7 +77,3 @@ Put the Dapr AI .NET SDK to the test. Walk through the samples to see Dapr in ac
This part of the .NET SDK allows you to interface with the Conversations API to send and receive messages from
large language models.
### Send messages

View File

@ -17,7 +17,7 @@ It maintains access to networking resources in the form of TCP sockets used to c
For best performance, create a single long-lived instance of `DaprConversationClient` and provide access to that shared
instance throughout your application. `DaprConversationClient` instances are thread-safe and intended to be shared.
This can be aided by utilizing the dependency injection functionality. The registration method supports registration using
This can be aided by utilizing the dependency injection functionality. The registration method supports registration
as a singleton, a scoped instance or as transient (meaning it's recreated every time it's injected), but also enables
registration to utilize values from an `IConfiguration` or other injected service in a way that's impractical when
creating the client from scratch in each of your classes.

View File

@ -0,0 +1,12 @@
---
type: docs
title: "Dapr Cryptography .NET SDK"
linkTitle: "Cryptography"
weight: 51000
description: Get up and running with the Dapr Cryptography .NET SDK
---
With the Dapr Cryptography package, you can perform high-performance encryption and decryption operations with Dapr.
To get started with this functionality, walk through the [Dapr Cryptography({{< ref dotnet-cryptography-howto.md >}})
how-to guide.

View File

@ -0,0 +1,74 @@
---
type: docs
title: "How to: Create an use Dapr Cryptography in the .NET SDK"
linkTitle: "How to: Use the Cryptography client"
weight: 510100
description: Learn how to create and use the Dapr Cryptography client using the .NET SDK
---
## Prerequisites
- [.NET 8](https://dotnet.microsoft.com/download/dotnet/8.0), or [.NET 9](https://dotnet.microsoft.com/download/dotnet/9.0) installed
- [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/)
- [Initialized Dapr environment](https://docs.dapr.io/getting-started/install-dapr-selfhost)
## Installation
To get started with the Dapr Cryptography client, install the [Dapr.Cryptography package](https://www.nuget.org/packages/Dapr.Cryptography) from NuGet:
```sh
dotnet add package Dapr.Cryptography
```
A `DaprEncryptionClient` maintains access to networking resources in the form of TCP sockets used to communicate with
the Dapr sidecar.
### Dependency Injection
The `AddDaprEncryptionClient()` method will register the Dapr client with dependency injection and is the recommended approach
for using this package. This method accepts an optional options delegate for configuring the `DaprEncryptionClient` and a
`ServiceLifetime` argument, allowing you to specify a different lifetime for the registered services instead of the default `Singleton`
value.
The following example assumes all default values are acceptable and is sufficient to register the `DaprEncryptionClient`:
```csharp
services.AddDaprEncryptionClient();
```
The optional configuration delegate is used to configure the `DaprEncryptionClient` by specifying options on the
`DaprEncryptionClientBuilder` as in the following example:
```csharp
services.AddSingleton<DefaultOptionsProvider>();
services.AddDaprEncryptionClient((serviceProvider, clientBuilder) => {
//Inject a service to source a value from
var optionsProvider = serviceProvider.GetRequiredService<DefaultOptionsProvider>();
var standardTimeout = optionsProvider.GetStandardTimeout();
//Configure the value on the client builder
clientBuilder.UseTimeout(standardTimeout);
});
```
### Manual Instantiation
Rather than using dependency injection, a `DaprEncryptionClient` can also be built using the static client builder.
For best performance, create a single long-lived instance of `DaprEncryptionClient` and provide access to that shared instance throughout
your application. `DaprEncryptionClient` instances are thread-safe and intended to be shared.
Avoid creating a `DaprEncryptionClient` per-operation.
A `DaprEncryptionClient` can be configured by invoking methods on the `DaprEncryptionClientBuilder` class before calling `.Build()`
to create the client. The settings for each `DaprEncryptionClient` are separate and cannot be changed after calling `.Build()`.
```csharp
var daprEncryptionClient = new DaprEncryptionClientBuilder()
.UseJsonSerializerSettings( ... ) //Configure JSON serializer
.Build();
```
See the .NET [documentation here]({{< ref dotnet-client >}}) for more information about the options available when configuring the Dapr client via the builder.
## Try it out
Put the Dapr AI .NET SDK to the test. Walk through the samples to see Dapr in action:
| SDK Samples | Description |
|-------------------------------------------------------------------------------------| ----------- |
| [SDK samples](https://github.com/dapr/dotnet-sdk/tree/master/examples/Cryptography) | Clone the SDK repo to try out some examples and get started. |

View File

@ -0,0 +1,131 @@
---
type: docs
title: "Dapr Cryptography Client"
linkTitle: "Cryptography client"
weight: 510005
description: Learn how to create Dapr Crytography clients
---
The Dapr Cryptography package allows you to perform encryption and decryption operations provided by the Dapr sidecar.
## Lifetime management
A `DaprEncryptionClient` is a version of the Dapr client that is dedicated to interacting with the Dapr Cryptography API.
It can be registered alongside a `DaprClient` and other Dapr clients without issue.
It maintains access to networking resources in the form of TCP sockets used to communicate with the Dapr sidecar.
For best performance, create a single long-lived instance of `DaprEncryptionClient` and provide access to that shared
instance throughout your application. `DaprEncryptionClient` instances are thread-safe and intended to be shared.
This can be aided by utilizing the dependency injection functionality. The registration method supports registration
as a singleton, a scoped instance, or as a transient (meaning it's recreated every time it's injected), but also enables
registration to utilize values from an `IConfiguration` or other injected service in a way that's impractical when creating
the client from scratch in each of your classes.
Avoid creating a `DaprEncryptionClient` for each operation.
## Configuring `DaprEncryptionClient` via `DaprEncryptionClientBuilder`
A `DaprCryptographyClient` can be configured by invoking methods on the `DaprEncryptionClientBuilder` class before calling
`.Build()` to create the client itself. The settings for each `DaprEncryptionClientBuilder` are separate can cannot be
changed after calling `.Build()`.
```cs
var daprEncryptionClient = new DaprEncryptionClientBuilder()
.UseDaprApiToken("abc123") //Specify the API token used to authenticate to the Dapr sidecar
.Build();
```
The `DaprEncryptionClientBuilder` contains settings for:
- The HTTP endpoint of the Dapr sidecar
- The gRPC endpoint of the Dapr sidecar
- The `JsonSerializerOptions` object used to configure JSON serialization
- The `GrpcChannelOptions` object used to configure gRPC
- The API token used to authenticate requests to the sidecar
- The factory method used to create the `HttpClient` instance used by the SDK
- The timeout used for the `HttpClient` instance when making requests to the sidecar
The SDK will read the following environment variables to configure the default values:
- `DAPR_HTTP_ENDPOINT`: used to find the HTTP endpoint of the Dapr sidecar, example: `https://dapr-api.mycompany.com`
- `DAPR_GRPC_ENDPOINT`: used to find the gRPC endpoint of the Dapr sidecar, example: `https://dapr-grpc-api.mycompany.com`
- `DAPR_HTTP_PORT`: if `DAPR_HTTP_ENDPOINT` is not set, this is used to find the HTTP local endpoint of the Dapr sidecar
- `DAPR_GRPC_PORT`: if `DAPR_GRPC_ENDPOINT` is not set, this is used to find the gRPC local endpoint of the Dapr sidecar
- `DAPR_API_TOKEN`: used to set the API token
### Configuring gRPC channel options
Dapr's use of `CancellationToken` for cancellation relies on the configuration of the gRPC channel options. If you need
to configure these options yourself, make sure to enable the [ThrowOperationCanceledOnCancellation setting](https://grpc.github.io/grpc/csharp-dotnet/api/Grpc.Net.Client.GrpcChannelOptions.html#Grpc_Net_Client_GrpcChannelOptions_ThrowOperationCanceledOnCancellation).
```cs
var daprEncryptionClient = new DaprEncryptionClientBuilder()
.UseGrpcChannelOptions(new GrpcChannelOptions { .. ThrowOperationCanceledOnCancellation = true })
.Build();
```
## Using cancellation with `DaprEncryptionClient`
The APIs on `DaprEncryptionClient` perform asynchronous operations and accept an optional `CancellationToken` parameter. This
follows a standard .NET practice for cancellable operations. Note that when cancellation occurs, there is no guarantee that
the remote endpoint stops processing the request, only that the client has stopped waiting for completion.
When an operation is cancelled, it will throw an `OperationCancelledException`.
## Configuring `DaprEncryptionClient` via dependency injection
Using the built-in extension methods for registering the `DaprEncryptionClient` in a dependency injection container can
provide the benefit of registering the long-lived service a single time, centralize complex configuration and improve
performance by ensuring similarly long-lived resources are re-purposed when possible (e.g. `HttpClient` instances).
There are three overloads available to give the developer the greatest flexibility in configuring the client for their
scenario. Each of these will register the `IHttpClientFactory` on your behalf if not already registered, and configure
the `DaprEncryptionClientBuilder` to use it when creating the `HttpClient` instance in order to re-use the same instance as
much as possible and avoid socket exhaustion and other issues.
In the first approach, there's no configuration done by the developer and the `DaprEncryptionClient` is configured with the
default settings.
```cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDaprEncryptionClent(); //Registers the `DaprEncryptionClient` to be injected as needed
var app = builder.Build();
```
Sometimes the developer will need to configure the created client using the various configuration options detailed
above. This is done through an overload that passes in the `DaprEncryptionClientBuiler` and exposes methods for configuring
the necessary options.
```cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDaprEncryptionClient((_, daprEncrpyptionClientBuilder) => {
//Set the API token
daprEncryptionClientBuilder.UseDaprApiToken("abc123");
//Specify a non-standard HTTP endpoint
daprEncryptionClientBuilder.UseHttpEndpoint("http://dapr.my-company.com");
});
var app = builder.Build();
```
Finally, it's possible that the developer may need to retrieve information from another service in order to populate
these configuration values. That value may be provided from a `DaprClient` instance, a vendor-specific SDK or some
local service, but as long as it's also registered in DI, it can be injected into this configuration operation via the
last overload:
```cs
var builder = WebApplication.CreateBuilder(args);
//Register a fictional service that retrieves secrets from somewhere
builder.Services.AddSingleton<SecretService>();
builder.Services.AddDaprEncryptionClient((serviceProvider, daprEncryptionClientBuilder) => {
//Retrieve an instance of the `SecretService` from the service provider
var secretService = serviceProvider.GetRequiredService<SecretService>();
var daprApiToken = secretService.GetSecret("DaprApiToken").Value;
//Configure the `DaprEncryptionClientBuilder`
daprEncryptionClientBuilder.UseDaprApiToken(daprApiToken);
});
var app = builder.Build();
```

View File

@ -1,48 +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 Cryptography.Examples;
namespace Cryptography;
class Program
{
private const string ComponentName = "localstorage";
private const string KeyName = "rsa-private-key.pem"; //This should match the name of your generated key - this sample expects an RSA symmetrical key.
private static readonly Example[] Examples = new Example[]
{
new EncryptDecryptStringExample(ComponentName, KeyName),
new EncryptDecryptFileStreamExample(ComponentName, KeyName)
};
static async Task<int> 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 by passing your selection's number into the arguments, e.g. 'dotnet run 0':");
for (var i = 0; i < Examples.Length; i++)
{
Console.WriteLine($"{i}: {Examples[i].DisplayName}");
}
Console.WriteLine();
return 1;
}
}

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<OutputType>Exe</OutputType>
@ -7,18 +7,15 @@
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<Compile Remove="C:\Users\whit_\source\repos\dapr-dotnet-sdk\properties\\IsExternalInit.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Dapr.Client\Dapr.Client.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="file.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Dapr.Common\Dapr.Common.csproj" />
<ProjectReference Include="..\..\src\Dapr.Cryptography\Dapr.Cryptography.csproj" />
</ItemGroup>
</Project>

View File

@ -12,18 +12,20 @@
// ------------------------------------------------------------------------
using System.Buffers;
using Dapr.Client;
using System.Text;
using Dapr.Cryptography.Encryption;
using Dapr.Cryptography.Encryption.Models;
#pragma warning disable CS0618 // Type or member is obsolete
namespace Cryptography.Examples;
internal class EncryptDecryptFileStreamExample(string componentName, string keyName) : Example
internal sealed class EncryptDecryptFileStreamExample(DaprEncryptionClient daprClient) : IExample
{
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();
public static string DisplayName => "Use Cryptography to encrypt and decrypt a file";
public async Task RunAsync(string componentName, string keyName, CancellationToken cancellationToken)
{
// The name of the file we're using as an example
const string fileName = "file.txt";
@ -37,14 +39,13 @@ internal class EncryptDecryptFileStreamExample(string componentName, string keyN
await using var encryptFs = new FileStream(fileName, FileMode.Open);
var bufferedEncryptedBytes = new ArrayBufferWriter<byte>();
await foreach (var bytes in (await client.EncryptAsync(componentName, encryptFs, keyName,
new EncryptionOptions(KeyWrapAlgorithm.Rsa), cancellationToken))
.WithCancellation(cancellationToken))
await foreach (var bytes in ((daprClient.EncryptAsync(componentName, encryptFs, keyName,
new EncryptionOptions(KeyWrapAlgorithm.Rsa), cancellationToken))))
{
bufferedEncryptedBytes.Write(bytes.Span);
}
Console.WriteLine("Encrypted bytes:");
Console.WriteLine($"Encrypted bytes ({bufferedEncryptedBytes.WrittenMemory.Length} bytes):");
Console.WriteLine(Convert.ToBase64String(bufferedEncryptedBytes.WrittenMemory.ToArray()));
//We'll write to a temporary file via a FileStream
@ -53,8 +54,8 @@ internal class EncryptDecryptFileStreamExample(string componentName, string keyN
//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 (await client.DecryptAsync(componentName, encryptedMs, keyName,
cancellationToken)).WithCancellation(cancellationToken))
await foreach (var result in ((daprClient.DecryptAsync(componentName, encryptedMs, keyName,
cancellationToken: cancellationToken))))
{
decryptFs.Write(result.Span);
}
@ -63,7 +64,7 @@ internal class EncryptDecryptFileStreamExample(string componentName, string keyN
//Let's confirm the value as written to the file
var decryptedValue = await File.ReadAllTextAsync(tempDecryptedFile, cancellationToken);
Console.WriteLine("Decrypted value: ");
Console.WriteLine($"Decrypted value ({Encoding.UTF8.GetByteCount(decryptedValue)} bytes): ");
Console.WriteLine(decryptedValue);
//And some cleanup to delete our temp file

View File

@ -0,0 +1,105 @@
// ------------------------------------------------------------------------
// Copyright 2025 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 Dapr.Cryptography.Encryption;
using Dapr.Cryptography.Encryption.Models;
#pragma warning disable CS0618 // Type or member is obsolete
namespace Cryptography.Examples;
internal sealed class EncryptDecryptLargeFileExample(DaprEncryptionClient daprClient) : IExample
{
public static string DisplayName => "Use Cryptography to encrypt and decrypt a large file";
public async Task RunAsync(string componentName, string keyName, CancellationToken cancellationToken)
{
//Create our large file locally and fill with random bytes
const string fileName = "templargefile.txt";
const long sizeLimitInBytes = 1L * 1024 * 1024 * 1024; //1 GB
await WriteLargeFileAsync(fileName, sizeLimitInBytes);
//Get the starting hash of the file for comparison reasons
var startingFileHash = await GetFileHashAsync(fileName);
var startingFileLength = new FileInfo(fileName).Length;
Console.WriteLine($"Starting with a file spanning {startingFileLength} bytes called '{fileName}' filled with random bytes with MD5 hash of: '{startingFileHash}'");
//Encrypt from the file stream and write the result to another file
const string encryptedFileName = "enc_templargefile.txt";
await using (var encryptFs = new FileStream(fileName, FileMode.Open))
{
await using (var encryptedFs = new FileStream(encryptedFileName, FileMode.Create))
{
await foreach (var encryptedBytes in ((daprClient.EncryptAsync(componentName, encryptFs, keyName,
new EncryptionOptions(KeyWrapAlgorithm.Rsa), cancellationToken))))
{
await encryptedFs.WriteAsync(encryptedBytes, cancellationToken);
}
encryptedFs.Close();
}
}
//Get a hash and length of our newly encrypted file
var encryptedFileHash = await GetFileHashAsync(encryptedFileName);
var encryptedFileLength = new FileInfo(encryptedFileName).Length;
Console.WriteLine($"Encrypted file spanning {encryptedFileLength} bytes called '{encryptedFileName}' with MD5 hash of: '{encryptedFileHash}'");
//Now we'll decrypt the file back into another file
const string decryptedFileName = "dec_templargefile.txt";
await using (var decryptFs = new FileStream(encryptedFileName, FileMode.Open))
{
await using (var decryptedFs = new FileStream(decryptedFileName, FileMode.Create))
{
await foreach (var decryptedBytes in ((daprClient.DecryptAsync(componentName, decryptFs, keyName,
cancellationToken: cancellationToken))))
{
await decryptedFs.WriteAsync(decryptedBytes, cancellationToken);
}
decryptedFs.Close();
}
}
//Get the hash and length of the decrypted file
var decryptedFileHash = await GetFileHashAsync(decryptedFileName);
var decryptedFileLength = new FileInfo(decryptedFileName).Length;
Console.WriteLine($"Decrypted file spanning {decryptedFileLength} bytes called '{decryptedFileName}' with MD5 hash of: '{decryptedFileHash}'");
var match = string.Equals(startingFileHash, decryptedFileHash);
Console.WriteLine($"The hash of the original and decrypted file are {(match ? "": "NOT ")}the same!");
//Clean up our large files
File.Delete(fileName);
File.Delete(encryptedFileName);
File.Delete(decryptedFileName);
}
private static async Task WriteLargeFileAsync(string fileName, long sizeLimit)
{
var buffer = new byte[5 * 1024 * 1024]; // 5 MB buffer
await using var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.Read);
for (var written = 0; written < sizeLimit; written += buffer.Length)
{
Random.Shared.NextBytes(buffer);
await fs.WriteAsync(buffer);
}
}
private static async Task<string> GetFileHashAsync(string fileName)
{
using var md5 = MD5.Create();
await using var stream = File.OpenRead(fileName);
return Convert.ToBase64String(await md5.ComputeHashAsync(stream));
}
}

View File

@ -12,31 +12,29 @@
// ------------------------------------------------------------------------
using System.Text;
using Dapr.Client;
using Dapr.Cryptography.Encryption;
using Dapr.Cryptography.Encryption.Models;
#pragma warning disable CS0618 // Type or member is obsolete
namespace Cryptography.Examples;
internal class EncryptDecryptStringExample(string componentName, string keyName) : Example
internal sealed class EncryptDecryptStringExample(DaprEncryptionClient daprClient) : IExample
{
public override string DisplayName => "Using Cryptography to encrypt and decrypt a string";
public override async Task RunAsync(CancellationToken cancellationToken)
public static string DisplayName => "Using Cryptography to encrypt and decrypt a string";
public async Task RunAsync(string componentName, string keyName, CancellationToken cancellationToken)
{
using var client = new DaprClientBuilder().Build();
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);
var encryptedBytesResult = await client.EncryptAsync(componentName, plaintextBytes, keyName, new EncryptionOptions(KeyWrapAlgorithm.Rsa),
cancellationToken);
var encryptedBytesResult = await daprClient.EncryptAsync(componentName, plaintextBytes, keyName, new EncryptionOptions(KeyWrapAlgorithm.Rsa), cancellationToken);
Console.WriteLine($"Encrypted bytes: '{Convert.ToBase64String(encryptedBytesResult.Span)}'");
Console.WriteLine($"Encrypted bytes ({encryptedBytesResult.Length} bytes): '{Convert.ToBase64String(encryptedBytesResult.Span)}'");
//Decrypt the string
var decryptedBytes = await client.DecryptAsync(componentName, encryptedBytesResult, keyName, cancellationToken);
Console.WriteLine($"Decrypted string: '{Encoding.UTF8.GetString(decryptedBytes.ToArray())}'");
var decryptedBytes = await daprClient.DecryptAsync(componentName, encryptedBytesResult, keyName, cancellationToken: cancellationToken);
Console.WriteLine($"Decrypted string ({decryptedBytes.Length} bytes): '{Encoding.UTF8.GetString(decryptedBytes.ToArray())}'");
}
}

View File

@ -13,9 +13,7 @@
namespace Cryptography;
internal abstract class Example
internal interface IExample
{
public abstract string DisplayName { get; }
public abstract Task RunAsync(CancellationToken cancellationToken);
public Task RunAsync(string componentName, string keyName, CancellationToken cancellationToken);
}

View File

@ -0,0 +1,64 @@
// ------------------------------------------------------------------------
// 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.Examples;
using Dapr.Cryptography.Encryption.Extensions;
const string ComponentName = "localstorage";
const string KeyName = "rsa-private-key.pem"; //This should match the name of your generated key - this sample expects an RSA symmetrical key.
if (int.TryParse(args[0], out var exampleId))
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDaprEncryptionClient((sp, opt) =>
{
opt.UseHttpEndpoint("http://localhost:6552");
opt.UseGrpcEndpoint("http://localhost:6551");
});
builder.Services.AddTransient<EncryptDecryptStringExample>();
builder.Services.AddTransient<EncryptDecryptFileStreamExample>();
builder.Services.AddTransient<EncryptDecryptLargeFileExample>();
var app = builder.Build();
var ctx = new CancellationTokenSource();
Console.CancelKeyPress += (object? sender, ConsoleCancelEventArgs e) => ctx.Cancel();
switch (exampleId)
{
case 0:
{
var ex0 = app.Services.GetRequiredService<EncryptDecryptStringExample>();
await ex0.RunAsync(ComponentName, KeyName, ctx.Token);
return 0;
}
case 1:
{
var ex1 = app.Services.GetRequiredService<EncryptDecryptFileStreamExample>();
await ex1.RunAsync(ComponentName, KeyName, ctx.Token);
return 0;
}
case 2:
{
var ex2 = app.Services.GetRequiredService<EncryptDecryptLargeFileExample>();
await ex2.RunAsync(ComponentName, KeyName, ctx.Token);
return 0;
}
}
}
Console.WriteLine("Please choose a sample to run by passing your selection's number into the arguments, e.g. 'dotnet run 0':");
Console.WriteLine($"0: {EncryptDecryptStringExample.DisplayName}");
Console.WriteLine($"1: {EncryptDecryptFileStreamExample.DisplayName}");
Console.WriteLine($"2: {EncryptDecryptLargeFileExample.DisplayName}");
Console.WriteLine();
return 1;

View File

@ -1,4 +1,6 @@
using System.Text;

using System.Text;
using Dapr.Messaging.PublishSubscribe;
using Dapr.Messaging.PublishSubscribe.Extensions;

View File

@ -1,13 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Dapr.Messaging\Dapr.Messaging.csproj" />
<ProjectReference Include="..\..\..\src\Dapr.Messaging\Dapr.Messaging.csproj" />
</ItemGroup>
</Project>

View File

@ -8,3 +8,4 @@ This repository contains a samples that highlight the Dapr .NET SDK capabilities
| [2. Actor](./Actor) | Demonstrates creating virtual actors that encapsulate code and state. |
| [3. ASP.NET Core](./AspNetCore) | Demonstrates ASP.NET Core integration with Dapr by creating Controllers and Routes. |
| [4. Workflow](./Workflow) | Demonstrates creating durable, long-running Dapr workflows using code. |
| [5. Cryptography](./Cryptography) | Demonstrates encryption and decryption operations using Dapr. |

View File

@ -19,6 +19,7 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Dapr.AI, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.AspNetCore, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Client, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Cryptography, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Jobs, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Messaging, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Extensions.Configuration, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
@ -34,6 +35,7 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Dapr.AspNetCore.IntegrationTest.App, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.AspNetCore.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Client.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Cryptography.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Common.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.E2E.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.E2E.Test.Actors, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]

View File

@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackageId>Dapr.Cryptography</PackageId>
<Title>Dapr Cryptography SDK</Title>
<Description>Dapr Cryptography SDK for performing encryption and decryption operations with Dapr</Description>
<VersionSuffix>alpha</VersionSuffix>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" />
<PackageReference Include="Grpc.Net.Client" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Http" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="$(AssemblyName).Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Dapr.Common\Dapr.Common.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,143 @@
// ------------------------------------------------------------------------
// Copyright 2025 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.Cryptography.Encryption.Models;
using Autogenerated = Dapr.Client.Autogen.Grpc.v1.Dapr;
namespace Dapr.Cryptography.Encryption;
/// <summary>
/// <para>
/// Defines client operations for performing cryptography operations with Dapr..
/// Use <see cref="DaprEncryptionClientBuilder"/> to create a <see cref="DaprEncryptionClient"/> or register
/// for use with dependency injection via
/// <see><cref>DaprJobsServiceCollectionExtensions.AddDaprJobsClient</cref></see>.
/// </para>
/// <para>
/// Implementations of <see cref="DaprEncryptionClient"/> implement <see cref="IDisposable"/> because the
/// client accesses network resources. For best performance, create a single long-lived client instance
/// and share it for the lifetime of the application. This is done for you if created via the DI extensions. Avoid
/// creating a disposing a client instance for each operation that the application performs - this can lead to socket
/// exhaustion and other problems.
/// </para>
/// </summary>
public abstract class DaprEncryptionClient(Autogenerated.DaprClient client, HttpClient httpClient, string? daprApiToken = null) : IDaprEncryptionClient
{
private bool disposed;
/// <summary>
/// The HTTP client used by the client for calling the Dapr runtime.
/// </summary>
/// <remarks>
/// Property exposed for testing purposes.
/// </remarks>
internal readonly HttpClient HttpClient = httpClient;
/// <summary>
/// The Dapr API token value.
/// </summary>
/// <remarks>
/// Property exposed for testing purposes.
/// </remarks>
internal readonly string? DaprApiToken = daprApiToken;
/// <summary>
/// The autogenerated Dapr client.
/// </summary>
/// <remarks>
/// Property exposed for testing purposes.
/// </remarks>
internal Autogenerated.DaprClient Client { get; } = client;
/// <summary>
/// Encrypts an array of bytes using the Dapr Cryptography encryption functionality.
/// </summary>
/// <param name="vaultResourceName">The name of the vault resource used by the operation.</param>
/// <param name="plaintextBytes">The bytes of the plaintext value to encrypt.</param>
/// <param name="keyName">The name of the key to use from the Vault for the encryption operation.</param>
/// <param name="encryptionOptions">Options informing how the encryption operation should be configured.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An array of encrypted bytes.</returns>
[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<ReadOnlyMemory<byte>> EncryptAsync(string vaultResourceName,
ReadOnlyMemory<byte> plaintextBytes, string keyName, EncryptionOptions encryptionOptions,
CancellationToken cancellationToken = default);
/// <summary>
/// Encrypts a stream using the Dapr Cryptography encryption functionality.
/// </summary>
/// <param name="vaultResourceName">The name of the vault resource used by the operation.</param>
/// <param name="plaintextStream">The stream containing the bytes of the plaintext value to encrypt.</param>
/// <param name="keyName">The name of the key to use from the Vault for the encryption operation.</param>
/// <param name="encryptionOptions">Options informing how the encryption operation should be configured.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An array of encrypted bytes.</returns>
[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<ReadOnlyMemory<byte>> EncryptAsync(string vaultResourceName, Stream plaintextStream, string keyName,
EncryptionOptions encryptionOptions, CancellationToken cancellationToken = default);
/// <summary>
/// Decrypts the specified ciphertext bytes using the Dapr Cryptography encryption functionality.
/// </summary>
/// <param name="vaultResourceName">The name of the vault resource used by the operation.</param>
/// <param name="ciphertextBytes">The bytes of the ciphertext value to decrypt.</param>
/// <param name="keyName">The name of the key to use from the Vault for the decryption operation.</param>
/// <param name="options">Options informing how the decryption operation should be configured.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An array of decrypted bytes.</returns>
[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<ReadOnlyMemory<byte>> DecryptAsync(string vaultResourceName, ReadOnlyMemory<byte> ciphertextBytes, string keyName, DecryptionOptions? options = null,
CancellationToken cancellationToken = default);
/// <summary>
/// Decrypts the specified stream of ciphertext using the Dapr Cryptography encryption functionality.
/// </summary>
/// <param name="vaultResourceName">The name of the vault resource used by the operation.</param>
/// <param name="ciphertextStream">The stream containing the bytes of the ciphertext value to decrypt.</param>
/// <param name="keyName">The name of the key to use from the Vault for the decryption operation.</param>
/// <param name="options">Options informing how the decryption operation should be configured.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An asynchronously enumerable array of decrypted bytes.</returns>
[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<ReadOnlyMemory<byte>> DecryptAsync(string vaultResourceName, Stream ciphertextStream,
string keyName, DecryptionOptions? options = null, CancellationToken cancellationToken = default);
internal static KeyValuePair<string, string>? GetDaprApiTokenHeader(string apiToken)
{
if (string.IsNullOrWhiteSpace(apiToken))
{
return null;
}
return new KeyValuePair<string, string>("dapr-api-token", apiToken);
}
/// <inheritdoc />
public void Dispose()
{
if (!this.disposed)
{
Dispose(disposing: true);
this.disposed = true;
}
}
/// <summary>
/// Disposes the resources associated with the object.
/// </summary>
/// <param name="disposing"><c>true</c> if called by a call to the <c>Dispose</c> method; otherwise false.</param>
protected virtual void Dispose(bool disposing)
{
}
}

View File

@ -0,0 +1,36 @@
// ------------------------------------------------------------------------
// Copyright 2025 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.Common;
using Microsoft.Extensions.Configuration;
using Autogenerated = Dapr.Client.Autogen.Grpc.v1;
namespace Dapr.Cryptography.Encryption;
/// <summary>
/// Builds a <see cref="DaprEncryptionClient"/>.
/// </summary>
/// <param name="configuration">An optional instance of <see cref="IConfiguration"/>.</param>
public sealed class DaprEncryptionClientBuilder(IConfiguration? configuration = null) : DaprGenericClientBuilder<DaprEncryptionClient>(configuration)
{
/// <summary>
/// Builds the client instance from the properties of the builder.
/// </summary>
/// <returns>The Dapr client instance.</returns>
public override DaprEncryptionClient Build()
{
var daprClientDependencies = this.BuildDaprClientDependencies(typeof(DaprEncryptionClient).Assembly);
var client = new Autogenerated.Dapr.DaprClient(daprClientDependencies.channel);
return new DaprEncryptionGrpcClient(client, daprClientDependencies.httpClient, daprClientDependencies.daprApiToken);
}
}

View File

@ -0,0 +1,208 @@
// ------------------------------------------------------------------------
// Copyright 2025 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.Buffers;
using System.Runtime.CompilerServices;
using Dapr.Common;
using Dapr.Cryptography.Extensions;
using Dapr.Common.Extensions;
using Dapr.Cryptography.Encryption.Models;
using Autogenerated = Dapr.Client.Autogen.Grpc.v1;
namespace Dapr.Cryptography.Encryption;
/// <summary>
/// A client for performing cryptography operations with Dapr.
/// </summary>
internal sealed class DaprEncryptionGrpcClient(Autogenerated.Dapr.DaprClient client, HttpClient httpClient, string? daprApiToken = null) : DaprEncryptionClient(client, httpClient, daprApiToken: daprApiToken)
{
/// <summary>
/// Encrypts an array of bytes using the Dapr Cryptography encryption functionality.
/// </summary>
/// <param name="vaultResourceName">The name of the vault resource used by the operation.</param>
/// <param name="plaintextBytes">The bytes of the plaintext value to encrypt.</param>
/// <param name="keyName">The name of the key to use from the Vault for the encryption operation.</param>
/// <param name="encryptionOptions">Options informing how the encryption operation should be configured.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An array of encrypted bytes.</returns>
[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<ReadOnlyMemory<byte>> EncryptAsync(
string vaultResourceName,
ReadOnlyMemory<byte> plaintextBytes,
string keyName,
EncryptionOptions encryptionOptions,
CancellationToken cancellationToken = default)
{
using var memoryStream = plaintextBytes.CreateMemoryStream(true);
var encryptionResult = EncryptAsync(vaultResourceName, memoryStream, keyName, encryptionOptions, cancellationToken);
var bufferedResult = new ArrayBufferWriter<byte>();
await foreach (var item in encryptionResult)
{
bufferedResult.Write(item.Span);
}
return bufferedResult.WrittenMemory;
}
/// <summary>
/// Encrypts a stream using the Dapr Cryptography encryption functionality.
/// </summary>
/// <param name="vaultResourceName">The name of the vault resource used by the operation.</param>
/// <param name="plaintextStream">The stream containing the bytes of the plaintext value to encrypt.</param>
/// <param name="keyName">The name of the key to use from the Vault for the encryption operation.</param>
/// <param name="encryptionOptions">Options informing how the encryption operation should be configured.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An array of encrypted bytes.</returns>
[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<ReadOnlyMemory<byte>> EncryptAsync(
string vaultResourceName,
Stream plaintextStream,
string keyName,
EncryptionOptions encryptionOptions,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName));
ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName));
ArgumentVerifier.ThrowIfNull(plaintextStream, nameof(plaintextStream));
ArgumentVerifier.ThrowIfNull(encryptionOptions, nameof(encryptionOptions));
EventHandler<Exception> exceptionHandler = (_, ex) => throw ex;
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 grpcCallOptions = DaprClientUtilities.ConfigureGrpcCallOptions(typeof(DaprEncryptionClient).Assembly,
this.DaprApiToken, cancellationToken);
var duplexStream = Client.EncryptAlpha1(grpcCallOptions);
using var streamProcessor = new EncryptionStreamProcessor();
try
{
streamProcessor.OnException += exceptionHandler;
await streamProcessor.ProcessStreamAsync(plaintextStream, duplexStream, encryptRequestOptions,
encryptionOptions.StreamingBlockSizeInBytes,
cancellationToken);
await foreach (var value in streamProcessor.GetProcessedDataAsync(cancellationToken))
{
yield return value;
}
}
finally
{
streamProcessor.OnException -= exceptionHandler;
}
}
/// <summary>
/// Decrypts the specified ciphertext bytes using the Dapr Cryptography encryption functionality.
/// </summary>
/// <param name="vaultResourceName">The name of the vault resource used by the operation.</param>
/// <param name="ciphertextBytes">The bytes of the ciphertext value to decrypt.</param>
/// <param name="keyName">The name of the key to use from the Vault for the decryption operation.</param>
/// <param name="options">Options informing how the decryption operation should be configured.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An array of decrypted bytes.</returns>
[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<ReadOnlyMemory<byte>> DecryptAsync(
string vaultResourceName,
ReadOnlyMemory<byte> ciphertextBytes,
string keyName,
DecryptionOptions? options = null,
CancellationToken cancellationToken = default)
{
using var memoryStream = ciphertextBytes.CreateMemoryStream(true);
var decryptionResult = DecryptAsync(vaultResourceName, memoryStream, keyName, options, cancellationToken);
var bufferedResult = new ArrayBufferWriter<byte>();
await foreach (var item in decryptionResult)
{
bufferedResult.Write(item.Span);
}
return bufferedResult.WrittenMemory;
}
/// <summary>
/// Decrypts the specified stream of ciphertext using the Dapr Cryptography encryption functionality.
/// </summary>
/// <param name="vaultResourceName">The name of the vault resource used by the operation.</param>
/// <param name="ciphertextStream">The stream containing the bytes of the ciphertext value to decrypt.</param>
/// <param name="keyName">The name of the key to use from the Vault for the decryption operation.</param>
/// <param name="options">Options informing how the decryption operation should be configured.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An asynchronously enumerable array of decrypted bytes.</returns>
[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<ReadOnlyMemory<byte>> DecryptAsync(
string vaultResourceName,
Stream ciphertextStream,
string keyName,
DecryptionOptions? options = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName));
ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName));
ArgumentVerifier.ThrowIfNull(ciphertextStream, nameof(ciphertextStream));
options = options ?? new DecryptionOptions();
EventHandler<Exception> exceptionHandler = (_, ex) => throw ex;
var decryptRequestOptions = new Autogenerated.DecryptRequestOptions
{
ComponentName = vaultResourceName,
KeyName = keyName
};
var grpcCallOptions = DaprClientUtilities.ConfigureGrpcCallOptions(typeof(DaprEncryptionClient).Assembly,
this.DaprApiToken, cancellationToken);
var duplexStream = Client.DecryptAlpha1(grpcCallOptions);
using var streamProcessor = new DecryptionStreamProcessor();
try
{
streamProcessor.OnException += exceptionHandler;
await streamProcessor.ProcessStreamAsync(ciphertextStream, duplexStream, options.StreamingBlockSizeInBytes,
decryptRequestOptions,
cancellationToken);
await foreach (var value in streamProcessor.GetProcessedDataAsync(cancellationToken))
{
yield return value;
}
}
finally
{
streamProcessor.OnException -= exceptionHandler;
}
}
}

View File

@ -0,0 +1,147 @@
// ------------------------------------------------------------------------
// Copyright 2025 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.CompilerServices;
using System.Threading.Channels;
using Google.Protobuf;
using Grpc.Core;
using Autogenerated = Dapr.Client.Autogen.Grpc.v1;
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
namespace Dapr.Cryptography.Encryption;
/// <summary>
/// Provides the implementation to decrypt a stream of plaintext data with the Dapr runtime.
/// </summary>
internal sealed class DecryptionStreamProcessor : IDecryptionStreamProcessor, IDisposable
{
private bool disposed;
private readonly Channel<ReadOnlyMemory<byte>> outputChannel = Channel.CreateUnbounded<ReadOnlyMemory<byte>>();
/// <summary>
/// Surfaces any exceptions encountered while asynchronously processing the inbound and outbound streams.
/// </summary>
internal event EventHandler<Exception>? OnException;
/// <summary>
/// Sends the provided bytes in chunks to the sidecar for the encryption operation.
/// </summary>
/// <param name="inputStream">The stream containing the bytes to decrypt.</param>
/// <param name="call">The call to make to the sidecar to process the encryption operation.</param>
/// <param name="streamingBlockSizeInBytes">The size, in bytes, of the streaming blocks.</param>
/// <param name="options">The decryption options.</param>
/// <param name="cancellationToken">Token used to cancel the ongoing request.</param>
public async Task ProcessStreamAsync(
Stream inputStream,
AsyncDuplexStreamingCall<Autogenerated.DecryptRequest, Autogenerated.DecryptResponse> call,
int streamingBlockSizeInBytes,
Autogenerated.DecryptRequestOptions options,
CancellationToken cancellationToken)
{
//Read from the input stream and write to the gRPC call
_ = Task.Run(async () =>
{
try
{
await using var bufferedStream = new BufferedStream(inputStream, streamingBlockSizeInBytes);
var buffer = new byte[streamingBlockSizeInBytes];
int bytesRead;
ulong sequenceNumber = 0;
while ((bytesRead = await bufferedStream.ReadAsync(buffer, cancellationToken)) > 0)
{
var request = new Autogenerated.DecryptRequest
{
Payload = new Autogenerated.StreamPayload
{
Data = ByteString.CopyFrom(buffer, 0, bytesRead), Seq = sequenceNumber
}
};
//Only include the options in the first message
if (sequenceNumber == 0)
{
request.Options = options;
}
await call.RequestStream.WriteAsync(request, cancellationToken);
//Increment the sequence number
sequenceNumber++;
}
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
// Expected cancellation exception
}
catch (Exception ex)
{
OnException?.Invoke(this, ex);
}
finally
{
await call.RequestStream.CompleteAsync();
}
}, cancellationToken);
//Start reading from the gRPC call and writing to the output channel
_ = Task.Run(async () =>
{
try
{
await foreach (var response in call.ResponseStream.ReadAllAsync(cancellationToken))
{
await outputChannel.Writer.WriteAsync(response.Payload.Data.Memory, cancellationToken);
}
}
catch (Exception ex)
{
OnException?.Invoke(this, ex);
}
finally
{
outputChannel.Writer.Complete();
}
}, cancellationToken);
}
/// <summary>
/// Retrieves the processed bytes from the operation from the sidecar and
/// returns as an enumerable stream.
/// </summary>
public async IAsyncEnumerable<ReadOnlyMemory<byte>> GetProcessedDataAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
await foreach (var data in outputChannel.Reader.ReadAllAsync(cancellationToken))
{
yield return data;
}
}
public void Dispose()
{
Dispose(true);
}
private void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
outputChannel.Writer.TryComplete();
}
disposed = true;
}
}
}

View File

@ -0,0 +1,148 @@
// ------------------------------------------------------------------------
// Copyright 2025 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.CompilerServices;
using System.Threading.Channels;
using Google.Protobuf;
using Grpc.Core;
using Autogenerated = Dapr.Client.Autogen.Grpc.v1;
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
namespace Dapr.Cryptography.Encryption;
/// <summary>
/// Provides the implementation to encrypt a stream of plaintext data with the Dapr runtime.
/// </summary>
internal sealed class EncryptionStreamProcessor : IEncryptionStreamProcessor, IDisposable
{
private bool disposed;
private readonly Channel<ReadOnlyMemory<byte>> outputChannel = Channel.CreateUnbounded<ReadOnlyMemory<byte>>();
/// <summary>
/// Surfaces any exceptions encountered while asynchronously processing the inbound and outbound streams.
/// </summary>
internal event EventHandler<Exception>? OnException;
/// <summary>
/// Sends the provided bytes in chunks to the sidecar for the encryption operation.
/// </summary>
/// <param name="inputStream">The stream containing the bytes to encrypt.</param>
/// <param name="call">The call to make to the sidecar to process the encryption operation.</param>
/// <param name="options">The encryption options.</param>
/// <param name="streamingBlockSizeInBytes">The size, in bytes, of the streaming blocks.</param>
/// <param name="cancellationToken">Token used to cancel the ongoing request.</param>
public async Task ProcessStreamAsync(
Stream inputStream,
AsyncDuplexStreamingCall<Autogenerated.EncryptRequest, Autogenerated.EncryptResponse> call,
Autogenerated.EncryptRequestOptions options,
int streamingBlockSizeInBytes,
CancellationToken cancellationToken)
{
//Read from the input stream and write to the gRPC call
_ = Task.Run(async () =>
{
try
{
await using var bufferedStream = new BufferedStream(inputStream, streamingBlockSizeInBytes);
var buffer = new byte[streamingBlockSizeInBytes];
int bytesRead;
ulong sequenceNumber = 0;
while ((bytesRead = await bufferedStream.ReadAsync(buffer, cancellationToken)) > 0)
{
var request = new Autogenerated.EncryptRequest
{
Payload = new Autogenerated.StreamPayload
{
Data = ByteString.CopyFrom(buffer, 0, bytesRead), Seq = sequenceNumber
}
};
//Only include the options in the first message
if (sequenceNumber == 0)
{
request.Options = options;
}
await call.RequestStream.WriteAsync(request, cancellationToken);
//Increment the sequence number
sequenceNumber++;
}
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
// Expected cancellation exception
}
catch (Exception ex)
{
OnException?.Invoke(this, ex);
}
finally
{
await call.RequestStream.CompleteAsync();
}
}, cancellationToken);
//Start reading from the gRPC call and writing to the output channel
_ = Task.Run(async () =>
{
try
{
await foreach (var response in call.ResponseStream.ReadAllAsync(cancellationToken))
{
await outputChannel.Writer.WriteAsync(response.Payload.Data.Memory, cancellationToken);
}
}
catch (Exception ex)
{
OnException?.Invoke(this, ex);
}
finally
{
outputChannel.Writer.Complete();
}
}, cancellationToken);
}
/// <summary>
/// Retrieves the processed bytes from the operation from the sidecar and
/// returns as an enumerable stream.
/// </summary>
public async IAsyncEnumerable<ReadOnlyMemory<byte>> GetProcessedDataAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
await foreach (var data in outputChannel.Reader.ReadAllAsync(cancellationToken))
{
yield return data;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
outputChannel.Writer.TryComplete();
}
disposed = true;
}
}
}

View File

@ -0,0 +1,28 @@
// ------------------------------------------------------------------------
// Copyright 2025 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 Microsoft.Extensions.DependencyInjection;
namespace Dapr.Cryptography.Encryption.Extensions;
/// <summary>
/// Used by the fluent registration builder to configure a Dapr crypto client.
/// </summary>
/// <param name="services"></param>
public sealed class DaprCryptographyBuilder(IServiceCollection services) : IDaprCryptographyBuilder
{
/// <summary>
/// The registered services on the builder.
/// </summary>
public IServiceCollection Services { get; } = services;
}

View File

@ -0,0 +1,42 @@
// ------------------------------------------------------------------------
// Copyright 2025 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.Common.Extensions;
using Microsoft.Extensions.DependencyInjection;
namespace Dapr.Cryptography.Encryption.Extensions;
/// <summary>
/// Contains extension methods for using Dapr cryptography with dependency injection.
/// </summary>
public static class DaprCryptographyServiceCollectionExtensions
{
/// <summary>
/// Adds Dapr encryption/decryption support to the service collection.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
/// <param name="configure">Optionally allows greater configuration of the <see cref="DaprEncryptionClient"/> using injected services.</param>
/// <param name="lifetime">The lifetime of the registered services.</param>
/// <returns></returns>
public static IDaprCryptographyBuilder AddDaprEncryptionClient(
this IServiceCollection services,
Action<IServiceProvider, DaprEncryptionClientBuilder>? configure = null,
ServiceLifetime lifetime = ServiceLifetime.Singleton)
{
services.AddTransient<IDecryptionStreamProcessor, DecryptionStreamProcessor>();
services.AddTransient<IEncryptionStreamProcessor, EncryptionStreamProcessor>();
return services
.AddDaprClient<DaprEncryptionClient, DaprEncryptionGrpcClient, DaprCryptographyBuilder,
DaprEncryptionClientBuilder>(configure, lifetime);
}
}

View File

@ -0,0 +1,19 @@
// ------------------------------------------------------------------------
// Copyright 2025 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 Dapr.Cryptography.Encryption;
/// <summary>
/// Provides a Dapr client specifically for encryption and decryption operations using Dapr.
/// </summary>
public interface IDaprEncryptionBuilder : IDaprCryptographyBuilder;

View File

@ -0,0 +1,77 @@
// ------------------------------------------------------------------------
// Copyright 2025 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.Common;
using Dapr.Cryptography.Encryption.Models;
namespace Dapr.Cryptography.Encryption;
/// <summary>
/// Provides the implementation shape for the Dapr encryption client.
/// </summary>
public interface IDaprEncryptionClient : IDaprClient
{
/// <summary>
/// Encrypts an array of bytes using the Dapr Cryptography encryption functionality.
/// </summary>
/// <param name="vaultResourceName">The name of the vault resource used by the operation.</param>
/// <param name="plaintextBytes">The bytes of the plaintext value to encrypt.</param>
/// <param name="keyName">The name of the key to use from the Vault for the encryption operation.</param>
/// <param name="encryptionOptions">Options informing how the encryption operation should be configured.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An array of encrypted bytes.</returns>
[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public Task<ReadOnlyMemory<byte>> EncryptAsync(string vaultResourceName,
ReadOnlyMemory<byte> plaintextBytes, string keyName, EncryptionOptions encryptionOptions,
CancellationToken cancellationToken = default);
/// <summary>
/// Encrypts a stream using the Dapr Cryptography encryption functionality.
/// </summary>
/// <param name="vaultResourceName">The name of the vault resource used by the operation.</param>
/// <param name="plaintextStream">The stream containing the bytes of the plaintext value to encrypt.</param>
/// <param name="keyName">The name of the key to use from the Vault for the encryption operation.</param>
/// <param name="encryptionOptions">Options informing how the encryption operation should be configured.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An array of encrypted bytes.</returns>
[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public IAsyncEnumerable<ReadOnlyMemory<byte>> EncryptAsync(string vaultResourceName, Stream plaintextStream, string keyName,
EncryptionOptions encryptionOptions, CancellationToken cancellationToken = default);
/// <summary>
/// Decrypts the specified ciphertext bytes using the Dapr Cryptography encryption functionality.
/// </summary>
/// <param name="vaultResourceName">The name of the vault resource used by the operation.</param>
/// <param name="ciphertextBytes">The bytes of the ciphertext value to decrypt.</param>
/// <param name="keyName">The name of the key to use from the Vault for the decryption operation.</param>
/// <param name="options">Options informing how the decryption operation should be configured.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An array of decrypted bytes.</returns>
[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public Task<ReadOnlyMemory<byte>> DecryptAsync(string vaultResourceName, ReadOnlyMemory<byte> ciphertextBytes, string keyName, DecryptionOptions? options = null,
CancellationToken cancellationToken = default);
/// <summary>
/// Decrypts the specified stream of ciphertext using the Dapr Cryptography encryption functionality.
/// </summary>
/// <param name="vaultResourceName">The name of the vault resource used by the operation.</param>
/// <param name="ciphertextStream">The stream containing the bytes of the ciphertext value to decrypt.</param>
/// <param name="keyName">The name of the key to use from the Vault for the decryption operation.</param>
/// <param name="options">Options informing how the decryption operation should be configured.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An asynchronously enumerable array of decrypted bytes.</returns>
[Obsolete(
"The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public IAsyncEnumerable<ReadOnlyMemory<byte>> DecryptAsync(string vaultResourceName, Stream ciphertextStream,
string keyName, DecryptionOptions? options = null, CancellationToken cancellationToken = default);
}

View File

@ -0,0 +1,40 @@
// ------------------------------------------------------------------------
// Copyright 2025 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 Grpc.Core;
namespace Dapr.Cryptography.Encryption;
internal interface IDecryptionStreamProcessor
{
/// <summary>
/// Sends the provided bytes in chunks to the sidecar for the encryption operation.
/// </summary>
/// <param name="inputStream">The stream containing the bytes to decrypt.</param>
/// <param name="call">The call to make to the sidecar to process the encryption operation.</param>
/// <param name="streamingBlockSizeInBytes">The size, in bytes, of the streaming blocks.</param>
/// <param name="options">The decryption options.</param>
/// <param name="cancellationToken">Token used to cancel the ongoing request.</param>
Task ProcessStreamAsync(
Stream inputStream,
AsyncDuplexStreamingCall<Client.Autogen.Grpc.v1.DecryptRequest, Client.Autogen.Grpc.v1.DecryptResponse> call,
int streamingBlockSizeInBytes,
Client.Autogen.Grpc.v1.DecryptRequestOptions options,
CancellationToken cancellationToken);
/// <summary>
/// Retrieves the processed bytes from the operation from the sidecar and
/// returns as an enumerable stream.
/// </summary>
IAsyncEnumerable<ReadOnlyMemory<byte>> GetProcessedDataAsync(CancellationToken cancellationToken);
}

View File

@ -0,0 +1,40 @@
// ------------------------------------------------------------------------
// Copyright 2025 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 Grpc.Core;
namespace Dapr.Cryptography.Encryption;
internal interface IEncryptionStreamProcessor
{
/// <summary>
/// Sends the provided bytes in chunks to the sidecar for the encryption operation.
/// </summary>
/// <param name="inputStream">The stream containing the bytes to encrypt.</param>
/// <param name="call">The call to make to the sidecar to process the encryption operation.</param>
/// <param name="options">The encryption options.</param>
/// <param name="streamingBlockSizeInBytes">The size, in bytes, of the streaming blocks.</param>
/// <param name="cancellationToken">Token used to cancel the ongoing request.</param>
Task ProcessStreamAsync(
Stream inputStream,
AsyncDuplexStreamingCall<Client.Autogen.Grpc.v1.EncryptRequest, Client.Autogen.Grpc.v1.EncryptResponse> call,
Client.Autogen.Grpc.v1.EncryptRequestOptions options,
int streamingBlockSizeInBytes,
CancellationToken cancellationToken);
/// <summary>
/// Retrieves the processed bytes from the operation from the sidecar and
/// returns as an enumerable stream.
/// </summary>
IAsyncEnumerable<ReadOnlyMemory<byte>> GetProcessedDataAsync(CancellationToken cancellationToken);
}

View File

@ -0,0 +1,33 @@
// ------------------------------------------------------------------------
// Copyright 2025 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.Cryptography.Encryption.Models;
/// <summary>
/// The cipher used for data encryption operations.
/// </summary>
public enum DataEncryptionCipher
{
/// <summary>
/// The default data encryption cipher used, this represents AES GCM.
/// </summary>
[EnumMember(Value = "aes-gcm")]
AesGcm,
/// <summary>
/// Represents the ChaCha20-Poly1305 data encryption cipher.
/// </summary>
[EnumMember(Value = "chacha20-poly1305")]
ChaCha20Poly1305
}

View File

@ -0,0 +1,34 @@
// ------------------------------------------------------------------------
// Copyright 2025 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 Dapr.Cryptography.Encryption.Models;
/// <summary>
/// A collection of options used to configure how decryption cryptographic operations are performed.
/// </summary>
public sealed class DecryptionOptions
{
private int streamingBlockSizeInBytes = 4 * 1024; // 4KB
/// <summary>
/// The size of the block in bytes used to send data to the sidecar for cryptography operations.
/// </summary>
public int StreamingBlockSizeInBytes
{
get => streamingBlockSizeInBytes;
set
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value);
streamingBlockSizeInBytes = value;
}
}
}

View File

@ -0,0 +1,52 @@
// ------------------------------------------------------------------------
// Copyright 2025 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 Dapr.Cryptography.Encryption.Models;
/// <summary>
/// A collection of options used to configure how encryption cryptographic operations are performed.
/// </summary>
public sealed class EncryptionOptions(KeyWrapAlgorithm keyWrapAlgorithm)
{
/// <summary>
/// The name of the algorithm used to wrap the encryption key.
/// </summary>
public KeyWrapAlgorithm KeyWrapAlgorithm { get; set; } = keyWrapAlgorithm;
private int streamingBlockSizeInBytes = 4 * 1024; // 4 KB
/// <summary>
/// The size of the block in bytes used to send data to the sidecar for cryptography operations.
/// </summary>
/// <remarks>
/// This defaults to 4KB and generally should not exceed 64KB.
/// </remarks>
public int StreamingBlockSizeInBytes
{
get => streamingBlockSizeInBytes;
set
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value);
streamingBlockSizeInBytes = value;
}
}
/// <summary>
/// The optional name (and optionally a version) of the key specified to use during decryption.
/// </summary>
public string? DecryptionKeyName { get; set; } = null;
/// <summary>
/// The name of the cipher to use for the encryption operation.
/// </summary>
public DataEncryptionCipher EncryptionCipher { get; set; } = DataEncryptionCipher.AesGcm;
}

View File

@ -0,0 +1,58 @@
// ------------------------------------------------------------------------
// Copyright 2025 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.Cryptography.Encryption.Models;
/// <summary>
/// The algorithm used for key wrapping cryptographic operations.
/// </summary>
public enum KeyWrapAlgorithm
{
/// <summary>
/// Represents the AES key wrap algorithm.
/// </summary>
[EnumMember(Value="A256KW")]
Aes,
/// <summary>
/// An alias for the AES key wrap algorithm.
/// </summary>
[EnumMember(Value="A256KW")]
A256kw,
/// <summary>
/// Represents the AES 128 CBC key wrap algorithm.
/// </summary>
[EnumMember(Value="A128CBC")]
A128cbc,
/// <summary>
/// Represents the AES 192 CBC key wrap algorithm.
/// </summary>
[EnumMember(Value="A192CBC")]
A192cbc,
/// <summary>
/// Represents the AES 256 CBC key wrap algorithm.
/// </summary>
[EnumMember(Value="A256CBC")]
A256cbc,
/// <summary>
/// Represents the RSA key wrap algorithm.
/// </summary>
[EnumMember(Value= "RSA-OAEP-256")]
Rsa,
/// <summary>
/// An alias for the RSA key wrap algorithm.
/// </summary>
[EnumMember(Value= "RSA-OAEP-256")]
RsaOaep256 //Alias for RSA
}

View File

@ -0,0 +1,34 @@
// ------------------------------------------------------------------------
// Copyright 2025 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.InteropServices;
namespace Dapr.Cryptography.Extensions;
internal static class ReadOnlyMemoryExtensions
{
public static MemoryStream CreateMemoryStream(this ReadOnlyMemory<byte> memory, bool isReadOnly)
{
if (memory.IsEmpty)
{
return new MemoryStream([], !isReadOnly);
}
if (MemoryMarshal.TryGetArray(memory, out ArraySegment<byte> segment) && segment.Array is not null)
{
return new MemoryStream(segment.Array, segment.Offset, segment.Count, !isReadOnly);
}
throw new ArgumentException("Unable to create MemoryStream from provided memory value", nameof(memory));
}
}

View File

@ -0,0 +1,22 @@
// ------------------------------------------------------------------------
// Copyright 2025 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.Common;
namespace Dapr.Cryptography;
/// <summary>
/// Provides a root builder for the Dapr cryptography operations facilitating a new fluent-style registration that looks
/// ahead to additional Dapr capabilities in this space.
/// </summary>
public interface IDaprCryptographyBuilder : IDaprServiceBuilder;

View File

@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.msbuild">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector"/>
<PackageReference Include="Microsoft.NET.Test.Sdk"/>
<PackageReference Include="moq" />
<PackageReference Include="xunit"/>
<PackageReference Include="xunit.extensibility.core" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Using Include="Xunit"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Dapr.Cryptography\Dapr.Cryptography.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,29 @@
using Dapr.Cryptography.Encryption.Extensions;
using Microsoft.Extensions.DependencyInjection;
namespace Dapr.Cryptography.Test.Encryption.Extensions;
public class DaprCryptographyBuilderTests
{
[Fact]
public void Constructor_InitializesServiceProperty()
{
var services = new ServiceCollection();
var builder = new DaprCryptographyBuilder(services);
Assert.NotNull(builder.Services);
Assert.Equal(services, builder.Services);
}
[Fact]
public void ServicesProperty_ReturnsRegisteredServices()
{
var services = new ServiceCollection();
services.AddSingleton<object>();
var builder = new DaprCryptographyBuilder(services);
Assert.NotNull(builder.Services);
Assert.Single(builder.Services);
}
}

View File

@ -0,0 +1,66 @@
using System;
using System.Linq;
using Dapr.Cryptography.Encryption;
using Dapr.Cryptography.Encryption.Extensions;
using Microsoft.Extensions.DependencyInjection;
namespace Dapr.Cryptography.Test.Encryption.Extensions;
public class DaprCryptographyServiceCollectionExtensionsTests
{
[Fact]
public void AddDaprEncryptionClient_RegistersServicesWithSingletonLifetime()
{
var services = new ServiceCollection();
var builder = services.AddDaprEncryptionClient();
var servicesProvider = services.BuildServiceProvider();
var daprClient = servicesProvider.GetService<DaprEncryptionClient>();
Assert.NotNull(builder);
Assert.NotNull(daprClient);
Assert.Equal(ServiceLifetime.Singleton, services.First(sd => sd.ServiceType == typeof(DaprEncryptionClient)).Lifetime);
}
[Fact]
public void AddDaprEncryptionClient_RegistersServicesWithScopedLifetime()
{
var services = new ServiceCollection();
var builder = services.AddDaprEncryptionClient(lifetime: ServiceLifetime.Scoped);
var serviceProvider = services.BuildServiceProvider();
var daprClient = serviceProvider.GetService<DaprEncryptionClient>();
Assert.NotNull(builder);
Assert.NotNull(daprClient);
Assert.Equal(ServiceLifetime.Scoped, services.First(sd => sd.ServiceType == typeof(DaprEncryptionClient)).Lifetime);
}
[Fact]
public void AddDaprEncryptionClient_RegistersServicesWithTransientLifetime()
{
var services = new ServiceCollection();
var builder = services.AddDaprEncryptionClient(lifetime: ServiceLifetime.Transient);
var serviceProvider = services.BuildServiceProvider();
var daprClient = serviceProvider.GetService<DaprEncryptionClient>();
Assert.NotNull(builder);
Assert.NotNull(daprClient);
Assert.Equal(ServiceLifetime.Transient, services.First(sd => sd.ServiceType == typeof(DaprEncryptionClient)).Lifetime);
}
[Fact]
public void AddDaprEncryptionClient_WithConfigureAction_ExecutesConfiguation()
{
var services = new ServiceCollection();
var configureCalled = false;
Action<IServiceProvider, DaprEncryptionClientBuilder> configure = (sp, builder) => configureCalled = true;
var builder = services.AddDaprEncryptionClient(configure);
var serviceProvider = services.BuildServiceProvider();
var daprClient = serviceProvider.GetService<DaprEncryptionClient>();
Assert.NotNull(builder);
Assert.NotNull(daprClient);
Assert.True(configureCalled);
}
}

View File

@ -0,0 +1,35 @@
using System;
using Dapr.Cryptography.Encryption.Models;
namespace Dapr.Cryptography.Test.Encryption.Models;
public class DecryptionOptionsTests
{
[Fact]
public void DefaultStreamingBlockSizeInBytes_Is4KB()
{
var options = new DecryptionOptions();
var defaultBlockSize = options.StreamingBlockSizeInBytes;
Assert.Equal(4 * 1024, defaultBlockSize);
}
[Theory]
[InlineData(1024)]
[InlineData(2048)]
[InlineData(8192)]
public void StreamingBlockSizeInBytes_SetValidValue_UpdatesBlockSize(int newBlockSize)
{
var options = new DecryptionOptions();
options.StreamingBlockSizeInBytes = newBlockSize;
Assert.Equal(newBlockSize, options.StreamingBlockSizeInBytes);
}
[Theory]
[InlineData(0)]
[InlineData(-1024)]
public void StreamingBlockSizeInBytes_SetInvalidValue_ThrowsArgumentOutOfRangeException(int invalidBlockSize)
{
var options = new DecryptionOptions();
Assert.Throws<ArgumentOutOfRangeException>(() => options.StreamingBlockSizeInBytes = invalidBlockSize);
}
}

View File

@ -0,0 +1,72 @@
// ------------------------------------------------------------------------
// Copyright 2025 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 Dapr.Cryptography.Encryption.Models;
namespace Dapr.Cryptography.Test.Encryption.Models;
public class EncryptionOptionsTests
{
[Fact]
public void Constructor_InitializesKeyWrapAlgorithm()
{
var keyWrapAlgorithm = KeyWrapAlgorithm.RsaOaep256;
var options = new EncryptionOptions(keyWrapAlgorithm);
Assert.Equal(keyWrapAlgorithm, options.KeyWrapAlgorithm);
}
[Fact]
public void DefaultStreamingBlockSizeInBytes_Is4Kb()
{
var options = new EncryptionOptions(KeyWrapAlgorithm.A256kw);
var defaultBlockSize = options.StreamingBlockSizeInBytes;
Assert.Equal(4 * 1024, defaultBlockSize);
}
[Theory]
[InlineData(1024)]
[InlineData(2048)]
[InlineData(4092)]
public void StreamingBlockSizeInBytes_SetValidValue_UpdatesBlockSize(int newBlockSize)
{
var options = new EncryptionOptions(KeyWrapAlgorithm.A192cbc);
options.StreamingBlockSizeInBytes = newBlockSize;
Assert.Equal(newBlockSize, options.StreamingBlockSizeInBytes);
}
[Theory]
[InlineData(0)]
[InlineData(-1024)]
public void StreamingBlockSizeInBytes_SetInvalidValue_ThrowsArgumentOutOfRangeException(int invalidBlockSize)
{
var options = new EncryptionOptions(KeyWrapAlgorithm.Rsa);
Assert.Throws<ArgumentOutOfRangeException>(() => options.StreamingBlockSizeInBytes = invalidBlockSize);
}
[Fact]
public void DefaultDecryptionKeyName_IsNull()
{
var options = new EncryptionOptions(KeyWrapAlgorithm.A256cbc);
string? defaultKeyName = options.DecryptionKeyName;
Assert.Null(defaultKeyName);
}
[Fact]
public void DefaultEncryptionCipher_IsAes()
{
var options = new EncryptionOptions(KeyWrapAlgorithm.Aes);
var defaultCipher = options.EncryptionCipher;
Assert.Equal(DataEncryptionCipher.AesGcm, defaultCipher);
}
}

View File

@ -0,0 +1,59 @@
// ------------------------------------------------------------------------
// Copyright 2025 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 Dapr.Cryptography.Extensions;
namespace Dapr.Cryptography.Test.Extensions;
public class ReadOnlyMemoryExtensionsTests
{
[Fact]
public void CreateMemoryStream_EmptyMemory_ReturnsEmptyMemoryStream()
{
var emptyMemory = ReadOnlyMemory<byte>.Empty;
var result = emptyMemory.CreateMemoryStream(isReadOnly: true);
Assert.NotNull(result);
Assert.Equal(0, result.Length);
Assert.True(result.CanRead);
Assert.False(result.CanWrite);
}
[Fact]
public void CreateMemoryStream_NonEmptyMemory_ReturnsMemoryStream()
{
byte[] data = { 1, 2, 3, 4, 5 };
var memory = new ReadOnlyMemory<byte>(data);
var result = memory.CreateMemoryStream(isReadOnly: true);
Assert.NotNull(result);
Assert.Equal(data.Length, result.Length);
Assert.True(result.CanRead);
Assert.False(result.CanWrite);
}
[Fact]
public void CreateMemoryStream_NonEmptyMemory_ReturnsWriteableMemoryStream()
{
byte[] data = { 1, 2, 3, 4, 5 };
var memory = new ReadOnlyMemory<byte>(data);
var result = memory.CreateMemoryStream(isReadOnly: false);
Assert.NotNull(result);
Assert.Equal(data.Length, result.Length);
Assert.True(result.CanRead);
Assert.True(result.CanWrite);
}
}

View File

@ -13,6 +13,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Dapr.Actors\Dapr.Actors.csproj" />
<ProjectReference Include="..\..\src\Dapr.Cryptography\Dapr.Cryptography.csproj" />
<ProjectReference Include="..\..\src\Dapr.Workflow\Dapr.Workflow.csproj" />
<ProjectReference Include="..\..\src\Dapr.Extensions.Configuration\Dapr.Extensions.Configuration.csproj" />
<ProjectReference Include="..\Dapr.E2E.Test.Actors\Dapr.E2E.Test.Actors.csproj" />
@ -24,4 +25,10 @@
<ItemGroup>
<Protobuf Include="../Dapr.E2E.Test.App.Grpc/Proto/message.proto" GrpcServices="Client" />
</ItemGroup>
<ItemGroup>
<None Remove="keys\rsa-private-key.pem" />
<Content Include="keys\rsa-private-key.pem">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

View File

@ -0,0 +1,11 @@
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: localstorage
spec:
type: crypto.dapr.localstorage
version: v1
metadata:
- name: path
# Path is relative to the folder where the example is located
value: ./keys

View File

@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQC0URLpxZCqDv7S
WfROh2Kei4VCEayNu/TK3NaD/QlIpip1rrsPKgTfTOZoRmkmG0Qj59srEJi2GEhL
xpjvRQpA/C/OS+KELU8AeGrqHw7uN/a99NkoAr+zYDCyY9yckPeC5wGxc0/Q6HQT
mWp+YcpR9wFO0PmTVlObssibagjjRNX7z/ZosecOOqjnAqlnYoHMavvoCD5fxM7y
cm7so0JWooXwVaZKgehBEBg1W5F0q5e9ssAQk3lY6IUd5sOskiylTNf/+3r1JU0j
YM8ik3a1/dyDALVXpLSfz7FM9VEj4QjiPF4UuXeBHPDFFiKWbiKfbjqvZ2Sz7Gl7
c5rTk1Fozpr70E/wihrrv22Mxs0sEPdtemQgHXroQfRW8K4FhI0WHs7tR2gVxLHu
OAU9LzCngz4yITh1eixVDmm/B5ZtNVrTQmaY84vGqhrFp+asyFNiXbhUAcT7D/q6
w/c4aQ635ntCFSPYpWvhKqrqVDsoanD/5AWfc3+6Ek2/GVMyEQq+9tnCMM10EVSX
8PsoAWHESDFude5zkHzn7IKy8mh6lfheEbBI5zN9z7WGexyiBgljmyUHXx6Pd8Uc
yxpLRm94kynkDXD9SapQLzXmz+D+X/OYeADMIDWlbdXiIb1+2Q62H1lo6n10KVP7
oEr8BHvcMFY89kwK4lKscUupn8xkzwIDAQABAoICACDuu78Rc8Hzeivt/PZIuMTP
I5f1BWhffy571fwGP2dS3edfcc+rs3cbIuvBjFvG2BOcuYUsg0+isLWSQIVWvTAw
PwT1DBpq8gZad+Bpqr7sXrbD3NN3aQ64TzyNi5HW0jXIviDsOBQmGGkp+G67qol8
zPLZrPNxbVS++u+Tlqr3fAOBMHZfo50QLp/+dvUoYx90HKz8sHOqTMewCb1Tdf6/
sSm7YuMxxbr4VwuLvU2rN0wQtQ5x+NQ5p3JWHr/KdLf+CGc6xXK3jNaczEf62dAU
XO1aOESZEtorQy0Ukuy0IXy8XMx5MS/WGs1MJSYHWHB43+QARL6tu3guHYVt3wyv
W6YTglQsSKc6uuK4JTZOx1VYZjjnSdeY/xiUmZGYp4ZiC9p8b9NvXmZT2EwqhCVt
4OTcX4lkwGAsKcoEdLHi0K5CbBfYJsRgVVheDjP0xUFjCJCYqfqo2rE5YMXMTeY7
clYEOXKGxwuy1Iu8nKqtWAV5r/eSmXBdxBqEBW9oxJfnnwNPG+yOk0Qkd1vaRj00
mdKCOjgB2fOuPX2JRZ2z41Cem3gqhH0NQGrx3APV4egGrYAMClasgtZkUeUOIgK5
xLlC/6svuHNyKXAKFpOubEy1FM8jz7111eNHxHRDP3+vH3u4CfAD2Sl+VDZdg51i
WmVpT+B/DrnlHVSP2/XNAoIBAQD7F49oSdveKuO/lAyqkE9iF61i09G0b0ouDGUI
qx+pd5/8vUcqi4upCxz+3AqMPWZRIqOyo8EUP7f4rSJrXn8U2SwnFfi4k2jiqmEA
Wr0b8z5P1q5MH6BtVDa0Sr1R8xI9s3UgIs4pUKgBoQu9+U4Du4NSucQFcea8nIVY
lLCqQcRhz8bCJPCNuHay5c77kK3Te197KPMasNurTNMOJcPMG95CZLB8Clf4A+pw
fixvA1/fE4mFo1L7Ymxoz5lFYVWOTY9hh50Kqz57wxw4laU4ii+MaJj+YHuNR83N
cO6FztUYKMR8BPgtl3/POTHTofSg7eIOiUYwcfRr6jbMWlsDAoIBAQC311xiMpho
Hvdcvp3/urrIp2QhdD05n6TnZOPkpnd9kwGku2RA+occDQOg/BzADVwJaR/aE97F
jbfRlfBesTZlUec0EwjKIFbeYh+QS/RmjQe9zpPQWMo1M7y0fMWU+yXRUcNBpcuy
R6KlphK0k4xFkIAdC3QHmJQ0XvOpqvrhFy3i/Prc5Wlg29FYBBTAF0WZCZ4uCG34
D0eG0CNaf8w9g9ClbU6nGLBCMcgjEOPYfyrJaedM+jXennLDPG6ySytrGwnwLAQc
Okx+SrIiNHUpQGKteT88Kdpgo3F4KUX/pm84uGdxrOpDS7L0T9/G4CbjzCe1nHeS
fJJsw5JN+Z9FAoIBAGn5S6FsasudtnnI9n+WYKq564fmdn986QX+XTYHY1mXD4MQ
L9UZCFzUP+yg2iLOVzyvLf/bdUYijnb6O6itPV2DO0tTzqG4NXBVEJOhuGbvhsET
joS6ZG9AN8ZoNPc9a9l2wFxL1E9Dp2Ton5gSfIa+wXJMzRqvM/8u4Gi+eMGi+Et/
8hdGl/B4hkCDFZS/P14el/HXGqONOWlXB0zVS4n9yRSkgogXpYEbxfqshfxkpDX2
fPhWMlO++ppR5BKQPhfNTFKRdgpms/xwIJ0RK6ZtTBwqmUfjWMIMKCQpIcJ/xRhp
PGRLhKNZaawAK7Nyi1jQjbQs497WeZ6CP5aIHBkCggEALHyl83FQ5ilQLJZH/6E9
H9854MqTIkWajxAgAa2yzqVrSWS7XuoBFe2kSimX/3V8Jx7UQV57kwy3RbVl5FQ3
2I7YRwawItFulAPkpXNr4gEQtYKuzEUgMX2ilX54BZQ804lYmaM4Rp0FI9arQh1O
XWsZRW4HFut6Oa4cgptIeH22ce5L+nZdaL3oy8a5Cr7W7bChIXySt+tioKHvXC/+
yYgDTnTECrVzuaD4UFv+9t3XCcRh34PQ010+YjZWhzifehyh7AeKuxX0er8ymgpd
q6zT9CyZ+8IZATer9qruMG4jDfO5vI1eZwiDdpF5klOdtZQqq80ANmeEu2McHVhh
jQKCAQBbohPxMb3QYdukGp8IsIF04GfnTgaDbRgl4KeUyzdBN3nzvCKK0HDluptR
4Ua64JksGG24gsTBy6yuQoGRCG0LJe0Ty3TRRnvZ8MpADoNMObspMSC8n8kk6ps+
SoG1U9t6HYlIgQagvTc7mTmCmwYX1zlCoZp24yz5pDkKxqoPFDtrGlXxeUgOhpDT
Mzi+DNTz9sH9vod4ibQiOseUxITwQpXHTJVrtNfvva6xjlhq+GGCuKIUwkUKOvBC
ds7SR9demn69aWCyzXqD1cTnmxtn6bNPukwowg7a07ieUyKftcJ1icOWQ/bdQkEf
dV1dhNiQEnqs4vDBVn40dnTKSSG2
-----END PRIVATE KEY-----