1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-11-20 12:02:22 +03:00

New JWT/JOSE crate

Still WIP, needs to handle time related claims
This commit is contained in:
Quentin Gliech
2022-01-04 22:28:00 +01:00
parent 694a0bff03
commit f933ace007
20 changed files with 1942 additions and 726 deletions

View File

@@ -12,16 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use mas_config::OAuth2Config;
use warp::{filters::BoxedFilter, Filter, Reply};
use std::sync::Arc;
pub(super) fn filter(config: &OAuth2Config) -> BoxedFilter<(Box<dyn Reply>,)> {
let jwks = config.keys.to_public_jwks();
use mas_jose::{ExportJwks, StaticKeystore};
use warp::{filters::BoxedFilter, Filter, Rejection, Reply};
pub(super) fn filter(key_store: &Arc<StaticKeystore>) -> BoxedFilter<(Box<dyn Reply>,)> {
let key_store = key_store.clone();
warp::path!("oauth2" / "keys.json")
.and(warp::get().map(move || {
let ret: Box<dyn Reply> = Box::new(warp::reply::json(&jwks));
ret
}))
.and(warp::get().map(move || key_store.clone()).and_then(get))
.boxed()
}
async fn get(key_store: Arc<StaticKeystore>) -> Result<Box<dyn Reply>, Rejection> {
let jwks = key_store.export_jwks().await;
Ok(Box::new(warp::reply::json(&jwks)))
}

View File

@@ -12,8 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::sync::Arc;
use hyper::Method;
use mas_config::{CookiesConfig, OAuth2Config};
use mas_jose::StaticKeystore;
use mas_templates::Templates;
use mas_warp_utils::filters::cors::cors;
use sqlx::PgPool;
@@ -36,15 +39,16 @@ use self::{
pub fn filter(
pool: &PgPool,
templates: &Templates,
key_store: &Arc<StaticKeystore>,
oauth2_config: &OAuth2Config,
cookies_config: &CookiesConfig,
) -> BoxedFilter<(impl Reply,)> {
let discovery = discovery(oauth2_config);
let keys = keys(oauth2_config);
let keys = keys(key_store);
let authorization = authorization(pool, templates, oauth2_config, cookies_config);
let userinfo = userinfo(pool, oauth2_config);
let introspection = introspection(pool, oauth2_config);
let token = token(pool, oauth2_config);
let token = token(pool, key_store, oauth2_config);
let filter = discovery
.or(keys)

View File

@@ -12,14 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::sync::Arc;
use anyhow::Context;
use chrono::{DateTime, Duration, Utc};
use data_encoding::BASE64URL_NOPAD;
use headers::{CacheControl, Pragma};
use hyper::StatusCode;
use jwt_compact::{Claims, Header, TimeOptions};
use mas_config::{KeySet, OAuth2ClientConfig, OAuth2Config};
use mas_config::{OAuth2ClientConfig, OAuth2Config};
use mas_data_model::{AuthorizationGrantStage, TokenType};
use mas_jose::{DecodedJsonWebToken, JsonWebSignatureAlgorithm, SigningKeystore, StaticKeystore};
use mas_storage::{
oauth2::{
access_token::{add_access_token, revoke_access_token},
@@ -30,7 +32,7 @@ use mas_storage::{
};
use mas_warp_utils::{
errors::WrapError,
filters::{client::client_authentication, database::connection, with_keys},
filters::{client::client_authentication, database::connection},
reply::with_typed_header,
};
use oauth2_types::{
@@ -89,7 +91,12 @@ where
Err(Error { json, status }.into())
}
pub fn filter(pool: &PgPool, oauth2_config: &OAuth2Config) -> BoxedFilter<(Box<dyn Reply>,)> {
pub fn filter(
pool: &PgPool,
key_store: &Arc<StaticKeystore>,
oauth2_config: &OAuth2Config,
) -> BoxedFilter<(Box<dyn Reply>,)> {
let key_store = key_store.clone();
let audience = oauth2_config
.issuer
.join("/oauth2/token")
@@ -101,7 +108,7 @@ pub fn filter(pool: &PgPool, oauth2_config: &OAuth2Config) -> BoxedFilter<(Box<d
.and(
warp::post()
.and(client_authentication(oauth2_config, audience))
.and(with_keys(oauth2_config))
.and(warp::any().map(move || key_store.clone()))
.and(warp::any().map(move || issuer.clone()))
.and(connection(pool))
.and_then(token)
@@ -123,13 +130,14 @@ async fn token(
_auth: ClientAuthenticationMethod,
client: OAuth2ClientConfig,
req: AccessTokenRequest,
keys: KeySet,
key_store: Arc<StaticKeystore>,
issuer: Url,
mut conn: PoolConnection<Postgres>,
) -> Result<Box<dyn Reply>, Rejection> {
let reply = match req {
AccessTokenRequest::AuthorizationCode(grant) => {
let reply = authorization_code_grant(&grant, &client, &keys, issuer, &mut conn).await?;
let reply =
authorization_code_grant(&grant, &client, &key_store, issuer, &mut conn).await?;
json(&reply)
}
AccessTokenRequest::RefreshToken(grant) => {
@@ -160,7 +168,7 @@ fn hash<H: Digest>(mut hasher: H, token: &str) -> anyhow::Result<String> {
async fn authorization_code_grant(
grant: &AuthorizationCodeGrant,
client: &OAuth2ClientConfig,
keys: &KeySet,
key_store: &StaticKeystore,
issuer: Url,
conn: &mut PoolConnection<Postgres>,
) -> Result<AccessTokenResponse, Rejection> {
@@ -245,9 +253,8 @@ async fn authorization_code_grant(
.wrap_error()?;
let id_token = if session.scope.contains(&OPENID) {
let header = Header::default();
let options = TimeOptions::default();
let claims = Claims::new(CustomClaims {
// TODO: time-related claims
let claims = CustomClaims {
issuer,
subject: browser_session.user.sub.clone(),
audiences: vec![client.client_id.clone()],
@@ -258,15 +265,15 @@ async fn authorization_code_grant(
.map(|a| a.created_at),
at_hash: hash(Sha256::new(), &access_token_str).wrap_error()?,
c_hash: hash(Sha256::new(), &grant.code).wrap_error()?,
})
.set_duration_and_issuance(&options, Duration::minutes(30));
let id_token = keys
.token(mas_config::Algorithm::Rs256, header, claims)
};
let header = key_store
.prepare_header(JsonWebSignatureAlgorithm::Rs256)
.await
.context("could not sign ID token")
.wrap_error()?;
let id_token = DecodedJsonWebToken::new(header, claims);
let id_token = id_token.sign(key_store).await.wrap_error()?;
Some(id_token)
Some(id_token.serialize())
} else {
None
};