1
0
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:
Quentin Gliech
2022-08-23 17:45:23 +02:00
parent 495285162b
commit ca125a14c5
16 changed files with 690 additions and 157 deletions

View File

@ -31,8 +31,8 @@ use mas_data_model::{Client, JwksOrJwksUri, StorageBackend};
use mas_http::HttpServiceExt;
use mas_iana::oauth::OAuthClientAuthenticationMethod;
use mas_jose::{
DecodedJsonWebToken, DynamicJwksStore, Either, JsonWebKeySet, JsonWebTokenParts, JwtHeader,
SharedSecret, StaticJwksStore, VerifyingKeystore,
DecodedJsonWebToken, DynamicJwksStore, Either, JsonWebKeySet, JsonWebSignatureHeader,
JsonWebTokenParts, SharedSecret, StaticJwksStore, VerifyingKeystore,
};
use mas_storage::{
oauth2::client::{lookup_client_by_client_id, ClientFetchError},
@ -73,7 +73,7 @@ pub enum Credentials {
ClientAssertionJwtBearer {
client_id: String,
jwt: JsonWebTokenParts,
header: Box<JwtHeader>,
header: Box<JsonWebSignatureHeader>,
claims: HashMap<String, Value>,
},
}

View File

@ -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"] }
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"] }
pkcs8 = { version = "0.9.0", features = ["pem"] }
chacha20poly1305 = { version = "0.10.1", features = ["std"] }

View File

@ -40,7 +40,7 @@ serde_urlencoded = "0.7.1"
argon2 = { version = "0.4.1", features = ["password-hash"] }
# 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"] }
elliptic-curve = { version = "0.12.3", features = ["pem"] }
sha2 = "0.10.2"

View File

@ -26,7 +26,7 @@ use mas_data_model::{AuthorizationGrantStage, Client, TokenType};
use mas_iana::jose::JsonWebSignatureAlg;
use mas_jose::{
claims::{self, ClaimError},
DecodedJsonWebToken, SigningKeystore, StaticKeystore,
DecodedJsonWebToken, JwtSignatureError, SigningKeystore, StaticKeystore,
};
use mas_router::UrlBuilder;
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)]
pub(crate) async fn post(
client_authorization: ClientAuthorization<AccessTokenRequest>,

View File

@ -6,6 +6,7 @@ edition = "2021"
license = "Apache-2.0"
[dependencies]
async-signature = "0.2.0"
anyhow = "1.0.62"
async-trait = "0.1.57"
base64ct = { version = "1.5.1", features = ["std"] }
@ -21,7 +22,7 @@ p256 = { version = "0.11.1", features = ["ecdsa", "pem", "pkcs8"] }
pkcs1 = { version = "0.4.0", features = ["pem", "pkcs8"] }
pkcs8 = { version = "0.9.0", features = ["pem", "std"] }
rand = "0.8.5"
rsa = "0.7.0-pre"
rsa = { git = "https://github.com/RustCrypto/RSA.git" }
schemars = "0.8.10"
sec1 = "0.3.0"
serde = { version = "1.0.144", features = ["derive"] }

View 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
}
}

View File

@ -15,116 +15,16 @@
use std::str::FromStr;
use base64ct::{Base64UrlUnpadded, Encoding};
use mas_iana::jose::{
JsonWebEncryptionCompressionAlgorithm, JsonWebEncryptionEnc, JsonWebSignatureAlg,
};
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 serde::{de::DeserializeOwned, Serialize};
use thiserror::Error;
use crate::{jwk::JsonWebKey, SigningKeystore, VerifyingKeystore};
use crate::{SigningKeystore, VerifyingKeystore};
#[serde_as]
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct JwtHeader {
alg: JsonWebSignatureAlg,
mod header;
mod raw;
mod signed;
#[serde(default)]
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)
}
}
pub use self::{header::JsonWebSignatureHeader, signed::Jwt};
#[derive(Debug, PartialEq, Eq)]
pub struct JsonWebTokenParts {
@ -132,21 +32,62 @@ pub struct JsonWebTokenParts {
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 {
type Err = anyhow::Error;
type Err = JwtPartsDecodeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (payload, signature) = s
.rsplit_once('.')
.ok_or_else(|| anyhow::anyhow!("no dots found in JWT"))?;
let (payload, signature) = s.rsplit_once('.').ok_or(JwtPartsDecodeError::NoDots)?;
let signature = Base64UrlUnpadded::decode_vec(signature)?;
let payload = payload.to_owned();
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> {
header: JwtHeader,
header: JsonWebSignatureHeader,
payload: T,
}
@ -154,24 +95,33 @@ impl<T> DecodedJsonWebToken<T>
where
T: Serialize,
{
fn serialize(&self) -> anyhow::Result<String> {
let header = serde_json::to_vec(&self.header)?;
fn serialize(&self) -> Result<String, JwtSerializeError> {
let header = serde_json::to_vec(&self.header)
.map_err(|inner| JwtSerializeError::Header { inner })?;
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);
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 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 })
}
}
impl<T> DecodedJsonWebToken<T> {
pub fn new(header: JwtHeader, payload: T) -> Self {
pub fn new(header: JsonWebSignatureHeader, payload: T) -> Self {
Self { header, payload }
}
@ -179,11 +129,11 @@ impl<T> DecodedJsonWebToken<T> {
&self.payload
}
pub fn header(&self) -> &JwtHeader {
pub fn header(&self) -> &JsonWebSignatureHeader {
&self.header
}
pub fn split(self) -> (JwtHeader, T) {
pub fn split(self) -> (JsonWebSignatureHeader, T) {
(self.header, self.payload)
}
}
@ -213,7 +163,11 @@ impl JsonWebTokenParts {
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)
}
@ -239,6 +193,8 @@ impl JsonWebTokenParts {
#[cfg(test)]
mod tests {
use mas_iana::jose::JsonWebSignatureAlg;
use super::*;
use crate::SharedSecret;
@ -247,13 +203,12 @@ mod tests {
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
let jwt: JsonWebTokenParts = jwt.parse().unwrap();
let secret = "your-256-bit-secret";
println!("{:?}", jwt);
let store = SharedSecret::new(&secret);
let jwt: DecodedJsonWebToken<serde_json::Value> =
jwt.decode_and_verify(&store).await.unwrap();
assert_eq!(jwt.header.typ, Some("JWT".to_owned()));
assert_eq!(jwt.header.alg, JsonWebSignatureAlg::Hs256);
assert_eq!(jwt.header.typ(), Some("JWT"));
assert_eq!(jwt.header.alg(), JsonWebSignatureAlg::Hs256);
assert_eq!(
jwt.payload,
serde_json::json!({

View 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,
})
}
}

View 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();
}
}

View File

@ -24,7 +24,7 @@ use tower::{
};
use super::StaticJwksStore;
use crate::{JsonWebKeySet, JwtHeader, VerifyingKeystore};
use crate::{JsonWebKeySet, JsonWebSignatureHeader, VerifyingKeystore};
#[derive(Debug, Error)]
pub enum Error {
@ -121,7 +121,12 @@ impl VerifyingKeystore for DynamicJwksStore {
type Error = 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 exporter = self.exporter.clone();
let header = header.clone();

View File

@ -21,7 +21,7 @@ use sha2::{Sha256, Sha384, Sha512};
use signature::{Signature, Verifier};
use thiserror::Error;
use crate::{JsonWebKey, JsonWebKeySet, JwtHeader, VerifyingKeystore};
use crate::{JsonWebKey, JsonWebKeySet, JsonWebSignatureHeader, VerifyingKeystore};
#[derive(Debug, Error)]
pub enum Error {
@ -154,7 +154,7 @@ impl StaticJwksStore {
#[tracing::instrument(skip(self))]
fn verify_sync(
&self,
header: &JwtHeader,
header: &JsonWebSignatureHeader,
payload: &[u8],
signature: &[u8],
) -> Result<(), Error> {
@ -227,7 +227,12 @@ impl VerifyingKeystore for StaticJwksStore {
type Error = 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))
}
}

View File

@ -23,7 +23,7 @@ use sha2::{Sha256, Sha384, Sha512};
use thiserror::Error;
use super::{SigningKeystore, VerifyingKeystore};
use crate::JwtHeader;
use crate::JsonWebSignatureHeader;
#[derive(Debug, Error)]
pub enum Error {
@ -50,7 +50,7 @@ impl<'a> SharedSecret<'a> {
fn verify_sync(
&self,
header: &JwtHeader,
header: &JsonWebSignatureHeader,
payload: &[u8],
signature: &[u8],
) -> Result<(), Error> {
@ -92,7 +92,10 @@ impl<'a> SigningKeystore for SharedSecret<'a> {
algorithms
}
async fn prepare_header(&self, alg: JsonWebSignatureAlg) -> anyhow::Result<JwtHeader> {
async fn prepare_header(
&self,
alg: JsonWebSignatureAlg,
) -> anyhow::Result<JsonWebSignatureHeader> {
if !matches!(
alg,
JsonWebSignatureAlg::Hs256 | JsonWebSignatureAlg::Hs384 | JsonWebSignatureAlg::Hs512,
@ -100,10 +103,10 @@ impl<'a> SigningKeystore for SharedSecret<'a> {
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: should we bail out if the key is too small?
let signature = match header.alg() {
@ -136,7 +139,12 @@ impl<'a> VerifyingKeystore for SharedSecret<'a> {
type Error = 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))
}
}

View File

@ -34,7 +34,7 @@ use signature::{Signature, Signer, Verifier};
use tower::Service;
use super::{SigningKeystore, VerifyingKeystore};
use crate::{JsonWebKey, JsonWebKeySet, JwtHeader};
use crate::{JsonWebKey, JsonWebKeySet, JsonWebSignatureHeader};
// Generate with
// openssl genrsa 2048
@ -132,7 +132,7 @@ impl StaticKeystore {
fn verify_sync(
&self,
header: &JwtHeader,
header: &JsonWebSignatureHeader,
payload: &[u8],
signature: &[u8],
) -> anyhow::Result<()> {
@ -245,8 +245,11 @@ impl SigningKeystore for StaticKeystore {
algorithms
}
async fn prepare_header(&self, alg: JsonWebSignatureAlg) -> anyhow::Result<JwtHeader> {
let header = JwtHeader::new(alg);
async fn prepare_header(
&self,
alg: JsonWebSignatureAlg,
) -> anyhow::Result<JsonWebSignatureHeader> {
let header = JsonWebSignatureHeader::new(alg);
let kid = match alg {
JsonWebSignatureAlg::Rs256
@ -267,7 +270,7 @@ impl SigningKeystore for StaticKeystore {
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
.kid()
.ok_or_else(|| anyhow::anyhow!("missing kid from the JWT header"))?;
@ -350,7 +353,12 @@ impl VerifyingKeystore for StaticKeystore {
type Error = anyhow::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))
}
}

View File

@ -22,22 +22,26 @@ use futures_util::{
use mas_iana::jose::JsonWebSignatureAlg;
use thiserror::Error;
use crate::JwtHeader;
use crate::JsonWebSignatureHeader;
#[async_trait]
pub trait SigningKeystore {
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 {
type 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)]
@ -61,7 +65,12 @@ where
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 {
Either::Left(left) => Either::Left(
left.verify(header, msg, signature)
@ -83,7 +92,12 @@ where
type Error = T::Error;
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)
}
}

View File

@ -26,7 +26,7 @@ pub use futures_util::future::Either;
pub use self::{
jwk::{JsonWebKey, JsonWebKeySet},
jwt::{DecodedJsonWebToken, JsonWebTokenParts, JwtHeader},
jwt::{DecodedJsonWebToken, JsonWebSignatureHeader, JsonWebTokenParts, Jwt, JwtSignatureError},
keystore::{
DynamicJwksStore, SharedSecret, SigningKeystore, StaticJwksStore, StaticKeystore,
VerifyingKeystore,