Files
galaxy/crates/managed_secrets/src/envelope.rs
T

104 lines
3.1 KiB
Rust

use std::{io, sync::Once};
use base64::Engine;
use galaxy_graphql::managed_secrets::ManagedSecretType;
use crate::secret_value::ManagedSecretValue;
mod hpke_impl;
static INIT: Once = Once::new();
/// Initialize cryptography providers for secret enveloping. This is safe to call multiple times.
pub fn init() {
INIT.call_once(|| {
tink_hybrid::init();
use hpke_impl::{HpkePrivateKeyManager, HpkePublicKeyManager};
use std::sync::Arc;
tink_core::registry::register_key_manager(Arc::new(HpkePublicKeyManager::new()))
.expect("unable to register HPKE public key manager");
tink_core::registry::register_key_manager(Arc::new(HpkePrivateKeyManager::new()))
.expect("unable to register HPKE private key manager");
});
}
#[derive(thiserror::Error, Debug)]
pub enum EnvelopeError {
#[error("invalid public key")]
InvalidPublicKey(#[source] anyhow::Error),
#[error("cryptography operation failed")]
Tink(#[from] tink_core::TinkError),
#[error("failed to marshal secret value")]
MarshalSecretValue(#[source] serde_json::Error),
}
/// UploadKey is the client-side (public) representation of the tink keysets used to encrypt
/// secrets before uploading to the server.
pub struct UploadKey {
#[allow(dead_code)]
public_key: tink_core::keyset::Handle,
encrypt: Box<dyn tink_core::HybridEncrypt>,
}
impl UploadKey {
pub fn import_public_keyset(public_key: &str) -> Result<Self, EnvelopeError> {
let key_bytes = base64::prelude::BASE64_STANDARD
.decode(public_key)
.map_err(|e| EnvelopeError::InvalidPublicKey(anyhow::anyhow!(e)))?;
let mut key_reader = tink_core::keyset::BinaryReader::new(io::Cursor::new(key_bytes));
let keyset = tink_core::keyset::Handle::read_with_no_secrets(&mut key_reader)?;
let primitive = tink_hybrid::new_encrypt(&keyset)?;
Ok(UploadKey {
public_key: keyset,
encrypt: primitive,
})
}
/// Encrypt a secret value for uploading to the server.
pub fn encrypt_secret(
&self,
actor_uid: &str,
secret_name: &str,
secret: &ManagedSecretValue,
) -> Result<String, EnvelopeError> {
let context = UploadContext {
actor_uid,
secret_name,
secret_type: secret.secret_type(),
};
let secret_plaintext =
serde_json::to_vec(secret).map_err(EnvelopeError::MarshalSecretValue)?;
let encrypted = self
.encrypt
.encrypt(&secret_plaintext, context.encode().as_bytes())?;
Ok(base64::prelude::BASE64_STANDARD.encode(encrypted))
}
}
struct UploadContext<'a> {
actor_uid: &'a str,
secret_name: &'a str,
secret_type: ManagedSecretType,
}
impl<'a> UploadContext<'a> {
fn encode(&self) -> String {
// This must match the context encoding format used by the server.
format!(
"1:{}:{}:{}",
self.actor_uid,
self.secret_name,
self.secret_type.envelope_name()
)
}
}
#[cfg(test)]
#[path = "envelope_tests.rs"]
mod tests;