1
0
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:
Kévin Commaille
2022-09-06 11:42:40 +02:00
committed by Quentin Gliech
parent 36668d9b91
commit 0452ac10e6
4 changed files with 83 additions and 22 deletions

View File

@ -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"

View File

@ -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)?;

View File

@ -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>);