diff --git a/Cargo.lock b/Cargo.lock index ccc02a1..2419ffd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -178,15 +178,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", + "base64ct", "crypto-bigint", "digest", "ff", "generic-array", "group", + "hkdf", "pem-rfc7468", "pkcs8", "rand_core", "sec1", + "serde_json", + "serdect", "subtle", "zeroize", ] @@ -234,6 +238,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" @@ -288,9 +301,12 @@ version = "1.0.1" dependencies = [ "base64", "chrono", + "ecdsa", "hmac", "p256", + "p384", "pkcs1", + "rand", "rsa", "serde", "serde_json", @@ -396,6 +412,18 @@ dependencies = [ "sha2", ] +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -468,6 +496,7 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", "rand_chacha", "rand_core", ] @@ -543,6 +572,7 @@ dependencies = [ "der", "generic-array", "pkcs8", + "serdect", "subtle", "zeroize", ] @@ -579,6 +609,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct", + "serde", +] + [[package]] name = "sha2" version = "0.10.8" diff --git a/Cargo.toml b/Cargo.toml index d614632..29e7c23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,12 +11,28 @@ thiserror = "2.0.11" hmac = { version = "0.12.1", optional = true } sha2 = { version = "0.10.8", features = ["oid"], optional = true } rsa = { version = "0.9.7", optional = true } -pkcs1 = "0.7.5" -p256 = { version = "0.13.2", features = ["pem"], optional = true } +pkcs1 = { version = "0.7.5", optional = true } +p256 = { version = "0.13.2", features = ["pem", "arithmetic", "jwk"], optional = true } +p384 = { version ="0.13.1", optional = true } chrono = "0.4.39" +ecdsa = "0.16.9" +rand = { version = "0.8.0", optional = true } [features] default = ["hs256"] +pkcs1 = ["dep:pkcs1"] +rand = ["dep:rand"] + +hmac = ["hs256", "hs384", "hs512"] hs256 = ["dep:hmac", "dep:sha2"] +hs384 = ["dep:hmac", "dep:sha2"] +hs512 = ["dep:hmac", "dep:sha2"] + +rsassa-pkcs1-v1_5 = ["rs256", "rs384", "rs512"] rs256 = ["dep:rsa", "dep:sha2"] +rs384 = ["dep:rsa", "dep:sha2"] +rs512 = ["dep:rsa", "dep:sha2"] + +ecdsa = ["es256", "es384"] es256 = ["dep:p256"] +es384 = ["dep:p384"] diff --git a/scripts/generate-jwks/.gitignore b/scripts/generate-jwks/.gitignore new file mode 100644 index 0000000..a14702c --- /dev/null +++ b/scripts/generate-jwks/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/scripts/generate-jwks/README.md b/scripts/generate-jwks/README.md new file mode 100644 index 0000000..7d9b94d --- /dev/null +++ b/scripts/generate-jwks/README.md @@ -0,0 +1,15 @@ +# generate-jwks + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.3.10. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime. diff --git a/scripts/generate-jwks/bun.lock b/scripts/generate-jwks/bun.lock new file mode 100644 index 0000000..5ef14f0 --- /dev/null +++ b/scripts/generate-jwks/bun.lock @@ -0,0 +1,26 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "generate-jwks", + "devDependencies": { + "@types/bun": "latest", + }, + "peerDependencies": { + "typescript": "^5", + }, + }, + }, + "packages": { + "@types/bun": ["@types/bun@1.3.10", "", { "dependencies": { "bun-types": "1.3.10" } }, "sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ=="], + + "@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], + + "bun-types": ["bun-types@1.3.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + } +} diff --git a/scripts/generate-jwks/index.ts b/scripts/generate-jwks/index.ts new file mode 100644 index 0000000..4afc261 --- /dev/null +++ b/scripts/generate-jwks/index.ts @@ -0,0 +1,31 @@ +const es256 = await crypto.subtle.generateKey( + { + name: "ECDSA", + namedCurve: "P-256", + }, + true, + ["sign", "verify"], +); + +const es256private = await crypto.subtle.exportKey("jwk", es256.privateKey); +const es256public = await crypto.subtle.exportKey("jwk", es256.publicKey); + +await Bun.write("es256-public.jwks.json", JSON.stringify(es256public, null, 2)); +await Bun.write("es256-private.jwks.json", JSON.stringify(es256private, null, 2)); + +const rs256 = await crypto.subtle.generateKey( + { + name: "RSASSA-PKCS1-v1_5", + modulusLength: 2048, + publicExponent: new Uint8Array([0x01, 0x00, 0x01]), + hash: "SHA-256", + }, + true, + ["sign", "verify"], +); + +const rs256private = await crypto.subtle.exportKey("jwk", rs256.privateKey); +const rs256public = await crypto.subtle.exportKey("jwk", rs256.publicKey); + +await Bun.write("rs256-public.jwks.json", JSON.stringify(rs256public, null, 2)); +await Bun.write("rs256-private.jwks.json", JSON.stringify(rs256private, null, 2)); diff --git a/scripts/generate-jwks/package.json b/scripts/generate-jwks/package.json new file mode 100644 index 0000000..5bf4393 --- /dev/null +++ b/scripts/generate-jwks/package.json @@ -0,0 +1,12 @@ +{ + "name": "generate-jwks", + "module": "index.ts", + "type": "module", + "private": true, + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5" + } +} diff --git a/scripts/generate-jwks/tsconfig.json b/scripts/generate-jwks/tsconfig.json new file mode 100644 index 0000000..bfa0fea --- /dev/null +++ b/scripts/generate-jwks/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "Preserve", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +} diff --git a/src/lib.rs b/src/lib.rs index d7956e8..2590169 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,18 @@ mod modules; pub use modules::token; -pub use modules::algorithm; \ No newline at end of file +pub use modules::algorithm; + +#[cfg(test)] +mod tests { + use crate::algorithm::es::ES256Private; + use crate::modules::key::JwkPrivateParams; + + #[test] + fn test_es256() { + let key = ES256Private::rand(); + let params = key.get_private_params(); + + dbg!(¶ms); + } +} \ No newline at end of file diff --git a/src/modules.rs b/src/modules.rs index 123cf88..806cf61 100644 --- a/src/modules.rs +++ b/src/modules.rs @@ -1,2 +1,3 @@ pub mod token; -pub mod algorithm; \ No newline at end of file +pub mod algorithm; +pub mod key; diff --git a/src/modules/algorithm/algorithms.rs b/src/modules/algorithm/algorithms.rs new file mode 100644 index 0000000..139597f --- /dev/null +++ b/src/modules/algorithm/algorithms.rs @@ -0,0 +1,2 @@ + + diff --git a/src/modules/algorithm/mod.rs b/src/modules/algorithm/mod.rs index 72bd57e..c640e45 100644 --- a/src/modules/algorithm/mod.rs +++ b/src/modules/algorithm/mod.rs @@ -1,15 +1,53 @@ mod models; mod traits; +mod algorithms; -pub use models::none_algorithm::NoneAlgorithm; +pub use traits::jw_alg_verify::JwAlgVerify; +pub use traits::jw_alg_sign::JwAlgSign; +pub use traits::jw_alg::JwAlg; +pub use traits::partial_jw_alg::PartialJwAlg; -#[cfg(feature = "hs256")] -pub use models::hs256_algorithm::HS256Algorithm; +pub use models::none_algorithm::NoneAlgorithm; -#[cfg(feature = "rs256")] -pub use models::rs256_algorithm::RS256Algorithm; +pub use models::*; -#[cfg(feature = "es256")] -pub use models::es256_algorithm::ES256Algorithm; +// #[cfg(any(feature = "es256", feature = "es384"))] +// pub use models::es; -pub use traits::jw_alg::JwAlg; \ No newline at end of file +// // HS +// #[cfg(any(feature = "hs256", feature = "hs384", feature = "hs512"))] +// use crate::algorithm::models::hs_algorithm::HSPrivate; +// +// #[cfg(feature = "hs256")] +// pub type HS256Private = HSPrivate; +// +// #[cfg(feature = "hs384")] +// pub type HS384Private = HSPrivate; +// +// #[cfg(feature = "hs512")] +// pub type HS512Private = HSPrivate; +// +// // RS +// #[cfg(any(feature = "rs256", feature = "rs384", feature = "rs512"))] +// use crate::algorithm::models::rs::rs_private::RSPrivate; +// +// #[cfg(any(feature = "rs256", feature = "rs384", feature = "rs512"))] +// use crate::algorithm::models::rs_algorithm::rs_public::RSPublic; +// +// #[cfg(feature = "rs256")] +// pub type RS256Private = RSPrivate; +// +// #[cfg(feature = "rs256")] +// pub type RS256Public = RSPublic; +// +// #[cfg(feature = "rs384")] +// pub type RS384Private = RSPrivate; +// +// #[cfg(feature = "rs384")] +// pub type RS384Public = RSPublic; +// +// #[cfg(feature = "rs512")] +// pub type RS512Private = RSPrivate; +// +// #[cfg(feature = "rs512")] +// pub type RS512Public = RSPublic; diff --git a/src/modules/algorithm/models.rs b/src/modules/algorithm/models.rs index 7d6e2d5..23988db 100644 --- a/src/modules/algorithm/models.rs +++ b/src/modules/algorithm/models.rs @@ -1,10 +1,11 @@ pub mod none_algorithm; -#[cfg(feature = "hs256")] -pub mod hs256_algorithm; +#[cfg(any(feature = "hs256", feature = "hs384", feature = "hs512"))] +pub mod hs; -#[cfg(feature = "rs256")] -pub mod rs256_algorithm; +#[cfg(any(feature = "rs256", feature = "rs384", feature = "rs512"))] +pub mod rs; -#[cfg(feature = "es256")] -pub mod es256_algorithm; \ No newline at end of file +#[cfg(any(feature = "es256", feature = "es384"))] +pub mod es; +pub mod any; \ No newline at end of file diff --git a/src/modules/algorithm/models/any/any_error.rs b/src/modules/algorithm/models/any/any_error.rs new file mode 100644 index 0000000..b498436 --- /dev/null +++ b/src/modules/algorithm/models/any/any_error.rs @@ -0,0 +1,8 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +#[error(transparent)] +pub enum AnyError { + #[cfg(any(feature = "es256", feature = "es384"))] + Ecdsa(#[from] ecdsa::Error), +} \ No newline at end of file diff --git a/src/modules/algorithm/models/any/any_private.rs b/src/modules/algorithm/models/any/any_private.rs new file mode 100644 index 0000000..d2d82fb --- /dev/null +++ b/src/modules/algorithm/models/any/any_private.rs @@ -0,0 +1,19 @@ +pub enum AnyPrivate { + #[cfg(feature = "es256")] + ES256Private(crate::algorithm::es::ES256Private), + + #[cfg(feature = "es384")] + ES384Private(crate::algorithm::es::es384::ES384Private), + + #[cfg(feature = "hs256")] + HS256Private(crate::algorithm::hs::HS256Private), + + #[cfg(feature = "rs256")] + RS256Private(crate::algorithm::rs::RS256Private), + + #[cfg(feature = "rs384")] + RS384Private(crate::algorithm::rs::RS384Private), + + #[cfg(feature = "rs512")] + RS512Private(crate::algorithm::rs::RS512Private), +} \ No newline at end of file diff --git a/src/modules/algorithm/models/any/any_public.rs b/src/modules/algorithm/models/any/any_public.rs new file mode 100644 index 0000000..80e940d --- /dev/null +++ b/src/modules/algorithm/models/any/any_public.rs @@ -0,0 +1,33 @@ +use crate::algorithm::any::any_error::AnyError; +use crate::algorithm::JwAlgVerify; + +pub enum AnyPublic { + #[cfg(feature = "es256")] + ES256Public(crate::algorithm::es::ES256Public), + + #[cfg(feature = "es384")] + ES384Public(crate::algorithm::es::es384::ES384Public), + + #[cfg(feature = "rs256")] + RS256Public(crate::algorithm::rs::RS256Public), + + #[cfg(feature = "rs384")] + RS384Public(crate::algorithm::rs::RS384Public), + + #[cfg(feature = "rs512")] + RS512Public(crate::algorithm::rs::RS512Public), +} + +impl JwAlgVerify for AnyPublic { + type Error = AnyError; + + fn verify(&self, payload: &str, signature: &[u8]) -> Result { + Ok(match self { + AnyPublic::ES256Public(inner) => inner.verify(payload, signature)?, + AnyPublic::ES384Public(inner) => inner.verify(payload, signature)?, + AnyPublic::RS256Public(inner) => inner.verify(payload, signature)?, + AnyPublic::RS384Public(inner) => inner.verify(payload, signature)?, + AnyPublic::RS512Public(inner) => inner.verify(payload, signature)?, + }) + } +} diff --git a/src/modules/algorithm/models/any/mod.rs b/src/modules/algorithm/models/any/mod.rs new file mode 100644 index 0000000..cb1c785 --- /dev/null +++ b/src/modules/algorithm/models/any/mod.rs @@ -0,0 +1,7 @@ +mod any_private; +mod any_public; +mod any_error; + +pub use any_private::*; +pub use any_public::*; +pub use any_error::*; \ No newline at end of file diff --git a/src/modules/algorithm/models/es/es256.rs b/src/modules/algorithm/models/es/es256.rs new file mode 100644 index 0000000..3ec8721 --- /dev/null +++ b/src/modules/algorithm/models/es/es256.rs @@ -0,0 +1,84 @@ +use base64::Engine; +use base64::prelude::{BASE64_URL_SAFE, BASE64_URL_SAFE_NO_PAD}; +use ecdsa::elliptic_curve::point::{AffineCoordinates, DecompressPoint}; +use ecdsa::EncodedPoint; +use crate::algorithm::{JwAlg, PartialJwAlg}; +use crate::algorithm::es::{ESPublic, ESPublicParams}; +use crate::algorithm::models::es::es_curve::ESCurve; +use crate::algorithm::models::es::es_private::ESPrivate; +use crate::algorithm::models::es::es_private_params::ESPrivateParams; +use crate::modules::key::{JwkPrivateParams, JwkPublicParams}; + +// Private +pub type ES256Private = ESPrivate; + +impl JwAlg for ES256Private { + fn alg() -> impl AsRef { + "ES256" + } +} + +impl PartialJwAlg for ES256Private { + fn partial_alg() -> Option> { + Some(Self::alg()) + } +} + +impl JwkPrivateParams<'_> for ES256Private { + type PrivateParams = ESPrivateParams; + + fn get_private_params(&self) -> Option { + let verifying_key = self.0.verifying_key(); + let affine_point = verifying_key.as_affine(); + let encoded_point: EncodedPoint = affine_point.clone().into(); // TODO remove clone + let x = encoded_point.x()?; + let y = encoded_point.y()?; + + let d_bytes = self.0.as_nonzero_scalar().to_bytes(); + + Some(ESPrivateParams { + crv: ESCurve::P256, + x: BASE64_URL_SAFE_NO_PAD.encode(&x), + y: BASE64_URL_SAFE_NO_PAD.encode(&y), + d: BASE64_URL_SAFE_NO_PAD.encode(&d_bytes), + }) + } +} + +// Public +pub type ES256Public = ESPublic; + +impl JwAlg for ES256Public { + fn alg() -> impl AsRef { + "ES256" + } +} + +impl PartialJwAlg for ES256Public { + fn partial_alg() -> Option> { + Some(Self::alg()) + } +} + +impl JwkPublicParams<'_> for ES256Public { + type PublicParams = ESPublicParams; + + fn get_public_params(&self) -> Option { + let affine_point = self.0.as_affine(); + let encoded_point: EncodedPoint = affine_point.clone().into(); // TODO remove clone + let x = encoded_point.x()?; + let y = encoded_point.y()?; + + Some(ESPublicParams { + crv: ESCurve::P256, + x: BASE64_URL_SAFE_NO_PAD.encode(&x), + y: BASE64_URL_SAFE_NO_PAD.encode(&y), + }) + } +} + +impl From for ES256Public { + fn from(value: ES256Private) -> Self { + Self(value.0.into()) + } +} diff --git a/src/modules/algorithm/models/es/es384.rs b/src/modules/algorithm/models/es/es384.rs new file mode 100644 index 0000000..ee8269e --- /dev/null +++ b/src/modules/algorithm/models/es/es384.rs @@ -0,0 +1,82 @@ +use base64::Engine; +use base64::prelude::{BASE64_URL_SAFE, BASE64_URL_SAFE_NO_PAD}; +use ecdsa::EncodedPoint; +use crate::algorithm::{JwAlg, PartialJwAlg}; +use crate::algorithm::es::{ES256Private, ES256Public, ESCurve, ESPrivateParams, ESPublicParams}; +use crate::algorithm::models::es::es_private::ESPrivate; +use crate::algorithm::models::es::es_public::ESPublic; +use crate::modules::key::{JwkPrivateParams, JwkPublicParams}; + +// Private +pub type ES384Private = ESPrivate; + +impl JwAlg for ES384Private { + fn alg() -> impl AsRef { + "ES384" + } +} + +impl PartialJwAlg for ESPrivate { + fn partial_alg() -> Option> { + Some(Self::alg()) + } +} + +impl JwkPrivateParams<'_> for ES384Private { + type PrivateParams = ESPrivateParams; + + fn get_private_params(&self) -> Option { + let verifying_key = self.0.verifying_key(); + let affine_point = verifying_key.as_affine(); + let encoded_point: EncodedPoint = affine_point.clone().into(); // TODO remove clone + let x = encoded_point.x()?; + let y = encoded_point.y()?; + + let d_bytes = self.0.as_nonzero_scalar().to_bytes(); + + Some(ESPrivateParams { + crv: ESCurve::P384, + x: BASE64_URL_SAFE.encode(&x), + y: BASE64_URL_SAFE.encode(&y), + d: BASE64_URL_SAFE.encode(&d_bytes), + }) + } +} + +// Public +pub type ES384Public = ESPublic; + +impl JwAlg for ES384Public { + fn alg() -> impl AsRef { + "ES384" + } +} + +impl PartialJwAlg for ES384Public { + fn partial_alg() -> Option> { + Some(Self::alg()) + } +} + +impl JwkPublicParams<'_> for ES384Public { + type PublicParams = ESPublicParams; + + fn get_public_params(&self) -> Option { + let affine_point = self.0.as_affine(); + let encoded_point: EncodedPoint = affine_point.clone().into(); // TODO remove clone + let x = encoded_point.x()?; + let y = encoded_point.y()?; + + Some(ESPublicParams { + crv: ESCurve::P384, + x: BASE64_URL_SAFE_NO_PAD.encode(&x), + y: BASE64_URL_SAFE_NO_PAD.encode(&y), + }) + } +} + +impl From for ES384Public { + fn from(value: ES384Private) -> Self { + Self(value.0.into()) + } +} diff --git a/src/modules/algorithm/models/es/es_curve.rs b/src/modules/algorithm/models/es/es_curve.rs new file mode 100644 index 0000000..dc809e9 --- /dev/null +++ b/src/modules/algorithm/models/es/es_curve.rs @@ -0,0 +1,13 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum ESCurve { + #[serde(rename = "P-256")] + P256, + + #[serde(rename = "P-384")] + P384, + + #[serde(rename = "P-521")] + P521, +} \ No newline at end of file diff --git a/src/modules/algorithm/models/es/es_private.rs b/src/modules/algorithm/models/es/es_private.rs new file mode 100644 index 0000000..a13ce76 --- /dev/null +++ b/src/modules/algorithm/models/es/es_private.rs @@ -0,0 +1,111 @@ +use std::convert::Infallible; +use ecdsa::elliptic_curve::{AffinePoint, CurveArithmetic, PrimeCurve, Scalar}; +use ecdsa::elliptic_curve::ops::Invert; +use ecdsa::elliptic_curve::subtle::CtOption; +use ecdsa::hazmat::{DigestPrimitive, SignPrimitive, VerifyPrimitive}; +use ecdsa::{Signature, SignatureSize, SigningKey}; +use ecdsa::elliptic_curve::generic_array::ArrayLength; +use ecdsa::signature::{Signer, Verifier}; +use crate::algorithm::{JwAlgSign, JwAlgVerify}; +use crate::algorithm::traits::jw_alg::JwAlg; +use ecdsa::elliptic_curve::FieldBytes; +use crate::algorithm::traits::partial_jw_alg::PartialJwAlg; +use crate::modules::key::{JwkPrivateParams, JwkType}; + +#[derive(Clone)] +pub struct ESPrivate(pub(crate) SigningKey) +where C: PrimeCurve + CurveArithmetic + DigestPrimitive, + Scalar: Invert>> + SignPrimitive, + AffinePoint: VerifyPrimitive, + SignatureSize: ArrayLength; + +impl ESPrivate +where C: PrimeCurve + CurveArithmetic + DigestPrimitive, + Scalar: Invert>> + SignPrimitive, + AffinePoint: VerifyPrimitive, + SignatureSize: ArrayLength, +{ + #[cfg(feature = "rand")] + pub fn rand() -> Self { + let mut rng = rand::thread_rng(); + ESPrivate::from(SigningKey::random(&mut rng)) + } + + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes().to_vec() + } + + pub fn from_bytes(bytes: &[u8]) -> Result { + let field_bytes = FieldBytes::::from_slice(bytes); + Ok(ESPrivate::from(SigningKey::from_bytes(field_bytes)?)) + } +} + +impl JwAlgVerify for SigningKey +where C: PrimeCurve + CurveArithmetic + DigestPrimitive, + Scalar: Invert>> + SignPrimitive, + AffinePoint: VerifyPrimitive, + SignatureSize: ArrayLength, +{ + type Error = Infallible; + + fn verify(&self, payload: &str, signature: &[u8]) -> Result { + let verifying_key = self.verifying_key(); + let signature = Signature::::try_from(signature).unwrap(); // TODO return false + + Ok(verifying_key.verify(payload.as_bytes(), &signature).is_ok()) + } +} + +impl JwAlgSign for SigningKey +where C: PrimeCurve + CurveArithmetic + DigestPrimitive, + Scalar: Invert>> + SignPrimitive, + AffinePoint: VerifyPrimitive, + SignatureSize: ArrayLength, +{ + fn sign(&self, payload: &str) -> Vec { + let signature: Signature = Signer::sign(self, payload.as_bytes()); + signature.to_vec() + } +} + +impl JwkType for ESPrivate +where C: PrimeCurve + CurveArithmetic + DigestPrimitive, + Scalar: Invert>> + SignPrimitive, + AffinePoint: VerifyPrimitive, + SignatureSize: ArrayLength, +{ + fn kty() -> impl AsRef { + "EC" + } +} + +impl From> for ESPrivate +where C: PrimeCurve + CurveArithmetic + DigestPrimitive, + Scalar: Invert>> + SignPrimitive, + AffinePoint: VerifyPrimitive, + SignatureSize: ArrayLength +{ + fn from(key: SigningKey) -> Self { + ESPrivate(key) + } +} + +#[cfg(test)] +mod tests { + use crate::algorithm::es::es256::ES256Private; + + #[test] + fn es256_can_be_generated_randomly() { + ES256Private::rand(); + } + + #[test] + fn es256_to_and_from_bytes() { + let alg = ES256Private::rand(); + let bytes = alg.to_bytes(); + + let alg2 = ES256Private::from_bytes(&bytes).unwrap(); + assert_eq!(alg.0, alg2.0); + } +} diff --git a/src/modules/algorithm/models/es/es_private_params.rs b/src/modules/algorithm/models/es/es_private_params.rs new file mode 100644 index 0000000..9d54b27 --- /dev/null +++ b/src/modules/algorithm/models/es/es_private_params.rs @@ -0,0 +1,10 @@ +use serde::{Deserialize, Serialize}; +use crate::algorithm::es::ESCurve; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ESPrivateParams { + pub crv: ESCurve, + pub x: String, + pub y: String, + pub d: String, +} \ No newline at end of file diff --git a/src/modules/algorithm/models/es/es_public.rs b/src/modules/algorithm/models/es/es_public.rs new file mode 100644 index 0000000..c705d64 --- /dev/null +++ b/src/modules/algorithm/models/es/es_public.rs @@ -0,0 +1,29 @@ +use ecdsa::{PrimeCurve, Signature, SignatureSize, VerifyingKey}; +use ecdsa::elliptic_curve::{AffinePoint, CurveArithmetic}; +use crate::algorithm::{JwAlg, JwAlgVerify}; +use ecdsa::elliptic_curve::generic_array::ArrayLength; +use ecdsa::hazmat::{DigestPrimitive, VerifyPrimitive}; +use ecdsa::signature::Verifier; +use crate::algorithm::traits::partial_jw_alg::PartialJwAlg; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ESPublic(pub(crate) VerifyingKey) +where + C: PrimeCurve + CurveArithmetic + DigestPrimitive, + AffinePoint: VerifyPrimitive, + SignatureSize: ArrayLength; + +impl JwAlgVerify for ESPublic +where + C: PrimeCurve + CurveArithmetic + DigestPrimitive, + AffinePoint: VerifyPrimitive, + SignatureSize: ArrayLength, +{ + type Error = ecdsa::Error; + + fn verify(&self, payload: &str, signature: &[u8]) -> Result { + let signature = Signature::::try_from(signature).unwrap(); // TODO return false + + Ok(self.0.verify(payload.as_bytes(), &signature).is_ok()) + } +} diff --git a/src/modules/algorithm/models/es/es_public_params.rs b/src/modules/algorithm/models/es/es_public_params.rs new file mode 100644 index 0000000..75f00b5 --- /dev/null +++ b/src/modules/algorithm/models/es/es_public_params.rs @@ -0,0 +1,9 @@ +use serde::{Deserialize, Serialize}; +use crate::algorithm::models::es::es_curve::ESCurve; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ESPublicParams { + pub crv: ESCurve, + pub x: String, + pub y: String, +} \ No newline at end of file diff --git a/src/modules/algorithm/models/es/mod.rs b/src/modules/algorithm/models/es/mod.rs new file mode 100644 index 0000000..8abc49b --- /dev/null +++ b/src/modules/algorithm/models/es/mod.rs @@ -0,0 +1,20 @@ +mod es_private; +mod es_public; +mod es_private_params; +mod es_curve; +mod es_public_params; + +pub use es_private::*; +pub use es_public::*; +pub use es_private_params::*; +pub use es_curve::*; +pub use es_public_params::*; + +#[cfg(feature = "es256")] +mod es256; + +#[cfg(feature = "es256")] +pub use es256::*; + +#[cfg(feature = "es384")] +pub mod es384; \ No newline at end of file diff --git a/src/modules/algorithm/models/es256_algorithm.rs b/src/modules/algorithm/models/es256_algorithm.rs deleted file mode 100644 index a2700e5..0000000 --- a/src/modules/algorithm/models/es256_algorithm.rs +++ /dev/null @@ -1,75 +0,0 @@ -use std::convert::Infallible; -use std::fmt::{Debug, Formatter}; -use p256::ecdsa::{SigningKey, Signature, signature::Signer}; -use p256::ecdsa::signature::Verifier; -use crate::algorithm::JwAlg; - -/// ```shell -/// openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out es256.pem -/// ``` -#[derive(Clone)] -pub struct ES256Algorithm { - inner: SigningKey, -} - -impl ES256Algorithm { - pub fn new(key: SigningKey) -> Self { - ES256Algorithm { - inner: key, - } - } -} - -impl JwAlg for ES256Algorithm { - type Error = Infallible; - - fn alg() -> impl AsRef { - "ES256" - } - - fn sign(&self, payload: &str) -> Vec { - let signature: Signature = self.inner.sign(payload.as_bytes()); - signature.to_vec() - } - - fn verify(&self, payload: &str, signature: &[u8]) -> Result { - let verifying_key = self.inner.verifying_key(); - let signature = Signature::try_from(signature).unwrap(); - - Ok(verifying_key.verify(payload.as_bytes(), &signature).is_ok()) - } -} - -impl Debug for ES256Algorithm { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "ES256Algorithm {{ .. }}") - } -} - -#[cfg(test)] -mod tests { - use base64::Engine; - use base64::prelude::BASE64_URL_SAFE_NO_PAD; - use p256::ecdsa::SigningKey; - use p256::SecretKey; - use crate::algorithm::JwAlg; - use crate::algorithm::models::es256_algorithm::ES256Algorithm; - - #[test] - fn es256_algorithm_works_as_expected() { - let payload = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0"; - let secret_key = include_str!("../../../../test-files/es256.key").parse::().unwrap(); - let signing_key = SigningKey::from(secret_key); - - let alg = ES256Algorithm::new(signing_key); - - let signature_bytes = alg.sign(payload); - let signature_string = BASE64_URL_SAFE_NO_PAD.encode(&signature_bytes); - - assert_eq!(signature_string, "XX7zPdDrYpegeS7mBfBIUVXnqVT-XSemrGjgoZBlrN0--n94Lv03J9vzbDDJXPzxnSs_62ymIJr1zBMaoMAveA"); - - let verify = alg.verify(payload, &signature_bytes).unwrap(); - - assert!(verify); - } -} \ No newline at end of file diff --git a/src/modules/algorithm/models/hs/hs256.rs b/src/modules/algorithm/models/hs/hs256.rs new file mode 100644 index 0000000..2d75008 --- /dev/null +++ b/src/modules/algorithm/models/hs/hs256.rs @@ -0,0 +1,17 @@ +use sha2::Sha256; +use crate::algorithm::{JwAlg, PartialJwAlg}; +use crate::algorithm::hs::HSPrivate; + +pub type HS256Private = HSPrivate; + +impl JwAlg for HS256Private { + fn alg() -> impl AsRef { + "HS256" + } +} + +impl PartialJwAlg for HS256Private { + fn partial_alg() -> Option> { + Some(Self::alg()) + } +} diff --git a/src/modules/algorithm/models/hs/mod.rs b/src/modules/algorithm/models/hs/mod.rs new file mode 100644 index 0000000..b957666 --- /dev/null +++ b/src/modules/algorithm/models/hs/mod.rs @@ -0,0 +1,129 @@ +use std::convert::Infallible; +use std::fmt::{Debug}; +use ecdsa::elliptic_curve::consts::U256; +use ecdsa::elliptic_curve::generic_array::typenum::{IsLess, Le, NonZero}; +use ecdsa::signature::digest::block_buffer::Eager; +use ecdsa::signature::digest::core_api::{BlockSizeUser, BufferKindUser, CoreProxy, FixedOutputCore, UpdateCore}; +use ecdsa::signature::digest::HashMarker; +use hmac::{Hmac, Mac}; +use crate::algorithm::{JwAlgVerify, JwAlgSign}; +use hmac::digest::InvalidLength; + +#[cfg(feature = "rand")] +use rand::RngCore; + +#[cfg(feature = "hs256")] +mod hs256; + +#[cfg(feature = "hs256")] +pub use hs256::HS256Private; + +#[derive(Clone)] +pub struct HSPrivate(Hmac) +where D: CoreProxy, + D::Core: HashMarker + + UpdateCore + + FixedOutputCore + + BufferKindUser + + Default + + Clone, + ::BlockSize: IsLess, + Le<::BlockSize, U256>: NonZero; + +impl HSPrivate +where D: CoreProxy, + D::Core: HashMarker + + UpdateCore + + FixedOutputCore + + BufferKindUser + + Default + + Clone, + ::BlockSize: IsLess, + Le<::BlockSize, U256>: NonZero, +{ + pub fn new(key: &[u8]) -> Result { + Ok(HSPrivate(Hmac::::new_from_slice(key)?)) + } + + #[cfg(feature = "rand")] + pub fn rand() -> Result { + let mut rng = rand::thread_rng(); + let mut slice = [0u8; 32]; + rng.fill_bytes(&mut slice); + + HSPrivate::new(&slice) + } +} + +impl JwAlgVerify for HSPrivate +where D: CoreProxy, + D::Core: HashMarker + + UpdateCore + + FixedOutputCore + + BufferKindUser + + Default + + Clone, + ::BlockSize: IsLess, + Le<::BlockSize, U256>: NonZero, +{ + type Error = Infallible; + + fn verify(&self, payload: &str, signature: &[u8]) -> Result { + let mut inner = self.0.clone(); + inner.update(payload.as_bytes()); + + let finalized = inner.finalize() + .into_bytes() + .to_vec(); + + Ok(signature == finalized) + } +} + +impl JwAlgSign for HSPrivate +where D: CoreProxy, + D::Core: HashMarker + + UpdateCore + + FixedOutputCore + + BufferKindUser + + Default + + Clone, + ::BlockSize: IsLess, + Le<::BlockSize, U256>: NonZero, +{ + fn sign(&self, payload: &str) -> Vec { + let mut inner = self.0.clone(); + inner.update(payload.as_bytes()); + + inner.finalize().into_bytes().to_vec() + } +} + +#[cfg(test)] +mod tests { + use base64::Engine; + use base64::prelude::BASE64_URL_SAFE_NO_PAD; + use crate::algorithm::hs::HS256Private; + use crate::algorithm::JwAlgSign; + use crate::modules::algorithm::{JwAlgVerify}; + + #[test] + fn hs256_algorithm_works_as_expected() { + let payload = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJoaiI6dHJ1ZX0"; + let alg = HS256Private::new("qwed".as_ref()).unwrap(); + + let signature_bytes = alg.sign(payload); + let signature_string = BASE64_URL_SAFE_NO_PAD.encode(&signature_bytes); + + assert_eq!(signature_string, "AeQU9YyCnBlrJwtd1PVmGW3apn6kQ6yi_U4qT9o0vkQ"); + + let verify = alg.verify(payload, &signature_bytes).unwrap(); + + assert!(verify); + } + + #[test] + fn hs256_can_be_generated_randomly() { + // HS256Private::rand().unwrap(); + } +} diff --git a/src/modules/algorithm/models/hs256_algorithm.rs b/src/modules/algorithm/models/hs256_algorithm.rs deleted file mode 100644 index ac393b6..0000000 --- a/src/modules/algorithm/models/hs256_algorithm.rs +++ /dev/null @@ -1,73 +0,0 @@ -use std::convert::Infallible; -use std::fmt::{Debug, Formatter}; -use hmac::{Hmac, Mac}; -use hmac::digest::InvalidLength; -use sha2::Sha256; -use crate::algorithm::JwAlg; - -#[derive(Clone)] -pub struct HS256Algorithm { - inner: Hmac, -} - -impl HS256Algorithm { - pub fn new(key: &[u8]) -> Result { - Ok(HS256Algorithm { - inner: Hmac::::new_from_slice(key)? - }) - } -} - -impl JwAlg for HS256Algorithm { - type Error = Infallible; - - fn alg() -> impl AsRef { - "HS256" - } - - fn sign(&self, payload: &str) -> Vec { - let mut inner = self.inner.clone(); - inner.update(payload.as_bytes()); - - inner.finalize().into_bytes().to_vec() - } - - fn verify(&self, payload: &str, signature: &[u8]) -> Result { - let mut inner = self.inner.clone(); - inner.update(payload.as_bytes()); - - let finalized = inner.finalize() - .into_bytes() - .to_vec(); - - Ok(signature == finalized) - } -} - -impl Debug for HS256Algorithm { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "HS256Algorithm {{ .. }}") - } -} - -#[cfg(test)] -mod tests { - use base64::Engine; - use base64::prelude::BASE64_URL_SAFE_NO_PAD; - use crate::modules::algorithm::{HS256Algorithm, JwAlg}; - - #[test] - fn hs256_algorithm_works_as_expected() { - let payload = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJoaiI6dHJ1ZX0"; - let alg = HS256Algorithm::new("qwed".as_ref()).unwrap(); - - let signature_bytes = alg.sign(payload); - let signature_string = BASE64_URL_SAFE_NO_PAD.encode(&signature_bytes); - - assert_eq!(signature_string, "AeQU9YyCnBlrJwtd1PVmGW3apn6kQ6yi_U4qT9o0vkQ"); - - let verify = alg.verify(payload, &signature_bytes).unwrap(); - - assert!(verify); - } -} \ No newline at end of file diff --git a/src/modules/algorithm/models/none_algorithm.rs b/src/modules/algorithm/models/none_algorithm.rs index 68aa95f..80e13e7 100644 --- a/src/modules/algorithm/models/none_algorithm.rs +++ b/src/modules/algorithm/models/none_algorithm.rs @@ -1,21 +1,19 @@ use std::convert::Infallible; -use crate::algorithm::JwAlg; +use crate::algorithm::{JwAlgVerify, JwAlgSign}; #[derive(Clone, Debug)] pub struct NoneAlgorithm; -impl JwAlg for NoneAlgorithm { +impl JwAlgVerify for NoneAlgorithm { type Error = Infallible; - fn alg() -> impl AsRef { - "none" + fn verify(&self, _: &str, _: &[u8]) -> Result { + Ok(true) } +} +impl JwAlgSign for NoneAlgorithm { fn sign(&self, _: &str) -> Vec { vec![] } - - fn verify(&self, _: &str, _: &[u8]) -> Result { - Ok(true) - } -} \ No newline at end of file +} diff --git a/src/modules/algorithm/models/rs/mod.rs b/src/modules/algorithm/models/rs/mod.rs new file mode 100644 index 0000000..d86dba3 --- /dev/null +++ b/src/modules/algorithm/models/rs/mod.rs @@ -0,0 +1,22 @@ +pub mod rs_public; +pub mod rs_private; + +#[cfg(feature = "rs256")] +mod rs256; + +#[cfg(feature = "rs256")] +pub use rs256::*; + +#[cfg(feature = "rs384")] +mod rs384; + +#[cfg(feature = "rs384")] +pub use rs384::*; + +#[cfg(feature = "rs512")] +pub mod rs512; +pub mod rs_private_params; +pub mod rs_public_params; + +#[cfg(feature = "rs512")] +pub use rs512::*; \ No newline at end of file diff --git a/src/modules/algorithm/models/rs/rs256.rs b/src/modules/algorithm/models/rs/rs256.rs new file mode 100644 index 0000000..e204b00 --- /dev/null +++ b/src/modules/algorithm/models/rs/rs256.rs @@ -0,0 +1,53 @@ +use base64::Engine; +use base64::prelude::BASE64_URL_SAFE_NO_PAD; +use rsa::traits::PublicKeyParts; +use crate::algorithm::{JwAlg, PartialJwAlg}; +use crate::algorithm::models::rs::rs_private::RSPrivate; +use crate::algorithm::models::rs::rs_public::RSPublic; +use crate::algorithm::rs::rs_public_params::RSPublicParams; +use crate::modules::key::JwkPublicParams; + +// Private +pub type RS256Private = RSPrivate; + +impl JwAlg for RS256Private { + fn alg() -> impl AsRef { + "RS256" + } +} + +impl PartialJwAlg for RS256Private { + fn partial_alg() -> Option> { + Some(Self::alg()) + } +} + +// Public +pub type RS256Public = RSPublic; + +impl JwAlg for RS256Public { + fn alg() -> impl AsRef { + "RS256" + } +} + +impl PartialJwAlg for RS256Public { + fn partial_alg() -> Option> { + Some(Self::alg()) + } +} + +impl JwkPublicParams<'_> for RS256Public { + type PublicParams = RSPublicParams; + + fn get_public_params(&self) -> Option { + let public_key = self.0.as_ref(); + let n = public_key.n().to_bytes_be(); + let e = public_key.e().to_bytes_be(); + + Some(RSPublicParams { + n: BASE64_URL_SAFE_NO_PAD.encode(&n), + e: BASE64_URL_SAFE_NO_PAD.encode(&e), + }) + } +} diff --git a/src/modules/algorithm/models/rs/rs384.rs b/src/modules/algorithm/models/rs/rs384.rs new file mode 100644 index 0000000..6b4a762 --- /dev/null +++ b/src/modules/algorithm/models/rs/rs384.rs @@ -0,0 +1,33 @@ +use crate::algorithm::{JwAlg, PartialJwAlg}; +use crate::algorithm::models::rs::rs_private::RSPrivate; +use crate::algorithm::models::rs::rs_public::RSPublic; + +// Private +pub type RS384Private = RSPrivate; + +impl JwAlg for RS384Private { + fn alg() -> impl AsRef { + "RS384" + } +} + +impl PartialJwAlg for RS384Private { + fn partial_alg() -> Option> { + Some(Self::alg()) + } +} + +// Public +pub type RS384Public = RSPublic; + +impl JwAlg for RS384Public { + fn alg() -> impl AsRef { + "RS384" + } +} + +impl PartialJwAlg for RS384Public { + fn partial_alg() -> Option> { + Some(Self::alg()) + } +} \ No newline at end of file diff --git a/src/modules/algorithm/models/rs/rs512.rs b/src/modules/algorithm/models/rs/rs512.rs new file mode 100644 index 0000000..3d63ea6 --- /dev/null +++ b/src/modules/algorithm/models/rs/rs512.rs @@ -0,0 +1,33 @@ +use crate::algorithm::{JwAlg, PartialJwAlg}; +use crate::algorithm::models::rs::rs_private::RSPrivate; +use crate::algorithm::models::rs::rs_public::RSPublic; + +// Private +pub type RS512Private = RSPrivate; + +impl JwAlg for RS512Private { + fn alg() -> impl AsRef { + "RS512" + } +} + +impl PartialJwAlg for RS512Private { + fn partial_alg() -> Option> { + Some(Self::alg()) + } +} + +// Public +pub type RS512Public = RSPublic; + +impl JwAlg for RS512Public { + fn alg() -> impl AsRef { + "RS512" + } +} + +impl PartialJwAlg for RS512Public { + fn partial_alg() -> Option> { + Some(Self::alg()) + } +} \ No newline at end of file diff --git a/src/modules/algorithm/models/rs/rs_private.rs b/src/modules/algorithm/models/rs/rs_private.rs new file mode 100644 index 0000000..32e4228 --- /dev/null +++ b/src/modules/algorithm/models/rs/rs_private.rs @@ -0,0 +1,105 @@ +use crate::algorithm::{JwAlg, JwAlgSign, JwAlgVerify}; +use ecdsa::elliptic_curve::pkcs8::AssociatedOid; +use rsa::pkcs1v15::{Signature, SigningKey}; +use rsa::signature::{Keypair, SignatureEncoding, Signer, Verifier}; +use rsa::RsaPrivateKey; +use serde::{Deserialize, Serialize}; +use sha2::Digest; + +#[cfg(feature = "pkcs1")] +use pkcs1::{DecodeRsaPrivateKey, EncodeRsaPrivateKey}; +use crate::algorithm::traits::partial_jw_alg::PartialJwAlg; + +#[derive(Clone)] +pub struct RSPrivate(pub(crate) SigningKey) +where D: Digest + AssociatedOid; + +impl RSPrivate +where D: Digest + AssociatedOid, +{ + #[cfg(feature = "rand")] + pub fn rand() -> Result { + Self::rand_size(4096) + } + + #[cfg(feature = "rand")] + pub fn rand_size(size: usize) -> Result { + let mut rng = rand::thread_rng(); + Ok(RSPrivate::from(SigningKey::random(&mut rng, size)?)) + } + + #[cfg(feature = "pkcs1")] + pub fn to_pkcs1_der_bytes(&self) -> Result, rsa::Error> { + Ok(self.0.to_pkcs1_der()? + .to_bytes() + .to_vec()) + } + + #[cfg(feature = "pkcs1")] + pub fn from_pkcs1_der_bytes(bytes: &[u8]) -> Result { + Ok(RSPrivate::from(SigningKey::from_pkcs1_der(bytes)?)) + } +} + +impl JwAlgVerify for RSPrivate +where D: Digest + AssociatedOid, +{ + type Error = rsa::signature::Error; + + fn verify(&self, payload: &str, signature: &[u8]) -> Result { + let signature = Signature::try_from(signature)?; + + Ok(self.0.verifying_key().verify(payload.as_bytes(), &signature).is_ok()) + } +} + +impl JwAlgSign for RSPrivate +where D: Digest + AssociatedOid, +{ + fn sign(&self, payload: &str) -> Vec { + self.0.sign(payload.as_bytes()).to_vec() + } +} + +impl From> for RSPrivate +where D: Digest + AssociatedOid, +{ + fn from(key: SigningKey) -> Self { + Self(key) + } +} + +impl From for RSPrivate +where D: Digest + AssociatedOid, +{ + fn from(key: RsaPrivateKey) -> Self { + Self::from(SigningKey::new(key)) + } +} + +#[cfg(all(test, feature = "pkcs1"))] +mod tests { + use crate::algorithm::{JwAlgSign, JwAlgVerify}; + use base64::prelude::BASE64_URL_SAFE_NO_PAD; + use base64::Engine; + use pkcs1::DecodeRsaPrivateKey; + pub use rsa::RsaPrivateKey; + use crate::algorithm::rs::RS256Private; + + #[test] + fn rs256_algorithm_works_as_expected() { + let payload = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJoaiI6dHJ1ZX0"; + + let private_key = RsaPrivateKey::from_pkcs1_pem(include_str!("../../../../../test-files/rs256.key")).unwrap(); + let alg = RS256Private::from(private_key); + + let signature_bytes = alg.sign(payload); + let signature_string = BASE64_URL_SAFE_NO_PAD.encode(&signature_bytes); + + assert_eq!(signature_string, "ptH8Vc-nhm4gTl7HqaictKQyK3fxiJmSfyu-ouYlmIfyyRBIYw2tUdKxIsxgYMPXC7oV0-ShYtlUm73-q2buLoYGc52d-03RQghcVvZrag2nQCKsBBmTXFUADEaVopO65aND5h7Uif_1aQJXmX-40-V5te0fT3WSyU_1oKayxpi53_c7RXD7gDlWSXAZFDNhPopcRnq2_4FQylzFf4qbwtGWUNdJA4SGOikr1lsTrQRPGXLNXREG0PWv9GFoobQDTj9DWBG4B_cCAUVAjYUCx8BbgHSY9jeiYE_FbDykW0tRSA3XAYpf1QCPZmrCPButUixWY03FTTxsQxlJuY8r-w"); + + let verify = alg.verify(payload, &signature_bytes).unwrap(); + + assert!(verify); + } +} diff --git a/src/modules/algorithm/models/rs/rs_private_params.rs b/src/modules/algorithm/models/rs/rs_private_params.rs new file mode 100644 index 0000000..ee21b65 --- /dev/null +++ b/src/modules/algorithm/models/rs/rs_private_params.rs @@ -0,0 +1,8 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RSPrivateParams { + pub n: String, + pub e: String, + pub d: String, +} \ No newline at end of file diff --git a/src/modules/algorithm/models/rs/rs_public.rs b/src/modules/algorithm/models/rs/rs_public.rs new file mode 100644 index 0000000..021378b --- /dev/null +++ b/src/modules/algorithm/models/rs/rs_public.rs @@ -0,0 +1,19 @@ +use p384::ecdsa::signature::digest::Digest; +use rsa::pkcs1v15::{Signature, VerifyingKey}; +use rsa::signature::Verifier; +use crate::algorithm::{JwAlgVerify}; + +#[derive(Clone)] +pub struct RSPublic(pub(crate) VerifyingKey) +where D : Digest; + +impl JwAlgVerify for RSPublic +where D : Digest +{ + type Error = rsa::signature::Error; + + fn verify(&self, payload: &str, signature: &[u8]) -> Result { + let signature = Signature::try_from(signature)?; + Ok(self.0.verify(payload.as_bytes(), &signature).is_ok()) + } +} diff --git a/src/modules/algorithm/models/rs/rs_public_params.rs b/src/modules/algorithm/models/rs/rs_public_params.rs new file mode 100644 index 0000000..d78626d --- /dev/null +++ b/src/modules/algorithm/models/rs/rs_public_params.rs @@ -0,0 +1,7 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RSPublicParams { + pub n: String, + pub e: String, +} \ No newline at end of file diff --git a/src/modules/algorithm/models/rs256_algorithm.rs b/src/modules/algorithm/models/rs256_algorithm.rs deleted file mode 100644 index ae604c5..0000000 --- a/src/modules/algorithm/models/rs256_algorithm.rs +++ /dev/null @@ -1,72 +0,0 @@ -use std::fmt::{Debug, Formatter}; -pub use rsa::pkcs1::DecodeRsaPrivateKey; -use rsa::pkcs1v15::{Signature, SigningKey}; -use rsa::signature::{Keypair, SignatureEncoding, Signer, Verifier}; -use serde::{Deserialize, Serialize}; -use sha2::Sha256; -use crate::algorithm::JwAlg; - -#[derive(Clone)] -pub struct RS256Algorithm { - inner: SigningKey -} - -impl RS256Algorithm { - pub fn new(key: SigningKey) -> Self { - RS256Algorithm { - inner: key, - } - } -} - -impl JwAlg for RS256Algorithm { - type Error = rsa::signature::Error; - - fn alg() -> impl AsRef { - "RS256" - } - - fn sign(&self, payload: &str) -> Vec { - self.inner.sign(payload.as_bytes()).to_vec() - } - - fn verify(&self, payload: &str, signature: &[u8]) -> Result { - let signature = Signature::try_from(signature)?; - - Ok(self.inner.verifying_key().verify(payload.as_bytes(), &signature).is_ok()) - } -} - -impl Debug for RS256Algorithm { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "RS256Algorithm {{ .. }}") - } -} - -#[cfg(test)] -mod tests { - use base64::Engine; - use base64::prelude::BASE64_URL_SAFE_NO_PAD; - use pkcs1::DecodeRsaPrivateKey; - use rsa::pkcs1v15::SigningKey; - pub use rsa::RsaPrivateKey; - use crate::algorithm::{JwAlg, RS256Algorithm}; - - #[test] - fn rs256_algorithm_works_as_expected() { - let payload = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJoaiI6dHJ1ZX0"; - - let private_key = RsaPrivateKey::from_pkcs1_pem(include_str!("../../../../test-files/rs256.key")).unwrap(); - let signing_key = SigningKey::new(private_key); - let alg = RS256Algorithm::new(signing_key); - - let signature_bytes = alg.sign(payload); - let signature_string = BASE64_URL_SAFE_NO_PAD.encode(&signature_bytes); - - assert_eq!(signature_string, "ptH8Vc-nhm4gTl7HqaictKQyK3fxiJmSfyu-ouYlmIfyyRBIYw2tUdKxIsxgYMPXC7oV0-ShYtlUm73-q2buLoYGc52d-03RQghcVvZrag2nQCKsBBmTXFUADEaVopO65aND5h7Uif_1aQJXmX-40-V5te0fT3WSyU_1oKayxpi53_c7RXD7gDlWSXAZFDNhPopcRnq2_4FQylzFf4qbwtGWUNdJA4SGOikr1lsTrQRPGXLNXREG0PWv9GFoobQDTj9DWBG4B_cCAUVAjYUCx8BbgHSY9jeiYE_FbDykW0tRSA3XAYpf1QCPZmrCPButUixWY03FTTxsQxlJuY8r-w"); - - let verify = alg.verify(payload, &signature_bytes).unwrap(); - - assert!(verify); - } -} \ No newline at end of file diff --git a/src/modules/algorithm/traits.rs b/src/modules/algorithm/traits.rs index de80376..37c62ae 100644 --- a/src/modules/algorithm/traits.rs +++ b/src/modules/algorithm/traits.rs @@ -1 +1,4 @@ -pub mod jw_alg; \ No newline at end of file +pub mod jw_alg_verify; +pub mod jw_alg_sign; +pub mod jw_alg; +pub mod partial_jw_alg; diff --git a/src/modules/algorithm/traits/jw_alg.rs b/src/modules/algorithm/traits/jw_alg.rs index 0ef5d4d..038ea9a 100644 --- a/src/modules/algorithm/traits/jw_alg.rs +++ b/src/modules/algorithm/traits/jw_alg.rs @@ -1,7 +1,5 @@ -pub trait JwAlg { - type Error: std::error::Error; +use crate::algorithm::traits::partial_jw_alg::PartialJwAlg; +pub trait JwAlg: PartialJwAlg { fn alg() -> impl AsRef; - fn sign(&self, payload: &str) -> Vec; - fn verify(&self, payload: &str, signature: &[u8]) -> Result; } \ No newline at end of file diff --git a/src/modules/algorithm/traits/jw_alg_sign.rs b/src/modules/algorithm/traits/jw_alg_sign.rs new file mode 100644 index 0000000..8bc573c --- /dev/null +++ b/src/modules/algorithm/traits/jw_alg_sign.rs @@ -0,0 +1,6 @@ +use crate::algorithm::JwAlgVerify; +use crate::algorithm::traits::jw_alg::JwAlg; + +pub trait JwAlgSign: JwAlgVerify { + fn sign(&self, payload: &str) -> Vec; +} \ No newline at end of file diff --git a/src/modules/algorithm/traits/jw_alg_verify.rs b/src/modules/algorithm/traits/jw_alg_verify.rs new file mode 100644 index 0000000..596c05d --- /dev/null +++ b/src/modules/algorithm/traits/jw_alg_verify.rs @@ -0,0 +1,7 @@ +use crate::algorithm::JwAlg; + +pub trait JwAlgVerify { + type Error: std::error::Error; + + fn verify(&self, payload: &str, signature: &[u8]) -> Result; +} diff --git a/src/modules/algorithm/traits/partial_jw_alg.rs b/src/modules/algorithm/traits/partial_jw_alg.rs new file mode 100644 index 0000000..36b1a05 --- /dev/null +++ b/src/modules/algorithm/traits/partial_jw_alg.rs @@ -0,0 +1,3 @@ +pub trait PartialJwAlg { + fn partial_alg() -> Option>; +} \ No newline at end of file diff --git a/src/modules/key/error.rs b/src/modules/key/error.rs new file mode 100644 index 0000000..b253c96 --- /dev/null +++ b/src/modules/key/error.rs @@ -0,0 +1,4 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum JwkError {} \ No newline at end of file diff --git a/src/modules/key/mod.rs b/src/modules/key/mod.rs new file mode 100644 index 0000000..83afd63 --- /dev/null +++ b/src/modules/key/mod.rs @@ -0,0 +1,27 @@ +mod models; +mod traits; +mod error; + +pub use traits::jwk_private_params::JwkPrivateParams; +pub use traits::jwk_public_params::JwkPublicParams; +pub use traits::jwk_type::JwkType; +pub use models::jwk::Jwk; +pub use error::JwkError; + +#[cfg(all(test, feature = "rs256"))] +mod tests { + use pkcs1::DecodeRsaPrivateKey; + use rsa::RsaPrivateKey; + use serde_json::json; + use crate::modules::key::models::jwk::Jwk; + + #[test] + fn jwk_is_created_correctly() { + // let ec_key = Jwk::try_from(json!({ + // "kty": "EC", + // "crv": "P-256", + // "x": "", + // "y": "", + // })); + } +} diff --git a/src/modules/key/models.rs b/src/modules/key/models.rs new file mode 100644 index 0000000..8bcff28 --- /dev/null +++ b/src/modules/key/models.rs @@ -0,0 +1,9 @@ +// pub mod jwk; +// pub mod rsa_private_jwk; +// mod jwk_sign; + +pub mod jwk; +pub mod jwt_use; +pub mod jwt_key_op; +pub mod jwt_set; +pub mod jwk_params; \ No newline at end of file diff --git a/src/modules/key/models/jwk.rs b/src/modules/key/models/jwk.rs new file mode 100644 index 0000000..93702c9 --- /dev/null +++ b/src/modules/key/models/jwk.rs @@ -0,0 +1,33 @@ +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use crate::algorithm::JwAlgVerify; +use crate::modules::key::models::jwk_params::JwkParams; +use crate::modules::key::models::jwt_key_op::JwtKeyOp; +use crate::modules::key::models::jwt_use::JwtUse; +use crate::token::JwtError; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Jwk { + pub kty: String, + + #[serde(rename = "use")] + pub usage: Option, + pub key_ops: Option>, + pub alg: Option, + pub kid: Option, + pub x5u: Option, + pub x5c: Option>, + pub x5t: Option, + + #[serde(rename = "x5t#S256")] + pub x5t_s256: Option, + + #[serde(flatten)] + pub params: JwkParams, +} + +impl Jwk { + // pub fn public_alg() -> Option> { + // + // } +} diff --git a/src/modules/key/models/jwk_params.rs b/src/modules/key/models/jwk_params.rs new file mode 100644 index 0000000..1328e21 --- /dev/null +++ b/src/modules/key/models/jwk_params.rs @@ -0,0 +1,9 @@ +use serde::{Deserialize, Serialize}; +use crate::algorithm::es::{ESPrivateParams, ESPublicParams}; + +#[derive(Debug, Serialize, Deserialize)] +#[serde(untagged)] +pub enum JwkParams { + ESPrivateParams(ESPrivateParams), + ESPublicParams(ESPublicParams), +} \ No newline at end of file diff --git a/src/modules/key/models/jwt_key_op.rs b/src/modules/key/models/jwt_key_op.rs new file mode 100644 index 0000000..6b29595 --- /dev/null +++ b/src/modules/key/models/jwt_key_op.rs @@ -0,0 +1,31 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub enum JwtKeyOp { + #[serde(rename = "sign")] + Sign, + + #[serde(rename = "verify")] + Verify, + + #[serde(rename = "encrypt")] + Encrypt, + + #[serde(rename = "decrypt")] + Decrypt, + + #[serde(rename = "wrapKey")] + WrapKey, + + #[serde(rename = "unwrapKey")] + UnwrapKey, + + #[serde(rename = "deriveKey")] + DeriveKey, + + #[serde(rename = "deriveBits")] + DeriveBits, + + #[serde(untagged)] + Other(String), +} \ No newline at end of file diff --git a/src/modules/key/models/jwt_set.rs b/src/modules/key/models/jwt_set.rs new file mode 100644 index 0000000..da51869 --- /dev/null +++ b/src/modules/key/models/jwt_set.rs @@ -0,0 +1,23 @@ +use serde::{Deserialize, Serialize}; +use crate::algorithm::{JwAlgVerify, PartialJwAlg}; +use crate::modules::key::error::JwkError; +use crate::modules::key::models::jwk::Jwk; + +#[derive(Debug, Serialize, Deserialize)] +pub struct JwtSet { + pub keys: Vec, +} + +impl PartialJwAlg for JwtSet { + fn partial_alg() -> Option> { + None::<&'static str> + } +} + +impl JwAlgVerify for JwtSet { + type Error = JwkError; + + fn verify(&self, payload: &str, signature: &[u8]) -> Result { + todo!() + } +} \ No newline at end of file diff --git a/src/modules/key/models/jwt_use.rs b/src/modules/key/models/jwt_use.rs new file mode 100644 index 0000000..cae4ac0 --- /dev/null +++ b/src/modules/key/models/jwt_use.rs @@ -0,0 +1,13 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub enum JwtUse { + #[serde(rename = "sig")] + Signature, + + #[serde(rename = "enc")] + Encryption, + + #[serde(untagged)] + Unknown(String), +} \ No newline at end of file diff --git a/src/modules/key/traits.rs b/src/modules/key/traits.rs new file mode 100644 index 0000000..5180b53 --- /dev/null +++ b/src/modules/key/traits.rs @@ -0,0 +1,3 @@ +pub mod jwk_public_params; +pub mod jwk_private_params; +pub mod jwk_type; diff --git a/src/modules/key/traits/jwk_private_params.rs b/src/modules/key/traits/jwk_private_params.rs new file mode 100644 index 0000000..d941a2c --- /dev/null +++ b/src/modules/key/traits/jwk_private_params.rs @@ -0,0 +1,7 @@ +use serde::{Deserialize, Serialize}; + +pub trait JwkPrivateParams<'a> { + type PrivateParams: Serialize + Deserialize<'a>; + + fn get_private_params(&self) -> Option; +} \ No newline at end of file diff --git a/src/modules/key/traits/jwk_public_params.rs b/src/modules/key/traits/jwk_public_params.rs new file mode 100644 index 0000000..06c8936 --- /dev/null +++ b/src/modules/key/traits/jwk_public_params.rs @@ -0,0 +1,7 @@ +use serde::{Deserialize, Serialize}; + +pub trait JwkPublicParams<'a> { + type PublicParams: Serialize + Deserialize<'a>; + + fn get_public_params(&self) -> Option; +} \ No newline at end of file diff --git a/src/modules/key/traits/jwk_type.rs b/src/modules/key/traits/jwk_type.rs new file mode 100644 index 0000000..e53aa7f --- /dev/null +++ b/src/modules/key/traits/jwk_type.rs @@ -0,0 +1,3 @@ +pub trait JwkType { + fn kty() -> impl AsRef; +} \ No newline at end of file diff --git a/src/modules/token/mod.rs b/src/modules/token/mod.rs index 3671d85..a6d0cd7 100644 --- a/src/modules/token/mod.rs +++ b/src/modules/token/mod.rs @@ -8,12 +8,13 @@ pub use error::JwtError; #[cfg(test)] mod tests { - use crate::algorithm::{HS256Algorithm, JwAlg}; + use crate::algorithm::{JwAlgVerify}; + use crate::algorithm::hs::HS256Private; use crate::token::Jwt; #[test] fn simple_jwt_token_can_be_generated() { - let algorithm = HS256Algorithm::new("something".as_bytes()) + let algorithm = HS256Private::new("something".as_bytes()) .unwrap(); let token = Jwt::new("hello world".to_string()) @@ -26,14 +27,14 @@ mod tests { #[test] fn incorrect_signature_key() { - let algorithm_1 = HS256Algorithm::new("something".as_bytes()) + let algorithm_1 = HS256Private::new("something".as_bytes()) .unwrap(); let token = Jwt::new("hello world".to_string()) .into_token(&algorithm_1) .unwrap(); - let algorithm_2 = HS256Algorithm::new("else".as_bytes()) + let algorithm_2 = HS256Private::new("else".as_bytes()) .unwrap(); let jwt = Jwt::::check(&token, &algorithm_2); diff --git a/src/modules/token/models/jwt.rs b/src/modules/token/models/jwt.rs index 742bdcc..83dbdee 100644 --- a/src/modules/token/models/jwt.rs +++ b/src/modules/token/models/jwt.rs @@ -5,7 +5,7 @@ use base64::prelude::BASE64_URL_SAFE_NO_PAD; use chrono::{DateTime, Duration, Utc}; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; -use crate::algorithm::JwAlg; +use crate::algorithm::{JwAlgVerify, JwAlgSign, JwAlg}; use crate::token::{JwtError, JwtHeader}; use crate::token::models::jwt_claims::JwtClaims; @@ -21,7 +21,7 @@ impl Jwt where T : Serialize + for<'a> Deserialize<'a>, { /// Takes the JWT instance, signs it, and returns the string representation for the token. - pub fn into_token(self, algorithm: &A) -> Result { + pub fn into_token(self, algorithm: &A) -> Result { let alg_ref = A::alg(); let header = JwtHeader { @@ -61,10 +61,10 @@ where T : Serialize + for<'a> Deserialize<'a>, /// Decodes and verifies the given string token with the given algorithm. Returns a JWT token /// instance with the expected payload. Note that this does not check any claims. To verify - /// basic expiry claims you can use [Jwt::verify_now] or you can further verify the token using + /// basic expiry claims, you can use [Jwt::verify_now] or you can further verify the token using /// [Jwt::against] or [Jwt::guard]. - pub fn check(token: &str, algorithm: &A) -> Result, JwtError> - where ::Error: 'static + pub fn check(token: &str, algorithm: &A) -> Result, JwtError> + where ::Error: 'static { let mut parts = token.split('.'); @@ -72,7 +72,9 @@ where T : Serialize + for<'a> Deserialize<'a>, let header_bytes = BASE64_URL_SAFE_NO_PAD.decode(header_string.as_bytes())?; let header: JwtHeader = serde_json::from_slice(&header_bytes)?; - if header.alg != Cow::Borrowed(A::alg().as_ref()) { + if let Some(alg_ref) = A::partial_alg() + && header.alg != Cow::Borrowed(alg_ref.as_ref()) + { return Err(JwtError::AlgMismatch); } @@ -103,8 +105,8 @@ where T : Serialize + for<'a> Deserialize<'a>, /// Largely the same as [Jwt::check], but also verifies basic expiry claims. You can further /// verify the token using [Jwt::against] or [Jwt::guard]. - pub fn verify_now(token: &str, algorithm: &A) -> Result, JwtError> - where ::Error: 'static + pub fn verify_now(token: &str, algorithm: &A) -> Result, JwtError> + where ::Error: 'static { let jwt = Jwt::::check(token, algorithm)? .against(&JwtClaims::now())?; diff --git a/test-files/es256-private.jwks.json b/test-files/es256-private.jwks.json new file mode 100644 index 0000000..fb2bbcf --- /dev/null +++ b/test-files/es256-private.jwks.json @@ -0,0 +1,11 @@ +{ + "crv": "P-256", + "d": "3XAy6ko-fegKfkV5uE5nKY4wTncjvFMYTJM0uh4rj8E", + "ext": true, + "key_ops": [ + "sign" + ], + "kty": "EC", + "x": "Q5PISQVZ7LMwDNrjCNUcPLR45-zArUIqVazbscbLZ5Y", + "y": "cum17-MtTGX2X7yrI0tvp0Xl9NVEmHUuotAwyJHOv7I" +} \ No newline at end of file diff --git a/test-files/es256-public.jwks.json b/test-files/es256-public.jwks.json new file mode 100644 index 0000000..b78824b --- /dev/null +++ b/test-files/es256-public.jwks.json @@ -0,0 +1,10 @@ +{ + "crv": "P-256", + "ext": true, + "key_ops": [ + "verify" + ], + "kty": "EC", + "x": "Q5PISQVZ7LMwDNrjCNUcPLR45-zArUIqVazbscbLZ5Y", + "y": "cum17-MtTGX2X7yrI0tvp0Xl9NVEmHUuotAwyJHOv7I" +} \ No newline at end of file diff --git a/test-files/rs256-private.jwks.json b/test-files/rs256-private.jwks.json new file mode 100644 index 0000000..3819cd1 --- /dev/null +++ b/test-files/rs256-private.jwks.json @@ -0,0 +1,16 @@ +{ + "alg": "RS256", + "d": "AWwlG9V-uOv5_VzUvnELzpaqnfmHcknukEUUDJIOv8GLESbRLLdyIP2N3zHlm0Ml2qumifJ6Z_NSxk-R0NskCIsKYmgTcMDmla_boF3eYcOq0ssByCND31mOG31FdqXZFYg2QDgwPK-ZL9ztYP3AJmJS9MYNVY1LckuPyEP6fERL63CYqIH7TxYfgHp5h3kwr0EsdF7aOCc41XLrUbXAptTQT82Ny3KQbG7B0FQo-ot2HSMkrDxsCFj8Cqrlqvre3xC9bJeZkuJR83Z093gM8-DrHJ5SzzkiczHHpZF17SnlyEsGsM3J1OX9tG1qPpmbBbgcaUlNPoWX4wu1LOMwQQ", + "dp": "B0bhmelCm1rdRTims3yDdbwctdnxyQYrfxuHDwk5ISZEriFmQRwFu-mk7MD58_BgMca86pcfU_4o-9ntk06pbeQhwQtTjiLpk2KfRlhw4s-EWh_V2YddPxcZIUK7v03_uiZqWwKlVdhNxV44DS6JnSx0Gq_jHXxVe_r0Vyz9458", + "dq": "prODOLzzxjRu2EgEfe4maUxYhLvPlCIhjUZeu1bX19AG9TLMPmmi08JS1iOXPtP6cyOc_jiV5hrP5t1qWl71j0zVoCVoI91XlBFpYZvcpWPcvL_Uvy2ACxn8Nrs0qfbLuN0WhE7cFFBl1ZiEXRPloJxxVjp9IJ_YaAro9Gfyd_U", + "e": "AQAB", + "ext": true, + "key_ops": [ + "sign" + ], + "kty": "RSA", + "n": "3t46Cq-6fA5fPu20XRLczcEWbNq_43dtw0cwkm0MTR5nxQAGXAIXWVYJM543nda3Ug66rslPIC8h39xnx5R6j4VJ-oLhCECk5_cC722_bPhQ5MrX9RmJS12Rxp1Cv5Qevwtoe4wrjTBb_4uaATdBezgTu18ETdlxD3r58unm-ERub9aNZIUqmZzYZfDFqf5arMUdEX5nVn-fYi_TYVeoPlPL7xFMpeWtII1xKRVQHkHhmg2ZTbTSRRd5sBxi_k79dbochxXGPb49bwurDwiRd4k15g6LoHvCiiU-dJ01jxy2C58RddZz_46UAaZo4P1hE2_7vw7j2VQ2vvZmZyShwQ", + "p": "_P_7wwC_2IsnQqvsdlGdKi2xOc7JrBBeJ0CIKDzda1fYZd4ZmejBYhsBATV4Cy5UOOURbZpnXcZHKlxFs7UiKcUCvXbqD9E-GurEguGcNuvsH4h8CweTJJRVVjhcoH3mN23wKw04bWm-OU9ek9cX_7zP8VTakT2nOTw0-7MBTjc", + "q": "4YLGGMCU0hosTonxmEuGKe3aXF7CL0rYHz1vPf4NE5_9UnQyhg91KKm44Gx1_8jW35jv3yPxqYhVUUn_DDao5WQDqXBrFiCsvQ3SFdy2lmRZKKjvLioUW6kU0CdKVY8442U7aakGB70XgRXKvkwWDGsXCvVle_vc09oV6lbjU8c", + "qi": "QsA_u5DtRHg_fZDMkU9-LoE1m5_p3ea13_VrAaXbLx97KGdtNT1X3GO62KfgV7uesS4F__goK9IsW6FQelDdz2PqvsB8pQVv1zVDCB-0__FKoueiYcJy3ECUPEEUxEE_kvknAiZM1Lyp240BWWCSKzw1dKmMtlO4sQeW7H9tzUE" +} \ No newline at end of file diff --git a/test-files/rs256-public.jwks.json b/test-files/rs256-public.jwks.json new file mode 100644 index 0000000..7ff20b9 --- /dev/null +++ b/test-files/rs256-public.jwks.json @@ -0,0 +1,10 @@ +{ + "alg": "RS256", + "e": "AQAB", + "ext": true, + "key_ops": [ + "verify" + ], + "kty": "RSA", + "n": "3t46Cq-6fA5fPu20XRLczcEWbNq_43dtw0cwkm0MTR5nxQAGXAIXWVYJM543nda3Ug66rslPIC8h39xnx5R6j4VJ-oLhCECk5_cC722_bPhQ5MrX9RmJS12Rxp1Cv5Qevwtoe4wrjTBb_4uaATdBezgTu18ETdlxD3r58unm-ERub9aNZIUqmZzYZfDFqf5arMUdEX5nVn-fYi_TYVeoPlPL7xFMpeWtII1xKRVQHkHhmg2ZTbTSRRd5sBxi_k79dbochxXGPb49bwurDwiRd4k15g6LoHvCiiU-dJ01jxy2C58RddZz_46UAaZo4P1hE2_7vw7j2VQ2vvZmZyShwQ" +} \ No newline at end of file