use crate::base64::base64url_encode;
use crate::claims::Claims;
use crate::error::Result;
use crate::pem;
use wasm_web_crypto::{
Algorithm as WasmAlgorithm, CryptoKey, Hash, KeyFormat, KeyUsage, NamedCurve, SubtleCrypto,
};
#[derive(Debug, Clone, Copy)]
pub enum Algorithm {
Rs256,
Es256,
Hs256,
}
impl Algorithm {
fn as_jwt_str(&self) -> &'static str {
match self {
Self::Rs256 => "RS256",
Self::Es256 => "ES256",
Self::Hs256 => "HS256",
}
}
fn wasm_algorithm(&self) -> WasmAlgorithm {
match self {
Self::Rs256 => WasmAlgorithm::RsassaPkcs1v15 { hash: Hash::Sha256 },
Self::Es256 => WasmAlgorithm::Ecdsa {
hash: Hash::Sha256,
named_curve: NamedCurve::P256,
},
Self::Hs256 => WasmAlgorithm::Hmac {
hash: Hash::Sha256,
length: None,
},
}
}
fn key_format(&self) -> KeyFormat {
match self {
Self::Rs256 | Self::Es256 => KeyFormat::Pkcs8,
Self::Hs256 => KeyFormat::Raw,
}
}
}
pub struct JwtSigner {
algorithm: Algorithm,
key: CryptoKey,
subtle: SubtleCrypto,
}
impl JwtSigner {
pub async fn new(algorithm: Algorithm, key_data: &[u8]) -> Result<Self> {
let der = match algorithm {
Algorithm::Rs256 | Algorithm::Es256 => pem::pem_to_der(key_data)?,
Algorithm::Hs256 => key_data.to_vec(),
};
let subtle = SubtleCrypto::new()?;
let key = subtle
.import_key(
algorithm.key_format(),
&der,
&algorithm.wasm_algorithm(),
false,
&[KeyUsage::Sign],
)
.await?;
Ok(Self {
algorithm,
key,
subtle,
})
}
pub async fn sign(&self, claims: &Claims) -> Result<String> {
let header = format!(r#"{{"alg":"{}","typ":"JWT"}}"#, self.algorithm.as_jwt_str());
let payload = serde_json::to_vec(claims)?;
let signing_input = format!(
"{}.{}",
base64url_encode(header.as_bytes()),
base64url_encode(&payload),
);
let signature = self
.subtle
.sign(
&self.algorithm.wasm_algorithm(),
&self.key,
signing_input.as_bytes(),
)
.await?;
Ok(format!(
"{}.{}",
signing_input,
base64url_encode(signature.to_bytes()),
))
}
}