use std::error::Error;
use std::fmt;
pub const AT_REST_ARTIFACT_FORMAT_VERSION: u16 = 1;
#[derive(Clone, PartialEq, Eq)]
pub struct KeyMaterial {
key_id: String,
secret: Vec<u8>,
}
impl KeyMaterial {
pub fn new(
key_id: impl Into<String>,
secret: impl Into<Vec<u8>>,
) -> Result<Self, SecurityError> {
let key_id = key_id.into();
let secret = secret.into();
if key_id.trim().is_empty() {
return Err(SecurityError::InvalidKeyMaterial("empty key id"));
}
if secret.is_empty() {
return Err(SecurityError::InvalidKeyMaterial("empty key secret"));
}
Ok(Self { key_id, secret })
}
pub fn key_id(&self) -> &str {
&self.key_id
}
}
impl fmt::Debug for KeyMaterial {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("KeyMaterial")
.field("key_id", &self.key_id)
.field("secret_len", &self.secret.len())
.finish_non_exhaustive()
}
}
pub trait AtRestKeyProvider: Send + Sync {
fn current_key(&self) -> Result<KeyMaterial, SecurityError>;
fn key(&self, key_id: &str) -> Result<KeyMaterial, SecurityError>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StaticAtRestKeyProvider {
current: KeyMaterial,
previous: Vec<KeyMaterial>,
}
impl StaticAtRestKeyProvider {
pub fn new(
key_id: impl Into<String>,
secret: impl Into<Vec<u8>>,
) -> Result<Self, SecurityError> {
Ok(Self {
current: KeyMaterial::new(key_id, secret)?,
previous: Vec::new(),
})
}
pub fn with_previous_key(
mut self,
key_id: impl Into<String>,
secret: impl Into<Vec<u8>>,
) -> Result<Self, SecurityError> {
self.previous.push(KeyMaterial::new(key_id, secret)?);
Ok(self)
}
}
impl AtRestKeyProvider for StaticAtRestKeyProvider {
fn current_key(&self) -> Result<KeyMaterial, SecurityError> {
Ok(self.current.clone())
}
fn key(&self, key_id: &str) -> Result<KeyMaterial, SecurityError> {
if self.current.key_id == key_id {
return Ok(self.current.clone());
}
self.previous
.iter()
.find(|key| key.key_id == key_id)
.cloned()
.ok_or_else(|| SecurityError::UnknownKey(key_id.to_owned()))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SealedArtifact {
pub format_version: u16,
pub artifact_kind: String,
pub key_id: String,
pub nonce: u64,
pub plaintext_checksum: u64,
pub ciphertext: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AtRestSealer<P> {
provider: P,
}
impl<P> AtRestSealer<P>
where
P: AtRestKeyProvider,
{
pub fn new(provider: P) -> Self {
Self { provider }
}
pub fn seal(
&self,
artifact_kind: impl Into<String>,
plaintext: &[u8],
) -> Result<SealedArtifact, SecurityError> {
let artifact_kind = artifact_kind.into();
if artifact_kind.trim().is_empty() {
return Err(SecurityError::InvalidArtifactKind);
}
let key = self.provider.current_key()?;
let nonce = checksum_parts(&[artifact_kind.as_bytes(), key.key_id.as_bytes(), plaintext]);
let ciphertext = apply_stream(plaintext, &key.secret, nonce);
Ok(SealedArtifact {
format_version: AT_REST_ARTIFACT_FORMAT_VERSION,
artifact_kind,
key_id: key.key_id,
nonce,
plaintext_checksum: checksum_parts(&[plaintext]),
ciphertext,
})
}
pub fn open(&self, artifact: &SealedArtifact) -> Result<Vec<u8>, SecurityError> {
if artifact.format_version != AT_REST_ARTIFACT_FORMAT_VERSION {
return Err(SecurityError::UnsupportedArtifactFormat(
artifact.format_version,
));
}
if artifact.artifact_kind.trim().is_empty() {
return Err(SecurityError::InvalidArtifactKind);
}
let key = self.provider.key(&artifact.key_id)?;
let plaintext = apply_stream(&artifact.ciphertext, &key.secret, artifact.nonce);
if checksum_parts(&[&plaintext]) != artifact.plaintext_checksum {
return Err(SecurityError::UndecryptableArtifact);
}
Ok(plaintext)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CertificateBundle {
pub cert_id: String,
pub subject: String,
pub not_after_epoch_secs: u64,
}
impl CertificateBundle {
pub fn new(
cert_id: impl Into<String>,
subject: impl Into<String>,
not_after_epoch_secs: u64,
) -> Result<Self, SecurityError> {
let cert_id = cert_id.into();
if cert_id.trim().is_empty() {
return Err(SecurityError::InvalidCertificate("empty certificate id"));
}
Ok(Self {
cert_id,
subject: subject.into(),
not_after_epoch_secs,
})
}
fn is_valid_at(&self, now_epoch_secs: u64) -> bool {
self.not_after_epoch_secs > now_epoch_secs
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CertificateRotationWindow {
current: CertificateBundle,
previous: Vec<CertificateBundle>,
}
impl CertificateRotationWindow {
pub fn new(current: CertificateBundle) -> Self {
Self {
current,
previous: Vec::new(),
}
}
pub fn with_previous(mut self, previous: CertificateBundle) -> Self {
self.previous.push(previous);
self
}
pub fn promote(mut self, current: CertificateBundle) -> Self {
self.previous.push(self.current);
self.current = current;
self
}
pub fn accepts(&self, cert_id: &str, now_epoch_secs: u64) -> bool {
self.current.cert_id == cert_id && self.current.is_valid_at(now_epoch_secs)
|| self
.previous
.iter()
.any(|cert| cert.cert_id == cert_id && cert.is_valid_at(now_epoch_secs))
}
pub fn current_id(&self) -> &str {
&self.current.cert_id
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SecurityError {
InvalidKeyMaterial(&'static str),
InvalidArtifactKind,
UnknownKey(String),
UnsupportedArtifactFormat(u16),
UndecryptableArtifact,
InvalidCertificate(&'static str),
}
impl fmt::Display for SecurityError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidKeyMaterial(reason) => {
write!(formatter, "invalid at-rest key material: {reason}")
}
Self::InvalidArtifactKind => formatter.write_str("artifact kind must be non-empty"),
Self::UnknownKey(key_id) => write!(formatter, "unknown at-rest key id: {key_id}"),
Self::UnsupportedArtifactFormat(version) => {
write!(formatter, "unsupported at-rest artifact format: {version}")
}
Self::UndecryptableArtifact => {
formatter.write_str("sealed artifact could not be opened and verified")
}
Self::InvalidCertificate(reason) => write!(formatter, "invalid certificate: {reason}"),
}
}
}
impl Error for SecurityError {}
fn apply_stream(input: &[u8], secret: &[u8], nonce: u64) -> Vec<u8> {
let nonce = nonce.to_le_bytes();
input
.iter()
.enumerate()
.map(|(index, byte)| {
let secret_byte = secret[index % secret.len()];
let nonce_byte = nonce[index % nonce.len()];
byte ^ secret_byte ^ nonce_byte ^ (index as u8).wrapping_mul(31)
})
.collect()
}
fn checksum_parts(parts: &[&[u8]]) -> u64 {
let mut hash = 0xcbf2_9ce4_8422_2325_u64;
for part in parts {
for byte in *part {
hash ^= u64::from(*byte);
hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
}
hash ^= 0xff;
hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
}
hash
}