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

Implement Cryptography API #144

Merged
merged 19 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/validate-examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ on:
required: false
default: ""
repository_dispatch:
types: [validate-examples]
types: [ validate-examples ]
merge_group:
jobs:
setup:
Expand Down Expand Up @@ -144,7 +144,7 @@ jobs:
fail-fast: false
matrix:
examples:
["actors", "client", "configuration", "invoke/grpc", "invoke/grpc-proxying", "pubsub", "secrets-bulk"]
[ "actors", "client", "configuration", "invoke/grpc", "invoke/grpc-proxying", "pubsub", "secrets-bulk", "crypto" ]
steps:
- name: Check out code
uses: actions/checkout@v4
Expand Down
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,7 @@ path = "examples/pubsub/subscriber.rs"
[[example]]
name = "secrets-bulk"
path = "examples/secrets-bulk/app.rs"

[[example]]
name = "crypto"
path = "examples/crypto/main.rs"
43 changes: 43 additions & 0 deletions examples/crypto/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Crypto Example

This is a simple example that demonstrates Dapr's Cryptography capabilities.

> **Note:** Make sure to use latest version of proto bindings.

## Running

> Before you run the example make sure generate keys in examples/crypto/keys directory:
> ```
> mkdir -p keys
> # Generate a private RSA key, 4096-bit keys
> openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out keys/rsa-private-key.pem
> # Generate a 256-bit key for AES
> openssl rand -out keys/symmetric-key-256 32
> ```

To run this example:

1. Run the multi-app run template:

<!-- STEP
name: Run Subscriber
output_match_mode: substring
match_order: none
expected_stdout_lines:
- '== APP - crypto-example == Successfully Decrypted String'
- '== APP - crypto-example == Successfully Decrypted Image'
background: true
sleep: 30
timeout_seconds: 90
-->

```bash
mkdir -p keys
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out keys/rsa-private-key.pem
openssl rand -out keys/symmetric-key-256 32
dapr run -f .
```

<!-- END_STEP -->

2. Stop with `ctrl + c`
11 changes: 11 additions & 0 deletions examples/crypto/components/local-storage.yml
Original file line number Diff line number Diff line change
@@ -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
10 changes: 10 additions & 0 deletions examples/crypto/dapr.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: 1
common:
daprdLogDestination: console
apps:
- appID: crypto-example
appDirPath: ./
daprGRPCPort: 35002
logLevel: debug
command: [ "cargo", "run", "--example", "crypto" ]
resourcesPath: ./components
Binary file added examples/crypto/image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
78 changes: 78 additions & 0 deletions examples/crypto/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use std::fs;

use tokio::time::sleep;

use dapr::dapr::dapr::proto::runtime::v1::{DecryptRequestOptions, EncryptRequestOptions};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
sleep(std::time::Duration::new(2, 0)).await;
let port: u16 = std::env::var("DAPR_GRPC_PORT")?.parse()?;
let addr = format!("https://127.0.0.1:{}", port);

let mut client = dapr::Client::<dapr::client::TonicClient>::connect(addr).await?;

let encrypted = client
.encrypt(
&"Test".to_string(),
EncryptRequestOptions {
component_name: "localstorage".to_string(),
key_name: "rsa-private-key.pem".to_string(),
key_wrap_algorithm: "RSA".to_string(),
data_encryption_cipher: "aes-gcm".to_string(),
omit_decryption_key_name: false,
decryption_key_name: "rsa-private-key.pem".to_string(),
},
)
.await
.unwrap();

let decrypted = client
.decrypt(
encrypted,
DecryptRequestOptions {
component_name: "localstorage".to_string(),
key_name: "rsa-private-key.pem".to_string(),
},
)
.await
.unwrap();

assert_eq!(String::from_utf8(decrypted).unwrap().as_str(), "Test");

println!("Successfully Decrypted String");

let image = fs::read("./image.png").unwrap();

let encrypted = client
.encrypt(
&image,
EncryptRequestOptions {
component_name: "localstorage".to_string(),
key_name: "rsa-private-key.pem".to_string(),
key_wrap_algorithm: "RSA".to_string(),
data_encryption_cipher: "aes-gcm".to_string(),
omit_decryption_key_name: false,
decryption_key_name: "rsa-private-key.pem".to_string(),
},
)
.await
.unwrap();

let decrypted = client
.decrypt(
encrypted,
DecryptRequestOptions {
component_name: "localstorage".to_string(),
key_name: "rsa-private-key.pem".to_string(),
},
)
.await
.unwrap();

assert_eq!(decrypted, image);

println!("Successfully Decrypted Image");

Ok(())
}
131 changes: 125 additions & 6 deletions src/client.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
use crate::dapr::dapr::proto::{common::v1 as common_v1, runtime::v1 as dapr_v1};
use prost_types::Any;
use std::collections::HashMap;
use tonic::Streaming;
use tonic::{transport::Channel as TonicChannel, Request};

use crate::error::Error;
use async_trait::async_trait;
use futures::StreamExt;
use prost_types::Any;
use serde::{Deserialize, Serialize};
use tonic::{Request, transport::Channel as TonicChannel};
use tonic::{Status, Streaming};
use tonic::codegen::tokio_stream;

use crate::dapr::dapr::proto::{common::v1 as common_v1, runtime::v1 as dapr_v1};
use crate::dapr::dapr::proto::common::v1::StreamPayload;
use crate::dapr::dapr::proto::runtime::v1::{
DecryptRequest, DecryptRequestOptions, EncryptRequest, EncryptRequestOptions,
};
use crate::error::Error;

#[derive(Clone)]
pub struct Client<T>(T);
pub struct Client<T>(pub(crate) T);

impl<T: DaprInterface> Client<T> {
/// Connect to a Dapr enabled app.
Expand Down Expand Up @@ -626,3 +633,115 @@ where
}
}
}

impl Client<TonicClient> {
/// Encrypt binary data using Dapr. returns Vec<StreamPayload> to be used in decrypt method
///
/// # Arguments
///
/// * `payload` - A Reference to the data to encrypt, should impl Into<Vec<u8>>
/// * `request_option` - Encryption request options.
///
/// # Example
/// ```
/// let encrypted = client
/// .encrypt(
/// &"Test".to_string(),
/// EncryptRequestOptions {
/// component_name: "localstorage".to_string(),
/// key_name: "rsa-private-key.pem".to_string(),
/// key_wrap_algorithm: "RSA".to_string(),
/// data_encryption_cipher: "aes-gcm".to_string(),
/// omit_decryption_key_name: false,
/// decryption_key_name: "rsa-private-key.pem".to_string(),
/// },
/// )
/// .await
/// .unwrap();
/// ```
pub async fn encrypt<T>(
&mut self,
payload: &T,
request_options: EncryptRequestOptions,
) -> Result<Vec<StreamPayload>, Status>
where
T: Into<Vec<u8>> + Clone,
{
let stream_payload = StreamPayload {
data: payload.clone().into(),
seq: 0,
};
let request = EncryptRequest {
options: Some(request_options),
payload: Some(stream_payload),
};
let request = Request::new(tokio_stream::iter([request]));
let stream = self.0.encrypt_alpha1(request).await?;
let mut stream = stream.into_inner();
let mut return_data = vec![];
while let Some(resp) = stream.next().await {
if let Ok(resp) = resp {
if let Some(data) = resp.payload {
return_data.push(data)
}
}
}
Ok(return_data)
}

/// Decrypt binary data using Dapr. returns Vec<u8>.
///
/// # Arguments
///
/// * `encrypted` - Encrypted data usually returned from encrypted, Vec<StreamPayload>
/// * `options` - Decryption request options.
///
/// # Example
/// ```
/// let decrypted = client
/// .decrypt(
/// encrypted,
/// DecryptRequestOptions {
/// component_name: "localstorage".to_string(),
/// key_name: "rsa-private-key.pem".to_string(),
/// },
/// )
/// .await
/// .unwrap();
/// ```
pub async fn decrypt(
&mut self,
encrypted: Vec<StreamPayload>,
options: DecryptRequestOptions,
) -> Result<Vec<u8>, Status> {
let requested_items: Vec<DecryptRequest> = encrypted
.iter()
.enumerate()
.map(|(i, item)| {
if i == 0 {
DecryptRequest {
options: Some(options.clone()),
payload: Some(item.clone()),
}
} else {
DecryptRequest {
options: None,
payload: Some(item.clone()),
}
}
})
.collect();
let request = Request::new(tokio_stream::iter(requested_items));
let stream = self.0.decrypt_alpha1(request).await?;
let mut stream = stream.into_inner();
let mut data = vec![];
while let Some(resp) = stream.next().await {
if let Ok(resp) = resp {
if let Some(mut payload) = resp.payload {
data.append(payload.data.as_mut())
}
}
}
Ok(data)
}
}
15 changes: 7 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
pub mod appcallback;
pub mod client;
pub mod dapr;
pub mod error;
pub mod server;
extern crate dapr_macros;

pub use dapr_macros::actor;
pub use serde;

pub use serde_json;

pub use client::Client;

extern crate dapr_macros;
pub use dapr_macros::actor;
pub mod appcallback;
pub mod client;
pub mod dapr;
pub mod error;
pub mod server;
Loading