use core::fmt;
use zeroize::Zeroizing;
use super::keymgmt::RecipientOptions;
use super::random::OsRandom;
use super::{compact, core as pipeline, json};
use crate::header::Header;
#[cfg(feature = "insecure-test-vectors")]
use crate::insecure_test_vectors::FixedRng;
use crate::jwa::{ContentEncAlg, KeyMgmtAlg};
use crate::jwk::Jwk;
use crate::{Error, Result};
#[derive(Debug, Clone)]
pub struct DecryptPolicy {
pub(super) allowed_algs: Vec<KeyMgmtAlg>,
pub(super) allowed_enc: Vec<ContentEncAlg>,
pub(super) max_pbes2_count: u32,
pub(super) max_decompressed_len: usize,
pub(super) understood_crit: Vec<String>,
pub(super) max_input_len: usize,
pub(super) max_recipients: usize,
}
impl DecryptPolicy {
pub const DEFAULT_MAX_PBES2_COUNT: u32 = 10_000;
pub const MAX_PBES2_COUNT_CEILING: u32 = 1_000_000;
pub const DEFAULT_MAX_DECOMPRESSED_LEN: usize = 250_000;
pub const DEFAULT_MAX_INPUT_LEN: usize = 1024 * 1024;
pub const DEFAULT_MAX_RECIPIENTS: usize = 16;
pub fn new(allowed_algs: &[KeyMgmtAlg], allowed_enc: &[ContentEncAlg]) -> Result<Self> {
if allowed_algs.is_empty() || allowed_enc.is_empty() {
return Err(Error::AlgorithmNotAllowed);
}
Ok(Self {
allowed_algs: allowed_algs.to_vec(),
allowed_enc: allowed_enc.to_vec(),
max_pbes2_count: Self::DEFAULT_MAX_PBES2_COUNT,
max_decompressed_len: Self::DEFAULT_MAX_DECOMPRESSED_LEN,
understood_crit: Vec::new(),
max_input_len: Self::DEFAULT_MAX_INPUT_LEN,
max_recipients: Self::DEFAULT_MAX_RECIPIENTS,
})
}
#[must_use]
pub fn max_pbes2_count(mut self, n: u32) -> Self {
self.max_pbes2_count = n.min(Self::MAX_PBES2_COUNT_CEILING);
self
}
#[must_use]
pub fn max_decompressed_len(mut self, n: usize) -> Self {
self.max_decompressed_len = n;
self
}
#[must_use]
pub fn understood_crit(mut self, names: &[&str]) -> Self {
self.understood_crit = names.iter().map(|&name| name.to_owned()).collect();
self
}
#[must_use]
pub fn max_input_len(mut self, n: usize) -> Self {
self.max_input_len = n;
self
}
#[must_use]
pub fn max_recipients(mut self, n: usize) -> Self {
self.max_recipients = n;
self
}
}
pub struct Decrypted {
pub(super) plaintext: Zeroizing<Vec<u8>>,
pub(super) protected_header: Option<Header>,
pub(super) shared_header: Option<Header>,
pub(super) recipient_header: Option<Header>,
pub(super) header: Header,
pub(super) aad: Option<Zeroizing<Vec<u8>>>,
pub(super) recipient: usize,
}
impl Decrypted {
pub fn plaintext(&self) -> &[u8] {
&self.plaintext
}
pub fn into_plaintext(self) -> Zeroizing<Vec<u8>> {
self.plaintext
}
pub fn protected_header(&self) -> Option<&Header> {
self.protected_header.as_ref()
}
pub fn shared_header(&self) -> Option<&Header> {
self.shared_header.as_ref()
}
pub fn recipient_header(&self) -> Option<&Header> {
self.recipient_header.as_ref()
}
pub fn header(&self) -> &Header {
&self.header
}
pub fn aad(&self) -> Option<&[u8]> {
self.aad.as_deref().map(Vec::as_slice)
}
pub fn recipient(&self) -> usize {
self.recipient
}
}
impl fmt::Debug for Decrypted {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Decrypted")
.field("plaintext", &"<redacted>")
.field("protected_header", &self.protected_header)
.field("shared_header", &self.shared_header)
.field("recipient_header", &self.recipient_header)
.field("header", &self.header)
.field("aad", &self.aad.as_ref().map(|_| "<redacted>"))
.field("recipient", &self.recipient)
.finish()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum JsonForm {
General,
Flattened,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Serialization {
Compact,
Json(JsonForm),
}
#[derive(Debug, Clone)]
pub struct Recipient {
pub(super) alg: KeyMgmtAlg,
pub(super) key: Jwk,
pub(super) header: Option<Header>,
pub(super) options: RecipientOptions,
}
impl Recipient {
pub fn new(alg: KeyMgmtAlg, key: &Jwk) -> Self {
Self {
alg,
key: key.clone(),
header: None,
options: RecipientOptions::default(),
}
}
#[must_use]
pub fn header(mut self, header: Header) -> Self {
self.header = Some(header);
self
}
#[must_use]
pub fn apu(mut self, apu: impl Into<Vec<u8>>) -> Self {
self.options.apu = Some(apu.into());
self
}
#[must_use]
pub fn apv(mut self, apv: impl Into<Vec<u8>>) -> Self {
self.options.apv = Some(apv.into());
self
}
#[must_use]
pub fn p2c(mut self, p2c: u32) -> Self {
self.options.p2c = Some(p2c);
self
}
#[must_use]
pub fn p2s_len(mut self, len: usize) -> Self {
self.options.p2s_len = Some(len);
self
}
}
#[derive(Clone)]
pub struct Encryptor {
pub(super) enc: ContentEncAlg,
pub(super) protected: Option<Header>,
pub(super) shared: Option<Header>,
pub(super) aad: Option<Zeroizing<Vec<u8>>>,
pub(super) compress: bool,
pub(super) recipients: Vec<Recipient>,
}
impl Encryptor {
pub fn new(enc: ContentEncAlg) -> Self {
Self {
enc,
protected: None,
shared: None,
aad: None,
compress: false,
recipients: Vec::new(),
}
}
#[must_use]
pub fn protected_header(mut self, header: Header) -> Self {
self.protected = Some(header);
self
}
pub(crate) fn protected(&self) -> Option<&Header> {
self.protected.as_ref()
}
pub(crate) fn compresses(&self) -> bool {
self.compress
}
#[must_use]
pub fn shared_unprotected_header(mut self, header: Header) -> Self {
self.shared = Some(header);
self
}
#[must_use]
pub fn aad(mut self, aad: impl Into<Vec<u8>>) -> Self {
self.aad = Some(Zeroizing::new(aad.into()));
self
}
#[must_use]
pub fn compress(mut self) -> Self {
self.compress = true;
self
}
#[must_use]
pub fn recipient(mut self, recipient: Recipient) -> Self {
self.recipients.push(recipient);
self
}
pub fn encrypt_compact(&self, plaintext: &[u8]) -> Result<String> {
let parts =
pipeline::encrypt(self, plaintext, Serialization::Compact, &mut OsRandom, None)?;
compact::serialize(&parts)
}
pub fn encrypt_json(&self, plaintext: &[u8], form: JsonForm) -> Result<String> {
let form_ = Serialization::Json(form);
let parts = pipeline::encrypt(self, plaintext, form_, &mut OsRandom, None)?;
json::serialize(&parts, form)
}
#[cfg(feature = "insecure-test-vectors")]
pub fn encrypt_reproducible(
&self,
plaintext: &[u8],
form: Serialization,
rng: &mut FixedRng,
protected_b64: Option<&str>,
) -> Result<String> {
let parts = pipeline::encrypt(self, plaintext, form, rng, protected_b64)?;
match form {
Serialization::Compact => compact::serialize(&parts),
Serialization::Json(json_form) => json::serialize(&parts, json_form),
}
}
}
impl fmt::Debug for Encryptor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
struct RedactedLen(usize);
impl fmt::Debug for RedactedLen {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "<redacted, len={}>", self.0)
}
}
f.debug_struct("Encryptor")
.field("enc", &self.enc)
.field("recipients", &self.recipients.len())
.field("protected_header", &self.protected.is_some())
.field("shared_header", &self.shared.is_some())
.field("aad", &self.aad.as_ref().map(|aad| RedactedLen(aad.len())))
.field("compress", &self.compress)
.finish()
}
}
pub fn decrypt_compact(token: &str, key: &Jwk, policy: &DecryptPolicy) -> Result<Decrypted> {
let parts = compact::parse(token, policy)?;
pipeline::decrypt(parts, key, policy)
}
pub fn decrypt_json(jwe: &str, key: &Jwk, policy: &DecryptPolicy) -> Result<Decrypted> {
let parts = json::parse(jwe, policy)?;
pipeline::decrypt(parts, key, policy)
}
#[cfg(test)]
mod tests {
use super::*;
fn policy() -> DecryptPolicy {
DecryptPolicy::new(&[KeyMgmtAlg::A128KW], &[ContentEncAlg::A128GCM])
.unwrap_or_else(|e| panic!("{e}"))
}
#[test]
fn empty_allow_lists_are_rejected() {
let alg = [KeyMgmtAlg::A128KW];
let enc = [ContentEncAlg::A128GCM];
let not_allowed = Err(Error::AlgorithmNotAllowed);
assert_eq!(DecryptPolicy::new(&[], &enc).map(|_| ()), not_allowed);
assert_eq!(DecryptPolicy::new(&alg, &[]).map(|_| ()), not_allowed);
assert_eq!(DecryptPolicy::new(&[], &[]).map(|_| ()), not_allowed);
assert!(DecryptPolicy::new(&alg, &enc).is_ok());
}
#[test]
fn defaults_follow_adr_0023_0017_and_0026() {
let p = policy();
assert_eq!(p.allowed_algs, [KeyMgmtAlg::A128KW]);
assert_eq!(p.allowed_enc, [ContentEncAlg::A128GCM]);
assert_eq!(p.max_pbes2_count, 10_000);
assert_eq!(p.max_decompressed_len, 250_000);
assert_eq!(p.max_input_len, 1 << 20);
assert_eq!(p.max_recipients, 16);
assert!(p.understood_crit.is_empty());
}
#[test]
fn max_pbes2_count_clamps_at_the_ceiling() {
assert_eq!(policy().max_pbes2_count(1_000).max_pbes2_count, 1_000);
assert_eq!(policy().max_pbes2_count(0).max_pbes2_count, 0);
let ceiling = DecryptPolicy::MAX_PBES2_COUNT_CEILING;
assert_eq!(ceiling, 1_000_000);
assert_eq!(policy().max_pbes2_count(ceiling).max_pbes2_count, ceiling);
assert_eq!(
policy().max_pbes2_count(ceiling + 1).max_pbes2_count,
ceiling
);
assert_eq!(policy().max_pbes2_count(u32::MAX).max_pbes2_count, ceiling);
}
#[test]
fn builders_set_their_limit() {
let p = policy()
.max_decompressed_len(0)
.max_input_len(10)
.max_recipients(1)
.understood_crit(&["exp"])
.understood_crit(&["a", "b"]);
assert_eq!(p.max_decompressed_len, 0);
assert_eq!(p.max_input_len, 10);
assert_eq!(p.max_recipients, 1);
assert_eq!(p.understood_crit, ["a", "b"]);
}
#[test]
fn unimplemented_algorithms_may_be_listed() {
let algs = [KeyMgmtAlg::from("RSA1_5"), KeyMgmtAlg::RsaOaep256];
let p = DecryptPolicy::new(&algs, &[ContentEncAlg::from("A128CTR")]);
assert!(p.is_ok());
}
fn oct_key() -> Jwk {
Jwk::from_json(br#"{"kty":"oct","k":"GawgguFyGrWKav7AX4VKUg"}"#)
.unwrap_or_else(|e| panic!("{e}"))
}
#[test]
fn builders_record_what_they_are_given() {
let r = Recipient::new(KeyMgmtAlg::EcdhEs, &oct_key())
.apu(b"Alice".to_vec())
.apv(*b"Bob")
.p2c(4096)
.p2s_len(8)
.header(Header::new());
assert_eq!(r.alg, KeyMgmtAlg::EcdhEs);
assert_eq!(r.options.apu.as_deref(), Some(&b"Alice"[..]));
assert_eq!(r.options.apv.as_deref(), Some(&b"Bob"[..]));
assert_eq!((r.options.p2c, r.options.p2s_len), (Some(4096), Some(8)));
assert!(r.header.is_some());
let e = Encryptor::new(ContentEncAlg::A128GCM)
.protected_header(Header::new())
.shared_unprotected_header(Header::new())
.aad(*b"aad")
.recipient(r.clone())
.recipient(r);
assert_eq!(e.enc, ContentEncAlg::A128GCM);
assert!(e.protected.is_some() && e.shared.is_some());
assert_eq!(e.aad.as_deref().map(Vec::as_slice), Some(&b"aad"[..]));
assert_eq!(e.recipients.len(), 2);
assert!(!e.compress);
assert!(e.compress().compress);
}
#[test]
fn encryptor_holds_the_aad_in_a_buffer_wiped_on_drop() {
fn is_zeroizing(_: &Option<Zeroizing<Vec<u8>>>) {}
let e = Encryptor::new(ContentEncAlg::A128GCM).aad(b"MARKERaad".to_vec());
is_zeroizing(&e.aad);
let copy = e.clone();
is_zeroizing(©.aad);
assert_eq!(
copy.aad.as_deref().map(Vec::as_slice),
Some(&b"MARKERaad"[..])
);
let debug = format!("{e:?}");
assert!(!debug.contains("MARKER"), "{debug}");
assert!(debug.contains("aad: Some(<redacted, len=9>)"), "{debug}");
}
#[test]
fn decrypted_redacts_plaintext_and_aad() {
let d = Decrypted {
plaintext: Zeroizing::new(b"MARKERsecret".to_vec()),
protected_header: None,
shared_header: None,
recipient_header: None,
header: Header::new(),
aad: Some(Zeroizing::new(b"MARKERaad".to_vec())),
recipient: 2,
};
let _: &Option<Zeroizing<Vec<u8>>> = &d.aad;
let debug = format!("{d:?}");
assert!(!debug.contains("MARKER"), "{debug}");
assert!(debug.contains("<redacted>"), "{debug}");
assert_eq!(
(d.plaintext(), d.aad(), d.recipient()),
(&b"MARKERsecret"[..], Some(&b"MARKERaad"[..]), 2)
);
assert!(d.protected_header().is_none() && d.shared_header().is_none());
assert!(d.recipient_header().is_none() && d.header().alg.is_none());
assert_eq!(d.into_plaintext().as_slice(), b"MARKERsecret");
}
#[test]
fn compact_round_trips_through_the_public_entry_points() {
let key = oct_key();
let policy = policy();
let e = Encryptor::new(ContentEncAlg::A128GCM)
.recipient(Recipient::new(KeyMgmtAlg::A128KW, &key));
let token = e.encrypt_compact(b"x").unwrap_or_else(|e| panic!("{e}"));
assert_eq!(token.split('.').count(), 5);
let d = decrypt_compact(&token, &key, &policy).unwrap_or_else(|e| panic!("{e}"));
assert_eq!((d.plaintext(), d.recipient()), (&b"x"[..], 0));
assert!(d.shared_header().is_none() && d.recipient_header().is_none());
assert!(d.aad().is_none());
assert_eq!(d.protected_header(), Some(d.header()));
assert!(decrypt_compact("a.b.c.d.e", &key, &policy).is_err());
}
}