dapr/rust-sdk

Implement Cryptography API

Closed this issue · 7 comments

Describe the feature/proposal

Implement access to the cryptography API which will include the methods:

  • Encrypt
  • Decrypt

Reference:

Release Note

RELEASE NOTE: ADD Cryptography API support to the client

@mikeee Its a pretty basic example but I am not getting back the decode response.

use tonic::{Request, Status};
use tonic::codegen::tokio_stream;

use crate::Client;
use crate::client::TonicClient;
use crate::dapr::dapr::proto::common::v1::StreamPayload;
use crate::dapr::dapr::proto::runtime::v1::{
    DecryptRequest, DecryptRequestOptions, EncryptRequest, EncryptRequestOptions,
};

impl Client<TonicClient> {
    pub async fn encrypt(
        &mut self,
        payload: String,
        request_options: EncryptRequestOptions,
    ) -> Result<StreamPayload, Status> {
        let stream_payload = StreamPayload {
            data: payload.as_bytes().to_vec(),
            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?;
        Ok(stream
            .into_inner()
            .message()
            .await?
            .unwrap()
            .payload
            .unwrap())
    }

    pub async fn decrypt<T>(
        &mut self,
        encrypted: T,
        options: DecryptRequestOptions,
    ) -> Result<Vec<u8>, Status>
    where
        T: Into<Vec<u8>>,
    {
        let payload = StreamPayload {
            data: encrypted.into(),
            seq: 0,
        };
        let request = DecryptRequest {
            options: Some(options),
            payload: Some(payload),
        };
        let request = Request::new(tokio_stream::iter([request]));
        let stream = self.0.decrypt_alpha1(request).await?;
        Ok(stream
            .into_inner()
            .message()
            .await?
            .unwrap()
            .payload
            .unwrap()
            .data)
    }
}

Sample output:

== APP ==    Compiling dapr v0.14.0 (/home/zachary/Documents/code/dapr/rust-sdk)
== APP ==     Finished dev [unoptimized + debuginfo] target(s) in 4.70s
== APP ==      Running `target/debug/examples/crypto`
== APP == dapr.io/enc/v1
== APP == {"k":"rsa-private-key.pem","kw":5,"wfk":"nmfaImoD16Z6QcCz3etHGvnEb9NoO2VC/mk0gd57ovB39J/I74W+Eq7gcvFMKAWvSGHrfPK2iFWhJZKHDjx1OB8vXQpHp3Dg8IYaCOsCdM2+aPuh5yf9ta4xmHlM9VJ0Fr2Kk89sbIIVU1IBh8NvPFqryjm48RmjuCNWGREFBHAXj+kc3+s3g9ZeOsIubjtXKg4htszMkTRwIC9+j2dvEehJB13qENjD5gn1290HSzXE+Dk3LvFXPhOiDXraOitAZouOVfErQEyeg8nuOMe3C/dFWR8wsphCfVJrFnddiRO+RpOcZ3vucHyuj4Vx6LXIQgCcHoH/ZpSRlrCmqqU7/cTi9SZlUcc/NtlDWAFJ03+PTyDqUdoQxcJa8Ka3C3hoKpC8k4aNbwaVyhNQA2ncAW9zVraKF/PJhOFsMFoETgoGDllgIbbCoC3Mh2J0vEqM8LYZxeRwItPKNfPrupQdJGNn5++FEAvs5tB4Np5DRszfjb5R47JSSMOmvRBTEicfna2yPgOi775jzKLneg6Cju4suuHRr1gbmI0OceoPTszBYmYCzHC8WMlnRVKHdo/7neq1gO68UQwOTfdTE/+U8ooNumpDrggTscSCo4F27PQTV9W63PCZof1+R88Eoor1ux1r9CdWOJstwXGryYuEE3zpfxHqFuF6vBAhHFg9fLw=","cph":1,"np":"WABh99CjPg=="}
== APP == 93ooYd7SK5a4OhkJkkVgFi5qF3V6QsTOtsPY5/rrDCw=
== APP == 
== APP == thread 'main' panicked at /home/zachary/Documents/code/dapr/rust-sdk/src/crypto.rs:58:14:
== APP == called `Option::unwrap()` on a `None` value
== APP == note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Sample code

use tokio::time::sleep;

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

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // TODO: Handle this issue in the sdk
    // Introduce delay so that dapr grpc port is assigned before app tries to connect
    sleep(std::time::Duration::new(2, 0)).await;

    // Get the Dapr port and create a connection
    let port: u16 = std::env::var("DAPR_GRPC_PORT")?.parse()?;
    let addr = format!("https://127.0.0.1:{}", port);

    // Create the client
    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();

    println!("{}", String::from_utf8(encrypted.clone().data).unwrap());

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

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

    Ok(())
}

@zedgell I believe you'll need to implement bidirectional streams in the client methods which you can then use to send/receive and await responses.

I have done that base off the tonic example here

use futures::StreamExt;
use tonic::codegen::tokio_stream;
use tonic::{Request, Status};

use crate::client::TonicClient;
use crate::dapr::dapr::proto::common::v1::StreamPayload;
use crate::dapr::dapr::proto::runtime::v1::{
    DecryptRequest, DecryptRequestOptions, EncryptRequest, EncryptRequestOptions,
};
use crate::Client;

impl Client<TonicClient> {
    pub async fn encrypt(
        &mut self,
        payload: String,
        request_options: EncryptRequestOptions,
    ) -> Result<StreamPayload, Status> {
        let stream_payload = StreamPayload {
            data: payload.as_bytes().to_vec(),
            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?;
        Ok(stream
            .into_inner()
            .take(1)
            .next()
            .await
            .unwrap()
            .unwrap()
            .payload
            .unwrap())
    }

    pub async fn decrypt<T>(
        &mut self,
        encrypted: T,
        options: DecryptRequestOptions,
    ) -> Result<Vec<u8>, Status>
    where
        T: Into<Vec<u8>>,
    {
        let payload = StreamPayload {
            data: encrypted.into(),
            seq: 0,
        };
        let request = DecryptRequest {
            options: Some(options),
            payload: Some(payload),
        };
        let request = Request::new(tokio_stream::iter([request]));
        let stream = self.0.decrypt_alpha1(request).await?;
        Ok(stream
            .into_inner()
            .take(1)
            .next()
            .await
            .unwrap()
            .unwrap()
            .payload
            .unwrap()
            .data)
    }
}

This block client.rs#L34 is similar to what the methods should resemble

@mikeee that is pretty much what I am doing in the second example using take but I am just awaiting one response instead of an infinite amount even if I implement while Some loop it will not return anything since next is None. I have to be just overlooking something!

@mikeee scratch the above. I see what I got wrong. I will get that a PR made come morning.

Great stuff, have a great evening!