use core::fmt;
use p256::elliptic_curve::{Curve, FieldBytes, SecretKey};
use zeroize::{ZeroizeOnDrop, Zeroizing};
use super::content;
use super::keymgmt::{self, Mode};
use crate::jwa::{ContentEncAlg, KeyMgmtAlg};
use crate::jwk::{
EcCurve, EcKey, Jwk, KeyAlg, KeyMaterial, KeyOp, OctKey, OkpCurve, OkpKey, RsaPublicKey,
};
use crate::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Direction {
Encrypt,
Decrypt,
}
pub(crate) enum RecipientKey {
Symmetric(Zeroizing<Vec<u8>>),
Password(Zeroizing<Vec<u8>>),
RsaPublic { n: Vec<u8>, e: Vec<u8> },
P256Public(p256::PublicKey),
P256Secret(p256::SecretKey),
P384Public(p384::PublicKey),
P384Secret(p384::SecretKey),
P521Public(p521::PublicKey),
P521Secret(p521::SecretKey),
X25519Public(x25519_dalek::PublicKey),
X25519Secret(x25519_dalek::StaticSecret),
}
impl RecipientKey {
pub(crate) fn from_jwk(
jwk: &Jwk,
alg: &KeyMgmtAlg,
enc: &ContentEncAlg,
direction: Direction,
) -> Result<Self> {
if !keymgmt::supported(alg, direction) {
return Err(Error::UnsupportedAlgorithm);
}
if *alg == KeyMgmtAlg::Dir {
content::cek_len(enc)?;
}
let bound = jwk.material().serves_key_mgmt_alg(alg)
&& jwk_alg_permits(jwk.alg(), alg, enc)
&& key_op(alg, direction).is_some_and(|op| jwk.permits(&op));
if !bound {
return Err(Error::KeyAlgorithmMismatch);
}
match jwk.material() {
KeyMaterial::Oct(key) => oct_key(key, alg, enc, direction),
KeyMaterial::Ec(key) => ec_key(key, direction),
KeyMaterial::Okp(key) => okp_key(key, direction),
KeyMaterial::Rsa(key) => rsa_key(key, direction),
}
}
}
fn jwk_alg_permits(jwk_alg: Option<&KeyAlg>, alg: &KeyMgmtAlg, enc: &ContentEncAlg) -> bool {
match jwk_alg {
None => true,
Some(KeyAlg::KeyMgmt(a)) => a == alg,
Some(KeyAlg::ContentEnc(e)) => *alg == KeyMgmtAlg::Dir && e == enc,
Some(KeyAlg::Signing(_) | KeyAlg::Unknown(_)) => false,
}
}
fn key_op(alg: &KeyMgmtAlg, direction: Direction) -> Option<KeyOp> {
let op = match (keymgmt::mode(alg)?, direction) {
(Mode::KeyEncryption | Mode::KeyWrapping, Direction::Encrypt) => KeyOp::WrapKey,
(Mode::KeyEncryption | Mode::KeyWrapping, Direction::Decrypt) => KeyOp::UnwrapKey,
(Mode::DirectEncryption, Direction::Encrypt) => KeyOp::Encrypt,
(Mode::DirectEncryption, Direction::Decrypt) => KeyOp::Decrypt,
(Mode::DirectAgreement | Mode::AgreementWithWrapping, _) => KeyOp::DeriveKey,
};
Some(op)
}
pub(crate) fn key_ops_either_direction(alg: &KeyMgmtAlg) -> [Option<KeyOp>; 2] {
[
key_op(alg, Direction::Encrypt),
key_op(alg, Direction::Decrypt),
]
}
fn unusable(direction: Direction, detail: &'static str) -> Error {
match direction {
Direction::Encrypt => Error::InvalidKey(detail),
Direction::Decrypt => Error::KeyAlgorithmMismatch,
}
}
fn wrapping_key_len(alg: &KeyMgmtAlg) -> Option<usize> {
match alg {
KeyMgmtAlg::A128KW | KeyMgmtAlg::A128GCMKW => Some(16),
KeyMgmtAlg::A192KW | KeyMgmtAlg::A192GCMKW => Some(24),
KeyMgmtAlg::A256KW | KeyMgmtAlg::A256GCMKW => Some(32),
_ => None,
}
}
fn oct_key(
key: &OctKey,
alg: &KeyMgmtAlg,
enc: &ContentEncAlg,
direction: Direction,
) -> Result<RecipientKey> {
let k = || Zeroizing::new(key.k().to_vec());
match alg {
KeyMgmtAlg::Pbes2Hs256A128KW
| KeyMgmtAlg::Pbes2Hs384A192KW
| KeyMgmtAlg::Pbes2Hs512A256KW => Ok(RecipientKey::Password(k())),
KeyMgmtAlg::Dir => {
if direction == Direction::Encrypt && key.len() != content::cek_len(enc)? {
return Err(Error::InvalidKey(
"dir key length must equal the enc key length",
));
}
Ok(RecipientKey::Symmetric(k()))
}
_ => match wrapping_key_len(alg) {
Some(len) if key.len() == len => Ok(RecipientKey::Symmetric(k())),
Some(_) => Err(unusable(
direction,
"AES key wrap key length does not match alg",
)),
None => Err(Error::KeyAlgorithmMismatch),
},
}
}
const EC_KEY_UNUSABLE: &str = "EC key is not usable for ECDH-ES";
fn ec_key(key: &EcKey, direction: Direction) -> Result<RecipientKey> {
let unusable = || unusable(direction, EC_KEY_UNUSABLE);
match direction {
Direction::Encrypt => {
let point = [&[0x04][..], key.x(), key.y()].concat();
match key.crv() {
EcCurve::P256 => p256::PublicKey::from_sec1_bytes(&point)
.map(RecipientKey::P256Public)
.map_err(|_| unusable()),
EcCurve::P384 => p384::PublicKey::from_sec1_bytes(&point)
.map(RecipientKey::P384Public)
.map_err(|_| unusable()),
EcCurve::P521 => p521::PublicKey::from_sec1_bytes(&point)
.map(RecipientKey::P521Public)
.map_err(|_| unusable()),
_ => Err(Error::KeyAlgorithmMismatch),
}
}
Direction::Decrypt => {
let d = key.d().ok_or(Error::KeyAlgorithmMismatch)?;
match key.crv() {
EcCurve::P256 => secret::<p256::NistP256>(d)
.map(RecipientKey::P256Secret)
.ok_or_else(unusable),
EcCurve::P384 => secret::<p384::NistP384>(d)
.map(RecipientKey::P384Secret)
.ok_or_else(unusable),
EcCurve::P521 => secret::<p521::NistP521>(d)
.map(RecipientKey::P521Secret)
.ok_or_else(unusable),
_ => Err(Error::KeyAlgorithmMismatch),
}
}
}
}
fn secret<C: Curve>(d: &[u8]) -> Option<SecretKey<C>> {
let d = <&FieldBytes<C>>::try_from(d).ok()?;
SecretKey::<C>::from_bytes(d).ok()
}
const X25519_KEY_UNUSABLE: &str = "X25519 key is not 32 octets";
fn okp_key(key: &OkpKey, direction: Direction) -> Result<RecipientKey> {
if key.crv() != OkpCurve::X25519 {
return Err(Error::KeyAlgorithmMismatch);
}
let unusable = || unusable(direction, X25519_KEY_UNUSABLE);
match direction {
Direction::Encrypt => {
let x = <[u8; 32]>::try_from(key.x()).map_err(|_| unusable())?;
Ok(RecipientKey::X25519Public(x25519_dalek::PublicKey::from(x)))
}
Direction::Decrypt => {
let d = key.d().ok_or(Error::KeyAlgorithmMismatch)?;
let d = Zeroizing::new(<[u8; 32]>::try_from(d).map_err(|_| unusable())?);
Ok(RecipientKey::X25519Secret(
x25519_dalek::StaticSecret::from(*d),
))
}
}
}
fn rsa_key(key: &RsaPublicKey, direction: Direction) -> Result<RecipientKey> {
if direction == Direction::Decrypt {
return Err(Error::UnsupportedAlgorithm);
}
Ok(RecipientKey::RsaPublic {
n: key.n().to_vec(),
e: key.e().to_vec(),
})
}
pub(crate) fn kid_matches(jwk: &Jwk, header_kid: Option<&str>) -> bool {
match (jwk.kid(), header_kid) {
(Some(key_kid), Some(header_kid)) => key_kid == header_kid,
_ => true,
}
}
impl ZeroizeOnDrop for RecipientKey {}
impl fmt::Debug for RecipientKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
RecipientKey::Symmetric(_) => "Symmetric",
RecipientKey::Password(_) => "Password",
RecipientKey::RsaPublic { .. } => "RsaPublic",
RecipientKey::P256Public(_) => "P256Public",
RecipientKey::P256Secret(_) => "P256Secret",
RecipientKey::P384Public(_) => "P384Public",
RecipientKey::P384Secret(_) => "P384Secret",
RecipientKey::P521Public(_) => "P521Public",
RecipientKey::P521Secret(_) => "P521Secret",
RecipientKey::X25519Public(_) => "X25519Public",
RecipientKey::X25519Secret(_) => "X25519Secret",
};
write!(f, "RecipientKey::{name}(<redacted>)")
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::util::b64url_encode;
use Direction::{Decrypt, Encrypt};
use KeyMgmtAlg as A;
const MISMATCH: Error = Error::KeyAlgorithmMismatch;
fn jwk(json: &str) -> Jwk {
Jwk::from_json(json.as_bytes()).unwrap_or_else(|e| panic!("{e}: {json}"))
}
fn oct(len: usize, extra: &str) -> Jwk {
let k = b64url_encode(&vec![0x5a; len]);
jwk(&format!(r#"{{"kty":"oct","k":"{k}"{extra}}}"#))
}
fn bind(key: &Jwk, alg: &KeyMgmtAlg, enc: &ContentEncAlg, d: Direction) -> Result<String> {
RecipientKey::from_jwk(key, alg, enc, d).map(|k| format!("{k:?}"))
}
fn variant(name: &str) -> Result<String> {
Ok(format!("RecipientKey::{name}(<redacted>)"))
}
const GCM128: ContentEncAlg = ContentEncAlg::A128GCM;
const WRAP_SIZES: [(KeyMgmtAlg, usize); 6] = [
(A::A128KW, 16),
(A::A192KW, 24),
(A::A256KW, 32),
(A::A128GCMKW, 16),
(A::A192GCMKW, 24),
(A::A256GCMKW, 32),
];
#[test]
fn oct_keys_are_symmetric_for_wrapping_and_dir_and_a_password_for_pbes2() {
for (alg, len) in &WRAP_SIZES {
for d in [Encrypt, Decrypt] {
assert_eq!(
bind(&oct(*len, ""), alg, &GCM128, d),
variant("Symmetric"),
"{alg}"
);
}
}
for d in [Encrypt, Decrypt] {
assert_eq!(
bind(&oct(16, ""), &A::Dir, &GCM128, d),
variant("Symmetric")
);
}
let pbes2 = [
A::Pbes2Hs256A128KW,
A::Pbes2Hs384A192KW,
A::Pbes2Hs512A256KW,
];
for alg in &pbes2 {
for (len, d) in [(1, Encrypt), (30, Decrypt), (100, Encrypt)] {
assert_eq!(
bind(&oct(len, ""), alg, &GCM128, d),
variant("Password"),
"{alg}"
);
}
}
}
#[test]
fn symmetric_and_password_keys_hold_k_exactly() {
let key = jwk(r#"{"kty":"oct","k":"GZy6sIZ6wl9NJOKB-jnmVQ"}"#);
let k = [
0x19, 0x9c, 0xba, 0xb0, 0x86, 0x7a, 0xc2, 0x5f, 0x4d, 0x24, 0xe2, 0x81, 0xfa, 0x39,
0xe6, 0x55,
];
let symmetric = RecipientKey::from_jwk(&key, &A::A128KW, &GCM128, Encrypt);
assert!(matches!(symmetric, Ok(RecipientKey::Symmetric(ref s)) if **s == k));
let password = RecipientKey::from_jwk(&key, &A::Pbes2Hs256A128KW, &GCM128, Decrypt);
assert!(matches!(password, Ok(RecipientKey::Password(ref p)) if **p == k));
}
#[test]
fn a_wrapping_key_of_the_wrong_size_is_invalid_on_encrypt_and_a_mismatch_on_decrypt() {
for (alg, len) in &WRAP_SIZES {
for wrong in [1, 15, 16, 17, 24, 31, 32, 33, 64] {
if wrong == *len {
continue;
}
let key = oct(wrong, "");
let encrypt = bind(&key, alg, &GCM128, Encrypt);
assert!(
matches!(encrypt, Err(Error::InvalidKey(_))),
"{alg} {wrong}"
);
assert_eq!(
bind(&key, alg, &GCM128, Decrypt),
Err(MISMATCH),
"{alg} {wrong}"
);
}
}
}
#[test]
fn a_dir_key_must_be_the_cek_length_on_encrypt_only() {
let sizes = [
(ContentEncAlg::A128CbcHs256, 32),
(ContentEncAlg::A192CbcHs384, 48),
(ContentEncAlg::A256CbcHs512, 64),
(ContentEncAlg::A128GCM, 16),
(ContentEncAlg::A192GCM, 24),
(ContentEncAlg::A256GCM, 32),
];
for (enc, len) in &sizes {
assert_eq!(
bind(&oct(*len, ""), &A::Dir, enc, Encrypt),
variant("Symmetric")
);
for wrong in [len - 1, len + 1, 1] {
let key = oct(wrong, "");
let encrypt = bind(&key, &A::Dir, enc, Encrypt);
assert!(
matches!(encrypt, Err(Error::InvalidKey(_))),
"{enc} {wrong}"
);
assert_eq!(bind(&key, &A::Dir, enc, Decrypt), variant("Symmetric"));
}
}
}
#[test]
fn dir_with_an_unknown_enc_is_unsupported() {
let enc = ContentEncAlg::from("A128GCM-ish");
for d in [Encrypt, Decrypt] {
assert_eq!(
bind(&oct(16, ""), &A::Dir, &enc, d),
Err(Error::UnsupportedAlgorithm)
);
}
}
#[test]
fn debug_names_the_variant_only() {
let key = RecipientKey::Symmetric(Zeroizing::new(vec![0xC5; 16]));
assert_eq!(format!("{key:?}"), "RecipientKey::Symmetric(<redacted>)");
let key = RecipientKey::X25519Secret(x25519_dalek::StaticSecret::from([0xC5; 32]));
let debug = format!("{key:?}");
assert_eq!(debug, "RecipientKey::X25519Secret(<redacted>)");
assert!(!debug.contains("197"));
}
const RFC7520_P521: &str = r#""kty":"EC","kid":"bilbo.baggins@hobbiton.example","crv":"P-521","x":"AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt","y":"AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1""#;
const RFC7520_P521_D: &str =
"AAhRON2r9cqXX1hg-RoI6R1tX5p2rUAYdmpHZoC1XNM56KtscrX6zbKipQrCW9CGZH3T4ubpnoTKLDYJ_fF3_rJt";
fn p521(extra: &str) -> Jwk {
jwk(&format!("{{{RFC7520_P521}{extra}}}"))
}
fn p521_private(extra: &str) -> Jwk {
p521(&format!(r#","d":"{RFC7520_P521_D}"{extra}"#))
}
fn rsa(extra: &str) -> Jwk {
jwk(&format!(
r#"{{"kty":"RSA","kid":"bilbo.baggins@hobbiton.example","n":"n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5gHdrNP5zw","e":"AQAB"{extra}}}"#
))
}
const X25519_BOB: &str =
r#""kty":"OKP","crv":"X25519","x":"3p7bfXt9wbTTW2HC7OQ1Nz-DQ8hbeGdNrfx-FG-IK08""#;
const X25519_BOB_D: &str = "XasIfmJKikt54X-Lg4AO5m87sSkmGLb9HC-LJ_-I4Os";
fn x25519(extra: &str) -> Jwk {
jwk(&format!("{{{X25519_BOB}{extra}}}"))
}
fn x25519_private(extra: &str) -> Jwk {
x25519(&format!(r#","d":"{X25519_BOB_D}"{extra}"#))
}
const ECDH: [KeyMgmtAlg; 4] = [A::EcdhEs, A::EcdhEsA128KW, A::EcdhEsA192KW, A::EcdhEsA256KW];
const RFC7520_P256: &str = r#""kty":"EC","kid":"meriadoc.brandybuck@buckland.example","use":"enc","crv":"P-256","x":"Ze2loSV3wrroKUN_4zhwGhCqo3Xhu1td4QjeQ5wIVR0","y":"HlLtdXARY_f55A3fnzQbPcm6hgr34Mp8p-nuzQCE0Zw""#;
const RFC7520_P256_D: &str = "r_kHyZ-a06rmxM3yESK84r1otSg-aQcVStkRhA-iCM8";
const RFC7520_P384: &str = r#""kty":"EC","kid":"peregrin.took@tuckborough.example","use":"enc","crv":"P-384","x":"YU4rRUzdmVqmRtWOs2OpDE_T5fsNIodcG8G5FWPrTPMyxpzsSOGaQLpe2FpxBmu2","y":"A8-yxCHxkfBz3hKZfI1jUYMjUhsEveZ9THuwFjH2sCNdtksRJU7D5-SkgaFL1ETP""#;
const RFC7520_P384_D: &str = "iTx2pk7wW-GqJkHcEkFQb2EFyYcO7RugmaW3mRrQVAOUiPommT0IdnYK2xDlZh-j";
fn ec_cases() -> [(Jwk, Jwk, Vec<u8>, Vec<u8>); 3] {
let case = |members: &str, d: &str| {
let public = jwk(&format!("{{{members}}}"));
let private = jwk(&format!(r#"{{{members},"d":"{d}"}}"#));
let KeyMaterial::Ec(ec) = public.material() else {
panic!("not EC")
};
let point = [&[0x04][..], ec.x(), ec.y()].concat();
let d = crate::util::b64url_decode(d).unwrap_or_default();
(public, private, point, d)
};
[
case(RFC7520_P256, RFC7520_P256_D),
case(RFC7520_P384, RFC7520_P384_D),
case(RFC7520_P521, RFC7520_P521_D),
]
}
fn ec_parts(key: &RecipientKey) -> (&'static str, bool, Vec<u8>) {
use p256::elliptic_curve::sec1::ToSec1Point;
match key {
RecipientKey::P256Public(k) => {
("P-256", false, k.to_sec1_point(false).as_bytes().to_vec())
}
RecipientKey::P384Public(k) => {
("P-384", false, k.to_sec1_point(false).as_bytes().to_vec())
}
RecipientKey::P521Public(k) => {
("P-521", false, k.to_sec1_point(false).as_bytes().to_vec())
}
RecipientKey::P256Secret(k) => ("P-256", true, k.to_bytes().to_vec()),
RecipientKey::P384Secret(k) => ("P-384", true, k.to_bytes().to_vec()),
RecipientKey::P521Secret(k) => ("P-521", true, k.to_bytes().to_vec()),
_ => ("other", false, Vec::new()),
}
}
#[test]
fn an_ec_key_encrypts_to_its_public_point_and_decrypts_with_d() {
for ((public, private, point, d), crv) in ec_cases().iter().zip(["P-256", "P-384", "P-521"])
{
for alg in &ECDH {
for key in [public, private] {
let bound = RecipientKey::from_jwk(key, alg, &GCM128, Encrypt);
let bound = bound.unwrap_or_else(|e| panic!("{crv} {alg}: {e}"));
assert_eq!(ec_parts(&bound), (crv, false, point.clone()), "{alg}");
}
let bound = RecipientKey::from_jwk(private, alg, &GCM128, Decrypt);
let bound = bound.unwrap_or_else(|e| panic!("{crv} {alg}: {e}"));
assert_eq!(ec_parts(&bound), (crv, true, d.clone()), "{alg}");
}
}
}
#[test]
fn an_ec_secret_derives_the_jwk_point() {
use p256::elliptic_curve::sec1::ToSec1Point;
for (_, private, point, _) in &ec_cases() {
let derived = match RecipientKey::from_jwk(private, &A::EcdhEs, &GCM128, Decrypt) {
Ok(RecipientKey::P256Secret(k)) => {
k.public_key().to_sec1_point(false).as_bytes().to_vec()
}
Ok(RecipientKey::P384Secret(k)) => {
k.public_key().to_sec1_point(false).as_bytes().to_vec()
}
Ok(RecipientKey::P521Secret(k)) => {
k.public_key().to_sec1_point(false).as_bytes().to_vec()
}
other => panic!("{other:?}"),
};
assert_eq!(&derived, point);
}
}
#[test]
fn a_public_ec_key_cannot_decrypt() {
for (public, ..) in &ec_cases() {
for alg in &ECDH {
assert_eq!(bind(public, alg, &GCM128, Decrypt), Err(MISMATCH), "{alg}");
}
}
}
#[test]
fn secp256k1_does_not_serve_ecdh_es() {
let k256 = jwk(
r#"{"kty":"EC","crv":"secp256k1","x":"eb5mfvncu6xVoGKVzocLBwKb_NstzijZWfKBWxb4F5g","y":"SDradyajxGVdpPv8DhEIqP0XtEimhVQZnEfQj_sQ1Lg","d":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE"}"#,
);
for alg in &ECDH {
for d in [Encrypt, Decrypt] {
assert_eq!(bind(&k256, alg, &GCM128, d), Err(MISMATCH), "{alg}");
}
}
}
#[test]
fn an_x25519_key_encrypts_to_x_and_decrypts_with_d() {
let x = crate::util::b64url_decode("3p7bfXt9wbTTW2HC7OQ1Nz-DQ8hbeGdNrfx-FG-IK08")
.unwrap_or_default();
let d = crate::util::b64url_decode(X25519_BOB_D).unwrap_or_default();
for alg in &ECDH {
for key in [x25519(""), x25519_private("")] {
let bound = RecipientKey::from_jwk(&key, alg, &GCM128, Encrypt);
assert!(
matches!(bound, Ok(RecipientKey::X25519Public(ref p)) if p.as_bytes()[..] == x[..]),
"{alg}"
);
}
let bound = RecipientKey::from_jwk(&x25519_private(""), alg, &GCM128, Decrypt);
let Ok(RecipientKey::X25519Secret(secret)) = bound else {
panic!("{alg}: {bound:?}")
};
assert_eq!(secret.to_bytes()[..], d[..], "{alg}");
let public = x25519_dalek::PublicKey::from(&secret);
assert_eq!(public.as_bytes()[..], x[..], "{alg}");
}
}
#[test]
fn a_public_x25519_key_cannot_decrypt() {
for alg in &ECDH {
assert_eq!(
bind(&x25519(""), alg, &GCM128, Decrypt),
Err(MISMATCH),
"{alg}"
);
}
}
#[test]
fn ed25519_does_not_serve_ecdh_es() {
let ed25519 = jwk(
r#"{"kty":"OKP","crv":"Ed25519","x":"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo","d":"nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A"}"#,
);
for alg in &ECDH {
for d in [Encrypt, Decrypt] {
assert_eq!(bind(&ed25519, alg, &GCM128, d), Err(MISMATCH), "{alg}");
}
}
}
#[test]
fn an_rsa_public_key_serves_rsa_oaep_256_encrypt_with_its_n_and_e() {
let key = rsa("");
let KeyMaterial::Rsa(material) = key.material() else {
panic!("not RSA")
};
let bound = RecipientKey::from_jwk(&key, &A::RsaOaep256, &GCM128, Encrypt);
assert!(matches!(
bound,
Ok(RecipientKey::RsaPublic { ref n, ref e }) if n == material.n() && *e == [1, 0, 1]
));
}
#[test]
fn rsa_oaep_256_decrypt_is_unsupported() {
assert_eq!(
bind(&rsa(""), &A::RsaOaep256, &GCM128, Decrypt),
Err(Error::UnsupportedAlgorithm)
);
}
#[test]
fn a_key_of_the_wrong_family_is_a_mismatch() {
let cases = [
(p521_private(""), A::A128KW),
(p521_private(""), A::Dir),
(p521_private(""), A::Pbes2Hs256A128KW),
(x25519_private(""), A::A256GCMKW),
(rsa(""), A::A128KW),
(rsa(""), A::EcdhEs),
(oct(16, ""), A::EcdhEs),
(oct(16, ""), A::EcdhEsA128KW),
(oct(256, ""), A::RsaOaep256),
(p521(""), A::RsaOaep256),
];
for (key, alg) in &cases {
for d in [Encrypt, Decrypt] {
if *alg == A::RsaOaep256 && d == Decrypt {
continue;
}
assert_eq!(bind(key, alg, &GCM128, d), Err(MISMATCH), "{alg} {d:?}");
}
}
}
#[test]
fn support_is_checked_before_the_family_and_the_family_before_the_size() {
for key in [rsa(""), p521_private(""), oct(16, "")] {
assert_eq!(
bind(&key, &A::RsaOaep256, &GCM128, Decrypt),
Err(Error::UnsupportedAlgorithm)
);
for name in ["RSA-OAEP", "RSA1_5"] {
for d in [Encrypt, Decrypt] {
let alg = KeyMgmtAlg::from(name);
assert_eq!(
bind(&key, &alg, &GCM128, d),
Err(Error::UnsupportedAlgorithm)
);
}
}
}
let key = oct(24, r#","use":"sig""#);
assert_eq!(bind(&key, &A::A128KW, &GCM128, Encrypt), Err(MISMATCH));
let key = oct(24, r#","alg":"A192KW""#);
assert_eq!(bind(&key, &A::A128KW, &GCM128, Encrypt), Err(MISMATCH));
}
#[test]
fn the_jwk_alg_must_be_the_requested_alg() {
let ok = [
(oct(16, r#","alg":"A128KW""#), A::A128KW),
(oct(16, r#","alg":"A128GCMKW""#), A::A128GCMKW),
(
oct(16, r#","alg":"PBES2-HS256+A128KW""#),
A::Pbes2Hs256A128KW,
),
(oct(16, r#","alg":"dir""#), A::Dir),
(p521_private(r#","alg":"ECDH-ES""#), A::EcdhEs),
(
x25519_private(r#","alg":"ECDH-ES+A256KW""#),
A::EcdhEsA256KW,
),
];
for (key, alg) in &ok {
for d in [Encrypt, Decrypt] {
assert!(bind(key, alg, &GCM128, d).is_ok(), "{alg} {d:?}");
}
}
assert!(
bind(
&rsa(r#","alg":"RSA-OAEP-256""#),
&A::RsaOaep256,
&GCM128,
Encrypt
)
.is_ok()
);
let mismatched = [
(oct(16, r#","alg":"A256KW""#), A::A128KW),
(oct(16, r#","alg":"A128KW""#), A::A128GCMKW),
(oct(16, r#","alg":"A128KW""#), A::Dir),
(oct(16, r#","alg":"A128KW""#), A::Pbes2Hs256A128KW),
(oct(16, r#","alg":"PBES2-HS256+A128KW""#), A::A128KW),
(oct(32, r#","alg":"HS256""#), A::A256KW),
(oct(16, r#","alg":"A128GCM""#), A::A128KW),
(oct(16, r#","alg":"A128KW-ish""#), A::A128KW),
(p521_private(r#","alg":"ECDH-ES""#), A::EcdhEsA128KW),
(p521_private(r#","alg":"ECDH-ES+A128KW""#), A::EcdhEs),
(
x25519_private(r#","alg":"ECDH-ES+A128KW""#),
A::EcdhEsA256KW,
),
];
for (key, alg) in &mismatched {
for d in [Encrypt, Decrypt] {
assert_eq!(bind(key, alg, &GCM128, d), Err(MISMATCH), "{alg} {d:?}");
}
}
}
#[test]
fn a_dir_key_may_name_its_enc_as_its_alg() {
let rfc7520_5_6 = jwk(
r#"{"kty":"oct","kid":"77c7e2b8-6e13-45cf-8672-617b5b45243a","use":"enc","alg":"A128GCM","k":"XctOhJAkA-pD9Lh7ZgW_2A"}"#,
);
for d in [Encrypt, Decrypt] {
assert_eq!(
bind(&rfc7520_5_6, &A::Dir, &GCM128, d),
variant("Symmetric")
);
let other = ContentEncAlg::A256GCM;
assert_eq!(bind(&rfc7520_5_6, &A::Dir, &other, d), Err(MISMATCH));
}
let rfc7520_3_6 = jwk(
r#"{"kty":"oct","kid":"1e571774-2e08-40da-8308-e8d68773842d","use":"enc","alg":"A256GCM","k":"AAPapAv4LbFbiVawEjagUBluYqN5rhna-8nuldDvOx8"}"#,
);
let enc = ContentEncAlg::A256GCM;
assert_eq!(
bind(&rfc7520_3_6, &A::Dir, &enc, Encrypt),
variant("Symmetric")
);
assert_eq!(bind(&rfc7520_3_6, &A::A256KW, &enc, Encrypt), Err(MISMATCH));
}
#[test]
fn the_jwk_use_must_be_enc() {
for (extra, ok) in [
(r#","use":"enc""#, true),
(r#","use":"sig""#, false),
(r#","use":"wrap""#, false),
] {
let cases = [
(oct(16, extra), A::A128KW),
(oct(16, extra), A::Dir),
(oct(16, extra), A::Pbes2Hs256A128KW),
(p521_private(extra), A::EcdhEs),
(x25519_private(extra), A::EcdhEsA128KW),
];
for (key, alg) in &cases {
for d in [Encrypt, Decrypt] {
let result = bind(key, alg, &GCM128, d);
assert_eq!(result.is_ok(), ok, "{alg} {d:?} {extra}");
assert!(ok || result == Err(MISMATCH));
}
}
let result = bind(&rsa(extra), &A::RsaOaep256, &GCM128, Encrypt);
assert_eq!(result.is_ok(), ok, "RSA {extra}");
}
}
#[test]
fn the_jwk_key_ops_must_contain_the_operation() {
let ops = |op: &str| format!(r#","key_ops":["{op}"]"#);
let wrapping = [A::A128KW, A::A128GCMKW, A::Pbes2Hs256A128KW];
let all = [
"wrapKey",
"unwrapKey",
"encrypt",
"decrypt",
"deriveKey",
"deriveBits",
"sign",
];
for op in all {
for alg in &wrapping {
let key = oct(16, &ops(op));
assert_eq!(
bind(&key, alg, &GCM128, Encrypt).is_ok(),
op == "wrapKey",
"{alg} {op}"
);
assert_eq!(
bind(&key, alg, &GCM128, Decrypt).is_ok(),
op == "unwrapKey",
"{alg} {op}"
);
}
let key = oct(16, &ops(op));
assert_eq!(
bind(&key, &A::Dir, &GCM128, Encrypt).is_ok(),
op == "encrypt",
"dir {op}"
);
assert_eq!(
bind(&key, &A::Dir, &GCM128, Decrypt).is_ok(),
op == "decrypt",
"dir {op}"
);
for alg in &ECDH {
for key in [p521_private(&ops(op)), x25519_private(&ops(op))] {
for d in [Encrypt, Decrypt] {
let result = bind(&key, alg, &GCM128, d);
assert_eq!(result.is_ok(), op == "deriveKey", "{alg} {op} {d:?}");
}
}
}
let result = bind(&rsa(&ops(op)), &A::RsaOaep256, &GCM128, Encrypt);
assert_eq!(result.is_ok(), op == "wrapKey", "RSA-OAEP-256 {op}");
}
let key = oct(16, r#","key_ops":["wrapKey","unwrapKey"]"#);
assert!(bind(&key, &A::A128KW, &GCM128, Encrypt).is_ok());
assert!(bind(&key, &A::A128KW, &GCM128, Decrypt).is_ok());
let key = oct(16, r#","key_ops":[]"#);
assert_eq!(bind(&key, &A::A128KW, &GCM128, Encrypt), Err(MISMATCH));
}
#[test]
fn kid_selects_only_when_both_sides_carry_one() {
let with = p521("");
let without = oct(16, "");
assert!(kid_matches(&with, Some("bilbo.baggins@hobbiton.example")));
assert!(!kid_matches(&with, Some("Bilbo.Baggins@hobbiton.example")));
assert!(!kid_matches(&with, Some("")));
assert!(kid_matches(&with, None));
assert!(kid_matches(&without, Some("anything")));
assert!(kid_matches(&without, None));
}
#[test]
fn debug_of_every_variant_prints_no_key_bytes() {
let marker = [0xC5_u8; 32];
let k = b64url_encode(&marker);
let symmetric = jwk(&format!(r#"{{"kty":"oct","k":"{k}"}}"#));
let keys = [
RecipientKey::from_jwk(&symmetric, &A::A256KW, &GCM128, Encrypt),
RecipientKey::from_jwk(&symmetric, &A::Pbes2Hs256A128KW, &GCM128, Decrypt),
RecipientKey::from_jwk(&rsa(""), &A::RsaOaep256, &GCM128, Encrypt),
RecipientKey::from_jwk(&x25519_private(""), &A::EcdhEs, &GCM128, Encrypt),
Ok(RecipientKey::X25519Secret(
x25519_dalek::StaticSecret::from(marker),
)),
];
let mut keys: Vec<RecipientKey> = keys
.into_iter()
.map(|k| k.unwrap_or_else(|e| panic!("{e}")))
.collect();
for (_, private, ..) in ec_cases() {
for d in [Encrypt, Decrypt] {
let key = RecipientKey::from_jwk(&private, &A::EcdhEs, &GCM128, d);
keys.push(key.unwrap_or_else(|e| panic!("{e}")));
}
}
let secrets = [
k.clone(),
"c5c5".to_owned(),
"C5C5".to_owned(),
"197".to_owned(),
RFC7520_P256_D.to_owned(),
RFC7520_P384_D.to_owned(),
RFC7520_P521_D.to_owned(),
X25519_BOB_D.to_owned(),
];
let mut names = Vec::new();
for key in &keys {
let debug = format!("{key:?}");
for secret in &secrets {
assert!(!debug.contains(secret.as_str()), "{debug}");
}
let name = debug
.strip_prefix("RecipientKey::")
.and_then(|d| d.strip_suffix("(<redacted>)"));
names.push(name.unwrap_or_else(|| panic!("{debug}")).to_owned());
}
names.sort();
names.dedup();
assert_eq!(names.len(), 11, "every variant is covered: {names:?}");
}
#[test]
fn every_secret_variant_wipes_itself_on_drop() {
fn zeroize_on_drop<T: ZeroizeOnDrop>() {}
zeroize_on_drop::<Zeroizing<Vec<u8>>>(); zeroize_on_drop::<p256::SecretKey>();
zeroize_on_drop::<p384::SecretKey>();
zeroize_on_drop::<p521::SecretKey>();
zeroize_on_drop::<x25519_dalek::StaticSecret>();
zeroize_on_drop::<RecipientKey>();
}
}