You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-08-07 17:03:01 +03:00
215 lines
8.1 KiB
Rust
215 lines
8.1 KiB
Rust
// 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 der::pem::LineEnding;
|
|
use mas_iana::jose::JsonWebSignatureAlg;
|
|
use mas_jose::{
|
|
jwk::ParametersInfo,
|
|
jwt::{JsonWebSignatureHeader, Jwt},
|
|
};
|
|
use mas_keystore::{JsonWebKey, JsonWebKeySet, Keystore, PrivateKey};
|
|
use rand::SeedableRng;
|
|
|
|
static PASSWORD: &str = "hunter2";
|
|
|
|
/// Generate a test which loads a key, and then tries signing and verifying a
|
|
/// JWT for each available algorithm
|
|
macro_rules! plain_test {
|
|
($name:ident, $kind:ident, $path:literal) => {
|
|
#[test]
|
|
fn $name() {
|
|
let bytes = include_bytes!(concat!("./keys/", $path));
|
|
let key = PrivateKey::load(bytes).unwrap();
|
|
assert!(matches!(key, PrivateKey::$kind(_)), "wrong key type");
|
|
|
|
let algs = key.possible_algs();
|
|
assert_ne!(algs.len(), 0);
|
|
|
|
for alg in algs {
|
|
let header = JsonWebSignatureHeader::new(alg.clone());
|
|
let payload = "hello";
|
|
let signer = key.signing_key_for_alg(alg).unwrap();
|
|
let jwt = Jwt::sign(header, payload, &signer).unwrap();
|
|
let verifier = key.verifying_key_for_alg(alg).unwrap();
|
|
jwt.verify(&verifier).unwrap();
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
/// Generate a test which loads an encrypted key, and then tries signing and
|
|
/// verifying a JWT for each available algorithm
|
|
macro_rules! enc_test {
|
|
($name:ident, $kind:ident, $path:literal) => {
|
|
#[test]
|
|
fn $name() {
|
|
let bytes = include_bytes!(concat!("./keys/", $path));
|
|
let key = PrivateKey::load_encrypted(bytes, PASSWORD).unwrap();
|
|
assert!(matches!(key, PrivateKey::$kind(_)), "wrong key type");
|
|
|
|
let algs = key.possible_algs();
|
|
assert_ne!(algs.len(), 0);
|
|
|
|
for alg in algs {
|
|
let header = JsonWebSignatureHeader::new(alg.clone());
|
|
let payload = "hello";
|
|
let signer = key.signing_key_for_alg(alg).unwrap();
|
|
let jwt = Jwt::sign(header, payload, &signer).unwrap();
|
|
let verifier = key.verifying_key_for_alg(alg).unwrap();
|
|
jwt.verify(&verifier).unwrap();
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
/// Generate a PEM decoding and encoding test
|
|
macro_rules! pem_test {
|
|
($name:ident, $path:literal) => {
|
|
#[test]
|
|
fn $name() {
|
|
let pem = include_str!(concat!("./keys/", $path, ".pem"));
|
|
let key = PrivateKey::load_pem(pem).unwrap();
|
|
let pem2 = key.to_pem(pem_rfc7468::LineEnding::LF).unwrap();
|
|
|
|
assert_eq!(pem, pem2.as_str());
|
|
}
|
|
};
|
|
}
|
|
|
|
/// Generate a DER decoding and encoding test
|
|
macro_rules! der_test {
|
|
($name:ident, $path:literal) => {
|
|
#[test]
|
|
fn $name() {
|
|
let der = include_bytes!(concat!("./keys/", $path, ".der"));
|
|
let key = PrivateKey::load_der(der).unwrap();
|
|
let der2 = key.to_der().unwrap();
|
|
|
|
assert_eq!(der, der2.as_slice());
|
|
}
|
|
};
|
|
}
|
|
|
|
plain_test!(plain_rsa_pkcs1_pem, Rsa, "rsa.pkcs1.pem");
|
|
plain_test!(plain_rsa_pkcs1_der, Rsa, "rsa.pkcs1.der");
|
|
plain_test!(plain_rsa_pkcs8_pem, Rsa, "rsa.pkcs8.pem");
|
|
plain_test!(plain_rsa_pkcs8_der, Rsa, "rsa.pkcs8.der");
|
|
plain_test!(plain_ec_p256_sec1_pem, EcP256, "ec-p256.sec1.pem");
|
|
plain_test!(plain_ec_p256_sec1_der, EcP256, "ec-p256.sec1.der");
|
|
plain_test!(plain_ec_p256_pkcs8_pem, EcP256, "ec-p256.pkcs8.pem");
|
|
plain_test!(plain_ec_p256_pkcs8_der, EcP256, "ec-p256.pkcs8.der");
|
|
plain_test!(plain_ec_p384_sec1_pem, EcP384, "ec-p384.sec1.pem");
|
|
plain_test!(plain_ec_p384_sec1_der, EcP384, "ec-p384.sec1.der");
|
|
plain_test!(plain_ec_p384_pkcs8_pem, EcP384, "ec-p384.pkcs8.pem");
|
|
plain_test!(plain_ec_p384_pkcs8_der, EcP384, "ec-p384.pkcs8.der");
|
|
plain_test!(plain_ec_k256_sec1_pem, EcK256, "ec-k256.sec1.pem");
|
|
plain_test!(plain_ec_k256_sec1_der, EcK256, "ec-k256.sec1.der");
|
|
plain_test!(plain_ec_k256_pkcs8_pem, EcK256, "ec-k256.pkcs8.pem");
|
|
plain_test!(plain_ec_k256_pkcs8_der, EcK256, "ec-k256.pkcs8.der");
|
|
|
|
enc_test!(enc_rsa_pkcs8_pem, Rsa, "rsa.pkcs8.encrypted.pem");
|
|
enc_test!(enc_rsa_pkcs8_der, Rsa, "rsa.pkcs8.encrypted.der");
|
|
enc_test!(enc_ec_p256_pkcs8_pem, EcP256, "ec-p256.pkcs8.encrypted.pem");
|
|
enc_test!(enc_ec_p256_pkcs8_der, EcP256, "ec-p256.pkcs8.encrypted.der");
|
|
enc_test!(enc_ec_p384_pkcs8_pem, EcP384, "ec-p384.pkcs8.encrypted.pem");
|
|
enc_test!(enc_ec_p384_pkcs8_der, EcP384, "ec-p384.pkcs8.encrypted.der");
|
|
enc_test!(enc_ec_k256_pkcs8_pem, EcK256, "ec-k256.pkcs8.encrypted.pem");
|
|
enc_test!(enc_ec_k256_pkcs8_der, EcK256, "ec-k256.pkcs8.encrypted.der");
|
|
|
|
// Test PEM/DER serialization
|
|
pem_test!(serialize_rsa_pkcs1_pem, "rsa.pkcs1");
|
|
der_test!(serialize_rsa_pkcs1_der, "rsa.pkcs1");
|
|
pem_test!(serialize_ec_p256_sec1_pem, "ec-p256.sec1");
|
|
der_test!(serialize_ec_p256_sec1_der, "ec-p256.sec1");
|
|
pem_test!(serialize_ec_p384_sec1_pem, "ec-p384.sec1");
|
|
der_test!(serialize_ec_p384_sec1_der, "ec-p384.sec1");
|
|
pem_test!(serialize_ec_k256_sec1_pem, "ec-k256.sec1");
|
|
der_test!(serialize_ec_k256_sec1_der, "ec-k256.sec1");
|
|
|
|
#[test]
|
|
fn load_encrypted_as_unencrypted_error() {
|
|
let pem = include_str!("./keys/rsa.pkcs8.encrypted.pem");
|
|
assert!(PrivateKey::load_pem(pem).unwrap_err().is_encrypted());
|
|
|
|
let der = include_bytes!("./keys/rsa.pkcs8.encrypted.der");
|
|
assert!(PrivateKey::load_der(der).unwrap_err().is_encrypted());
|
|
}
|
|
|
|
#[test]
|
|
fn load_unencrypted_as_encrypted_error() {
|
|
let pem = include_str!("./keys/rsa.pkcs8.pem");
|
|
assert!(PrivateKey::load_encrypted_pem(pem, PASSWORD)
|
|
.unwrap_err()
|
|
.is_unencrypted());
|
|
|
|
let der = include_bytes!("./keys/rsa.pkcs8.der");
|
|
assert!(PrivateKey::load_encrypted_der(der, PASSWORD)
|
|
.unwrap_err()
|
|
.is_unencrypted());
|
|
}
|
|
|
|
#[allow(clippy::similar_names)]
|
|
#[test]
|
|
fn generate_sign_and_verify() {
|
|
// Use a seeded RNG to keep the snapshot stable
|
|
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(42);
|
|
|
|
let rsa = PrivateKey::generate_rsa(&mut rng).expect("Failed to generate RSA key");
|
|
insta::assert_snapshot!(&*rsa.to_pem(LineEnding::LF).unwrap());
|
|
|
|
let ec_p256 = PrivateKey::generate_ec_p256(&mut rng);
|
|
insta::assert_snapshot!(&*ec_p256.to_pem(LineEnding::LF).unwrap());
|
|
|
|
let ec_p384 = PrivateKey::generate_ec_p384(&mut rng);
|
|
insta::assert_snapshot!(&*ec_p384.to_pem(LineEnding::LF).unwrap());
|
|
|
|
let ec_k256 = PrivateKey::generate_ec_k256(&mut rng);
|
|
insta::assert_snapshot!(&*ec_k256.to_pem(LineEnding::LF).unwrap());
|
|
|
|
// Create a keystore out of the keys
|
|
let keyset = Keystore::new(JsonWebKeySet::new(vec![
|
|
JsonWebKey::new(rsa),
|
|
JsonWebKey::new(ec_p256),
|
|
JsonWebKey::new(ec_p384),
|
|
JsonWebKey::new(ec_k256),
|
|
]));
|
|
|
|
// And extract the public JWKS
|
|
let jwks = keyset.public_jwks();
|
|
insta::assert_yaml_snapshot!(jwks);
|
|
|
|
// Try signing for each supported algorithm
|
|
for alg in [
|
|
JsonWebSignatureAlg::Rs256,
|
|
JsonWebSignatureAlg::Rs384,
|
|
JsonWebSignatureAlg::Rs512,
|
|
JsonWebSignatureAlg::Ps256,
|
|
JsonWebSignatureAlg::Ps384,
|
|
JsonWebSignatureAlg::Ps512,
|
|
JsonWebSignatureAlg::Es256,
|
|
JsonWebSignatureAlg::Es384,
|
|
JsonWebSignatureAlg::Es256K,
|
|
] {
|
|
// Find a matching key and sign with it
|
|
let key = keyset.signing_key_for_algorithm(&alg).unwrap();
|
|
let signer = key.params().signing_key_for_alg(&alg).unwrap();
|
|
let header = JsonWebSignatureHeader::new(alg.clone());
|
|
let token = Jwt::sign_with_rng(&mut rng, header, "", &signer).unwrap();
|
|
insta::assert_snapshot!(format!("jwt_{alg}"), token.as_str());
|
|
|
|
// Then try to verify from the public JWKS
|
|
token.verify_with_jwks(&jwks).unwrap();
|
|
}
|
|
}
|