use core::cmp::Ordering;
use core::fmt;
use core::ops::RangeInclusive;
use std::collections::BTreeSet;
use p256::elliptic_curve::sec1::{FromSec1Point, ModulusSize, Sec1Point, ToSec1Point};
use p256::elliptic_curve::{
AffinePoint, Curve, CurveArithmetic, FieldBytes, FieldBytesSize, PublicKey, SecretKey,
};
use subtle::ConstantTimeEq as _;
use zeroize::{ZeroizeOnDrop, Zeroizing};
use crate::error::{Error, Result};
use crate::json::{self, Object, Value};
use crate::jwa::{ContentEncAlg, KeyMgmtAlg, SigningAlg};
use crate::util::{b64url_decode, b64url_encode};
mod set;
mod thumbprint;
pub(crate) mod x25519_consistency;
pub use set::{JwkSelector, JwkSet};
pub use thumbprint::ThumbprintHash;
use x25519_consistency::{X25519_PRIVATE_MISMATCH, x25519_private_matches};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum KeyType {
Oct,
Rsa,
Ec,
Okp,
}
impl KeyType {
pub fn as_str(self) -> &'static str {
match self {
KeyType::Oct => "oct",
KeyType::Rsa => "RSA",
KeyType::Ec => "EC",
KeyType::Okp => "OKP",
}
}
fn from_name(kty: &str) -> Option<KeyType> {
match kty {
"oct" => Some(KeyType::Oct),
"RSA" => Some(KeyType::Rsa),
"EC" => Some(KeyType::Ec),
"OKP" => Some(KeyType::Okp),
_ => None,
}
}
}
impl fmt::Display for KeyType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum EcCurve {
P256,
P384,
P521,
Secp256k1,
}
impl EcCurve {
pub fn as_str(self) -> &'static str {
match self {
EcCurve::P256 => "P-256",
EcCurve::P384 => "P-384",
EcCurve::P521 => "P-521",
EcCurve::Secp256k1 => "secp256k1",
}
}
pub fn coordinate_len(self) -> usize {
match self {
EcCurve::P256 | EcCurve::Secp256k1 => 32,
EcCurve::P384 => 48,
EcCurve::P521 => 66,
}
}
fn from_name(crv: &str) -> Option<EcCurve> {
match crv {
"P-256" => Some(EcCurve::P256),
"P-384" => Some(EcCurve::P384),
"P-521" => Some(EcCurve::P521),
"secp256k1" => Some(EcCurve::Secp256k1),
_ => None,
}
}
}
impl fmt::Display for EcCurve {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum OkpCurve {
Ed25519,
X25519,
}
impl OkpCurve {
pub fn as_str(self) -> &'static str {
match self {
OkpCurve::Ed25519 => "Ed25519",
OkpCurve::X25519 => "X25519",
}
}
pub fn key_len(self) -> usize {
match self {
OkpCurve::Ed25519 | OkpCurve::X25519 => 32,
}
}
fn from_name(crv: &str) -> Option<OkpCurve> {
match crv {
"Ed25519" => Some(OkpCurve::Ed25519),
"X25519" => Some(OkpCurve::X25519),
_ => None,
}
}
}
impl fmt::Display for OkpCurve {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum KeyUse {
Sig,
Enc,
Unknown(String),
}
impl KeyUse {
pub fn as_str(&self) -> &str {
match self {
KeyUse::Sig => "sig",
KeyUse::Enc => "enc",
KeyUse::Unknown(s) => s,
}
}
}
impl From<&str> for KeyUse {
fn from(s: &str) -> KeyUse {
match s {
"sig" => KeyUse::Sig,
"enc" => KeyUse::Enc,
_ => KeyUse::Unknown(s.to_owned()),
}
}
}
impl fmt::Display for KeyUse {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum KeyOp {
Sign,
Verify,
Encrypt,
Decrypt,
WrapKey,
UnwrapKey,
DeriveKey,
DeriveBits,
Unknown(String),
}
impl KeyOp {
pub fn as_str(&self) -> &str {
match self {
KeyOp::Sign => "sign",
KeyOp::Verify => "verify",
KeyOp::Encrypt => "encrypt",
KeyOp::Decrypt => "decrypt",
KeyOp::WrapKey => "wrapKey",
KeyOp::UnwrapKey => "unwrapKey",
KeyOp::DeriveKey => "deriveKey",
KeyOp::DeriveBits => "deriveBits",
KeyOp::Unknown(s) => s,
}
}
fn key_use(&self) -> Option<KeyUse> {
match self {
KeyOp::Sign | KeyOp::Verify => Some(KeyUse::Sig),
KeyOp::Encrypt
| KeyOp::Decrypt
| KeyOp::WrapKey
| KeyOp::UnwrapKey
| KeyOp::DeriveKey
| KeyOp::DeriveBits => Some(KeyUse::Enc),
KeyOp::Unknown(_) => None,
}
}
}
impl From<&str> for KeyOp {
fn from(s: &str) -> KeyOp {
match s {
"sign" => KeyOp::Sign,
"verify" => KeyOp::Verify,
"encrypt" => KeyOp::Encrypt,
"decrypt" => KeyOp::Decrypt,
"wrapKey" => KeyOp::WrapKey,
"unwrapKey" => KeyOp::UnwrapKey,
"deriveKey" => KeyOp::DeriveKey,
"deriveBits" => KeyOp::DeriveBits,
_ => KeyOp::Unknown(s.to_owned()),
}
}
}
impl fmt::Display for KeyOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum KeyAlg {
Signing(SigningAlg),
KeyMgmt(KeyMgmtAlg),
ContentEnc(ContentEncAlg),
Unknown(String),
}
impl KeyAlg {
pub fn as_str(&self) -> &str {
match self {
KeyAlg::Signing(a) => a.as_str(),
KeyAlg::KeyMgmt(a) => a.as_str(),
KeyAlg::ContentEnc(a) => a.as_str(),
KeyAlg::Unknown(s) => s,
}
}
}
impl From<&str> for KeyAlg {
fn from(s: &str) -> KeyAlg {
let signing = SigningAlg::from(s);
if signing.is_known() {
return KeyAlg::Signing(signing);
}
let key_mgmt = KeyMgmtAlg::from(s);
if key_mgmt.is_known() {
return KeyAlg::KeyMgmt(key_mgmt);
}
let content_enc = ContentEncAlg::from(s);
if content_enc.is_known() {
return KeyAlg::ContentEnc(content_enc);
}
KeyAlg::Unknown(s.to_owned())
}
}
impl fmt::Display for KeyAlg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
const NOT_AN_OBJECT: &str = "a JWK must be a JSON object";
const REDACTED: &str = "<redacted>";
const COMMON_MEMBERS: &[&str] = &[
"kty", "use", "key_ops", "alg", "kid", "x5u", "x5c", "x5t", "x5t#S256",
];
#[derive(Clone)]
pub struct OctKey {
k: Zeroizing<Vec<u8>>,
}
impl OctKey {
pub fn len(&self) -> usize {
self.k.len()
}
pub fn is_empty(&self) -> bool {
self.k.is_empty()
}
pub(crate) fn k(&self) -> &[u8] {
&self.k
}
}
impl fmt::Debug for OctKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OctKey").field("k", &REDACTED).finish()
}
}
impl ZeroizeOnDrop for OctKey {}
pub(crate) const RSA_MODULUS_BITS: RangeInclusive<usize> = 2048..=8192;
#[derive(Clone)]
pub struct RsaKey {
n: Vec<u8>,
e: Vec<u8>,
private: Option<RsaPrivateParts>,
}
#[deprecated(since = "0.2.0", note = "use RsaKey")]
pub type RsaPublicKey = RsaKey;
#[derive(Clone)]
pub(crate) struct RsaPrivateParts {
pub(crate) d: Zeroizing<Vec<u8>>,
pub(crate) p: Zeroizing<Vec<u8>>,
pub(crate) q: Zeroizing<Vec<u8>>,
pub(crate) dp: Zeroizing<Vec<u8>>,
pub(crate) dq: Zeroizing<Vec<u8>>,
pub(crate) qi: Zeroizing<Vec<u8>>,
}
impl RsaPrivateParts {
pub(crate) fn members(&self) -> [(&'static str, &[u8]); 6] {
[
("d", &self.d),
("p", &self.p),
("q", &self.q),
("dp", &self.dp),
("dq", &self.dq),
("qi", &self.qi),
]
}
}
impl ZeroizeOnDrop for RsaPrivateParts {}
impl RsaKey {
pub fn n(&self) -> &[u8] {
&self.n
}
pub fn e(&self) -> &[u8] {
&self.e
}
pub fn modulus_bits(&self) -> usize {
bit_len(&self.n)
}
pub fn is_private(&self) -> bool {
self.private.is_some()
}
pub(crate) fn private(&self) -> Option<&RsaPrivateParts> {
self.private.as_ref()
}
fn to_public(&self) -> RsaKey {
RsaKey {
n: self.n.clone(),
e: self.e.clone(),
private: None,
}
}
}
impl fmt::Debug for RsaKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("RsaKey");
s.field("n", &b64url_encode(&self.n))
.field("e", &b64url_encode(&self.e));
if let Some(private) = &self.private {
for (name, _) in private.members() {
s.field(name, &REDACTED);
}
}
s.finish()
}
}
impl ZeroizeOnDrop for RsaKey {}
#[derive(Clone)]
pub struct EcKey {
crv: EcCurve,
x: Vec<u8>,
y: Vec<u8>,
d: Option<Zeroizing<Vec<u8>>>,
}
impl EcKey {
pub fn crv(&self) -> EcCurve {
self.crv
}
pub fn x(&self) -> &[u8] {
&self.x
}
pub fn y(&self) -> &[u8] {
&self.y
}
pub fn is_private(&self) -> bool {
self.d.is_some()
}
pub(crate) fn d(&self) -> Option<&[u8]> {
self.d.as_deref().map(Vec::as_slice)
}
fn to_public(&self) -> EcKey {
EcKey {
crv: self.crv,
x: self.x.clone(),
y: self.y.clone(),
d: None,
}
}
}
impl fmt::Debug for EcKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("EcKey");
s.field("crv", &self.crv)
.field("x", &b64url_encode(&self.x))
.field("y", &b64url_encode(&self.y));
if self.d.is_some() {
s.field("d", &REDACTED);
}
s.finish()
}
}
impl ZeroizeOnDrop for EcKey {}
#[derive(Clone)]
pub struct OkpKey {
crv: OkpCurve,
x: Vec<u8>,
d: Option<Zeroizing<Vec<u8>>>,
}
impl OkpKey {
pub fn crv(&self) -> OkpCurve {
self.crv
}
pub fn x(&self) -> &[u8] {
&self.x
}
pub fn is_private(&self) -> bool {
self.d.is_some()
}
pub(crate) fn d(&self) -> Option<&[u8]> {
self.d.as_deref().map(Vec::as_slice)
}
fn to_public(&self) -> OkpKey {
OkpKey {
crv: self.crv,
x: self.x.clone(),
d: None,
}
}
}
impl fmt::Debug for OkpKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("OkpKey");
s.field("crv", &self.crv)
.field("x", &b64url_encode(&self.x));
if self.d.is_some() {
s.field("d", &REDACTED);
}
s.finish()
}
}
impl ZeroizeOnDrop for OkpKey {}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum KeyMaterial {
Oct(OctKey),
Rsa(RsaKey),
Ec(EcKey),
Okp(OkpKey),
}
impl KeyMaterial {
pub fn key_type(&self) -> KeyType {
match self {
KeyMaterial::Oct(_) => KeyType::Oct,
KeyMaterial::Rsa(_) => KeyType::Rsa,
KeyMaterial::Ec(_) => KeyType::Ec,
KeyMaterial::Okp(_) => KeyType::Okp,
}
}
pub(crate) fn serves_signing_alg(&self, alg: &SigningAlg) -> bool {
use SigningAlg as A;
match (alg, self) {
(A::HS256 | A::HS384 | A::HS512, KeyMaterial::Oct(_)) => true,
(
A::RS256 | A::RS384 | A::RS512 | A::PS256 | A::PS384 | A::PS512,
KeyMaterial::Rsa(_),
) => true,
(A::ES256, KeyMaterial::Ec(key)) => key.crv == EcCurve::P256,
(A::ES384, KeyMaterial::Ec(key)) => key.crv == EcCurve::P384,
(A::ES512, KeyMaterial::Ec(key)) => key.crv == EcCurve::P521,
(A::ES256K, KeyMaterial::Ec(key)) => key.crv == EcCurve::Secp256k1,
(A::EdDSA | A::Ed25519, KeyMaterial::Okp(key)) => key.crv == OkpCurve::Ed25519,
(
A::HS256
| A::HS384
| A::HS512
| A::RS256
| A::RS384
| A::RS512
| A::PS256
| A::PS384
| A::PS512
| A::ES256
| A::ES384
| A::ES512
| A::ES256K
| A::EdDSA
| A::Ed25519
| A::None
| A::Unknown(_),
_,
) => false,
}
}
pub(crate) fn serves_key_mgmt_alg(&self, alg: &KeyMgmtAlg) -> bool {
use KeyMgmtAlg as A;
match (alg, self) {
(A::RsaOaep256, KeyMaterial::Rsa(_)) => true,
(
A::A128KW
| A::A192KW
| A::A256KW
| A::Dir
| A::A128GCMKW
| A::A192GCMKW
| A::A256GCMKW
| A::Pbes2Hs256A128KW
| A::Pbes2Hs384A192KW
| A::Pbes2Hs512A256KW,
KeyMaterial::Oct(_),
) => true,
(
A::EcdhEs | A::EcdhEsA128KW | A::EcdhEsA192KW | A::EcdhEsA256KW,
KeyMaterial::Ec(key),
) => matches!(key.crv, EcCurve::P256 | EcCurve::P384 | EcCurve::P521),
(
A::EcdhEs | A::EcdhEsA128KW | A::EcdhEsA192KW | A::EcdhEsA256KW,
KeyMaterial::Okp(key),
) => key.crv == OkpCurve::X25519,
(
A::RsaOaep256
| A::A128KW
| A::A192KW
| A::A256KW
| A::Dir
| A::EcdhEs
| A::EcdhEsA128KW
| A::EcdhEsA192KW
| A::EcdhEsA256KW
| A::A128GCMKW
| A::A192GCMKW
| A::A256GCMKW
| A::Pbes2Hs256A128KW
| A::Pbes2Hs384A192KW
| A::Pbes2Hs512A256KW
| A::Unknown(_),
_,
) => false,
}
}
pub(crate) fn serves_content_enc_alg(&self, enc: &ContentEncAlg) -> bool {
use ContentEncAlg as E;
match (enc, self) {
(
E::A128CbcHs256
| E::A192CbcHs384
| E::A256CbcHs512
| E::A128GCM
| E::A192GCM
| E::A256GCM,
KeyMaterial::Oct(_),
) => true,
(
E::A128CbcHs256
| E::A192CbcHs384
| E::A256CbcHs512
| E::A128GCM
| E::A192GCM
| E::A256GCM
| E::Unknown(_),
_,
) => false,
}
}
}
const UNSUPPORTED_KEY_ALGS: &[&str] = &["RSA1_5", "RSA-OAEP"];
fn check_alg_binding(material: &KeyMaterial, alg: &KeyAlg) -> Result<()> {
let serves = match alg {
KeyAlg::Signing(alg) => material.serves_signing_alg(alg),
KeyAlg::KeyMgmt(alg) => material.serves_key_mgmt_alg(alg),
KeyAlg::ContentEnc(enc) => material.serves_content_enc_alg(enc),
KeyAlg::Unknown(name) if UNSUPPORTED_KEY_ALGS.contains(&name.as_str()) => {
return Err(Error::UnsupportedAlgorithm);
}
KeyAlg::Unknown(_) => true,
};
if serves {
Ok(())
} else {
Err(Error::KeyAlgorithmMismatch)
}
}
impl ZeroizeOnDrop for KeyMaterial {}
#[derive(Clone)]
pub struct Jwk {
material: KeyMaterial,
key_use: Option<KeyUse>,
key_ops: Option<Vec<KeyOp>>,
alg: Option<KeyAlg>,
kid: Option<String>,
x5u: Option<String>,
x5c: Option<Vec<String>>,
x5t: Option<String>,
x5t_s256: Option<String>,
other: Object,
}
impl Jwk {
pub fn from_json(bytes: &[u8]) -> Result<Jwk> {
let object = json::parse(bytes)?
.into_object()
.ok_or(Error::InvalidJson(NOT_AN_OBJECT))?;
Jwk::from_object(&object)
}
pub fn from_object(object: &Object) -> Result<Jwk> {
let material = match key_kind(object)? {
KeyKind::Oct => KeyMaterial::Oct(parse_oct(object)?),
KeyKind::Rsa => KeyMaterial::Rsa(parse_rsa(object)?),
KeyKind::Ec(crv) => KeyMaterial::Ec(parse_ec(object, crv)?),
KeyKind::Okp(crv) => KeyMaterial::Okp(parse_okp(object, crv)?),
};
let kty = material.key_type();
let key_use = optional_str(object, "use", "use must be a string")?.map(KeyUse::from);
let key_ops = parse_key_ops(object)?;
check_use_matches_key_ops(key_use.as_ref(), key_ops.as_deref())?;
let alg = optional_str(object, "alg", "alg must be a string")?.map(KeyAlg::from);
let kid = optional_str(object, "kid", "kid must be a string")?.map(str::to_owned);
let x5u = optional_str(object, "x5u", "x5u must be a string")?.map(str::to_owned);
let x5c = parse_x5c(object)?;
let x5t = optional_str(object, "x5t", "x5t must be a string")?.map(str::to_owned);
let x5t_s256 =
optional_str(object, "x5t#S256", "x5t#S256 must be a string")?.map(str::to_owned);
if let Some(alg) = &alg {
check_alg_binding(&material, alg)?;
}
let typed = type_members(kty);
let other = Object::from_unique_members(
object
.iter()
.filter(|(name, _)| !COMMON_MEMBERS.contains(name) && !typed.contains(name))
.map(|(name, value)| member(name, value.clone()))
.collect(),
);
Ok(Jwk {
material,
key_use,
key_ops,
alg,
kid,
x5u,
x5c,
x5t,
x5t_s256,
other,
})
}
pub fn to_object(&self) -> Object {
let mut o = Vec::with_capacity(COMMON_MEMBERS.len() + 4 + self.other.len());
o.push(member("kty", self.key_type().as_str()));
match &self.material {
KeyMaterial::Oct(key) => {
o.push(member("k", b64url_encode(&key.k)));
}
KeyMaterial::Rsa(key) => {
o.push(member("n", b64url_encode(&key.n)));
o.push(member("e", b64url_encode(&key.e)));
if let Some(private) = &key.private {
for (name, value) in private.members() {
o.push(member(name, b64url_encode(value)));
}
}
}
KeyMaterial::Ec(key) => {
o.push(member("crv", key.crv.as_str()));
o.push(member("x", b64url_encode(&key.x)));
o.push(member("y", b64url_encode(&key.y)));
if let Some(d) = &key.d {
o.push(member("d", b64url_encode(d)));
}
}
KeyMaterial::Okp(key) => {
o.push(member("crv", key.crv.as_str()));
o.push(member("x", b64url_encode(&key.x)));
if let Some(d) = &key.d {
o.push(member("d", b64url_encode(d)));
}
}
}
if let Some(v) = &self.key_use {
o.push(member("use", v.as_str()));
}
if let Some(ops) = &self.key_ops {
let ops: Vec<Value> = ops.iter().map(|op| Value::from(op.as_str())).collect();
o.push(member("key_ops", ops));
}
if let Some(v) = &self.alg {
o.push(member("alg", v.as_str()));
}
if let Some(v) = &self.kid {
o.push(member("kid", v.as_str()));
}
if let Some(v) = &self.x5u {
o.push(member("x5u", v.as_str()));
}
if let Some(certs) = &self.x5c {
let certs: Vec<Value> = certs.iter().map(|c| Value::from(c.as_str())).collect();
o.push(member("x5c", certs));
}
if let Some(v) = &self.x5t {
o.push(member("x5t", v.as_str()));
}
if let Some(v) = &self.x5t_s256 {
o.push(member("x5t#S256", v.as_str()));
}
o.extend(
self.other
.iter()
.map(|(name, value)| member(name, value.clone())),
);
Object::from_unique_members(o)
}
pub fn to_json(&self) -> Zeroizing<String> {
Zeroizing::new(self.to_object().to_json())
}
pub fn to_public(&self) -> Option<Jwk> {
let material = match &self.material {
KeyMaterial::Oct(_) => return None,
KeyMaterial::Rsa(key) => KeyMaterial::Rsa(key.to_public()),
KeyMaterial::Ec(key) => KeyMaterial::Ec(key.to_public()),
KeyMaterial::Okp(key) => KeyMaterial::Okp(key.to_public()),
};
Some(Jwk {
material,
key_use: self.key_use.clone(),
key_ops: self.key_ops.clone(),
alg: self.alg.clone(),
kid: self.kid.clone(),
x5u: self.x5u.clone(),
x5c: self.x5c.clone(),
x5t: self.x5t.clone(),
x5t_s256: self.x5t_s256.clone(),
other: Object::new(),
})
}
pub fn is_public(&self) -> bool {
match &self.material {
KeyMaterial::Oct(_) => false,
KeyMaterial::Rsa(key) => !key.is_private(),
KeyMaterial::Ec(key) => !key.is_private(),
KeyMaterial::Okp(key) => !key.is_private(),
}
}
pub(crate) fn thumbprint_members(&self) -> Object {
let mut o = Object::new();
match &self.material {
KeyMaterial::Oct(key) => {
o.insert("k", b64url_encode(&key.k));
o.insert("kty", KeyType::Oct.as_str());
}
KeyMaterial::Rsa(key) => {
o.insert("e", b64url_encode(&key.e));
o.insert("kty", KeyType::Rsa.as_str());
o.insert("n", b64url_encode(&key.n));
}
KeyMaterial::Ec(key) => {
o.insert("crv", key.crv.as_str());
o.insert("kty", KeyType::Ec.as_str());
o.insert("x", b64url_encode(&key.x));
o.insert("y", b64url_encode(&key.y));
}
KeyMaterial::Okp(key) => {
o.insert("crv", key.crv.as_str());
o.insert("kty", KeyType::Okp.as_str());
o.insert("x", b64url_encode(&key.x));
}
}
o
}
pub fn key_type(&self) -> KeyType {
self.material.key_type()
}
pub fn material(&self) -> &KeyMaterial {
&self.material
}
pub fn key_use(&self) -> Option<&KeyUse> {
self.key_use.as_ref()
}
pub fn key_ops(&self) -> Option<&[KeyOp]> {
self.key_ops.as_deref()
}
pub fn alg(&self) -> Option<&KeyAlg> {
self.alg.as_ref()
}
pub fn kid(&self) -> Option<&str> {
self.kid.as_deref()
}
pub fn x5u(&self) -> Option<&str> {
self.x5u.as_deref()
}
pub fn x5c(&self) -> Option<&[String]> {
self.x5c.as_deref()
}
pub fn x5t(&self) -> Option<&str> {
self.x5t.as_deref()
}
pub fn x5t_s256(&self) -> Option<&str> {
self.x5t_s256.as_deref()
}
pub fn other_params(&self) -> &Object {
&self.other
}
pub fn permits(&self, op: &KeyOp) -> bool {
let use_ok = self
.key_use
.as_ref()
.is_none_or(|key_use| op.key_use().as_ref() == Some(key_use));
let ops_ok = self.key_ops.as_ref().is_none_or(|ops| ops.contains(op));
use_ok && ops_ok
}
}
impl fmt::Debug for Jwk {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Jwk")
.field("material", &self.material)
.field("use", &self.key_use)
.field("key_ops", &self.key_ops)
.field("alg", &self.alg)
.field("kid", &self.kid)
.field("x5u", &self.x5u)
.field("x5c", &self.x5c)
.field("x5t", &self.x5t)
.field("x5t#S256", &self.x5t_s256)
.field("other_names", &MemberNames(&self.other))
.finish()
}
}
struct MemberNames<'a>(&'a Object);
impl fmt::Debug for MemberNames<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list()
.entries(self.0.iter().map(|(name, _)| name))
.finish()
}
}
impl ZeroizeOnDrop for Jwk {}
#[cfg(test)]
impl Jwk {
pub(crate) fn okp_unchecked(crv: OkpCurve, x: &[u8], d: Option<&[u8]>) -> Jwk {
Jwk {
material: KeyMaterial::Okp(OkpKey {
crv,
x: x.to_vec(),
d: d.map(|d| Zeroizing::new(d.to_vec())),
}),
key_use: None,
key_ops: None,
alg: None,
kid: None,
x5u: None,
x5c: None,
x5t: None,
x5t_s256: None,
other: Object::new(),
}
}
}
fn member(name: &str, value: impl Into<Value>) -> (Zeroizing<String>, Value) {
(Zeroizing::new(name.to_owned()), value.into())
}
fn type_members(kty: KeyType) -> &'static [&'static str] {
match kty {
KeyType::Oct => &["k"],
KeyType::Rsa => &["n", "e", "d", "p", "q", "dp", "dq", "qi"],
KeyType::Ec => &["crv", "x", "y", "d"],
KeyType::Okp => &["crv", "x", "d"],
}
}
fn required_str<'a>(
object: &'a Object,
name: &str,
missing: &'static str,
not_string: &'static str,
) -> Result<&'a str> {
optional_str(object, name, not_string)?.ok_or(Error::InvalidKey(missing))
}
fn optional_str<'a>(
object: &'a Object,
name: &str,
not_string: &'static str,
) -> Result<Option<&'a str>> {
match object.get(name) {
None => Ok(None),
Some(value) => value
.as_str()
.map(Some)
.ok_or(Error::InvalidKey(not_string)),
}
}
fn required_secret(
object: &Object,
name: &str,
missing: &'static str,
not_string: &'static str,
) -> Result<Zeroizing<Vec<u8>>> {
let encoded = required_str(object, name, missing, not_string)?;
Ok(Zeroizing::new(b64url_decode(encoded)?))
}
fn optional_secret(
object: &Object,
name: &str,
not_string: &'static str,
) -> Result<Option<Zeroizing<Vec<u8>>>> {
match optional_str(object, name, not_string)? {
None => Ok(None),
Some(encoded) => Ok(Some(Zeroizing::new(b64url_decode(encoded)?))),
}
}
fn parse_oct(object: &Object) -> Result<OctKey> {
let k = required_secret(object, "k", "k is missing", "k must be a string")?;
if k.is_empty() {
return Err(Error::InvalidKey("k must not be empty"));
}
Ok(OctKey { k })
}
#[derive(Clone, Copy)]
enum KeyKind {
Oct,
Rsa,
Ec(EcCurve),
Okp(OkpCurve),
}
fn key_kind(object: &Object) -> Result<KeyKind> {
let kty = required_str(object, "kty", "kty is missing", "kty must be a string")?;
let kty = KeyType::from_name(kty).ok_or(Error::UnsupportedAlgorithm)?;
let crv = || required_str(object, "crv", "crv is missing", "crv must be a string");
Ok(match kty {
KeyType::Oct => KeyKind::Oct,
KeyType::Rsa => KeyKind::Rsa,
KeyType::Ec => KeyKind::Ec(EcCurve::from_name(crv()?).ok_or(Error::UnsupportedAlgorithm)?),
KeyType::Okp => {
KeyKind::Okp(OkpCurve::from_name(crv()?).ok_or(Error::UnsupportedAlgorithm)?)
}
})
}
pub(crate) fn is_unsupported_key_kind(object: &Object) -> bool {
matches!(key_kind(object), Err(Error::UnsupportedAlgorithm))
}
const RSA_PRIVATE_MEMBERS: [&str; 6] = ["d", "p", "q", "dp", "dq", "qi"];
const RSA_PRIVATE_INCOMPLETE: &str = "RSA private key must have d, p, q, dp, dq and qi";
fn required_uint(
object: &Object,
name: &str,
missing: &'static str,
not_string: &'static str,
not_minimal: &'static str,
) -> Result<Vec<u8>> {
let octets = b64url_decode(required_str(object, name, missing, not_string)?)?;
match octets.as_slice() {
[] => Err(Error::InvalidKey(not_minimal)),
[0, _, ..] => Err(Error::InvalidKey(not_minimal)),
_ => Ok(octets),
}
}
fn required_secret_uint(
object: &Object,
name: &str,
not_minimal: &'static str,
) -> Result<Zeroizing<Vec<u8>>> {
let octets = required_secret(
object,
name,
RSA_PRIVATE_INCOMPLETE,
"RSA private key members must be strings",
)?;
match octets.as_slice() {
[] | [0, _, ..] => Err(Error::InvalidKey(not_minimal)),
_ => Ok(octets),
}
}
fn bit_len(octets: &[u8]) -> usize {
let top = octets.first().copied().unwrap_or(0);
octets
.len()
.saturating_mul(8)
.saturating_sub(top.leading_zeros() as usize)
}
fn parse_rsa(object: &Object) -> Result<RsaKey> {
if object.contains("oth") {
return Err(Error::UnsupportedAlgorithm);
}
let n = required_uint(
object,
"n",
"n is missing",
"n must be a string",
"n is not a minimal Base64urlUInt",
)?;
let Some(&last) = n.last() else {
return Err(Error::InvalidKey("n is not a minimal Base64urlUInt"));
};
let bits = bit_len(&n);
if bits < *RSA_MODULUS_BITS.start() {
return Err(Error::InvalidKey("RSA modulus is shorter than 2048 bits"));
}
if bits > *RSA_MODULUS_BITS.end() {
return Err(Error::InvalidKey("RSA modulus is longer than 8192 bits"));
}
if last & 1 == 0 {
return Err(Error::InvalidKey("RSA modulus must be odd"));
}
let e = required_uint(
object,
"e",
"e is missing",
"e must be a string",
"e is not a minimal Base64urlUInt",
)?;
let e_odd = e.last().is_some_and(|last| last & 1 == 1);
if !e_odd || e.as_slice() == [1] {
return Err(Error::InvalidKey(
"RSA exponent must be odd and greater than 1",
));
}
if e.len() > n.len() || (e.len() == n.len() && e >= n) {
return Err(Error::InvalidKey(
"RSA exponent must be less than the modulus",
));
}
let private = parse_rsa_private(object)?;
if let Some(private) = &private {
crate::keys::rsa::check_private(&n, &e, private)?;
}
Ok(RsaKey { n, e, private })
}
fn parse_rsa_private(object: &Object) -> Result<Option<RsaPrivateParts>> {
let present = RSA_PRIVATE_MEMBERS
.iter()
.filter(|name| object.contains(name))
.count();
if present == 0 {
return Ok(None);
}
if present != RSA_PRIVATE_MEMBERS.len() {
return Err(Error::InvalidKey(RSA_PRIVATE_INCOMPLETE));
}
Ok(Some(RsaPrivateParts {
d: required_secret_uint(object, "d", "d is not a minimal Base64urlUInt")?,
p: required_secret_uint(object, "p", "p is not a minimal Base64urlUInt")?,
q: required_secret_uint(object, "q", "q is not a minimal Base64urlUInt")?,
dp: required_secret_uint(object, "dp", "dp is not a minimal Base64urlUInt")?,
dq: required_secret_uint(object, "dq", "dq is not a minimal Base64urlUInt")?,
qi: required_secret_uint(object, "qi", "qi is not a minimal Base64urlUInt")?,
}))
}
fn parse_ec(object: &Object, crv: EcCurve) -> Result<EcKey> {
let len = crv.coordinate_len();
let x = b64url_decode(required_str(
object,
"x",
"x is missing",
"x must be a string",
)?)?;
if x.len() != len {
return Err(Error::InvalidKey("x has the wrong length for crv"));
}
let y = b64url_decode(required_str(
object,
"y",
"y is missing",
"y must be a string",
)?)?;
if y.len() != len {
return Err(Error::InvalidKey("y has the wrong length for crv"));
}
let d = optional_secret(object, "d", "d must be a string")?;
if d.as_ref().is_some_and(|d| d.len() != len) {
return Err(Error::InvalidKey("d has the wrong length for crv"));
}
if !ec_point_is_valid(crv, &x, &y) {
return Err(Error::InvalidKey(EC_POINT_INVALID));
}
if d.as_ref().is_some_and(|d| !ec_scalar_is_valid(crv, d)) {
return Err(Error::InvalidKey(EC_SCALAR_INVALID));
}
if d.as_ref()
.is_some_and(|d| !ec_private_matches(crv, &x, &y, d))
{
return Err(Error::InvalidKey(EC_PRIVATE_MISMATCH));
}
Ok(EcKey { crv, x, y, d })
}
const EC_POINT_INVALID: &str = "EC point is not on the curve";
const EC_SCALAR_INVALID: &str = "EC private key d is out of range";
const EC_PRIVATE_MISMATCH: &str = "EC private key d does not match x and y";
const ED25519_PUBLIC_INVALID: &str = "Ed25519 public key is invalid";
const ED25519_PRIVATE_MISMATCH: &str = "Ed25519 private key d does not match x";
fn ec_point_is_valid(crv: EcCurve, x: &[u8], y: &[u8]) -> bool {
match crv {
EcCurve::P256 => point_on_curve::<p256::NistP256>(x, y),
EcCurve::P384 => point_on_curve::<p384::NistP384>(x, y),
EcCurve::P521 => point_on_curve::<p521::NistP521>(x, y),
EcCurve::Secp256k1 => point_on_curve::<k256::Secp256k1>(x, y),
}
}
fn ec_scalar_is_valid(crv: EcCurve, d: &[u8]) -> bool {
match crv {
EcCurve::P256 => scalar_in_range::<p256::NistP256>(d),
EcCurve::P384 => scalar_in_range::<p384::NistP384>(d),
EcCurve::P521 => scalar_in_range::<p521::NistP521>(d),
EcCurve::Secp256k1 => scalar_in_range::<k256::Secp256k1>(d),
}
}
fn ec_private_matches(crv: EcCurve, x: &[u8], y: &[u8], d: &[u8]) -> bool {
match crv {
EcCurve::P256 => private_matches_point::<p256::NistP256>(x, y, d),
EcCurve::P384 => private_matches_point::<p384::NistP384>(x, y, d),
EcCurve::P521 => private_matches_point::<p521::NistP521>(x, y, d),
EcCurve::Secp256k1 => private_matches_point::<k256::Secp256k1>(x, y, d),
}
}
fn private_matches_point<C>(x: &[u8], y: &[u8], d: &[u8]) -> bool
where
C: CurveArithmetic,
AffinePoint<C>: FromSec1Point<C> + ToSec1Point<C>,
FieldBytesSize<C>: ModulusSize,
{
let (Ok(x), Ok(y), Ok(d)) = (
<&FieldBytes<C>>::try_from(x),
<&FieldBytes<C>>::try_from(y),
<&FieldBytes<C>>::try_from(d),
) else {
return false;
};
let Ok(secret) = SecretKey::<C>::from_bytes(d) else {
return false;
};
let point = Sec1Point::<C>::from_affine_coordinates(x, y, false);
let Some(public) = PublicKey::<C>::from_sec1_point(&point).into_option() else {
return false;
};
bool::from(
secret
.public_key()
.to_projective()
.ct_eq(&public.to_projective()),
)
}
fn point_on_curve<C>(x: &[u8], y: &[u8]) -> bool
where
C: CurveArithmetic,
AffinePoint<C>: FromSec1Point<C> + ToSec1Point<C>,
FieldBytesSize<C>: ModulusSize,
{
let (Ok(x), Ok(y)) = (<&FieldBytes<C>>::try_from(x), <&FieldBytes<C>>::try_from(y)) else {
return false;
};
let point = Sec1Point::<C>::from_affine_coordinates(x, y, false);
PublicKey::<C>::from_sec1_point(&point)
.into_option()
.is_some()
}
fn scalar_in_range<C: Curve>(d: &[u8]) -> bool {
<&FieldBytes<C>>::try_from(d).is_ok_and(|d| SecretKey::<C>::from_bytes(d).is_ok())
}
pub(crate) fn ed25519_public_is_valid(x: &[u8]) -> bool {
let Ok(bytes) = <&[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>::try_from(x) else {
return false;
};
let Ok(key) = ed25519_dalek::VerifyingKey::from_bytes(bytes) else {
return false;
};
let point = key.to_edwards();
let canonical = ed25519_dalek::VerifyingKey::from(point).to_bytes() == *bytes;
canonical && !key.is_weak() && point.is_torsion_free()
}
fn ed25519_private_matches(x: &[u8], d: &[u8]) -> bool {
let Ok(seed) = <&ed25519_dalek::SecretKey>::try_from(d) else {
return false;
};
let signing_key = ed25519_dalek::SigningKey::from_bytes(seed);
bool::from(signing_key.verifying_key().as_bytes()[..].ct_eq(x))
}
fn parse_okp(object: &Object, crv: OkpCurve) -> Result<OkpKey> {
let len = crv.key_len();
let x = b64url_decode(required_str(
object,
"x",
"x is missing",
"x must be a string",
)?)?;
if x.len() != len {
return Err(Error::InvalidKey("x has the wrong length for crv"));
}
let d = optional_secret(object, "d", "d must be a string")?;
if d.as_ref().is_some_and(|d| d.len() != len) {
return Err(Error::InvalidKey("d has the wrong length for crv"));
}
if crv == OkpCurve::Ed25519 && !ed25519_public_is_valid(&x) {
return Err(Error::InvalidKey(ED25519_PUBLIC_INVALID));
}
if crv == OkpCurve::Ed25519 && d.as_ref().is_some_and(|d| !ed25519_private_matches(&x, d)) {
return Err(Error::InvalidKey(ED25519_PRIVATE_MISMATCH));
}
if crv == OkpCurve::X25519 && d.as_ref().is_some_and(|d| !x25519_private_matches(&x, d)) {
return Err(Error::InvalidKey(X25519_PRIVATE_MISMATCH));
}
Ok(OkpKey { crv, x, d })
}
fn check_use_matches_key_ops(key_use: Option<&KeyUse>, key_ops: Option<&[KeyOp]>) -> Result<()> {
let (Some(key_use @ (KeyUse::Sig | KeyUse::Enc)), Some(ops)) = (key_use, key_ops) else {
return Ok(());
};
for op in ops {
if op.key_use().is_some_and(|op_use| op_use != *key_use) {
return Err(Error::InvalidKey("use and key_ops are inconsistent"));
}
}
Ok(())
}
fn parse_key_ops(object: &Object) -> Result<Option<Vec<KeyOp>>> {
const NOT_STRINGS: &str = "key_ops must be an array of strings";
let Some(value) = object.get("key_ops") else {
return Ok(None);
};
let items = value.as_array().ok_or(Error::InvalidKey(NOT_STRINGS))?;
let mut ops: Vec<KeyOp> = Vec::with_capacity(items.len());
let mut seen = BTreeSet::new();
for item in items {
let name = item.as_str().ok_or(Error::InvalidKey(NOT_STRINGS))?;
if !seen.insert(KeyOpName(name)) {
return Err(Error::InvalidKey("key_ops must not contain duplicates"));
}
ops.push(KeyOp::from(name));
}
Ok(Some(ops))
}
#[derive(PartialEq, Eq)]
struct KeyOpName<'a>(&'a str);
impl Ord for KeyOpName<'_> {
fn cmp(&self, other: &Self) -> Ordering {
#[cfg(test)]
key_op_comparisons::tick();
self.0.cmp(other.0)
}
}
impl PartialOrd for KeyOpName<'_> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[cfg(test)]
use crate::test_counters::key_op_comparisons;
fn parse_x5c(object: &Object) -> Result<Option<Vec<String>>> {
const NOT_STRINGS: &str = "x5c must be an array of strings";
let Some(value) = object.get("x5c") else {
return Ok(None);
};
let items = value.as_array().ok_or(Error::InvalidKey(NOT_STRINGS))?;
items
.iter()
.map(|item| {
item.as_str()
.map(str::to_owned)
.ok_or(Error::InvalidKey(NOT_STRINGS))
})
.collect::<Result<Vec<String>>>()
.map(Some)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn key_type_names_round_trip_case_sensitively() {
for kty in [KeyType::Oct, KeyType::Rsa, KeyType::Ec, KeyType::Okp] {
assert_eq!(KeyType::from_name(kty.as_str()), Some(kty));
assert_eq!(kty.to_string(), kty.as_str());
}
for other in ["ec", "rsa", "OCT", "okp", "", "EC "] {
assert_eq!(KeyType::from_name(other), None, "{other}");
}
}
#[test]
fn curve_names_and_lengths() {
let ec = [
(EcCurve::P256, "P-256", 32),
(EcCurve::P384, "P-384", 48),
(EcCurve::P521, "P-521", 66),
(EcCurve::Secp256k1, "secp256k1", 32),
];
for (crv, name, len) in ec {
assert_eq!(crv.as_str(), name);
assert_eq!(crv.to_string(), name);
assert_eq!(crv.coordinate_len(), len);
assert_eq!(EcCurve::from_name(name), Some(crv));
}
for (crv, name) in [(OkpCurve::Ed25519, "Ed25519"), (OkpCurve::X25519, "X25519")] {
assert_eq!(crv.as_str(), name);
assert_eq!(crv.to_string(), name);
assert_eq!(crv.key_len(), 32);
assert_eq!(OkpCurve::from_name(name), Some(crv));
}
for other in ["p-256", "P256", "Ed25519", "secp256r1", ""] {
assert_eq!(EcCurve::from_name(other), None, "{other}");
}
for other in ["Ed448", "X448", "ed25519", "P-256", ""] {
assert_eq!(OkpCurve::from_name(other), None, "{other}");
}
}
#[test]
fn use_values_round_trip_and_keep_unknown_verbatim() {
assert_eq!(KeyUse::from("sig"), KeyUse::Sig);
assert_eq!(KeyUse::from("enc"), KeyUse::Enc);
for s in ["sig", "enc", "Sig", "tls", ""] {
assert_eq!(KeyUse::from(s).as_str(), s);
assert_eq!(KeyUse::from(s).to_string(), s);
}
assert_eq!(KeyUse::from("SIG"), KeyUse::Unknown("SIG".into()));
}
#[test]
fn key_op_values_round_trip_and_classify() {
let registered = [
("sign", KeyOp::Sign, Some(KeyUse::Sig)),
("verify", KeyOp::Verify, Some(KeyUse::Sig)),
("encrypt", KeyOp::Encrypt, Some(KeyUse::Enc)),
("decrypt", KeyOp::Decrypt, Some(KeyUse::Enc)),
("wrapKey", KeyOp::WrapKey, Some(KeyUse::Enc)),
("unwrapKey", KeyOp::UnwrapKey, Some(KeyUse::Enc)),
("deriveKey", KeyOp::DeriveKey, Some(KeyUse::Enc)),
("deriveBits", KeyOp::DeriveBits, Some(KeyUse::Enc)),
];
for (s, op, key_use) in registered {
assert_eq!(KeyOp::from(s), op);
assert_eq!(op.as_str(), s);
assert_eq!(op.to_string(), s);
assert_eq!(op.key_use(), key_use);
}
let unknown = KeyOp::from("wrapkey");
assert_eq!(unknown, KeyOp::Unknown("wrapkey".into()));
assert_eq!(unknown.as_str(), "wrapkey");
assert_eq!(unknown.key_use(), None);
}
#[test]
fn alg_maps_to_its_family_or_unknown() {
assert_eq!(KeyAlg::from("RS256"), KeyAlg::Signing(SigningAlg::RS256));
assert_eq!(
KeyAlg::from("Ed25519"),
KeyAlg::Signing(SigningAlg::Ed25519)
);
assert_eq!(KeyAlg::from("EdDSA"), KeyAlg::Signing(SigningAlg::EdDSA));
assert_eq!(KeyAlg::from("A128KW"), KeyAlg::KeyMgmt(KeyMgmtAlg::A128KW));
assert_eq!(
KeyAlg::from("A256GCM"),
KeyAlg::ContentEnc(ContentEncAlg::A256GCM)
);
for s in ["RSA-OAEP", "RSA1_5", "Ed448", "hs256", "", "x-custom"] {
assert_eq!(KeyAlg::from(s), KeyAlg::Unknown(s.to_owned()), "{s}");
}
for s in [
"HS256",
"ECDH-ES+A128KW",
"A128CBC-HS256",
"none",
"RSA-OAEP",
] {
assert_eq!(KeyAlg::from(s).as_str(), s);
assert_eq!(KeyAlg::from(s).to_string(), s);
}
}
const EC_PRIVATE: &str = r#"{"kty":"EC","crv":"P-256",
"x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
"y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
"d":"870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE",
"use":"enc","kid":"1"}"#;
const EC_D: &str = "870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE";
const OKP_PRIVATE: &str = r#"{"kty":"OKP","crv":"Ed25519",
"d":"nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A",
"x":"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"}"#;
const OKP_D: &str = "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A";
const OCT_K: &str =
"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow";
const RSA_N: &str = "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw";
fn parse(json: &str) -> Jwk {
match Jwk::from_json(json.as_bytes()) {
Ok(jwk) => jwk,
Err(e) => panic!("valid test key rejected: {e}"),
}
}
fn object(json: &str) -> Object {
json::parse(json.as_bytes())
.ok()
.and_then(Value::into_object)
.unwrap_or_default()
}
fn oct_json() -> String {
format!(r#"{{"kty":"oct","k":"{OCT_K}","x-secret":"hunter2"}}"#)
}
fn rsa_json() -> String {
format!(r#"{{"kty":"RSA","n":"{RSA_N}","e":"AQAB","alg":"RS256","kid":"2011-04-29"}}"#)
}
#[test]
fn debug_redacts_private_members_and_unknown_values() {
for (json, secret) in [
(EC_PRIVATE.to_owned(), EC_D),
(OKP_PRIVATE.to_owned(), OKP_D),
(oct_json(), OCT_K),
] {
let jwk = parse(&json);
let debug = format!("{jwk:?}");
assert!(debug.contains("\"<redacted>\""), "{debug}");
assert!(!debug.contains(secret), "{debug}");
assert!(!debug.contains("hunter2"), "{debug}");
let material = format!("{:?}", jwk.material());
assert!(material.contains("\"<redacted>\""), "{material}");
assert!(!material.contains(secret), "{material}");
}
let debug = format!("{:?}", parse(&oct_json()));
assert!(debug.contains("other_names: [\"x-secret\"]"), "{debug}");
let public = parse(EC_PRIVATE).to_public().map(|k| format!("{k:?}"));
let public = public.unwrap_or_default();
assert!(
public.contains("MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4"),
"{public}"
);
assert!(!public.contains("<redacted>"), "{public}");
let rsa = format!("{:?}", parse(&rsa_json()));
assert!(rsa.contains("AQAB") && rsa.contains(RSA_N), "{rsa}");
}
fn assert_zeroize_on_drop<T: ZeroizeOnDrop>() {}
#[test]
fn secrets_live_in_zeroizing_buffers() {
assert_zeroize_on_drop::<OctKey>();
assert_zeroize_on_drop::<EcKey>();
assert_zeroize_on_drop::<OkpKey>();
assert_zeroize_on_drop::<KeyMaterial>();
assert_zeroize_on_drop::<Jwk>();
let oct = parse(&oct_json());
let KeyMaterial::Oct(key) = oct.material() else {
panic!("not oct");
};
let k: &Zeroizing<Vec<u8>> = &key.k;
assert_eq!(k.as_slice(), b64url_decode(OCT_K).unwrap_or_default());
assert_eq!(key.k(), k.as_slice());
assert_eq!(key.len(), 64);
assert!(!key.is_empty());
let ec = parse(EC_PRIVATE);
let KeyMaterial::Ec(key) = ec.material() else {
panic!("not EC");
};
let d: &Option<Zeroizing<Vec<u8>>> = &key.d;
assert_eq!(d.as_deref().map(Vec::len), Some(32));
assert_eq!(key.d().map(<[u8]>::to_vec), b64url_decode(EC_D).ok());
let okp = parse(OKP_PRIVATE);
let KeyMaterial::Okp(key) = okp.material() else {
panic!("not OKP");
};
let d: &Option<Zeroizing<Vec<u8>>> = &key.d;
assert_eq!(d.as_deref().map(Vec::len), Some(32));
assert_eq!(key.d().map(<[u8]>::to_vec), b64url_decode(OKP_D).ok());
let json: Zeroizing<String> = okp.to_json();
assert!(json.contains(OKP_D));
}
#[test]
fn decoded_secret_buffers_have_exact_capacity() {
let ec = parse(EC_PRIVATE);
if let KeyMaterial::Ec(key) = ec.material() {
assert_eq!(key.d.as_ref().map(|d| d.capacity()), Some(32));
}
let oct = parse(&oct_json());
if let KeyMaterial::Oct(key) = oct.material() {
assert_eq!(key.k.capacity(), 64);
}
}
#[test]
fn to_public_drops_d_and_unknown_members() {
let ec = parse(EC_PRIVATE);
assert!(!ec.is_public());
let public = ec.to_public();
let public = public.as_ref();
assert!(public.is_some_and(Jwk::is_public));
let KeyMaterial::Ec(key) = public.map(Jwk::material).unwrap_or(ec.material()) else {
panic!("not EC");
};
assert!(key.d().is_none() && !key.is_private());
assert_eq!(public.and_then(Jwk::kid), Some("1"));
let okp = parse(OKP_PRIVATE).to_public();
assert!(okp.as_ref().is_some_and(Jwk::is_public));
assert!(okp.is_some_and(|k| !k.to_json().contains(OKP_D)));
assert!(parse(&oct_json()).to_public().is_none());
assert!(!parse(&oct_json()).is_public());
let rsa = parse(&format!(
r#"{{"kty":"RSA","n":"{RSA_N}","e":"AQAB","ext":true}}"#
));
assert!(rsa.is_public());
let rsa_public = rsa.to_public();
assert!(rsa_public.is_some_and(|k| k.other_params().is_empty() && k.is_public()));
}
#[test]
fn thumbprint_members_are_the_rfc_7638_required_set_in_order() {
let names = |jwk: &Jwk| -> Vec<String> {
jwk.thumbprint_members()
.iter()
.map(|(n, _)| n.to_owned())
.collect()
};
let ec = parse(EC_PRIVATE);
assert_eq!(names(&ec), ["crv", "kty", "x", "y"]);
let okp = parse(OKP_PRIVATE);
assert_eq!(names(&okp), ["crv", "kty", "x"]);
let rsa = parse(&rsa_json());
assert_eq!(names(&rsa), ["e", "kty", "n"]);
let oct = parse(&oct_json());
assert_eq!(names(&oct), ["k", "kty"]);
assert_eq!(
rsa.thumbprint_members().to_json(),
format!(r#"{{"e":"AQAB","kty":"RSA","n":"{RSA_N}"}}"#)
);
assert_eq!(
okp.thumbprint_members().to_json(),
r#"{"crv":"Ed25519","kty":"OKP","x":"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"}"#
);
let ec_public = ec.to_public().map(|k| k.thumbprint_members());
assert_eq!(ec_public, Some(ec.thumbprint_members()));
assert_eq!(
oct.thumbprint_members().to_json(),
format!(r#"{{"k":"{OCT_K}","kty":"oct"}}"#)
);
}
#[test]
fn unsupported_key_kind_matches_import() {
let skipped = [
r#"{"kty":"oct2"}"#,
r#"{"kty":"ec","crv":"P-256"}"#,
r#"{"kty":"AKP","alg":"ML-DSA-44"}"#,
r#"{"kty":"EC","crv":"P-192","x":"AA","y":"AA"}"#,
r#"{"kty":"EC","crv":"Ed25519","x":"AA"}"#,
r#"{"kty":"OKP","crv":"Ed448","x":"AA"}"#,
r#"{"kty":"OKP","crv":"X448","x":"AA"}"#,
];
for json in skipped {
assert!(is_unsupported_key_kind(&object(json)), "{json}");
assert_eq!(
Jwk::from_json(json.as_bytes()).err(),
Some(Error::UnsupportedAlgorithm)
);
}
let kept = [
r#"{}"#,
r#"{"kty":1}"#,
r#"{"kty":"EC"}"#,
r#"{"kty":"OKP","crv":7}"#,
r#"{"kty":"oct","k":""}"#,
r#"{"kty":"RSA","n":"AQAB","e":"AQAB","d":"AQAB"}"#,
EC_PRIVATE,
];
for json in kept {
assert!(!is_unsupported_key_kind(&object(json)), "{json}");
}
}
fn rsa_private_object() -> Object {
object(include_str!(
"../tests/data/keys/rfc/rfc7515-a2-rsa-private.jwk.json"
))
}
fn rsa_private_with(name: &str, value: &str) -> String {
let mut o = rsa_private_object();
o.insert(name, json::parse(value.as_bytes()).unwrap());
o.to_json()
}
#[test]
fn an_rsa_private_jwk_imports_with_its_members_in_rfc_order() {
let jwk = Jwk::from_object(&rsa_private_object()).unwrap();
let KeyMaterial::Rsa(key) = jwk.material() else {
panic!("RSA")
};
assert!(key.is_private() && !jwk.is_public());
assert_eq!(key.modulus_bits(), 2048);
let members = |jwk: &Jwk| -> Vec<String> {
let mut names: Vec<String> =
jwk.to_object().iter().map(|(n, _)| n.to_owned()).collect();
names.sort();
names
};
assert_eq!(
members(&jwk),
["d", "dp", "dq", "e", "kty", "n", "p", "q", "qi"]
);
let json = jwk.to_json();
let order: Vec<usize> = [
"\"n\"", "\"e\"", "\"d\"", "\"p\"", "\"q\"", "\"dp\"", "\"dq\"", "\"qi\"",
]
.iter()
.map(|name| json.find(name).unwrap())
.collect();
assert!(order.windows(2).all(|w| w[0] < w[1]), "{order:?}");
assert_eq!(*Jwk::from_json(json.as_bytes()).unwrap().to_json(), *json);
let public = jwk.to_public().unwrap();
assert!(public.is_public());
assert_eq!(members(&public), ["e", "kty", "n"]);
assert_eq!(
public.thumbprint_members().to_json(),
jwk.thumbprint_members().to_json()
);
}
#[test]
fn rsa_private_members_never_show_in_debug_or_errors() {
let jwk = Jwk::from_object(&rsa_private_object()).unwrap();
let debug = format!("{jwk:?} {jwk:#?}");
let object = rsa_private_object();
for name in RSA_PRIVATE_MEMBERS {
let value = object.get(name).and_then(Value::as_str).unwrap();
assert!(!debug.contains(&value[..16]), "{name}");
assert!(
debug.contains(&format!("{name}: \"<redacted>\"")),
"{name}: {debug}"
);
}
for (name, value) in [("d", r#""AAAB""#), ("qi", r#""AQAB""#), ("p", r#""AQAB""#)] {
let error = Jwk::from_json(rsa_private_with(name, value).as_bytes()).unwrap_err();
let text = format!("{error:?} {error}");
for member in RSA_PRIVATE_MEMBERS {
let value = object.get(member).and_then(Value::as_str).unwrap();
assert!(!text.contains(&value[..16]), "{text}");
}
}
fn wipes<T: zeroize::ZeroizeOnDrop>() {}
wipes::<RsaKey>();
wipes::<RsaPrivateParts>();
}
#[test]
#[allow(deprecated)]
fn the_0_1_name_rsa_public_key_still_names_rsa_key() {
fn public_part(key: &RsaPublicKey) -> &[u8] {
key.n()
}
let KeyMaterial::Rsa(key) = parse(&rsa_json()).material().clone() else {
panic!("not RSA");
};
let same: &RsaKey = &key;
assert_eq!(public_part(same).len(), 256);
}
#[test]
fn rsa_private_members_must_be_minimal_and_the_key_of_n_and_e() {
let a2 = rsa_private_object();
let member = |name: &str| a2.get(name).and_then(Value::as_str).unwrap().to_owned();
let padded = |name: &str| {
let mut octets = vec![0];
octets.extend(b64url_decode(&member(name)).unwrap());
format!("\"{}\"", b64url_encode(&octets))
};
for name in RSA_PRIVATE_MEMBERS {
assert!(
matches!(
Jwk::from_json(rsa_private_with(name, &padded(name)).as_bytes()),
Err(Error::InvalidKey(detail)) if detail.contains("not a minimal Base64urlUInt")
),
"{name}"
);
assert_eq!(
Jwk::from_json(rsa_private_with(name, "7").as_bytes()).err(),
Some(Error::InvalidKey("RSA private key members must be strings")),
"{name}"
);
assert_eq!(
Jwk::from_json(rsa_private_with(name, r#""*""#).as_bytes()).err(),
Some(Error::InvalidBase64),
"{name}"
);
}
let mismatch = Error::InvalidKey("RSA private key does not match n and e");
let crt = Error::InvalidKey("RSA private key dp, dq or qi does not match d, p and q");
let swapped = {
let mut o = rsa_private_object();
o.insert("p", member("q"));
o.insert("q", member("p"));
o.to_json()
};
let rfc7520 = object(include_str!(
"../tests/data/keys/rfc/rfc7520-3.4-rsa-private.jwk.json"
));
let other_member = |name: &str| {
rfc7520
.get(name)
.and_then(Value::as_str)
.unwrap()
.to_owned()
};
for (what, json, expected) in [
("p and q swapped", swapped, &crt),
(
"another key's p",
rsa_private_with("p", &format!("\"{}\"", other_member("p"))),
&mismatch,
),
(
"another key's d",
rsa_private_with("d", &format!("\"{}\"", other_member("d"))),
&mismatch,
),
(
"another key's dp",
rsa_private_with("dp", &format!("\"{}\"", other_member("dp"))),
&crt,
),
(
"another key's qi",
rsa_private_with("qi", &format!("\"{}\"", other_member("qi"))),
&crt,
),
("d = 1", rsa_private_with("d", r#""AQ""#), &mismatch),
] {
assert_eq!(
Jwk::from_json(json.as_bytes()).err().as_ref(),
Some(expected),
"{what}"
);
}
let short = rsa_private_with("n", &format!("\"{}\"", b64url_encode(&[0xff; 128])));
assert_eq!(
Jwk::from_json(short.as_bytes()).err(),
Some(Error::InvalidKey("RSA modulus is shorter than 2048 bits"))
);
}
#[test]
fn rsa_oth_is_unsupported_and_partial_private_keys_are_invalid() {
for json in [
r#"{"kty":"RSA","oth":null,"alg":"RSA1_5"}"#.to_owned(),
r#"{"kty":"RSA","n":"x","oth":[]}"#.to_owned(),
format!(r#"{{"kty":"RSA","n":"{RSA_N}","e":"AQAB","d":"AQAB","oth":[]}}"#),
] {
assert_eq!(
Jwk::from_json(json.as_bytes()).err(),
Some(Error::UnsupportedAlgorithm),
"{json}"
);
}
for json in [
format!(r#"{{"kty":"RSA","n":"{RSA_N}","e":"AQAB","d":"AQAB"}}"#),
format!(r#"{{"kty":"RSA","n":"{RSA_N}","e":"AQAB","qi":"AQAB","alg":"RS256"}}"#),
format!(
r#"{{"kty":"RSA","n":"{RSA_N}","e":"AQAB","d":"AQ","p":"AQ","q":"AQ","dp":"AQ","dq":"AQ"}}"#
),
] {
assert_eq!(
Jwk::from_json(json.as_bytes()).err(),
Some(Error::InvalidKey(RSA_PRIVATE_INCOMPLETE)),
"{json}"
);
}
}
#[test]
fn the_key_op_counter_sees_the_duplicate_check() {
let two = object(r#"{"kty":"oct","k":"AA","key_ops":["sign","verify"]}"#);
let (ops, count) = key_op_comparisons::count(|| parse_key_ops(&two));
assert_eq!(ops, Ok(Some(vec![KeyOp::Sign, KeyOp::Verify])));
assert!(count >= 1, "the second value is checked against the first");
let none = object(r#"{"kty":"oct","k":"AA"}"#);
assert_eq!(
key_op_comparisons::count(|| parse_key_ops(&none)),
(Ok(None), 0)
);
}
fn n_log_n_bound(n: usize) -> u64 {
let n = n as u64;
4 * n * u64::from(u64::BITS - n.leading_zeros())
}
fn oct_with_unknown_members(unknown: usize) -> String {
let mut json = format!(r#"{{"kty":"oct","k":"{OCT_K}""#);
for i in 0..unknown {
json.push_str(&format!(r#","u{i}":{i}"#));
}
json.push('}');
json
}
fn oct_with_key_ops(ops: impl Iterator<Item = String>) -> String {
let ops: Vec<String> = ops.map(|op| format!(r#""{op}""#)).collect();
format!(
r#"{{"kty":"oct","k":"{OCT_K}","key_ops":[{}]}}"#,
ops.join(",")
)
}
#[test]
fn import_with_10_000_members_is_n_log_n() {
const N: usize = 10_000;
let json = oct_with_unknown_members(N - 2);
let (jwk, count) = Object::count_name_comparisons(|| Jwk::from_json(json.as_bytes()));
let jwk = jwk.expect("a JWK with unknown members imports");
assert_eq!(jwk.other_params().len(), N - 2);
assert!(count <= n_log_n_bound(N), "{count} comparisons");
}
#[test]
fn to_object_with_10_000_members_is_n_log_n() {
const N: usize = 10_000;
let json = oct_with_unknown_members(N - 2);
let jwk = parse(&json);
let (written, count) = Object::count_name_comparisons(|| jwk.to_object());
assert!(count <= n_log_n_bound(N), "{count} comparisons");
assert_eq!(written.len(), N);
assert_eq!(written.to_json(), json);
}
#[test]
fn key_ops_errors_follow_value_order() {
const DUPLICATES: Error = Error::InvalidKey("key_ops must not contain duplicates");
const NOT_STRINGS: Error = Error::InvalidKey("key_ops must be an array of strings");
let ops = |list: &str| parse_key_ops(&object(&format!(r#"{{"key_ops":{list}}}"#)));
assert_eq!(ops(r#"["sign","sign",5]"#), Err(DUPLICATES));
assert_eq!(ops(r#"["sign",5,"sign"]"#), Err(NOT_STRINGS));
assert_eq!(ops(r#"["x-op","verify","x-op"]"#), Err(DUPLICATES));
assert_eq!(
ops(r#"["sign","Sign","x-op","X-op"]"#),
Ok(Some(vec![
KeyOp::Sign,
KeyOp::Unknown("Sign".to_owned()),
KeyOp::Unknown("x-op".to_owned()),
KeyOp::Unknown("X-op".to_owned()),
]))
);
assert_eq!(ops("[]"), Ok(Some(Vec::new())));
}
#[test]
fn key_ops_duplicate_check_is_n_log_n() {
const N: usize = 10_000;
let distinct = oct_with_key_ops((0..N).map(|i| format!("op{i}")));
let (jwk, count) = key_op_comparisons::count(|| Jwk::from_json(distinct.as_bytes()));
let jwk = jwk.expect("distinct key_ops values import");
assert_eq!(jwk.key_ops().map(<[KeyOp]>::len), Some(N));
assert!(count <= n_log_n_bound(N), "{count} comparisons");
let late_duplicate = oct_with_key_ops(
(0..N - 1)
.map(|i| format!("op{i}"))
.chain(["op0".to_owned()]),
);
let (result, count) =
key_op_comparisons::count(|| Jwk::from_json(late_duplicate.as_bytes()));
assert_eq!(
result.err(),
Some(Error::InvalidKey("key_ops must not contain duplicates"))
);
assert!(count <= n_log_n_bound(N), "{count} comparisons");
}
fn family_keys() -> Vec<(&'static str, KeyMaterial)> {
let ec = |crv: EcCurve| {
let len = crv.coordinate_len();
KeyMaterial::Ec(EcKey {
crv,
x: vec![7; len],
y: vec![7; len],
d: None,
})
};
let okp = |crv: OkpCurve| {
KeyMaterial::Okp(OkpKey {
crv,
x: vec![7; 32],
d: None,
})
};
vec![
("oct", parse(&oct_json()).material().clone()),
("RSA", parse(&rsa_json()).material().clone()),
("P-256", ec(EcCurve::P256)),
("P-384", ec(EcCurve::P384)),
("P-521", ec(EcCurve::P521)),
("secp256k1", ec(EcCurve::Secp256k1)),
("Ed25519", okp(OkpCurve::Ed25519)),
("X25519", okp(OkpCurve::X25519)),
]
}
#[test]
fn key_mgmt_family_table() {
const ECDH: &[&str] = &["P-256", "P-384", "P-521", "X25519"];
let table: &[(KeyMgmtAlg, &[&str])] = &[
(KeyMgmtAlg::RsaOaep256, &["RSA"]),
(KeyMgmtAlg::A128KW, &["oct"]),
(KeyMgmtAlg::A192KW, &["oct"]),
(KeyMgmtAlg::A256KW, &["oct"]),
(KeyMgmtAlg::Dir, &["oct"]),
(KeyMgmtAlg::EcdhEs, ECDH),
(KeyMgmtAlg::EcdhEsA128KW, ECDH),
(KeyMgmtAlg::EcdhEsA192KW, ECDH),
(KeyMgmtAlg::EcdhEsA256KW, ECDH),
(KeyMgmtAlg::A128GCMKW, &["oct"]),
(KeyMgmtAlg::A192GCMKW, &["oct"]),
(KeyMgmtAlg::A256GCMKW, &["oct"]),
(KeyMgmtAlg::Pbes2Hs256A128KW, &["oct"]),
(KeyMgmtAlg::Pbes2Hs384A192KW, &["oct"]),
(KeyMgmtAlg::Pbes2Hs512A256KW, &["oct"]),
(KeyMgmtAlg::from("RSA-OAEP"), &[]),
(KeyMgmtAlg::from("RSA1_5"), &[]),
(KeyMgmtAlg::from("x-future"), &[]),
];
for alg in KeyMgmtAlg::KNOWN {
assert!(table.iter().any(|(a, _)| a == alg), "{alg} not in table");
}
for (alg, kinds) in table {
for (kind, key) in family_keys() {
assert_eq!(
key.serves_key_mgmt_alg(alg),
kinds.contains(&kind),
"{alg} on {kind}"
);
}
}
}
#[test]
fn content_enc_family_table() {
for enc in ContentEncAlg::KNOWN {
for (kind, key) in family_keys() {
assert_eq!(
key.serves_content_enc_alg(enc),
kind == "oct",
"{enc} on {kind}"
);
}
}
for (_, key) in family_keys() {
assert!(!key.serves_content_enc_alg(&ContentEncAlg::from("A128CBC")));
}
}
#[test]
fn alg_binding_check() {
for (kind, key) in family_keys() {
for name in UNSUPPORTED_KEY_ALGS {
assert_eq!(
check_alg_binding(&key, &KeyAlg::from(*name)),
Err(Error::UnsupportedAlgorithm),
"{name} on {kind}"
);
}
for unknown in ["x-future", "rsa1_5", "Ed448", ""] {
assert_eq!(check_alg_binding(&key, &KeyAlg::from(unknown)), Ok(()));
}
let none = KeyAlg::Signing(SigningAlg::None);
assert_eq!(
check_alg_binding(&key, &none),
Err(Error::KeyAlgorithmMismatch)
);
}
let oct = parse(&oct_json());
let oct = oct.material();
assert_eq!(check_alg_binding(oct, &KeyAlg::from("HS256")), Ok(()));
assert_eq!(check_alg_binding(oct, &KeyAlg::from("A256GCM")), Ok(()));
assert_eq!(check_alg_binding(oct, &KeyAlg::from("dir")), Ok(()));
assert_eq!(
check_alg_binding(oct, &KeyAlg::from("ES256")),
Err(Error::KeyAlgorithmMismatch)
);
assert_eq!(UNSUPPORTED_KEY_ALGS, ["RSA1_5", "RSA-OAEP"]);
}
#[test]
fn modulus_bits_counts_from_the_top_set_bit() {
let KeyMaterial::Rsa(key) = parse(&rsa_json()).material().clone() else {
panic!("not RSA");
};
assert_eq!(key.modulus_bits(), 2048);
assert_eq!(key.n().len(), 256);
assert_eq!(key.e(), [1, 0, 1]);
let n = RsaKey {
n: vec![0x01, 0xff, 0xff],
e: vec![3],
private: None,
};
assert_eq!(n.modulus_bits(), 17);
}
#[test]
fn bit_len_counts_from_the_top_set_bit() {
assert_eq!(bit_len(&[]), 0);
assert_eq!(bit_len(&[0]), 0);
assert_eq!(bit_len(&[1]), 1);
assert_eq!(bit_len(&[0x80, 0]), 16);
assert_eq!(bit_len(&[0x01, 0, 0]), 17);
}
#[test]
fn the_rsa_modulus_details_match_rsa_modulus_bits() {
assert_eq!(RSA_MODULUS_BITS, 2048..=8192);
let key = |n: &[u8]| {
let n = b64url_encode(n);
Jwk::from_json(format!(r#"{{"kty":"RSA","n":"{n}","e":"AQAB"}}"#).as_bytes())
};
let mut n = vec![0xff; 1024];
assert!(key(&n).is_ok(), "8192 bits");
n.insert(0, 0x01);
assert_eq!(
key(&n).err(),
Some(Error::InvalidKey("RSA modulus is longer than 8192 bits"))
);
assert_eq!(
key(&[0xff; 255]).err(),
Some(Error::InvalidKey("RSA modulus is shorter than 2048 bits"))
);
}
}