use base64::Engine;
use zeroize::Zeroizing;
use crate::grant::{RedeemedGrant, WrappingKey};
use crate::operation::ActType;
use crate::phases::setup::seal_ad;
use crate::primitives::{Aead, Csprng, Kdf, Kem, KeyWrap, PrimitiveSuite, WrapBinding};
use crate::state::{ProtectedState, SealedCredential, SealedState};
use crate::Result;
pub fn open<S: PrimitiveSuite>(
redeemed: &RedeemedGrant,
sealed: &SealedState,
) -> Result<OpenedState> {
let entry = sealed
.find_credential(&redeemed.credential_id)
.ok_or(crate::Error::UnknownCredential)?;
let binding = WrapBinding {
credential_id: &redeemed.credential_id,
version: sealed.version,
};
let k_bytes = S::Wrap::unwrap(
redeemed.wrapping_key.as_bytes(),
&entry.wrapped_key,
&binding,
)
.map_err(|_| crate::Error::SealDecryptionFailed)?;
if k_bytes.len() != S::Aead::KEY_LEN {
return Err(crate::Error::SealDecryptionFailed);
}
let k = Zeroizing::new(k_bytes);
let m_bytes = S::Aead::open(&k[..], &sealed.ciphertext, &seal_ad(sealed.version))?;
let m = ProtectedState::from_canonical(&m_bytes)?;
Ok(OpenedState { k, m })
}
pub struct OpenedState {
pub k: Zeroizing<Vec<u8>>,
pub m: ProtectedState,
}
pub fn execute_use<S, F, R>(redeemed: RedeemedGrant, sealed: &SealedState, handler: F) -> Result<R>
where
S: PrimitiveSuite,
F: FnOnce(&str, &[u8]) -> Result<R>,
{
if redeemed.o.act.kind != ActType::Use {
return Err(crate::Error::ActTypeMismatch("expected ActType::Use"));
}
let opened = open::<S>(&redeemed, sealed)?;
let s_o = opened.m.target(&redeemed.o.act.target)?;
handler(&redeemed.o.act.target, s_o)
}
#[derive(Debug, Clone)]
pub struct ExportArtifact {
pub encapsulated_key: Vec<u8>,
pub sealed_payload: Vec<u8>,
}
pub fn execute_export<S, F>(
redeemed: RedeemedGrant,
sealed: &SealedState,
seal_for_recipient: F,
) -> Result<ExportArtifact>
where
S: PrimitiveSuite,
F: FnOnce(&[u8; 32], &[u8]) -> Result<ExportArtifact>,
{
if redeemed.o.act.kind != ActType::Export {
return Err(crate::Error::ActTypeMismatch("expected ActType::Export"));
}
if redeemed.o.bind.recipient.is_none() {
return Err(crate::Error::MissingRecipient);
}
let opened = open::<S>(&redeemed, sealed)?;
let s_o = opened.m.target(&redeemed.o.act.target)?;
let op_canonical = redeemed.o.canonical_bytes()?;
let op_hash = <S::Hash as crate::primitives::Hash>::hash(&op_canonical);
seal_for_recipient(&op_hash, s_o)
}
pub fn seal_export<S: PrimitiveSuite, K: Kem>(
recipient_pk: &K::PublicKey,
op_hash: &[u8; 32],
s_o: &[u8],
) -> Result<ExportArtifact> {
let (k_d_raw, ct_d) =
K::encap(recipient_pk).map_err(|_| crate::Error::Primitive("KEM encap failed"))?;
let mut k_d = Zeroizing::new(vec![0u8; S::Aead::KEY_LEN]);
S::Kdf::derive(&k_d_raw, &[], op_hash, &mut k_d)?;
let payload = S::Aead::seal(&k_d, s_o, op_hash)?;
Ok(ExportArtifact {
encapsulated_key: ct_d,
sealed_payload: payload,
})
}
pub fn open_export<S: PrimitiveSuite, K: Kem>(
recipient_sk: &K::SecretKey,
op_hash: &[u8; 32],
artifact: &ExportArtifact,
) -> Result<Vec<u8>> {
let k_d_raw = K::decap(recipient_sk, &artifact.encapsulated_key)
.map_err(|_| crate::Error::Primitive("KEM decap failed"))?;
let mut k_d = Zeroizing::new(vec![0u8; S::Aead::KEY_LEN]);
S::Kdf::derive(&k_d_raw, &[], op_hash, &mut k_d)?;
S::Aead::open(&k_d, &artifact.sealed_payload, op_hash)
}
pub type Mutation = dyn FnOnce(&mut ProtectedState) -> Result<()>;
pub struct LifecycleOutput {
pub sealed_state: SealedState,
pub k_prime: Zeroizing<Vec<u8>>,
}
pub fn execute_lifecycle<S: PrimitiveSuite>(
redeemed: RedeemedGrant,
sealed: &SealedState,
next_prf_salt: &[u8],
mutation: Box<Mutation>,
) -> Result<LifecycleOutput> {
if !redeemed.o.act.kind.is_rotation_class() {
return Err(crate::Error::ActTypeMismatch(
"expected Write|Rotate|Enroll|Revoke",
));
}
let w_next = redeemed
.opt
.wrapping_key_next
.as_ref()
.ok_or(crate::Error::MissingRotationKey)?;
let mut opened = open::<S>(&redeemed, sealed)?;
mutation(&mut opened.m)?;
let k_prime = Zeroizing::new(S::Csprng::random_32().to_vec());
let acting_cid_b64 = base64::engine::general_purpose::STANDARD.encode(&redeemed.credential_id);
let mut new_credentials = Vec::with_capacity(sealed.credentials.len());
for cred in &sealed.credentials {
if cred.credential_id == redeemed.credential_id {
let binding = WrapBinding {
credential_id: &cred.credential_id,
version: sealed.version,
};
let wrapped = S::Wrap::wrap(w_next.as_bytes(), &k_prime[..], &binding)?;
new_credentials.push(SealedCredential {
credential_id: cred.credential_id.clone(),
prf_salt: next_prf_salt.to_vec(),
wrapped_key: wrapped,
});
opened
.m
.peers
.insert(acting_cid_b64.clone(), w_next.clone());
} else {
let cid_b64 = base64::engine::general_purpose::STANDARD.encode(&cred.credential_id);
let Some(w_c) = opened.m.peers.get(&cid_b64) else {
continue;
};
let binding = WrapBinding {
credential_id: &cred.credential_id,
version: sealed.version,
};
let wrapped = S::Wrap::wrap(w_c.as_bytes(), &k_prime[..], &binding)?;
new_credentials.push(SealedCredential {
credential_id: cred.credential_id.clone(),
prf_salt: cred.prf_salt.clone(),
wrapped_key: wrapped,
});
}
}
let m_prime_bytes = opened.m.to_canonical()?;
let nonce = S::Aead::fresh_nonce();
let mut ciphertext = Vec::with_capacity(nonce.len() + m_prime_bytes.len() + S::Aead::TAG_LEN);
ciphertext.extend_from_slice(&nonce);
let mut ct = S::Aead::encrypt(
&k_prime[..],
&nonce,
&m_prime_bytes,
&seal_ad(sealed.version),
)?;
ciphertext.append(&mut ct);
let sealed_state = SealedState {
version: sealed.version,
registry: sealed.registry.clone(),
credentials: new_credentials,
ciphertext,
};
Ok(LifecycleOutput {
sealed_state,
k_prime,
})
}
pub fn add_credential_after_lifecycle<S: PrimitiveSuite, A: crate::primitives::Authenticator>(
mut state: SealedState,
new_credential_id: Vec<u8>,
new_public_key: A::PublicKey,
new_prf_salt: Vec<u8>,
new_wrapping_key: WrappingKey,
k_prime: &Zeroizing<Vec<u8>>,
) -> Result<SealedState> {
state
.registry
.insert::<A>(&new_credential_id, &new_public_key)?;
let binding = WrapBinding {
credential_id: &new_credential_id,
version: state.version,
};
let wrapped = S::Wrap::wrap(new_wrapping_key.as_bytes(), &k_prime[..], &binding)?;
state.credentials.push(SealedCredential {
credential_id: new_credential_id,
prf_salt: new_prf_salt,
wrapped_key: wrapped,
});
Ok(state)
}
pub fn remove_credential_after_lifecycle(
mut state: SealedState,
removed_credential_id: &[u8],
) -> SealedState {
state.registry.remove(removed_credential_id);
state
.credentials
.retain(|c| c.credential_id != removed_credential_id);
state
}