use std::fmt;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use base64::Engine as _;
#[cfg(feature = "jwt-p256")]
use p256::ecdsa::signature::{Signer as _, Verifier as _};
#[cfg(feature = "jwt-p256")]
use p256::ecdsa::{Signature, SigningKey, VerifyingKey};
#[cfg(feature = "jwt-pkcs8")]
use p256::pkcs8::{DecodePrivateKey as _, EncodePrivateKey as _};
#[cfg(feature = "jwt-p256")]
use p256::SecretKey;
use serde::{Deserialize, Serialize};
use sha2::{Digest as _, Sha256};
#[cfg(feature = "jwt-p256")]
#[cfg_attr(docsrs, doc(cfg(feature = "jwt-p256")))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KeyError(String);
#[cfg(feature = "jwt-p256")]
impl fmt::Display for KeyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "signing key error: {}", self.0)
}
}
#[cfg(feature = "jwt-p256")]
impl std::error::Error for KeyError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct JwtError(String);
impl fmt::Display for JwtError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "JWT signing error: {}", self.0)
}
}
impl std::error::Error for JwtError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SignerError(String);
impl SignerError {
pub fn new(message: impl Into<String>) -> Self {
SignerError(message.into())
}
}
impl fmt::Display for SignerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ES256 signer error: {}", self.0)
}
}
impl std::error::Error for SignerError {}
#[cfg(feature = "jwt")]
#[cfg_attr(docsrs, doc(cfg(feature = "jwt")))]
pub trait Es256Signer: Send + Sync {
fn sign(
&self,
signing_input: &[u8],
) -> impl Future<Output = Result<[u8; 64], SignerError>> + Send;
fn public_jwk(&self) -> Jwk;
}
#[cfg(feature = "jwt")]
impl<T: Es256Signer + ?Sized> Es256Signer for Arc<T> {
fn sign(
&self,
signing_input: &[u8],
) -> impl Future<Output = Result<[u8; 64], SignerError>> + Send {
(**self).sign(signing_input)
}
fn public_jwk(&self) -> Jwk {
(**self).public_jwk()
}
}
#[cfg(feature = "jwt")]
#[cfg_attr(docsrs, doc(cfg(feature = "jwt")))]
pub trait Es256Verifier: Send + Sync {
fn verify(&self, key: &PublicJwk, signing_input: &[u8], signature: &[u8]) -> bool;
}
#[cfg(feature = "jwt")]
trait DynEs256Signer: Send + Sync {
fn dyn_sign<'a>(
&'a self,
signing_input: &'a [u8],
) -> Pin<Box<dyn Future<Output = Result<[u8; 64], SignerError>> + Send + 'a>>;
}
#[cfg(feature = "jwt")]
impl<T: Es256Signer> DynEs256Signer for T {
fn dyn_sign<'a>(
&'a self,
signing_input: &'a [u8],
) -> Pin<Box<dyn Future<Output = Result<[u8; 64], SignerError>> + Send + 'a>> {
Box::pin(self.sign(signing_input))
}
}
#[cfg(feature = "jwt-p256")]
#[cfg_attr(docsrs, doc(cfg(feature = "jwt-p256")))]
#[derive(Clone)]
pub struct EcdsaP256Key {
kid: String,
signing: SigningKey,
}
#[cfg(feature = "jwt-p256")]
impl EcdsaP256Key {
pub fn from_scalar_bytes(kid: impl Into<String>, scalar: &[u8]) -> Result<Self, KeyError> {
if scalar.len() != 32 {
return Err(KeyError(
"a P-256 private scalar is exactly 32 bytes".into(),
));
}
let secret = SecretKey::from_slice(scalar)
.map_err(|_| KeyError("not a valid P-256 private scalar".into()))?;
Ok(EcdsaP256Key {
kid: kid.into(),
signing: SigningKey::from(&secret),
})
}
#[cfg(feature = "jwt-pkcs8")]
#[cfg_attr(docsrs, doc(cfg(feature = "jwt-pkcs8")))]
pub fn from_pkcs8_der(kid: impl Into<String>, der: &[u8]) -> Result<Self, KeyError> {
let secret = SecretKey::from_pkcs8_der(der)
.map_err(|_| KeyError("not a valid PKCS#8 P-256 private key".into()))?;
Ok(EcdsaP256Key {
kid: kid.into(),
signing: SigningKey::from(&secret),
})
}
pub fn generate(kid: impl Into<String>) -> Self {
let kid = kid.into();
loop {
let mut buf = [0u8; 32];
getrandom::fill(&mut buf).expect("OS randomness for OAuth artifacts");
if let Ok(key) = Self::from_scalar_bytes(kid.clone(), &buf) {
return key;
}
}
}
#[cfg(feature = "jwt-pkcs8")]
#[cfg_attr(docsrs, doc(cfg(feature = "jwt-pkcs8")))]
pub fn to_pkcs8_der(&self) -> Result<Vec<u8>, KeyError> {
let doc = SecretKey::from(&self.signing)
.to_pkcs8_der()
.map_err(|_| KeyError("PKCS#8 encoding failed".into()))?;
Ok(doc.as_bytes().to_vec())
}
pub fn kid(&self) -> &str {
&self.kid
}
pub fn public_jwk(&self) -> Jwk {
let point = self.signing.verifying_key().to_encoded_point(false);
let x = point.x().expect("uncompressed point has an x coordinate");
let y = point.y().expect("uncompressed point has a y coordinate");
Jwk {
kty: "EC",
crv: "P-256",
x: URL_SAFE_NO_PAD.encode(x),
y: URL_SAFE_NO_PAD.encode(y),
kid: self.kid.clone(),
use_: "sig",
alg: "ES256",
}
}
fn sign_es256(&self, message: &[u8]) -> Result<[u8; 64], JwtError> {
let signature: Signature = self
.signing
.try_sign(message)
.map_err(|_| JwtError("ECDSA signing failed".into()))?;
let bytes = signature.to_bytes();
let mut out = [0u8; 64];
out.copy_from_slice(&bytes);
Ok(out)
}
}
#[cfg(feature = "jwt-p256")]
impl fmt::Debug for EcdsaP256Key {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EcdsaP256Key")
.field("kid", &self.kid)
.field("private_key", &"<redacted>")
.finish()
}
}
#[cfg(feature = "jwt-p256")]
impl PartialEq for EcdsaP256Key {
fn eq(&self, other: &Self) -> bool {
self.kid == other.kid
&& self
.signing
.verifying_key()
.to_encoded_point(false)
.as_bytes()
== other
.signing
.verifying_key()
.to_encoded_point(false)
.as_bytes()
}
}
#[cfg(feature = "jwt-p256")]
impl Eq for EcdsaP256Key {}
#[cfg(feature = "jwt-p256")]
impl Es256Signer for EcdsaP256Key {
fn sign(
&self,
signing_input: &[u8],
) -> impl Future<Output = Result<[u8; 64], SignerError>> + Send {
let signed = self.sign_es256(signing_input).map_err(|e| SignerError(e.0));
async move { signed }
}
fn public_jwk(&self) -> Jwk {
EcdsaP256Key::public_jwk(self)
}
}
#[cfg(feature = "jwt-p256")]
#[cfg_attr(docsrs, doc(cfg(feature = "jwt-p256")))]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct P256Verifier;
#[cfg(feature = "jwt-p256")]
impl Es256Verifier for P256Verifier {
fn verify(&self, key: &PublicJwk, signing_input: &[u8], signature: &[u8]) -> bool {
verify_es256(key, signing_input, signature)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct Jwk {
pub kty: &'static str,
pub crv: &'static str,
pub x: String,
pub y: String,
pub kid: String,
#[serde(rename = "use")]
pub use_: &'static str,
pub alg: &'static str,
}
impl Jwk {
pub fn to_public_jwk(&self) -> PublicJwk {
PublicJwk {
kty: self.kty.to_string(),
crv: self.crv.to_string(),
x: self.x.clone(),
y: self.y.clone(),
kid: Some(self.kid.clone()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct Jwks {
pub keys: Vec<Jwk>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(untagged)]
pub enum Audience {
One(String),
Many(Vec<String>),
}
impl Audience {
pub fn names_a_resource_server(&self) -> bool {
match self {
Audience::One(one) => !one.is_empty(),
Audience::Many(many) => !many.is_empty() && many.iter().all(|a| !a.is_empty()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[non_exhaustive]
pub struct AccessTokenClaims {
pub iss: String,
pub exp: u64,
pub aud: Audience,
pub sub: String,
pub client_id: String,
pub iat: u64,
pub jti: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub scope: Option<String>,
#[cfg(feature = "rar")]
#[serde(
default,
skip_serializing_if = "crate::rar::AuthorizationDetails::is_empty"
)]
pub authorization_details: crate::rar::AuthorizationDetails,
#[cfg(feature = "consent")]
#[cfg_attr(docsrs, doc(cfg(feature = "consent")))]
#[serde(skip_serializing_if = "Option::is_none")]
pub auth_time: Option<u64>,
#[cfg(feature = "consent")]
#[cfg_attr(docsrs, doc(cfg(feature = "consent")))]
#[serde(skip_serializing_if = "Option::is_none")]
pub acr: Option<String>,
#[cfg(any(feature = "dpop", feature = "mtls"))]
#[serde(skip_serializing_if = "Option::is_none")]
pub cnf: Option<crate::token::Confirmation>,
#[cfg(feature = "token-exchange")]
#[cfg_attr(docsrs, doc(cfg(feature = "token-exchange")))]
#[serde(skip_serializing_if = "Option::is_none")]
pub act: Option<crate::token_exchange::ActClaim>,
}
impl AccessTokenClaims {
#[allow(clippy::too_many_arguments)]
pub fn new(
iss: impl Into<String>,
exp: u64,
aud: Audience,
sub: impl Into<String>,
client_id: impl Into<String>,
iat: u64,
jti: impl Into<String>,
) -> Self {
AccessTokenClaims {
iss: iss.into(),
exp,
aud,
sub: sub.into(),
client_id: client_id.into(),
iat,
jti: jti.into(),
scope: None,
#[cfg(feature = "rar")]
authorization_details: crate::rar::AuthorizationDetails::none(),
#[cfg(feature = "consent")]
auth_time: None,
#[cfg(feature = "consent")]
acr: None,
#[cfg(any(feature = "dpop", feature = "mtls"))]
cnf: None,
#[cfg(feature = "token-exchange")]
act: None,
}
}
}
#[derive(Clone)]
pub struct JwtConfig {
signer: Arc<dyn DynEs256Signer>,
active: Jwk,
retired: Vec<Jwk>,
audience: Audience,
jwks_uri: Option<String>,
encoded_header: Box<str>,
}
fn encoded_jose_header(kid: &str) -> Box<str> {
let mut json = String::with_capacity(40 + kid.len());
json.push_str(r#"{"alg":"ES256","typ":"at+jwt","kid":""#);
for c in kid.chars() {
match c {
'"' => json.push_str("\\\""),
'\\' => json.push_str("\\\\"),
'\n' => json.push_str("\\n"),
'\r' => json.push_str("\\r"),
'\t' => json.push_str("\\t"),
'\u{8}' => json.push_str("\\b"),
'\u{c}' => json.push_str("\\f"),
c if (c as u32) < 0x20 => json.push_str(&format!("\\u{:04x}", c as u32)),
c => json.push(c),
}
}
json.push_str(r#""}"#);
URL_SAFE_NO_PAD.encode(json).into_boxed_str()
}
impl JwtConfig {
pub fn new(signer: impl Es256Signer + 'static, audience: impl Into<String>) -> Self {
let active = signer.public_jwk();
JwtConfig {
encoded_header: encoded_jose_header(&active.kid),
active,
signer: Arc::new(signer),
retired: Vec::new(),
audience: Audience::One(audience.into()),
jwks_uri: None,
}
}
pub fn rotate_to(mut self, new_active: impl Es256Signer + 'static) -> Self {
let retiring = std::mem::replace(&mut self.active, new_active.public_jwk());
self.signer = Arc::new(new_active);
self.encoded_header = encoded_jose_header(&self.active.kid);
let active_kid = self.active.kid.as_str();
self.retired
.retain(|jwk| jwk.kid != retiring.kid && jwk.kid != active_kid);
if retiring.kid != active_kid {
self.retired.insert(0, retiring);
}
self
}
pub fn retired_kids(&self) -> impl Iterator<Item = &str> {
self.retired.iter().map(|jwk| jwk.kid.as_str())
}
pub fn forget_retired_key_breaking_its_live_tokens(mut self, kid: &str) -> Self {
self.retired.retain(|jwk| jwk.kid != kid);
self
}
pub fn with_audiences(mut self, audiences: Vec<String>) -> Result<Self, JwtError> {
let audience = Audience::Many(audiences);
if !audience.names_a_resource_server() {
return Err(JwtError(
"aud must name at least one resource server, and no member may be empty".into(),
));
}
self.audience = audience;
Ok(self)
}
pub fn with_jwks_uri(mut self, uri: impl Into<String>) -> Self {
self.jwks_uri = Some(uri.into());
self
}
pub fn jwks_uri(&self) -> Option<&str> {
self.jwks_uri.as_deref()
}
pub fn kid(&self) -> &str {
&self.active.kid
}
pub fn jwks(&self) -> Jwks {
let mut keys = Vec::with_capacity(1 + self.retired.len());
keys.push(self.active.clone());
keys.extend(self.retired.iter().cloned());
Jwks { keys }
}
pub fn audience(&self) -> &Audience {
&self.audience
}
pub async fn sign_access_token(&self, claims: &AccessTokenClaims) -> Result<String, JwtError> {
self.finish_signing(self.signing_input(claims)?).await
}
pub(crate) fn signing_input(&self, claims: &AccessTokenClaims) -> Result<String, JwtError> {
let header = &self.encoded_header;
if !claims.aud.names_a_resource_server() {
return Err(JwtError(
"aud must name at least one resource server, and no member may be empty".into(),
));
}
let claims_json = serde_json::to_vec(claims)
.map_err(|e| JwtError(format!("claims serialization: {e}")))?;
let mut compact =
String::with_capacity(header.len() + 1 + base64_len(claims_json.len()) + 1 + 86);
compact.push_str(header);
compact.push('.');
URL_SAFE_NO_PAD.encode_string(&claims_json, &mut compact);
Ok(compact)
}
pub(crate) async fn finish_signing(&self, mut compact: String) -> Result<String, JwtError> {
let signature = self
.signer
.dyn_sign(compact.as_bytes())
.await
.map_err(|_| JwtError("the ES256 signer could not sign".into()))?;
compact.push('.');
URL_SAFE_NO_PAD.encode_string(signature, &mut compact);
Ok(compact)
}
}
impl fmt::Debug for JwtConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("JwtConfig")
.field("signer", &"<redacted>")
.field("active", &self.active)
.field("retired", &self.retired)
.field("audience", &self.audience)
.field("jwks_uri", &self.jwks_uri)
.finish()
}
}
impl PartialEq for JwtConfig {
fn eq(&self, other: &Self) -> bool {
self.active == other.active
&& self.retired == other.retired
&& self.audience == other.audience
&& self.jwks_uri == other.jwks_uri
}
}
impl Eq for JwtConfig {}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum AccessTokenFormat {
#[default]
Opaque,
Jwt(Box<JwtConfig>),
}
pub(crate) fn unix_seconds(t: SystemTime) -> Result<u64, JwtError> {
t.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.map_err(|_| JwtError("clock is before the Unix epoch".into()))
}
#[cfg(test)]
#[path = "tests/jwt.rs"]
mod tests;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VerifyError(String);
impl VerifyError {
pub(crate) fn new(msg: impl Into<String>) -> Self {
VerifyError(msg.into())
}
}
impl fmt::Display for VerifyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "JWS verification error: {}", self.0)
}
}
impl std::error::Error for VerifyError {}
const PRIVATE_JWK_MEMBERS: &[&str] = &["d", "p", "q", "dp", "dq", "qi", "oth", "k"];
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct PublicJwk {
kty: String,
crv: String,
x: String,
y: String,
#[serde(skip_serializing_if = "Option::is_none")]
kid: Option<String>,
}
impl<'de> Deserialize<'de> for PublicJwk {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let value = serde_json::Value::deserialize(d)?;
PublicJwk::from_json(&value).map_err(serde::de::Error::custom)
}
}
impl PublicJwk {
pub fn from_json(value: &serde_json::Value) -> Result<Self, VerifyError> {
let object = value
.as_object()
.ok_or_else(|| VerifyError::new("a JWK must be a JSON object"))?;
for member in PRIVATE_JWK_MEMBERS {
if object.contains_key(*member) {
return Err(VerifyError::new(
"the JWK carries a private or symmetric key parameter",
));
}
}
let string = |name: &str| -> Result<String, VerifyError> {
object
.get(name)
.and_then(|v| v.as_str())
.map(str::to_string)
.ok_or_else(|| VerifyError::new("the JWK is missing a required member"))
};
let kty = string("kty")?;
if kty != "EC" {
return Err(VerifyError::new("only EC keys are supported"));
}
let crv = string("crv")?;
if crv != "P-256" {
return Err(VerifyError::new("only the P-256 curve is supported"));
}
let x = string("x")?;
let y = string("y")?;
let coordinate = |b64: &str| -> Result<(), VerifyError> {
match URL_SAFE_NO_PAD.decode(b64) {
Ok(bytes) if bytes.len() == 32 => Ok(()),
_ => Err(VerifyError::new(
"a P-256 coordinate is exactly 32 base64url-encoded bytes",
)),
}
};
coordinate(&x)?;
coordinate(&y)?;
Ok(PublicJwk {
kty,
crv,
x,
y,
kid: object
.get("kid")
.and_then(|v| v.as_str())
.map(str::to_string),
})
}
pub fn from_coordinates(x: &str, y: &str) -> Result<Self, VerifyError> {
let coordinate = |b64: &str| -> Result<(), VerifyError> {
match URL_SAFE_NO_PAD.decode(b64) {
Ok(bytes) if bytes.len() == 32 => Ok(()),
_ => Err(VerifyError::new(
"a P-256 coordinate is exactly 32 base64url-encoded bytes",
)),
}
};
coordinate(x)?;
coordinate(y)?;
Ok(PublicJwk {
kty: "EC".to_string(),
crv: "P-256".to_string(),
x: x.to_string(),
y: y.to_string(),
kid: None,
})
}
pub fn with_kid(mut self, kid: &str) -> Self {
self.kid = Some(kid.to_string());
self
}
pub fn kty(&self) -> &str {
&self.kty
}
pub fn crv(&self) -> &str {
&self.crv
}
pub fn x(&self) -> &str {
&self.x
}
pub fn y(&self) -> &str {
&self.y
}
pub fn kid(&self) -> Option<&str> {
self.kid.as_deref()
}
pub fn thumbprint(&self) -> String {
let mut json = String::with_capacity(40 + self.crv.len() + self.x.len() + self.y.len());
json.push_str("{\"crv\":\"");
json.push_str(&self.crv);
json.push_str("\",\"kty\":\"");
json.push_str(&self.kty);
json.push_str("\",\"x\":\"");
json.push_str(&self.x);
json.push_str("\",\"y\":\"");
json.push_str(&self.y);
json.push_str("\"}");
URL_SAFE_NO_PAD.encode(Sha256::digest(json.as_bytes()))
}
}
pub struct CompactJws<'a> {
pub signing_input: &'a str,
pub header: serde_json::Map<String, serde_json::Value>,
pub payload: serde_json::Map<String, serde_json::Value>,
pub signature: Vec<u8>,
}
impl fmt::Debug for CompactJws<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CompactJws")
.field("signing_input", &"[redacted]")
.field("header", &self.header)
.field("payload", &self.payload)
.field("signature", &"[redacted]")
.finish()
}
}
impl<'a> CompactJws<'a> {
pub fn parse(token: &'a str) -> Result<Self, VerifyError> {
let malformed = || VerifyError::new("not a compact JWS of exactly three segments");
let mut parts = token.split('.');
let header_b64 = parts.next().ok_or_else(malformed)?;
let payload_b64 = parts.next().ok_or_else(malformed)?;
let signature_b64 = parts.next().ok_or_else(malformed)?;
if parts.next().is_some() {
return Err(malformed());
}
let signing_input = &token[..header_b64.len() + 1 + payload_b64.len()];
let object =
|b64: &str| -> Result<serde_json::Map<String, serde_json::Value>, VerifyError> {
let bytes = URL_SAFE_NO_PAD
.decode(b64)
.map_err(|_| VerifyError::new("a JWS segment is not unpadded base64url"))?;
match serde_json::from_slice::<serde_json::Value>(&bytes) {
Ok(serde_json::Value::Object(map)) => Ok(map),
_ => Err(VerifyError::new("a JWS segment is not a JSON object")),
}
};
Ok(CompactJws {
header: object(header_b64)?,
payload: object(payload_b64)?,
signature: URL_SAFE_NO_PAD
.decode(signature_b64)
.map_err(|_| VerifyError::new("the signature is not unpadded base64url"))?,
signing_input,
})
}
pub fn header_str(&self, name: &str) -> Option<&str> {
self.header.get(name).and_then(|v| v.as_str())
}
pub fn reject_unknown_crit(&self) -> Result<(), VerifyError> {
match self.header.get("crit") {
None => Ok(()),
Some(serde_json::Value::Array(names)) if names.is_empty() => Err(VerifyError::new(
"the header has an empty crit, which RFC 7515 s4.1.11 forbids",
)),
Some(serde_json::Value::Array(_)) => Err(VerifyError::new(
"the header's crit names an extension this server does not implement",
)),
Some(_) => Err(VerifyError::new("the header's crit is not an array")),
}
}
pub fn claim_str(&self, name: &str) -> Option<&str> {
self.payload.get(name).and_then(|v| v.as_str())
}
pub fn claim_time(&self, name: &str) -> Option<u64> {
self.payload.get(name).and_then(|v| v.as_u64())
}
}
#[cfg(feature = "jwt-p256")]
#[cfg_attr(docsrs, doc(cfg(feature = "jwt-p256")))]
pub fn verify_es256(jwk: &PublicJwk, signing_input: &[u8], signature: &[u8]) -> bool {
if signature.len() != 64 {
return false;
}
let (Ok(x), Ok(y)) = (
URL_SAFE_NO_PAD.decode(&jwk.x),
URL_SAFE_NO_PAD.decode(&jwk.y),
) else {
return false;
};
if x.len() != 32 || y.len() != 32 {
return false;
}
let mut sec1 = [0u8; 65];
sec1[0] = 0x04;
sec1[1..33].copy_from_slice(&x);
sec1[33..].copy_from_slice(&y);
let Ok(key) = VerifyingKey::from_sec1_bytes(&sec1) else {
return false;
};
let Ok(signature) = Signature::from_slice(signature) else {
return false;
};
key.verify(signing_input, &signature).is_ok()
}
pub fn hmac_sha256(key: &[u8], message: &[u8]) -> [u8; 32] {
let mut block = [0u8; 64];
if key.len() > 64 {
block[..32].copy_from_slice(&Sha256::digest(key));
} else {
block[..key.len()].copy_from_slice(key);
}
let mut ipad = [0x36u8; 64];
let mut opad = [0x5cu8; 64];
for i in 0..64 {
ipad[i] ^= block[i];
opad[i] ^= block[i];
}
let inner = Sha256::new()
.chain_update(ipad)
.chain_update(message)
.finalize();
Sha256::new()
.chain_update(opad)
.chain_update(inner)
.finalize()
.into()
}
pub fn verify_hs256(secret: &[u8], signing_input: &[u8], signature: &[u8]) -> bool {
if signature.len() != 32 {
return false;
}
let expected = hmac_sha256(secret, signing_input);
let mut acc = 0u8;
for i in 0..32 {
acc |= expected[i] ^ signature[i];
}
acc == 0
}
pub fn compact_jws(header: &[u8], payload: &[u8], sign: impl FnOnce(&str) -> Vec<u8>) -> String {
let mut compact =
String::with_capacity(base64_len(header.len()) + 1 + base64_len(payload.len()) + 1 + 86);
URL_SAFE_NO_PAD.encode_string(header, &mut compact);
compact.push('.');
URL_SAFE_NO_PAD.encode_string(payload, &mut compact);
let signature = sign(&compact);
compact.push('.');
URL_SAFE_NO_PAD.encode_string(signature, &mut compact);
compact
}
fn base64_len(n: usize) -> usize {
(n * 4).div_ceil(3)
}
#[cfg(feature = "jwt-p256")]
impl EcdsaP256Key {
pub fn sign_signing_input(&self, signing_input: &str) -> Result<Vec<u8>, JwtError> {
self.sign_es256(signing_input.as_bytes())
.map(|s| s.to_vec())
}
pub fn to_public_jwk(&self) -> PublicJwk {
Jwk::to_public_jwk(&self.public_jwk())
}
}