use p256::elliptic_curve::rand_core::TryCryptoRng;
use p256::elliptic_curve::sec1::{FromSec1Point, ModulusSize, ToSec1Point};
use p256::elliptic_curve::{
AffinePoint, CurveArithmetic, FieldBytesSize, Generate, NonZeroScalar, SecretKey,
};
use x25519_dalek::StaticSecret;
use zeroize::{Zeroize, Zeroizing};
use super::jwk_build;
use super::types::{JoseAlg, KeySpec};
use crate::error::{Error, Result};
use crate::jwa::{ContentEncAlg, KeyMgmtAlg, SigningAlg};
use crate::jwk::{EcCurve, Jwk, OkpCurve};
use crate::rng::CryptoRngAdapter;
const OCT_MIN_BYTES: usize = 16;
const OCT_MAX_BYTES: usize = 512;
pub fn generate(spec: KeySpec) -> Result<Jwk> {
generate_with(spec, &mut CryptoRngAdapter::os())
}
fn generate_with<R: TryCryptoRng + ?Sized>(spec: KeySpec, rng: &mut R) -> Result<Jwk> {
match spec {
KeySpec::Oct { bytes } => generate_oct(bytes, rng),
KeySpec::Ec(crv) => generate_ec(crv, rng),
KeySpec::Okp(crv) => generate_okp(crv, rng),
}
}
pub(crate) fn generate_ec<R: TryCryptoRng + ?Sized>(crv: EcCurve, rng: &mut R) -> Result<Jwk> {
ec_key(crv, None, rng)
}
pub(crate) fn generate_ec_secret<C, R>(rng: &mut R) -> Result<SecretKey<C>>
where
C: CurveArithmetic,
R: TryCryptoRng + ?Sized,
{
let mut scalar =
NonZeroScalar::<C>::try_generate_from_rng(rng).map_err(|_| Error::RandomnessUnavailable)?;
let secret = SecretKey::from(&scalar);
scalar.zeroize();
Ok(secret)
}
pub(crate) fn generate_okp<R: TryCryptoRng + ?Sized>(crv: OkpCurve, rng: &mut R) -> Result<Jwk> {
okp_key(crv, None, rng)
}
pub(crate) fn generate_x25519_secret<R: TryCryptoRng + ?Sized>(
rng: &mut R,
) -> Result<StaticSecret> {
let mut bytes = Zeroizing::new([0u8; 32]);
fill(rng, bytes.as_mut_slice())?;
Ok(StaticSecret::from(*bytes))
}
fn okp_key<R: TryCryptoRng + ?Sized>(crv: OkpCurve, alg: Option<&str>, rng: &mut R) -> Result<Jwk> {
match crv {
OkpCurve::Ed25519 => {
let mut d = Zeroizing::new([0u8; 32]);
fill(rng, d.as_mut_slice())?;
let x = ed25519_dalek::SigningKey::from_bytes(&d)
.verifying_key()
.to_bytes();
jwk_build::okp_jwk(crv, &x, Some(d.as_slice()), alg)
}
OkpCurve::X25519 => {
let secret = generate_x25519_secret(rng)?;
let d = secret.as_bytes();
let x = super::okp::x25519_public(d);
jwk_build::okp_jwk(crv, &x, Some(d.as_slice()), alg)
}
}
}
fn ec_key<R: TryCryptoRng + ?Sized>(crv: EcCurve, alg: Option<&str>, rng: &mut R) -> Result<Jwk> {
match crv {
EcCurve::P256 => {
ec_jwk_from_secret(crv, &generate_ec_secret::<p256::NistP256, R>(rng)?, alg)
}
EcCurve::P384 => {
ec_jwk_from_secret(crv, &generate_ec_secret::<p384::NistP384, R>(rng)?, alg)
}
EcCurve::P521 => {
ec_jwk_from_secret(crv, &generate_ec_secret::<p521::NistP521, R>(rng)?, alg)
}
EcCurve::Secp256k1 => {
ec_jwk_from_secret(crv, &generate_ec_secret::<k256::Secp256k1, R>(rng)?, alg)
}
}
}
fn ec_jwk_from_secret<C>(crv: EcCurve, secret: &SecretKey<C>, alg: Option<&str>) -> Result<Jwk>
where
C: CurveArithmetic,
AffinePoint<C>: FromSec1Point<C> + ToSec1Point<C>,
FieldBytesSize<C>: ModulusSize,
{
let point = super::ec::public_key(secret).to_sec1_point(false);
let (Some(x), Some(y)) = (point.x(), point.y()) else {
return Err(Error::InvalidKey("EC public key is the identity"));
};
let mut d = secret.to_bytes();
ec_jwk_from_copy(crv, x, y, &mut d, alg)
}
fn ec_jwk_from_copy(
crv: EcCurve,
x: &[u8],
y: &[u8],
copy: &mut [u8],
alg: Option<&str>,
) -> Result<Jwk> {
let jwk = jwk_build::ec_jwk(crv, x, y, Some(copy), alg);
copy.zeroize();
jwk
}
pub fn generate_for(alg: impl Into<JoseAlg>) -> Result<Jwk> {
generate_for_with(&alg.into(), &mut CryptoRngAdapter::os())
}
fn generate_for_with<R: TryCryptoRng + ?Sized>(alg: &JoseAlg, rng: &mut R) -> Result<Jwk> {
let name = Some(jose_alg_name(alg));
match spec_for(alg)? {
KeySpec::Oct { bytes } => oct_key(bytes, name, rng),
KeySpec::Ec(crv) => ec_key(crv, name, rng),
KeySpec::Okp(crv) => okp_key(crv, name, rng),
}
}
fn spec_for(alg: &JoseAlg) -> Result<KeySpec> {
const CURVE_REQUIRED: Error = Error::InvalidKey("curve required");
let oct = |bytes| Ok(KeySpec::Oct { bytes });
match alg {
JoseAlg::Signing(alg) => match alg {
SigningAlg::HS256 => oct(32),
SigningAlg::HS384 => oct(48),
SigningAlg::HS512 => oct(64),
SigningAlg::ES256 => Ok(KeySpec::Ec(EcCurve::P256)),
SigningAlg::ES384 => Ok(KeySpec::Ec(EcCurve::P384)),
SigningAlg::ES512 => Ok(KeySpec::Ec(EcCurve::P521)),
SigningAlg::ES256K => Ok(KeySpec::Ec(EcCurve::Secp256k1)),
SigningAlg::EdDSA | SigningAlg::Ed25519 => Ok(KeySpec::Okp(OkpCurve::Ed25519)),
SigningAlg::RS256
| SigningAlg::RS384
| SigningAlg::RS512
| SigningAlg::PS256
| SigningAlg::PS384
| SigningAlg::PS512
| SigningAlg::None
| SigningAlg::Unknown(_) => Err(Error::UnsupportedAlgorithm),
},
JoseAlg::KeyMgmt(alg) => match alg {
KeyMgmtAlg::A128KW | KeyMgmtAlg::A128GCMKW => oct(16),
KeyMgmtAlg::A192KW | KeyMgmtAlg::A192GCMKW => oct(24),
KeyMgmtAlg::A256KW | KeyMgmtAlg::A256GCMKW => oct(32),
KeyMgmtAlg::EcdhEs
| KeyMgmtAlg::EcdhEsA128KW
| KeyMgmtAlg::EcdhEsA192KW
| KeyMgmtAlg::EcdhEsA256KW => Err(CURVE_REQUIRED),
KeyMgmtAlg::RsaOaep256
| KeyMgmtAlg::Dir
| KeyMgmtAlg::Pbes2Hs256A128KW
| KeyMgmtAlg::Pbes2Hs384A192KW
| KeyMgmtAlg::Pbes2Hs512A256KW
| KeyMgmtAlg::Unknown(_) => Err(Error::UnsupportedAlgorithm),
},
JoseAlg::ContentEnc(enc) => match enc {
ContentEncAlg::A128GCM => oct(16),
ContentEncAlg::A192GCM => oct(24),
ContentEncAlg::A256GCM => oct(32),
ContentEncAlg::A128CbcHs256 => oct(32),
ContentEncAlg::A192CbcHs384 => oct(48),
ContentEncAlg::A256CbcHs512 => oct(64),
ContentEncAlg::Unknown(_) => Err(Error::UnsupportedAlgorithm),
},
}
}
fn jose_alg_name(alg: &JoseAlg) -> &str {
match alg {
JoseAlg::Signing(alg) => alg.as_str(),
JoseAlg::KeyMgmt(alg) => alg.as_str(),
JoseAlg::ContentEnc(enc) => enc.as_str(),
}
}
fn generate_oct<R: TryCryptoRng + ?Sized>(bytes: usize, rng: &mut R) -> Result<Jwk> {
oct_key(bytes, None, rng)
}
fn oct_key<R: TryCryptoRng + ?Sized>(bytes: usize, alg: Option<&str>, rng: &mut R) -> Result<Jwk> {
if !(OCT_MIN_BYTES..=OCT_MAX_BYTES).contains(&bytes) {
return Err(Error::InvalidKey("oct key length must be 16 to 512 octets"));
}
let mut k = Zeroizing::new(vec![0u8; bytes]);
fill(rng, &mut k)?;
jwk_build::oct_jwk(&k, alg)
}
fn fill<R: TryCryptoRng + ?Sized>(rng: &mut R, dest: &mut [u8]) -> Result<()> {
rng.try_fill_bytes(dest)
.map_err(|_| Error::RandomnessUnavailable)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Error;
use crate::jwa::{ContentEncAlg, KeyMgmtAlg, SigningAlg};
use crate::jwk::{EcCurve, EcKey, Jwk, KeyMaterial, OkpCurve, OkpKey};
use crate::rng::CryptoRngAdapter;
use crate::rng::insecure_test_vectors::FixedRng;
use crate::util::b64url_encode;
const EC_CURVES: [EcCurve; 4] = [
EcCurve::P256,
EcCurve::P384,
EcCurve::P521,
EcCurve::Secp256k1,
];
fn ec_len(crv: EcCurve) -> usize {
match crv {
EcCurve::P256 | EcCurve::Secp256k1 => 32,
EcCurve::P384 => 48,
EcCurve::P521 => 66,
}
}
fn published_ec_key(crv: EcCurve) -> Jwk {
let json: &[u8] = match crv {
EcCurve::P256 => {
include_bytes!("../../tests/data/keys/rfc/rfc7517-a2-ec-p256-pkcs8.jwk.json")
}
EcCurve::P384 => {
include_bytes!("../../tests/data/keys/rfc/rfc9500-testECCP384-sec1.jwk.json")
}
EcCurve::P521 => {
include_bytes!("../../tests/data/keys/rfc/rfc9500-testECCP521-sec1.jwk.json")
}
EcCurve::Secp256k1 => {
include_bytes!("../../tests/data/keys/rfc/rfc7468-fig12-secp256k1-pkcs8.jwk.json")
}
};
Jwk::from_json(json).expect("the fixture is a valid private EC JWK")
}
fn ec_key(jwk: &Jwk) -> &EcKey {
let KeyMaterial::Ec(key) = jwk.material() else {
panic!("an EC key")
};
key
}
fn ec_d(jwk: &Jwk) -> Vec<u8> {
ec_key(jwk).d().expect("a private EC key").to_vec()
}
const OKP_CURVES: [OkpCurve; 2] = [OkpCurve::Ed25519, OkpCurve::X25519];
fn published_okp_key(crv: OkpCurve) -> Jwk {
let json: &[u8] = match crv {
OkpCurve::Ed25519 => {
include_bytes!("../../tests/data/keys/rfc/rfc8037-a1-ed25519-pkcs8.jwk.json")
}
OkpCurve::X25519 => {
include_bytes!("../../tests/data/keys/rfc/rfc8037-a6-x25519-pkcs8.jwk.json")
}
};
Jwk::from_json(json).expect("the fixture is a valid private OKP JWK")
}
fn okp_key(jwk: &Jwk) -> &OkpKey {
let KeyMaterial::Okp(key) = jwk.material() else {
panic!("an OKP key")
};
key
}
fn okp_d(jwk: &Jwk) -> Vec<u8> {
okp_key(jwk).d().expect("a private OKP key").to_vec()
}
fn ec_from_bytes(crv: EcCurve, bytes: &[u8]) -> (Result<Jwk>, usize) {
let mut fixed = FixedRng::new(bytes);
let jwk = generate_ec(crv, &mut CryptoRngAdapter::fixed(&mut fixed));
(jwk, fixed.remaining())
}
fn oct_k(jwk: &Jwk) -> Vec<u8> {
let KeyMaterial::Oct(key) = jwk.material() else {
panic!("an oct key")
};
key.k().to_vec()
}
#[test]
fn oct_has_exactly_the_requested_length_from_16_to_512() {
for bytes in [16, 17, 32, 64, 511, 512] {
let jwk = generate(KeySpec::Oct { bytes }).unwrap();
assert_eq!(oct_k(&jwk).len(), bytes);
assert!(jwk.alg().is_none());
}
}
#[test]
fn oct_outside_16_to_512_is_invalid_and_draws_nothing() {
for bytes in [0, 1, 15, 513, usize::MAX] {
let mut fixed = FixedRng::new([0x42u8; 1024]);
let mut rng = CryptoRngAdapter::fixed(&mut fixed);
assert!(matches!(
generate_with(KeySpec::Oct { bytes }, &mut rng),
Err(Error::InvalidKey(_))
));
assert_eq!(fixed.remaining(), 1024, "no random byte is drawn");
}
}
#[test]
fn oct_takes_its_bytes_from_the_rng_passed_in() {
let seed: Vec<u8> = (0u8..32).collect();
let mut fixed = FixedRng::new(seed.clone());
let jwk = generate_with(
KeySpec::Oct { bytes: 32 },
&mut CryptoRngAdapter::fixed(&mut fixed),
)
.unwrap();
assert_eq!(oct_k(&jwk), seed);
assert_eq!(fixed.remaining(), 0);
}
#[test]
fn oct_fails_closed_on_rng_failure() {
let spec = KeySpec::Oct { bytes: 32 };
assert_eq!(
generate_with(
spec,
&mut CryptoRngAdapter::failing(getrandom::Error::UNEXPECTED)
)
.unwrap_err(),
Error::RandomnessUnavailable
);
let mut fixed = FixedRng::new([7u8; 31]);
assert_eq!(
generate_with(spec, &mut CryptoRngAdapter::fixed(&mut fixed)).unwrap_err(),
Error::RandomnessUnavailable
);
}
#[test]
fn ec_keys_have_full_size_members_and_validate_after_a_json_round_trip() {
for crv in EC_CURVES {
let jwk = generate(KeySpec::Ec(crv)).unwrap();
let key = ec_key(&jwk);
assert_eq!(key.crv(), crv);
assert_eq!(key.x().len(), ec_len(crv));
assert_eq!(key.y().len(), ec_len(crv));
assert_eq!(ec_d(&jwk).len(), ec_len(crv));
assert!(jwk.alg().is_none());
let reparsed = Jwk::from_json(jwk.to_json().as_bytes()).unwrap();
let again = ec_key(&reparsed);
assert_eq!(again.crv(), crv);
assert_eq!(again.x(), key.x());
assert_eq!(again.y(), key.y());
assert_eq!(ec_d(&reparsed), ec_d(&jwk));
assert_eq!(*reparsed.to_json(), *jwk.to_json());
let public = jwk.to_public().expect("an EC key has a public part");
assert!(public.is_public());
assert_eq!(ec_key(&public).d(), None);
assert_eq!(ec_key(&public).x(), key.x());
}
}
fn check_ec_jwk_wipes_d<C>(crv: EcCurve)
where
C: CurveArithmetic,
AffinePoint<C>: FromSec1Point<C> + ToSec1Point<C>,
FieldBytesSize<C>: ModulusSize,
{
let published = published_ec_key(crv);
let key = ec_key(&published);
let secret = SecretKey::<C>::from_slice(key.d().unwrap()).unwrap();
let built = ec_jwk_from_secret(crv, &secret, None).unwrap();
assert_eq!(ec_key(&built).x(), key.x(), "{crv:?}");
assert_eq!(ec_key(&built).y(), key.y(), "{crv:?}");
assert_eq!(ec_d(&built), ec_d(&published), "{crv:?}");
let mut copy = secret.to_bytes();
let built = ec_jwk_from_copy(crv, key.x(), key.y(), &mut copy, None).unwrap();
assert_eq!(ec_d(&built), ec_d(&published), "{crv:?}");
assert!(copy.iter().all(|&b| b == 0), "{crv:?}: d is wiped");
let mut copy = secret.to_bytes();
assert!(ec_jwk_from_copy(crv, key.y(), key.x(), &mut copy, None).is_err());
assert!(copy.iter().all(|&b| b == 0), "{crv:?}: d is wiped on error");
}
#[test]
fn ec_jwk_from_secret_wipes_its_field_bytes() {
check_ec_jwk_wipes_d::<p256::NistP256>(EcCurve::P256);
check_ec_jwk_wipes_d::<p384::NistP384>(EcCurve::P384);
check_ec_jwk_wipes_d::<p521::NistP521>(EcCurve::P521);
check_ec_jwk_wipes_d::<k256::Secp256k1>(EcCurve::Secp256k1);
}
#[test]
fn ec_reproduces_published_keys_from_their_scalar() {
for crv in EC_CURVES {
let published = published_ec_key(crv);
let (jwk, left) = ec_from_bytes(crv, &ec_d(&published));
let jwk = jwk.unwrap();
assert_eq!(left, 0);
assert_eq!(*jwk.to_json(), *published.to_json(), "{crv:?}");
}
}
#[test]
fn ec_rejection_samples_zero_and_out_of_range_scalars() {
for crv in EC_CURVES {
let published = published_ec_key(crv);
let d = ec_d(&published);
let mut bytes = vec![0u8; ec_len(crv)];
bytes.extend(vec![0xFFu8; ec_len(crv)]);
bytes.extend(&d);
let (jwk, left) = ec_from_bytes(crv, &bytes);
assert_eq!(left, 0, "{crv:?}: both rejected draws were consumed");
assert_eq!(ec_d(&jwk.unwrap()), d, "{crv:?}");
}
}
#[test]
fn ec_secret_is_the_d_of_generate_ec() {
fn secret_bytes<C: CurveArithmetic>(bytes: &[u8]) -> Vec<u8> {
let mut fixed = FixedRng::new(bytes);
let secret =
generate_ec_secret::<C, _>(&mut CryptoRngAdapter::fixed(&mut fixed)).unwrap();
assert_eq!(fixed.remaining(), 0);
secret.to_bytes().to_vec()
}
for crv in EC_CURVES {
let counting: Vec<u8> = (0..ec_len(crv)).map(|i| i as u8).collect();
for bytes in [ec_d(&published_ec_key(crv)), counting] {
let (jwk, left) = ec_from_bytes(crv, &bytes);
assert_eq!(left, 0);
let secret = match crv {
EcCurve::P256 => secret_bytes::<p256::NistP256>(&bytes),
EcCurve::P384 => secret_bytes::<p384::NistP384>(&bytes),
EcCurve::P521 => secret_bytes::<p521::NistP521>(&bytes),
EcCurve::Secp256k1 => secret_bytes::<k256::Secp256k1>(&bytes),
};
assert_eq!(secret, ec_d(&jwk.unwrap()), "{crv:?}");
assert_eq!(secret, bytes, "{crv:?}");
}
}
}
#[test]
fn ec_fails_closed_on_rng_failure() {
for crv in EC_CURVES {
assert_eq!(
generate_ec(
crv,
&mut CryptoRngAdapter::failing(getrandom::Error::UNSUPPORTED)
)
.unwrap_err(),
Error::RandomnessUnavailable
);
let (jwk, _) = ec_from_bytes(crv, &vec![0x11u8; ec_len(crv) - 1]);
assert_eq!(jwk.unwrap_err(), Error::RandomnessUnavailable);
let (jwk, _) = ec_from_bytes(crv, &vec![0u8; ec_len(crv)]);
assert_eq!(jwk.unwrap_err(), Error::RandomnessUnavailable);
}
let failing = || CryptoRngAdapter::failing(getrandom::Error::UNEXPECTED);
assert!(matches!(
generate_ec_secret::<p256::NistP256, _>(&mut failing()),
Err(Error::RandomnessUnavailable)
));
assert!(matches!(
generate_ec_secret::<p384::NistP384, _>(&mut failing()),
Err(Error::RandomnessUnavailable)
));
assert!(matches!(
generate_ec_secret::<p521::NistP521, _>(&mut failing()),
Err(Error::RandomnessUnavailable)
));
assert!(matches!(
generate_ec_secret::<k256::Secp256k1, _>(&mut failing()),
Err(Error::RandomnessUnavailable)
));
}
#[test]
fn debug_of_a_generated_ec_key_redacts_d() {
let jwk = generate(KeySpec::Ec(EcCurve::P256)).unwrap();
let debug = format!("{jwk:?}");
assert!(debug.contains(r#"d: "<redacted>""#), "{debug}");
assert!(!debug.contains(&b64url_encode(&ec_d(&jwk))));
}
#[test]
fn okp_keys_have_32_octet_members_and_validate_after_a_json_round_trip() {
for crv in OKP_CURVES {
let jwk = generate(KeySpec::Okp(crv)).unwrap();
let key = okp_key(&jwk);
assert_eq!(key.crv(), crv);
assert_eq!(key.x().len(), 32);
assert_eq!(okp_d(&jwk).len(), 32);
assert!(jwk.alg().is_none());
let reparsed = Jwk::from_json(jwk.to_json().as_bytes()).unwrap();
assert_eq!(okp_key(&reparsed).crv(), crv);
assert_eq!(okp_key(&reparsed).x(), key.x());
assert_eq!(okp_d(&reparsed), okp_d(&jwk));
assert_eq!(*reparsed.to_json(), *jwk.to_json());
let public = jwk.to_public().expect("an OKP key has a public part");
assert!(public.is_public());
assert_eq!(okp_key(&public).d(), None);
}
}
#[test]
fn okp_derives_the_published_public_keys() {
for crv in OKP_CURVES {
let published = published_okp_key(crv);
let mut fixed = FixedRng::new(okp_d(&published));
let jwk = generate_okp(crv, &mut CryptoRngAdapter::fixed(&mut fixed)).unwrap();
assert_eq!(fixed.remaining(), 0);
assert_eq!(*jwk.to_json(), *published.to_json(), "{crv:?}");
}
}
#[test]
fn x25519_secret_is_the_d_of_generate_okp() {
let counting: Vec<u8> = (0u8..32).collect();
for bytes in [okp_d(&published_okp_key(OkpCurve::X25519)), counting] {
let mut fixed = FixedRng::new(bytes.as_slice());
let secret = generate_x25519_secret(&mut CryptoRngAdapter::fixed(&mut fixed)).unwrap();
assert_eq!(fixed.remaining(), 0);
assert_eq!(&secret.as_bytes()[..], &bytes[..]);
let mut fixed = FixedRng::new(bytes.as_slice());
let jwk =
generate_okp(OkpCurve::X25519, &mut CryptoRngAdapter::fixed(&mut fixed)).unwrap();
assert_eq!(fixed.remaining(), 0);
assert_eq!(okp_d(&jwk), bytes);
let public = x25519_dalek::PublicKey::from(&secret);
assert_eq!(okp_key(&jwk).x(), public.as_bytes());
}
}
#[test]
fn x25519_secret_fails_closed_on_rng_failure() {
assert!(matches!(
generate_x25519_secret(&mut CryptoRngAdapter::failing(getrandom::Error::UNEXPECTED)),
Err(Error::RandomnessUnavailable)
));
let mut fixed = FixedRng::new([0x5Au8; 31]);
assert!(matches!(
generate_x25519_secret(&mut CryptoRngAdapter::fixed(&mut fixed)),
Err(Error::RandomnessUnavailable)
));
}
#[test]
fn a_generated_ed25519_key_signs_and_verifies() {
use ed25519_dalek::{Signer, Verifier};
let jwk = generate(KeySpec::Okp(OkpCurve::Ed25519)).unwrap();
let d: [u8; 32] = okp_d(&jwk).try_into().unwrap();
let x: [u8; 32] = okp_key(&jwk).x().try_into().unwrap();
let signature = ed25519_dalek::SigningKey::from_bytes(&d).sign(b"polyjose");
let verifying = ed25519_dalek::VerifyingKey::from_bytes(&x).unwrap();
assert!(verifying.verify(b"polyjose", &signature).is_ok());
assert!(verifying.verify(b"polyjosE", &signature).is_err());
}
#[test]
fn okp_fails_closed_on_rng_failure() {
for crv in OKP_CURVES {
assert_eq!(
generate_okp(
crv,
&mut CryptoRngAdapter::failing(getrandom::Error::UNSUPPORTED)
)
.unwrap_err(),
Error::RandomnessUnavailable
);
let mut fixed = FixedRng::new([0x5Au8; 31]);
assert_eq!(
generate_okp(crv, &mut CryptoRngAdapter::fixed(&mut fixed)).unwrap_err(),
Error::RandomnessUnavailable
);
}
}
#[test]
fn debug_of_a_generated_okp_key_redacts_d() {
for crv in OKP_CURVES {
let jwk = generate(KeySpec::Okp(crv)).unwrap();
let debug = format!("{jwk:?}");
assert!(debug.contains(r#"d: "<redacted>""#), "{debug}");
assert!(!debug.contains(&b64url_encode(&okp_d(&jwk))));
}
}
#[derive(Debug, PartialEq)]
enum Expect {
Key(KeySpec),
CurveRequired,
Unsupported,
}
fn expected(alg: &JoseAlg) -> Expect {
use Expect::{CurveRequired, Key, Unsupported};
let oct = |bytes| Key(KeySpec::Oct { bytes });
match alg.clone() {
JoseAlg::Signing(SigningAlg::HS256) => oct(32),
JoseAlg::Signing(SigningAlg::HS384) => oct(48),
JoseAlg::Signing(SigningAlg::HS512) => oct(64),
JoseAlg::Signing(SigningAlg::ES256) => Key(KeySpec::Ec(EcCurve::P256)),
JoseAlg::Signing(SigningAlg::ES384) => Key(KeySpec::Ec(EcCurve::P384)),
JoseAlg::Signing(SigningAlg::ES512) => Key(KeySpec::Ec(EcCurve::P521)),
JoseAlg::Signing(SigningAlg::ES256K) => Key(KeySpec::Ec(EcCurve::Secp256k1)),
JoseAlg::Signing(SigningAlg::EdDSA | SigningAlg::Ed25519) => {
Key(KeySpec::Okp(OkpCurve::Ed25519))
}
JoseAlg::KeyMgmt(KeyMgmtAlg::A128KW | KeyMgmtAlg::A128GCMKW) => oct(16),
JoseAlg::KeyMgmt(KeyMgmtAlg::A192KW | KeyMgmtAlg::A192GCMKW) => oct(24),
JoseAlg::KeyMgmt(KeyMgmtAlg::A256KW | KeyMgmtAlg::A256GCMKW) => oct(32),
JoseAlg::KeyMgmt(
KeyMgmtAlg::EcdhEs
| KeyMgmtAlg::EcdhEsA128KW
| KeyMgmtAlg::EcdhEsA192KW
| KeyMgmtAlg::EcdhEsA256KW,
) => CurveRequired,
JoseAlg::ContentEnc(ContentEncAlg::A128GCM) => oct(16),
JoseAlg::ContentEnc(ContentEncAlg::A192GCM) => oct(24),
JoseAlg::ContentEnc(ContentEncAlg::A256GCM) => oct(32),
JoseAlg::ContentEnc(ContentEncAlg::A128CbcHs256) => oct(32),
JoseAlg::ContentEnc(ContentEncAlg::A192CbcHs384) => oct(48),
JoseAlg::ContentEnc(ContentEncAlg::A256CbcHs512) => oct(64),
_ => Unsupported,
}
}
fn every_alg() -> Vec<JoseAlg> {
let mut all: Vec<JoseAlg> = SigningAlg::KNOWN.iter().cloned().map(Into::into).collect();
all.extend(KeyMgmtAlg::KNOWN.iter().cloned().map(JoseAlg::from));
all.extend(ContentEncAlg::KNOWN.iter().cloned().map(JoseAlg::from));
all.push(KeyMgmtAlg::from("RSA-OAEP").into());
all.push(KeyMgmtAlg::from("RSA1_5").into());
all.push(SigningAlg::from("HS1").into());
all.push(ContentEncAlg::from("A512GCM").into());
all
}
fn alg_str(alg: &JoseAlg) -> &str {
match alg {
JoseAlg::Signing(a) => a.as_str(),
JoseAlg::KeyMgmt(a) => a.as_str(),
JoseAlg::ContentEnc(a) => a.as_str(),
}
}
fn spec_of(jwk: &Jwk) -> KeySpec {
match jwk.material() {
KeyMaterial::Oct(key) => KeySpec::Oct {
bytes: key.k().len(),
},
KeyMaterial::Ec(key) => KeySpec::Ec(key.crv()),
KeyMaterial::Okp(key) => KeySpec::Okp(key.crv()),
other => panic!("generated an unexpected key type: {other:?}"),
}
}
#[test]
fn generate_for_follows_the_plan_table_and_sets_alg() {
let mut keys = 0;
for alg in every_alg() {
let got = generate_for(alg.clone());
match expected(&alg) {
Expect::Key(spec) => {
let jwk = got.unwrap_or_else(|e| panic!("{}: {e:?}", alg_str(&alg)));
assert_eq!(spec_of(&jwk), spec, "{}", alg_str(&alg));
assert!(!jwk.is_public());
let member = jwk.alg().map(|a| a.as_str().to_owned());
assert_eq!(member.as_deref(), Some(alg_str(&alg)));
let reparsed = Jwk::from_json(jwk.to_json().as_bytes()).unwrap();
assert_eq!(*reparsed.to_json(), *jwk.to_json());
keys += 1;
}
Expect::CurveRequired => assert_eq!(
got.unwrap_err(),
Error::InvalidKey("curve required"),
"{}",
alg_str(&alg)
),
Expect::Unsupported => assert_eq!(
got.unwrap_err(),
Error::UnsupportedAlgorithm,
"{}",
alg_str(&alg)
),
}
}
assert_eq!(keys, 21);
}
#[test]
fn generate_for_rejects_what_it_cannot_generate() {
for alg in [
JoseAlg::from(SigningAlg::RS256),
SigningAlg::PS512.into(),
SigningAlg::None.into(),
KeyMgmtAlg::RsaOaep256.into(),
KeyMgmtAlg::from("RSA1_5").into(),
KeyMgmtAlg::Pbes2Hs256A128KW.into(),
KeyMgmtAlg::Dir.into(),
SigningAlg::from("XS256").into(),
] {
assert_eq!(generate_for(alg).unwrap_err(), Error::UnsupportedAlgorithm);
}
assert_eq!(
generate_for(KeyMgmtAlg::EcdhEs).unwrap_err(),
Error::InvalidKey("curve required")
);
}
#[test]
fn generate_for_fails_closed_on_rng_failure() {
for alg in [
JoseAlg::from(SigningAlg::HS256),
ContentEncAlg::A256GCM.into(),
SigningAlg::ES256.into(),
SigningAlg::EdDSA.into(),
] {
assert_eq!(
generate_for_with(
&alg,
&mut CryptoRngAdapter::failing(getrandom::Error::UNEXPECTED)
)
.unwrap_err(),
Error::RandomnessUnavailable
);
let mut fixed = FixedRng::new([]);
assert_eq!(
generate_for_with(&alg, &mut CryptoRngAdapter::fixed(&mut fixed)).unwrap_err(),
Error::RandomnessUnavailable
);
}
}
#[test]
fn generate_for_es256_reproduces_a_published_key_with_its_alg() {
let published = published_ec_key(EcCurve::P256);
let mut fixed = FixedRng::new(ec_d(&published));
let jwk = generate_for_with(
&SigningAlg::ES256.into(),
&mut CryptoRngAdapter::fixed(&mut fixed),
)
.unwrap();
assert_eq!(ec_key(&jwk).x(), ec_key(&published).x());
assert_eq!(ec_key(&jwk).y(), ec_key(&published).y());
assert_eq!(ec_d(&jwk), ec_d(&published));
assert_eq!(jwk.alg().map(|a| a.as_str()), Some("ES256"));
}
fn secret_of(jwk: &Jwk) -> Vec<u8> {
match jwk.material() {
KeyMaterial::Oct(key) => key.k().to_vec(),
KeyMaterial::Ec(_) => ec_d(jwk),
KeyMaterial::Okp(_) => okp_d(jwk),
other => panic!("generated an unexpected key type: {other:?}"),
}
}
#[test]
fn consecutive_generations_differ() {
let mut specs = vec![KeySpec::Oct { bytes: 16 }];
specs.extend(EC_CURVES.map(KeySpec::Ec));
specs.extend(OKP_CURVES.map(KeySpec::Okp));
for spec in specs {
let first = generate(spec).unwrap();
let second = generate(spec).unwrap();
assert_ne!(secret_of(&first), secret_of(&second), "{spec:?}");
assert_ne!(*first.to_json(), *second.to_json(), "{spec:?}");
}
}
#[test]
fn debug_of_a_generated_oct_key_redacts_k() {
let jwk = generate_for(SigningAlg::HS256).unwrap();
let debug = format!("{jwk:?}");
assert!(debug.contains(r#"k: "<redacted>""#), "{debug}");
assert!(!debug.contains(&b64url_encode(&oct_k(&jwk))));
}
#[test]
fn ec_sampling_uses_nonzero_scalar_rejection_sampling() {
let source = include_str!("generate.rs");
let production = &source[..source.find("#[cfg(test)]").unwrap()];
assert_eq!(
production
.matches("NonZeroScalar::<C>::try_generate_from_rng(rng)")
.count(),
1
);
assert!(!production.contains("SecretKey::try_generate_from_rng("));
assert!(!production.contains("SecretKey::<C>::try_generate_from_rng("));
assert!(!production.contains(&["impl", "TryCryptoRng"].join(" ")));
}
#[test]
fn x25519_secrets_are_built_from_random_bytes_in_one_place() {
let source = include_str!("generate.rs");
let production = &source[..source.find("#[cfg(test)]").unwrap()];
let needle = ["StaticSecret", "::from("].concat();
assert_eq!(production.matches(needle.as_str()).count(), 1);
let body = &production[production
.find("pub(crate) fn generate_x25519_secret")
.unwrap()..];
let body = &body[..body.find("\n}\n").unwrap()];
assert!(body.contains(needle.as_str()));
}
}