Skip to main content

postrust_auth/
keys.rs

1//! The keys tokens are verified with.
2//!
3//! `PGRST_JWT_SECRET` is read the way PostgREST reads `jwt-secret`: a plain
4//! string is an HMAC secret, and a JSON Web Key or a JSON Web Key Set is a set
5//! of keys -- symmetric (`oct`) or asymmetric (`RSA`, `EC`, `OKP`). Either way
6//! the result is a [`KeySet`], parsed once when the configuration is read and
7//! never per request.
8//!
9//! The rule everything here serves: **the algorithm comes from the key, never
10//! from the token**. A token's header says how it claims to be signed, and the
11//! party that wrote that header is the one being authenticated. Each key
12//! carries the algorithms it admits -- its JWK `alg` if it names one, the
13//! algorithms its type can do if it does not -- and a token whose `alg` the
14//! selected key does not admit is refused before any cryptography runs. That
15//! is what closes the classic confusions: `alg: none` names nothing any key
16//! admits, and an `HS256` token "signed" with an RSA public key's bytes finds
17//! no HMAC key to be checked against, because the RSA key admits only RSA.
18//!
19//! Selection follows PostgREST (which follows `jose-jwt`'s `findMatchingJwsKeys`):
20//! a token that names a `kid` is checked against the keys carrying that `kid`
21//! and no others; a token that names none is checked against every key that
22//! admits its algorithm, and passes if any of them verifies it.
23
24use std::sync::atomic::{AtomicU64, Ordering};
25use std::sync::{Arc, RwLock};
26
27use base64::Engine;
28
29use jsonwebtoken::{Algorithm, DecodingKey};
30
31/// The algorithms a plain-string HMAC secret admits.
32///
33/// Only HS256, as in PostgREST, which builds its key for a string secret as
34/// `SymmetricJwk bytes Nothing (Just Sig) (Just (Signed HS256))`. A
35/// deployment that wants HS384 or HS512 says so with an `oct` JWK.
36const PLAIN_SECRET_ALGORITHMS: &[Algorithm] = &[Algorithm::HS256];
37
38const HMAC: &[Algorithm] = &[Algorithm::HS256, Algorithm::HS384, Algorithm::HS512];
39const RSA: &[Algorithm] = &[
40    Algorithm::RS256,
41    Algorithm::RS384,
42    Algorithm::RS512,
43    Algorithm::PS256,
44    Algorithm::PS384,
45    Algorithm::PS512,
46];
47
48/// The smallest RSA modulus accepted, in bits.
49///
50/// ring, which verifies natively, refuses anything smaller at verification
51/// time, and the RustCrypto backend a Worker uses does not. Refusing it here,
52/// once, makes both builds agree -- and says so at startup rather than as a
53/// 401 on every request.
54const MIN_RSA_BITS: usize = 2048;
55
56/// A JWT secret that cannot be turned into keys.
57///
58/// Always a configuration fault, reported when the configuration is read: a
59/// server that started with a key it cannot use would answer every token with
60/// a 401 and leave the operator to work out why.
61#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
62#[non_exhaustive]
63pub enum JwtKeyError {
64    /// The secret is said to be base64 and is not.
65    #[error("the JWT secret is configured as base64 and is not valid base64")]
66    NotBase64,
67
68    /// The secret starts like a JSON object and does not parse as one.
69    ///
70    /// Not quietly read as an HMAC secret instead, which is what PostgREST
71    /// does: a public key with a typo in it would then become an HMAC secret
72    /// that anyone holding the public key can sign with.
73    #[error("the JWT secret looks like a JSON Web Key but is not valid JSON: {0}")]
74    NotJson(String),
75
76    /// A JSON object that is neither a JWK nor a JWK Set.
77    #[error("the JWT secret is a JSON object but neither a JWK (no \"kty\") nor a JWK Set (no \"keys\" array)")]
78    NotAKey,
79
80    /// One key is malformed.
81    #[error("{at}: {reason}")]
82    BadKey {
83        /// Which key: `JWK`, or `key 2 of the JWK Set`.
84        at: String,
85        /// What is wrong with it.
86        reason: String,
87    },
88
89    /// Every key was set aside as unable to verify a signature.
90    #[error("no key in the JWT secret can verify a JWT signature ({0})")]
91    NoUsableKeys(String),
92
93    /// `PGRST_JWT_JWKS_URL` is not a URL keys may be fetched from; see
94    /// [`crate::jwks::check_url`].
95    #[error("PGRST_JWT_JWKS_URL: {0}")]
96    JwksUrl(String),
97}
98
99/// One key, and what it may verify.
100#[derive(Clone)]
101struct Key {
102    /// The JWK `kid`, if it has one.
103    kid: Option<String>,
104    /// Whether a token's `kid` is ignored when selecting this key.
105    ///
106    /// Only for the plain-string secret, which is a single key nobody can
107    /// name. PostgREST gives it no `kid`, so a token carrying one is refused
108    /// there; tokens minted by identity providers routinely carry one even
109    /// when signed with a shared secret, and this server has always accepted
110    /// them, so it still does.
111    any_kid: bool,
112    /// The algorithms this key admits: never empty.
113    algorithms: &'static [Algorithm],
114    /// Kept for the `Debug` output; the key material never is.
115    kty: &'static str,
116    key: DecodingKey,
117}
118
119impl Key {
120    fn admits(&self, alg: Algorithm) -> bool {
121        self.algorithms.contains(&alg)
122    }
123
124    fn named(&self, kid: Option<&str>) -> bool {
125        match kid {
126            None => true,
127            Some(_) if self.any_kid => true,
128            // A key with no `kid` cannot be the one a `kid` names.
129            Some(kid) => self.kid.as_deref() == Some(kid),
130        }
131    }
132}
133
134/// A parsed set of verification keys.
135///
136/// Immutable once built. A set that has to change -- a JWKS fetched again
137/// after its issuer rotated a key -- is replaced whole through [`JwtKeys`], so
138/// a request never sees half of one set and half of another.
139pub struct KeySet {
140    keys: Vec<Key>,
141}
142
143impl std::fmt::Debug for KeySet {
144    /// The shape of the set, never the key material: a `Debug` of the
145    /// configuration ends up in logs.
146    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147        f.debug_list()
148            .entries(self.keys.iter().map(|k| {
149                format!(
150                    "{}{} {:?}",
151                    k.kty,
152                    k.kid
153                        .as_deref()
154                        .map(|kid| format!(" kid={kid}"))
155                        .unwrap_or_default(),
156                    k.algorithms
157                )
158            }))
159            .finish()
160    }
161}
162
163impl KeySet {
164    /// Read `PGRST_JWT_SECRET`.
165    ///
166    /// Base64 is undone first when the configuration says the secret is
167    /// base64, and what it decodes to is then read like any other secret --
168    /// so a base64-encoded JWK is a JWK, as in PostgREST.
169    pub fn from_secret(secret: &str, secret_is_base64: bool) -> Result<Self, JwtKeyError> {
170        let bytes = if secret_is_base64 {
171            base64::engine::general_purpose::STANDARD
172                .decode(secret)
173                .map_err(|_| JwtKeyError::NotBase64)?
174        } else {
175            secret.as_bytes().to_vec()
176        };
177
178        // A JSON object is a key or a set of them; anything else is the bytes
179        // of an HMAC secret. Only text can be JSON: random bytes from a base64
180        // secret that happen to start with `{` are almost never UTF-8, and
181        // stay a secret.
182        if let Ok(text) = std::str::from_utf8(&bytes) {
183            if text.trim_start().starts_with('{') {
184                return Self::from_jwk_json(text);
185            }
186        }
187
188        Ok(Self {
189            keys: vec![Key {
190                kid: None,
191                any_kid: true,
192                algorithms: PLAIN_SECRET_ALGORITHMS,
193                kty: "secret",
194                key: DecodingKey::from_secret(&bytes),
195            }],
196        })
197    }
198
199    /// Read a JWK, or a JWK Set (`{"keys": [...]}`), from its JSON.
200    ///
201    /// Keys that cannot verify a signature -- `"use": "enc"`, `key_ops`
202    /// without `verify`, an encryption `alg`, a key type or curve this server
203    /// does not implement -- are set aside with a warning rather than failing
204    /// the whole set, because an identity provider's JWKS routinely mixes them
205    /// in with its signing keys. A set left with no key at all is an error.
206    ///
207    /// A key that claims to be usable and is malformed fails the set: that is
208    /// a mistake in the configuration, not a key meant for something else.
209    pub fn from_jwk_json(json: &str) -> Result<Self, JwtKeyError> {
210        let value: serde_json::Value =
211            serde_json::from_str(json).map_err(|e| JwtKeyError::NotJson(e.to_string()))?;
212        let object = value.as_object().ok_or(JwtKeyError::NotAKey)?;
213
214        let entries: Vec<(String, &serde_json::Value)> = match object.get("keys") {
215            Some(serde_json::Value::Array(keys)) => keys
216                .iter()
217                .enumerate()
218                .map(|(i, k)| (format!("key {} of the JWK Set", i + 1), k))
219                .collect(),
220            Some(_) => {
221                return Err(JwtKeyError::BadKey {
222                    at: "JWK Set".into(),
223                    reason: "\"keys\" must be an array".into(),
224                })
225            }
226            None if object.contains_key("kty") => vec![("JWK".to_string(), &value)],
227            None => return Err(JwtKeyError::NotAKey),
228        };
229
230        let mut keys = Vec::with_capacity(entries.len());
231        let mut set_aside = Vec::new();
232        for (at, entry) in entries {
233            match parse_jwk(entry) {
234                Ok(Parsed::Key(key)) => keys.push(key),
235                Ok(Parsed::SetAside(why)) => {
236                    tracing::warn!("JWT secret: {at} is not used: {why}");
237                    set_aside.push(format!("{at}: {why}"));
238                }
239                Err(reason) => return Err(JwtKeyError::BadKey { at, reason }),
240            }
241        }
242
243        if keys.is_empty() {
244            return Err(JwtKeyError::NoUsableKeys(if set_aside.is_empty() {
245                "the JWK Set is empty".into()
246            } else {
247                set_aside.join("; ")
248            }));
249        }
250        Ok(Self { keys })
251    }
252
253    /// How many keys the set holds.
254    pub fn len(&self) -> usize {
255        self.keys.len()
256    }
257
258    /// Whether the set holds no keys. Never true of a set parsed from a
259    /// secret; true of the set a JWKS URL starts with, before its first fetch.
260    pub fn is_empty(&self) -> bool {
261        self.keys.is_empty()
262    }
263
264    /// A set with no keys, which verifies nothing: what a server configured
265    /// with only `PGRST_JWT_JWKS_URL` holds until the first fetch succeeds.
266    /// Every token is refused then -- fail closed -- with PGRST301, the same
267    /// answer as a token whose key this server does not have.
268    pub(crate) fn empty() -> Self {
269        Self { keys: Vec::new() }
270    }
271
272    /// The keys of both sets, `first`'s first.
273    ///
274    /// How `PGRST_JWT_SECRET` and `PGRST_JWT_JWKS_URL` combine: a token is
275    /// accepted if either source holds a key that verifies it. Order only
276    /// matters to a token without a `kid`, which is tried against every key
277    /// that admits its algorithm anyway.
278    pub(crate) fn union(first: &KeySet, second: &KeySet) -> Self {
279        Self {
280            keys: first.keys.iter().chain(&second.keys).cloned().collect(),
281        }
282    }
283
284    /// Whether a token with this header has any key to be checked against.
285    ///
286    /// The question a JWKS refresher asks before verifying: "no" is the
287    /// token whose `kid` this server has never seen, which may be a key the
288    /// identity provider rotated in since the set was last fetched.
289    pub(crate) fn selects(&self, kid: Option<&str>, alg: Algorithm) -> bool {
290        self.candidates(kid, alg).next().is_some()
291    }
292
293    /// The keys a token with this header may be checked against, in the order
294    /// the configuration lists them.
295    ///
296    /// The seam key selection lives behind: a `kid` narrows the set to the
297    /// keys carrying it, and the header's algorithm to the keys admitting it.
298    pub(crate) fn candidates<'a>(
299        &'a self,
300        kid: Option<&'a str>,
301        alg: Algorithm,
302    ) -> impl Iterator<Item = &'a DecodingKey> + 'a {
303        self.keys
304            .iter()
305            .filter(move |k| k.named(kid) && k.admits(alg))
306            .map(|k| &k.key)
307    }
308}
309
310/// The verification keys a server holds, shared and replaceable.
311///
312/// Cloning shares the set. [`JwtKeys::replace`] swaps the whole set at once
313/// for every holder, which is what a JWKS refresher needs: a request takes
314/// [`JwtKeys::current`] once and verifies against that snapshot, so a swap
315/// mid-request changes nothing about the request already running.
316///
317/// A plain `RwLock` around an `Arc`: the lock is held only to clone the `Arc`
318/// out, never across a verification, so readers do not wait on each other in
319/// any way that matters and a writer waits for nothing but those clones.
320///
321/// Each set, the first and every replacement, also gets a generation, under
322/// the same lock as the set itself so the two can never disagree. A
323/// [`crate::JwtCache`] remembers the generation its entries were verified
324/// under and forgets them all when a later one appears; see
325/// [`JwtKeys::generation`].
326#[derive(Clone)]
327pub struct JwtKeys {
328    current: Arc<RwLock<(Arc<KeySet>, u64)>>,
329}
330
331impl std::fmt::Debug for JwtKeys {
332    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
333        self.current().fmt(f)
334    }
335}
336
337impl JwtKeys {
338    /// Hold `set`.
339    pub fn new(set: KeySet) -> Self {
340        Self {
341            current: Arc::new(RwLock::new((Arc::new(set), next_generation()))),
342        }
343    }
344
345    /// The keys for `PGRST_JWT_SECRET`, or `None` when no secret is set.
346    ///
347    /// Call it where the configuration is read, and treat an error as a
348    /// reason not to start.
349    pub fn from_config(
350        secret: Option<&str>,
351        secret_is_base64: bool,
352    ) -> Result<Option<Self>, JwtKeyError> {
353        secret
354            .map(|secret| KeySet::from_secret(secret, secret_is_base64).map(Self::new))
355            .transpose()
356    }
357
358    /// The set as it is now.
359    pub fn current(&self) -> Arc<KeySet> {
360        // A poisoned lock still holds a whole set: the only write is a single
361        // assignment of a finished one.
362        self.current
363            .read()
364            .unwrap_or_else(|e| e.into_inner())
365            .0
366            .clone()
367    }
368
369    /// Which set this is: a number no earlier set, of this handle or any
370    /// other in the process, was given, and greater than all of theirs.
371    ///
372    /// Read before a token is verified and compared after, it says whether
373    /// the set the token was verified against is still the one in force.
374    /// Increasing rather than merely different, so that a request which read
375    /// it before a replacement is recognisably behind one that read it after,
376    /// and cannot put a cache back on the old set. Process-wide rather than
377    /// per handle, so that a cache that outlives one `JwtKeys` -- a Worker
378    /// isolate's, when its configuration changes -- sees the next one's sets
379    /// as later too.
380    pub fn generation(&self) -> u64 {
381        self.current.read().unwrap_or_else(|e| e.into_inner()).1
382    }
383
384    /// Replace the set, for every holder of this handle.
385    ///
386    /// A [`crate::JwtCache`] that sees the change forgets every token it
387    /// holds, so a token verified by a key that has since been withdrawn is
388    /// refused from the next request on -- not answered from the cache until
389    /// its entry expires. Replacing a set with an identical one forgets them
390    /// too, which costs one verification per token; a JWKS refresher that
391    /// fetched the same set again does not call this.
392    pub fn replace(&self, set: KeySet) {
393        let mut current = self.current.write().unwrap_or_else(|e| e.into_inner());
394        *current = (Arc::new(set), next_generation());
395    }
396}
397
398/// A generation no set has had yet. Starts at 1, so that 0 -- what a
399/// [`crate::JwtCache`] starts at, and what it is given when there are no
400/// keys -- is earlier than every set.
401fn next_generation() -> u64 {
402    static NEXT: AtomicU64 = AtomicU64::new(1);
403    NEXT.fetch_add(1, Ordering::Relaxed)
404}
405
406/// What one JWK turned out to be.
407enum Parsed {
408    Key(Key),
409    /// A well-formed key that cannot verify a signature here, and why.
410    SetAside(String),
411}
412
413/// Read one JWK. `Err` is a malformed key, with the reason.
414fn parse_jwk(value: &serde_json::Value) -> Result<Parsed, String> {
415    let jwk = value.as_object().ok_or("a JWK must be a JSON object")?;
416
417    let string = |name: &str| -> Result<Option<&str>, String> {
418        match jwk.get(name) {
419            None | Some(serde_json::Value::Null) => Ok(None),
420            Some(serde_json::Value::String(s)) => Ok(Some(s)),
421            Some(_) => Err(format!("\"{name}\" must be a string")),
422        }
423    };
424    let required = |name: &str| -> Result<&str, String> {
425        string(name)?.ok_or_else(|| format!("\"{name}\" is missing"))
426    };
427
428    let kty = required("kty")?;
429    let kid = string("kid")?.map(str::to_string);
430
431    // RFC 7517 4.2 and 4.3: a key declared for encryption, or for operations
432    // that do not include verifying, is not a signature key whatever its
433    // type would allow.
434    match string("use")? {
435        None | Some("sig") => {}
436        Some(other) => return Ok(Parsed::SetAside(format!("\"use\" is \"{other}\""))),
437    }
438    if let Some(ops) = jwk.get("key_ops") {
439        let ops = ops
440            .as_array()
441            .ok_or("\"key_ops\" must be an array of strings")?;
442        if !ops.iter().any(|op| op.as_str() == Some("verify")) {
443            return Ok(Parsed::SetAside(
444                "\"key_ops\" does not include \"verify\"".into(),
445            ));
446        }
447    }
448
449    let alg = match string("alg")? {
450        None => None,
451        Some(name) => match signing_algorithm(name) {
452            Some(alg) => Some(alg),
453            // `RSA-OAEP`, `ECDH-ES`, `ES512`, ...: an encryption algorithm or
454            // one this server does not verify.
455            None => {
456                return Ok(Parsed::SetAside(format!(
457                    "\"alg\" is \"{name}\", which this server does not verify"
458                )))
459            }
460        },
461    };
462
463    let (kty, family, key): (&'static str, &'static [Algorithm], DecodingKey) = match kty {
464        "oct" => {
465            let k = base64url(required("k")?, "k")?;
466            if k.is_empty() {
467                return Err("\"k\" is empty".into());
468            }
469            ("oct", HMAC, DecodingKey::from_secret(&k))
470        }
471        "RSA" => {
472            let (n, e) = (required("n")?, required("e")?);
473            let modulus = base64url(n, "n")?;
474            let bits = significant_bits(&modulus);
475            if bits < MIN_RSA_BITS {
476                return Err(format!(
477                    "the RSA modulus is {bits} bits; at least {MIN_RSA_BITS} are required"
478                ));
479            }
480            base64url(e, "e")?;
481            let key = DecodingKey::from_rsa_components(n, e).map_err(|e| e.to_string())?;
482            ("RSA", RSA, key)
483        }
484        "EC" => {
485            let (family, size): (&'static [Algorithm], usize) = match required("crv")? {
486                "P-256" => (&[Algorithm::ES256], 32),
487                "P-384" => (&[Algorithm::ES384], 48),
488                other => {
489                    return Ok(Parsed::SetAside(format!(
490                        "the curve \"{other}\" is not one this server verifies"
491                    )))
492                }
493            };
494            let (x, y) = (required("x")?, required("y")?);
495            for (name, coordinate) in [("x", x), ("y", y)] {
496                if base64url(coordinate, name)?.len() != size {
497                    return Err(format!("\"{name}\" must be {size} bytes for this curve"));
498                }
499            }
500            let key = DecodingKey::from_ec_components(x, y).map_err(|e| e.to_string())?;
501            ("EC", family, key)
502        }
503        "OKP" => {
504            match required("crv")? {
505                "Ed25519" => {}
506                other => {
507                    return Ok(Parsed::SetAside(format!(
508                        "the curve \"{other}\" is not one this server verifies"
509                    )))
510                }
511            }
512            let x = required("x")?;
513            if base64url(x, "x")?.len() != 32 {
514                return Err("\"x\" must be 32 bytes for Ed25519".into());
515            }
516            let key = DecodingKey::from_ed_components(x).map_err(|e| e.to_string())?;
517            ("OKP", &[Algorithm::EdDSA], key)
518        }
519        other => {
520            return Ok(Parsed::SetAside(format!(
521                "the key type \"{other}\" is not one this server verifies"
522            )))
523        }
524    };
525
526    // An `alg` naming a signing algorithm of another family is a key that
527    // contradicts itself -- `"kty": "RSA", "alg": "HS256"` is exactly the
528    // confusion this module exists to prevent -- so it is refused outright
529    // rather than set aside.
530    let algorithms: &'static [Algorithm] = match alg {
531        None => family,
532        Some(alg) => match family.iter().position(|a| *a == alg) {
533            Some(i) => &family[i..=i],
534            None => return Err(format!("\"alg\" is {alg:?}, which a {kty} key cannot do")),
535        },
536    };
537
538    Ok(Parsed::Key(Key {
539        kid,
540        any_kid: false,
541        algorithms,
542        kty,
543        key,
544    }))
545}
546
547/// The JWS algorithms this server verifies, by their JOSE names.
548///
549/// Spelled out rather than parsed through the library's `Algorithm`, so that
550/// `none` -- and anything else the library might one day learn -- can never be
551/// admitted by a key without this list changing.
552fn signing_algorithm(name: &str) -> Option<Algorithm> {
553    Some(match name {
554        "HS256" => Algorithm::HS256,
555        "HS384" => Algorithm::HS384,
556        "HS512" => Algorithm::HS512,
557        "RS256" => Algorithm::RS256,
558        "RS384" => Algorithm::RS384,
559        "RS512" => Algorithm::RS512,
560        "PS256" => Algorithm::PS256,
561        "PS384" => Algorithm::PS384,
562        "PS512" => Algorithm::PS512,
563        "ES256" => Algorithm::ES256,
564        "ES384" => Algorithm::ES384,
565        "EdDSA" => Algorithm::EdDSA,
566        _ => return None,
567    })
568}
569
570/// Decode a JWK member: base64url, unpadded per RFC 7515, though a padded one
571/// is accepted rather than refused over an `=`.
572fn base64url(value: &str, name: &str) -> Result<Vec<u8>, String> {
573    base64::engine::general_purpose::URL_SAFE_NO_PAD
574        .decode(value.trim_end_matches('='))
575        .map_err(|_| format!("\"{name}\" is not base64url"))
576}
577
578/// The bit length of a big-endian unsigned integer.
579fn significant_bits(bytes: &[u8]) -> usize {
580    match bytes.iter().position(|b| *b != 0) {
581        None => 0,
582        Some(i) => (bytes.len() - i) * 8 - bytes[i].leading_zeros() as usize,
583    }
584}
585
586#[cfg(test)]
587mod tests {
588    use super::*;
589    use crate::{validate_token, ClaimFault, JwtConfig, JwtError};
590    use base64::engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD};
591    use jsonwebtoken::{encode, EncodingKey, Header};
592    use serde_json::{json, Value};
593
594    /// Keys generated once for these tests (two RSA 2048, P-256, P-384,
595    /// Ed25519): each a private key in DER -- PKCS#1 for RSA, PKCS#8
596    /// otherwise -- beside its public JWK.
597    fn fixture(name: &str) -> (EncodingKey, Value) {
598        let all: Value = serde_json::from_str(include_str!("../tests/fixtures/keys.json")).unwrap();
599        let entry = &all[name];
600        let der = STANDARD
601            .decode(entry["private_der"].as_str().unwrap())
602            .unwrap();
603        let key = match entry["jwk"]["kty"].as_str().unwrap() {
604            "RSA" => EncodingKey::from_rsa_der(&der),
605            "EC" => EncodingKey::from_ec_der(&der),
606            _ => EncodingKey::from_ed_der(&der),
607        };
608        (key, entry["jwk"].clone())
609    }
610
611    /// A fixture's public JWK, with members added or overridden.
612    fn jwk(name: &str, extra: Value) -> Value {
613        let mut jwk = fixture(name).1;
614        jwk.as_object_mut()
615            .unwrap()
616            .extend(extra.as_object().unwrap().clone());
617        jwk
618    }
619
620    fn config_for(secret: &str) -> JwtConfig {
621        JwtConfig {
622            keys: JwtKeys::from_config(Some(secret), false).unwrap(),
623            ..JwtConfig::default()
624        }
625    }
626
627    fn jwks(keys: &[Value]) -> JwtConfig {
628        config_for(&json!({ "keys": keys }).to_string())
629    }
630
631    fn token(alg: Algorithm, kid: Option<&str>, key: &EncodingKey) -> String {
632        let mut header = Header::new(alg);
633        header.kid = kid.map(str::to_string);
634        encode(&header, &json!({"role": "web_user"}), key).unwrap()
635    }
636
637    fn signed_by(name: &str, alg: Algorithm, kid: Option<&str>) -> String {
638        token(alg, kid, &fixture(name).0)
639    }
640
641    fn accepted(token: &str, config: &JwtConfig) -> bool {
642        match validate_token(token, config) {
643            Ok(result) => {
644                assert_eq!(result.role, "web_user");
645                true
646            }
647            Err(_) => false,
648        }
649    }
650
651    fn refusal(token: &str, config: &JwtConfig) -> JwtError {
652        validate_token(token, config).expect_err("the token should have been refused")
653    }
654
655    #[test]
656    fn an_rsa_key_without_alg_admits_both_rsa_families() {
657        let config = config_for(&fixture("rsa_a").1.to_string());
658        for alg in RSA {
659            assert!(
660                accepted(&signed_by("rsa_a", *alg, None), &config),
661                "{alg:?}"
662            );
663        }
664    }
665
666    #[test]
667    fn each_curve_verifies_its_own_algorithm() {
668        for (name, alg) in [
669            ("ec_p256", Algorithm::ES256),
670            ("ec_p384", Algorithm::ES384),
671            ("ed25519", Algorithm::EdDSA),
672        ] {
673            let config = config_for(&fixture(name).1.to_string());
674            assert!(accepted(&signed_by(name, alg, None), &config), "{alg:?}");
675        }
676    }
677
678    #[test]
679    fn a_curve_admits_no_other_algorithm() {
680        // A P-384 key is not a P-256 key: ES256 names no key in this set.
681        let config = config_for(&fixture("ec_p384").1.to_string());
682        assert!(matches!(
683            refusal(&signed_by("ec_p256", Algorithm::ES256, None), &config),
684            JwtError::NoSuitableKey
685        ));
686    }
687
688    #[test]
689    fn a_token_signed_by_another_key_does_not_verify() {
690        let config = config_for(&fixture("rsa_a").1.to_string());
691        assert!(matches!(
692            refusal(&signed_by("rsa_b", Algorithm::RS256, None), &config),
693            JwtError::InvalidSignature
694        ));
695    }
696
697    #[test]
698    fn a_jwk_alg_narrows_what_the_key_admits() {
699        let config = config_for(&jwk("rsa_a", json!({"alg": "RS256"})).to_string());
700        assert!(accepted(
701            &signed_by("rsa_a", Algorithm::RS256, None),
702            &config
703        ));
704        assert!(matches!(
705            refusal(&signed_by("rsa_a", Algorithm::PS256, None), &config),
706            JwtError::NoSuitableKey
707        ));
708    }
709
710    #[test]
711    fn a_kid_selects_that_key_and_no_other() {
712        let config = jwks(&[
713            jwk("rsa_a", json!({"kid": "a"})),
714            jwk("rsa_b", json!({"kid": "b"})),
715        ]);
716        assert!(accepted(
717            &signed_by("rsa_b", Algorithm::RS256, Some("b")),
718            &config
719        ));
720        // Named the wrong key: that key is the only one tried.
721        assert!(matches!(
722            refusal(&signed_by("rsa_b", Algorithm::RS256, Some("a")), &config),
723            JwtError::InvalidSignature
724        ));
725        // Named a key the server does not hold.
726        assert!(matches!(
727            refusal(&signed_by("rsa_b", Algorithm::RS256, Some("c")), &config),
728            JwtError::NoSuitableKey
729        ));
730    }
731
732    #[test]
733    fn a_kid_does_not_select_a_key_without_one() {
734        // As in jose-jwt, which PostgREST uses: a key with no `kid` cannot be
735        // the one a `kid` names.
736        let config = config_for(&fixture("rsa_a").1.to_string());
737        assert!(matches!(
738            refusal(&signed_by("rsa_a", Algorithm::RS256, Some("a")), &config),
739            JwtError::NoSuitableKey
740        ));
741    }
742
743    #[test]
744    fn without_a_kid_every_key_that_admits_the_algorithm_is_tried() {
745        let config = jwks(&[
746            jwk("rsa_a", json!({"kid": "a"})),
747            jwk("ec_p256", json!({})),
748            jwk("rsa_b", json!({"kid": "b"})),
749        ]);
750        assert!(accepted(
751            &signed_by("rsa_b", Algorithm::RS512, None),
752            &config
753        ));
754        assert!(accepted(
755            &signed_by("ec_p256", Algorithm::ES256, None),
756            &config
757        ));
758        assert!(matches!(
759            refusal(&signed_by("ed25519", Algorithm::EdDSA, None), &config),
760            JwtError::NoSuitableKey
761        ));
762    }
763
764    #[test]
765    fn an_hmac_token_made_from_a_public_key_finds_no_key() {
766        // The classic confusion: the public key is public, so a server that
767        // let the header choose HMAC would verify a token "signed" with it.
768        let public = fixture("rsa_a").1;
769        let config = config_for(&public.to_string());
770        let n = URL_SAFE_NO_PAD
771            .decode(public["n"].as_str().unwrap())
772            .unwrap();
773        for secret in [public.to_string().into_bytes(), n] {
774            let forged = token(Algorithm::HS256, None, &EncodingKey::from_secret(&secret));
775            assert!(matches!(refusal(&forged, &config), JwtError::NoSuitableKey));
776        }
777    }
778
779    #[test]
780    fn a_key_whose_alg_contradicts_its_type_is_a_configuration_error() {
781        let error = KeySet::from_secret(&jwk("rsa_a", json!({"alg": "HS256"})).to_string(), false)
782            .unwrap_err();
783        assert!(matches!(error, JwtKeyError::BadKey { .. }), "{error}");
784    }
785
786    #[test]
787    fn alg_none_is_refused_whatever_the_key() {
788        let unsigned = format!(
789            "{}.{}.",
790            URL_SAFE_NO_PAD.encode(json!({"alg": "none"}).to_string()),
791            URL_SAFE_NO_PAD.encode(json!({"role": "web_user"}).to_string())
792        );
793        for config in [
794            config_for("reallyreallyreallyreallyverysafe"),
795            config_for(&fixture("rsa_a").1.to_string()),
796        ] {
797            assert!(matches!(
798                refusal(&unsigned, &config),
799                JwtError::NoSuitableKey
800            ));
801        }
802        // And no key can be configured to admit it.
803        let error = KeySet::from_secret(&jwk("rsa_a", json!({"alg": "none"})).to_string(), false)
804            .unwrap_err();
805        assert!(matches!(error, JwtKeyError::NoUsableKeys(_)), "{error}");
806    }
807
808    #[test]
809    fn an_encryption_key_is_never_used_to_verify() {
810        let config = jwks(&[
811            jwk("rsa_a", json!({"kid": "a", "use": "enc"})),
812            jwk("rsa_b", json!({"kid": "b", "use": "sig"})),
813        ]);
814        assert!(matches!(
815            refusal(&signed_by("rsa_a", Algorithm::RS256, Some("a")), &config),
816            JwtError::NoSuitableKey
817        ));
818        // Without a kid only the signing key is tried, and it is the wrong one.
819        assert!(matches!(
820            refusal(&signed_by("rsa_a", Algorithm::RS256, None), &config),
821            JwtError::InvalidSignature
822        ));
823        assert!(accepted(
824            &signed_by("rsa_b", Algorithm::RS256, None),
825            &config
826        ));
827
828        // `key_ops` without `verify` says the same thing another way.
829        let config = jwks(&[
830            jwk("rsa_a", json!({"key_ops": ["encrypt"]})),
831            jwk("rsa_b", json!({})),
832        ]);
833        assert!(matches!(
834            refusal(&signed_by("rsa_a", Algorithm::RS256, None), &config),
835            JwtError::InvalidSignature
836        ));
837    }
838
839    #[test]
840    fn a_secret_of_nothing_but_encryption_keys_is_refused_at_startup() {
841        let only_enc = jwk("rsa_a", json!({"use": "enc"})).to_string();
842        assert!(matches!(
843            KeySet::from_secret(&only_enc, false),
844            Err(JwtKeyError::NoUsableKeys(_))
845        ));
846        assert!(matches!(
847            KeySet::from_secret(r#"{"keys": []}"#, false),
848            Err(JwtKeyError::NoUsableKeys(_))
849        ));
850    }
851
852    #[test]
853    fn keys_this_server_cannot_use_are_set_aside_not_fatal() {
854        // What an identity provider's JWKS looks like: a P-521 key, an X25519
855        // key and an encryption algorithm beside the one signing key.
856        let config = jwks(&[
857            json!({"kty": "EC", "crv": "P-521", "x": "AA", "y": "AA"}),
858            json!({"kty": "OKP", "crv": "X25519", "x": "AA"}),
859            jwk("rsa_b", json!({"alg": "RSA-OAEP"})),
860            json!({"kty": "unheard-of"}),
861            jwk("rsa_a", json!({})),
862        ]);
863        assert_eq!(config.keys.as_ref().unwrap().current().len(), 1);
864        assert!(accepted(
865            &signed_by("rsa_a", Algorithm::RS256, None),
866            &config
867        ));
868    }
869
870    #[test]
871    fn an_oct_jwk_is_an_hmac_key() {
872        let secret = b"reallyreallyreallyreallyverysafe";
873        let oct = json!({"kty": "oct", "k": URL_SAFE_NO_PAD.encode(secret)});
874        let config = config_for(&oct.to_string());
875        for alg in HMAC {
876            let signed = token(*alg, None, &EncodingKey::from_secret(secret));
877            assert!(accepted(&signed, &config), "{alg:?}");
878        }
879
880        let mut only_512 = oct.clone();
881        only_512["alg"] = json!("HS512");
882        let config = config_for(&only_512.to_string());
883        let hs256 = token(Algorithm::HS256, None, &EncodingKey::from_secret(secret));
884        assert!(matches!(refusal(&hs256, &config), JwtError::NoSuitableKey));
885    }
886
887    #[test]
888    fn a_plain_secret_is_hs256_and_ignores_kid() {
889        let secret = "reallyreallyreallyreallyverysafe";
890        let key = EncodingKey::from_secret(secret.as_bytes());
891        let config = config_for(secret);
892        assert!(accepted(&token(Algorithm::HS256, None, &key), &config));
893        assert!(accepted(
894            &token(Algorithm::HS256, Some("whatever"), &key),
895            &config
896        ));
897        // PostgREST builds a string secret's key for HS256 alone.
898        assert!(matches!(
899            refusal(&token(Algorithm::HS512, None, &key), &config),
900            JwtError::NoSuitableKey
901        ));
902        let other = EncodingKey::from_secret(b"other");
903        assert!(matches!(
904            refusal(&token(Algorithm::HS256, None, &other), &config),
905            JwtError::InvalidSignature
906        ));
907    }
908
909    #[test]
910    fn a_base64_secret_may_itself_be_a_jwk() {
911        let public = STANDARD.encode(fixture("ed25519").1.to_string());
912        let config = JwtConfig {
913            keys: JwtKeys::from_config(Some(&public), true).unwrap(),
914            ..JwtConfig::default()
915        };
916        assert!(accepted(
917            &signed_by("ed25519", Algorithm::EdDSA, None),
918            &config
919        ));
920
921        let secret = STANDARD.encode("reallyreallyreallyreallyverysafe");
922        let config = JwtConfig {
923            keys: JwtKeys::from_config(Some(&secret), true).unwrap(),
924            ..JwtConfig::default()
925        };
926        let key = EncodingKey::from_secret(b"reallyreallyreallyreallyverysafe");
927        assert!(accepted(&token(Algorithm::HS256, None, &key), &config));
928
929        assert_eq!(
930            KeySet::from_secret("not base64!", true).unwrap_err(),
931            JwtKeyError::NotBase64
932        );
933    }
934
935    #[test]
936    fn a_malformed_key_is_a_configuration_error() {
937        let rsa = fixture("rsa_a").1;
938        let short_modulus =
939            json!({"kty": "RSA", "n": URL_SAFE_NO_PAD.encode([0xffu8; 128]), "e": "AQAB"});
940        let mut no_n = rsa.clone();
941        no_n.as_object_mut().unwrap().remove("n");
942        let mut bad_x = fixture("ec_p256").1;
943        bad_x["x"] = json!("AAAA");
944
945        assert!(matches!(
946            KeySet::from_secret(r#"{"kty": "RSA", "n": "#, false),
947            Err(JwtKeyError::NotJson(_))
948        ));
949        assert_eq!(
950            KeySet::from_secret(r#"{"hello": "world"}"#, false).unwrap_err(),
951            JwtKeyError::NotAKey
952        );
953        for malformed in [short_modulus, no_n, bad_x, json!({"keys": "nope"})] {
954            let error = KeySet::from_secret(&malformed.to_string(), false).unwrap_err();
955            assert!(matches!(error, JwtKeyError::BadKey { .. }), "{error}");
956        }
957        // The message says which key, and what is wrong with it.
958        let error = KeySet::from_secret(
959            &json!({"keys": [rsa, {"kty": "RSA", "e": "AQAB"}]}).to_string(),
960            false,
961        )
962        .unwrap_err();
963        assert_eq!(error.to_string(), "key 2 of the JWK Set: \"n\" is missing");
964    }
965
966    #[test]
967    fn replacing_the_set_is_seen_by_every_holder() {
968        let set = |name: &str| KeySet::from_secret(&fixture(name).1.to_string(), false).unwrap();
969        let keys = JwtKeys::new(set("rsa_a"));
970        let config = JwtConfig {
971            keys: Some(keys.clone()),
972            ..JwtConfig::default()
973        };
974        let by_b = signed_by("rsa_b", Algorithm::RS256, None);
975        assert!(!accepted(&by_b, &config));
976
977        keys.replace(set("rsa_b"));
978        assert!(accepted(&by_b, &config));
979    }
980
981    #[test]
982    fn debug_output_shows_no_key_material() {
983        let secret = "reallyreallyreallyreallyverysafe";
984        let k = URL_SAFE_NO_PAD.encode(secret);
985        let oct = json!({"kty": "oct", "kid": "k1", "k": k});
986        for keys in [
987            KeySet::from_secret(secret, false).unwrap(),
988            KeySet::from_secret(&oct.to_string(), false).unwrap(),
989        ] {
990            let shown = format!("{keys:?}");
991            assert!(!shown.contains(secret) && !shown.contains(&k), "{shown}");
992        }
993    }
994
995    #[test]
996    fn the_issuer_is_checked_once_the_signature_verifies() {
997        let config = JwtConfig {
998            issuer: Some("https://issuer.example".into()),
999            ..config_for(&fixture("rsa_a").1.to_string())
1000        };
1001        let (key, _) = fixture("rsa_a");
1002        let with = |claims: Value| encode(&Header::new(Algorithm::RS256), &claims, &key).unwrap();
1003        assert!(validate_token(
1004            &with(json!({"role": "r", "iss": "https://issuer.example"})),
1005            &config
1006        )
1007        .is_ok());
1008        for claims in [
1009            json!({"role": "r", "iss": "https://elsewhere.example"}),
1010            json!({"role": "r", "iss": ["https://issuer.example"]}),
1011            json!({"role": "r"}),
1012        ] {
1013            assert!(matches!(
1014                validate_token(&with(claims), &config),
1015                Err(JwtError::Claim(ClaimFault::UnexpectedIssuer))
1016            ));
1017        }
1018    }
1019}