use zeroize::Zeroizing;
use super::pkcs1::RsaPrivateFields;
use crate::error::Result;
use crate::json::{Object, Value};
use crate::jwk::{EcCurve, Jwk, OkpCurve};
use crate::util::b64url_encode;
fn b64url_member(bytes: &[u8]) -> Value {
Value::String(Zeroizing::new(b64url_encode(bytes)))
}
fn finish(mut object: Object, alg: Option<&str>) -> Result<Jwk> {
if let Some(alg) = alg {
object.insert("alg", alg);
}
Jwk::from_object(&object)
}
pub(crate) fn ec_jwk(
crv: EcCurve,
x: &[u8],
y: &[u8],
d: Option<&[u8]>,
alg: Option<&str>,
) -> Result<Jwk> {
let mut object = Object::new();
object.insert("kty", "EC");
object.insert("crv", crv.as_str());
object.insert("x", b64url_member(x));
object.insert("y", b64url_member(y));
if let Some(d) = d {
object.insert("d", b64url_member(d));
}
finish(object, alg)
}
pub(crate) fn okp_jwk(crv: OkpCurve, x: &[u8], d: Option<&[u8]>, alg: Option<&str>) -> Result<Jwk> {
let mut object = Object::new();
object.insert("kty", "OKP");
object.insert("crv", crv.as_str());
object.insert("x", b64url_member(x));
if let Some(d) = d {
object.insert("d", b64url_member(d));
}
finish(object, alg)
}
pub(crate) fn rsa_public_jwk(n: &[u8], e: &[u8], alg: Option<&str>) -> Result<Jwk> {
let mut object = Object::new();
object.insert("kty", "RSA");
object.insert("n", b64url_member(n));
object.insert("e", b64url_member(e));
finish(object, alg)
}
pub(crate) fn rsa_private_jwk(key: &RsaPrivateFields<'_>, alg: Option<&str>) -> Result<Jwk> {
let mut object = Object::new();
object.insert("kty", "RSA");
object.insert("n", b64url_member(key.n));
object.insert("e", b64url_member(key.e));
for (name, value) in [
("d", key.d),
("p", key.p),
("q", key.q),
("dp", key.dp),
("dq", key.dq),
("qi", key.qi),
] {
object.insert(name, b64url_member(value));
}
finish(object, alg)
}
pub(crate) fn oct_jwk(k: &[u8], alg: Option<&str>) -> Result<Jwk> {
let mut object = Object::new();
object.insert("kty", "oct");
object.insert("k", b64url_member(k));
finish(object, alg)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Error;
use crate::jwk::KeyMaterial;
use crate::util::b64url_decode;
fn b64(s: &str) -> Vec<u8> {
b64url_decode(s).expect("RFC vector is canonical base64url")
}
fn assert_same_key(built: &Jwk, json: &str) {
let expected = Jwk::from_json(json.as_bytes()).expect("RFC JWK parses");
assert_eq!(*built.to_json(), *expected.to_json());
}
const A_EC_X: &str = "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4";
const A_EC_Y: &str = "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM";
const A2_EC_D: &str = "870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE";
const A1_RSA_N: &str = "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw";
#[test]
fn rfc7517_a1_ec_public_key() {
let jwk = ec_jwk(EcCurve::P256, &b64(A_EC_X), &b64(A_EC_Y), None, None).unwrap();
assert!(jwk.is_public());
assert_same_key(
&jwk,
&format!(r#"{{"kty":"EC","crv":"P-256","x":"{A_EC_X}","y":"{A_EC_Y}"}}"#),
);
}
#[test]
fn rfc7517_a2_ec_private_key() {
let d = b64(A2_EC_D);
let jwk = ec_jwk(EcCurve::P256, &b64(A_EC_X), &b64(A_EC_Y), Some(&d), None).unwrap();
assert!(!jwk.is_public());
let KeyMaterial::Ec(key) = jwk.material() else {
panic!("an EC key")
};
assert_eq!(key.d(), Some(d.as_slice()));
assert_same_key(
&jwk,
&format!(
r#"{{"kty":"EC","crv":"P-256","x":"{A_EC_X}","y":"{A_EC_Y}","d":"{A2_EC_D}"}}"#
),
);
}
#[test]
fn rfc7517_a1_rsa_public_key_with_its_alg() {
let jwk = rsa_public_jwk(&b64(A1_RSA_N), &b64("AQAB"), Some("RS256")).unwrap();
assert_eq!(jwk.alg().map(|a| a.as_str()), Some("RS256"));
assert_same_key(
&jwk,
&format!(r#"{{"kty":"RSA","n":"{A1_RSA_N}","e":"AQAB","alg":"RS256"}}"#),
);
}
#[test]
fn rfc7517_a3_oct_keys() {
let aes = "GawgguFyGrWKav7AX4VKUg";
let jwk = oct_jwk(&b64(aes), Some("A128KW")).unwrap();
assert_same_key(
&jwk,
&format!(r#"{{"kty":"oct","alg":"A128KW","k":"{aes}"}}"#),
);
let hmac = "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow";
let jwk = oct_jwk(&b64(hmac), None).unwrap();
assert_same_key(&jwk, &format!(r#"{{"kty":"oct","k":"{hmac}"}}"#));
}
#[test]
fn rfc8037_a1_a2_ed25519_keys() {
let d = "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A";
let x = "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo";
let private = okp_jwk(OkpCurve::Ed25519, &b64(x), Some(&b64(d)), None).unwrap();
assert_same_key(
&private,
&format!(r#"{{"kty":"OKP","crv":"Ed25519","d":"{d}","x":"{x}"}}"#),
);
let public = okp_jwk(OkpCurve::Ed25519, &b64(x), None, Some("EdDSA")).unwrap();
assert!(public.is_public());
assert_same_key(
&public,
&format!(r#"{{"kty":"OKP","crv":"Ed25519","x":"{x}","alg":"EdDSA"}}"#),
);
}
#[test]
fn rfc8037_a6_x25519_keys() {
let d = "dwdtCnMYpX08FsFyUbJmRd9ML4frwJkqsXf7pR25LCo";
let x = "hSDwCYkwp1R0i33ctD73Wg2_Og0mOBr066SpjqqbTmo";
let jwk = okp_jwk(OkpCurve::X25519, &b64(x), Some(&b64(d)), Some("ECDH-ES")).unwrap();
assert_same_key(
&jwk,
&format!(r#"{{"kty":"OKP","crv":"X25519","x":"{x}","d":"{d}","alg":"ECDH-ES"}}"#),
);
let bob = "3p7bfXt9wbTTW2HC7OQ1Nz-DQ8hbeGdNrfx-FG-IK08";
let jwk = okp_jwk(OkpCurve::X25519, &b64(bob), None, None).unwrap();
assert_same_key(
&jwk,
&format!(r#"{{"kty":"OKP","crv":"X25519","x":"{bob}"}}"#),
);
}
#[test]
fn an_off_curve_point_is_rejected() {
let mut y = b64(A_EC_Y);
*y.last_mut().unwrap() ^= 0x01;
assert!(matches!(
ec_jwk(EcCurve::P256, &b64(A_EC_X), &y, None, None),
Err(Error::InvalidKey(_))
));
}
#[test]
fn a_1024_bit_rsa_modulus_is_rejected() {
let n = b64(A1_RSA_N);
assert_eq!(
rsa_public_jwk(&n[..128], &b64("AQAB"), None).unwrap_err(),
Error::InvalidKey("RSA modulus is shorter than 2048 bits")
);
}
#[test]
fn an_alg_the_key_cannot_serve_is_rejected() {
assert_eq!(
ec_jwk(
EcCurve::P256,
&b64(A_EC_X),
&b64(A_EC_Y),
None,
Some("RS256")
)
.unwrap_err(),
Error::KeyAlgorithmMismatch
);
let x = b64("11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo");
assert_eq!(
okp_jwk(OkpCurve::Ed25519, &x, None, Some("ECDH-ES")).unwrap_err(),
Error::KeyAlgorithmMismatch
);
assert_eq!(
oct_jwk(&[7; 32], Some("ES256")).unwrap_err(),
Error::KeyAlgorithmMismatch
);
}
#[test]
fn wrong_lengths_are_rejected() {
assert!(matches!(
ec_jwk(EcCurve::P384, &b64(A_EC_X), &b64(A_EC_Y), None, None),
Err(Error::InvalidKey(_))
));
assert!(matches!(
okp_jwk(OkpCurve::X25519, &[9; 31], None, None),
Err(Error::InvalidKey(_))
));
assert!(matches!(oct_jwk(&[], None), Err(Error::InvalidKey(_))));
}
#[test]
fn secret_members_are_zeroizing_strings() {
let Value::String(member) = b64url_member(&[0xA5; 32]) else {
panic!("a string member")
};
let _: &Zeroizing<String> = &member;
assert_eq!(member.as_str(), b64url_encode(&[0xA5; 32]));
}
}