Mirror of https://github.com/roostorg/osprey
github.com/roostorg/osprey
1use anyhow::{anyhow, Result};
2use base64::engine::Engine as _;
3use prost::Message;
4use tink_aead::subtle::AesGcm;
5use tink_core::Aead;
6use tonic::codegen::InterceptedService;
7
8use crate::gcloud::auth::AuthorizationHeaderInterceptor;
9use crate::gcloud::google::crypto::tink::AesGcmKey;
10use crate::gcloud::google::kms::v1 as proto;
11use crate::gcloud::google::kms::v1::key_management_service_client::KeyManagementServiceClient;
12use crate::gcloud::grpc::connection::Connection;
13
14pub const GOOGLE_KMS_DOMAIN: &str = "cloudkms.googleapis.com";
15
16const GCP_PREFIX: &str = "gcp-kms://";
17
18impl Connection {
19 pub fn create_kms_aes_gcm_envelope(
20 &self,
21 key_uri: String,
22 associated_data: Vec<u8>,
23 base64_encoded: bool,
24 ) -> Result<AesGcmEnvelope> {
25 let key_uri = key_uri
26 .strip_prefix(GCP_PREFIX)
27 .ok_or_else(|| anyhow!("key uri must start with {}", GCP_PREFIX))?;
28 Ok(AesGcmEnvelope {
29 kms_client: self.create_kms_client(),
30 key_uri: key_uri.into(),
31 associated_data,
32 base64_encoded,
33 })
34 }
35
36 fn create_kms_client(
37 &self,
38 ) -> KeyManagementServiceClient<
39 InterceptedService<tonic::transport::Channel, AuthorizationHeaderInterceptor>,
40 > {
41 KeyManagementServiceClient::with_interceptor(
42 self.channel.clone(),
43 self.authorization_header_interceptor.clone(),
44 )
45 }
46}
47
48/// Reimplements some logic from tink-rust (https://github.com/project-oak/tink-rust) because the library doesn't
49/// work with async code.
50pub struct AesGcmEnvelope {
51 kms_client: KeyManagementServiceClient<
52 InterceptedService<tonic::transport::Channel, AuthorizationHeaderInterceptor>,
53 >,
54 key_uri: String,
55 associated_data: Vec<u8>,
56 base64_encoded: bool,
57}
58
59impl AesGcmEnvelope {
60 // TODO: implement encrypt
61
62 pub async fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>> {
63 let ciphertext_decoded = if self.base64_encoded {
64 Some(base64::engine::general_purpose::STANDARD.decode(ciphertext)?)
65 } else {
66 None
67 };
68
69 let ciphertext = ciphertext_decoded
70 .as_ref()
71 .map(Vec::as_slice)
72 .unwrap_or(ciphertext);
73
74 let enc_dek_len = u32::from_be_bytes(ciphertext[..4].try_into()?) as usize;
75 let ciphertext = &ciphertext[4..];
76
77 let encrypted_dek = &ciphertext[..enc_dek_len];
78 let payload = &ciphertext[enc_dek_len..];
79
80 let decrypt_req = proto::DecryptRequest {
81 name: self.key_uri.clone(),
82 ciphertext: encrypted_dek.to_vec(),
83 ..Default::default()
84 };
85
86 let mut client_clone = self.kms_client.clone();
87
88 let decrypt_res = client_clone.decrypt(decrypt_req).await?.into_inner();
89
90 let data_encryption_key = AesGcmKey::decode(decrypt_res.plaintext.as_slice())?;
91 let aes_gcm = AesGcm::new(&data_encryption_key.key_value)
92 .map_err(|e| anyhow!("failed initializing AesGcm key object: {:#?}", e))?;
93
94 Ok(aes_gcm
95 .decrypt(payload, &self.associated_data)
96 .map_err(|e| anyhow!("failed decrypting payload: {:#?}", e))?)
97 }
98}