~/bend-docscommunity

jwt.bend relies on unsafe/foreign

raw source on the hub · import bend-kit-jwt@0.3.0.1/jwt.bend as Jwt

JSON Web Tokens: HS256/384/512, RS256, and ES256 compact JWS sign and verify, claim checks, and JWKS keys over Hairpin. Source: https://github.com/paymog/bend-kit/tree/main/jwt

6 imports
import Base
import bend-kit-crypto@0.2.2.1/crypto.bend as Crypto
import bend-kit-hairpin@0.3.0.0/hairpin.bend as Hairpin
import bend-kit-http@0.32.0.0/http.bend as Http
import 0x584fc27920487ceab242392391418d7f/json.bend as Json
import 0x49814d83de8f70993a43e1002be29ecd/bytes.bend as Bytes

Types

type Alg source · line 14 · raw

Data

type Key source · line 24 · raw

Type

Hmac: the shared secret, at least the digest's size (RFC 7518 §3.2). Pem: a PEM "PUBLIC KEY" to verify, or an unencrypted PEM private key to sign. Rsa, Ec: JWK public key parts as big-endian octets (RFC 7518 §6.3, §6.2 on P-256).

type Err source · line 30 · raw

Data

type Policy source · line 49 · raw

Data

What verify accepts: the one alg, the issuer and audience when Some, and leeway seconds of clock skew.

type Lit source · line 161 · raw

Data

A NumericDate (RFC 7519 §2) rounded up to whole seconds and capped at the U32 top, or None when it is negative or has an exponent. Up is exact against a whole now: now >= t iff now >= ceil(t).

type Field source · line 168 · raw

Data

One member: its name as octets, and what the checks need of its value.

type Parts source · line 264 · raw

Type

A compact JWS: the header and claim members, the claims, the signing input, and the signature.

type Jwk source · line 667 · raw

Data

A usable public key from a set: its kid, RSA or EC P-256, its alg ("" when the set gives none), and its two base64url parts (n and e, or x and y).

type Jwks source · line 796 · raw

Data

A cached key set from the caller's own trusted url; a token never picks the url (jku and x5u are ignored). live: fetched once; at: the unix time of the last good fetch; ttl: seconds it stays fresh; tried: the unix time of the last fetch, good or not.

Definitions

def policy source · line 52 · raw

@a:Alg -> Policy

def iss source · line 55 · raw

@p:Policy -> @s:String -> Policy

def aud source · line 59 · raw

@p:Policy -> @s:String -> Policy

def leeway source · line 63 · raw

@p:Policy -> @+n:U32 -> Policy

def policy.alg source · line 67 · raw

@p:Policy -> Alg

def alg.name source · line 71 · raw

@a:Alg -> String

def b64u.push source · line 86 · raw

@+c:U32 -> @acc:String -> String

def b64u.go source · line 97 · raw

@s:String -> @acc:String -> String

def b64u source · line 104 · raw

@b:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> String

def unb64u.c source · line 108 · raw

@+c:U32 -> U32

The standard alphabet's char for c. '+', '/', '=' and NUL become NUL, which no decoder accepts.

def unb64u.pad source · line 124 · raw

@+r:U32 -> @acc:String -> String

Padding for n mod 4 chars; 1 is never a whole group, so it gets a NUL.

def unb64u.go source · line 135 · raw

@s:String -> @acc:String -> @+n:U32 -> String

def unb64u.same source · line 142 · raw

@+s:String -> @r:Pair(0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes) -> Maybe<&1, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes>

def unb64u.canon source · line 146 · raw

@+s:String -> @m:Maybe<&1, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes> -> Maybe<&1, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes>

def unb64u source · line 154 · raw

@+s:String -> Maybe<&1, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes>

Strict: only the URL-safe alphabet, no '=', and only the canonical text of its octets, so pad bits are 0.

def secs.digit source · line 171 · raw

@+acc:U32 -> @+d:U32 -> U32

def secs.up source · line 174 · raw

@+acc:U32 -> @frac:Bool -> U32

def secs.go source · line 178 · raw

@s:String -> @+acc:U32 -> @+frac:Bool -> @+point:Bool -> @+ok:Bool -> Maybe<&2, U32>

The JSON parser has checked the grammar, so only digits and one '.' need telling apart.

def secs source · line 189 · raw

@s:String -> Maybe<&2, U32>

def str.of source · line 192 · raw

@v:0x584fc27920487ceab242392391418d7f/json.Val -> Maybe<&2, String>

def strs.push source · line 199 · raw

@s:Maybe<&2, String> -> @acc:Maybe<&2, List<&2, String>> -> Maybe<&2, List<&2, String>>

def strs source · line 206 · raw

@xs:List<&1, 0x584fc27920487ceab242392391418d7f/json.Val> -> @acc:Maybe<&2, List<&2, String>> -> Maybe<&2, List<&2, String>>

def lit source · line 213 · raw

@v:0x584fc27920487ceab242392391418d7f/json.Val -> Lit

def fields source · line 225 · raw

@kvs:List<&1, Pair(0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes, 0x584fc27920487ceab242392391418d7f/json.Val)> -> @acc:List<&2, Field> -> List<&2, Field>

An object's members, names as octets, last first.

def field source · line 232 · raw

@l:List<&2, Field> -> @+k:String -> Maybe<&2, Lit>

def has source · line 239 · raw

@l:List<&2, Field> -> @+k:String -> Bool

def dup source · line 247 · raw

@l:List<&2, Field> -> Bool

RFC 7515 §5.2 allows rejecting repeated names; a second "alg" is how parsers come to disagree.

def str source · line 254 · raw

@m:Maybe<&2, Lit> -> Maybe<&2, String>

def parse.objs source · line 267 · raw

@hf:List<&2, Field> -> @ff:List<&2, Field> -> @cv:0x584fc27920487ceab242392391418d7f/json.Val -> @input:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> @sig:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> Result<&1, &1, Err, Parts>

def parse.json source · line 273 · raw

@h:Maybe<&1, 0x584fc27920487ceab242392391418d7f/json.Val> -> @f:Maybe<&1, 0x584fc27920487ceab242392391418d7f/json.Val> -> @c:Maybe<&1, 0x584fc27920487ceab242392391418d7f/json.Val> -> @input:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> @sig:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> Result<&1, &1, Err, Parts>

def parse.claims source · line 281 · raw

@hb:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> @r:Pair(0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes) -> @input:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> @sig:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> Result<&1, &1, Err, Parts>

def parse.segs source · line 285 · raw

@h:Maybe<&1, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes> -> @c:Maybe<&1, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes> -> @s:Maybe<&1, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes> -> @input:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> Result<&1, &1, Err, Parts>

def parse.split source · line 293 · raw

@xs:List<&2, String> -> Result<&1, &1, Err, Parts>

def parse source · line 301 · raw

@token:String -> Result<&1, &1, Err, Parts>

Three strict base64url segments, the first two JSON objects with no repeated names.

def fail.if source · line 306 · raw

@bad:Bool -> @+e:Err -> Result<&1, &1, Err, Unit>

def head.alg source · line 313 · raw

@m:Maybe<&2, Lit> -> Result<&1, &1, Err, String>

def head.check source · line 322 · raw

@+a:Alg -> @+h:List<&2, Field> -> Result<&1, &1, Err, Unit>

The header's alg is exactly the pinned one, and it asks for no extension: crit (RFC 7515 §4.1.11) and b64 (RFC 7797) change how the token must be read, and neither is implemented.

def when source · line 328 · raw

@m:Maybe<&2, Lit> -> Result<&1, &1, Err, Maybe<&2, U32>>

def text source · line 337 · raw

@m:Maybe<&2, Lit> -> Result<&1, &1, Err, Maybe<&2, String>>

def auds source · line 347 · raw

@m:Maybe<&2, Lit> -> Result<&1, &1, Err, Maybe<&2, List<&2, String>>>

RFC 7519 §4.1.3: aud is one string or an array of them.

def late source · line 359 · raw

@+now:U32 -> @+lw:U32 -> @m:Maybe<&2, U32> -> Bool

exp (RFC 7519 §4.1.4): now must be before it; late by at least leeway fails.

def early source · line 367 · raw

@+now:U32 -> @+lw:U32 -> @m:Maybe<&2, U32> -> Bool

nbf and iat: at most leeway seconds in the future.

def octets source · line 374 · raw

@s:String -> String

def iss.bad source · line 377 · raw

@want:Maybe<&2, String> -> @got:Maybe<&2, String> -> Bool

def aud.has source · line 386 · raw

@l:List<&2, String> -> @+a:String -> Bool

def aud.bad source · line 394 · raw

@want:Maybe<&2, String> -> @got:Maybe<&2, List<&2, String>> -> Bool

RFC 7519 §4.1.3: a token with an aud that does not name us fails, as does one without aud when we want one.

def claims.check source · line 406 · raw

@p:Policy -> @+now:U32 -> @+c:List<&2, Field> -> Result<&1, &1, Err, Unit>

The registered claims of c at unix time now.

def inspect.parts source · line 420 · raw

@+p:Policy -> @+now:U32 -> @r:Result<&1, &1, Err, Parts> -> Result<&1, &1, Err, Unit>

def inspect source · line 430 · raw

@p:Policy -> @token:String -> @+now:U32 -> Result<&1, &1, Err, Unit>

The header and claims of token, without its signature: the pure half of verify.

def crypto.bytes source · line 435 · raw

@r:Result<&1, &1, Pair(U32, String), Pair(U32, Array<U32>)> -> Result<&1, &1, Err, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes>

def crypto.ok source · line 442 · raw

@r:Result<&1, &1, Pair(U32, String), Bool> -> Result<&1, &1, Err, Bool>

def mac.if source · line 449 · raw

@weak:Bool -> @+alg:String -> @+kn:U32 -> @kb:Array<U32> -> @+dn:U32 -> @db:Array<U32> -> IO(Result<&1, &1, Err, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes>)

def mac source · line 459 · raw

@+alg:String -> @+min:U32 -> @k:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> @input:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> IO(Result<&1, &1, Err, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes>)

def mac.cmp source · line 464 · raw

@m:Result<&1, &1, Err, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes> -> @sig:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> IO(Result<&1, &1, Err, Bool>)

def mac.check source · line 475 · raw

@+alg:String -> @+min:U32 -> @k:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> @input:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> @sig:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> IO(Result<&1, &1, Err, Bool>)

The MAC compares in constant time.

def rsa.pem source · line 480 · raw

@p:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> @input:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> @sig:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> IO(Result<&1, &1, Err, Bool>)

def rsa.jwk source · line 488 · raw

@n:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> @e:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> @input:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> @sig:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> IO(Result<&1, &1, Err, Bool>)

def ec.pem source · line 497 · raw

@p:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> @input:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> @sig:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> IO(Result<&1, &1, Err, Bool>)

def ec.jwk source · line 505 · raw

@x:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> @y:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> @input:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> @sig:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> IO(Result<&1, &1, Err, Bool>)

def sig.check source · line 515 · raw

@a:Alg -> @key:Key -> @input:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> @sig:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> IO(Result<&1, &1, Err, Bool>)

Does sig sign input under key for alg? A key of the wrong kind fails with KeyType before any crypto.

def rsa.sign source · line 534 · raw

@p:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> @input:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> IO(Result<&1, &1, Err, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes>)

def ec.sign source · line 541 · raw

@p:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> @input:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> IO(Result<&1, &1, Err, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes>)

def sig.make source · line 549 · raw

@a:Alg -> @key:Key -> @input:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> IO(Result<&1, &1, Err, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes>)

The signature of input under key for alg. RS256 and ES256 sign with a PEM private key.

def header.kid source · line 566 · raw

@m:Maybe<&2, String> -> List<&1, Pair(0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes, 0x584fc27920487ceab242392391418d7f/json.Val)>

def signing.input source · line 578 · raw

@a:Alg -> @kid:Maybe<&2, String> -> @body:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> String

header.body in base64url, body being the compact JSON claims: the text the signature covers.

def sign.done source · line 581 · raw

@+input:String -> @s:Result<&1, &1, Err, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes> -> Result<&1, &1, Err, String>

def sign.obj source · line 589 · raw

@m:Maybe<&1, 0x584fc27920487ceab242392391418d7f/json.Val> -> Bool

verify accepts only an object with no repeated names, so sign makes no other kind.

def sign.if source · line 596 · raw

@ok:Bool -> @+a:Alg -> @key:Key -> @kid:Maybe<&2, String> -> @body:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> IO(Result<&1, &1, Err, String>)

def sign.body source · line 606 · raw

@+a:Alg -> @key:Key -> @kid:Maybe<&2, String> -> @r:Pair(0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes) -> IO(Result<&1, &1, Err, String>)

def sign source · line 612 · raw

@+a:Alg -> @key:Key -> @kid:Maybe<&2, String> -> @claims:0x584fc27920487ceab242392391418d7f/json.Val -> IO(Result<&1, &1, Err, String>)

A compact token of claims signed with key for a; kid names the key in the header. Claims that are not an object, or repeat a name, fail with BadClaim.

def claims.done source · line 615 · raw

@r:Result<&1, &1, Err, Unit> -> @v:0x584fc27920487ceab242392391418d7f/json.Val -> Result<&1, &1, Err, 0x584fc27920487ceab242392391418d7f/json.Val>

def check.sig source · line 622 · raw

@ok:Bool -> @+p:Policy -> @+now:U32 -> @+f:List<&2, Field> -> @v:0x584fc27920487ceab242392391418d7f/json.Val -> Result<&1, &1, Err, 0x584fc27920487ceab242392391418d7f/json.Val>

def check.done source · line 629 · raw

@s:Result<&1, &1, Err, Bool> -> @+p:Policy -> @+now:U32 -> @+f:List<&2, Field> -> @v:0x584fc27920487ceab242392391418d7f/json.Val -> Result<&1, &1, Err, 0x584fc27920487ceab242392391418d7f/json.Val>

def check.pinned source · line 637 · raw

@ok:Result<&1, &1, Err, Unit> -> @+p:Policy -> @key:Key -> @+now:U32 -> @+f:List<&2, Field> -> @v:0x584fc27920487ceab242392391418d7f/json.Val -> @input:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> @sig:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> IO(Result<&1, &1, Err, 0x584fc27920487ceab242392391418d7f/json.Val>)

def check source · line 648 · raw

@+p:Policy -> @key:Key -> @+now:U32 -> @parts:Parts -> IO(Result<&1, &1, Err, 0x584fc27920487ceab242392391418d7f/json.Val>)

The pinned alg, then the signature, then the claims.

def verify.parsed source · line 652 · raw

@+p:Policy -> @key:Key -> @+now:U32 -> @r:Result<&1, &1, Err, Parts> -> IO(Result<&1, &1, Err, 0x584fc27920487ceab242392391418d7f/json.Val>)

def verify source · line 660 · raw

@+p:Policy -> @key:Key -> @token:String -> @+now:U32 -> IO(Result<&1, &1, Err, 0x584fc27920487ceab242392391418d7f/json.Val>)

The claims of token when it is signed by key under p's alg and its claims hold at unix time now.

def jwk.two source · line 670 · raw

@+rsa:Bool -> @+kid:String -> @+alg:String -> @a:Maybe<&2, String> -> @b:Maybe<&2, String> -> Maybe<&2, Jwk>

def jwk.alg source · line 678 · raw

@m:Maybe<&2, Lit> -> String

An alg member that is not a string can match no pinned alg.

def jwk.use source · line 687 · raw

@m:Maybe<&2, Lit> -> Maybe<&2, Unit>

def jwk.kty source · line 696 · raw

@+kty:String -> @+kid:String -> @+f:List<&2, Field> -> Maybe<&2, Jwk>

def jwk.fields source · line 702 · raw

@+f:List<&2, Field> -> Maybe<&2, Jwk>

def jwk.of source · line 710 · raw

@v:0x584fc27920487ceab242392391418d7f/json.Val -> Maybe<&2, Jwk>

def jwk.push source · line 717 · raw

@m:Maybe<&2, Jwk> -> @acc:List<&2, Jwk> -> List<&2, Jwk>

def jwks.go source · line 724 · raw

@xs:List<&1, 0x584fc27920487ceab242392391418d7f/json.Val> -> @acc:List<&2, Jwk> -> List<&2, Jwk>

def jwks.arr source · line 731 · raw

@m:Maybe<&1, 0x584fc27920487ceab242392391418d7f/json.Val> -> Maybe<&2, List<&2, Jwk>>

def jwks.doc source · line 738 · raw

@m:Maybe<&1, 0x584fc27920487ceab242392391418d7f/json.Val> -> Maybe<&2, List<&2, Jwk>>

def jwks.parse source · line 747 · raw

@b:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> Maybe<&2, List<&2, Jwk>>

The signing keys of a JWK Set document; members it cannot use (enc keys, other curves, oct) are skipped. None when the document is not {"keys": [...]}.

def fam source · line 750 · raw

@a:Alg -> U32

def jwk.fits source · line 759 · raw

@k:Jwk -> @+kid:String -> @+a:Alg -> Bool

def jwk.pick source · line 765 · raw

@ks:List<&2, Jwk> -> @+kid:String -> @+a:Alg -> Maybe<&2, Jwk>

The first key named kid that can check alg a. RFC 7515 §4.1.4 leaves kid matching to exact comparison.

def jwk.key.of source · line 772 · raw

@rsa:Bool -> @a:Maybe<&1, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes> -> @b:Maybe<&1, 0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes> -> Result<&1, &1, Err, Key>

def jwk.key source · line 782 · raw

@k:Jwk -> Result<&1, &1, Err, Key>

A set member as a verify key.

def jwk.found source · line 786 · raw

@m:Maybe<&2, Jwk> -> Result<&1, &1, Err, Key>

def jwks source · line 799 · raw

@url:String -> @+ttl:U32 -> Jwks

def jwks.keys source · line 802 · raw

@s:Jwks -> List<&2, Jwk>

def fetch.keys source · line 806 · raw

@m:Maybe<&2, List<&2, Jwk>> -> @c:0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client -> @s:Jwks -> @+now:U32 -> Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Pair(Jwks, Result<&1, &1, Err, Unit>))

def fetch.body source · line 814 · raw

@ok:Bool -> @+status:U32 -> @c:0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client -> @s:Jwks -> @+now:U32 -> @b:0x49814d83de8f70993a43e1002be29ecd/bytes.Bytes -> Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Pair(Jwks, Result<&1, &1, Err, Unit>))

def fetch.res source · line 822 · raw

@c:0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client -> @s:Jwks -> @+now:U32 -> @r:Result<&1, &1, 0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res> -> Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Pair(Jwks, Result<&1, &1, Err, Unit>))

def fetch.done source · line 830 · raw

@s:Jwks -> @+now:U32 -> @x:Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Result<&1, &1, 0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Err, 0x6a946b742ab6f76f0ba5b90b14da52ad/http.Res>) -> Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Pair(Jwks, Result<&1, &1, Err, Unit>))

def fetch source · line 836 · raw

@c:0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client -> @s:Jwks -> @+now:U32 -> IO(Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Pair(Jwks, Result<&1, &1, Err, Unit>)))

GET the set now. On any failure the client and the old keys come back, with only tried moved to now.

def key.after.r source · line 842 · raw

@r:Result<&1, &1, Err, Unit> -> @c:0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client -> @+s:Jwks -> @+kid:String -> @+a:Alg -> Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Pair(Jwks, Result<&1, &1, Err, Key>))

def key.after source · line 850 · raw

@+kid:String -> @+a:Alg -> @x:Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Pair(Jwks, Result<&1, &1, Err, Unit>)) -> Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Pair(Jwks, Result<&1, &1, Err, Key>))

def key.refetch source · line 855 · raw

@go:Bool -> @c:0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client -> @s:Jwks -> @+kid:String -> @+a:Alg -> @+now:U32 -> IO(Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Pair(Jwks, Result<&1, &1, Err, Key>)))

def key.go source · line 865 · raw

@cached:Bool -> @+go:Bool -> @c:0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client -> @s:Jwks -> @found:Maybe<&2, Jwk> -> @+kid:String -> @+a:Alg -> @+now:U32 -> IO(Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Pair(Jwks, Result<&1, &1, Err, Key>)))

def since source · line 873 · raw

@+now:U32 -> @m:Maybe<&2, U32> -> U32

def key source · line 883 · raw

@c:0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client -> @s:Jwks -> @+kid:String -> @+a:Alg -> @+now:U32 -> IO(Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Pair(Jwks, Result<&1, &1, Err, Key>)))

The verify key named kid for alg a. A known key stays usable through the 60 s fetch cooldown even when ttl is shorter. After that, refresh a stale set or an unknown kid at most once a minute. ponytail: fixed 60 s cooldown; make it a Jwks field if an issuer rotates faster.

def jwks.run source · line 890 · raw

@r:Result<&1, &1, Err, Key> -> @c:0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client -> @s:Jwks -> @+p:Policy -> @+now:U32 -> @parts:Parts -> IO(Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Pair(Jwks, Result<&1, &1, Err, 0x584fc27920487ceab242392391418d7f/json.Val>)))

def jwks.keyed source · line 900 · raw

@+p:Policy -> @+now:U32 -> @parts:Parts -> @x:Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Pair(Jwks, Result<&1, &1, Err, Key>)) -> IO(Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Pair(Jwks, Result<&1, &1, Err, 0x584fc27920487ceab242392391418d7f/json.Val>)))

def jwks.kid source · line 905 · raw

@k:Maybe<&2, String> -> @+p:Policy -> @c:0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client -> @s:Jwks -> @+now:U32 -> @parts:Parts -> IO(Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Pair(Jwks, Result<&1, &1, Err, 0x584fc27920487ceab242392391418d7f/json.Val>)))

def jwks.pinned source · line 915 · raw

@ok:Result<&1, &1, Err, Unit> -> @+p:Policy -> @c:0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client -> @s:Jwks -> @+now:U32 -> @+h:List<&2, Field> -> @parts:Parts -> IO(Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Pair(Jwks, Result<&1, &1, Err, 0x584fc27920487ceab242392391418d7f/json.Val>)))

def jwks.parsed source · line 923 · raw

@+p:Policy -> @c:0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client -> @s:Jwks -> @+now:U32 -> @r:Result<&1, &1, Err, Parts> -> IO(Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Pair(Jwks, Result<&1, &1, Err, 0x584fc27920487ceab242392391418d7f/json.Val>)))

def verify.jwks source · line 933 · raw

@+p:Policy -> @c:0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client -> @s:Jwks -> @token:String -> @+now:U32 -> IO(Pair(0x5f997a9351e7133e43ba9611e734f8a6/hairpin.Client, Pair(Jwks, Result<&1, &1, Err, 0x584fc27920487ceab242392391418d7f/json.Val>)))

verify with the key the token's kid names in the set. The alg is pinned before any fetch. The client and the set (refreshed or not) always come back.