You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-08-09 04:22:45 +03:00
Get rid of legacy JWKS store
This commit is contained in:
@@ -30,10 +30,7 @@ use mas_config::Encrypter;
|
|||||||
use mas_data_model::{Client, JwksOrJwksUri, StorageBackend};
|
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::{jwk::PublicJsonWebKeySet, Jwt};
|
||||||
jwk::PublicJsonWebKeySet, DecodedJsonWebToken, DynamicJwksStore, Either,
|
|
||||||
JsonWebSignatureHeader, 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},
|
||||||
PostgresqlBackend,
|
PostgresqlBackend,
|
||||||
@@ -72,9 +69,7 @@ pub enum Credentials {
|
|||||||
},
|
},
|
||||||
ClientAssertionJwtBearer {
|
ClientAssertionJwtBearer {
|
||||||
client_id: String,
|
client_id: String,
|
||||||
jwt: JsonWebTokenParts,
|
jwt: Box<Jwt<'static, HashMap<String, serde_json::Value>>>,
|
||||||
header: Box<JsonWebSignatureHeader>,
|
|
||||||
claims: HashMap<String, Value>,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +123,7 @@ impl Credentials {
|
|||||||
}
|
}
|
||||||
|
|
||||||
(
|
(
|
||||||
Credentials::ClientAssertionJwtBearer { jwt, header, .. },
|
Credentials::ClientAssertionJwtBearer { jwt, .. },
|
||||||
OAuthClientAuthenticationMethod::PrivateKeyJwt,
|
OAuthClientAuthenticationMethod::PrivateKeyJwt,
|
||||||
) => {
|
) => {
|
||||||
// Get the client JWKS
|
// Get the client JWKS
|
||||||
@@ -137,14 +132,16 @@ impl Credentials {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or(CredentialsVerificationError::InvalidClientConfig)?;
|
.ok_or(CredentialsVerificationError::InvalidClientConfig)?;
|
||||||
|
|
||||||
let store: Either<StaticJwksStore, DynamicJwksStore> = jwks_key_store(jwks);
|
let jwks = fetch_jwks(jwks)
|
||||||
let fut = jwt.verify(header, &store);
|
.await
|
||||||
fut.await
|
.map_err(|_| CredentialsVerificationError::JwksFetchFailed)?;
|
||||||
|
|
||||||
|
jwt.verify_from_jwks(&jwks)
|
||||||
.map_err(|_| CredentialsVerificationError::InvalidAssertionSignature)?;
|
.map_err(|_| CredentialsVerificationError::InvalidAssertionSignature)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
(
|
(
|
||||||
Credentials::ClientAssertionJwtBearer { jwt, header, .. },
|
Credentials::ClientAssertionJwtBearer { jwt, .. },
|
||||||
OAuthClientAuthenticationMethod::ClientSecretJwt,
|
OAuthClientAuthenticationMethod::ClientSecretJwt,
|
||||||
) => {
|
) => {
|
||||||
// Decrypt the client_secret
|
// Decrypt the client_secret
|
||||||
@@ -157,9 +154,7 @@ impl Credentials {
|
|||||||
.decrypt_string(encrypted_client_secret)
|
.decrypt_string(encrypted_client_secret)
|
||||||
.map_err(|_e| CredentialsVerificationError::DecryptionError)?;
|
.map_err(|_e| CredentialsVerificationError::DecryptionError)?;
|
||||||
|
|
||||||
let store = SharedSecret::new(&decrypted_client_secret);
|
jwt.verify_from_shared_secret(decrypted_client_secret)
|
||||||
let fut = jwt.verify(header, &store);
|
|
||||||
fut.await
|
|
||||||
.map_err(|_| CredentialsVerificationError::InvalidAssertionSignature)?;
|
.map_err(|_| CredentialsVerificationError::InvalidAssertionSignature)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,38 +166,25 @@ impl Credentials {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn jwks_key_store(jwks: &JwksOrJwksUri) -> Either<StaticJwksStore, DynamicJwksStore> {
|
async fn fetch_jwks(jwks: &JwksOrJwksUri) -> Result<PublicJsonWebKeySet, BoxError> {
|
||||||
// Assert that the output is both a VerifyingKeystore and Send
|
let uri = match jwks {
|
||||||
fn assert<T: Send + VerifyingKeystore>(t: T) -> T {
|
JwksOrJwksUri::Jwks(j) => return Ok(j.clone()),
|
||||||
t
|
JwksOrJwksUri::JwksUri(u) => u,
|
||||||
}
|
|
||||||
|
|
||||||
let inner = match jwks {
|
|
||||||
JwksOrJwksUri::Jwks(jwks) => Either::Left(StaticJwksStore::new(jwks.clone())),
|
|
||||||
JwksOrJwksUri::JwksUri(uri) => {
|
|
||||||
let uri = uri.clone();
|
|
||||||
|
|
||||||
// TODO: get the client from somewhere else?
|
|
||||||
let exporter = mas_http::client("fetch-jwks")
|
|
||||||
.response_body_to_bytes()
|
|
||||||
.json_response::<PublicJsonWebKeySet>()
|
|
||||||
.map_request(move |_: ()| {
|
|
||||||
http::Request::builder()
|
|
||||||
.method("GET")
|
|
||||||
// TODO: change the Uri type in config to avoid reparsing here
|
|
||||||
.uri(uri.to_string())
|
|
||||||
.body(http_body::Empty::new())
|
|
||||||
.unwrap()
|
|
||||||
})
|
|
||||||
.map_response(http::Response::into_body)
|
|
||||||
.map_err(BoxError::from)
|
|
||||||
.boxed_clone();
|
|
||||||
|
|
||||||
Either::Right(DynamicJwksStore::new(exporter))
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
assert(inner)
|
let request = http::Request::builder()
|
||||||
|
.uri(uri.as_str())
|
||||||
|
.body(http_body::Empty::new())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let client = mas_http::client("fetch-jwks")
|
||||||
|
.response_body_to_bytes()
|
||||||
|
.json_response::<PublicJsonWebKeySet>()
|
||||||
|
.map_err(Box::new);
|
||||||
|
|
||||||
|
let response = client.oneshot(request).await?;
|
||||||
|
|
||||||
|
Ok(response.into_body())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
@@ -221,6 +203,9 @@ pub enum CredentialsVerificationError {
|
|||||||
|
|
||||||
#[error("invalid assertion signature")]
|
#[error("invalid assertion signature")]
|
||||||
InvalidAssertionSignature,
|
InvalidAssertionSignature,
|
||||||
|
|
||||||
|
#[error("failed to fetch jwks")]
|
||||||
|
JwksFetchFailed,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
@@ -344,16 +329,10 @@ where
|
|||||||
Some(client_assertion),
|
Some(client_assertion),
|
||||||
) if client_assertion_type == JWT_BEARER_CLIENT_ASSERTION => {
|
) if client_assertion_type == JWT_BEARER_CLIENT_ASSERTION => {
|
||||||
// Got a JWT bearer client_assertion
|
// Got a JWT bearer client_assertion
|
||||||
|
let jwt: Jwt<'static, HashMap<String, Value>> = Jwt::try_from(client_assertion)
|
||||||
let jwt: JsonWebTokenParts = client_assertion
|
|
||||||
.parse()
|
|
||||||
.map_err(|_| ClientAuthorizationError::InvalidAssertion)?;
|
.map_err(|_| ClientAuthorizationError::InvalidAssertion)?;
|
||||||
let decoded: DecodedJsonWebToken<HashMap<String, Value>> = jwt
|
|
||||||
.decode()
|
|
||||||
.map_err(|_| ClientAuthorizationError::InvalidAssertion)?;
|
|
||||||
let (header, claims) = decoded.split();
|
|
||||||
|
|
||||||
let client_id = if let Some(Value::String(client_id)) = claims.get("sub") {
|
let client_id = if let Some(Value::String(client_id)) = jwt.payload().get("sub") {
|
||||||
client_id.clone()
|
client_id.clone()
|
||||||
} else {
|
} else {
|
||||||
return Err(ClientAuthorizationError::InvalidAssertion);
|
return Err(ClientAuthorizationError::InvalidAssertion);
|
||||||
@@ -371,9 +350,7 @@ where
|
|||||||
|
|
||||||
Credentials::ClientAssertionJwtBearer {
|
Credentials::ClientAssertionJwtBearer {
|
||||||
client_id,
|
client_id,
|
||||||
jwt,
|
jwt: Box::new(jwt),
|
||||||
header: Box::new(header),
|
|
||||||
claims,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -585,19 +562,15 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(authz.form, Some(serde_json::json!({"foo": "bar"})));
|
assert_eq!(authz.form, Some(serde_json::json!({"foo": "bar"})));
|
||||||
|
|
||||||
let (client_id, _jwt, _header, _claims) = if let Credentials::ClientAssertionJwtBearer {
|
let (client_id, jwt) =
|
||||||
client_id,
|
if let Credentials::ClientAssertionJwtBearer { client_id, jwt } = authz.credentials {
|
||||||
jwt,
|
(client_id, jwt)
|
||||||
header,
|
} else {
|
||||||
claims,
|
panic!("expected a JWT client_assertion");
|
||||||
} = authz.credentials
|
};
|
||||||
{
|
|
||||||
(client_id, jwt, header, claims)
|
|
||||||
} else {
|
|
||||||
panic!("expected a JWT client_assertion");
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(client_id, "client-id");
|
assert_eq!(client_id, "client-id");
|
||||||
// TODO: test more things
|
jwt.verify_from_shared_secret(b"client-secret".to_vec())
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -16,12 +16,10 @@ use std::{convert::Infallible, sync::Arc};
|
|||||||
|
|
||||||
use axum::{extract::Extension, response::IntoResponse, Json};
|
use axum::{extract::Extension, response::IntoResponse, Json};
|
||||||
use mas_jose::StaticKeystore;
|
use mas_jose::StaticKeystore;
|
||||||
use tower::{Service, ServiceExt};
|
|
||||||
|
|
||||||
pub(crate) async fn get(
|
pub(crate) async fn get(
|
||||||
Extension(key_store): Extension<Arc<StaticKeystore>>,
|
Extension(key_store): Extension<Arc<StaticKeystore>>,
|
||||||
) -> Result<impl IntoResponse, Infallible> {
|
) -> Result<impl IntoResponse, Infallible> {
|
||||||
let mut key_store: &StaticKeystore = key_store.as_ref();
|
let jwks = key_store.to_public_jwks();
|
||||||
let jwks = key_store.ready().await?.call(()).await?;
|
|
||||||
Ok(Json(jwks))
|
Ok(Json(jwks))
|
||||||
}
|
}
|
||||||
|
@@ -260,7 +260,8 @@ mod tests {
|
|||||||
let jwks: PublicJsonWebKeySet = serde_json::from_value(jwks).unwrap();
|
let jwks: PublicJsonWebKeySet = serde_json::from_value(jwks).unwrap();
|
||||||
// Both keys are RSA public keys
|
// Both keys are RSA public keys
|
||||||
for jwk in &jwks.keys {
|
for jwk in &jwks.keys {
|
||||||
rsa::RsaPublicKey::try_from(jwk.parameters.clone()).unwrap();
|
let p = jwk.params().rsa().expect("an RSA key");
|
||||||
|
rsa::RsaPublicKey::try_from(p).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let constraints = ConstraintSet::default()
|
let constraints = ConstraintSet::default()
|
||||||
@@ -394,12 +395,23 @@ mod tests {
|
|||||||
let jwks: PublicJsonWebKeySet = serde_json::from_value(jwks).unwrap();
|
let jwks: PublicJsonWebKeySet = serde_json::from_value(jwks).unwrap();
|
||||||
// The first 6 keys are RSA, 7th is P-256
|
// The first 6 keys are RSA, 7th is P-256
|
||||||
let mut keys = jwks.keys.into_iter();
|
let mut keys = jwks.keys.into_iter();
|
||||||
rsa::RsaPublicKey::try_from(keys.next().unwrap().parameters).unwrap();
|
rsa::RsaPublicKey::try_from(keys.next().unwrap().params().rsa().unwrap()).unwrap();
|
||||||
rsa::RsaPublicKey::try_from(keys.next().unwrap().parameters).unwrap();
|
rsa::RsaPublicKey::try_from(keys.next().unwrap().params().rsa().unwrap()).unwrap();
|
||||||
rsa::RsaPublicKey::try_from(keys.next().unwrap().parameters).unwrap();
|
rsa::RsaPublicKey::try_from(keys.next().unwrap().params().rsa().unwrap()).unwrap();
|
||||||
rsa::RsaPublicKey::try_from(keys.next().unwrap().parameters).unwrap();
|
rsa::RsaPublicKey::try_from(keys.next().unwrap().params().rsa().unwrap()).unwrap();
|
||||||
rsa::RsaPublicKey::try_from(keys.next().unwrap().parameters).unwrap();
|
rsa::RsaPublicKey::try_from(keys.next().unwrap().params().rsa().unwrap()).unwrap();
|
||||||
rsa::RsaPublicKey::try_from(keys.next().unwrap().parameters).unwrap();
|
rsa::RsaPublicKey::try_from(keys.next().unwrap().params().rsa().unwrap()).unwrap();
|
||||||
ecdsa::VerifyingKey::try_from(keys.next().unwrap().parameters).unwrap();
|
// 7th is P-256
|
||||||
|
ecdsa::VerifyingKey::<p256::NistP256>::try_from(
|
||||||
|
keys.next().unwrap().params().ec().unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
// 8th is P-384
|
||||||
|
ecdsa::VerifyingKey::<p384::NistP384>::try_from(
|
||||||
|
keys.next().unwrap().params().ec().unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
// 8th is P-521, but we don't support it yet
|
||||||
|
keys.next().unwrap().params().ec().unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -43,6 +43,40 @@ pub enum JsonWebKeyPrivateParameters {
|
|||||||
Okp(OkpPrivateParameters),
|
Okp(OkpPrivateParameters),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl JsonWebKeyPrivateParameters {
|
||||||
|
#[must_use]
|
||||||
|
pub const fn oct(&self) -> Option<&OctPrivateParameters> {
|
||||||
|
match self {
|
||||||
|
Self::Oct(params) => Some(params),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn rsa(&self) -> Option<&RsaPrivateParameters> {
|
||||||
|
match self {
|
||||||
|
Self::Rsa(params) => Some(params),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn ec(&self) -> Option<&EcPrivateParameters> {
|
||||||
|
match self {
|
||||||
|
Self::Ec(params) => Some(params),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn okp(&self) -> Option<&OkpPrivateParameters> {
|
||||||
|
match self {
|
||||||
|
Self::Okp(params) => Some(params),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ParametersInfo for JsonWebKeyPrivateParameters {
|
impl ParametersInfo for JsonWebKeyPrivateParameters {
|
||||||
fn kty(&self) -> JsonWebKeyType {
|
fn kty(&self) -> JsonWebKeyType {
|
||||||
match self {
|
match self {
|
||||||
|
@@ -39,6 +39,32 @@ pub enum JsonWebKeyPublicParameters {
|
|||||||
Okp(OkpPublicParameters),
|
Okp(OkpPublicParameters),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl JsonWebKeyPublicParameters {
|
||||||
|
#[must_use]
|
||||||
|
pub const fn rsa(&self) -> Option<&RsaPublicParameters> {
|
||||||
|
match self {
|
||||||
|
Self::Rsa(params) => Some(params),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn ec(&self) -> Option<&EcPublicParameters> {
|
||||||
|
match self {
|
||||||
|
Self::Ec(params) => Some(params),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn okp(&self) -> Option<&OkpPublicParameters> {
|
||||||
|
match self {
|
||||||
|
Self::Okp(params) => Some(params),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ParametersInfo for JsonWebKeyPublicParameters {
|
impl ParametersInfo for JsonWebKeyPublicParameters {
|
||||||
fn kty(&self) -> JsonWebKeyType {
|
fn kty(&self) -> JsonWebKeyType {
|
||||||
match self {
|
match self {
|
||||||
@@ -163,11 +189,38 @@ impl OkpPublicParameters {
|
|||||||
|
|
||||||
mod rsa_impls {
|
mod rsa_impls {
|
||||||
use digest::DynDigest;
|
use digest::DynDigest;
|
||||||
use rsa::{BigUint, RsaPublicKey};
|
use rsa::{BigUint, PublicKeyParts, RsaPublicKey};
|
||||||
|
|
||||||
use super::RsaPublicParameters;
|
use super::{JsonWebKeyPublicParameters, RsaPublicParameters};
|
||||||
use crate::jwa::rsa::RsaHashIdentifier;
|
use crate::jwa::rsa::RsaHashIdentifier;
|
||||||
|
|
||||||
|
impl From<RsaPublicKey> for JsonWebKeyPublicParameters {
|
||||||
|
fn from(key: RsaPublicKey) -> Self {
|
||||||
|
Self::from(&key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&RsaPublicKey> for JsonWebKeyPublicParameters {
|
||||||
|
fn from(key: &RsaPublicKey) -> Self {
|
||||||
|
Self::Rsa(key.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RsaPublicKey> for RsaPublicParameters {
|
||||||
|
fn from(key: RsaPublicKey) -> Self {
|
||||||
|
Self::from(&key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&RsaPublicKey> for RsaPublicParameters {
|
||||||
|
fn from(key: &RsaPublicKey) -> Self {
|
||||||
|
Self {
|
||||||
|
n: key.n().to_bytes_be(),
|
||||||
|
e: key.e().to_bytes_be(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<H> TryFrom<RsaPublicParameters> for crate::jwa::rsa::pkcs1v15::VerifyingKey<H>
|
impl<H> TryFrom<RsaPublicParameters> for crate::jwa::rsa::pkcs1v15::VerifyingKey<H>
|
||||||
where
|
where
|
||||||
H: RsaHashIdentifier,
|
H: RsaHashIdentifier,
|
||||||
@@ -236,7 +289,56 @@ mod ec_impls {
|
|||||||
AffinePoint, Curve, FieldBytes, FieldSize, ProjectiveArithmetic, PublicKey,
|
AffinePoint, Curve, FieldBytes, FieldSize, ProjectiveArithmetic, PublicKey,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{super::JwkEcCurve, EcPublicParameters};
|
use super::{super::JwkEcCurve, EcPublicParameters, JsonWebKeyPublicParameters};
|
||||||
|
|
||||||
|
impl<C> From<ecdsa::VerifyingKey<C>> for JsonWebKeyPublicParameters
|
||||||
|
where
|
||||||
|
C: PrimeCurve + ProjectiveArithmetic + JwkEcCurve,
|
||||||
|
AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
|
||||||
|
FieldSize<C>: ModulusSize,
|
||||||
|
{
|
||||||
|
fn from(key: ecdsa::VerifyingKey<C>) -> Self {
|
||||||
|
Self::from(&key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> From<&ecdsa::VerifyingKey<C>> for JsonWebKeyPublicParameters
|
||||||
|
where
|
||||||
|
C: PrimeCurve + ProjectiveArithmetic + JwkEcCurve,
|
||||||
|
AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
|
||||||
|
FieldSize<C>: ModulusSize,
|
||||||
|
{
|
||||||
|
fn from(key: &ecdsa::VerifyingKey<C>) -> Self {
|
||||||
|
Self::Ec(key.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> From<ecdsa::VerifyingKey<C>> for EcPublicParameters
|
||||||
|
where
|
||||||
|
C: PrimeCurve + ProjectiveArithmetic + JwkEcCurve,
|
||||||
|
AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
|
||||||
|
FieldSize<C>: ModulusSize,
|
||||||
|
{
|
||||||
|
fn from(key: ecdsa::VerifyingKey<C>) -> Self {
|
||||||
|
Self::from(&key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> From<&ecdsa::VerifyingKey<C>> for EcPublicParameters
|
||||||
|
where
|
||||||
|
C: PrimeCurve + ProjectiveArithmetic + JwkEcCurve,
|
||||||
|
AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
|
||||||
|
FieldSize<C>: ModulusSize,
|
||||||
|
{
|
||||||
|
fn from(key: &ecdsa::VerifyingKey<C>) -> Self {
|
||||||
|
let points = key.to_encoded_point(false);
|
||||||
|
EcPublicParameters {
|
||||||
|
x: points.x().unwrap().to_vec(),
|
||||||
|
y: points.y().unwrap().to_vec(),
|
||||||
|
crv: C::CRV,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<C> TryFrom<EcPublicParameters> for VerifyingKey<C>
|
impl<C> TryFrom<EcPublicParameters> for VerifyingKey<C>
|
||||||
where
|
where
|
||||||
@@ -311,71 +413,3 @@ mod ec_impls {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Some legacy implementations to remove
|
|
||||||
mod legacy {
|
|
||||||
use anyhow::bail;
|
|
||||||
use mas_iana::jose::JsonWebKeyEcEllipticCurve;
|
|
||||||
use p256::NistP256;
|
|
||||||
use rsa::{BigUint, PublicKeyParts};
|
|
||||||
|
|
||||||
use super::{EcPublicParameters, JsonWebKeyPublicParameters, RsaPublicParameters};
|
|
||||||
|
|
||||||
impl TryFrom<JsonWebKeyPublicParameters> for ecdsa::VerifyingKey<NistP256> {
|
|
||||||
type Error = anyhow::Error;
|
|
||||||
|
|
||||||
fn try_from(params: JsonWebKeyPublicParameters) -> Result<Self, Self::Error> {
|
|
||||||
let (x, y): ([u8; 32], [u8; 32]) = match params {
|
|
||||||
JsonWebKeyPublicParameters::Ec(EcPublicParameters {
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
crv: JsonWebKeyEcEllipticCurve::P256,
|
|
||||||
}) => (
|
|
||||||
x.try_into()
|
|
||||||
.map_err(|_| anyhow::anyhow!("invalid curve parameter x"))?,
|
|
||||||
y.try_into()
|
|
||||||
.map_err(|_| anyhow::anyhow!("invalid curve parameter y"))?,
|
|
||||||
),
|
|
||||||
_ => bail!("Wrong curve"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let point = sec1::EncodedPoint::from_affine_coordinates(&x.into(), &y.into(), false);
|
|
||||||
let key = ecdsa::VerifyingKey::from_encoded_point(&point)?;
|
|
||||||
Ok(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ecdsa::VerifyingKey<NistP256>> for JsonWebKeyPublicParameters {
|
|
||||||
fn from(key: ecdsa::VerifyingKey<NistP256>) -> Self {
|
|
||||||
let points = key.to_encoded_point(false);
|
|
||||||
JsonWebKeyPublicParameters::Ec(EcPublicParameters {
|
|
||||||
x: points.x().unwrap().to_vec(),
|
|
||||||
y: points.y().unwrap().to_vec(),
|
|
||||||
crv: JsonWebKeyEcEllipticCurve::P256,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<JsonWebKeyPublicParameters> for rsa::RsaPublicKey {
|
|
||||||
type Error = anyhow::Error;
|
|
||||||
|
|
||||||
fn try_from(params: JsonWebKeyPublicParameters) -> Result<Self, Self::Error> {
|
|
||||||
let (n, e) = match ¶ms {
|
|
||||||
JsonWebKeyPublicParameters::Rsa(RsaPublicParameters { n, e }) => (n, e),
|
|
||||||
_ => bail!("Wrong key type"),
|
|
||||||
};
|
|
||||||
let n = BigUint::from_bytes_be(n);
|
|
||||||
let e = BigUint::from_bytes_be(e);
|
|
||||||
Ok(rsa::RsaPublicKey::new(n, e)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<rsa::RsaPublicKey> for JsonWebKeyPublicParameters {
|
|
||||||
fn from(key: rsa::RsaPublicKey) -> Self {
|
|
||||||
JsonWebKeyPublicParameters::Rsa(RsaPublicParameters {
|
|
||||||
n: key.n().to_bytes_be(),
|
|
||||||
e: key.e().to_bytes_be(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -190,32 +190,3 @@ impl JsonWebTokenParts {
|
|||||||
format!("{}.{}", payload, signature)
|
format!("{}.{}", payload, signature)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use mas_iana::jose::JsonWebSignatureAlg;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::SharedSecret;
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn decode_hs256() {
|
|
||||||
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
|
|
||||||
let jwt: JsonWebTokenParts = jwt.parse().unwrap();
|
|
||||||
let secret = "your-256-bit-secret";
|
|
||||||
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"));
|
|
||||||
assert_eq!(jwt.header.alg(), JsonWebSignatureAlg::Hs256);
|
|
||||||
assert_eq!(
|
|
||||||
jwt.payload,
|
|
||||||
serde_json::json!({
|
|
||||||
"sub": "1234567890",
|
|
||||||
"name": "John Doe",
|
|
||||||
"iat": 1_516_239_022
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -16,6 +16,7 @@ use std::{borrow::Cow, ops::Deref};
|
|||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
pub struct RawJwt<'a> {
|
pub struct RawJwt<'a> {
|
||||||
inner: Cow<'a, str>,
|
inner: Cow<'a, str>,
|
||||||
first_dot: usize,
|
first_dot: usize,
|
||||||
@@ -54,6 +55,14 @@ impl<'a> RawJwt<'a> {
|
|||||||
pub fn signed_part(&'a self) -> &'a str {
|
pub fn signed_part(&'a self) -> &'a str {
|
||||||
&self.inner[..self.second_dot]
|
&self.inner[..self.second_dot]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn into_owned(self) -> RawJwt<'static> {
|
||||||
|
RawJwt {
|
||||||
|
inner: self.inner.into_owned().into(),
|
||||||
|
first_dot: self.first_dot,
|
||||||
|
second_dot: self.second_dot,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Deref for RawJwt<'a> {
|
impl<'a> Deref for RawJwt<'a> {
|
||||||
@@ -97,3 +106,25 @@ impl<'a> TryFrom<&'a str> for RawJwt<'a> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<String> for RawJwt<'static> {
|
||||||
|
type Error = DecodeError;
|
||||||
|
fn try_from(value: String) -> 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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -18,7 +18,9 @@ use signature::{Signature, Signer, Verifier};
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use super::{header::JsonWebSignatureHeader, raw::RawJwt};
|
use super::{header::JsonWebSignatureHeader, raw::RawJwt};
|
||||||
|
use crate::{constraints::ConstraintSet, jwk::PublicJsonWebKeySet};
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
pub struct Jwt<'a, T> {
|
pub struct Jwt<'a, T> {
|
||||||
raw: RawJwt<'a>,
|
raw: RawJwt<'a>,
|
||||||
header: JsonWebSignatureHeader,
|
header: JsonWebSignatureHeader,
|
||||||
@@ -107,14 +109,12 @@ impl JwtDecodeError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T> TryFrom<&'a str> for Jwt<'a, T>
|
impl<'a, T> TryFrom<RawJwt<'a>> for Jwt<'a, T>
|
||||||
where
|
where
|
||||||
T: DeserializeOwned,
|
T: DeserializeOwned,
|
||||||
{
|
{
|
||||||
type Error = JwtDecodeError;
|
type Error = JwtDecodeError;
|
||||||
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
|
fn try_from(raw: RawJwt<'a>) -> Result<Self, Self::Error> {
|
||||||
let raw = RawJwt::try_from(value)?;
|
|
||||||
|
|
||||||
let header_reader =
|
let header_reader =
|
||||||
base64ct::Decoder::<'_, Base64UrlUnpadded>::new(raw.header().as_bytes())
|
base64ct::Decoder::<'_, Base64UrlUnpadded>::new(raw.header().as_bytes())
|
||||||
.map_err(JwtDecodeError::decode_header)?;
|
.map_err(JwtDecodeError::decode_header)?;
|
||||||
@@ -139,6 +139,28 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)?;
|
||||||
|
Self::try_from(raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> TryFrom<String> for Jwt<'static, T>
|
||||||
|
where
|
||||||
|
T: DeserializeOwned,
|
||||||
|
{
|
||||||
|
type Error = JwtDecodeError;
|
||||||
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||||
|
let raw = RawJwt::try_from(value)?;
|
||||||
|
Self::try_from(raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum JwtVerificationError {
|
pub enum JwtVerificationError {
|
||||||
#[error("failed to parse signature")]
|
#[error("failed to parse signature")]
|
||||||
@@ -164,6 +186,12 @@ impl JwtVerificationError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error, Default)]
|
||||||
|
#[error("none of the keys worked")]
|
||||||
|
pub struct NoKeyWorked {
|
||||||
|
_inner: (),
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a, T> Jwt<'a, T> {
|
impl<'a, T> Jwt<'a, T> {
|
||||||
pub fn header(&self) -> &JsonWebSignatureHeader {
|
pub fn header(&self) -> &JsonWebSignatureHeader {
|
||||||
&self.header
|
&self.header
|
||||||
@@ -173,6 +201,15 @@ impl<'a, T> Jwt<'a, T> {
|
|||||||
&self.payload
|
&self.payload
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn into_owned(self) -> Jwt<'static, T> {
|
||||||
|
Jwt {
|
||||||
|
raw: self.raw.into_owned(),
|
||||||
|
header: self.header,
|
||||||
|
payload: self.payload,
|
||||||
|
signature: self.signature,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn verify<K, S>(&self, key: &K) -> Result<(), JwtVerificationError>
|
pub fn verify<K, S>(&self, key: &K) -> Result<(), JwtVerificationError>
|
||||||
where
|
where
|
||||||
K: Verifier<S>,
|
K: Verifier<S>,
|
||||||
@@ -185,6 +222,37 @@ impl<'a, T> Jwt<'a, T> {
|
|||||||
.map_err(JwtVerificationError::verify)
|
.map_err(JwtVerificationError::verify)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn verify_from_shared_secret(&self, secret: Vec<u8>) -> Result<(), NoKeyWorked> {
|
||||||
|
let verifier = crate::verifier::Verifier::for_oct_and_alg(secret, self.header().alg())
|
||||||
|
.map_err(|_| NoKeyWorked::default())?;
|
||||||
|
|
||||||
|
self.verify(&verifier).map_err(|_| NoKeyWorked::default())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_from_jwks(&self, jwks: &PublicJsonWebKeySet) -> Result<(), NoKeyWorked> {
|
||||||
|
let constraints = ConstraintSet::from(self.header());
|
||||||
|
let candidates = constraints.filter(&**jwks);
|
||||||
|
|
||||||
|
for candidate in candidates {
|
||||||
|
let verifier = match crate::verifier::Verifier::for_jwk_and_alg(
|
||||||
|
candidate.params(),
|
||||||
|
self.header().alg(),
|
||||||
|
) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
match self.verify(&verifier) {
|
||||||
|
Ok(_) => return Ok(()),
|
||||||
|
Err(_) => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(NoKeyWorked::default())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn as_str(&'a self) -> &'a str {
|
pub fn as_str(&'a self) -> &'a str {
|
||||||
&self.raw
|
&self.raw
|
||||||
}
|
}
|
||||||
|
@@ -1,165 +0,0 @@
|
|||||||
// 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::sync::Arc;
|
|
||||||
|
|
||||||
use chrono::{DateTime, Duration, Utc};
|
|
||||||
use futures_util::future::BoxFuture;
|
|
||||||
use thiserror::Error;
|
|
||||||
use tokio::sync::RwLock;
|
|
||||||
use tower::{
|
|
||||||
util::{BoxCloneService, ServiceExt},
|
|
||||||
BoxError, Service,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::StaticJwksStore;
|
|
||||||
use crate::{jwk::PublicJsonWebKeySet, JsonWebSignatureHeader, VerifyingKeystore};
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum Error {
|
|
||||||
#[error("cache in inconsistent state")]
|
|
||||||
InconsistentCache,
|
|
||||||
|
|
||||||
#[error(transparent)]
|
|
||||||
Cached(Arc<BoxError>),
|
|
||||||
|
|
||||||
#[error("todo")]
|
|
||||||
Todo,
|
|
||||||
|
|
||||||
#[error(transparent)]
|
|
||||||
Verification(#[from] super::static_store::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
enum State<E> {
|
|
||||||
Pending,
|
|
||||||
Errored {
|
|
||||||
at: DateTime<Utc>,
|
|
||||||
error: E,
|
|
||||||
},
|
|
||||||
Fulfilled {
|
|
||||||
at: DateTime<Utc>,
|
|
||||||
store: StaticJwksStore,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E> Default for State<E> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Pending
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E> State<E> {
|
|
||||||
fn fullfill(&mut self, key_set: PublicJsonWebKeySet) {
|
|
||||||
*self = Self::Fulfilled {
|
|
||||||
at: Utc::now(),
|
|
||||||
store: StaticJwksStore::new(key_set),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn error(&mut self, error: E) {
|
|
||||||
*self = Self::Errored {
|
|
||||||
at: Utc::now(),
|
|
||||||
error,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn should_refresh(&self) -> bool {
|
|
||||||
let now = Utc::now();
|
|
||||||
match self {
|
|
||||||
Self::Pending => true,
|
|
||||||
Self::Errored { at, .. } if *at - now > Duration::minutes(5) => true,
|
|
||||||
Self::Fulfilled { at, .. } if *at - now > Duration::hours(1) => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn should_force_refresh(&self) -> bool {
|
|
||||||
let now = Utc::now();
|
|
||||||
match self {
|
|
||||||
Self::Pending => true,
|
|
||||||
Self::Errored { at, .. } | Self::Fulfilled { at, .. }
|
|
||||||
if *at - now > Duration::minutes(5) =>
|
|
||||||
{
|
|
||||||
true
|
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct DynamicJwksStore {
|
|
||||||
exporter: BoxCloneService<(), PublicJsonWebKeySet, BoxError>,
|
|
||||||
cache: Arc<RwLock<State<Arc<BoxError>>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DynamicJwksStore {
|
|
||||||
pub fn new<T>(exporter: T) -> Self
|
|
||||||
where
|
|
||||||
T: Service<(), Response = PublicJsonWebKeySet, Error = BoxError> + Send + Clone + 'static,
|
|
||||||
T::Future: Send,
|
|
||||||
{
|
|
||||||
Self {
|
|
||||||
exporter: exporter.boxed_clone(),
|
|
||||||
cache: Arc::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VerifyingKeystore for DynamicJwksStore {
|
|
||||||
type Error = Error;
|
|
||||||
type Future = BoxFuture<'static, Result<(), Self::Error>>;
|
|
||||||
|
|
||||||
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();
|
|
||||||
let payload = payload.to_owned();
|
|
||||||
let signature = signature.to_owned();
|
|
||||||
|
|
||||||
let fut = async move {
|
|
||||||
if cache.read().await.should_refresh() {
|
|
||||||
let mut cache = cache.write().await;
|
|
||||||
|
|
||||||
if cache.should_force_refresh() {
|
|
||||||
let jwks = async move { exporter.ready_oneshot().await?.call(()).await }.await;
|
|
||||||
|
|
||||||
match jwks {
|
|
||||||
Ok(jwks) => cache.fullfill(jwks),
|
|
||||||
Err(err) => cache.error(Arc::new(err)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let cache = cache.read().await;
|
|
||||||
// TODO: we could bubble up the underlying error here
|
|
||||||
let store = match &*cache {
|
|
||||||
State::Pending => return Err(Error::InconsistentCache),
|
|
||||||
State::Errored { error, .. } => return Err(Error::Cached(error.clone())),
|
|
||||||
State::Fulfilled { store, .. } => store,
|
|
||||||
};
|
|
||||||
|
|
||||||
store.verify(&header, &payload, &signature).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
};
|
|
||||||
|
|
||||||
Box::pin(fut)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,18 +0,0 @@
|
|||||||
// 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.
|
|
||||||
|
|
||||||
mod dynamic_store;
|
|
||||||
mod static_store;
|
|
||||||
|
|
||||||
pub use self::{dynamic_store::DynamicJwksStore, static_store::StaticJwksStore};
|
|
@@ -1,245 +0,0 @@
|
|||||||
// 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::future::Ready;
|
|
||||||
|
|
||||||
use digest::Digest;
|
|
||||||
use mas_iana::jose::{JsonWebKeyType, JsonWebSignatureAlg};
|
|
||||||
use rsa::{PublicKey, RsaPublicKey};
|
|
||||||
use sha2::{Sha256, Sha384, Sha512};
|
|
||||||
use signature::{Signature, Verifier};
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
constraints::Constrainable,
|
|
||||||
jwk::{PublicJsonWebKey, PublicJsonWebKeySet},
|
|
||||||
JsonWebSignatureHeader, VerifyingKeystore,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum Error {
|
|
||||||
#[error("key not found")]
|
|
||||||
KeyNotFound,
|
|
||||||
|
|
||||||
#[error("multiple key matched")]
|
|
||||||
MultipleKeyMatched,
|
|
||||||
|
|
||||||
#[error(r#"missing "kid" field in header"#)]
|
|
||||||
MissingKid,
|
|
||||||
|
|
||||||
#[error(transparent)]
|
|
||||||
Rsa(#[from] rsa::errors::Error),
|
|
||||||
|
|
||||||
#[error("unsupported algorithm {alg}")]
|
|
||||||
UnsupportedAlgorithm { alg: JsonWebSignatureAlg },
|
|
||||||
|
|
||||||
#[error(transparent)]
|
|
||||||
Signature(#[from] signature::Error),
|
|
||||||
|
|
||||||
#[error("invalid {kty} key")]
|
|
||||||
InvalidKey {
|
|
||||||
kty: JsonWebKeyType,
|
|
||||||
source: anyhow::Error,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
struct KeyConstraint<'a> {
|
|
||||||
kty: Option<JsonWebKeyType>,
|
|
||||||
alg: Option<JsonWebSignatureAlg>,
|
|
||||||
kid: Option<&'a str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> KeyConstraint<'a> {
|
|
||||||
fn matches(&self, key: &'a PublicJsonWebKey) -> bool {
|
|
||||||
// If a specific KID was asked, match the key only if it has a matching kid
|
|
||||||
// field
|
|
||||||
if let Some(kid) = self.kid {
|
|
||||||
if key.kid() != Some(kid) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(kty) = self.kty {
|
|
||||||
if key.kty() != kty {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(alg) = self.alg {
|
|
||||||
if key.alg() != None && key.alg() != Some(alg) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_keys(&self, key_set: &'a PublicJsonWebKeySet) -> Vec<&'a PublicJsonWebKey> {
|
|
||||||
key_set.iter().filter(|k| self.matches(k)).collect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct StaticJwksStore {
|
|
||||||
key_set: PublicJsonWebKeySet,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StaticJwksStore {
|
|
||||||
#[must_use]
|
|
||||||
pub fn new(key_set: PublicJsonWebKeySet) -> Self {
|
|
||||||
Self { key_set }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_key<'a>(
|
|
||||||
&'a self,
|
|
||||||
constraint: &KeyConstraint<'a>,
|
|
||||||
) -> Result<&'a PublicJsonWebKey, Error> {
|
|
||||||
let keys = constraint.find_keys(&self.key_set);
|
|
||||||
|
|
||||||
match &keys[..] {
|
|
||||||
[one] => Ok(one),
|
|
||||||
[] => Err(Error::KeyNotFound),
|
|
||||||
_ => Err(Error::MultipleKeyMatched),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_rsa_key(&self, kid: Option<&str>) -> Result<RsaPublicKey, Error> {
|
|
||||||
let constraint = KeyConstraint {
|
|
||||||
kty: Some(JsonWebKeyType::Rsa),
|
|
||||||
kid,
|
|
||||||
alg: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let key = self.find_key(&constraint)?;
|
|
||||||
|
|
||||||
let key = key
|
|
||||||
.params()
|
|
||||||
.clone()
|
|
||||||
.try_into()
|
|
||||||
.map_err(|source| Error::InvalidKey {
|
|
||||||
kty: JsonWebKeyType::Rsa,
|
|
||||||
source,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_ecdsa_key(
|
|
||||||
&self,
|
|
||||||
kid: Option<&str>,
|
|
||||||
) -> Result<ecdsa::VerifyingKey<p256::NistP256>, Error> {
|
|
||||||
let constraint = KeyConstraint {
|
|
||||||
kty: Some(JsonWebKeyType::Ec),
|
|
||||||
kid,
|
|
||||||
alg: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let key = self.find_key(&constraint)?;
|
|
||||||
|
|
||||||
let key = key
|
|
||||||
.params()
|
|
||||||
.clone()
|
|
||||||
.try_into()
|
|
||||||
.map_err(|source| Error::InvalidKey {
|
|
||||||
kty: JsonWebKeyType::Ec,
|
|
||||||
source,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
|
||||||
fn verify_sync(
|
|
||||||
&self,
|
|
||||||
header: &JsonWebSignatureHeader,
|
|
||||||
payload: &[u8],
|
|
||||||
signature: &[u8],
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let kid = header.kid();
|
|
||||||
match header.alg() {
|
|
||||||
JsonWebSignatureAlg::Rs256 => {
|
|
||||||
let key = self.find_rsa_key(kid)?;
|
|
||||||
|
|
||||||
let digest = {
|
|
||||||
let mut digest = Sha256::new();
|
|
||||||
digest.update(&payload);
|
|
||||||
digest.finalize()
|
|
||||||
};
|
|
||||||
|
|
||||||
key.verify(
|
|
||||||
rsa::PaddingScheme::new_pkcs1v15_sign(Some(rsa::Hash::SHA2_256)),
|
|
||||||
&digest,
|
|
||||||
signature,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonWebSignatureAlg::Rs384 => {
|
|
||||||
let key = self.find_rsa_key(kid)?;
|
|
||||||
|
|
||||||
let digest = {
|
|
||||||
let mut digest = Sha384::new();
|
|
||||||
digest.update(&payload);
|
|
||||||
digest.finalize()
|
|
||||||
};
|
|
||||||
|
|
||||||
key.verify(
|
|
||||||
rsa::PaddingScheme::new_pkcs1v15_sign(Some(rsa::Hash::SHA2_384)),
|
|
||||||
&digest,
|
|
||||||
signature,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonWebSignatureAlg::Rs512 => {
|
|
||||||
let key = self.find_rsa_key(kid)?;
|
|
||||||
|
|
||||||
let digest = {
|
|
||||||
let mut digest = Sha512::new();
|
|
||||||
digest.update(&payload);
|
|
||||||
digest.finalize()
|
|
||||||
};
|
|
||||||
|
|
||||||
key.verify(
|
|
||||||
rsa::PaddingScheme::new_pkcs1v15_sign(Some(rsa::Hash::SHA2_512)),
|
|
||||||
&digest,
|
|
||||||
signature,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonWebSignatureAlg::Es256 => {
|
|
||||||
let key = self.find_ecdsa_key(kid)?;
|
|
||||||
|
|
||||||
let signature = ecdsa::Signature::from_bytes(signature)?;
|
|
||||||
|
|
||||||
key.verify(payload, &signature)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
alg => return Err(Error::UnsupportedAlgorithm { alg }),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VerifyingKeystore for StaticJwksStore {
|
|
||||||
type Error = Error;
|
|
||||||
type Future = Ready<Result<(), Self::Error>>;
|
|
||||||
|
|
||||||
fn verify(
|
|
||||||
&self,
|
|
||||||
header: &JsonWebSignatureHeader,
|
|
||||||
payload: &[u8],
|
|
||||||
signature: &[u8],
|
|
||||||
) -> Self::Future {
|
|
||||||
std::future::ready(self.verify_sync(header, payload, signature))
|
|
||||||
}
|
|
||||||
}
|
|
@@ -12,14 +12,10 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
mod jwks;
|
|
||||||
mod shared_secret;
|
|
||||||
mod static_keystore;
|
mod static_keystore;
|
||||||
mod traits;
|
mod traits;
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
jwks::{DynamicJwksStore, StaticJwksStore},
|
|
||||||
shared_secret::SharedSecret,
|
|
||||||
static_keystore::StaticKeystore,
|
static_keystore::StaticKeystore,
|
||||||
traits::{SigningKeystore, VerifyingKeystore},
|
traits::{SigningKeystore, VerifyingKeystore},
|
||||||
};
|
};
|
||||||
|
@@ -1,172 +0,0 @@
|
|||||||
// Copyright 2022 The Matrix.org Foundation C.I.C.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
use std::{collections::HashSet, future::Ready};
|
|
||||||
|
|
||||||
use anyhow::bail;
|
|
||||||
use async_trait::async_trait;
|
|
||||||
use digest::{InvalidLength, MacError};
|
|
||||||
use hmac::{Hmac, Mac};
|
|
||||||
use mas_iana::jose::JsonWebSignatureAlg;
|
|
||||||
use sha2::{Sha256, Sha384, Sha512};
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
use super::{SigningKeystore, VerifyingKeystore};
|
|
||||||
use crate::JsonWebSignatureHeader;
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum Error {
|
|
||||||
#[error("invalid key")]
|
|
||||||
InvalidKey(#[from] InvalidLength),
|
|
||||||
|
|
||||||
#[error("unsupported algorithm {alg}")]
|
|
||||||
UnsupportedAlgorithm { alg: JsonWebSignatureAlg },
|
|
||||||
|
|
||||||
#[error("signature verification failed")]
|
|
||||||
Verification(#[from] MacError),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SharedSecret<'a> {
|
|
||||||
inner: &'a [u8],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> SharedSecret<'a> {
|
|
||||||
pub fn new(source: &'a impl AsRef<[u8]>) -> Self {
|
|
||||||
Self {
|
|
||||||
inner: source.as_ref(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn verify_sync(
|
|
||||||
&self,
|
|
||||||
header: &JsonWebSignatureHeader,
|
|
||||||
payload: &[u8],
|
|
||||||
signature: &[u8],
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
match header.alg() {
|
|
||||||
JsonWebSignatureAlg::Hs256 => {
|
|
||||||
let mut mac = Hmac::<Sha256>::new_from_slice(self.inner)?;
|
|
||||||
mac.update(payload);
|
|
||||||
mac.verify(signature.into())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonWebSignatureAlg::Hs384 => {
|
|
||||||
let mut mac = Hmac::<Sha384>::new_from_slice(self.inner)?;
|
|
||||||
mac.update(payload);
|
|
||||||
mac.verify(signature.into())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonWebSignatureAlg::Hs512 => {
|
|
||||||
let mut mac = Hmac::<Sha512>::new_from_slice(self.inner)?;
|
|
||||||
mac.update(payload);
|
|
||||||
mac.verify(signature.into())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
alg => return Err(Error::UnsupportedAlgorithm { alg }),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl<'a> SigningKeystore for SharedSecret<'a> {
|
|
||||||
fn supported_algorithms(&self) -> HashSet<JsonWebSignatureAlg> {
|
|
||||||
let mut algorithms = HashSet::with_capacity(3);
|
|
||||||
|
|
||||||
algorithms.insert(JsonWebSignatureAlg::Hs256);
|
|
||||||
algorithms.insert(JsonWebSignatureAlg::Hs384);
|
|
||||||
algorithms.insert(JsonWebSignatureAlg::Hs512);
|
|
||||||
|
|
||||||
algorithms
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn prepare_header(
|
|
||||||
&self,
|
|
||||||
alg: JsonWebSignatureAlg,
|
|
||||||
) -> anyhow::Result<JsonWebSignatureHeader> {
|
|
||||||
if !matches!(
|
|
||||||
alg,
|
|
||||||
JsonWebSignatureAlg::Hs256 | JsonWebSignatureAlg::Hs384 | JsonWebSignatureAlg::Hs512,
|
|
||||||
) {
|
|
||||||
bail!("unsupported algorithm")
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(JsonWebSignatureHeader::new(alg))
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
|
||||||
JsonWebSignatureAlg::Hs256 => {
|
|
||||||
let mut mac = Hmac::<Sha256>::new_from_slice(self.inner)?;
|
|
||||||
mac.update(msg);
|
|
||||||
mac.finalize().into_bytes().to_vec()
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonWebSignatureAlg::Hs384 => {
|
|
||||||
let mut mac = Hmac::<Sha384>::new_from_slice(self.inner)?;
|
|
||||||
mac.update(msg);
|
|
||||||
mac.finalize().into_bytes().to_vec()
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonWebSignatureAlg::Hs512 => {
|
|
||||||
let mut mac = Hmac::<Sha512>::new_from_slice(self.inner)?;
|
|
||||||
mac.update(msg);
|
|
||||||
mac.finalize().into_bytes().to_vec()
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => bail!("unsupported algorithm"),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(signature)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> VerifyingKeystore for SharedSecret<'a> {
|
|
||||||
type Error = Error;
|
|
||||||
type Future = Ready<Result<(), Self::Error>>;
|
|
||||||
|
|
||||||
fn verify(
|
|
||||||
&self,
|
|
||||||
header: &JsonWebSignatureHeader,
|
|
||||||
payload: &[u8],
|
|
||||||
signature: &[u8],
|
|
||||||
) -> Self::Future {
|
|
||||||
std::future::ready(self.verify_sync(header, payload, signature))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_shared_secret() {
|
|
||||||
let secret = "super-complicated-secret-that-should-be-big-enough-for-sha512";
|
|
||||||
let message = "this is the message to sign".as_bytes();
|
|
||||||
let store = SharedSecret::new(&secret);
|
|
||||||
for alg in [
|
|
||||||
JsonWebSignatureAlg::Hs256,
|
|
||||||
JsonWebSignatureAlg::Hs384,
|
|
||||||
JsonWebSignatureAlg::Hs512,
|
|
||||||
] {
|
|
||||||
let header = store.prepare_header(alg).await.unwrap();
|
|
||||||
assert_eq!(header.alg(), alg);
|
|
||||||
let signature = store.sign(&header, message).await.unwrap();
|
|
||||||
store.verify(&header, message, &signature).await.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -14,9 +14,7 @@
|
|||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
convert::Infallible,
|
|
||||||
future::Ready,
|
future::Ready,
|
||||||
task::Poll,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
@@ -31,11 +29,10 @@ use pkcs8::{DecodePrivateKey, EncodePublicKey};
|
|||||||
use rsa::{PublicKey as _, RsaPrivateKey, RsaPublicKey};
|
use rsa::{PublicKey as _, RsaPrivateKey, RsaPublicKey};
|
||||||
use sha2::{Sha256, Sha384, Sha512};
|
use sha2::{Sha256, Sha384, Sha512};
|
||||||
use signature::{Signature, Signer, Verifier};
|
use signature::{Signature, Signer, Verifier};
|
||||||
use tower::Service;
|
|
||||||
|
|
||||||
use super::{SigningKeystore, VerifyingKeystore};
|
use super::{SigningKeystore, VerifyingKeystore};
|
||||||
use crate::{
|
use crate::{
|
||||||
jwk::{JsonWebKey, JsonWebKeySet, PublicJsonWebKeySet},
|
jwk::{JsonWebKey, PublicJsonWebKeySet},
|
||||||
JsonWebSignatureHeader,
|
JsonWebSignatureHeader,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -133,6 +130,27 @@ impl StaticKeystore {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn to_public_jwks(&self) -> PublicJsonWebKeySet {
|
||||||
|
let rsa = self.rsa_keys.iter().map(|(kid, key)| {
|
||||||
|
let pubkey = RsaPublicKey::from(key);
|
||||||
|
JsonWebKey::new(pubkey.into())
|
||||||
|
.with_kid(kid)
|
||||||
|
.with_use(JsonWebKeyUse::Sig)
|
||||||
|
});
|
||||||
|
|
||||||
|
let es256 = self.es256_keys.iter().map(|(kid, key)| {
|
||||||
|
let pubkey = ecdsa::VerifyingKey::from(key);
|
||||||
|
JsonWebKey::new(pubkey.into())
|
||||||
|
.with_kid(kid)
|
||||||
|
.with_use(JsonWebKeyUse::Sig)
|
||||||
|
.with_alg(JsonWebSignatureAlg::Es256)
|
||||||
|
});
|
||||||
|
|
||||||
|
let keys = rsa.chain(es256).collect();
|
||||||
|
PublicJsonWebKeySet::new(keys)
|
||||||
|
}
|
||||||
|
|
||||||
fn verify_sync(
|
fn verify_sync(
|
||||||
&self,
|
&self,
|
||||||
header: &JsonWebSignatureHeader,
|
header: &JsonWebSignatureHeader,
|
||||||
@@ -366,36 +384,6 @@ impl VerifyingKeystore for StaticKeystore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Service<()> for &StaticKeystore {
|
|
||||||
type Future = Ready<Result<Self::Response, Self::Error>>;
|
|
||||||
type Response = PublicJsonWebKeySet;
|
|
||||||
type Error = Infallible;
|
|
||||||
|
|
||||||
fn poll_ready(&mut self, _cx: &mut std::task::Context<'_>) -> Poll<Result<(), Self::Error>> {
|
|
||||||
Poll::Ready(Ok(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call(&mut self, _req: ()) -> Self::Future {
|
|
||||||
let rsa = self.rsa_keys.iter().map(|(kid, key)| {
|
|
||||||
let pubkey = RsaPublicKey::from(key);
|
|
||||||
JsonWebKey::new(pubkey.into())
|
|
||||||
.with_kid(kid)
|
|
||||||
.with_use(JsonWebKeyUse::Sig)
|
|
||||||
});
|
|
||||||
|
|
||||||
let es256 = self.es256_keys.iter().map(|(kid, key)| {
|
|
||||||
let pubkey = ecdsa::VerifyingKey::from(key);
|
|
||||||
JsonWebKey::new(pubkey.into())
|
|
||||||
.with_kid(kid)
|
|
||||||
.with_use(JsonWebKeyUse::Sig)
|
|
||||||
.with_alg(JsonWebSignatureAlg::Es256)
|
|
||||||
});
|
|
||||||
|
|
||||||
let keys = rsa.chain(es256).collect();
|
|
||||||
std::future::ready(Ok(JsonWebKeySet::new(keys)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@@ -12,15 +12,10 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use std::{collections::HashSet, future::Future, sync::Arc};
|
use std::{collections::HashSet, future::Future};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use futures_util::{
|
|
||||||
future::{Either, MapErr},
|
|
||||||
TryFutureExt,
|
|
||||||
};
|
|
||||||
use mas_iana::jose::JsonWebSignatureAlg;
|
use mas_iana::jose::JsonWebSignatureAlg;
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
use crate::JsonWebSignatureHeader;
|
use crate::JsonWebSignatureHeader;
|
||||||
|
|
||||||
@@ -43,61 +38,3 @@ pub trait VerifyingKeystore {
|
|||||||
fn verify(&self, header: &JsonWebSignatureHeader, msg: &[u8], signature: &[u8])
|
fn verify(&self, header: &JsonWebSignatureHeader, msg: &[u8], signature: &[u8])
|
||||||
-> Self::Future;
|
-> Self::Future;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum EitherError<A, B> {
|
|
||||||
#[error(transparent)]
|
|
||||||
Left(A),
|
|
||||||
#[error(transparent)]
|
|
||||||
Right(B),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<L, R> VerifyingKeystore for Either<L, R>
|
|
||||||
where
|
|
||||||
L: VerifyingKeystore,
|
|
||||||
R: VerifyingKeystore,
|
|
||||||
{
|
|
||||||
type Error = EitherError<L::Error, R::Error>;
|
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
type Future = Either<
|
|
||||||
MapErr<L::Future, fn(L::Error) -> Self::Error>,
|
|
||||||
MapErr<R::Future, fn(R::Error) -> Self::Error>,
|
|
||||||
>;
|
|
||||||
|
|
||||||
fn verify(
|
|
||||||
&self,
|
|
||||||
header: &JsonWebSignatureHeader,
|
|
||||||
msg: &[u8],
|
|
||||||
signature: &[u8],
|
|
||||||
) -> Self::Future {
|
|
||||||
match self {
|
|
||||||
Either::Left(left) => Either::Left(
|
|
||||||
left.verify(header, msg, signature)
|
|
||||||
.map_err(EitherError::Left),
|
|
||||||
),
|
|
||||||
Either::Right(right) => Either::Right(
|
|
||||||
right
|
|
||||||
.verify(header, msg, signature)
|
|
||||||
.map_err(EitherError::Right),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> VerifyingKeystore for Arc<T>
|
|
||||||
where
|
|
||||||
T: VerifyingKeystore,
|
|
||||||
{
|
|
||||||
type Error = T::Error;
|
|
||||||
type Future = T::Future;
|
|
||||||
|
|
||||||
fn verify(
|
|
||||||
&self,
|
|
||||||
header: &JsonWebSignatureHeader,
|
|
||||||
msg: &[u8],
|
|
||||||
signature: &[u8],
|
|
||||||
) -> Self::Future {
|
|
||||||
self.as_ref().verify(header, msg, signature)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -30,8 +30,5 @@ pub use futures_util::future::Either;
|
|||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
jwt::{DecodedJsonWebToken, JsonWebSignatureHeader, JsonWebTokenParts, Jwt, JwtSignatureError},
|
jwt::{DecodedJsonWebToken, JsonWebSignatureHeader, JsonWebTokenParts, Jwt, JwtSignatureError},
|
||||||
keystore::{
|
keystore::{SigningKeystore, StaticKeystore, VerifyingKeystore},
|
||||||
DynamicJwksStore, SharedSecret, SigningKeystore, StaticJwksStore, StaticKeystore,
|
|
||||||
VerifyingKeystore,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user