uselesskey_jose_openid/
lib.rs1#![forbid(unsafe_code)]
2
3use jsonwebtoken::{DecodingKey, EncodingKey};
9
10pub trait JoseOpenIdKeyExt {
12 fn encoding_key(&self) -> EncodingKey;
14
15 fn decoding_key(&self) -> DecodingKey;
17}
18
19#[cfg(feature = "rsa")]
20impl JoseOpenIdKeyExt for uselesskey_rsa::RsaKeyPair {
21 fn encoding_key(&self) -> EncodingKey {
22 EncodingKey::from_rsa_pem(self.private_key_pkcs8_pem().as_bytes())
23 .expect("failed to create EncodingKey from RSA PKCS8")
24 }
25
26 fn decoding_key(&self) -> DecodingKey {
27 DecodingKey::from_rsa_pem(self.public_key_spki_pem().as_bytes())
28 .expect("failed to create DecodingKey from RSA SPKI")
29 }
30}
31
32#[cfg(feature = "ecdsa")]
33impl JoseOpenIdKeyExt for uselesskey_ecdsa::EcdsaKeyPair {
34 fn encoding_key(&self) -> EncodingKey {
35 EncodingKey::from_ec_pem(self.private_key_pkcs8_pem().as_bytes())
36 .expect("failed to create EncodingKey from ECDSA PKCS8")
37 }
38
39 fn decoding_key(&self) -> DecodingKey {
40 DecodingKey::from_ec_pem(self.public_key_spki_pem().as_bytes())
41 .expect("failed to create DecodingKey from ECDSA SPKI")
42 }
43}
44
45#[cfg(feature = "ed25519")]
46impl JoseOpenIdKeyExt for uselesskey_ed25519::Ed25519KeyPair {
47 fn encoding_key(&self) -> EncodingKey {
48 EncodingKey::from_ed_pem(self.private_key_pkcs8_pem().as_bytes())
49 .expect("failed to create EncodingKey from Ed25519 PKCS8")
50 }
51
52 fn decoding_key(&self) -> DecodingKey {
53 DecodingKey::from_ed_pem(self.public_key_spki_pem().as_bytes())
54 .expect("failed to create DecodingKey from Ed25519 SPKI")
55 }
56}
57
58#[cfg(feature = "hmac")]
59impl JoseOpenIdKeyExt for uselesskey_hmac::HmacSecret {
60 fn encoding_key(&self) -> EncodingKey {
61 EncodingKey::from_secret(self.secret_bytes())
62 }
63
64 fn decoding_key(&self) -> DecodingKey {
65 DecodingKey::from_secret(self.secret_bytes())
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use jsonwebtoken::{Algorithm, Header, Validation, decode, encode};
72 use serde::{Deserialize, Serialize};
73 use uselesskey_core::Factory;
74
75 use super::JoseOpenIdKeyExt;
76 use uselesskey_ecdsa::{EcdsaFactoryExt, EcdsaSpec};
77 use uselesskey_rsa::{RsaFactoryExt, RsaSpec};
78
79 #[derive(Debug, Serialize, Deserialize, PartialEq)]
80 struct TestClaims {
81 sub: String,
82 scope: String,
83 }
84
85 fn relaxed_validation(algorithm: Algorithm) -> Validation {
86 let mut validation = Validation::new(algorithm);
87 validation.validate_exp = false;
88 validation.required_spec_claims = std::collections::HashSet::new();
89 validation
90 }
91
92 #[test]
93 fn rsa_sign_and_verify() {
94 let fx = Factory::random();
95 let keypair = fx.rsa("jwt-rsa", RsaSpec::rs256());
96
97 let claims = TestClaims {
98 sub: "alice".into(),
99 scope: "openid profile email".into(),
100 };
101
102 let token = encode(
103 &Header::new(Algorithm::RS256),
104 &claims,
105 &keypair.encoding_key(),
106 )
107 .expect("sign token with RS256 fixture");
108
109 let decoded = decode::<TestClaims>(
110 &token,
111 &keypair.decoding_key(),
112 &relaxed_validation(Algorithm::RS256),
113 )
114 .expect("decode token with RS256 fixture");
115
116 assert_eq!(decoded.claims, claims);
117 }
118
119 #[test]
120 fn cross_algorithm_fail_on_mismatch() {
121 let fx = Factory::random();
122 let rsa = fx.rsa("iss-a", RsaSpec::rs256());
123 let ecdsa = fx.ecdsa("iss-b", EcdsaSpec::es256());
124 let claims = TestClaims {
125 sub: "alice".into(),
126 scope: "openid".into(),
127 };
128
129 let token = encode(
130 &Header::new(Algorithm::ES256),
131 &claims,
132 &ecdsa.encoding_key(),
133 )
134 .expect("sign token with ES256 fixture");
135
136 let bad = decode::<TestClaims>(
137 &token,
138 &rsa.decoding_key(),
139 &Validation::new(Algorithm::RS256),
140 );
141 assert!(bad.is_err(), "cross-family verification should fail");
142 }
143
144 #[cfg(feature = "hmac")]
145 #[test]
146 fn hmac_roundtrip() {
147 use uselesskey_hmac::{HmacFactoryExt, HmacSpec};
148
149 let fx = Factory::random();
150 let secret = fx.hmac("secret", HmacSpec::hs256());
151
152 let claims = TestClaims {
153 sub: "service".into(),
154 scope: "read write".into(),
155 };
156
157 let token = encode(
158 &Header::new(Algorithm::HS256),
159 &claims,
160 &secret.encoding_key(),
161 )
162 .expect("sign token with HS256 fixture");
163
164 let decoded = decode::<TestClaims>(
165 &token,
166 &secret.decoding_key(),
167 &relaxed_validation(Algorithm::HS256),
168 )
169 .expect("decode token with HS256 fixture");
170
171 assert_eq!(decoded.claims, claims);
172
173 let _ = secret.encoding_key();
174 let _ = secret.decoding_key();
175 }
176}