You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-07-29 22:01:14 +03:00
WIP: better JOSE
This commit is contained in:
15
Cargo.lock
generated
15
Cargo.lock
generated
@ -152,6 +152,16 @@ dependencies = [
|
|||||||
"tokio",
|
"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]]
|
[[package]]
|
||||||
name = "async-stream"
|
name = "async-stream"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
@ -2545,6 +2555,7 @@ name = "mas-jose"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"async-signature",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"base64ct",
|
"base64ct",
|
||||||
"chrono",
|
"chrono",
|
||||||
@ -3725,8 +3736,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "rsa"
|
name = "rsa"
|
||||||
version = "0.7.0-pre"
|
version = "0.7.0-pre"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/RustCrypto/RSA.git#40242fbbb019cac4af216145bfa468a0d24d12ef"
|
||||||
checksum = "6168b9a0f38e487db90dc109ad6d8f37fc5590183b7bfe8d8687e0b86116d53f"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"digest 0.10.3",
|
"digest 0.10.3",
|
||||||
@ -3737,6 +3747,7 @@ dependencies = [
|
|||||||
"pkcs1",
|
"pkcs1",
|
||||||
"pkcs8",
|
"pkcs8",
|
||||||
"rand_core",
|
"rand_core",
|
||||||
|
"signature",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"subtle",
|
"subtle",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
|
@ -31,8 +31,8 @@ use mas_data_model::{Client, JwksOrJwksUri, StorageBackend};
|
|||||||
use mas_http::HttpServiceExt;
|
use mas_http::HttpServiceExt;
|
||||||
use mas_iana::oauth::OAuthClientAuthenticationMethod;
|
use mas_iana::oauth::OAuthClientAuthenticationMethod;
|
||||||
use mas_jose::{
|
use mas_jose::{
|
||||||
DecodedJsonWebToken, DynamicJwksStore, Either, JsonWebKeySet, JsonWebTokenParts, JwtHeader,
|
DecodedJsonWebToken, DynamicJwksStore, Either, JsonWebKeySet, JsonWebSignatureHeader,
|
||||||
SharedSecret, StaticJwksStore, VerifyingKeystore,
|
JsonWebTokenParts, SharedSecret, StaticJwksStore, VerifyingKeystore,
|
||||||
};
|
};
|
||||||
use mas_storage::{
|
use mas_storage::{
|
||||||
oauth2::client::{lookup_client_by_client_id, ClientFetchError},
|
oauth2::client::{lookup_client_by_client_id, ClientFetchError},
|
||||||
@ -73,7 +73,7 @@ pub enum Credentials {
|
|||||||
ClientAssertionJwtBearer {
|
ClientAssertionJwtBearer {
|
||||||
client_id: String,
|
client_id: String,
|
||||||
jwt: JsonWebTokenParts,
|
jwt: JsonWebTokenParts,
|
||||||
header: Box<JwtHeader>,
|
header: Box<JsonWebSignatureHeader>,
|
||||||
claims: HashMap<String, Value>,
|
claims: HashMap<String, Value>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ sqlx = { version = "0.6.1", features = ["runtime-tokio-rustls", "postgres"] }
|
|||||||
lettre = { version = "0.10.1", default-features = false, features = ["serde", "builder"] }
|
lettre = { version = "0.10.1", default-features = false, features = ["serde", "builder"] }
|
||||||
|
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
rsa = "0.7.0-pre"
|
rsa = { git = "https://github.com/RustCrypto/RSA.git" }
|
||||||
p256 = { version = "0.11.1", features = ["ecdsa", "pem", "pkcs8"] }
|
p256 = { version = "0.11.1", features = ["ecdsa", "pem", "pkcs8"] }
|
||||||
pkcs8 = { version = "0.9.0", features = ["pem"] }
|
pkcs8 = { version = "0.9.0", features = ["pem"] }
|
||||||
chacha20poly1305 = { version = "0.10.1", features = ["std"] }
|
chacha20poly1305 = { version = "0.10.1", features = ["std"] }
|
||||||
|
@ -40,7 +40,7 @@ serde_urlencoded = "0.7.1"
|
|||||||
argon2 = { version = "0.4.1", features = ["password-hash"] }
|
argon2 = { version = "0.4.1", features = ["password-hash"] }
|
||||||
|
|
||||||
# Crypto, hashing and signing stuff
|
# Crypto, hashing and signing stuff
|
||||||
rsa = "0.7.0-pre"
|
rsa = { git = "https://github.com/RustCrypto/RSA.git" }
|
||||||
pkcs8 = { version = "0.9.0", features = ["pem"] }
|
pkcs8 = { version = "0.9.0", features = ["pem"] }
|
||||||
elliptic-curve = { version = "0.12.3", features = ["pem"] }
|
elliptic-curve = { version = "0.12.3", features = ["pem"] }
|
||||||
sha2 = "0.10.2"
|
sha2 = "0.10.2"
|
||||||
|
@ -26,7 +26,7 @@ use mas_data_model::{AuthorizationGrantStage, Client, TokenType};
|
|||||||
use mas_iana::jose::JsonWebSignatureAlg;
|
use mas_iana::jose::JsonWebSignatureAlg;
|
||||||
use mas_jose::{
|
use mas_jose::{
|
||||||
claims::{self, ClaimError},
|
claims::{self, ClaimError},
|
||||||
DecodedJsonWebToken, SigningKeystore, StaticKeystore,
|
DecodedJsonWebToken, JwtSignatureError, SigningKeystore, StaticKeystore,
|
||||||
};
|
};
|
||||||
use mas_router::UrlBuilder;
|
use mas_router::UrlBuilder;
|
||||||
use mas_storage::{
|
use mas_storage::{
|
||||||
@ -173,6 +173,12 @@ impl From<ClaimError> for RouteError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<JwtSignatureError> for RouteError {
|
||||||
|
fn from(e: JwtSignatureError) -> Self {
|
||||||
|
Self::Internal(Box::new(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, err)]
|
#[tracing::instrument(skip_all, err)]
|
||||||
pub(crate) async fn post(
|
pub(crate) async fn post(
|
||||||
client_authorization: ClientAuthorization<AccessTokenRequest>,
|
client_authorization: ClientAuthorization<AccessTokenRequest>,
|
||||||
|
@ -6,6 +6,7 @@ edition = "2021"
|
|||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
async-signature = "0.2.0"
|
||||||
anyhow = "1.0.62"
|
anyhow = "1.0.62"
|
||||||
async-trait = "0.1.57"
|
async-trait = "0.1.57"
|
||||||
base64ct = { version = "1.5.1", features = ["std"] }
|
base64ct = { version = "1.5.1", features = ["std"] }
|
||||||
@ -21,7 +22,7 @@ p256 = { version = "0.11.1", features = ["ecdsa", "pem", "pkcs8"] }
|
|||||||
pkcs1 = { version = "0.4.0", features = ["pem", "pkcs8"] }
|
pkcs1 = { version = "0.4.0", features = ["pem", "pkcs8"] }
|
||||||
pkcs8 = { version = "0.9.0", features = ["pem", "std"] }
|
pkcs8 = { version = "0.9.0", features = ["pem", "std"] }
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
rsa = "0.7.0-pre"
|
rsa = { git = "https://github.com/RustCrypto/RSA.git" }
|
||||||
schemars = "0.8.10"
|
schemars = "0.8.10"
|
||||||
sec1 = "0.3.0"
|
sec1 = "0.3.0"
|
||||||
serde = { version = "1.0.144", features = ["derive"] }
|
serde = { version = "1.0.144", features = ["derive"] }
|
||||||
|
143
crates/jose/src/jwt/header.rs
Normal file
143
crates/jose/src/jwt/header.rs
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
// 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::JsonWebSignatureAlg;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_with::{
|
||||||
|
base64::{Base64, Standard, UrlSafe},
|
||||||
|
formats::{Padded, Unpadded},
|
||||||
|
serde_as, skip_serializing_none,
|
||||||
|
};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use crate::jwk::JsonWebKey;
|
||||||
|
|
||||||
|
#[serde_as]
|
||||||
|
#[skip_serializing_none]
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||||
|
pub struct JsonWebSignatureHeader {
|
||||||
|
alg: JsonWebSignatureAlg,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
jku: Option<Url>,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
jwk: Option<JsonWebKey>,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
kid: Option<String>,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
x5u: Option<Url>,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
#[serde_as(as = "Option<Vec<Base64<Standard, Padded>>>")]
|
||||||
|
x5c: Option<Vec<Vec<u8>>>,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
#[serde_as(as = "Option<Base64<UrlSafe, Unpadded>>")]
|
||||||
|
x5t: Option<Vec<u8>>,
|
||||||
|
|
||||||
|
#[serde(default, rename = "x5t#S256")]
|
||||||
|
#[serde_as(as = "Option<Base64<UrlSafe, Unpadded>>")]
|
||||||
|
x5t_s256: Option<Vec<u8>>,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
typ: Option<String>,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
cty: Option<String>,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
crit: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JsonWebSignatureHeader {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(alg: JsonWebSignatureAlg) -> Self {
|
||||||
|
Self {
|
||||||
|
alg,
|
||||||
|
jku: None,
|
||||||
|
jwk: None,
|
||||||
|
kid: None,
|
||||||
|
x5u: None,
|
||||||
|
x5c: None,
|
||||||
|
x5t: None,
|
||||||
|
x5t_s256: None,
|
||||||
|
typ: None,
|
||||||
|
cty: None,
|
||||||
|
crit: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn alg(&self) -> JsonWebSignatureAlg {
|
||||||
|
self.alg
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn jku(&self) -> Option<&Url> {
|
||||||
|
self.jku.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_jku(mut self, jku: Url) -> Self {
|
||||||
|
self.jku = Some(jku);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn jwk(&self) -> Option<&JsonWebKey> {
|
||||||
|
self.jwk.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_jwk(mut self, jwk: JsonWebKey) -> Self {
|
||||||
|
self.jwk = Some(jwk);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn kid(&self) -> Option<&str> {
|
||||||
|
self.kid.as_deref()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_kid(mut self, kid: impl Into<String>) -> Self {
|
||||||
|
self.kid = Some(kid.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn typ(&self) -> Option<&str> {
|
||||||
|
self.typ.as_deref()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_typ(mut self, typ: String) -> Self {
|
||||||
|
self.typ = Some(typ);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn crit(&self) -> Option<&[String]> {
|
||||||
|
self.crit.as_deref()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_crit(mut self, crit: Vec<String>) -> Self {
|
||||||
|
self.crit = Some(crit);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
@ -15,116 +15,16 @@
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use base64ct::{Base64UrlUnpadded, Encoding};
|
use base64ct::{Base64UrlUnpadded, Encoding};
|
||||||
use mas_iana::jose::{
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
JsonWebEncryptionCompressionAlgorithm, JsonWebEncryptionEnc, JsonWebSignatureAlg,
|
use thiserror::Error;
|
||||||
};
|
|
||||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
|
||||||
use serde_with::{
|
|
||||||
base64::{Base64, Standard, UrlSafe},
|
|
||||||
formats::{Padded, Unpadded},
|
|
||||||
serde_as, skip_serializing_none,
|
|
||||||
};
|
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
use crate::{jwk::JsonWebKey, SigningKeystore, VerifyingKeystore};
|
use crate::{SigningKeystore, VerifyingKeystore};
|
||||||
|
|
||||||
#[serde_as]
|
mod header;
|
||||||
#[skip_serializing_none]
|
mod raw;
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
mod signed;
|
||||||
pub struct JwtHeader {
|
|
||||||
alg: JsonWebSignatureAlg,
|
|
||||||
|
|
||||||
#[serde(default)]
|
pub use self::{header::JsonWebSignatureHeader, signed::Jwt};
|
||||||
enc: Option<JsonWebEncryptionEnc>,
|
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
jku: Option<Url>,
|
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
jwk: Option<JsonWebKey>,
|
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
kid: Option<String>,
|
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
x5u: Option<Url>,
|
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
#[serde_as(as = "Option<Vec<Base64<Standard, Padded>>>")]
|
|
||||||
x5c: Option<Vec<Vec<u8>>>,
|
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
#[serde_as(as = "Option<Base64<UrlSafe, Unpadded>>")]
|
|
||||||
x5t: Option<Vec<u8>>,
|
|
||||||
#[serde(default, rename = "x5t#S256")]
|
|
||||||
#[serde_as(as = "Option<Base64<UrlSafe, Unpadded>>")]
|
|
||||||
x5t_s256: Option<Vec<u8>>,
|
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
typ: Option<String>,
|
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
cty: Option<String>,
|
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
crit: Option<Vec<String>>,
|
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
zip: Option<JsonWebEncryptionCompressionAlgorithm>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl JwtHeader {
|
|
||||||
pub fn encode(&self) -> anyhow::Result<String> {
|
|
||||||
let payload = serde_json::to_string(self)?;
|
|
||||||
let encoded = Base64UrlUnpadded::encode_string(payload.as_bytes());
|
|
||||||
Ok(encoded)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn new(alg: JsonWebSignatureAlg) -> Self {
|
|
||||||
Self {
|
|
||||||
alg,
|
|
||||||
enc: None,
|
|
||||||
jku: None,
|
|
||||||
jwk: None,
|
|
||||||
kid: None,
|
|
||||||
x5u: None,
|
|
||||||
x5c: None,
|
|
||||||
x5t: None,
|
|
||||||
x5t_s256: None,
|
|
||||||
typ: None,
|
|
||||||
cty: None,
|
|
||||||
crit: None,
|
|
||||||
zip: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn alg(&self) -> JsonWebSignatureAlg {
|
|
||||||
self.alg
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn kid(&self) -> Option<&str> {
|
|
||||||
self.kid.as_deref()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn with_kid(mut self, kid: impl Into<String>) -> Self {
|
|
||||||
self.kid = Some(kid.into());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for JwtHeader {
|
|
||||||
type Err = anyhow::Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
let decoded = Base64UrlUnpadded::decode_vec(s)?;
|
|
||||||
let parsed = serde_json::from_slice(&decoded)?;
|
|
||||||
Ok(parsed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct JsonWebTokenParts {
|
pub struct JsonWebTokenParts {
|
||||||
@ -132,21 +32,62 @@ pub struct JsonWebTokenParts {
|
|||||||
signature: Vec<u8>,
|
signature: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
#[error("failed to decode JWT")]
|
||||||
|
pub enum JwtPartsDecodeError {
|
||||||
|
#[error("no dots found in the JWT")]
|
||||||
|
NoDots,
|
||||||
|
|
||||||
|
#[error("could not decode signature")]
|
||||||
|
SignatureEncoding {
|
||||||
|
#[from]
|
||||||
|
inner: base64ct::Error,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
impl FromStr for JsonWebTokenParts {
|
impl FromStr for JsonWebTokenParts {
|
||||||
type Err = anyhow::Error;
|
type Err = JwtPartsDecodeError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let (payload, signature) = s
|
let (payload, signature) = s.rsplit_once('.').ok_or(JwtPartsDecodeError::NoDots)?;
|
||||||
.rsplit_once('.')
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("no dots found in JWT"))?;
|
|
||||||
let signature = Base64UrlUnpadded::decode_vec(signature)?;
|
let signature = Base64UrlUnpadded::decode_vec(signature)?;
|
||||||
let payload = payload.to_owned();
|
let payload = payload.to_owned();
|
||||||
Ok(Self { payload, signature })
|
Ok(Self { payload, signature })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
#[error("failed to serialize JWT")]
|
||||||
|
pub enum JwtSerializeError {
|
||||||
|
#[error("failed to serialize JWT header")]
|
||||||
|
Header {
|
||||||
|
#[source]
|
||||||
|
inner: serde_json::Error,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("failed to serialize payload")]
|
||||||
|
Payload {
|
||||||
|
#[source]
|
||||||
|
inner: serde_json::Error,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
#[error("failed to serialize JWT")]
|
||||||
|
pub enum JwtSignatureError {
|
||||||
|
Serialize {
|
||||||
|
#[from]
|
||||||
|
inner: JwtSerializeError,
|
||||||
|
},
|
||||||
|
|
||||||
|
Sign {
|
||||||
|
#[source]
|
||||||
|
inner: anyhow::Error,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
pub struct DecodedJsonWebToken<T> {
|
pub struct DecodedJsonWebToken<T> {
|
||||||
header: JwtHeader,
|
header: JsonWebSignatureHeader,
|
||||||
payload: T,
|
payload: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,24 +95,33 @@ impl<T> DecodedJsonWebToken<T>
|
|||||||
where
|
where
|
||||||
T: Serialize,
|
T: Serialize,
|
||||||
{
|
{
|
||||||
fn serialize(&self) -> anyhow::Result<String> {
|
fn serialize(&self) -> Result<String, JwtSerializeError> {
|
||||||
let header = serde_json::to_vec(&self.header)?;
|
let header = serde_json::to_vec(&self.header)
|
||||||
|
.map_err(|inner| JwtSerializeError::Header { inner })?;
|
||||||
let header = Base64UrlUnpadded::encode_string(&header);
|
let header = Base64UrlUnpadded::encode_string(&header);
|
||||||
let payload = serde_json::to_vec(&self.payload)?;
|
|
||||||
|
let payload = serde_json::to_vec(&self.payload)
|
||||||
|
.map_err(|inner| JwtSerializeError::Payload { inner })?;
|
||||||
let payload = Base64UrlUnpadded::encode_string(&payload);
|
let payload = Base64UrlUnpadded::encode_string(&payload);
|
||||||
|
|
||||||
Ok(format!("{}.{}", header, payload))
|
Ok(format!("{}.{}", header, payload))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn sign<S: SigningKeystore>(&self, store: &S) -> anyhow::Result<JsonWebTokenParts> {
|
pub async fn sign<S: SigningKeystore>(
|
||||||
|
&self,
|
||||||
|
store: &S,
|
||||||
|
) -> Result<JsonWebTokenParts, JwtSignatureError> {
|
||||||
let payload = self.serialize()?;
|
let payload = self.serialize()?;
|
||||||
let signature = store.sign(&self.header, payload.as_bytes()).await?;
|
let signature = store
|
||||||
|
.sign(&self.header, payload.as_bytes())
|
||||||
|
.await
|
||||||
|
.map_err(|inner| JwtSignatureError::Sign { inner })?;
|
||||||
Ok(JsonWebTokenParts { payload, signature })
|
Ok(JsonWebTokenParts { payload, signature })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> DecodedJsonWebToken<T> {
|
impl<T> DecodedJsonWebToken<T> {
|
||||||
pub fn new(header: JwtHeader, payload: T) -> Self {
|
pub fn new(header: JsonWebSignatureHeader, payload: T) -> Self {
|
||||||
Self { header, payload }
|
Self { header, payload }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,11 +129,11 @@ impl<T> DecodedJsonWebToken<T> {
|
|||||||
&self.payload
|
&self.payload
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn header(&self) -> &JwtHeader {
|
pub fn header(&self) -> &JsonWebSignatureHeader {
|
||||||
&self.header
|
&self.header
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn split(self) -> (JwtHeader, T) {
|
pub fn split(self) -> (JsonWebSignatureHeader, T) {
|
||||||
(self.header, self.payload)
|
(self.header, self.payload)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -213,7 +163,11 @@ impl JsonWebTokenParts {
|
|||||||
Ok(decoded)
|
Ok(decoded)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verify<S: VerifyingKeystore>(&self, header: &JwtHeader, store: &S) -> S::Future {
|
pub fn verify<S: VerifyingKeystore>(
|
||||||
|
&self,
|
||||||
|
header: &JsonWebSignatureHeader,
|
||||||
|
store: &S,
|
||||||
|
) -> S::Future {
|
||||||
store.verify(header, self.payload.as_bytes(), &self.signature)
|
store.verify(header, self.payload.as_bytes(), &self.signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,6 +193,8 @@ impl JsonWebTokenParts {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use mas_iana::jose::JsonWebSignatureAlg;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::SharedSecret;
|
use crate::SharedSecret;
|
||||||
|
|
||||||
@ -247,13 +203,12 @@ mod tests {
|
|||||||
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
|
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
|
||||||
let jwt: JsonWebTokenParts = jwt.parse().unwrap();
|
let jwt: JsonWebTokenParts = jwt.parse().unwrap();
|
||||||
let secret = "your-256-bit-secret";
|
let secret = "your-256-bit-secret";
|
||||||
println!("{:?}", jwt);
|
|
||||||
let store = SharedSecret::new(&secret);
|
let store = SharedSecret::new(&secret);
|
||||||
let jwt: DecodedJsonWebToken<serde_json::Value> =
|
let jwt: DecodedJsonWebToken<serde_json::Value> =
|
||||||
jwt.decode_and_verify(&store).await.unwrap();
|
jwt.decode_and_verify(&store).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(jwt.header.typ, Some("JWT".to_owned()));
|
assert_eq!(jwt.header.typ(), Some("JWT"));
|
||||||
assert_eq!(jwt.header.alg, JsonWebSignatureAlg::Hs256);
|
assert_eq!(jwt.header.alg(), JsonWebSignatureAlg::Hs256);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
jwt.payload,
|
jwt.payload,
|
||||||
serde_json::json!({
|
serde_json::json!({
|
99
crates/jose/src/jwt/raw.rs
Normal file
99
crates/jose/src/jwt/raw.rs
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
// 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::{borrow::Cow, ops::Deref};
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub struct RawJwt<'a> {
|
||||||
|
inner: Cow<'a, str>,
|
||||||
|
first_dot: usize,
|
||||||
|
second_dot: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawJwt<'static> {
|
||||||
|
pub(super) fn new(inner: String, first_dot: usize, second_dot: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: inner.into(),
|
||||||
|
first_dot,
|
||||||
|
second_dot,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> std::fmt::Display for RawJwt<'a> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> RawJwt<'a> {
|
||||||
|
pub fn header(&'a self) -> &'a str {
|
||||||
|
&self.inner[..self.first_dot]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn payload(&'a self) -> &'a str {
|
||||||
|
&self.inner[self.first_dot + 1..self.second_dot]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn signature(&'a self) -> &'a str {
|
||||||
|
&self.inner[self.second_dot + 1..]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn signed_part(&'a self) -> &'a str {
|
||||||
|
&self.inner[..self.second_dot]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Deref for RawJwt<'a> {
|
||||||
|
type Target = str;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum DecodeError {
|
||||||
|
#[error("no dots found in JWT")]
|
||||||
|
NoDots,
|
||||||
|
|
||||||
|
#[error("only one dot found in JWT")]
|
||||||
|
OnlyOneDot,
|
||||||
|
|
||||||
|
#[error("too many dots in JWT")]
|
||||||
|
TooManyDots,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<&'a str> for RawJwt<'a> {
|
||||||
|
type Error = DecodeError;
|
||||||
|
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
|
||||||
|
let mut indices = value
|
||||||
|
.char_indices()
|
||||||
|
.filter_map(|(idx, c)| (c == '.').then(|| idx));
|
||||||
|
|
||||||
|
let first_dot = indices.next().ok_or(DecodeError::NoDots)?;
|
||||||
|
let second_dot = indices.next().ok_or(DecodeError::OnlyOneDot)?;
|
||||||
|
|
||||||
|
if indices.next().is_some() {
|
||||||
|
return Err(DecodeError::TooManyDots);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
inner: value.into(),
|
||||||
|
first_dot,
|
||||||
|
second_dot,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
278
crates/jose/src/jwt/signed.rs
Normal file
278
crates/jose/src/jwt/signed.rs
Normal file
@ -0,0 +1,278 @@
|
|||||||
|
// 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 base64ct::{Base64UrlUnpadded, Encoding};
|
||||||
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
|
use signature::{Signature, Signer, Verifier};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use super::{header::JsonWebSignatureHeader, raw::RawJwt};
|
||||||
|
|
||||||
|
pub struct Jwt<'a, T> {
|
||||||
|
raw: RawJwt<'a>,
|
||||||
|
header: JsonWebSignatureHeader,
|
||||||
|
payload: T,
|
||||||
|
signature: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> std::fmt::Display for Jwt<'a, T> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> std::fmt::Debug for Jwt<'a, T>
|
||||||
|
where
|
||||||
|
T: std::fmt::Debug,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("Jwt")
|
||||||
|
.field("raw", &"...")
|
||||||
|
.field("header", &self.header)
|
||||||
|
.field("payload", &self.payload)
|
||||||
|
.field("signature", &"...")
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum JwtDecodeError {
|
||||||
|
#[error(transparent)]
|
||||||
|
RawDecode {
|
||||||
|
#[from]
|
||||||
|
inner: super::raw::DecodeError,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("failed to decode JWT header")]
|
||||||
|
DecodeHeader {
|
||||||
|
#[source]
|
||||||
|
inner: base64ct::Error,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("failed to deserialize JWT header")]
|
||||||
|
DeserializeHeader {
|
||||||
|
#[source]
|
||||||
|
inner: serde_json::Error,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("failed to decode JWT payload")]
|
||||||
|
DecodePayload {
|
||||||
|
#[source]
|
||||||
|
inner: base64ct::Error,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("failed to deserialize JWT payload")]
|
||||||
|
DeserializePayload {
|
||||||
|
#[source]
|
||||||
|
inner: serde_json::Error,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("failed to decode JWT signature")]
|
||||||
|
DecodeSignature {
|
||||||
|
#[source]
|
||||||
|
inner: base64ct::Error,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JwtDecodeError {
|
||||||
|
fn decode_header(inner: base64ct::Error) -> Self {
|
||||||
|
Self::DecodeHeader { inner }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_header(inner: serde_json::Error) -> Self {
|
||||||
|
Self::DeserializeHeader { inner }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_payload(inner: base64ct::Error) -> Self {
|
||||||
|
Self::DecodePayload { inner }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_payload(inner: serde_json::Error) -> Self {
|
||||||
|
Self::DeserializePayload { inner }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_signature(inner: base64ct::Error) -> Self {
|
||||||
|
Self::DecodeSignature { inner }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> TryFrom<&'a str> for Jwt<'a, T>
|
||||||
|
where
|
||||||
|
T: DeserializeOwned,
|
||||||
|
{
|
||||||
|
type Error = JwtDecodeError;
|
||||||
|
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
|
||||||
|
let raw = RawJwt::try_from(value)?;
|
||||||
|
|
||||||
|
let header =
|
||||||
|
Base64UrlUnpadded::decode_vec(raw.header()).map_err(JwtDecodeError::decode_header)?;
|
||||||
|
let header = serde_json::from_slice(&header).map_err(JwtDecodeError::deserialize_header)?;
|
||||||
|
|
||||||
|
let payload =
|
||||||
|
Base64UrlUnpadded::decode_vec(raw.payload()).map_err(JwtDecodeError::decode_payload)?;
|
||||||
|
let payload =
|
||||||
|
serde_json::from_slice(&payload).map_err(JwtDecodeError::deserialize_payload)?;
|
||||||
|
|
||||||
|
let signature = Base64UrlUnpadded::decode_vec(raw.signature())
|
||||||
|
.map_err(JwtDecodeError::decode_signature)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
raw,
|
||||||
|
header,
|
||||||
|
payload,
|
||||||
|
signature,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum JwtVerificationError {
|
||||||
|
#[error("failed to parse signature")]
|
||||||
|
ParseSignature {
|
||||||
|
#[source]
|
||||||
|
inner: signature::Error,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("signature verification failed")]
|
||||||
|
Verify {
|
||||||
|
#[source]
|
||||||
|
inner: signature::Error,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JwtVerificationError {
|
||||||
|
fn parse_signature(inner: signature::Error) -> Self {
|
||||||
|
Self::ParseSignature { inner }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify(inner: signature::Error) -> Self {
|
||||||
|
Self::Verify { inner }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Jwt<'a, T> {
|
||||||
|
pub fn verify<K, S>(&self, key: &K) -> Result<(), JwtVerificationError>
|
||||||
|
where
|
||||||
|
K: Verifier<S>,
|
||||||
|
S: Signature,
|
||||||
|
{
|
||||||
|
let signature =
|
||||||
|
S::from_bytes(&self.signature).map_err(JwtVerificationError::parse_signature)?;
|
||||||
|
|
||||||
|
key.verify(self.raw.signed_part().as_bytes(), &signature)
|
||||||
|
.map_err(JwtVerificationError::verify)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_str(&'a self) -> &'a str {
|
||||||
|
&self.raw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum JwtSignatureError {
|
||||||
|
#[error("failed to serialize header")]
|
||||||
|
EncodeHeader {
|
||||||
|
#[source]
|
||||||
|
inner: serde_json::Error,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("failed to serialize payload")]
|
||||||
|
EncodePayload {
|
||||||
|
#[source]
|
||||||
|
inner: serde_json::Error,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JwtSignatureError {
|
||||||
|
fn encode_header(inner: serde_json::Error) -> Self {
|
||||||
|
Self::EncodeHeader { inner }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_payload(inner: serde_json::Error) -> Self {
|
||||||
|
Self::EncodePayload { inner }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Jwt<'static, T> {
|
||||||
|
pub fn sign<K, S>(
|
||||||
|
header: JsonWebSignatureHeader,
|
||||||
|
payload: T,
|
||||||
|
key: &K,
|
||||||
|
) -> Result<Self, JwtSignatureError>
|
||||||
|
where
|
||||||
|
K: Signer<S>,
|
||||||
|
S: Signature,
|
||||||
|
T: Serialize,
|
||||||
|
{
|
||||||
|
let header_ = serde_json::to_vec(&header).map_err(JwtSignatureError::encode_header)?;
|
||||||
|
let header_ = Base64UrlUnpadded::encode_string(&header_);
|
||||||
|
|
||||||
|
let payload_ = serde_json::to_vec(&payload).map_err(JwtSignatureError::encode_payload)?;
|
||||||
|
let payload_ = Base64UrlUnpadded::encode_string(&payload_);
|
||||||
|
|
||||||
|
let mut inner = format!("{}.{}", header_, payload_);
|
||||||
|
|
||||||
|
let first_dot = header_.len();
|
||||||
|
let second_dot = inner.len();
|
||||||
|
|
||||||
|
let signature = key.sign(inner.as_bytes()).as_bytes().to_vec();
|
||||||
|
let signature_ = Base64UrlUnpadded::encode_string(&signature);
|
||||||
|
inner.reserve_exact(1 + signature_.len());
|
||||||
|
inner.push('.');
|
||||||
|
inner.push_str(&signature_);
|
||||||
|
|
||||||
|
let raw = RawJwt::new(inner, first_dot, second_dot);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
raw,
|
||||||
|
header,
|
||||||
|
payload,
|
||||||
|
signature,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use mas_iana::jose::JsonWebSignatureAlg;
|
||||||
|
use rand::thread_rng;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_jwt_decode() {
|
||||||
|
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
|
||||||
|
let jwt: Jwt<'_, serde_json::Value> = Jwt::try_from(jwt).unwrap();
|
||||||
|
assert_eq!(jwt.raw.header(), "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9");
|
||||||
|
assert_eq!(
|
||||||
|
jwt.raw.payload(),
|
||||||
|
"eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
jwt.raw.signature(),
|
||||||
|
"SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
|
||||||
|
);
|
||||||
|
assert_eq!(jwt.raw.signed_part(), "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_jwt_sign_and_verify() {
|
||||||
|
let header = JsonWebSignatureHeader::new(JsonWebSignatureAlg::Es256);
|
||||||
|
let payload = serde_json::json!({"hello": "world"});
|
||||||
|
|
||||||
|
let key = ecdsa::SigningKey::<p256::NistP256>::random(&mut thread_rng());
|
||||||
|
let signed = Jwt::sign(header, payload, &key).unwrap();
|
||||||
|
signed.verify(&key.verifying_key()).unwrap();
|
||||||
|
}
|
||||||
|
}
|
@ -24,7 +24,7 @@ use tower::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use super::StaticJwksStore;
|
use super::StaticJwksStore;
|
||||||
use crate::{JsonWebKeySet, JwtHeader, VerifyingKeystore};
|
use crate::{JsonWebKeySet, JsonWebSignatureHeader, VerifyingKeystore};
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
@ -121,7 +121,12 @@ impl VerifyingKeystore for DynamicJwksStore {
|
|||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = BoxFuture<'static, Result<(), Self::Error>>;
|
type Future = BoxFuture<'static, Result<(), Self::Error>>;
|
||||||
|
|
||||||
fn verify(&self, header: &JwtHeader, payload: &[u8], signature: &[u8]) -> Self::Future {
|
fn verify(
|
||||||
|
&self,
|
||||||
|
header: &JsonWebSignatureHeader,
|
||||||
|
payload: &[u8],
|
||||||
|
signature: &[u8],
|
||||||
|
) -> Self::Future {
|
||||||
let cache = self.cache.clone();
|
let cache = self.cache.clone();
|
||||||
let exporter = self.exporter.clone();
|
let exporter = self.exporter.clone();
|
||||||
let header = header.clone();
|
let header = header.clone();
|
||||||
|
@ -21,7 +21,7 @@ use sha2::{Sha256, Sha384, Sha512};
|
|||||||
use signature::{Signature, Verifier};
|
use signature::{Signature, Verifier};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{JsonWebKey, JsonWebKeySet, JwtHeader, VerifyingKeystore};
|
use crate::{JsonWebKey, JsonWebKeySet, JsonWebSignatureHeader, VerifyingKeystore};
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
@ -154,7 +154,7 @@ impl StaticJwksStore {
|
|||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
fn verify_sync(
|
fn verify_sync(
|
||||||
&self,
|
&self,
|
||||||
header: &JwtHeader,
|
header: &JsonWebSignatureHeader,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
signature: &[u8],
|
signature: &[u8],
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
@ -227,7 +227,12 @@ impl VerifyingKeystore for StaticJwksStore {
|
|||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = Ready<Result<(), Self::Error>>;
|
type Future = Ready<Result<(), Self::Error>>;
|
||||||
|
|
||||||
fn verify(&self, header: &JwtHeader, payload: &[u8], signature: &[u8]) -> Self::Future {
|
fn verify(
|
||||||
|
&self,
|
||||||
|
header: &JsonWebSignatureHeader,
|
||||||
|
payload: &[u8],
|
||||||
|
signature: &[u8],
|
||||||
|
) -> Self::Future {
|
||||||
std::future::ready(self.verify_sync(header, payload, signature))
|
std::future::ready(self.verify_sync(header, payload, signature))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ use sha2::{Sha256, Sha384, Sha512};
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use super::{SigningKeystore, VerifyingKeystore};
|
use super::{SigningKeystore, VerifyingKeystore};
|
||||||
use crate::JwtHeader;
|
use crate::JsonWebSignatureHeader;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
@ -50,7 +50,7 @@ impl<'a> SharedSecret<'a> {
|
|||||||
|
|
||||||
fn verify_sync(
|
fn verify_sync(
|
||||||
&self,
|
&self,
|
||||||
header: &JwtHeader,
|
header: &JsonWebSignatureHeader,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
signature: &[u8],
|
signature: &[u8],
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
@ -92,7 +92,10 @@ impl<'a> SigningKeystore for SharedSecret<'a> {
|
|||||||
algorithms
|
algorithms
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn prepare_header(&self, alg: JsonWebSignatureAlg) -> anyhow::Result<JwtHeader> {
|
async fn prepare_header(
|
||||||
|
&self,
|
||||||
|
alg: JsonWebSignatureAlg,
|
||||||
|
) -> anyhow::Result<JsonWebSignatureHeader> {
|
||||||
if !matches!(
|
if !matches!(
|
||||||
alg,
|
alg,
|
||||||
JsonWebSignatureAlg::Hs256 | JsonWebSignatureAlg::Hs384 | JsonWebSignatureAlg::Hs512,
|
JsonWebSignatureAlg::Hs256 | JsonWebSignatureAlg::Hs384 | JsonWebSignatureAlg::Hs512,
|
||||||
@ -100,10 +103,10 @@ impl<'a> SigningKeystore for SharedSecret<'a> {
|
|||||||
bail!("unsupported algorithm")
|
bail!("unsupported algorithm")
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(JwtHeader::new(alg))
|
Ok(JsonWebSignatureHeader::new(alg))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn sign(&self, header: &JwtHeader, msg: &[u8]) -> anyhow::Result<Vec<u8>> {
|
async fn sign(&self, header: &JsonWebSignatureHeader, msg: &[u8]) -> anyhow::Result<Vec<u8>> {
|
||||||
// TODO: do the signing in a blocking task
|
// TODO: do the signing in a blocking task
|
||||||
// TODO: should we bail out if the key is too small?
|
// TODO: should we bail out if the key is too small?
|
||||||
let signature = match header.alg() {
|
let signature = match header.alg() {
|
||||||
@ -136,7 +139,12 @@ impl<'a> VerifyingKeystore for SharedSecret<'a> {
|
|||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = Ready<Result<(), Self::Error>>;
|
type Future = Ready<Result<(), Self::Error>>;
|
||||||
|
|
||||||
fn verify(&self, header: &JwtHeader, payload: &[u8], signature: &[u8]) -> Self::Future {
|
fn verify(
|
||||||
|
&self,
|
||||||
|
header: &JsonWebSignatureHeader,
|
||||||
|
payload: &[u8],
|
||||||
|
signature: &[u8],
|
||||||
|
) -> Self::Future {
|
||||||
std::future::ready(self.verify_sync(header, payload, signature))
|
std::future::ready(self.verify_sync(header, payload, signature))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ use signature::{Signature, Signer, Verifier};
|
|||||||
use tower::Service;
|
use tower::Service;
|
||||||
|
|
||||||
use super::{SigningKeystore, VerifyingKeystore};
|
use super::{SigningKeystore, VerifyingKeystore};
|
||||||
use crate::{JsonWebKey, JsonWebKeySet, JwtHeader};
|
use crate::{JsonWebKey, JsonWebKeySet, JsonWebSignatureHeader};
|
||||||
|
|
||||||
// Generate with
|
// Generate with
|
||||||
// openssl genrsa 2048
|
// openssl genrsa 2048
|
||||||
@ -132,7 +132,7 @@ impl StaticKeystore {
|
|||||||
|
|
||||||
fn verify_sync(
|
fn verify_sync(
|
||||||
&self,
|
&self,
|
||||||
header: &JwtHeader,
|
header: &JsonWebSignatureHeader,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
signature: &[u8],
|
signature: &[u8],
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
@ -245,8 +245,11 @@ impl SigningKeystore for StaticKeystore {
|
|||||||
algorithms
|
algorithms
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn prepare_header(&self, alg: JsonWebSignatureAlg) -> anyhow::Result<JwtHeader> {
|
async fn prepare_header(
|
||||||
let header = JwtHeader::new(alg);
|
&self,
|
||||||
|
alg: JsonWebSignatureAlg,
|
||||||
|
) -> anyhow::Result<JsonWebSignatureHeader> {
|
||||||
|
let header = JsonWebSignatureHeader::new(alg);
|
||||||
|
|
||||||
let kid = match alg {
|
let kid = match alg {
|
||||||
JsonWebSignatureAlg::Rs256
|
JsonWebSignatureAlg::Rs256
|
||||||
@ -267,7 +270,7 @@ impl SigningKeystore for StaticKeystore {
|
|||||||
Ok(header.with_kid(kid))
|
Ok(header.with_kid(kid))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn sign(&self, header: &JwtHeader, msg: &[u8]) -> anyhow::Result<Vec<u8>> {
|
async fn sign(&self, header: &JsonWebSignatureHeader, msg: &[u8]) -> anyhow::Result<Vec<u8>> {
|
||||||
let kid = header
|
let kid = header
|
||||||
.kid()
|
.kid()
|
||||||
.ok_or_else(|| anyhow::anyhow!("missing kid from the JWT header"))?;
|
.ok_or_else(|| anyhow::anyhow!("missing kid from the JWT header"))?;
|
||||||
@ -350,7 +353,12 @@ impl VerifyingKeystore for StaticKeystore {
|
|||||||
type Error = anyhow::Error;
|
type Error = anyhow::Error;
|
||||||
type Future = Ready<Result<(), Self::Error>>;
|
type Future = Ready<Result<(), Self::Error>>;
|
||||||
|
|
||||||
fn verify(&self, header: &JwtHeader, msg: &[u8], signature: &[u8]) -> Self::Future {
|
fn verify(
|
||||||
|
&self,
|
||||||
|
header: &JsonWebSignatureHeader,
|
||||||
|
msg: &[u8],
|
||||||
|
signature: &[u8],
|
||||||
|
) -> Self::Future {
|
||||||
std::future::ready(self.verify_sync(header, msg, signature))
|
std::future::ready(self.verify_sync(header, msg, signature))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,22 +22,26 @@ use futures_util::{
|
|||||||
use mas_iana::jose::JsonWebSignatureAlg;
|
use mas_iana::jose::JsonWebSignatureAlg;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::JwtHeader;
|
use crate::JsonWebSignatureHeader;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait SigningKeystore {
|
pub trait SigningKeystore {
|
||||||
fn supported_algorithms(&self) -> HashSet<JsonWebSignatureAlg>;
|
fn supported_algorithms(&self) -> HashSet<JsonWebSignatureAlg>;
|
||||||
|
|
||||||
async fn prepare_header(&self, alg: JsonWebSignatureAlg) -> anyhow::Result<JwtHeader>;
|
async fn prepare_header(
|
||||||
|
&self,
|
||||||
|
alg: JsonWebSignatureAlg,
|
||||||
|
) -> anyhow::Result<JsonWebSignatureHeader>;
|
||||||
|
|
||||||
async fn sign(&self, header: &JwtHeader, msg: &[u8]) -> anyhow::Result<Vec<u8>>;
|
async fn sign(&self, header: &JsonWebSignatureHeader, msg: &[u8]) -> anyhow::Result<Vec<u8>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait VerifyingKeystore {
|
pub trait VerifyingKeystore {
|
||||||
type Error;
|
type Error;
|
||||||
type Future: Future<Output = Result<(), Self::Error>>;
|
type Future: Future<Output = Result<(), Self::Error>>;
|
||||||
|
|
||||||
fn verify(&self, header: &JwtHeader, msg: &[u8], signature: &[u8]) -> Self::Future;
|
fn verify(&self, header: &JsonWebSignatureHeader, msg: &[u8], signature: &[u8])
|
||||||
|
-> Self::Future;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
@ -61,7 +65,12 @@ where
|
|||||||
MapErr<R::Future, fn(R::Error) -> Self::Error>,
|
MapErr<R::Future, fn(R::Error) -> Self::Error>,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
fn verify(&self, header: &JwtHeader, msg: &[u8], signature: &[u8]) -> Self::Future {
|
fn verify(
|
||||||
|
&self,
|
||||||
|
header: &JsonWebSignatureHeader,
|
||||||
|
msg: &[u8],
|
||||||
|
signature: &[u8],
|
||||||
|
) -> Self::Future {
|
||||||
match self {
|
match self {
|
||||||
Either::Left(left) => Either::Left(
|
Either::Left(left) => Either::Left(
|
||||||
left.verify(header, msg, signature)
|
left.verify(header, msg, signature)
|
||||||
@ -83,7 +92,12 @@ where
|
|||||||
type Error = T::Error;
|
type Error = T::Error;
|
||||||
type Future = T::Future;
|
type Future = T::Future;
|
||||||
|
|
||||||
fn verify(&self, header: &JwtHeader, msg: &[u8], signature: &[u8]) -> Self::Future {
|
fn verify(
|
||||||
|
&self,
|
||||||
|
header: &JsonWebSignatureHeader,
|
||||||
|
msg: &[u8],
|
||||||
|
signature: &[u8],
|
||||||
|
) -> Self::Future {
|
||||||
self.as_ref().verify(header, msg, signature)
|
self.as_ref().verify(header, msg, signature)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ pub use futures_util::future::Either;
|
|||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
jwk::{JsonWebKey, JsonWebKeySet},
|
jwk::{JsonWebKey, JsonWebKeySet},
|
||||||
jwt::{DecodedJsonWebToken, JsonWebTokenParts, JwtHeader},
|
jwt::{DecodedJsonWebToken, JsonWebSignatureHeader, JsonWebTokenParts, Jwt, JwtSignatureError},
|
||||||
keystore::{
|
keystore::{
|
||||||
DynamicJwksStore, SharedSecret, SigningKeystore, StaticJwksStore, StaticKeystore,
|
DynamicJwksStore, SharedSecret, SigningKeystore, StaticJwksStore, StaticKeystore,
|
||||||
VerifyingKeystore,
|
VerifyingKeystore,
|
||||||
|
Reference in New Issue
Block a user