Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing Cryptography building block in .NET #1217

Merged
merged 43 commits into from
Feb 14, 2024

Conversation

WhitWaldo
Copy link
Contributor

@WhitWaldo WhitWaldo commented Dec 22, 2023

Description

This adds support for Cryptography building block (alpha) in .NET supporting both the encrypt and decrypt streaming APIs.

Examples work and I've updated the README to reflect use with Azure Key Vault (RBAC, environment variable install, etc.).

Issue reference

We strive to have all PR being opened based on an issue, where the problem or feature have been discussed prior to implementation.

This closes #1072 - per recommendations in Discord, this streams the requests in configurable blocks (defaulting to 4 KB), sending only the request metadata payload in the first message and sending the plaintext/ciphertext blocks in subsequent messages. I manually updated the relevant sections of the dapr.proto pulling from the latest in dapr/dapr.

Submitted update for docs at dapr/docs#3928.
Also needs a Quickstart to show how this is used dapr/quickstarts#969

I still need to figure out how to write any tests for this, but I confirmed that my example (properly configured) runs without issue locally.

Checklist

Please make sure you've completed the relevant tasks for this PR, out of the following list:

  • Code compiles correctly
  • Created/updated tests (some tests written - presumably room for more)
  • Extended the documentation

@WhitWaldo WhitWaldo requested review from a team as code owners December 22, 2023 09:59
Copy link

codecov bot commented Dec 22, 2023

Codecov Report

Attention: 122 lines in your changes are missing coverage. Please review.

Comparison is base (348d143) 68.48% compared to head (48bd541) 67.17%.

Files Patch % Lines
src/Dapr.Client/DaprClientGrpc.cs 6.95% 103 Missing and 4 partials ⚠️
src/Dapr.Client/CryptographyOptions.cs 27.77% 13 Missing ⚠️
src/Dapr.Client/EnumExtensions.cs 60.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1217      +/-   ##
==========================================
- Coverage   68.48%   67.17%   -1.32%     
==========================================
  Files         172      174       +2     
  Lines        5848     5986     +138     
  Branches      648      667      +19     
==========================================
+ Hits         4005     4021      +16     
- Misses       1681     1798     +117     
- Partials      162      167       +5     
Flag Coverage Δ
net6 67.15% <11.59%> (-1.32%) ⬇️
net7 67.15% <11.59%> (-1.32%) ⬇️
net8 67.15% <11.59%> (-1.32%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@WhitWaldo WhitWaldo changed the title WIP: Implementing Cryptography building block in .NET Implementing Cryptography building block in .NET Dec 23, 2023
@WhitWaldo
Copy link
Contributor Author

/assign

/// <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<byte[]> EncryptAsync(string vaultResourceName, byte[] plainTextBytes,
KeyWrapAlgorithm algorithm, string keyName, string decryptionKeyName, DataEncryptionCipher dataEncryptionCipher = DataEncryptionCipher.AesGcm,
Copy link
Collaborator

@philliphoff philliphoff Jan 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should there be an explicit endeavour to make the default cipher consistent across SDKs? (Is this consistent with other SDKs today?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the default per the comments in the dapr.proto so I simply selected it, but in case there was a reason for users to use the alternative (right now just "chacha20-poly1305"), I wanted to leave users the option to select the other one. That said, this issue suggested that a third should be added and used as the default (perhaps completely replacing the alternatives) and by setting it as the default it would promote better security.

Copy link
Contributor

@ItalyPaleAle ItalyPaleAle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is awesome, thanks for writing this as I know it wasn't the simplest code :) I mentioned to you before that I'm really bad at C#, especially when streaming is involved, so please take my comments with a grain of salt!

src/Dapr.Client/CryptographyEnums.cs Outdated Show resolved Hide resolved
src/Dapr.Client/CryptographyEnums.cs Show resolved Hide resolved
src/Dapr.Client/DaprClientGrpc.cs Outdated Show resolved Hide resolved
src/Dapr.Client/DaprClientGrpc.cs Outdated Show resolved Hide resolved
public override async Task<ReadOnlyMemory<byte>> DecryptAsync(string vaultResourceName,
ReadOnlyMemory<byte> ciphertextBytes, string keyName, DecryptionOptions decryptionOptions,
CancellationToken cancellationToken = default) =>
await DecryptAsync(vaultResourceName, new MemoryStream(ciphertextBytes.ToArray()), keyName,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ReadOnlyMemory<byte> should by implicitly convertible to byte[], no? ToArray() will make a copy which is probably not what we want.

Copy link
Contributor Author

@WhitWaldo WhitWaldo Jan 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

byte[] is implicitly convertible to a ReadOnlyMemory<byte>, but not vice versa. The best alternative I can find is using a MemoryMarshal to create a reference to the byte array and pass that in instead of copying it, but as this is exposed via TryGetArray, it begs the new question of what the exception message should be if it fails for some reason. Is there any documented guidance on the shape of exceptions for the SDK?

Guidance needed for here and here.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Ach! The purpose of accepting ReadOnlyMemory<byte> was to be more flexible for callers, but if we have to essentially restrict it to just array-like memory (or else make a copy), I'm wondering if that flexibility is really worth it. (Note that there is a performant Stream implementation over ReadOnlyMemory<byte> in the .NET "high efficiency" community toolkit. That said, I don't think we want to go down that path of taking on a new dependency or having our own implementation just for this purpose.)

I'm starting to feel like accepting (for now) byte[] as the better approach for now. (And also returning byte[] to keep the symmetry of the method.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like their implementation to make a memory stream from a ReadOnlyMemory<byte> is awfully similar to what I've got, using the MemoryMarshal to access the byte array.

I did copy over their argument exception text as it's richer than what I had.

As such, I vote leaving this as-is. It's about as high-performant as it'll probably get with this version of .NET and there's nothing that stops the developer from simply passing in a byte[] to the method if that's what they have or a ReadOnlyMemory<byte> if they happen to have that as well (as opposed to making this the developer's problem by reverting to only accepting byte[] and leaving them having to do this same research to convert their own ReadOnlyMemory<byte> accordingly).

@WhitWaldo
Copy link
Contributor Author

@philliphoff Would appreciate your thoughts on this: Because of the method signature conflict, I renamed the encrypt/decrypt methods that took a stream and returned an IAsyncEnumerable<byte[]> to EncryptStreamAsync and DecryptStreamAsync.

Now that the Stream -> ReadOnlyMemory method is out of the picture, do you think it makes the API more accessible to keep these different names or instead expose just the EncryptAsync and DecryptAsync methods again with their various overloads?

@philliphoff
Copy link
Collaborator

Now that the Stream -> ReadOnlyMemory method is out of the picture, do you think it makes the API more accessible to keep these different names or instead expose just the EncryptAsync and DecryptAsync methods again with their various overloads?

I would try to use just a single method name (EncryptAsync() and DecryptAsync()), if possible, as that keeps the API completion lists shorter and groups related methods together better.

…e shorter variation in .NET 6

Signed-off-by: Whit Waldo <[email protected]>
… just EncryptAsync or DecryptAsync

Signed-off-by: Whit Waldo <[email protected]>
…ely to the sidecar. Also simplified operation per suggestion in review.

Signed-off-by: Whit Waldo <[email protected]>
…ncEnumerable<ReadOnlyMemory<byte>> instead of IAsyncEnumerable<byte[]>.

Signed-off-by: Whit Waldo <[email protected]>
…ncEnumerable<ReadOnlyMemory<byte>>

Signed-off-by: Whit Waldo <[email protected]>
…reate MemoryStream from ReadOnlyMemory<byte>.

Signed-off-by: Whit Waldo <[email protected]>
…minate unnecessary allocations.

Signed-off-by: Whit Waldo <[email protected]>
@WhitWaldo
Copy link
Contributor Author

If the terminal only supported multi-line commit messages, that would have been much easier to deal with. As it is, I believe I've properly merged this and the DCO check is validating as it should. @philliphoff

@philliphoff
Copy link
Collaborator

@WhitWaldo Would you be willing to look at the differences in code coverage and see if there are some tests that could be added to bring it back to parity with the baseline?

@WhitWaldo
Copy link
Contributor Author

@philliphoff I can give it a look, but I don't know that there's a whole lot that can be added on the .NET side since it's largely just shuffling the bytes out and vice versa, as all the magic happens on the Dapr sidecar. Give me a few days and I'll see what I can add to it that I don't already have in there. What's the baseline we're aiming for?

@philliphoff
Copy link
Collaborator

philliphoff commented Feb 1, 2024

What's the baseline we're aiming for?

@WhitWaldo Ideally we'd end up close to the same test coverage as the baseline (i.e. prior to the changes). The coverage maps show a good portion of the new logic that isn't covered. This may just be a false-negative, but something to look at to see if that's the case or, if not, how easy it is to get it covered.

@philliphoff philliphoff merged commit ca2fab2 into dapr:master Feb 14, 2024
10 of 12 checks passed
@philliphoff
Copy link
Collaborator

Merging this to ensure it's in 1.13; more test coverage would be nice prior to the APIs being considered stable but can wait.

jamesmcroft pushed a commit to jamesmcroft/dotnet-sdk that referenced this pull request Feb 16, 2024
* Added method to DaprClient and GRPC implementation to call cryptography proto endpoints

Signed-off-by: Whit Waldo <[email protected]>

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

Signed-off-by: Whit Waldo <[email protected]>

* Added examples for Cryptography block

Signed-off-by: Whit Waldo <[email protected]>

* Added missing copyright statements

Signed-off-by: Whit Waldo <[email protected]>

* Updated to properly support Crypto API this time

Signed-off-by: Whit Waldo <[email protected]>

* Added copyright statements

Signed-off-by: Whit Waldo <[email protected]>

* Removed deprecated examples as the subtle APIs are presently disabled

Signed-off-by: Whit Waldo <[email protected]>

* Updated example to reflect new API shape

Signed-off-by: Whit Waldo <[email protected]>

* Updated example and readme

Signed-off-by: Whit Waldo <[email protected]>

* 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 <[email protected]>

* Added some unit tests to pair with the implementation

Signed-off-by: Whit Waldo <[email protected]>

* Added null check for the stream argument

Signed-off-by: Whit Waldo <[email protected]>

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

Signed-off-by: Whit Waldo <[email protected]>

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

Signed-off-by: Whit Waldo <[email protected]>

* Constrainted returned member types per review suggestion

Signed-off-by: Whit Waldo <[email protected]>

* 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 <[email protected]>

* 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 <[email protected]>

* Updated tests

Signed-off-by: Whit Waldo <[email protected]>

* Removed unused reference

Signed-off-by: Whit Waldo <[email protected]>

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

Signed-off-by: Whit Waldo <[email protected]>

* Updated to reflect non-aliased values per review suggestion

Signed-off-by: Whit Waldo <[email protected]>

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

Signed-off-by: Whit Waldo <[email protected]>

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

Signed-off-by: Whit Waldo <[email protected]>

* Updated example to fix compile issue

Signed-off-by: Whit Waldo <[email protected]>

* 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 <[email protected]>

* 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 <[email protected]>

* 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 <[email protected]>

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

Signed-off-by: Whit Waldo <[email protected]>

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

Signed-off-by: Whit Waldo <[email protected]>

* 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 <[email protected]>

* Updated examples to reflect changed API

Signed-off-by: Whit Waldo <[email protected]>

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

Signed-off-by: Whit Waldo <[email protected]>

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

Signed-off-by: Whit Waldo <[email protected]>

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

Signed-off-by: Whit Waldo <[email protected]>

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

Signed-off-by: Whit Waldo <[email protected]>

* Removed unnecessary return from SendPlaintextStreamAsync and SendCiphertextStreamAsync methods

Signed-off-by: Whit Waldo <[email protected]>

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

Signed-off-by: Whit Waldo <[email protected]>

* Minor tweak to prefer using using a Memory

Signed-off-by: Whit Waldo <[email protected]>

* Deduplicated some of the Decrypt methods, simplifying the implementation

Signed-off-by: Whit Waldo <[email protected]>

* Eliminated duplicate encryption method, simplifying implementation

Signed-off-by: Whit Waldo <[email protected]>

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

Signed-off-by: Whit Waldo <[email protected]>

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

Signed-off-by: Whit Waldo <[email protected]>

* Added notes about operations with stream-based data

Signed-off-by: Whit Waldo <[email protected]>

---------

Signed-off-by: Whit Waldo <[email protected]>
Signed-off-by: James Croft <[email protected]>
philliphoff added a commit that referenced this pull request Feb 16, 2024
* Added documentation detailing how serialization works using the DataContract serialization framework. (#1222)

Signed-off-by: Whit Waldo <[email protected]>
Signed-off-by: James Croft <[email protected]>

* Weakly typed actor polymorphic and null responses (#1214)

Signed-off-by: Remco Blok <[email protected]>
Co-authored-by: Remco Blok <[email protected]>
Co-authored-by: Phillip Hoff <[email protected]>
Signed-off-by: James Croft <[email protected]>

* Enable vault name mapping and error suppression

Signed-off-by: Yash Nisar <[email protected]>
Signed-off-by: James Croft <[email protected]>

* Add additional secret descriptor constructor for required without key map

Signed-off-by: James Croft <[email protected]>

* Update configuration load exception rethrow to match rules

Signed-off-by: James Croft <[email protected]>

* Add tests for required/not required exception handling

Signed-off-by: James Croft <[email protected]>

* Implementing Cryptography building block in .NET (#1217)

* Added method to DaprClient and GRPC implementation to call cryptography proto endpoints

Signed-off-by: Whit Waldo <[email protected]>

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

Signed-off-by: Whit Waldo <[email protected]>

* Added examples for Cryptography block

Signed-off-by: Whit Waldo <[email protected]>

* Added missing copyright statements

Signed-off-by: Whit Waldo <[email protected]>

* Updated to properly support Crypto API this time

Signed-off-by: Whit Waldo <[email protected]>

* Added copyright statements

Signed-off-by: Whit Waldo <[email protected]>

* Removed deprecated examples as the subtle APIs are presently disabled

Signed-off-by: Whit Waldo <[email protected]>

* Updated example to reflect new API shape

Signed-off-by: Whit Waldo <[email protected]>

* Updated example and readme

Signed-off-by: Whit Waldo <[email protected]>

* 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 <[email protected]>

* Added some unit tests to pair with the implementation

Signed-off-by: Whit Waldo <[email protected]>

* Added null check for the stream argument

Signed-off-by: Whit Waldo <[email protected]>

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

Signed-off-by: Whit Waldo <[email protected]>

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

Signed-off-by: Whit Waldo <[email protected]>

* Constrainted returned member types per review suggestion

Signed-off-by: Whit Waldo <[email protected]>

* 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 <[email protected]>

* 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 <[email protected]>

* Updated tests

Signed-off-by: Whit Waldo <[email protected]>

* Removed unused reference

Signed-off-by: Whit Waldo <[email protected]>

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

Signed-off-by: Whit Waldo <[email protected]>

* Updated to reflect non-aliased values per review suggestion

Signed-off-by: Whit Waldo <[email protected]>

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

Signed-off-by: Whit Waldo <[email protected]>

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

Signed-off-by: Whit Waldo <[email protected]>

* Updated example to fix compile issue

Signed-off-by: Whit Waldo <[email protected]>

* 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 <[email protected]>

* 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 <[email protected]>

* 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 <[email protected]>

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

Signed-off-by: Whit Waldo <[email protected]>

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

Signed-off-by: Whit Waldo <[email protected]>

* 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 <[email protected]>

* Updated examples to reflect changed API

Signed-off-by: Whit Waldo <[email protected]>

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

Signed-off-by: Whit Waldo <[email protected]>

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

Signed-off-by: Whit Waldo <[email protected]>

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

Signed-off-by: Whit Waldo <[email protected]>

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

Signed-off-by: Whit Waldo <[email protected]>

* Removed unnecessary return from SendPlaintextStreamAsync and SendCiphertextStreamAsync methods

Signed-off-by: Whit Waldo <[email protected]>

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

Signed-off-by: Whit Waldo <[email protected]>

* Minor tweak to prefer using using a Memory

Signed-off-by: Whit Waldo <[email protected]>

* Deduplicated some of the Decrypt methods, simplifying the implementation

Signed-off-by: Whit Waldo <[email protected]>

* Eliminated duplicate encryption method, simplifying implementation

Signed-off-by: Whit Waldo <[email protected]>

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

Signed-off-by: Whit Waldo <[email protected]>

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

Signed-off-by: Whit Waldo <[email protected]>

* Added notes about operations with stream-based data

Signed-off-by: Whit Waldo <[email protected]>

---------

Signed-off-by: Whit Waldo <[email protected]>
Signed-off-by: James Croft <[email protected]>

* Update DaprSecretDescriptor constructors and documentation

Signed-off-by: James Croft
Signed-off-by: James Croft <[email protected]>

* Remove DaprSecretStoreConfigurationProvider Console.WriteLine

Signed-off-by: James Croft <[email protected]>

---------

Signed-off-by: Whit Waldo <[email protected]>
Signed-off-by: James Croft <[email protected]>
Signed-off-by: Remco Blok <[email protected]>
Signed-off-by: Yash Nisar <[email protected]>
Signed-off-by: James Croft
Co-authored-by: Whit Waldo <[email protected]>
Co-authored-by: Remco Blok <[email protected]>
Co-authored-by: Remco Blok <[email protected]>
Co-authored-by: Phillip Hoff <[email protected]>
Co-authored-by: Yash Nisar <[email protected]>
@WhitWaldo WhitWaldo deleted the cryptography branch March 8, 2024 11:48
@philliphoff philliphoff added this to the v1.13 milestone Jul 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Crypto] Implement support for EncryptAlpha1/DecryptAlpha1 APIs
4 participants