use ring::aead::{Aad, LessSafeKey, Nonce, UnboundKey, AES_256_GCM, NONCE_LEN};
use ring::rand::{SecureRandom, SystemRandom};
pub mod provider;
pub mod service;
pub mod trail;
pub mod vault;
pub use provider::{
InMemoryKeyProvider, KeyFileProvider, KeyProvider, KeyVersions, WrappedDataKey,
};
pub use service::{KeyService, StoredWrap};
pub use vault::VaultTransitProvider;
const KEY_LEN: usize = 32;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error(
"sensitive_fields declares a field to encrypt, but no key provider was configured \
(SkiljBuilder::key_provider)"
)]
MasterKeyNotConfigured,
#[error("AEAD decryption failed - corrupted or tampered ciphertext, or the wrong key")]
DecryptFailed,
#[error("key provider {provider} is unavailable: {message}")]
ProviderUnavailable {
provider: String,
message: String,
},
#[error("key provider {provider} refused: {message}")]
ProviderRefused {
provider: String,
message: String,
},
#[error("the master key version {version} this data key is wrapped under is retired")]
KeyVersionRetired {
version: u32,
},
#[error(
"data keys are wrapped by key provider {stored}, but {configured} is configured: \
configure the provider, and the key, that wrapped them"
)]
ProviderChanged {
stored: String,
configured: String,
},
#[error(
"key {provider} unwraps none of {bounded_context}'s newest data keys: it is not the key \
that wrapped them (a key file recreated, another Vault cluster?) - restore that key"
)]
KeyMaterialChanged {
provider: String,
bounded_context: String,
},
#[error(
"data keys wrapped before key providers are stored: configure the master key they were \
wrapped under with SkiljBuilder::legacy_encryption_master_key until they are rewrapped"
)]
LegacyMasterKeyRequired,
#[error(
"the legacy encryption master key unwraps none of {bounded_context}'s newest data keys \
from before key providers: configure the master key they were wrapped under"
)]
LegacyMasterKeyChanged {
bounded_context: String,
},
}
impl crate::error::SkiljRejection for Error {
fn code(&self) -> &str {
match self {
Error::MasterKeyNotConfigured => "encryption_master_key_not_configured",
Error::DecryptFailed => "encryption_decrypt_failed",
Error::ProviderUnavailable { .. } => "encryption_key_provider_unavailable",
Error::ProviderRefused { .. } => "encryption_key_provider_refused",
Error::KeyVersionRetired { .. } => "encryption_key_version_retired",
Error::ProviderChanged { .. } => "encryption_key_provider_changed",
Error::KeyMaterialChanged { .. } => "encryption_key_material_changed",
Error::LegacyMasterKeyRequired => "encryption_legacy_master_key_required",
Error::LegacyMasterKeyChanged { .. } => "encryption_legacy_master_key_changed",
}
}
fn message(&self) -> String {
self.to_string()
}
}
#[derive(Clone)]
pub struct EncryptionMasterKey([u8; KEY_LEN]);
impl EncryptionMasterKey {
pub fn from_bytes(bytes: [u8; KEY_LEN]) -> Self {
Self(bytes)
}
}
impl std::fmt::Debug for EncryptionMasterKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("EncryptionMasterKey(REDACTED)")
}
}
#[derive(Clone)]
pub struct DataKey([u8; KEY_LEN]);
impl DataKey {
pub(crate) fn bytes(&self) -> &[u8; KEY_LEN] {
&self.0
}
}
fn random_bytes<const N: usize>() -> [u8; N] {
let rng = SystemRandom::new();
let mut bytes = [0u8; N];
rng.fill(&mut bytes).expect(
"SystemRandom::fill only fails on catastrophic OS RNG failure - see ring's own docs",
);
bytes
}
fn seal(key_bytes: &[u8; KEY_LEN], nonce_bytes: [u8; NONCE_LEN], mut in_out: Vec<u8>) -> Vec<u8> {
let unbound = UnboundKey::new(&AES_256_GCM, key_bytes)
.expect("a KEY_LEN=32-byte key is always valid for AES_256_GCM");
let key = LessSafeKey::new(unbound);
let nonce = Nonce::assume_unique_for_key(nonce_bytes);
key.seal_in_place_append_tag(nonce, Aad::empty(), &mut in_out)
.expect("sealing a plain byte buffer under a fresh nonce never fails");
in_out
}
fn open(
key_bytes: &[u8; KEY_LEN],
nonce_bytes: [u8; NONCE_LEN],
mut in_out: Vec<u8>,
) -> Result<Vec<u8>, Error> {
let unbound = UnboundKey::new(&AES_256_GCM, key_bytes)
.expect("a KEY_LEN=32-byte key is always valid for AES_256_GCM");
let key = LessSafeKey::new(unbound);
let nonce = Nonce::assume_unique_for_key(nonce_bytes);
let len = key
.open_in_place(nonce, Aad::empty(), &mut in_out)
.map_err(|_| Error::DecryptFailed)?
.len();
in_out.truncate(len);
Ok(in_out)
}
pub fn generate_and_wrap_data_key(master_key: &EncryptionMasterKey) -> (DataKey, Vec<u8>, Vec<u8>) {
let data_key_bytes = random_bytes::<KEY_LEN>();
let nonce_bytes = random_bytes::<NONCE_LEN>();
let wrapped = seal(&master_key.0, nonce_bytes, data_key_bytes.to_vec());
(DataKey(data_key_bytes), wrapped, nonce_bytes.to_vec())
}
pub fn unwrap_data_key(
master_key: &EncryptionMasterKey,
wrapped: &[u8],
nonce: &[u8],
) -> Result<DataKey, Error> {
let nonce_bytes: [u8; NONCE_LEN] = nonce.try_into().map_err(|_| Error::DecryptFailed)?;
let opened = open(&master_key.0, nonce_bytes, wrapped.to_vec())?;
let key_bytes: [u8; KEY_LEN] = opened.try_into().map_err(|_| Error::DecryptFailed)?;
Ok(DataKey(key_bytes))
}
pub fn encrypt_leaf(data_key: &DataKey, plaintext: &str) -> String {
let nonce_bytes = random_bytes::<NONCE_LEN>();
let sealed = seal(&data_key.0, nonce_bytes, plaintext.as_bytes().to_vec());
let mut combined = Vec::with_capacity(NONCE_LEN + sealed.len());
combined.extend_from_slice(&nonce_bytes);
combined.extend_from_slice(&sealed);
crate::base64::encode(&combined)
}
pub fn decrypt_leaf(data_key: &DataKey, ciphertext_b64: &str) -> Result<String, Error> {
let combined = crate::base64::decode(ciphertext_b64).ok_or(Error::DecryptFailed)?;
if combined.len() < NONCE_LEN {
return Err(Error::DecryptFailed);
}
let (nonce_bytes, ciphertext) = combined.split_at(NONCE_LEN);
let nonce_bytes: [u8; NONCE_LEN] = nonce_bytes.try_into().map_err(|_| Error::DecryptFailed)?;
let opened = open(&data_key.0, nonce_bytes, ciphertext.to_vec())?;
String::from_utf8(opened).map_err(|_| Error::DecryptFailed)
}
pub fn decrypt_ciphertext_leaves(value: &mut serde_json::Value, data_keys: &[DataKey]) {
match value {
serde_json::Value::String(s) => {
for data_key in data_keys {
if let Ok(plaintext) = decrypt_leaf(data_key, s) {
*s = plaintext;
return;
}
}
}
serde_json::Value::Array(items) => {
for item in items {
decrypt_ciphertext_leaves(item, data_keys);
}
}
serde_json::Value::Object(fields) => {
for field in fields.values_mut() {
decrypt_ciphertext_leaves(field, data_keys);
}
}
_ => {}
}
}