use jsonwebtoken::{Algorithm, EncodingKey, Header, encode};
use serde::Serialize;
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Clone)]
pub struct OidcSigner {
pub key: EncodingKey,
pub kid: String,
pub algorithm: Algorithm,
}
impl OidcSigner {
pub fn from_rsa_pem(pem: &str, kid: impl Into<String>) -> jsonwebtoken::errors::Result<Self> {
Ok(Self {
key: EncodingKey::from_rsa_pem(pem.as_bytes())?,
kid: kid.into(),
algorithm: Algorithm::RS256,
})
}
pub fn from_x509_pem(pem: &str, kid: impl Into<String>) -> jsonwebtoken::errors::Result<Self> {
let key = EncodingKey::from_rsa_pem(pem.as_bytes())
.or_else(|_| EncodingKey::from_rsa_pem(pem.as_bytes()))?;
Ok(Self {
key,
kid: kid.into(),
algorithm: Algorithm::RS256,
})
}
pub fn sign<T: Serialize>(
&self,
claims: T,
) -> Result<String, jsonwebtoken::errors::Error>
where
T: Serialize,
{
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let exp = now + 3600;
let mut map = serde_json::to_value(&claims)?
.as_object_mut()
.unwrap()
.clone();
if !map.contains_key("exp") {
map.insert("exp".into(), serde_json::json!(exp));
}
if !map.contains_key("iat") {
map.insert("iat".into(), serde_json::json!(now));
}
if !map.contains_key("aud") {
map.insert("aud".into(), serde_json::json!(uuid::Uuid::new_v4().to_string()));
}
if !map.contains_key("iss") {
map.insert("iss".into(), serde_json::json!(uuid::Uuid::new_v4().to_string()));
}
if !map.contains_key("sub") {
map.insert("sub".into(), serde_json::json!(uuid::Uuid::new_v4().to_string()));
}
let mut header = Header::new(self.algorithm);
header.kid = Some(self.kid.clone());
encode(&header, &map, &self.key)
}
}
use rand::rngs::OsRng;
use rsa::{
RsaPrivateKey,
pkcs8::{EncodePrivateKey, EncodePublicKey},
};
pub fn generate_rsa_pkcs8_pair() -> (String, String) {
let mut rng = OsRng;
let private_key = RsaPrivateKey::new(&mut rng, 2048).expect("failed to generate key");
let private_key_pem = private_key
.to_pkcs8_pem(Default::default())
.expect("failed to encode private key");
let public_key = private_key.to_public_key();
let public_key_pem = public_key
.to_public_key_pem(Default::default())
.expect("failed to encode public key");
(private_key_pem.to_string(), public_key_pem)
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::Validator;
use serde_json::json;
use std::time::Duration;
use std::error::Error;
#[test]
fn sign_and_validate() -> Result<(), Box<dyn std::error::Error>> {
let (test_private_pem, test_public_pem) = generate_rsa_pkcs8_pair();
let signer = OidcSigner::from_x509_pem(&test_private_pem, "test-kid")
.expect("failed to create signer");
let token = signer
.sign(
json!({ "sub": "user-123", "role": "admin", "iss": "http://localhost:8080", "aud": "test-audience" }),
)
.unwrap();
let validator = Validator::from_rsa_pem(
"http://localhost:8080".to_string(),
"test-audience".to_string(),
"RS256".to_string(),
&test_public_pem,
)
.unwrap();
let claims: serde_json::Value = validator
.decode_with_iss_alg("http://localhost:8080", "RS256", &token)
.unwrap()
.claims;
assert_eq!(claims.get("sub").and_then(|v| v.as_str()), Some("user-123"));
assert_eq!(claims.get("role").and_then(|v| v.as_str()), Some("admin"));
Ok(())
}
}