1use base64::engine::general_purpose::URL_SAFE_NO_PAD;
6use base64::Engine;
7use serde::{Deserialize, Serialize};
8use sha2::{Digest, Sha256};
9
10use crate::error::{Error, Result};
11
12#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
14pub struct Jwk {
15 pub kty: String,
17 #[serde(default, skip_serializing_if = "Option::is_none")]
19 pub crv: Option<String>,
20 #[serde(default, skip_serializing_if = "Option::is_none")]
22 pub x: Option<String>,
23 #[serde(default, skip_serializing_if = "Option::is_none")]
25 pub y: Option<String>,
26 #[serde(default, skip_serializing_if = "Option::is_none")]
28 pub n: Option<String>,
29 #[serde(default, skip_serializing_if = "Option::is_none")]
31 pub e: Option<String>,
32 #[serde(default, skip_serializing_if = "Option::is_none")]
34 pub alg: Option<String>,
35 #[serde(default, skip_serializing_if = "Option::is_none")]
37 pub kid: Option<String>,
38 #[serde(default, rename = "use", skip_serializing_if = "Option::is_none")]
40 pub use_: Option<String>,
41}
42
43#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
45pub struct Jwks {
46 pub keys: Vec<Jwk>,
48}
49
50#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
52#[non_exhaustive]
53pub enum Algorithm {
54 Ed25519,
56 Es256,
58}
59
60impl Algorithm {
61 pub fn as_str(self) -> &'static str {
63 match self {
64 Algorithm::Ed25519 => "Ed25519",
65 Algorithm::Es256 => "ES256",
66 }
67 }
68
69 pub fn from_name(name: &str) -> Option<Self> {
71 match name {
72 "Ed25519" => Some(Algorithm::Ed25519),
73 "ES256" => Some(Algorithm::Es256),
74 _ => None,
75 }
76 }
77}
78
79#[derive(Debug, Clone, PartialEq, Eq)]
81#[non_exhaustive]
82pub enum KeyProblem {
83 Invalid(String),
85 Unsupported(String),
87}
88
89impl Jwk {
90 pub fn thumbprint(&self) -> Result<String> {
92 let member = |v: &Option<String>, name: &str| {
93 v.clone().ok_or_else(|| {
94 Error::Key(format!("{} key missing required member {name}", self.kty))
95 })
96 };
97 let canonical = match self.kty.as_str() {
98 "OKP" => serde_json::json!({
99 "crv": member(&self.crv, "crv")?, "kty": "OKP", "x": member(&self.x, "x")?,
100 }),
101 "EC" => serde_json::json!({
102 "crv": member(&self.crv, "crv")?, "kty": "EC",
103 "x": member(&self.x, "x")?, "y": member(&self.y, "y")?,
104 }),
105 "RSA" => serde_json::json!({
106 "e": member(&self.e, "e")?, "kty": "RSA", "n": member(&self.n, "n")?,
107 }),
108 other => return Err(Error::Key(format!("unsupported key type: {other}"))),
109 };
110 Ok(URL_SAFE_NO_PAD.encode(Sha256::digest(canonical.to_string().as_bytes())))
112 }
113
114 pub fn algorithm(&self) -> Result<Algorithm, KeyProblem> {
117 if self.kty.is_empty() {
118 return Err(KeyProblem::Invalid(
119 "JWK missing required member: kty".into(),
120 ));
121 }
122 if self.kty == "oct" {
123 return Err(KeyProblem::Invalid(
124 "symmetric keys are not permitted".into(),
125 ));
126 }
127 let Some(alg) = self.alg.as_deref() else {
128 return Err(KeyProblem::Invalid(
129 "JWK missing required member: alg".into(),
130 ));
131 };
132 if alg == "EdDSA" || alg.starts_with("HS") || alg == "none" {
133 return Err(KeyProblem::Invalid(format!(
134 "alg {alg:?} is not fully specified or is symmetric"
135 )));
136 }
137 let algorithm = Algorithm::from_name(alg)
138 .ok_or_else(|| KeyProblem::Unsupported(format!("unsupported algorithm {alg:?}")))?;
139 let (kty, crv, members): (&str, &str, &[&Option<String>]) = match algorithm {
140 Algorithm::Ed25519 => ("OKP", "Ed25519", &[&self.x]),
141 Algorithm::Es256 => ("EC", "P-256", &[&self.x, &self.y]),
142 };
143 if self.kty != kty || self.crv.as_deref() != Some(crv) {
144 return Err(KeyProblem::Invalid(format!(
145 "JWK kty/crv are inconsistent with alg {alg}"
146 )));
147 }
148 if members.iter().any(|m| m.is_none()) {
149 return Err(KeyProblem::Invalid("JWK missing key material".into()));
150 }
151 Ok(algorithm)
152 }
153
154 pub fn verify(&self, algorithm: Algorithm, message: &[u8], signature: &[u8]) -> bool {
157 let decode = |v: &Option<String>| v.as_deref().and_then(|v| URL_SAFE_NO_PAD.decode(v).ok());
158 match algorithm {
159 Algorithm::Ed25519 => {
160 let Some(Ok(x)) = decode(&self.x).map(<[u8; 32]>::try_from) else {
161 return false;
162 };
163 let Ok(key) = ed25519_dalek::VerifyingKey::from_bytes(&x) else {
164 return false;
165 };
166 let Ok(signature) = ed25519_dalek::Signature::from_slice(signature) else {
167 return false;
168 };
169 key.verify_strict(message, &signature).is_ok()
171 }
172 Algorithm::Es256 => {
173 use p256::ecdsa::signature::Verifier;
174 let (Some(x), Some(y)) = (decode(&self.x), decode(&self.y)) else {
175 return false;
176 };
177 if x.len() != 32 || y.len() != 32 {
178 return false;
179 }
180 let point = p256::EncodedPoint::from_affine_coordinates(
181 x.as_slice().into(),
182 y.as_slice().into(),
183 false,
184 );
185 let Ok(key) = p256::ecdsa::VerifyingKey::from_encoded_point(&point) else {
186 return false;
187 };
188 let Ok(signature) = p256::ecdsa::Signature::from_slice(signature) else {
189 return false;
190 };
191 key.verify(message, &signature).is_ok()
192 }
193 }
194 }
195
196 pub fn check_confirmation_key(&self) -> Result<()> {
199 let required: &[(&str, &Option<String>)] = match self.kty.as_str() {
200 "" => {
201 return Err(Error::Key(
202 "cnf.jwk is structurally incomplete: missing kty".into(),
203 ))
204 }
205 "oct" => return Err(Error::Key("cnf.jwk uses a symmetric key type".into())),
206 "OKP" => &[("crv", &self.crv), ("x", &self.x)],
207 "EC" => &[("crv", &self.crv), ("x", &self.x), ("y", &self.y)],
208 "RSA" => &[("n", &self.n), ("e", &self.e)],
209 other => {
210 return Err(Error::Key(format!(
211 "cnf.jwk uses an unsupported key type: {other}"
212 )))
213 }
214 };
215 if let Some((name, _)) = required.iter().find(|(_, v)| v.is_none()) {
216 return Err(Error::Key(format!(
217 "cnf.jwk is structurally incomplete: missing {name}"
218 )));
219 }
220 match self.alg.as_deref() {
221 None | Some("") => Err(Error::Key(
222 "cnf.jwk: alg is REQUIRED and must be fully specified".into(),
223 )),
224 Some(alg @ ("none" | "EdDSA" | "HS256" | "HS384" | "HS512")) => {
225 Err(Error::Key(format!("cnf.jwk: alg {alg:?} MUST NOT be used")))
226 }
227 Some("Ed25519") if self.kty == "OKP" && self.crv.as_deref() != Some("Ed25519") => {
228 Err(Error::Key("cnf.jwk crv disagrees with alg".into()))
229 }
230 Some("ES256") if self.kty == "EC" && self.crv.as_deref() != Some("P-256") => {
231 Err(Error::Key("cnf.jwk crv disagrees with alg".into()))
232 }
233 Some(_) => Ok(()),
234 }
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241
242 #[test]
243 fn the_thumbprint_is_rfc_8037s() {
244 let jwk = Jwk {
246 kty: "OKP".into(),
247 crv: Some("Ed25519".into()),
248 x: Some("11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo".into()),
249 ..Default::default()
250 };
251 assert_eq!(
252 jwk.thumbprint().unwrap(),
253 "kPrK_qmxVWaYVA9wwBF6Iuo3vVzz7TxHCTwXBygrS4k"
254 );
255 }
256
257 #[test]
258 fn the_algorithm_comes_from_alg_and_must_agree_with_the_key() {
259 let mut jwk = Jwk {
260 kty: "OKP".into(),
261 crv: Some("Ed25519".into()),
262 x: Some("x".into()),
263 alg: Some("Ed25519".into()),
264 ..Default::default()
265 };
266 assert_eq!(jwk.algorithm(), Ok(Algorithm::Ed25519));
267 jwk.alg = Some("EdDSA".into());
268 assert!(matches!(jwk.algorithm(), Err(KeyProblem::Invalid(_))));
269 jwk.alg = None;
270 assert!(matches!(jwk.algorithm(), Err(KeyProblem::Invalid(_))));
271 jwk.alg = Some("ES256".into());
272 assert!(matches!(jwk.algorithm(), Err(KeyProblem::Invalid(_))));
273 jwk.alg = Some("Ed448".into());
274 assert!(matches!(jwk.algorithm(), Err(KeyProblem::Unsupported(_))));
275 }
276
277 #[test]
278 fn a_small_order_key_verifies_nothing() {
279 let mut identity = [0u8; 32];
282 identity[0] = 1;
283 let jwk = Jwk {
284 kty: "OKP".into(),
285 crv: Some("Ed25519".into()),
286 x: Some(URL_SAFE_NO_PAD.encode(identity)),
287 alg: Some("Ed25519".into()),
288 ..Default::default()
289 };
290 let mut signature = [0u8; 64];
291 signature[0] = 1;
292 assert!(!jwk.verify(Algorithm::Ed25519, b"anything", &signature));
293 }
294}