Fork i18n + search + filtering- v0.2
at main 7.6 kB view raw
1use base64::{engine::general_purpose, Engine as _}; 2use jwt::{Claims, Header}; 3use p256::{ 4 ecdsa::{ 5 signature::{Signer, Verifier}, 6 Signature, SigningKey, VerifyingKey, 7 }, 8 PublicKey, SecretKey, 9}; 10use std::time::{SystemTime, UNIX_EPOCH}; 11 12use crate::encoding::ToBase64; 13use crate::jose_errors::JoseError; 14 15/// Signs a JWT token with the provided secret key, header, and claims 16/// 17/// Creates a JSON Web Token (JWT) by: 18/// 1. Base64URL encoding the header and claims 19/// 2. Signing the encoded header and claims with the secret key 20/// 3. Returning the complete JWT (header.claims.signature) 21pub fn mint_token( 22 secret_key: &SecretKey, 23 header: &Header, 24 claims: &Claims, 25) -> Result<String, JoseError> { 26 // Encode header and claims to base64url 27 let header = header 28 .to_base64() 29 .map_err(|_| JoseError::SigningKeyNotFound)?; 30 let claims = claims 31 .to_base64() 32 .map_err(|_| JoseError::SigningKeyNotFound)?; 33 let content = format!("{}.{}", header, claims); 34 35 // Create signature 36 let signing_key = SigningKey::from(secret_key.clone()); 37 let signature: Signature = signing_key 38 .try_sign(content.as_bytes()) 39 .map_err(JoseError::SigningFailed)?; 40 41 // Return complete JWT 42 Ok(format!( 43 "{}.{}", 44 content, 45 general_purpose::URL_SAFE_NO_PAD.encode(signature.to_bytes()) 46 )) 47} 48 49/// Verifies a JWT token's signature and validates its claims 50/// 51/// Performs the following validations: 52/// 1. Checks token format is valid (three parts separated by periods) 53/// 2. Decodes header and claims from base64url format 54/// 3. Verifies the token signature using the provided public key 55/// 4. Validates token expiration (if provided in claims) 56/// 5. Validates token not-before time (if provided in claims) 57/// 6. Returns the decoded claims if all validation passes 58pub fn verify_token(token: &str, public_key: &PublicKey) -> Result<Claims, JoseError> { 59 // Split token into its parts 60 let parts: Vec<&str> = token.split('.').collect(); 61 if parts.len() != 3 { 62 return Err(JoseError::InvalidTokenFormat); 63 } 64 65 let encoded_header = parts[0]; 66 let encoded_claims = parts[1]; 67 let encoded_signature = parts[2]; 68 69 // Decode header 70 let header_bytes = general_purpose::URL_SAFE_NO_PAD 71 .decode(encoded_header) 72 .map_err(|_| JoseError::InvalidHeader)?; 73 74 let header: Header = 75 serde_json::from_slice(&header_bytes).map_err(|_| JoseError::InvalidHeader)?; 76 77 // Verify algorithm matches what we expect 78 // We only support ES256 for now 79 if header.algorithm.as_deref() != Some("ES256") { 80 return Err(JoseError::UnsupportedAlgorithm); 81 } 82 83 // Decode claims 84 let claims_bytes = general_purpose::URL_SAFE_NO_PAD 85 .decode(encoded_claims) 86 .map_err(|_| JoseError::InvalidClaims)?; 87 88 let claims: Claims = 89 serde_json::from_slice(&claims_bytes).map_err(|_| JoseError::InvalidClaims)?; 90 91 // Decode signature 92 let signature_bytes = general_purpose::URL_SAFE_NO_PAD 93 .decode(encoded_signature) 94 .map_err(|_| JoseError::InvalidSignature)?; 95 96 let signature = 97 Signature::try_from(signature_bytes.as_slice()).map_err(|_| JoseError::InvalidSignature)?; 98 99 // Verify signature 100 let verifying_key = VerifyingKey::from(public_key); 101 let content = format!("{}.{}", encoded_header, encoded_claims); 102 103 verifying_key 104 .verify(content.as_bytes(), &signature) 105 .map_err(|_| JoseError::SignatureVerificationFailed)?; 106 107 // Get current timestamp for validation 108 let now = SystemTime::now() 109 .duration_since(UNIX_EPOCH) 110 .map_err(|_| JoseError::SystemTimeError)? 111 .as_secs(); 112 113 // Validate expiration time if present 114 if let Some(exp) = claims.jose.expiration { 115 if now >= exp { 116 return Err(JoseError::TokenExpired); 117 } 118 } 119 120 // Validate not-before time if present 121 if let Some(nbf) = claims.jose.not_before { 122 if now < nbf { 123 return Err(JoseError::TokenNotYetValid); 124 } 125 } 126 127 // Return validated claims 128 Ok(claims) 129} 130 131pub mod jwk { 132 use elliptic_curve::JwkEcKey; 133 use p256::SecretKey; 134 use rand::rngs::OsRng; 135 use serde::{Deserialize, Serialize}; 136 137 #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] 138 pub struct WrappedJsonWebKey { 139 #[serde(skip_serializing_if = "Option::is_none", default)] 140 pub kid: Option<String>, 141 142 #[serde(skip_serializing_if = "Option::is_none", default)] 143 pub alg: Option<String>, 144 145 #[serde(flatten)] 146 pub jwk: JwkEcKey, 147 } 148 149 #[derive(Serialize, Deserialize, Clone)] 150 pub struct WrappedJsonWebKeySet { 151 pub keys: Vec<WrappedJsonWebKey>, 152 } 153 154 pub fn generate() -> WrappedJsonWebKey { 155 let secret_key = SecretKey::random(&mut OsRng); 156 157 let kid = ulid::Ulid::new().to_string(); 158 159 WrappedJsonWebKey { 160 kid: Some(kid), 161 alg: Some("ES256".to_string()), 162 jwk: secret_key.to_jwk(), 163 } 164 } 165} 166 167pub mod jwt { 168 169 use std::collections::BTreeMap; 170 171 use elliptic_curve::JwkEcKey; 172 use serde::{Deserialize, Serialize}; 173 174 #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] 175 pub struct Header { 176 #[serde(rename = "alg", skip_serializing_if = "Option::is_none")] 177 pub algorithm: Option<String>, 178 179 #[serde(rename = "kid", skip_serializing_if = "Option::is_none")] 180 pub key_id: Option<String>, 181 182 #[serde(rename = "typ", skip_serializing_if = "Option::is_none")] 183 pub type_: Option<String>, 184 185 #[serde(rename = "jwk", skip_serializing_if = "Option::is_none")] 186 pub json_web_key: Option<JwkEcKey>, 187 } 188 189 #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] 190 pub struct Claims { 191 #[serde(flatten)] 192 pub jose: JoseClaims, 193 #[serde(flatten)] 194 pub private: BTreeMap<String, serde_json::Value>, 195 } 196 197 impl Claims { 198 pub fn new(jose: JoseClaims) -> Self { 199 Claims { 200 jose, 201 private: BTreeMap::new(), 202 } 203 } 204 } 205 206 pub type SecondsSinceEpoch = u64; 207 208 #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] 209 pub struct JoseClaims { 210 #[serde(rename = "iss", skip_serializing_if = "Option::is_none")] 211 pub issuer: Option<String>, 212 213 #[serde(rename = "sub", skip_serializing_if = "Option::is_none")] 214 pub subject: Option<String>, 215 216 #[serde(rename = "aud", skip_serializing_if = "Option::is_none")] 217 pub audience: Option<String>, 218 219 #[serde(rename = "exp", skip_serializing_if = "Option::is_none")] 220 pub expiration: Option<SecondsSinceEpoch>, 221 222 #[serde(rename = "nbf", skip_serializing_if = "Option::is_none")] 223 pub not_before: Option<SecondsSinceEpoch>, 224 225 #[serde(rename = "iat", skip_serializing_if = "Option::is_none")] 226 pub issued_at: Option<SecondsSinceEpoch>, 227 228 #[serde(rename = "jti", skip_serializing_if = "Option::is_none")] 229 pub json_web_token_id: Option<String>, 230 231 #[serde(rename = "htm", skip_serializing_if = "Option::is_none")] 232 pub http_method: Option<String>, 233 234 #[serde(rename = "htu", skip_serializing_if = "Option::is_none")] 235 pub http_uri: Option<String>, 236 237 #[serde(rename = "nonce", skip_serializing_if = "Option::is_none")] 238 pub nonce: Option<String>, 239 240 #[serde(rename = "ath", skip_serializing_if = "Option::is_none")] 241 pub auth: Option<String>, 242 } 243}