You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-07-06 05:42:30 +03:00
174 lines
6.0 KiB
Rust
174 lines
6.0 KiB
Rust
// Copyright 2022 Kévin Commaille.
|
|
//
|
|
// 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::HashMap;
|
|
|
|
use chrono::{DateTime, Duration, Utc};
|
|
use mas_iana::{jose::JsonWebSignatureAlg, oauth::OAuthClientAuthenticationMethod};
|
|
use mas_jose::{
|
|
claims::{self, hash_token},
|
|
constraints::Constrainable,
|
|
jwk::PublicJsonWebKeySet,
|
|
jwt::{JsonWebSignatureHeader, Jwt},
|
|
};
|
|
use mas_keystore::{JsonWebKey, JsonWebKeySet, Keystore, PrivateKey};
|
|
use mas_oidc_client::{
|
|
http_service::{hyper::hyper_service, HttpService},
|
|
types::{
|
|
client_credentials::{ClientCredentials, JwtSigningFn, JwtSigningMethod},
|
|
IdToken,
|
|
},
|
|
};
|
|
use rand::{
|
|
distributions::{Alphanumeric, DistString},
|
|
SeedableRng,
|
|
};
|
|
use url::Url;
|
|
use wiremock::MockServer;
|
|
|
|
mod requests;
|
|
mod types;
|
|
|
|
const REDIRECT_URI: &str = "http://localhost/";
|
|
const CLIENT_ID: &str = "client!+ID";
|
|
const CLIENT_SECRET: &str = "SECRET?%Gclient";
|
|
const REQUEST_URI: &str = "REQUESTur1";
|
|
const AUTHORIZATION_CODE: &str = "authC0D3";
|
|
const CODE_VERIFIER: &str = "cODEv3R1f1ER";
|
|
const NONCE: &str = "No0o0o0once";
|
|
const ACCESS_TOKEN: &str = "AccessToken1";
|
|
const REFRESH_TOKEN: &str = "RefreshToken1";
|
|
const SUBJECT_IDENTIFIER: &str = "SubjectID";
|
|
const ID_TOKEN_SIGNING_ALG: JsonWebSignatureAlg = JsonWebSignatureAlg::Rs256;
|
|
|
|
fn now() -> DateTime<Utc> {
|
|
#[allow(clippy::disallowed_methods)]
|
|
Utc::now()
|
|
}
|
|
|
|
async fn init_test() -> (HttpService, MockServer, Url) {
|
|
let http_service = hyper_service();
|
|
let mock_server = MockServer::start().await;
|
|
let issuer = Url::parse(&mock_server.uri()).expect("Couldn't parse URL");
|
|
|
|
(http_service, mock_server, issuer)
|
|
}
|
|
|
|
/// Generate a keystore with a single key for the given algorithm.
|
|
fn keystore(alg: &JsonWebSignatureAlg) -> Keystore {
|
|
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(42);
|
|
|
|
let private_key = match alg {
|
|
JsonWebSignatureAlg::Rs256
|
|
| JsonWebSignatureAlg::Rs384
|
|
| JsonWebSignatureAlg::Rs512
|
|
| JsonWebSignatureAlg::Ps256
|
|
| JsonWebSignatureAlg::Ps384
|
|
| JsonWebSignatureAlg::Ps512 => PrivateKey::generate_rsa(&mut rng).unwrap(),
|
|
JsonWebSignatureAlg::Es256 => PrivateKey::generate_ec_p256(&mut rng),
|
|
JsonWebSignatureAlg::Es384 => PrivateKey::generate_ec_p384(&mut rng),
|
|
_ => unimplemented!(),
|
|
};
|
|
|
|
let jwk = JsonWebKey::new(private_key).with_kid(Alphanumeric.sample_string(&mut rng, 10));
|
|
|
|
Keystore::new(JsonWebKeySet::new(vec![jwk]))
|
|
}
|
|
|
|
/// Generate an ID token.
|
|
fn id_token(issuer: &str) -> (IdToken, PublicJsonWebKeySet) {
|
|
let signing_alg = ID_TOKEN_SIGNING_ALG;
|
|
|
|
let keystore = keystore(&signing_alg);
|
|
let mut claims = HashMap::new();
|
|
let now = now();
|
|
|
|
claims::ISS.insert(&mut claims, issuer.to_string()).unwrap();
|
|
claims::SUB
|
|
.insert(&mut claims, SUBJECT_IDENTIFIER.to_owned())
|
|
.unwrap();
|
|
claims::AUD
|
|
.insert(&mut claims, CLIENT_ID.to_owned())
|
|
.unwrap();
|
|
claims::NONCE.insert(&mut claims, NONCE.to_owned()).unwrap();
|
|
|
|
claims::IAT.insert(&mut claims, now).unwrap();
|
|
claims::EXP
|
|
.insert(&mut claims, now + Duration::hours(1))
|
|
.unwrap();
|
|
|
|
claims::AT_HASH
|
|
.insert(&mut claims, hash_token(&signing_alg, ACCESS_TOKEN).unwrap())
|
|
.unwrap();
|
|
claims::C_HASH
|
|
.insert(
|
|
&mut claims,
|
|
hash_token(&signing_alg, AUTHORIZATION_CODE).unwrap(),
|
|
)
|
|
.unwrap();
|
|
|
|
let key = keystore.signing_key_for_algorithm(&signing_alg).unwrap();
|
|
let signer = key.params().signing_key_for_alg(&signing_alg).unwrap();
|
|
let header = JsonWebSignatureHeader::new(signing_alg).with_kid(key.kid().unwrap());
|
|
let id_token = Jwt::sign(header, claims, &signer).unwrap();
|
|
|
|
(id_token, keystore.public_jwks())
|
|
}
|
|
|
|
/// Generate client credentials for the given authentication method.
|
|
fn client_credentials(
|
|
auth_method: OAuthClientAuthenticationMethod,
|
|
issuer: &Url,
|
|
custom_signing: Option<Box<JwtSigningFn>>,
|
|
) -> ClientCredentials {
|
|
match auth_method {
|
|
OAuthClientAuthenticationMethod::None => ClientCredentials::None {
|
|
client_id: CLIENT_ID.to_owned(),
|
|
},
|
|
OAuthClientAuthenticationMethod::ClientSecretPost => ClientCredentials::ClientSecretPost {
|
|
client_id: CLIENT_ID.to_owned(),
|
|
client_secret: CLIENT_SECRET.to_owned(),
|
|
},
|
|
OAuthClientAuthenticationMethod::ClientSecretBasic => {
|
|
ClientCredentials::ClientSecretBasic {
|
|
client_id: CLIENT_ID.to_owned(),
|
|
client_secret: CLIENT_SECRET.to_owned(),
|
|
}
|
|
}
|
|
OAuthClientAuthenticationMethod::ClientSecretJwt => ClientCredentials::ClientSecretJwt {
|
|
client_id: CLIENT_ID.to_owned(),
|
|
client_secret: CLIENT_SECRET.to_owned(),
|
|
signing_algorithm: JsonWebSignatureAlg::Hs256,
|
|
token_endpoint: issuer.join("token").unwrap(),
|
|
},
|
|
OAuthClientAuthenticationMethod::PrivateKeyJwt => {
|
|
let signing_algorithm = JsonWebSignatureAlg::Es256;
|
|
|
|
let jwt_signing_method = if let Some(signing_fn) = custom_signing {
|
|
JwtSigningMethod::with_custom_signing_method(signing_fn)
|
|
} else {
|
|
JwtSigningMethod::with_keystore(keystore(&signing_algorithm))
|
|
};
|
|
|
|
ClientCredentials::PrivateKeyJwt {
|
|
client_id: CLIENT_ID.to_owned(),
|
|
jwt_signing_method,
|
|
signing_algorithm,
|
|
token_endpoint: issuer.join("token").unwrap(),
|
|
}
|
|
}
|
|
_ => unimplemented!(),
|
|
}
|
|
}
|