From 956556b0ff5530b656e4ffe4335e9e5aef1c6ba7 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Fri, 26 Aug 2022 14:00:33 +0200 Subject: [PATCH] Sign all the things --- Cargo.lock | 41 ++- crates/jose/Cargo.toml | 10 +- crates/jose/src/constraints.rs | 280 +++++++++++++++ crates/jose/src/hmac.rs | 109 ++++++ crates/jose/src/jwk.rs | 99 ++++-- crates/jose/src/jwt/signed.rs | 21 +- crates/jose/src/keystore/jwks/static_store.rs | 5 +- crates/jose/src/lib.rs | 4 + crates/jose/src/rsa.rs | 146 ++++++++ crates/jose/src/verifier.rs | 330 ++++++++++++++++++ crates/jose/tests/generate.py | 138 ++++++++ crates/jose/tests/jws.rs | 107 ++++++ crates/jose/tests/jwts/eddsa-ed25519.jwt | 1 + crates/jose/tests/jwts/eddsa-ed448.jwt | 1 + crates/jose/tests/jwts/es256.jwt | 1 + crates/jose/tests/jwts/es256k.jwt | 1 + crates/jose/tests/jwts/es384.jwt | 1 + crates/jose/tests/jwts/es512.jwt | 1 + crates/jose/tests/jwts/hs256.jwt | 1 + crates/jose/tests/jwts/hs384.jwt | 1 + crates/jose/tests/jwts/hs512.jwt | 1 + crates/jose/tests/jwts/ps256.jwt | 1 + crates/jose/tests/jwts/ps384.jwt | 1 + crates/jose/tests/jwts/ps512.jwt | 1 + crates/jose/tests/jwts/rs256.jwt | 1 + crates/jose/tests/jwts/rs384.jwt | 1 + crates/jose/tests/jwts/rs512.jwt | 1 + crates/jose/tests/keys/ed25519.priv.pem | 3 + crates/jose/tests/keys/ed25519.pub.pem | 3 + crates/jose/tests/keys/ed448.priv.pem | 4 + crates/jose/tests/keys/ed448.pub.pem | 4 + crates/jose/tests/keys/jwks.priv.json | 67 ++++ crates/jose/tests/keys/jwks.pub.json | 50 +++ crates/jose/tests/keys/k256.priv.pem | 8 + crates/jose/tests/keys/k256.pub.pem | 4 + crates/jose/tests/keys/oct.bin | 1 + crates/jose/tests/keys/p256.priv.pem | 8 + crates/jose/tests/keys/p256.pub.pem | 4 + crates/jose/tests/keys/p384.priv.pem | 9 + crates/jose/tests/keys/p384.pub.pem | 5 + crates/jose/tests/keys/p521.priv.pem | 10 + crates/jose/tests/keys/p521.pub.pem | 6 + crates/jose/tests/keys/rsa.priv.pem | 27 ++ crates/jose/tests/keys/rsa.pub.pem | 9 + 44 files changed, 1479 insertions(+), 48 deletions(-) create mode 100644 crates/jose/src/constraints.rs create mode 100644 crates/jose/src/hmac.rs create mode 100644 crates/jose/src/rsa.rs create mode 100644 crates/jose/src/verifier.rs create mode 100644 crates/jose/tests/generate.py create mode 100644 crates/jose/tests/jws.rs create mode 100644 crates/jose/tests/jwts/eddsa-ed25519.jwt create mode 100644 crates/jose/tests/jwts/eddsa-ed448.jwt create mode 100644 crates/jose/tests/jwts/es256.jwt create mode 100644 crates/jose/tests/jwts/es256k.jwt create mode 100644 crates/jose/tests/jwts/es384.jwt create mode 100644 crates/jose/tests/jwts/es512.jwt create mode 100644 crates/jose/tests/jwts/hs256.jwt create mode 100644 crates/jose/tests/jwts/hs384.jwt create mode 100644 crates/jose/tests/jwts/hs512.jwt create mode 100644 crates/jose/tests/jwts/ps256.jwt create mode 100644 crates/jose/tests/jwts/ps384.jwt create mode 100644 crates/jose/tests/jwts/ps512.jwt create mode 100644 crates/jose/tests/jwts/rs256.jwt create mode 100644 crates/jose/tests/jwts/rs384.jwt create mode 100644 crates/jose/tests/jwts/rs512.jwt create mode 100644 crates/jose/tests/keys/ed25519.priv.pem create mode 100644 crates/jose/tests/keys/ed25519.pub.pem create mode 100644 crates/jose/tests/keys/ed448.priv.pem create mode 100644 crates/jose/tests/keys/ed448.pub.pem create mode 100644 crates/jose/tests/keys/jwks.priv.json create mode 100644 crates/jose/tests/keys/jwks.pub.json create mode 100644 crates/jose/tests/keys/k256.priv.pem create mode 100644 crates/jose/tests/keys/k256.pub.pem create mode 100644 crates/jose/tests/keys/oct.bin create mode 100644 crates/jose/tests/keys/p256.priv.pem create mode 100644 crates/jose/tests/keys/p256.pub.pem create mode 100644 crates/jose/tests/keys/p384.priv.pem create mode 100644 crates/jose/tests/keys/p384.pub.pem create mode 100644 crates/jose/tests/keys/p521.priv.pem create mode 100644 crates/jose/tests/keys/p521.pub.pem create mode 100644 crates/jose/tests/keys/rsa.priv.pem create mode 100644 crates/jose/tests/keys/rsa.pub.pem diff --git a/Cargo.lock b/Cargo.lock index a0945915..ec07521c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -152,16 +152,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "async-signature" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c57ade8a273b42906cb87145358c778667c1ca515bfe24f61ed8a4bfe756c8e3" -dependencies = [ - "async-trait", - "signature", -] - [[package]] name = "async-stream" version = "0.3.3" @@ -619,9 +609,9 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "base64ct" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdca834647821e0b13d9539a8634eb62d3501b6b6c2cec1722786ee6671b851" +checksum = "ea2b2456fd614d856680dcd9fcc660a51a820fa09daef2e49772b56a193c8474" [[package]] name = "bincode" @@ -2200,6 +2190,18 @@ dependencies = [ "treediff", ] +[[package]] +name = "k256" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2573d3fd3e4cc741affc9b5ce1a8ce36cf29f09f80f36da4309d0ae6d7854" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sha2 0.10.2", +] + [[package]] name = "language-tags" version = "0.3.2" @@ -2555,7 +2557,6 @@ name = "mas-jose" version = "0.1.0" dependencies = [ "anyhow", - "async-signature", "async-trait", "base64ct", "chrono", @@ -2566,9 +2567,10 @@ dependencies = [ "futures-util", "hmac", "http", - "mas-http", + "k256", "mas-iana", "p256", + "p384", "pkcs1", "pkcs8", "rand", @@ -3092,6 +3094,17 @@ dependencies = [ "sha2 0.10.2", ] +[[package]] +name = "p384" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc8c5bf642dde52bb9e87c0ecd8ca5a76faac2eeed98dedb7c717997e1080aa" +dependencies = [ + "ecdsa", + "elliptic-curve", + "sha2 0.10.2", +] + [[package]] name = "parking_lot" version = "0.11.2" diff --git a/crates/jose/Cargo.toml b/crates/jose/Cargo.toml index 43221998..8f8b41d6 100644 --- a/crates/jose/Cargo.toml +++ b/crates/jose/Cargo.toml @@ -6,10 +6,9 @@ edition = "2021" license = "Apache-2.0" [dependencies] -async-signature = "0.2.0" anyhow = "1.0.62" async-trait = "0.1.57" -base64ct = { version = "1.5.1", features = ["std"] } +base64ct = { version = "1.5.2", features = ["std"] } chrono = { version = "0.4.22", features = ["serde"] } crypto-mac = { version = "0.11.1", features = ["std"] } digest = "0.10.3" @@ -18,11 +17,13 @@ elliptic-curve = { version = "0.12.3", features = ["ecdh", "pem"] } futures-util = "0.3.23" hmac = "0.12.1" http = "0.2.8" +k256 = { version = "0.11.2", features = ["ecdsa", "pem", "pkcs8"] } p256 = { version = "0.11.1", features = ["ecdsa", "pem", "pkcs8"] } +p384 = { version = "0.11.2", features = ["ecdsa", "pem", "pkcs8"] } pkcs1 = { version = "0.4.0", features = ["pem", "pkcs8"] } pkcs8 = { version = "0.9.0", features = ["pem", "std"] } rand = "0.8.5" -rsa = { git = "https://github.com/RustCrypto/RSA.git" } +rsa = { git = "https://github.com/RustCrypto/RSA.git", features = ["std"] } schemars = "0.8.10" sec1 = "0.3.0" serde = { version = "1.0.144", features = ["derive"] } @@ -37,6 +38,3 @@ tracing = "0.1.36" url = { version = "2.2.2", features = ["serde"] } mas-iana = { path = "../iana" } - -[dev-dependencies] -mas-http = { path = "../http" } diff --git a/crates/jose/src/constraints.rs b/crates/jose/src/constraints.rs new file mode 100644 index 00000000..9af4c431 --- /dev/null +++ b/crates/jose/src/constraints.rs @@ -0,0 +1,280 @@ +// Copyright 2022 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::collections::HashSet; + +use futures_util::future::Either; +use mas_iana::jose::{JsonWebKeyType, JsonWebKeyUse, JsonWebSignatureAlg}; + +use crate::JsonWebSignatureHeader; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Constraint<'a> { + Alg { + constraint_alg: JsonWebSignatureAlg, + }, + + Algs { + constraint_algs: &'a [JsonWebSignatureAlg], + }, + + Kid { + constraint_kid: &'a str, + }, + + Use { + constraint_use: JsonWebKeyUse, + }, + + Kty { + constraint_kty: JsonWebKeyType, + }, +} + +impl<'a> Constraint<'a> { + #[must_use] + pub fn alg(constraint_alg: JsonWebSignatureAlg) -> Self { + Constraint::Alg { constraint_alg } + } + + #[must_use] + pub fn algs(constraint_algs: &'a [JsonWebSignatureAlg]) -> Self { + Constraint::Algs { constraint_algs } + } + + #[must_use] + pub fn kid(constraint_kid: &'a str) -> Self { + Constraint::Kid { constraint_kid } + } + + #[must_use] + pub fn use_(constraint_use: JsonWebKeyUse) -> Self { + Constraint::Use { constraint_use } + } + + #[must_use] + pub fn kty(constraint_kty: JsonWebKeyType) -> Self { + Constraint::Kty { constraint_kty } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ConstraintDecision { + Positive, + Neutral, + Negative, +} + +pub trait Constrainable { + /// List of available algorithms for this key + fn algs(&self) -> Option> { + None + } + + /// Key ID (`kid`) of this key + fn kid(&self) -> Option<&str> { + None + } + + /// Usage specified for this key + fn use_(&self) -> Option { + None + } + + /// Key type (`kty`) of this key + fn kty(&self) -> JsonWebKeyType; +} + +impl Constrainable for Either +where + L: Constrainable, + R: Constrainable, +{ + fn algs(&self) -> Option> { + match self { + Either::Left(l) => l.algs(), + Either::Right(r) => r.algs(), + } + } + + fn kid(&self) -> Option<&str> { + match self { + Either::Left(l) => l.kid(), + Either::Right(r) => r.kid(), + } + } + + fn use_(&self) -> Option { + match self { + Either::Left(l) => l.use_(), + Either::Right(r) => r.use_(), + } + } + + fn kty(&self) -> JsonWebKeyType { + match self { + Either::Left(l) => l.kty(), + Either::Right(r) => r.kty(), + } + } +} + +impl<'a> Constraint<'a> { + fn decide(&self, constrainable: &T) -> ConstraintDecision { + match self { + Constraint::Alg { constraint_alg } => { + if let Some(algs) = constrainable.algs() { + if algs.contains(constraint_alg) { + ConstraintDecision::Positive + } else { + ConstraintDecision::Negative + } + } else { + ConstraintDecision::Neutral + } + } + Constraint::Algs { constraint_algs } => { + if let Some(algs) = constrainable.algs() { + if algs.iter().any(|alg| constraint_algs.contains(alg)) { + ConstraintDecision::Positive + } else { + ConstraintDecision::Negative + } + } else { + ConstraintDecision::Neutral + } + } + Constraint::Kid { constraint_kid } => { + if let Some(kid) = constrainable.kid() { + if kid == *constraint_kid { + ConstraintDecision::Positive + } else { + ConstraintDecision::Negative + } + } else { + ConstraintDecision::Neutral + } + } + Constraint::Use { constraint_use } => { + if let Some(use_) = constrainable.use_() { + if use_ == *constraint_use { + ConstraintDecision::Positive + } else { + ConstraintDecision::Negative + } + } else { + ConstraintDecision::Neutral + } + } + Constraint::Kty { constraint_kty } => { + if *constraint_kty == constrainable.kty() { + ConstraintDecision::Positive + } else { + ConstraintDecision::Negative + } + } + } + } +} + +#[derive(Default)] +pub struct ConstraintSet<'a> { + constraints: HashSet>, +} + +impl<'a> FromIterator> for ConstraintSet<'a> { + fn from_iter>>(iter: T) -> Self { + Self { + constraints: HashSet::from_iter(iter), + } + } +} + +#[allow(dead_code)] +impl<'a> ConstraintSet<'a> { + pub fn new(constraints: impl IntoIterator>) -> Self { + constraints.into_iter().collect() + } + + pub fn filter<'b, T: Constrainable, I: IntoIterator>( + &self, + constrainables: I, + ) -> Vec<&'b T> { + let mut selected = Vec::new(); + + 'outer: for constrainable in constrainables { + let mut score = 0; + + for constraint in &self.constraints { + match constraint.decide(constrainable) { + ConstraintDecision::Positive => score += 1, + ConstraintDecision::Neutral => {} + // If any constraint was negative, don't add it to the candidates + ConstraintDecision::Negative => break 'outer, + } + } + + selected.push((score, constrainable)); + } + + selected.sort_by_key(|(score, _)| *score); + + selected + .into_iter() + .map(|(_score, constrainable)| constrainable) + .collect() + } + + #[must_use] + pub fn alg(mut self, constraint_alg: JsonWebSignatureAlg) -> Self { + self.constraints.insert(Constraint::alg(constraint_alg)); + self + } + + #[must_use] + pub fn algs(mut self, constraint_algs: &'a [JsonWebSignatureAlg]) -> Self { + self.constraints.insert(Constraint::algs(constraint_algs)); + self + } + + #[must_use] + pub fn kid(mut self, constraint_kid: &'a str) -> Self { + self.constraints.insert(Constraint::kid(constraint_kid)); + self + } + + #[must_use] + pub fn use_(mut self, constraint_use: JsonWebKeyUse) -> Self { + self.constraints.insert(Constraint::use_(constraint_use)); + self + } + + #[must_use] + pub fn kty(mut self, constraint_kty: JsonWebKeyType) -> Self { + self.constraints.insert(Constraint::kty(constraint_kty)); + self + } +} + +impl<'a> From<&'a JsonWebSignatureHeader> for ConstraintSet<'a> { + fn from(header: &'a JsonWebSignatureHeader) -> Self { + let mut constraints = Self::default().alg(header.alg()); + + if let Some(kid) = header.kid() { + constraints = constraints.kid(kid); + } + + constraints + } +} diff --git a/crates/jose/src/hmac.rs b/crates/jose/src/hmac.rs new file mode 100644 index 00000000..e6bf2490 --- /dev/null +++ b/crates/jose/src/hmac.rs @@ -0,0 +1,109 @@ +// Copyright 2022 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::marker::PhantomData; + +use digest::{ + crypto_common::BlockSizeUser, + generic_array::{ArrayLength, GenericArray}, + Digest, Mac, OutputSizeUser, +}; +use signature::{Signer, Verifier}; +use thiserror::Error; + +pub struct Signature> { + signature: GenericArray, +} + +impl> PartialEq for Signature { + fn eq(&self, other: &Self) -> bool { + self.signature == other.signature + } +} + +impl> Eq for Signature {} + +impl> std::fmt::Debug for Signature { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.signature) + } +} + +impl> signature::Signature for Signature { + fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() != S::to_usize() { + return Err(signature::Error::new()); + } + + Ok(Self { + signature: GenericArray::from_slice(bytes).clone(), + }) + } +} + +impl> AsRef<[u8]> for Signature { + fn as_ref(&self) -> &[u8] { + self.signature.as_ref() + } +} + +pub struct Hmac { + key: Vec, + digest: PhantomData, +} + +#[derive(Error, Debug)] +#[error("invalid length")] +pub struct InvalidLength; + +impl From> for Hmac { + fn from(key: Vec) -> Self { + Self { + key, + digest: PhantomData::default(), + } + } +} + +impl + Signer as OutputSizeUser>::OutputSize>> for Hmac +{ + fn try_sign( + &self, + msg: &[u8], + ) -> Result as OutputSizeUser>::OutputSize>, signature::Error> + { + let mut mac = as Mac>::new_from_slice(&self.key) + .map_err(signature::Error::from_source)?; + mac.update(msg); + let signature = mac.finalize().into_bytes(); + Ok(Signature { signature }) + } +} + +impl + Verifier as OutputSizeUser>::OutputSize>> for Hmac +{ + fn verify( + &self, + msg: &[u8], + signature: &Signature< as OutputSizeUser>::OutputSize>, + ) -> Result<(), signature::Error> { + let new_signature = self.try_sign(msg)?; + if &new_signature != signature { + return Err(signature::Error::new()); + } + Ok(()) + } +} diff --git a/crates/jose/src/jwk.rs b/crates/jose/src/jwk.rs index 65e46ebb..b8f42b82 100644 --- a/crates/jose/src/jwk.rs +++ b/crates/jose/src/jwk.rs @@ -30,6 +30,8 @@ use serde_with::{ }; use url::Url; +use crate::constraints::Constrainable; + #[serde_as] #[skip_serializing_none] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] @@ -109,20 +111,6 @@ impl JsonWebKey { self } - #[must_use] - pub const fn kty(&self) -> JsonWebKeyType { - match self.parameters { - JsonWebKeyParameters::Ec { .. } => JsonWebKeyType::Ec, - JsonWebKeyParameters::Rsa { .. } => JsonWebKeyType::Rsa, - JsonWebKeyParameters::Okp { .. } => JsonWebKeyType::Okp, - } - } - - #[must_use] - pub fn kid(&self) -> Option<&str> { - self.kid.as_deref() - } - #[must_use] pub const fn alg(&self) -> Option { self.alg @@ -134,6 +122,58 @@ impl JsonWebKey { } } +impl Constrainable for JsonWebKey { + fn kid(&self) -> Option<&str> { + self.kid.as_deref() + } + + fn kty(&self) -> JsonWebKeyType { + match self.parameters { + JsonWebKeyParameters::Ec { .. } => JsonWebKeyType::Ec, + JsonWebKeyParameters::Rsa { .. } => JsonWebKeyType::Rsa, + JsonWebKeyParameters::Okp { .. } => JsonWebKeyType::Okp, + } + } + + fn algs(&self) -> Option> { + if let Some(alg) = self.alg { + Some(vec![alg]) + } else { + match self.parameters { + JsonWebKeyParameters::Rsa { .. } => Some(vec![ + JsonWebSignatureAlg::Rs256, + JsonWebSignatureAlg::Rs384, + JsonWebSignatureAlg::Rs512, + JsonWebSignatureAlg::Ps256, + JsonWebSignatureAlg::Ps384, + JsonWebSignatureAlg::Ps512, + ]), + JsonWebKeyParameters::Ec { + crv: JsonWebKeyEcEllipticCurve::P256, + .. + } => Some(vec![JsonWebSignatureAlg::Es256]), + JsonWebKeyParameters::Ec { + crv: JsonWebKeyEcEllipticCurve::P384, + .. + } => Some(vec![JsonWebSignatureAlg::Es384]), + JsonWebKeyParameters::Ec { + crv: JsonWebKeyEcEllipticCurve::P521, + .. + } => Some(vec![JsonWebSignatureAlg::Es512]), + JsonWebKeyParameters::Ec { + crv: JsonWebKeyEcEllipticCurve::Secp256K1, + .. + } => Some(vec![JsonWebSignatureAlg::Es256K]), + JsonWebKeyParameters::Okp { .. } => Some(vec![JsonWebSignatureAlg::EdDsa]), + } + } + } + + fn use_(&self) -> Option { + self.r#use + } +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] pub struct JsonWebKeySet { keys: Vec, @@ -251,10 +291,11 @@ impl From for JsonWebKeyParameters { #[cfg(test)] mod tests { use super::*; + use crate::constraints::ConstraintSet; #[test] fn load_google_keys() { - let jwks = r#"{ + let jwks = serde_json::json!({ "keys": [ { "alg": "RS256", @@ -273,19 +314,33 @@ mod tests { "alg": "RS256" } ] - }"#; + }); - let jwks: JsonWebKeySet = serde_json::from_str(jwks).unwrap(); + let jwks: JsonWebKeySet = serde_json::from_value(jwks).unwrap(); // Both keys are RSA public keys - for jwk in jwks.keys { - rsa::RsaPublicKey::try_from(jwk.parameters).unwrap(); + for jwk in &jwks.keys { + rsa::RsaPublicKey::try_from(jwk.parameters.clone()).unwrap(); } + + let constraints = ConstraintSet::default() + .use_(JsonWebKeyUse::Sig) + .kty(JsonWebKeyType::Rsa) + .alg(JsonWebSignatureAlg::Rs256); + let candidates = constraints.filter(&jwks.keys); + assert_eq!(candidates.len(), 2); + + let constraints = ConstraintSet::default() + .use_(JsonWebKeyUse::Sig) + .kty(JsonWebKeyType::Rsa) + .kid("03e84aed4ef4431014e8617567864c4efaaaede9"); + let candidates = constraints.filter(&jwks.keys); + assert_eq!(candidates.len(), 1); } #[allow(clippy::too_many_lines)] #[test] fn load_keycloak_keys() { - let jwks = r#"{ + let jwks = serde_json::json!({ "keys": [ { "kid": "SuGUPE9Sr-1Gha2NLse33r5NQu3XoS_I3Qds3bcmfQE", @@ -393,9 +448,9 @@ mod tests { "y": "Vu3rTFNn_9zWTki95UGT1Bd9PN84KDXmttCrJ1bsYHTWQCaEONk8iwA3U6mEDrg4xtZSTXXKCFdFP13ONWB9oZ4" } ] - }"#; + }); - let jwks: JsonWebKeySet = serde_json::from_str(jwks).unwrap(); + let jwks: JsonWebKeySet = serde_json::from_value(jwks).unwrap(); // The first 6 keys are RSA, 7th is P-256 let mut keys = jwks.keys.into_iter(); rsa::RsaPublicKey::try_from(keys.next().unwrap().parameters).unwrap(); diff --git a/crates/jose/src/jwt/signed.rs b/crates/jose/src/jwt/signed.rs index 3c159f18..97b50a6d 100644 --- a/crates/jose/src/jwt/signed.rs +++ b/crates/jose/src/jwt/signed.rs @@ -115,14 +115,17 @@ where fn try_from(value: &'a str) -> Result { let raw = RawJwt::try_from(value)?; + let header_reader = + base64ct::Decoder::<'_, Base64UrlUnpadded>::new(raw.header().as_bytes()) + .map_err(JwtDecodeError::decode_header)?; let header = - Base64UrlUnpadded::decode_vec(raw.header()).map_err(JwtDecodeError::decode_header)?; - let header = serde_json::from_slice(&header).map_err(JwtDecodeError::deserialize_header)?; + serde_json::from_reader(header_reader).map_err(JwtDecodeError::deserialize_header)?; + let payload_reader = + base64ct::Decoder::<'_, Base64UrlUnpadded>::new(raw.payload().as_bytes()) + .map_err(JwtDecodeError::decode_payload)?; let payload = - Base64UrlUnpadded::decode_vec(raw.payload()).map_err(JwtDecodeError::decode_payload)?; - let payload = - serde_json::from_slice(&payload).map_err(JwtDecodeError::deserialize_payload)?; + serde_json::from_reader(payload_reader).map_err(JwtDecodeError::deserialize_payload)?; let signature = Base64UrlUnpadded::decode_vec(raw.signature()) .map_err(JwtDecodeError::decode_signature)?; @@ -162,6 +165,14 @@ impl JwtVerificationError { } impl<'a, T> Jwt<'a, T> { + pub fn header(&self) -> &JsonWebSignatureHeader { + &self.header + } + + pub fn payload(&self) -> &T { + &self.payload + } + pub fn verify(&self, key: &K) -> Result<(), JwtVerificationError> where K: Verifier, diff --git a/crates/jose/src/keystore/jwks/static_store.rs b/crates/jose/src/keystore/jwks/static_store.rs index 7c51226b..731d63c3 100644 --- a/crates/jose/src/keystore/jwks/static_store.rs +++ b/crates/jose/src/keystore/jwks/static_store.rs @@ -21,7 +21,10 @@ use sha2::{Sha256, Sha384, Sha512}; use signature::{Signature, Verifier}; use thiserror::Error; -use crate::{JsonWebKey, JsonWebKeySet, JsonWebSignatureHeader, VerifyingKeystore}; +use crate::{ + constraints::Constrainable, JsonWebKey, JsonWebKeySet, JsonWebSignatureHeader, + VerifyingKeystore, +}; #[derive(Debug, Error)] pub enum Error { diff --git a/crates/jose/src/lib.rs b/crates/jose/src/lib.rs index a2b671e4..855b7145 100644 --- a/crates/jose/src/lib.rs +++ b/crates/jose/src/lib.rs @@ -18,9 +18,13 @@ #![allow(clippy::missing_errors_doc, clippy::module_name_repetitions)] pub mod claims; +pub mod constraints; +pub mod hmac; pub(crate) mod jwk; pub(crate) mod jwt; mod keystore; +pub(crate) mod rsa; +pub mod verifier; pub use futures_util::future::Either; diff --git a/crates/jose/src/rsa.rs b/crates/jose/src/rsa.rs new file mode 100644 index 00000000..3998a92f --- /dev/null +++ b/crates/jose/src/rsa.rs @@ -0,0 +1,146 @@ +// Copyright 2022 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This is a temporary wrapper until the RSA crate actually hashes the input +// See + +pub(crate) mod pkcs1v15 { + use std::marker::PhantomData; + + use digest::Digest; + use rsa::RsaPublicKey; + use sha2::{Sha256, Sha384, Sha512}; + + pub struct VerifyingKey { + inner: rsa::pkcs1v15::VerifyingKey, + hash: PhantomData, + } + + impl From for VerifyingKey { + fn from(key: RsaPublicKey) -> Self { + let inner = rsa::pkcs1v15::VerifyingKey::new_with_hash(key, rsa::Hash::SHA2_256); + ensure_verifier(Self { + inner, + hash: PhantomData::default(), + }) + } + } + + impl From for VerifyingKey { + fn from(key: RsaPublicKey) -> Self { + let inner = rsa::pkcs1v15::VerifyingKey::new_with_hash(key, rsa::Hash::SHA2_384); + ensure_verifier(Self { + inner, + hash: PhantomData::default(), + }) + } + } + + impl From for VerifyingKey { + fn from(key: RsaPublicKey) -> Self { + let inner = rsa::pkcs1v15::VerifyingKey::new_with_hash(key, rsa::Hash::SHA2_512); + ensure_verifier(Self { + inner, + hash: PhantomData::default(), + }) + } + } + + #[inline] + fn ensure_verifier(t: T) -> T + where + T: signature::Verifier, + { + t + } + + impl signature::Verifier for VerifyingKey + where + H: Digest, + { + fn verify( + &self, + msg: &[u8], + signature: &rsa::pkcs1v15::Signature, + ) -> Result<(), signature::Error> { + let digest = H::digest(msg); + self.inner.verify(&digest, signature) + } + } +} + +pub(crate) mod pss { + use std::marker::PhantomData; + + use digest::Digest; + use rsa::RsaPublicKey; + use sha2::{Sha256, Sha384, Sha512}; + + pub struct VerifyingKey { + inner: rsa::pss::VerifyingKey, + hash: PhantomData, + } + + impl From for VerifyingKey { + fn from(key: RsaPublicKey) -> Self { + let inner = rsa::pss::VerifyingKey::new(key, Box::new(Sha256::new())); + ensure_verifier(Self { + inner, + hash: PhantomData::default(), + }) + } + } + + impl From for VerifyingKey { + fn from(key: RsaPublicKey) -> Self { + let inner = rsa::pss::VerifyingKey::new(key, Box::new(Sha384::new())); + ensure_verifier(Self { + inner, + hash: PhantomData::default(), + }) + } + } + + impl From for VerifyingKey { + fn from(key: RsaPublicKey) -> Self { + let inner = rsa::pss::VerifyingKey::new(key, Box::new(Sha512::new())); + ensure_verifier(Self { + inner, + hash: PhantomData::default(), + }) + } + } + + #[inline] + fn ensure_verifier(t: T) -> T + where + T: signature::Verifier, + { + t + } + + impl signature::Verifier for VerifyingKey + where + H: Digest, + { + fn verify( + &self, + msg: &[u8], + signature: &rsa::pss::Signature, + ) -> Result<(), signature::Error> { + let digest = H::digest(msg); + self.inner.verify(&digest, signature) + } + } +} diff --git a/crates/jose/src/verifier.rs b/crates/jose/src/verifier.rs new file mode 100644 index 00000000..8318f16e --- /dev/null +++ b/crates/jose/src/verifier.rs @@ -0,0 +1,330 @@ +// Copyright 2022 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use mas_iana::jose::{JsonWebKeyEcEllipticCurve, JsonWebSignatureAlg}; +use rsa::BigUint; +use sha2::{Sha256, Sha384, Sha512}; +use signature::Signature; +use thiserror::Error; + +use crate::jwk::JsonWebKeyParameters; + +pub enum Verifier { + Hs256 { + key: crate::hmac::Hmac, + }, + Hs384 { + key: crate::hmac::Hmac, + }, + Hs512 { + key: crate::hmac::Hmac, + }, + Rs256 { + key: crate::rsa::pkcs1v15::VerifyingKey, + }, + Rs384 { + key: crate::rsa::pkcs1v15::VerifyingKey, + }, + Rs512 { + key: crate::rsa::pkcs1v15::VerifyingKey, + }, + Ps256 { + key: crate::rsa::pss::VerifyingKey, + }, + Ps384 { + key: crate::rsa::pss::VerifyingKey, + }, + Ps512 { + key: crate::rsa::pss::VerifyingKey, + }, + Es256 { + key: ecdsa::VerifyingKey, + }, + Es384 { + key: ecdsa::VerifyingKey, + }, + Es256K { + key: ecdsa::VerifyingKey, + }, +} + +#[derive(Debug, Error)] +pub enum VerifierFromJwkError { + #[error("invalid RSA key")] + InvalidRsaKey { + #[from] + inner: rsa::errors::Error, + }, + + #[error("invalid elliptic curve key")] + InvalidEcKey { + #[from] + inner: ecdsa::Error, + }, + + #[error("invalid curve parameter X")] + InvalidCurveParameterX, + + #[error("invalid curve parameter Y")] + InvalidCurveParameterY, + + #[error("algorithm {algorithm} is not supported")] + UnsupportedAlgorithm { algorithm: JsonWebSignatureAlg }, + + #[error("key is not suitable for algorithm {algorithm}")] + KeyNotSuitable { algorithm: JsonWebSignatureAlg }, +} +#[derive(Debug, Error)] +pub enum VerifierFromOctError { + #[error("algorithm {algorithm} is not supported")] + UnsupportedAlgorithm { algorithm: JsonWebSignatureAlg }, +} + +impl Verifier { + pub fn for_oct_and_alg( + key: Vec, + alg: JsonWebSignatureAlg, + ) -> Result { + match alg { + JsonWebSignatureAlg::Hs256 => Ok(Self::Hs256 { key: key.into() }), + JsonWebSignatureAlg::Hs384 => Ok(Self::Hs384 { key: key.into() }), + JsonWebSignatureAlg::Hs512 => Ok(Self::Hs512 { key: key.into() }), + algorithm => Err(VerifierFromOctError::UnsupportedAlgorithm { algorithm }), + } + } + + #[allow(clippy::too_many_lines)] + pub fn for_jwk_and_alg( + key: &JsonWebKeyParameters, + alg: JsonWebSignatureAlg, + ) -> Result { + match (key, alg) { + (JsonWebKeyParameters::Rsa { n, e }, JsonWebSignatureAlg::Rs256) => { + let n = BigUint::from_bytes_be(n); + let e = BigUint::from_bytes_be(e); + let key = rsa::RsaPublicKey::new(n, e)?; + Ok(Self::Rs256 { key: key.into() }) + } + + (JsonWebKeyParameters::Rsa { n, e }, JsonWebSignatureAlg::Rs384) => { + let n = BigUint::from_bytes_be(n); + let e = BigUint::from_bytes_be(e); + let key = rsa::RsaPublicKey::new(n, e)?; + Ok(Self::Rs384 { key: key.into() }) + } + + (JsonWebKeyParameters::Rsa { n, e }, JsonWebSignatureAlg::Rs512) => { + let n = BigUint::from_bytes_be(n); + let e = BigUint::from_bytes_be(e); + let key = rsa::RsaPublicKey::new(n, e)?; + Ok(Self::Rs512 { key: key.into() }) + } + + (JsonWebKeyParameters::Rsa { n, e }, JsonWebSignatureAlg::Ps256) => { + let n = BigUint::from_bytes_be(n); + let e = BigUint::from_bytes_be(e); + let key = rsa::RsaPublicKey::new(n, e)?; + Ok(Self::Ps256 { key: key.into() }) + } + + (JsonWebKeyParameters::Rsa { n, e }, JsonWebSignatureAlg::Ps384) => { + let n = BigUint::from_bytes_be(n); + let e = BigUint::from_bytes_be(e); + let key = rsa::RsaPublicKey::new(n, e)?; + Ok(Self::Ps384 { key: key.into() }) + } + + (JsonWebKeyParameters::Rsa { n, e }, JsonWebSignatureAlg::Ps512) => { + let n = BigUint::from_bytes_be(n); + let e = BigUint::from_bytes_be(e); + let key = rsa::RsaPublicKey::new(n, e)?; + Ok(Self::Ps512 { key: key.into() }) + } + + ( + JsonWebKeyParameters::Ec { + crv: JsonWebKeyEcEllipticCurve::P256, + x, + y, + }, + JsonWebSignatureAlg::Es256, + ) => { + let x: &[u8; 32] = x + .as_slice() + .try_into() + .map_err(|_| VerifierFromJwkError::InvalidCurveParameterX)?; + let y: &[u8; 32] = y + .as_slice() + .try_into() + .map_err(|_| VerifierFromJwkError::InvalidCurveParameterY)?; + + let point = sec1::EncodedPoint::from_affine_coordinates(x.into(), y.into(), false); + let key = ecdsa::VerifyingKey::from_encoded_point(&point)?; + + Ok(Self::Es256 { key }) + } + + ( + JsonWebKeyParameters::Ec { + crv: JsonWebKeyEcEllipticCurve::P384, + x, + y, + }, + JsonWebSignatureAlg::Es384, + ) => { + let x: &[u8; 48] = x + .as_slice() + .try_into() + .map_err(|_| VerifierFromJwkError::InvalidCurveParameterX)?; + let y: &[u8; 48] = y + .as_slice() + .try_into() + .map_err(|_| VerifierFromJwkError::InvalidCurveParameterY)?; + + let point = sec1::EncodedPoint::from_affine_coordinates(x.into(), y.into(), false); + let key = ecdsa::VerifyingKey::from_encoded_point(&point)?; + + Ok(Self::Es384 { key }) + } + + ( + JsonWebKeyParameters::Ec { + crv: JsonWebKeyEcEllipticCurve::P521, + x: _, + y: _, + }, + JsonWebSignatureAlg::Es512, + ) => Err(VerifierFromJwkError::UnsupportedAlgorithm { + algorithm: JsonWebSignatureAlg::Es512, + }), + + ( + JsonWebKeyParameters::Ec { + crv: JsonWebKeyEcEllipticCurve::Secp256K1, + x, + y, + }, + JsonWebSignatureAlg::Es256K, + ) => { + let x: &[u8; 32] = x + .as_slice() + .try_into() + .map_err(|_| VerifierFromJwkError::InvalidCurveParameterX)?; + let y: &[u8; 32] = y + .as_slice() + .try_into() + .map_err(|_| VerifierFromJwkError::InvalidCurveParameterY)?; + + let point = sec1::EncodedPoint::from_affine_coordinates(x.into(), y.into(), false); + let key = ecdsa::VerifyingKey::from_encoded_point(&point)?; + + Ok(Self::Es256K { key }) + } + + (JsonWebKeyParameters::Okp { crv: _, x: _ }, JsonWebSignatureAlg::EdDsa) => { + Err(VerifierFromJwkError::UnsupportedAlgorithm { + algorithm: JsonWebSignatureAlg::EdDsa, + }) + } + + (_, algorithm) => Err(VerifierFromJwkError::KeyNotSuitable { algorithm }), + } + } +} + +#[derive(Debug)] +pub struct GenericSignature { + bytes: Vec, +} + +impl AsRef<[u8]> for GenericSignature { + fn as_ref(&self) -> &[u8] { + &self.bytes + } +} + +impl signature::Signature for GenericSignature { + fn from_bytes(bytes: &[u8]) -> Result { + Ok(Self { + bytes: bytes.to_vec(), + }) + } +} + +impl signature::Verifier for Verifier { + fn verify(&self, msg: &[u8], signature: &GenericSignature) -> Result<(), signature::Error> { + match self { + Verifier::Hs256 { key } => { + let signature = crate::hmac::Signature::from_bytes(signature.as_bytes())?; + key.verify(msg, &signature)?; + Ok(()) + } + Verifier::Hs384 { key } => { + let signature = crate::hmac::Signature::from_bytes(signature.as_bytes())?; + key.verify(msg, &signature)?; + Ok(()) + } + Verifier::Hs512 { key } => { + let signature = crate::hmac::Signature::from_bytes(signature.as_bytes())?; + key.verify(msg, &signature)?; + Ok(()) + } + Verifier::Rs256 { key } => { + let signature = rsa::pkcs1v15::Signature::from_bytes(signature.as_bytes())?; + key.verify(msg, &signature)?; + Ok(()) + } + Verifier::Rs384 { key } => { + let signature = rsa::pkcs1v15::Signature::from_bytes(signature.as_bytes())?; + key.verify(msg, &signature)?; + Ok(()) + } + Verifier::Rs512 { key } => { + let signature = rsa::pkcs1v15::Signature::from_bytes(signature.as_bytes())?; + key.verify(msg, &signature)?; + Ok(()) + } + Verifier::Ps256 { key } => { + let signature = rsa::pss::Signature::from_bytes(signature.as_bytes())?; + key.verify(msg, &signature)?; + Ok(()) + } + Verifier::Ps384 { key } => { + let signature = rsa::pss::Signature::from_bytes(signature.as_bytes())?; + key.verify(msg, &signature)?; + Ok(()) + } + Verifier::Ps512 { key } => { + let signature = rsa::pss::Signature::from_bytes(signature.as_bytes())?; + key.verify(msg, &signature)?; + Ok(()) + } + Verifier::Es256 { key } => { + let signature = ecdsa::Signature::from_bytes(signature.as_bytes())?; + key.verify(msg, &signature)?; + Ok(()) + } + Verifier::Es384 { key } => { + let signature = ecdsa::Signature::from_bytes(signature.as_bytes())?; + key.verify(msg, &signature)?; + Ok(()) + } + Verifier::Es256K { key } => { + let signature = ecdsa::Signature::from_bytes(signature.as_bytes())?; + key.verify(msg, &signature)?; + Ok(()) + } + } + } +} diff --git a/crates/jose/tests/generate.py b/crates/jose/tests/generate.py new file mode 100644 index 00000000..d66f5536 --- /dev/null +++ b/crates/jose/tests/generate.py @@ -0,0 +1,138 @@ +# Generates test keys, JWKS and JWTs +# Required the `openssl` binary and the `authlib` python library + +import json +import subprocess +from pathlib import Path +from typing import List + +from authlib.jose import JsonWebKey, JsonWebSignature, KeySet + +output_path = Path(__file__).parent + +keys_path = output_path / "keys" +keys_path.mkdir(parents=True, exist_ok=True) + +jwts_path = output_path / "jwts" +jwts_path.mkdir(parents=True, exist_ok=True) + + +def gen_key( + name: str, + priv_command: List[str], + pub_command: List[str], +): + """Generate a keypair + + Args: + name: Name + priv_command: Command to generate the private key. This must write the + key to stdout. + pub_command: Command to convert the private key to a public one. This + must read the private key from stdin and write the public key to + stdout. + """ + priv_path = keys_path / f"{name}.priv.pem" + pub_path = keys_path / f"{name}.pub.pem" + + with open(priv_path, "wb") as f: + subprocess.run(priv_command, stdout=f, stderr=subprocess.DEVNULL) + + with open(priv_path, "rb") as priv, open(pub_path, "wb") as pub: + subprocess.run(pub_command, stdin=priv, stdout=pub, stderr=subprocess.DEVNULL) + + +def import_key(name: str, kty: str) -> JsonWebKey: + """Import a key from a file""" + with open(keys_path / name, "r") as f: + pem = f.read() + return JsonWebKey.import_key(pem, {"kty": kty}) + + +def sign_jwt(alg: str, filename: str, key: JsonWebKey): + """Sign a JWT for the given key""" + path = jwts_path / filename + protected = {"alg": alg, "kid": key.thumbprint()} + payload = '{"hello":"world"}' + jws = JsonWebSignature(algorithms=[alg]) + jwt = jws.serialize_compact(protected, payload, key) + with open(path, "wb") as f: + f.write(jwt) + + +with open(keys_path / "oct.bin", "wb") as f: + subprocess.run( + ["openssl", "rand", "-hex", "64"], stdout=f, stderr=subprocess.DEVNULL + ) + +gen_key("rsa", ["openssl", "genrsa", "2048"], ["openssl", "rsa", "-pubout"]) +gen_key( + "p256", + ["openssl", "ecparam", "-genkey", "-name", "prime256v1"], + ["openssl", "ec", "-pubout"], +) +gen_key( + "p384", + ["openssl", "ecparam", "-genkey", "-name", "secp384r1"], + ["openssl", "ec", "-pubout"], +) +gen_key( + "p521", + ["openssl", "ecparam", "-genkey", "-name", "secp521r1"], + ["openssl", "ec", "-pubout"], +) +gen_key( + "k256", + ["openssl", "ecparam", "-genkey", "-name", "secp256k1"], + ["openssl", "ec", "-pubout"], +) +gen_key( + "ed25519", + ["openssl", "genpkey", "-algorithm", "ed25519"], + ["openssl", "pkey", "-pubout"], +) +gen_key( + "ed448", + ["openssl", "genpkey", "-algorithm", "ed448"], + ["openssl", "pkey", "-pubout"], +) + +oct_key = import_key("oct.bin", "oct") +rsa_key = import_key("rsa.priv.pem", "RSA") +p256_key = import_key("p256.priv.pem", "EC") +p384_key = import_key("p384.priv.pem", "EC") +p521_key = import_key("p521.priv.pem", "EC") +k256_key = import_key("k256.priv.pem", "EC") +ed25519_key = import_key("ed25519.priv.pem", "OKP") +ed448_key = import_key("ed448.priv.pem", "OKP") + +key_set = KeySet( + [rsa_key, p256_key, p384_key, p521_key, k256_key, ed25519_key, ed448_key] +) + +with open(keys_path / "jwks.pub.json", "w", encoding="utf8") as f: + json.dump(key_set.as_dict(is_private=False), f, indent=2, sort_keys=True) + +key_set.keys.insert(0, oct_key) + +with open(keys_path / "jwks.priv.json", "w", encoding="utf8") as f: + json.dump(key_set.as_dict(is_private=True), f, indent=2, sort_keys=True) + +sign_jwt("HS256", "hs256.jwt", oct_key) +sign_jwt("HS384", "hs384.jwt", oct_key) +sign_jwt("HS512", "hs512.jwt", oct_key) +sign_jwt("RS256", "rs256.jwt", rsa_key) +sign_jwt("RS384", "rs384.jwt", rsa_key) +sign_jwt("RS512", "rs512.jwt", rsa_key) +sign_jwt("RS256", "rs256.jwt", rsa_key) +sign_jwt("RS384", "rs384.jwt", rsa_key) +sign_jwt("RS512", "rs512.jwt", rsa_key) +sign_jwt("PS256", "ps256.jwt", rsa_key) +sign_jwt("PS384", "ps384.jwt", rsa_key) +sign_jwt("PS512", "ps512.jwt", rsa_key) +sign_jwt("ES256", "es256.jwt", p256_key) +sign_jwt("ES384", "es384.jwt", p384_key) +sign_jwt("ES512", "es512.jwt", p521_key) +sign_jwt("ES256K", "es256k.jwt", k256_key) +sign_jwt("EdDSA", "eddsa-ed25519.jwt", ed25519_key) +sign_jwt("EdDSA", "eddsa-ed448.jwt", ed448_key) diff --git a/crates/jose/tests/jws.rs b/crates/jose/tests/jws.rs new file mode 100644 index 00000000..6cabfb67 --- /dev/null +++ b/crates/jose/tests/jws.rs @@ -0,0 +1,107 @@ +// Copyright 2022 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::ops::Deref; + +use mas_jose::{constraints::ConstraintSet, JsonWebKeySet, Jwt}; +use serde::Deserialize; + +static HS256_JWT: &str = include_str!("./jwts/hs256.jwt"); +static HS384_JWT: &str = include_str!("./jwts/hs384.jwt"); +static HS512_JWT: &str = include_str!("./jwts/hs512.jwt"); +static RS256_JWT: &str = include_str!("./jwts/rs256.jwt"); +static RS384_JWT: &str = include_str!("./jwts/rs384.jwt"); +static RS512_JWT: &str = include_str!("./jwts/rs512.jwt"); +static PS256_JWT: &str = include_str!("./jwts/ps256.jwt"); +static PS384_JWT: &str = include_str!("./jwts/ps384.jwt"); +static PS512_JWT: &str = include_str!("./jwts/ps512.jwt"); +static ES256_JWT: &str = include_str!("./jwts/es256.jwt"); +static ES384_JWT: &str = include_str!("./jwts/es384.jwt"); +static ES512_JWT: &str = include_str!("./jwts/es512.jwt"); +static ES256K_JWT: &str = include_str!("./jwts/es256k.jwt"); +static EDDSA_ED25519_JWT: &str = include_str!("./jwts/eddsa-ed25519.jwt"); +static EDDSA_ED448_JWT: &str = include_str!("./jwts/eddsa-ed448.jwt"); +static OCT_KEY: &[u8] = include_bytes!("./keys/oct.bin"); + +fn public_jwks() -> JsonWebKeySet { + serde_json::from_str(include_str!("./keys/jwks.pub.json")).unwrap() +} + +fn oct_key() -> Vec { + OCT_KEY.to_vec() +} + +#[derive(Deserialize)] +struct Payload { + hello: String, +} + +macro_rules! asymetric_jwt_test { + ($test_name:ident, $jwt:ident) => { + asymetric_jwt_test!($test_name, $jwt, verify = true); + }; + ($test_name:ident, $jwt:ident, verify = $verify:ident) => { + #[test] + fn $test_name() { + let jwks = public_jwks(); + let jwt: Jwt<'_, Payload> = Jwt::try_from($jwt).unwrap(); + assert_eq!(jwt.payload().hello, "world"); + + let constraints = ConstraintSet::from(jwt.header()); + let candidates = constraints.filter(jwks.deref()); + assert_eq!(candidates.len(), 1); + let candidate = candidates[0]; + + if $verify { + let verifier = mas_jose::verifier::Verifier::for_jwk_and_alg( + candidate.params(), + jwt.header().alg(), + ) + .unwrap(); + jwt.verify(&verifier).unwrap(); + } + } + }; +} + +macro_rules! symetric_jwt_test { + ($test_name:ident, $jwt:ident) => { + #[test] + fn $test_name() { + let jwt: Jwt<'_, Payload> = Jwt::try_from($jwt).unwrap(); + let verifier = + mas_jose::verifier::Verifier::for_oct_and_alg(oct_key(), jwt.header().alg()) + .unwrap(); + assert_eq!(jwt.payload().hello, "world"); + jwt.verify(&verifier).unwrap(); + } + }; +} + +symetric_jwt_test!(test_hs256, HS256_JWT); +symetric_jwt_test!(test_hs384, HS384_JWT); +symetric_jwt_test!(test_hs512, HS512_JWT); + +asymetric_jwt_test!(test_rs256, RS256_JWT); +asymetric_jwt_test!(test_rs384, RS384_JWT); +asymetric_jwt_test!(test_rs512, RS512_JWT); +asymetric_jwt_test!(test_ps256, PS256_JWT); +asymetric_jwt_test!(test_ps384, PS384_JWT); +asymetric_jwt_test!(test_ps512, PS512_JWT); +asymetric_jwt_test!(test_es256, ES256_JWT); +asymetric_jwt_test!(test_es384, ES384_JWT); +asymetric_jwt_test!(test_es512, ES512_JWT, verify = false); +asymetric_jwt_test!(test_es256k, ES256K_JWT); +asymetric_jwt_test!(test_eddsa_ed25519, EDDSA_ED25519_JWT, verify = false); +asymetric_jwt_test!(test_eddsa_ed448, EDDSA_ED448_JWT, verify = false); diff --git a/crates/jose/tests/jwts/eddsa-ed25519.jwt b/crates/jose/tests/jwts/eddsa-ed25519.jwt new file mode 100644 index 00000000..4d7793e1 --- /dev/null +++ b/crates/jose/tests/jwts/eddsa-ed25519.jwt @@ -0,0 +1 @@ +eyJhbGciOiJFZERTQSIsImtpZCI6ImlYa2l5aEVoNkU3VS1hWDBmZzd3LWVzSFdxUHZ2eFdkNmdIMUpHMnU3TjAifQ.eyJoZWxsbyI6IndvcmxkIn0.ZFiNWsheqUC_mQNztHpZXLnyb5LtvyT1dTGcMSCgG97Cobju83xCIkbJwfjOSgZrI2CpEVobVM_mfnmFIAUfBg \ No newline at end of file diff --git a/crates/jose/tests/jwts/eddsa-ed448.jwt b/crates/jose/tests/jwts/eddsa-ed448.jwt new file mode 100644 index 00000000..e7e36b04 --- /dev/null +++ b/crates/jose/tests/jwts/eddsa-ed448.jwt @@ -0,0 +1 @@ +eyJhbGciOiJFZERTQSIsImtpZCI6IlFsdGEycVZsaEhoZzNqcmlKcDBIc0lCUXFHVkIxWkgycEVueVBIemwxTXMifQ.eyJoZWxsbyI6IndvcmxkIn0.7EqBc73c8UjbZnW5LkkDmPlAnlgjVdDzfABvssoLE3FoFX3uUr1dPdX3I9Hu_rtOIdRtTLfN9eeABuG5cugUoshrYSFuHF6vy2Nim7uM3GWa6mVZx6fzOBq6goCK4JpNfwkJ3a4VyslHU7wQBfXAOxcA \ No newline at end of file diff --git a/crates/jose/tests/jwts/es256.jwt b/crates/jose/tests/jwts/es256.jwt new file mode 100644 index 00000000..169b0676 --- /dev/null +++ b/crates/jose/tests/jwts/es256.jwt @@ -0,0 +1 @@ +eyJhbGciOiJFUzI1NiIsImtpZCI6ImxNYlNJNjlhanNCSEhrSXBWQUZLUktZblI2NmtHZEd0ZWcyb3FNenAwX0UifQ.eyJoZWxsbyI6IndvcmxkIn0.YckCGhpak2hpO9EiR-X2MD6CVBnUAmQbRVKvKoYCbRnydOOksNlzWaOl0S-C4KZxGTuKG-spzFQJov5h_ob5nw \ No newline at end of file diff --git a/crates/jose/tests/jwts/es256k.jwt b/crates/jose/tests/jwts/es256k.jwt new file mode 100644 index 00000000..cf20eedb --- /dev/null +++ b/crates/jose/tests/jwts/es256k.jwt @@ -0,0 +1 @@ +eyJhbGciOiJFUzI1NksiLCJraWQiOiJuOWI0Z3lkNU5nSHY3cEo3UzI3QUtCcmhCUEhhM0g1cHRjaXhtWWVyU1VnIn0.eyJoZWxsbyI6IndvcmxkIn0.e0XIMec0_gvlxS8je5hVpYQGls2A5r2TUJ9eJNmdwZQbo1alRB93dgbh3yd4fh8bDOmmLhRfMKti93c7-ljPVg \ No newline at end of file diff --git a/crates/jose/tests/jwts/es384.jwt b/crates/jose/tests/jwts/es384.jwt new file mode 100644 index 00000000..6a23aafa --- /dev/null +++ b/crates/jose/tests/jwts/es384.jwt @@ -0,0 +1 @@ +eyJhbGciOiJFUzM4NCIsImtpZCI6IkoxRVpKR1AxTHloWDJabHo4eFBjc3BNUUVsOFczYllHMngzTnFpTWJQeVkifQ.eyJoZWxsbyI6IndvcmxkIn0.XK3AIs0TQ1r5Wbpd14MkVIp3rvisQEb_8wlp3F4usveL23GH15y5TQ8mcU5NrxNFFylclwikyz4ozM2zmU7fkCYfjKD8AoEABOTlfjH3DRQnynVcpkvB47CsSgt8QpGe \ No newline at end of file diff --git a/crates/jose/tests/jwts/es512.jwt b/crates/jose/tests/jwts/es512.jwt new file mode 100644 index 00000000..8b70bb84 --- /dev/null +++ b/crates/jose/tests/jwts/es512.jwt @@ -0,0 +1 @@ +eyJhbGciOiJFUzUxMiIsImtpZCI6Il94R3lJM21zOTBBdmdGNjU4d3o5NzF3c3dTeVluR1NHX0EwZEFnbXJBTTAifQ.eyJoZWxsbyI6IndvcmxkIn0.AJ9YcP56d-1Z1wsZL0ikFRY_4Q6du7YEWsqtQDOloCLMYQ-3citw6Fm35t4kg8E5aoe8QrEj8kTqsQLloWv0eBMFAWh-Uyrupmz0Kzllc6xbOEVoWuM5DWc6AJ6Da6k0f6XHsZ_MVcayQpdmZTLcM_pyo1U6olqwLYqv1YNx-2M2GdCl \ No newline at end of file diff --git a/crates/jose/tests/jwts/hs256.jwt b/crates/jose/tests/jwts/hs256.jwt new file mode 100644 index 00000000..ff2c13be --- /dev/null +++ b/crates/jose/tests/jwts/hs256.jwt @@ -0,0 +1 @@ +eyJhbGciOiJIUzI1NiIsImtpZCI6ImRqSEtvV1Uzck9sV2c1RTBFSV80RmxiRVRmZDRPRlFnVjk4REZYRW1HZmcifQ.eyJoZWxsbyI6IndvcmxkIn0.GBxkJdc15D26siv1Ov_a2jgQSIsgLwiF2ZDFSUdzoFY \ No newline at end of file diff --git a/crates/jose/tests/jwts/hs384.jwt b/crates/jose/tests/jwts/hs384.jwt new file mode 100644 index 00000000..a6c79979 --- /dev/null +++ b/crates/jose/tests/jwts/hs384.jwt @@ -0,0 +1 @@ +eyJhbGciOiJIUzM4NCIsImtpZCI6ImRqSEtvV1Uzck9sV2c1RTBFSV80RmxiRVRmZDRPRlFnVjk4REZYRW1HZmcifQ.eyJoZWxsbyI6IndvcmxkIn0.pOZkiI4HMCNHgUf9diq6CkFxsMIMCNADvDPHmtkjerSYWy16dmlZy-FT9ZxyyD_1 \ No newline at end of file diff --git a/crates/jose/tests/jwts/hs512.jwt b/crates/jose/tests/jwts/hs512.jwt new file mode 100644 index 00000000..bebbb1a3 --- /dev/null +++ b/crates/jose/tests/jwts/hs512.jwt @@ -0,0 +1 @@ +eyJhbGciOiJIUzUxMiIsImtpZCI6ImRqSEtvV1Uzck9sV2c1RTBFSV80RmxiRVRmZDRPRlFnVjk4REZYRW1HZmcifQ.eyJoZWxsbyI6IndvcmxkIn0.1kVwcE7LajF4Ph3yl2cKhJRs4FtZUMT6mxVCbtfttLPLqkxX-WAlZ0Hd7zg1JAzxNUmkeF8bsgZ9P0bPxBDSyw \ No newline at end of file diff --git a/crates/jose/tests/jwts/ps256.jwt b/crates/jose/tests/jwts/ps256.jwt new file mode 100644 index 00000000..c97ee9d3 --- /dev/null +++ b/crates/jose/tests/jwts/ps256.jwt @@ -0,0 +1 @@ +eyJhbGciOiJQUzI1NiIsImtpZCI6ImxqQXdGc1czMmV4cHlBMFJqcktvT0h1WnhmazdLTFNlajh6bGRPOXo0aVUifQ.eyJoZWxsbyI6IndvcmxkIn0.JWY1HZhLrDngEV7-V7to489hsX3muDOeCr4cedGUY2cpDNgJs0CgTe1pknXws9msZSlG4C-oA08UqgousBA2FWbcuVDhSEmSyNWM2rHekFuYcLlAupP8wucMQ3yzP425V2PzlgWV85xRe18PifNaTldMHLArbTKplMQgHHHopz28kuP1Uko99lHxpZrDVMHSLXNTyYaoQeOd81Hbx8uSx5wZO6tVIErV1RhKhSFGLP9DsbOKKW6jRgam_tKNh35VYBQZ6CIQkgsZCruDP7KFHHqC4xHTbkNQ6VlxHHHOpHz-SuRcBS901EN6NVCSPRSc0oYp1ChQCPgUeH_SrloCMg \ No newline at end of file diff --git a/crates/jose/tests/jwts/ps384.jwt b/crates/jose/tests/jwts/ps384.jwt new file mode 100644 index 00000000..adf3723e --- /dev/null +++ b/crates/jose/tests/jwts/ps384.jwt @@ -0,0 +1 @@ +eyJhbGciOiJQUzM4NCIsImtpZCI6ImxqQXdGc1czMmV4cHlBMFJqcktvT0h1WnhmazdLTFNlajh6bGRPOXo0aVUifQ.eyJoZWxsbyI6IndvcmxkIn0.XLe8Fxg1wALfGIYBtGtYCSxneiReNMRsUiXukYPS3KWvIH6xcLV93GflNRBHRE1aijy1GPnqZv-mZoKjfZr4PoZMX0MalE0j0bFqrLJvfoyxlZLTIzjfyYm81JtPwlB3iU3DvqKGAYBE8aknTOnv65nyprdhGuJhFEW-_7omDzXqE03DofIGQOu-F3nkVP5Om28VKY6Vdr7PswJhKawP97VXrhN5aIubSjldv5-LcKlVwjV9_3RTiEbVGCgluyhzUUhoa-y0Y1oplJC4GMzvQ1YCYQeYJOn0bB1FjpOryJ2mxlIf8qNzlDHnpyr5MVRJ2PAlhZ31GB5JGr_ZQYTRUA \ No newline at end of file diff --git a/crates/jose/tests/jwts/ps512.jwt b/crates/jose/tests/jwts/ps512.jwt new file mode 100644 index 00000000..43fde27c --- /dev/null +++ b/crates/jose/tests/jwts/ps512.jwt @@ -0,0 +1 @@ +eyJhbGciOiJQUzUxMiIsImtpZCI6ImxqQXdGc1czMmV4cHlBMFJqcktvT0h1WnhmazdLTFNlajh6bGRPOXo0aVUifQ.eyJoZWxsbyI6IndvcmxkIn0.aQ6sXJsU-U14WW7cD344aZi72Hf_XNq9LBi0_feKRVQOO98gV-jlBKWer-n_FI1qLcOUoHfitOciTOVLgvYxeJUwePCwUm9JhQ186CLAc6i_AhqpeKRlDkVOF_viQeZTFwEadHT2KMIe0ImZnPqGUb07arUdzGO67Lwsts2ob7qgG_uWgVbjXMkTUwt-JSHdXUcGIz1FgCJaFgGygfQE_I_doNiApWr2okiuIMs_4Q5BfxIlvPR-uaOcpqxk7ldukvQgUjv4rTfOGE12fCx5eLDVF4P1OXgMjgmcXH1yaV89DgTBgDPP11tQrbsFbANX004VLF9MQWoVF6esl6xwQw \ No newline at end of file diff --git a/crates/jose/tests/jwts/rs256.jwt b/crates/jose/tests/jwts/rs256.jwt new file mode 100644 index 00000000..136e3e4d --- /dev/null +++ b/crates/jose/tests/jwts/rs256.jwt @@ -0,0 +1 @@ +eyJhbGciOiJSUzI1NiIsImtpZCI6ImxqQXdGc1czMmV4cHlBMFJqcktvT0h1WnhmazdLTFNlajh6bGRPOXo0aVUifQ.eyJoZWxsbyI6IndvcmxkIn0.JLzSM5NDbAIb5vpbnKJeHUgU-uJ46616qzDjWXRbIAdxPk8WUqpRDRTlPoRUBXsAKn7E14r_CZmwvGAgJipS7EY0PbJYOkA_6oi8sYWykMUT1F2BlqKQGv2BvRR0LGu0tmm4XYZT2nOLRiEa4bs9l-D2jA5GRTKjDnmgUBHXpX4vIICtnkHHvZilMf1Fjsdm-3X9NFmxjtvQChg-w0h6hM3NZAt6Gd5AG8MaFf-mj3sLa40c51XXz1J1WE9iOWF8lGC6EfP5MSWunKnhyHf2xPQiH4C_Tvm529p2EiEBjjoL1f2A8WH8EYruHF8AXsz2F8HxN_7ryGmjrqLGwuw7iQ \ No newline at end of file diff --git a/crates/jose/tests/jwts/rs384.jwt b/crates/jose/tests/jwts/rs384.jwt new file mode 100644 index 00000000..efb5c6b2 --- /dev/null +++ b/crates/jose/tests/jwts/rs384.jwt @@ -0,0 +1 @@ +eyJhbGciOiJSUzM4NCIsImtpZCI6ImxqQXdGc1czMmV4cHlBMFJqcktvT0h1WnhmazdLTFNlajh6bGRPOXo0aVUifQ.eyJoZWxsbyI6IndvcmxkIn0.Qf9DbTCoisWvPvGYahn-dMe9r-escYv5cTL-5X2tz5uPRUmAEJ6D6cn0VtLCCPmTIuzSYDzeMqdEx1Is-AVkzvWMKdFRXNVL_E54bhS6Dg04a75bL4YGQOg8iaTTdRlHMaLLfClf8sXttpHmnOFhQ9C6pLcmtT5cfle8qrAw9x7Ivri7jkcjydWcR2WKsYHJxEWDwdhDiBK461F2fi9YtbZOL4qdKEoYpg08v4jH7hFf5G60W_k2oKvPQnbVJe0VcnGcEXvItMAEi8omMn3_OxIGNH-mxBf9DOpOu8Vj-kvvWuE03f31goWLqiL6-Eq8ykqqFZ3sKb23WfGPd26pDw \ No newline at end of file diff --git a/crates/jose/tests/jwts/rs512.jwt b/crates/jose/tests/jwts/rs512.jwt new file mode 100644 index 00000000..f03fb845 --- /dev/null +++ b/crates/jose/tests/jwts/rs512.jwt @@ -0,0 +1 @@ +eyJhbGciOiJSUzUxMiIsImtpZCI6ImxqQXdGc1czMmV4cHlBMFJqcktvT0h1WnhmazdLTFNlajh6bGRPOXo0aVUifQ.eyJoZWxsbyI6IndvcmxkIn0.UevGIlEIlrQWvLLm3Iouq6cxjWf7CtFwaDXQOUEQzdQxa3Mg9H0KD7Ztc1LRS36RFd0rnh9dWsXmeDbQ9yWNepnRvv0QP8Vxq3ty7wOHZtLn2kG1SjDQqgaU743p4n-YUpVugzSha0RHTiRN1TU4zufpx26jQBuO7ihOFof6trc8E2UG98Pgd1w3kv20Glwo_cWauhAefgDRhS-sOaH_SsOFWSBNCa8ISeIOiuKLFOEp2o1m2sla0yCDHVptERYDp3D_LHTLX-BP0dyaxpKwfQ7EuECGK1r7_yyiSq_pOwPrainC3lBKYovOgj8tYGTJxfw4Au_QSY57J96M7N4TmA \ No newline at end of file diff --git a/crates/jose/tests/keys/ed25519.priv.pem b/crates/jose/tests/keys/ed25519.priv.pem new file mode 100644 index 00000000..c3e3badf --- /dev/null +++ b/crates/jose/tests/keys/ed25519.priv.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIIutDmtMjMBKXN/Oxmfvxw3cNwtqgcyR2awtQYH/OS/5 +-----END PRIVATE KEY----- diff --git a/crates/jose/tests/keys/ed25519.pub.pem b/crates/jose/tests/keys/ed25519.pub.pem new file mode 100644 index 00000000..26e2dc92 --- /dev/null +++ b/crates/jose/tests/keys/ed25519.pub.pem @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAnVo63sClAQ8qwBAZW0tttHFhXdrLiKqJnFeJ+j3nA3U= +-----END PUBLIC KEY----- diff --git a/crates/jose/tests/keys/ed448.priv.pem b/crates/jose/tests/keys/ed448.priv.pem new file mode 100644 index 00000000..269ce375 --- /dev/null +++ b/crates/jose/tests/keys/ed448.priv.pem @@ -0,0 +1,4 @@ +-----BEGIN PRIVATE KEY----- +MEcCAQAwBQYDK2VxBDsEOdwdrXdIIxmkz/6pi3/JeOemGYvMECA+CvW5CAGXCvwi +VXFdnXxUt22BpU8Hl1jl1+kuGe3Mx5Pt3w== +-----END PRIVATE KEY----- diff --git a/crates/jose/tests/keys/ed448.pub.pem b/crates/jose/tests/keys/ed448.pub.pem new file mode 100644 index 00000000..bf447516 --- /dev/null +++ b/crates/jose/tests/keys/ed448.pub.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MEMwBQYDK2VxAzoAKgaYHB+xIpPPvmH2PdbnWT+67/CfJhuD3U90sv+i5CZmGdwt +WOErsowzNYSvuFWk8vztPOERjn4A +-----END PUBLIC KEY----- diff --git a/crates/jose/tests/keys/jwks.priv.json b/crates/jose/tests/keys/jwks.priv.json new file mode 100644 index 00000000..0dcaf8c2 --- /dev/null +++ b/crates/jose/tests/keys/jwks.priv.json @@ -0,0 +1,67 @@ +{ + "keys": [ + { + "k": "MjNlYWE0MzdjMmFjZTA0ZTM1YTNhNzdiODEzMjEwMGYzNDM4YWE2ODk0NmEzMDU1M2NiZjhmZTgwZTJhNWYzY2E3MDA4MjA1M2Y0YmQ0NDU3NjUzYmJiMTU4NzdkODQ3YzIzMjk0OGVhNzUxMmNjZDY3OWY3MWMyNjhmMWRkMDgK", + "kid": "djHKoWU3rOlWg5E0EI_4FlbETfd4OFQgV98DFXEmGfg", + "kty": "oct" + }, + { + "d": "PvWpVR1APf5Yxf0ZO20wx7jQXOFusJ5nijKAzyWAPFNx_pjoXD82BTaM0h7odiUGP8Bhc8VqctAQudQ_eEga-YXdpbKXL5wHZG0QzEQlZ_MT8tP_S4z_3rWoUyT9S1rPqWUuQvnb-YsfMWR3RuLGpS4a-3f_zUKbUT_RA5aJUWOMSMSOqMCuh4wdhK7GJKOQlWr8yXF6STEOx7nYxWpIxRdcJK3WinBXPZ5B88wgdDpcR0Yv32u-f7h4R5GHmjPPC2NXsWPpNMe8G6CbDQapgk_1mYViq6zpo4NgkrvBUsMRhFkI__Wlcfz12Ds9dBGxg1R-T7lIPY6wIZKUKLYVQQ", + "dp": "PlzIdqVNjdf5bJaRhW9-d3kF-sQJVV1x2prqRKr-FJK79M39fCmrlhsnyxVc7iqt6gSb3Mz_N1rsCbdn1TYUq6RFed3gJO2Bl57_K8L1N7LKjIxm2TpjLTtrMyxwQx72igxdjdvQytN1l6nmoAI76tv4dsw02dyQIsJ3BR7JiGE", + "dq": "rYGSaiGBnWhIJfI3ajRPy5Icuq7dO3pCgdoDPTMPgib-tHxegjx-v4frCq5Uqc9s94FsgR5_FsY3sttmMrYA7MCqGT3n2Peo718DharXhaScWabONJ8wAt2LPbXh74D3931F3hU9cKLWWg9L6Vckh5QPNgSu2ICEH6f0BxCiTUE", + "e": "AQAB", + "kid": "ljAwFsW32expyA0RjrKoOHuZxfk7KLSej8zldO9z4iU", + "kty": "RSA", + "n": "qNYHjNfiXl2SPu7NYSkd5RiF45bo4c_WW4K1NwH-iBwOOb970RvwlcyhctsvrtrUAJ046Z-3LgW27MR73NdcBP6z756XWIQ6CV6XlowG9NlgnEmOolh3XujZuNig-_05anzhTJr6Xl7uxh8o61VQgBjOgDma7cnEJNz2s89nu9f-WOMNage63O02ecA17TsZjU2jYcOCnV5UhsIyVRUcB3S2Jtk8FtRGBIydbkFHCX61atyh8GjzXYpneVPxTm1fRr-qUvGgNJqvK2HhuOzP6wRpcyKS6cl47I9Mu4L36pavtB_WOTXNhtduSYbUvMnkifAGuYTJHpT2e5QzSm2p7Q", + "p": "1djfJgLXpMaNuUaXd7V4bnkN38XC1Of8dubiGO5bFAym1qnExUQmJSq6Uubs3bRLSaa1i1EQqvIB_bBLm6we4m_jQbuNl8-m6dODaZQ2XVWmMWK84zDsRyJL2U-qDbSwt346AI3b3p-rtWlQLftx3qwj0-neAU4Jopfdz6R4HN0", + "q": "yh3QLx9gsrVF3crgejhJiEf4DLiGAglGAJpekeSllPhXWGF_0hw3YurhTm9swLumHhj0MvDNjVHz_fm-W5uLvkO0CW2hfoOtG3cj3AbnVu34v323EK27wTsp3ADMmykKk7Vc7rRtKUNuTawL-rL7ImvRhgiA_1La0NKkIk_yKFE", + "qi": "asGs7u5s2MXkwm0DHcyC9290wmlufSFOUrBgG-o8iahLFnYl9fCazTZJtBrSNQdfSPzr_htSSDav082q1Khaj1dCl_Jn84wuW2zTXgKhWbIvnCHM0GqgWGJ2HKHW3MKhGgitO5xlkv0nuv8znrkJtkQdxdw8x22eSNHUIPh_NtU" + }, + { + "crv": "P-256", + "d": "V9_Lc6jc0hyqFuqSHVaMzXcmWMj34Ib3pG5vls3EbaM", + "kid": "lMbSI69ajsBHHkIpVAFKRKYnR66kGdGteg2oqMzp0_E", + "kty": "EC", + "x": "4rnALl_X1zeOJtDmxz-YiUR1-9QGBfRE90qy_rqe0N0", + "y": "qGl3Telg02usgXK9jQTwcNQRLLovo07vffwaaZ3Dc5o" + }, + { + "crv": "P-384", + "d": "qLmbibr7wHPvUolsAC6I2rD_a78oCA3SvNiOh4C_WvZmtVJtIBDeQ3IGhz8q5lF0", + "kid": "J1EZJGP1LyhX2Zlz8xPcspMQEl8W3bYG2x3NqiMbPyY", + "kty": "EC", + "x": "dT7_3-Wp3kgewAiAyoarKQ2_rL4dketqaUti8nHOIT9K0dGtMGDt5W9uThc4mALN", + "y": "LHdC5G42RYTZcdnnEMftzcx3DOOaeTErrcdMpVTdu0gdjAQDrDwtHGu73E3AeHGp" + }, + { + "crv": "P-521", + "d": "Ae6RJFt67feYXDBIsWEv32WL7MsuiNJgO3A7WEyidaHN1-CP5nIjFN5urf8MvD8fBWqaxdwrAEIA6uPfc8f2U1J8", + "kid": "_xGyI3ms90AvgF658wz971wswSyYnGSG_A0dAgmrAM0", + "kty": "EC", + "x": "ARv-BW1gxTWyey_wil6Sc2iaPuu_iBsEKji2B8UBgW-vIp-JSHzTut1dR1UpkFnZe53EkS6P5kTNm5cBA-r9282J", + "y": "AeVtvHkJslwuPji-M71Zp3DzOostp_keWMB0f1zljl0P1CVhcpC5x4T_D3nEO_zRduL1R0Fv5gE6zaLm8X4cZD5m" + }, + { + "crv": "secp256k1", + "d": "_BuT_AckwrIi8AJpUTkqbxTMaViLXK5z0oIePWf9kkg", + "kid": "n9b4gyd5NgHv7pJ7S27AKBrhBPHa3H5ptcixmYerSUg", + "kty": "EC", + "x": "t-mBmz-Rvh0n3W_bRL_TSOc3Vv0ZB0oGaPZBEqu4sTQ", + "y": "PLm8asqUtHw5gVZN09vA5giJkDIPDOZ4zaG7NR77qSg" + }, + { + "crv": "Ed25519", + "d": "i60Oa0yMwEpc387GZ-_HDdw3C2qBzJHZrC1Bgf85L_k", + "kid": "iXkiyhEh6E7U-aX0fg7w-esHWqPvvxWd6gH1JG2u7N0", + "kty": "OKP", + "x": "nVo63sClAQ8qwBAZW0tttHFhXdrLiKqJnFeJ-j3nA3U" + }, + { + "crv": "Ed448", + "d": "3B2td0gjGaTP_qmLf8l456YZi8wQID4K9bkIAZcK_CJVcV2dfFS3bYGlTweXWOXX6S4Z7czHk-3f", + "kid": "Qlta2qVlhHhg3jriJp0HsIBQqGVB1ZH2pEnyPHzl1Ms", + "kty": "OKP", + "x": "KgaYHB-xIpPPvmH2PdbnWT-67_CfJhuD3U90sv-i5CZmGdwtWOErsowzNYSvuFWk8vztPOERjn4A" + } + ] +} \ No newline at end of file diff --git a/crates/jose/tests/keys/jwks.pub.json b/crates/jose/tests/keys/jwks.pub.json new file mode 100644 index 00000000..60a85e2c --- /dev/null +++ b/crates/jose/tests/keys/jwks.pub.json @@ -0,0 +1,50 @@ +{ + "keys": [ + { + "e": "AQAB", + "kid": "ljAwFsW32expyA0RjrKoOHuZxfk7KLSej8zldO9z4iU", + "kty": "RSA", + "n": "qNYHjNfiXl2SPu7NYSkd5RiF45bo4c_WW4K1NwH-iBwOOb970RvwlcyhctsvrtrUAJ046Z-3LgW27MR73NdcBP6z756XWIQ6CV6XlowG9NlgnEmOolh3XujZuNig-_05anzhTJr6Xl7uxh8o61VQgBjOgDma7cnEJNz2s89nu9f-WOMNage63O02ecA17TsZjU2jYcOCnV5UhsIyVRUcB3S2Jtk8FtRGBIydbkFHCX61atyh8GjzXYpneVPxTm1fRr-qUvGgNJqvK2HhuOzP6wRpcyKS6cl47I9Mu4L36pavtB_WOTXNhtduSYbUvMnkifAGuYTJHpT2e5QzSm2p7Q" + }, + { + "crv": "P-256", + "kid": "lMbSI69ajsBHHkIpVAFKRKYnR66kGdGteg2oqMzp0_E", + "kty": "EC", + "x": "4rnALl_X1zeOJtDmxz-YiUR1-9QGBfRE90qy_rqe0N0", + "y": "qGl3Telg02usgXK9jQTwcNQRLLovo07vffwaaZ3Dc5o" + }, + { + "crv": "P-384", + "kid": "J1EZJGP1LyhX2Zlz8xPcspMQEl8W3bYG2x3NqiMbPyY", + "kty": "EC", + "x": "dT7_3-Wp3kgewAiAyoarKQ2_rL4dketqaUti8nHOIT9K0dGtMGDt5W9uThc4mALN", + "y": "LHdC5G42RYTZcdnnEMftzcx3DOOaeTErrcdMpVTdu0gdjAQDrDwtHGu73E3AeHGp" + }, + { + "crv": "P-521", + "kid": "_xGyI3ms90AvgF658wz971wswSyYnGSG_A0dAgmrAM0", + "kty": "EC", + "x": "ARv-BW1gxTWyey_wil6Sc2iaPuu_iBsEKji2B8UBgW-vIp-JSHzTut1dR1UpkFnZe53EkS6P5kTNm5cBA-r9282J", + "y": "AeVtvHkJslwuPji-M71Zp3DzOostp_keWMB0f1zljl0P1CVhcpC5x4T_D3nEO_zRduL1R0Fv5gE6zaLm8X4cZD5m" + }, + { + "crv": "secp256k1", + "kid": "n9b4gyd5NgHv7pJ7S27AKBrhBPHa3H5ptcixmYerSUg", + "kty": "EC", + "x": "t-mBmz-Rvh0n3W_bRL_TSOc3Vv0ZB0oGaPZBEqu4sTQ", + "y": "PLm8asqUtHw5gVZN09vA5giJkDIPDOZ4zaG7NR77qSg" + }, + { + "crv": "Ed25519", + "kid": "iXkiyhEh6E7U-aX0fg7w-esHWqPvvxWd6gH1JG2u7N0", + "kty": "OKP", + "x": "nVo63sClAQ8qwBAZW0tttHFhXdrLiKqJnFeJ-j3nA3U" + }, + { + "crv": "Ed448", + "kid": "Qlta2qVlhHhg3jriJp0HsIBQqGVB1ZH2pEnyPHzl1Ms", + "kty": "OKP", + "x": "KgaYHB-xIpPPvmH2PdbnWT-67_CfJhuD3U90sv-i5CZmGdwtWOErsowzNYSvuFWk8vztPOERjn4A" + } + ] +} \ No newline at end of file diff --git a/crates/jose/tests/keys/k256.priv.pem b/crates/jose/tests/keys/k256.priv.pem new file mode 100644 index 00000000..ec6b8421 --- /dev/null +++ b/crates/jose/tests/keys/k256.priv.pem @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BgUrgQQACg== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHQCAQEEIPwbk/wHJMKyIvACaVE5Km8UzGlYi1yuc9KCHj1n/ZJIoAcGBSuBBAAK +oUQDQgAEt+mBmz+Rvh0n3W/bRL/TSOc3Vv0ZB0oGaPZBEqu4sTQ8ubxqypS0fDmB +Vk3T28DmCImQMg8M5njNobs1HvupKA== +-----END EC PRIVATE KEY----- diff --git a/crates/jose/tests/keys/k256.pub.pem b/crates/jose/tests/keys/k256.pub.pem new file mode 100644 index 00000000..9b6ee66e --- /dev/null +++ b/crates/jose/tests/keys/k256.pub.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEt+mBmz+Rvh0n3W/bRL/TSOc3Vv0ZB0oG +aPZBEqu4sTQ8ubxqypS0fDmBVk3T28DmCImQMg8M5njNobs1HvupKA== +-----END PUBLIC KEY----- diff --git a/crates/jose/tests/keys/oct.bin b/crates/jose/tests/keys/oct.bin new file mode 100644 index 00000000..2d4fd5fb --- /dev/null +++ b/crates/jose/tests/keys/oct.bin @@ -0,0 +1 @@ +23eaa437c2ace04e35a3a77b8132100f3438aa68946a30553cbf8fe80e2a5f3ca70082053f4bd4457653bbb15877d847c232948ea7512ccd679f71c268f1dd08 diff --git a/crates/jose/tests/keys/p256.priv.pem b/crates/jose/tests/keys/p256.priv.pem new file mode 100644 index 00000000..ab863128 --- /dev/null +++ b/crates/jose/tests/keys/p256.priv.pem @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BggqhkjOPQMBBw== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIFffy3Oo3NIcqhbqkh1WjM13JljI9+CG96Rub5bNxG2joAoGCCqGSM49 +AwEHoUQDQgAE4rnALl/X1zeOJtDmxz+YiUR1+9QGBfRE90qy/rqe0N2oaXdN6WDT +a6yBcr2NBPBw1BEsui+jTu99/BppncNzmg== +-----END EC PRIVATE KEY----- diff --git a/crates/jose/tests/keys/p256.pub.pem b/crates/jose/tests/keys/p256.pub.pem new file mode 100644 index 00000000..9d90ecb4 --- /dev/null +++ b/crates/jose/tests/keys/p256.pub.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4rnALl/X1zeOJtDmxz+YiUR1+9QG +BfRE90qy/rqe0N2oaXdN6WDTa6yBcr2NBPBw1BEsui+jTu99/BppncNzmg== +-----END PUBLIC KEY----- diff --git a/crates/jose/tests/keys/p384.priv.pem b/crates/jose/tests/keys/p384.priv.pem new file mode 100644 index 00000000..64102003 --- /dev/null +++ b/crates/jose/tests/keys/p384.priv.pem @@ -0,0 +1,9 @@ +-----BEGIN EC PARAMETERS----- +BgUrgQQAIg== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDCouZuJuvvAc+9SiWwALojasP9rvygIDdK82I6HgL9a9ma1Um0gEN5D +cgaHPyrmUXSgBwYFK4EEACKhZANiAAR1Pv/f5aneSB7ACIDKhqspDb+svh2R62pp +S2Lycc4hP0rR0a0wYO3lb25OFziYAs0sd0LkbjZFhNlx2ecQx+3NzHcM45p5MSut +x0ylVN27SB2MBAOsPC0ca7vcTcB4cak= +-----END EC PRIVATE KEY----- diff --git a/crates/jose/tests/keys/p384.pub.pem b/crates/jose/tests/keys/p384.pub.pem new file mode 100644 index 00000000..7257da82 --- /dev/null +++ b/crates/jose/tests/keys/p384.pub.pem @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEdT7/3+Wp3kgewAiAyoarKQ2/rL4dketq +aUti8nHOIT9K0dGtMGDt5W9uThc4mALNLHdC5G42RYTZcdnnEMftzcx3DOOaeTEr +rcdMpVTdu0gdjAQDrDwtHGu73E3AeHGp +-----END PUBLIC KEY----- diff --git a/crates/jose/tests/keys/p521.priv.pem b/crates/jose/tests/keys/p521.priv.pem new file mode 100644 index 00000000..0cba8282 --- /dev/null +++ b/crates/jose/tests/keys/p521.priv.pem @@ -0,0 +1,10 @@ +-----BEGIN EC PARAMETERS----- +BgUrgQQAIw== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MIHcAgEBBEIB7pEkW3rt95hcMEixYS/fZYvsyy6I0mA7cDtYTKJ1oc3X4I/mciMU +3m6t/wy8Px8FaprF3CsAQgDq499zx/ZTUnygBwYFK4EEACOhgYkDgYYABAEb/gVt +YMU1snsv8IpeknNomj7rv4gbBCo4tgfFAYFvryKfiUh807rdXUdVKZBZ2XudxJEu +j+ZEzZuXAQPq/dvNiQHlbbx5CbJcLj44vjO9Wadw8zqLLaf5HljAdH9c5Y5dD9Ql +YXKQuceE/w95xDv80Xbi9UdBb+YBOs2i5vF+HGQ+Zg== +-----END EC PRIVATE KEY----- diff --git a/crates/jose/tests/keys/p521.pub.pem b/crates/jose/tests/keys/p521.pub.pem new file mode 100644 index 00000000..560380cd --- /dev/null +++ b/crates/jose/tests/keys/p521.pub.pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBG/4FbWDFNbJ7L/CKXpJzaJo+67+I +GwQqOLYHxQGBb68in4lIfNO63V1HVSmQWdl7ncSRLo/mRM2blwED6v3bzYkB5W28 +eQmyXC4+OL4zvVmncPM6iy2n+R5YwHR/XOWOXQ/UJWFykLnHhP8PecQ7/NF24vVH +QW/mATrNoubxfhxkPmY= +-----END PUBLIC KEY----- diff --git a/crates/jose/tests/keys/rsa.priv.pem b/crates/jose/tests/keys/rsa.priv.pem new file mode 100644 index 00000000..5f34f888 --- /dev/null +++ b/crates/jose/tests/keys/rsa.priv.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAqNYHjNfiXl2SPu7NYSkd5RiF45bo4c/WW4K1NwH+iBwOOb97 +0RvwlcyhctsvrtrUAJ046Z+3LgW27MR73NdcBP6z756XWIQ6CV6XlowG9NlgnEmO +olh3XujZuNig+/05anzhTJr6Xl7uxh8o61VQgBjOgDma7cnEJNz2s89nu9f+WOMN +age63O02ecA17TsZjU2jYcOCnV5UhsIyVRUcB3S2Jtk8FtRGBIydbkFHCX61atyh +8GjzXYpneVPxTm1fRr+qUvGgNJqvK2HhuOzP6wRpcyKS6cl47I9Mu4L36pavtB/W +OTXNhtduSYbUvMnkifAGuYTJHpT2e5QzSm2p7QIDAQABAoIBAD71qVUdQD3+WMX9 +GTttMMe40FzhbrCeZ4oygM8lgDxTcf6Y6Fw/NgU2jNIe6HYlBj/AYXPFanLQELnU +P3hIGvmF3aWyly+cB2RtEMxEJWfzE/LT/0uM/961qFMk/Utaz6llLkL52/mLHzFk +d0bixqUuGvt3/81Cm1E/0QOWiVFjjEjEjqjAroeMHYSuxiSjkJVq/MlxekkxDse5 +2MVqSMUXXCSt1opwVz2eQfPMIHQ6XEdGL99rvn+4eEeRh5ozzwtjV7Fj6TTHvBug +mw0GqYJP9ZmFYqus6aODYJK7wVLDEYRZCP/1pXH89dg7PXQRsYNUfk+5SD2OsCGS +lCi2FUECgYEA1djfJgLXpMaNuUaXd7V4bnkN38XC1Of8dubiGO5bFAym1qnExUQm +JSq6Uubs3bRLSaa1i1EQqvIB/bBLm6we4m/jQbuNl8+m6dODaZQ2XVWmMWK84zDs +RyJL2U+qDbSwt346AI3b3p+rtWlQLftx3qwj0+neAU4Jopfdz6R4HN0CgYEAyh3Q +Lx9gsrVF3crgejhJiEf4DLiGAglGAJpekeSllPhXWGF/0hw3YurhTm9swLumHhj0 +MvDNjVHz/fm+W5uLvkO0CW2hfoOtG3cj3AbnVu34v323EK27wTsp3ADMmykKk7Vc +7rRtKUNuTawL+rL7ImvRhgiA/1La0NKkIk/yKFECgYA+XMh2pU2N1/lslpGFb353 +eQX6xAlVXXHamupEqv4Ukrv0zf18KauWGyfLFVzuKq3qBJvczP83WuwJt2fVNhSr +pEV53eAk7YGXnv8rwvU3ssqMjGbZOmMtO2szLHBDHvaKDF2N29DK03WXqeagAjvq +2/h2zDTZ3JAiwncFHsmIYQKBgQCtgZJqIYGdaEgl8jdqNE/Lkhy6rt07ekKB2gM9 +Mw+CJv60fF6CPH6/h+sKrlSpz2z3gWyBHn8Wxjey22YytgDswKoZPefY96jvXwOF +qteFpJxZps40nzAC3Ys9teHvgPf3fUXeFT1wotZaD0vpVySHlA82BK7YgIQfp/QH +EKJNQQKBgGrBrO7ubNjF5MJtAx3MgvdvdMJpbn0hTlKwYBvqPImoSxZ2JfXwms02 +SbQa0jUHX0j86/4bUkg2r9PNqtSoWo9XQpfyZ/OMLlts014CoVmyL5whzNBqoFhi +dhyh1tzCoRoIrTucZZL9J7r/M565CbZEHcXcPMdtnkjR1CD4fzbV +-----END RSA PRIVATE KEY----- diff --git a/crates/jose/tests/keys/rsa.pub.pem b/crates/jose/tests/keys/rsa.pub.pem new file mode 100644 index 00000000..c85b229d --- /dev/null +++ b/crates/jose/tests/keys/rsa.pub.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqNYHjNfiXl2SPu7NYSkd +5RiF45bo4c/WW4K1NwH+iBwOOb970RvwlcyhctsvrtrUAJ046Z+3LgW27MR73Ndc +BP6z756XWIQ6CV6XlowG9NlgnEmOolh3XujZuNig+/05anzhTJr6Xl7uxh8o61VQ +gBjOgDma7cnEJNz2s89nu9f+WOMNage63O02ecA17TsZjU2jYcOCnV5UhsIyVRUc +B3S2Jtk8FtRGBIydbkFHCX61atyh8GjzXYpneVPxTm1fRr+qUvGgNJqvK2HhuOzP +6wRpcyKS6cl47I9Mu4L36pavtB/WOTXNhtduSYbUvMnkifAGuYTJHpT2e5QzSm2p +7QIDAQAB +-----END PUBLIC KEY-----