use base64::Engine as _;
use jsonwebtoken::jwk::{Jwk, JwkSet};
use jsonwebtoken::{Algorithm, EncodingKey, Header, encode};
use rsa::RsaPrivateKey;
use rsa::pkcs1::{EncodeRsaPrivateKey, EncodeRsaPublicKey};
use serde::{Deserialize, Serialize};
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};
use crate::test_support::{EMAIL, SUB};
const RUTA_DESCUBRIMIENTO: &str = "/.well-known/openid-configuration";
const RUTA_TOKEN: &str = "/token";
const RUTA_JWKS: &str = "/jwks";
pub(crate) const KID: &str = "kesto-clave-de-prueba";
pub(crate) const KID_AJENO: &str = "kid-que-el-jwks-no-publica";
pub(crate) const ACCESS_TOKEN: &str = "access-token-del-proveedor-distinguible";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct IdToken {
pub iss: String,
pub aud: String,
pub sub: String,
pub email: String,
pub nonce: String,
pub exp: u64,
pub iat: u64,
}
impl IdToken {
pub(crate) fn valido(issuer: &str, client_id: &str, nonce: &str) -> Self {
let ahora = jsonwebtoken::get_current_timestamp();
Self {
iss: issuer.to_owned(),
aud: client_id.to_owned(),
sub: SUB.to_owned(),
email: EMAIL.to_owned(),
nonce: nonce.to_owned(),
exp: ahora + 600,
iat: ahora,
}
}
}
pub(crate) struct ProveedorFalso {
server: MockServer,
pub(crate) issuer: String,
clave: EncodingKey,
clave_no_publicada: EncodingKey,
clave_confusa: EncodingKey,
}
impl ProveedorFalso {
pub(crate) async fn servir() -> Self {
let server = MockServer::start().await;
let issuer = server.uri();
let (clave, clave_publica) = generar_clave();
let (clave_no_publicada, _) = generar_clave();
let documento = serde_json::json!({
"authorization_endpoint": format!("{issuer}/authorize"),
"token_endpoint": format!("{issuer}{RUTA_TOKEN}"),
"jwks_uri": format!("{issuer}{RUTA_JWKS}"),
});
Mock::given(method("GET"))
.and(path(RUTA_DESCUBRIMIENTO))
.respond_with(ResponseTemplate::new(200).set_body_json(documento))
.mount(&server)
.await;
Mock::given(method("GET"))
.and(path(RUTA_JWKS))
.respond_with(ResponseTemplate::new(200).set_body_json(jwks(&clave)))
.mount(&server)
.await;
Self {
server,
issuer,
clave,
clave_no_publicada,
clave_confusa: EncodingKey::from_secret(&clave_publica),
}
}
pub(crate) fn firmar(&self, claims: &IdToken) -> String {
firmar_con(&self.clave, claims)
}
pub(crate) fn firmar_claims(&self, claims: &serde_json::Value) -> String {
let mut cabecera = Header::new(Algorithm::RS256);
cabecera.kid = Some(KID.to_owned());
encode(&cabecera, claims, &self.clave).expect("el ID token de prueba debe firmarse")
}
pub(crate) fn firmar_con_clave_ajena(&self, claims: &IdToken) -> String {
firmar_con(&self.clave_no_publicada, claims)
}
pub(crate) fn firmar_sin_kid(&self, claims: &serde_json::Value) -> String {
let cabecera = Header::new(Algorithm::RS256);
encode(&cabecera, claims, &self.clave).expect("el ID token de prueba debe firmarse")
}
pub(crate) fn firmar_con_kid_ajeno(&self, claims: &serde_json::Value) -> String {
let mut cabecera = Header::new(Algorithm::RS256);
cabecera.kid = Some(KID_AJENO.to_owned());
encode(&cabecera, claims, &self.clave).expect("el ID token de prueba debe firmarse")
}
pub(crate) fn firmar_confundiendo_algoritmo(&self, claims: &serde_json::Value) -> String {
let mut cabecera = Header::new(Algorithm::HS256);
cabecera.kid = Some(KID.to_owned());
encode(&cabecera, claims, &self.clave_confusa).expect("el ID token de prueba debe firmarse")
}
pub(crate) async fn montar_token_endpoint(&self, id_token: &str) {
let cuerpo = serde_json::json!({
"id_token": id_token,
"access_token": ACCESS_TOKEN,
"token_type": "Bearer",
"expires_in": 600,
});
Mock::given(method("POST"))
.and(path(RUTA_TOKEN))
.respond_with(ResponseTemplate::new(200).set_body_json(cuerpo))
.mount(&self.server)
.await;
}
pub(crate) async fn montar_token_endpoint_con_error(&self, estado: u16, cuerpo: &str) {
Mock::given(method("POST"))
.and(path(RUTA_TOKEN))
.respond_with(ResponseTemplate::new(estado).set_body_string(cuerpo))
.mount(&self.server)
.await;
}
pub(crate) async fn peticiones_de_descubrimiento(&self) -> usize {
self.peticiones_a(RUTA_DESCUBRIMIENTO).await
}
pub(crate) async fn peticiones_al_token(&self) -> usize {
self.peticiones_a(RUTA_TOKEN).await
}
pub(crate) async fn peticiones_a_userinfo(&self) -> usize {
self.server
.received_requests()
.await
.unwrap_or_default()
.iter()
.filter(|peticion| peticion.url.path().contains("userinfo"))
.count()
}
async fn peticiones_a(&self, ruta: &str) -> usize {
self.server
.received_requests()
.await
.unwrap_or_default()
.iter()
.filter(|peticion| peticion.url.path() == ruta)
.count()
}
}
fn generar_clave() -> (EncodingKey, Vec<u8>) {
let mut rng = rand::thread_rng();
let privada = RsaPrivateKey::new(&mut rng, 2048)
.expect("la generación de un par RSA de prueba no puede fallar");
let der = privada
.to_pkcs1_der()
.expect("la clave privada de prueba debe codificarse en PKCS#1")
.as_bytes()
.to_vec();
let publica = privada
.to_public_key()
.to_pkcs1_der()
.expect("la clave pública de prueba debe codificarse en PKCS#1")
.as_bytes()
.to_vec();
(EncodingKey::from_rsa_der(&der), publica)
}
fn jwks(clave: &EncodingKey) -> serde_json::Value {
let mut jwk = Jwk::from_encoding_key(clave, Algorithm::RS256)
.expect("la clave pública de prueba debe poder publicarse como JWK");
jwk.common.key_id = Some(KID.to_owned());
serde_json::to_value(JwkSet { keys: vec![jwk] }).expect("el JWKS de prueba debe serializarse")
}
fn firmar_con(clave: &EncodingKey, claims: &IdToken) -> String {
let mut cabecera = Header::new(Algorithm::RS256);
cabecera.kid = Some(KID.to_owned());
encode(&cabecera, claims, clave).expect("el ID token de prueba debe firmarse")
}
pub(crate) fn token_con_alg_none(claims: &serde_json::Value) -> String {
let cabecera = serde_json::json!({ "alg": "none", "kid": KID });
let codificar = |valor: &serde_json::Value| {
base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(valor.to_string())
};
format!("{}.{}.", codificar(&cabecera), codificar(claims))
}