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
Move claim hash token function to mas-jose crate
Change the hash function according to the signature algorithm, according to the OpendID Connect spec.
This commit is contained in:
committed by
Quentin Gliech
parent
36668d9b91
commit
0452ac10e6
@ -39,11 +39,7 @@ serde_urlencoded = "0.7.1"
|
||||
# Password hashing
|
||||
argon2 = { version = "0.4.1", features = ["password-hash"] }
|
||||
|
||||
# Crypto, hashing and signing stuff
|
||||
sha2 = "0.10.5"
|
||||
|
||||
# Various data types and utilities
|
||||
data-encoding = "2.3.2"
|
||||
chrono = { version = "0.4.22", features = ["serde"] }
|
||||
url = { version = "2.2.2", features = ["serde"] }
|
||||
mime = "0.3.16"
|
||||
|
@ -17,14 +17,13 @@ use std::collections::HashMap;
|
||||
use anyhow::Context;
|
||||
use axum::{extract::State, response::IntoResponse, Json};
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use data_encoding::BASE64URL_NOPAD;
|
||||
use headers::{CacheControl, HeaderMap, HeaderMapExt, Pragma};
|
||||
use hyper::StatusCode;
|
||||
use mas_axum_utils::client_authorization::{ClientAuthorization, CredentialsVerificationError};
|
||||
use mas_data_model::{AuthorizationGrantStage, Client, TokenType};
|
||||
use mas_iana::jose::JsonWebSignatureAlg;
|
||||
use mas_jose::{
|
||||
claims::{self, ClaimError},
|
||||
claims::{self, hash_token, ClaimError},
|
||||
constraints::Constrainable,
|
||||
jwt::{JsonWebSignatureHeader, Jwt, JwtSignatureError},
|
||||
};
|
||||
@ -54,7 +53,6 @@ use oauth2_types::{
|
||||
use rand::thread_rng;
|
||||
use serde::Serialize;
|
||||
use serde_with::{serde_as, skip_serializing_none};
|
||||
use sha2::{Digest, Sha256};
|
||||
use sqlx::{PgPool, Postgres, Transaction};
|
||||
use thiserror::Error;
|
||||
use tracing::debug;
|
||||
@ -228,16 +226,6 @@ pub(crate) async fn post(
|
||||
Ok((headers, Json(reply)))
|
||||
}
|
||||
|
||||
fn hash<H: Digest>(mut hasher: H, token: &str) -> anyhow::Result<String> {
|
||||
hasher.update(token);
|
||||
let hash = hasher.finalize();
|
||||
// Left-most 128bit
|
||||
let bits = hash
|
||||
.get(..16)
|
||||
.context("failed to get first 128 bits of hash")?;
|
||||
Ok(BASE64URL_NOPAD.encode(bits))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
async fn authorization_code_grant(
|
||||
grant: &AuthorizationCodeGrant,
|
||||
@ -343,9 +331,6 @@ async fn authorization_code_grant(
|
||||
claims::AUTH_TIME.insert(&mut claims, last_authentication.created_at)?;
|
||||
}
|
||||
|
||||
claims::AT_HASH.insert(&mut claims, hash(Sha256::new(), &access_token_str)?)?;
|
||||
claims::C_HASH.insert(&mut claims, hash(Sha256::new(), &grant.code)?)?;
|
||||
|
||||
let alg = client
|
||||
.id_token_signed_response_alg
|
||||
.unwrap_or(JsonWebSignatureAlg::Rs256);
|
||||
@ -353,6 +338,9 @@ async fn authorization_code_grant(
|
||||
.signing_key_for_algorithm(alg)
|
||||
.context("no suitable key found")?;
|
||||
|
||||
claims::AT_HASH.insert(&mut claims, hash_token(alg, &access_token_str)?)?;
|
||||
claims::C_HASH.insert(&mut claims, hash_token(alg, &grant.code)?)?;
|
||||
|
||||
let header = JsonWebSignatureHeader::new(alg)
|
||||
.with_kid(key.kid().context("key has no `kid` for some reason")?);
|
||||
let signer = key.params().signing_key_for_alg(alg)?;
|
||||
|
@ -14,7 +14,11 @@
|
||||
|
||||
use std::{collections::HashMap, marker::PhantomData, ops::Deref};
|
||||
|
||||
use anyhow::Context;
|
||||
use base64ct::{Base64UrlUnpadded, Encoding};
|
||||
use mas_iana::jose::JsonWebSignatureAlg;
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256, Sha384, Sha512};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
@ -235,6 +239,81 @@ impl From<&TimeOptions> for TimeNotBefore {
|
||||
}
|
||||
}
|
||||
|
||||
/// Hash the given token with the given algorithm for an ID Token claim.
|
||||
///
|
||||
/// According to the [OpenID Connect Core 1.0 specification].
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the algorithm is not supported.
|
||||
///
|
||||
/// [OpenID Connect Core 1.0 specification]: https://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken
|
||||
pub fn hash_token(alg: JsonWebSignatureAlg, token: &str) -> anyhow::Result<String> {
|
||||
let bits = match alg {
|
||||
JsonWebSignatureAlg::Hs256
|
||||
| JsonWebSignatureAlg::Rs256
|
||||
| JsonWebSignatureAlg::Es256
|
||||
| JsonWebSignatureAlg::Ps256
|
||||
| JsonWebSignatureAlg::Es256K => {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(token);
|
||||
let hash = hasher.finalize();
|
||||
// Left-most half
|
||||
hash.get(..16).map(ToOwned::to_owned)
|
||||
}
|
||||
JsonWebSignatureAlg::Hs384
|
||||
| JsonWebSignatureAlg::Rs384
|
||||
| JsonWebSignatureAlg::Es384
|
||||
| JsonWebSignatureAlg::Ps384 => {
|
||||
let mut hasher = Sha384::new();
|
||||
hasher.update(token);
|
||||
let hash = hasher.finalize();
|
||||
// Left-most half
|
||||
hash.get(..24).map(ToOwned::to_owned)
|
||||
}
|
||||
JsonWebSignatureAlg::Hs512
|
||||
| JsonWebSignatureAlg::Rs512
|
||||
| JsonWebSignatureAlg::Es512
|
||||
| JsonWebSignatureAlg::Ps512 => {
|
||||
let mut hasher = Sha512::new();
|
||||
hasher.update(token);
|
||||
let hash = hasher.finalize();
|
||||
// Left-most half
|
||||
hash.get(..32).map(ToOwned::to_owned)
|
||||
}
|
||||
JsonWebSignatureAlg::EdDsa | JsonWebSignatureAlg::None => {
|
||||
return Err(anyhow::anyhow!("unsupported algorithm for hashing"))
|
||||
}
|
||||
}
|
||||
.context("failed to get first half of hash")?;
|
||||
|
||||
Ok(Base64UrlUnpadded::encode_string(&bits))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TokenHash<'a> {
|
||||
alg: JsonWebSignatureAlg,
|
||||
token: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> TokenHash<'a> {
|
||||
/// Creates a new `TokenHash` validator for the given algorithm and token.
|
||||
#[must_use]
|
||||
pub fn new(alg: JsonWebSignatureAlg, token: &'a str) -> Self {
|
||||
Self { alg, token }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Validator<String> for TokenHash<'a> {
|
||||
fn validate(&self, value: &String) -> Result<(), anyhow::Error> {
|
||||
if hash_token(self.alg, self.token)? == *value {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow::anyhow!("hashes don't match"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
|
||||
#[serde(transparent)]
|
||||
pub struct Timestamp(#[serde(with = "chrono::serde::ts_seconds")] chrono::DateTime<chrono::Utc>);
|
||||
|
Reference in New Issue
Block a user