dotnet-sdk/examples/Client/Cryptography
Whit Waldo ca2fab2567
Implementing Cryptography building block in .NET (#1217)
* Added method to DaprClient and GRPC implementation to call cryptography proto endpoints

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* First pass at implementing all exposed Cryptography methods on Go interface

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Added examples for Cryptography block

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Added missing copyright statements

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Updated to properly support Crypto API this time

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Added copyright statements

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Removed deprecated examples as the subtle APIs are presently disabled

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Updated example to reflect new API shape

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Updated example and readme

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Added overloads for encrypting/decrypting streams instead of just fixed byte arrays. Added example demonstrating the same encrypting a file via a FileStream and decrypting from a MemoryStream.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Added some unit tests to pair with the implementation

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Added null check for the stream argument

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Changed case of the arguments as they should read "plaintext" and not "plainText"

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Reduced number of encryption implementations by just wrapping byte array into memory stream

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Constrainted returned member types per review suggestion

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Updated methods to use ReadOnlyMemory<byte> instead of byte[] - updated implementations to use low-allocation spans where possible (though ToArray is necessary to wrap with MemoryStream).

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Updated to use encryption/decryption options instead of lots of method overload variations. Simplified gRPC implementation to use fewer methods. Applied argument name updates applied previously (plainText -> plaintext).

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Updated tests

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Removed unused reference

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Updated examples to reflect new method shapes. Downgraded package to .net 6 instead of .net 8 per review suggestion.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Updated to reflect non-aliased values per review suggestion

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Update to ensure that both send/receive streams run at the same time instead of sequentially.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Updated to support streamed results in addition to fixed byte arrays. Refactored implementation to minimize duplicative code.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Updated example to fix compile issue

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Removed encrypt/decrypt methods that accepted streams and returned ReadOnlyMemory<byte>. Marked implementations that use this on the gRPC class as private instead.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Added missing Obsolete attributes on Encrypt/Decrypt methods. Added overloads on decrypt methods that do not require a DecryptionOptions to be passed in.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Updated encrypt/decrypt options so the streaming block size no longer uses a uint. Added validation in its place to ensure the value provided is never less than or equal to 0.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Updated how validation works in the options to accommodate lack of the shorter variation in .NET 6

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Updated names of encrypt/decrypt streaming methods so everything uses just EncryptAsync or DecryptAsync

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Fixed regression that would have prevented data from being sent entirely to the sidecar. Also simplified operation per suggestion in review.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Updated examples to reflect changed API

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Updated so IAsyncEnumerable methods (encrypt and decrypt) return IAsyncEnumerable<ReadOnlyMemory<byte>> instead of IAsyncEnumerable<byte[]>.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Updated example to reflect change from IAsyncEnumerable<byte> to IAsyncEnumerable<ReadOnlyMemory<byte>>

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Avoiding allocation by using MemoryMarshal instead of .ToArray() to create MemoryStream from ReadOnlyMemory<byte>.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Performance updates to minimize unnecessary byte array copies and eliminate unnecessary allocations.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Removed unnecessary return from SendPlaintextStreamAsync and SendCiphertextStreamAsync methods

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Updated exception text to be more specific as to what's wrong with the input value.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Minor tweak to prefer using using a Memory

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Deduplicated some of the Decrypt methods, simplifying the implementation

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Eliminated duplicate encryption method, simplifying implementation

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Updated to eliminate an unnecessary `await` and `async foreach`.

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Updated stream example to reflect the changes to the API shape

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

* Added notes about operations with stream-based data

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>

---------

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
2024-02-14 11:29:08 -08:00
..
Components Implementing Cryptography building block in .NET (#1217) 2024-02-14 11:29:08 -08:00
Examples Implementing Cryptography building block in .NET (#1217) 2024-02-14 11:29:08 -08:00
Cryptography.csproj Implementing Cryptography building block in .NET (#1217) 2024-02-14 11:29:08 -08:00
Example.cs Implementing Cryptography building block in .NET (#1217) 2024-02-14 11:29:08 -08:00
Program.cs Implementing Cryptography building block in .NET (#1217) 2024-02-14 11:29:08 -08:00
README.md Implementing Cryptography building block in .NET (#1217) 2024-02-14 11:29:08 -08:00
file.txt Implementing Cryptography building block in .NET (#1217) 2024-02-14 11:29:08 -08:00

README.md

Dapr .NET SDK Cryptography example

Prerequisites

Service Principal/Environment Variables Setup

In your Azure portal, open Microsoft Entra ID and click App Registrations. Click the button at the top to create a new registration. Select a name for your service principal and click register, noting this name for later.

Once the registration is completed, open it from the list and select Certificates & Secrets from the left navigation. Select "Client secrets" from the page body (middle column) and click the button to add a new client secret giving it an optional description and changing the expiry date as you desire. Click Add to create the secret. Record the secret value it shows you - it will not be shown to you again without creating another client secret.

Click Overview from the left navigation and record the "Application (client) ID" and the "Directory (tenant) ID" values.

On your computer (assuming Windows), open your start menu and type "Environment Variables". An option should appear named "Edit the system environment variables". Select this and your System Properties window will open. Click the "Environment Variables" button in the bottom and said window will appear. Click the "New..." button under System variables to add the requisite service principal values to your environment variables. You can change these names as to want by updating the ./Components/azurekeyvault.yaml names, but for now configure as follows:

Variable Name Value
read_azure_client_id Paste the value from your app registration overview for "Application (client) ID"
read_azure_client_secret Paste the value of the client secret you generated for your app registration
read_azure_tenant_id Paste the valeu from your app registration overview for "Directory (tenant) ID"

Click OK to save your environment variables and to close your System Properties window. You may need to close restart your command line tool for it to recognize the new values.

Azure Key Vault Setup

This example is implemented using the Azure Key Vault and will not work without it. Assuming you have a Key Vault instance configured, ensure that you have the Key Vault Crypto Officer role assigned to yourself as you'll need to in order to generate a new key in the instance. After selecting Keys under the Objects header, click the Generate/Import button at the top of the instance panel.

Under options, select Generate and name your key. This example is pre-configured to assume a key name of 'myKey', but feel free to change this (but also update the name in the example you wish to run). The other default options are fine for our purposes, so click Create at the bottom and if you've got the appropriate roles, it will show up in the list of Keys.

Update your ./Components/azurekeyvault.yaml file with the name of your Key Vault under vaultName where it currently reads "changeMe". This sample assumes authentication via a service principal, so you might also need to set this up.

Back in the Azure Portal, assign at least the Key Vault Crypto User role to the service principal you previously created in the last step. Do this by clicking Access Control (IAM) from the left navigation, clicking "Add" from the top and clicking "Add Role Assignment". Select Key Vault Crypto User from the list and click the Next button. Ensuring that the "User, group or service principal" option is selected, click the "Select members" link and search for the name of the app registration you created. Click Add to add this service principal to the list of members for the new role assignment and click Review + Assign twice to assign the role. This will take effect within a few seconds or minutes. This step ensures that while Dapr can authenticate as your service principal, that it also has permission to access and use the key in your Key Vault.

Running the example

To run the sample locally, run this command in the DaprClient directory:

dapr run --resources-path ./Components --app-id DaprClient -- dotnet run <zero-indexed sample number>

Running the following command will output a list of the samples included:

dapr run --resources-path ./Components --app-id DaprClient -- dotnet run

Press Ctrl+C to exit, and then run the command again and provide a sample number to run the samples.

For example, run this command to run the first sample from the list produced earlier (the 0th example):

dapr run --resources-path ./Components --app-id DaprClient -- dotnet run 0

Encryption/Decryption with strings

See EncryptDecryptStringExample.cs for an example of using DaprClient for basic string-based encryption and decryption operations as performed against UTF-8 encoded byte arrays.

Encryption/Decryption with streams

See EncryptDecryptFileStreamExample.cs for an example of using DaprClient to perform an encrypt and decrypt operation against a stream of data. In the example, we stream a local file to the sidecar to encrypt and write the result (as it's streamed back) to an in-memory buffer. Once the operation fully completes, we perform the decrypt operation against this in-memory buffer and write the decrypted result back out to a temporary file.

In either operation, rather than load the entire stream into memory and send all at once to the sidecar as we do in the other string-based example (as this might cause you to run out of memory either on the node the app is running on or do the same to the sidecar itself), this example instead breaks the input stream into more manageable 4KB chunks (a value you can override via the EncryptionOptions or DecryptionOptions parameters respectively up to 64KB. Further, rather than waiting for the entire stream to send to the sidecar before the encryption operation proceeds, it immediately works to process the sidecar response, continuing to minimize resource usage.