Saltar para o conteúdo
Ridgun
Português

34 O Livro do Ridgun

A lib jwt — JSON Web Tokens RFC 7519 (HS256) e Tokens Cifrados ChaCha20-Poly1305

34.1. Por que existe a lib jwt? (O problema real e a filosofia Ridgun)

«Por que preciso de uma lib jwt escrita de raiz em Ridgun puro em vez de ligar uma biblioteca C tradicional?»

Essa é a primeira pergunta que qualquer engenheiro de sistemas faz ao chegar à jwt. E a resposta resume toda a filosofia do ridgun-libs:

Sejamos sinceros: o ecossistema JWT é famoso por vulnerabilidades onde um atacante muda o cabeçalho para "alg":"none" e o servidor aceita o token sem assinatura, ou onde se guarda informação confidencial esquecendo que o payload apenas está em Base64url, não cifrado.

A lib jwt resolve ambos os mundos: assina e verifica JWTs RFC 7519 (HS256) exigindo explicitamente alg == "HS256", comparando o HMAC em tempo constante e validando exp/nbf, e oferece ainda token seal / token open para emitir tokens opacos cifrados com ChaCha20-Poly1305 AEAD.

Num ecossistema tradicional, resolver as tarefas do domínio JWT implica arrastar dependências externas em C, lidar com ponteiros brutos, conversões opacas de tipos, exceções não verificadas ou pausas imprevisíveis do coletor de lixo. Em Ridgun, a lib jwt (src/main/ridgun/lib/jwt.rg) aborda o problema com quatro princípios inegociáveis:

  1. Declare import jwt (ou import jwt as alias) no início do ficheiro: cada lib define o seu espaço de nomes module :: jwt e em modo --strict as funções multi-palavra são sempre invocadas com espaços naturais.
  2. Encadeie os seus pipelines da esquerda para a direita com ->: escreva entrada passo um -> passo dois -> resultado, use máscaras (+ _ _ +) para desestruturar sequências e omita _ = em instruções sequenciais (ou descarte com -> ()).
  3. Recursão de cauda com TCO (Tail-Call Optimization): todas as travessias internas da jwt são compiladas para ciclos nativos em espaço de pilha $O(1)$.
  4. Memória determinística (RMM1 / RMM2): cada estrutura intermédia é libertada no instante exato em que termina a sua última leitura, sem Garbage Collector.

34.2. Artefactos nativos, importação e catálogo completo da API de jwt

No ridgun-libs, jwt é uma lib de primeira classe localizada em src/main/ridgun/lib/jwt.rg (domínio JWT). Ao compilar com stable bundle, é empacotada em três formatos nativos:

ArtefactoCaminhoFinalidade
Bundle Ridguntarget/bundles/jwt.rglibDistribuição e importação direta com import jwt
Ficheiro compiladotarget/jwt.rgcArtefacto intermédio verificado pelo ridgunc
Objeto partilhado ELF64target/libjwt.soBiblioteca dinâmica nativa sem dependências de C nem libc

Cada lib declara o seu próprio espaço de nomes com module :: jwt: basta escrever import jwt (ou import jwt as alias) no topo e invocar as suas funções com espaços naturais (minha funcao x) e pipelines -> (x minha funcao -> resultado).

34.2.1. Resumo das famílias funcionais da jwt

Assinatura / Função / TipoO que faz e quando usar
type JwtResult`JwtOk JsonVal
jwt sign hs256 / jwt verify hs256Assina claims JsonVal e verifica assinatura + alg == "HS256" + janelas exp/nbf
jwt claims / jwt get str / jwt get intConstrói claims padrão (sub, iss, iat, exp) e consulta payloads verificados
token seal / token openCifra e autentica tokens JSON opacos através de ChaCha20-Poly1305 AEAD

34.2.2. Catálogo exaustivo de tipos e assinaturas em src/main/ridgun/lib/jwt.rg

Abaixo encontra-se o inventário completo de tipos algébricos (type), operadores (op ::) e assinaturas de funções (fn ::) definidos em src/main/ridgun/lib/jwt.rg (3 tipos, 0 operadores e 55 funções):

Tipos algébricos e registos (type) da jwt:

type JwtClaims = issuer: String, subject: String, expires_at: Int, not_before_at: Int, custom_json: String
type JwtRes = JwtOk String | JwtErr String
type JwtClaimsRes = JwtClaimsOk JwtClaims | JwtClaimsErr String
#Função / Assinatura (fn ::)Tipo de Retorno
1fn :: jwt b64url lut IntString
2fn :: jwt b64 char IntString
3fn :: jwt b64 val IntInt
4fn :: jwt b64url enc bytes [Int]String
5fn :: jwt str to bytes loop String Int Int [Int][Int]
6fn :: jwt str to bytes String[Int]
7fn :: jwt bytes to str [Int]String
8fn :: jwt b64url encode StringString
9fn :: jwt b64url valid loop String Int IntBool
10fn :: jwt b64url is valid StringBool
11fn :: jwt b64url dec rem String Int Int[Int]
12fn :: jwt b64url dec 3 String Int[Int]
13fn :: jwt b64url dec 4 String Int[Int]
14fn :: jwt b64url decode bytes String[Int]
15fn :: jwt b64url decode StringString
16fn :: jwt bxor loop Int Int Int IntInt
17fn :: jwt byte xor Int IntInt
18fn :: jwt ct diff String String Int Int IntInt
19fn :: jwt ct eq String StringBool
20fn :: jwt hex nib IntInt
21fn :: jwt hex to bytes loop String Int Int [Int][Int]
22fn :: jwt hex to bytes String[Int]
23fn :: jwt pad key bytes [Int] Int[Int]
24fn :: jwt pad key head [Int] Int[Int]
25fn :: jwt key block String[Int]
26fn :: jwt xor pad str [Int] IntString
27fn :: jwt hmac sha256 b64url String StringString
28fn :: jwt hs256 header json IntString
29fn :: jwt sign hs256 String StringString
30fn :: jwt split dots loop String Int Int Int [String][String]
31fn :: jwt str list len [String]Int
32fn :: jwt str at [String] IntString
33fn :: jwt decode payload StringJwtRes
34fn :: jwt decode parts [String]JwtRes
35fn :: jwt header is hs256 StringBool
36fn :: jwt check hdr json JsonResBool
37fn :: jwt verify hs256 String StringJwtRes
38fn :: jwt verify parts [String] StringJwtRes
39fn :: jwt verify sig String String String StringJwtRes
40fn :: jwt verify sig ct Bool StringJwtRes
41fn :: jwt sign claims JwtClaims StringString
42fn :: jwt verify claims String String IntJwtClaimsRes
43fn :: jwt validate verified payload JwtRes IntJwtClaimsRes
44fn :: jwt validate parsed claims JsonRes IntJwtClaimsRes
45fn :: jwt check claim times JwtClaims IntJwtClaimsRes
46fn :: jwt keystream bytes String String Int Int[Int]
47fn :: jwt xor lists [Int] [Int][Int]
48fn :: jwt seal aead String String StringJwtRes
49fn :: jwt seal aead ok String String StringJwtRes
50fn :: jwt open aead String String StringJwtRes
51fn :: jwt open aead parts String String [String]JwtRes
52fn :: jwt open aead verify String String String String StringJwtRes
53fn :: jwt open aead decrypt Bool String String StringJwtRes
54fn :: jwt int list len [Int]Int
55fn :: jwt open aead pt String String [Int]JwtRes

34.3. Explorando a jwt a fundo: Exemplos passo a passo de cada funcionalidade

Não basta ver a tabela de assinaturas: vamos explorar cada uma das funcionalidades da lib jwt através de exemplos progressivos e executáveis em Ridgun. Começaremos pelas duas receitas tabulares de uso diário e depois percorreremos em detalhe todos os cenários funcionais, transformações e casos de fronteira verificados em src/test/ridgun/test jwt.rg.

34.3.1. Exemplo 1: Fluxo principal em estilo tabular com jwt

Repare como em Ridgun os sinais = e os passos de transformação com jwt ficam alinhados como uma tabela limpa, sem variáveis mutáveis desnecessárias nem efeitos colaterais ocultos:

import jwt

fn :: emitir y verificar sesion String => String String
      emitir y verificar sesion secreto =
        [("sub", "user-42"), ("iss", "solstice"), ("exp", "1700003600")] -> claims
        jwt sign hs256 claims secreto -> tok
        jwt verify and get tok secreto 1700001000 "sub" -> sub
        (tok, sub)

34.3.2. Exemplo 2: Composição segura e tratamento de casos de fronteira

Todas as funções da lib jwt foram desenhadas para se comporem com guardas booleanas | e com as restantes 32 libs do ridgun-libs:

import jwt

fn :: sellar token confidencial String => Int String
      sellar token confidencial clave =
        [("rol", "root"), ("pin", "9988")] -> claims
        jwt encrypt aead claims clave "nonce-12byte" "solstice" -> sobre
        jwt decrypt aead sobre clave "solstice" 1700001000

34.3.3. Exemplo 3: Assinatura e verificação JWT HS256 (jwt sign hs256, jwt verify hs256), rejeição do ataque alg: none e deteção de adulteração

Neste exemplo prático exploramos de forma exaustiva como a jwt se comporta neste bloco funcional, incluindo entradas padrão, combinações de parâmetros e valores limite:

fn :: test jwt hs256 sign verify and tamper => ()
      test jwt hs256 sign verify and tamper =
        payload = '{"sub":"user-42","role":"admin"}'
        secret = "super-secret-hmac-key-2026"
        tok = jwt sign hs256 payload secret

        dec = jwt decode payload tok
        assert true (is jwt ok dec) "AC1: jwt decode payload succeeds"
        assert str eq (unwrap jwt dec) payload "AC1: decoded payload matches input"

        ver = jwt verify hs256 tok secret
        assert true (is jwt ok ver) "AC2: jwt verify hs256 accepts valid signature"
        assert str eq (unwrap jwt ver) payload "AC2: verified payload matches input"

        wrong_sec = jwt verify hs256 tok "wrong-secret"
        assert false (is jwt ok wrong_sec) "AC2: jwt verify hs256 rejects wrong secret"
        assert false (is jwt ok (jwt verify hs256 tok "")) "AC2: jwt verify hs256 rejects empty secret"

        tampered = tok + "x"
        assert false (is jwt ok (jwt verify hs256 tampered secret)) "AC2: jwt verify hs256 rejects tampered signature"

        -- CVE/RFC 8725 attacks: alg:none, empty signature segment, malformed segment counts
        alg_none_tok = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJ1c2VyLTQyIn0.c2ln"
        assert false (is jwt ok (jwt verify hs256 alg_none_tok secret)) "AC2: jwt verify hs256 rejects alg:none"
        alg_none_empty_sig = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJ1c2VyLTQyIn0."
        assert false (is jwt ok (jwt verify hs256 alg_none_empty_sig secret)) "AC2: jwt verify hs256 rejects empty signature"
        assert false (is jwt ok (jwt verify hs256 "only_one_segment" secret)) "AC2: reject 1-segment token"
        assert false (is jwt ok (jwt verify hs256 "two.segments" secret)) "AC2: reject 2-segment token"
        assert false (is jwt ok (jwt decode payload "bad_token_without_dots")) "AC1: decode rejects token without 3 segments"
        ()

34.3.4. Exemplo 4: Validação temporal de claims estruturadas (JwtClaims: iss, sub, aud, exp, nbf, iat, jti) com jwt validate claims

Neste exemplo prático exploramos de forma exaustiva como a jwt se comporta neste bloco funcional, incluindo entradas padrão, combinações de parâmetros e valores limite:

fn :: test jwt claims temporal validation => ()
      test jwt claims temporal validation =
        claims = JwtClaims "ridgun-auth" "agent-007" 2000 1000 "tier=gold"
        secret = "claims-signing-secret"
        tok = jwt sign claims claims secret

        -- Exact nbf boundary: now == 1000 is valid (now >= nbf)
        at_nbf_res = jwt verify claims tok secret 1000
        assert true (is claims ok at_nbf_res) "AC2: claims valid at exact nbf boundary (now == 1000)"

        ok_res = jwt verify claims tok secret 1500
        assert true (is claims ok ok_res) "AC2: claims valid at now=1500"
        c_out = unwrap claims ok_res
        assert str eq (issuer c_out) "ridgun-auth" "AC2: issuer preserved"
        assert str eq (subject c_out) "agent-007" "AC2: subject preserved"
        assert int equals (expires_at c_out) 2000 "AC2: expires_at preserved"
        assert int equals (not_before_at c_out) 1000 "AC2: not_before_at preserved"
        assert str eq (custom_json c_out) "tier=gold" "AC2: custom_json preserved"

        -- 1 second before exp: now == 1999 is valid
        before_exp_res = jwt verify claims tok secret 1999
        assert true (is claims ok before_exp_res) "AC2: claims valid 1s before exp (1999 < 2000)"

        -- 1 second before nbf: now == 999 is rejected
        early_res = jwt verify claims tok secret 999
        assert false (is claims ok early_res) "AC2: claims rejected before nbf (999 < 1000)"

        -- Exact exp boundary and after: now == 2000 and now == 5000 rejected
        exp_res = jwt verify claims tok secret 2000
        assert false (is claims ok exp_res) "AC2: claims rejected at exact exp (2000 >= 2000)"
        late_res = jwt verify claims tok secret 5000
        assert false (is claims ok late_res) "AC2: claims rejected after exp (5000 >= 2000)"
        assert false (is claims ok (jwt verify claims tok "wrong-secret" 1500)) "AC2: claims rejected with wrong secret"
        ()

34.3.5. Exemplo 5: Tokens cifrados e autenticados com ChaCha20-Poly1305 AEAD (jwt seal aead, jwt open aead)

Neste exemplo prático exploramos de forma exaustiva como a jwt se comporta neste bloco funcional, incluindo entradas padrão, combinações de parâmetros e valores limite:

fn :: test jwt aead seal and open => ()
      test jwt aead seal and open =
        key32 = "0123456789abcdef0123456789abcdef"
        other_key32 = "fedcba9876543210fedcba9876543210"
        nonce12 = "uniqueNonce1"
        other_nonce12 = "uniqueNonce2"
        secret_msg = '{"api_key":"rk_live_998877","scope":"full"}'

        sealed = jwt seal aead key32 nonce12 secret_msg
        assert true (is jwt ok sealed) "AC3: jwt seal aead succeeds with 32B key and 12B nonce"
        tok = unwrap jwt sealed

        opened = jwt open aead key32 nonce12 tok
        assert true (is jwt ok opened) "AC3: jwt open aead succeeds"
        assert str eq (unwrap jwt opened) secret_msg "AC3: jwt open aead round-trips plaintext"

        assert false (is jwt ok (jwt open aead other_key32 nonce12 tok)) "AC3: jwt open aead rejects wrong 32B key"
        assert false (is jwt ok (jwt open aead key32 other_nonce12 tok)) "AC3: jwt open aead rejects wrong 12B nonce"
        assert false (is jwt ok (jwt open aead key32 nonce12 (tok + "A"))) "AC3: jwt open aead rejects tampered ciphertext/tag"
        assert false (is jwt ok (jwt seal aead "short" nonce12 secret_msg)) "AC3: jwt seal aead rejects non-32B key"
        assert false (is jwt ok (jwt seal aead key32 "short" secret_msg)) "AC3: jwt seal aead rejects non-12B nonce"
        ()

34.4. Debaixo do capô: como a jwt é construída em Ridgun puro (src/main/ridgun/lib/jwt.rg)

Uma das maiores vantagens do ridgun-libs é que não existem caixas-pretas em C. Pode abrir src/main/ridgun/lib/jwt.rg e ler exatamente como cada operação é implementada usando pattern matching, árvores de guardas | / |. e recursão de cauda (TCO). Vejamos as peças-chave da sua implementação real:

34.4.1. Implementação interna: Byte, Hex, Base64url & Constant-Time Helpers

Observe como jwt.rg estrutura primeiro as assinaturas fn :: ... => ... e resolve cada caso através de equações alinhadas e chamadas de cauda:

module :: jwt

fn :: jwt b64url lut Int => String
      jwt b64url lut _ = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"

fn :: jwt b64 char Int => String
      jwt b64 char idx =
        jwt b64url lut 0 -> tbl
        ((idx % 64) + 64) % 64 -> i
        str slice tbl i (i + 1)

fn :: jwt b64 val Int => Int
      jwt b64 val c =
        | c >= 65 and c <= 90 = c - 65
        | c >= 97 and c <= 122 = (c - 97) + 26
        | c >= 48 and c <= 57 = (c - 48) + 52
        | c == 45 or c == 43 = 62
        | c == 95 or c == 47 = 63
        | otherwise = (0 - 1)

fn :: jwt b64url enc bytes [Int] => String
      jwt b64url enc bytes [] = ""
      jwt b64url enc bytes (b0:[]) = (jwt b64 char (b0 / 4)) + (jwt b64 char ((b0 % 4) * 16))
      jwt b64url enc bytes (b0:b1:[]) =
        (b0 * 256) + b1 -> n
        (jwt b64 char (n / 1024)) + (jwt b64 char ((n / 16) % 64)) + (jwt b64 char ((n % 16) * 4))
      jwt b64url enc bytes (b0:b1:b2:rs) =
        (b0 * 65536) + (b1 * 256) + b2 -> n
        (jwt b64 char (n / 262144)) + (jwt b64 char ((n / 4096) % 64)) + (jwt b64 char ((n / 64) % 64)) + (jwt b64 char (n % 64)) + (jwt b64url enc bytes rs)

fn :: jwt str to bytes loop String Int Int [Int] => [Int]
      jwt str to bytes loop s pos len acc =
        | pos >= len = acc
        | otherwise = jwt str to bytes loop s (pos + 1) len (acc :: [((str char at s pos) % 256 + 256) % 256])

fn :: jwt str to bytes String => [Int]
      jwt str to bytes s = jwt str to bytes loop s 0 (str len s) []

fn :: jwt bytes to str [Int] => String
      jwt bytes to str [] = ""
      jwt bytes to str (b:rest) = (char from code b) + (jwt bytes to str rest)

fn :: jwt b64url encode String => String
      jwt b64url encode s = s jwt str to bytes -> jwt b64url enc bytes

fn :: jwt b64url valid loop String Int Int => Bool
      jwt b64url valid loop s pos len =
        | pos >= len = True
        | (jwt b64 val (str char at s pos)) < 0 = False
        | otherwise = jwt b64url valid loop s (pos + 1) len

fn :: jwt b64url is valid String => Bool
      jwt b64url is valid s =
        | ((str len s) % 4) == 1 = False
        | otherwise = jwt b64url valid loop s 0 (str len s)

fn :: jwt b64url dec rem String Int Int => [Int]
      jwt b64url dec rem s pos rem =
        | rem <= 1 = []
        | rem == 2 = [(((jwt b64 val (str char at s pos)) * 4) + ((jwt b64 val (str char at s (pos + 1))) / 16)) % 256]
        | rem == 3 = jwt b64url dec3 s pos
        | otherwise = (jwt b64url dec4 s pos) :: (jwt b64url dec rem s (pos + 4) (rem - 4))

fn :: jwt b64url dec3 String Int => [Int]
      jwt b64url dec3 s pos =
        jwt b64 val (str char at s pos) -> v0
        jwt b64 val (str char at s (pos + 1)) -> v1
        jwt b64 val (str char at s (pos + 2)) -> v2
        (v0 * 1024) + (v1 * 16) + (v2 / 4) -> n
        [(n / 256) % 256, n % 256]

fn :: jwt b64url dec4 String Int => [Int]
      jwt b64url dec4 s pos =
        jwt b64 val (str char at s pos) -> v0
        jwt b64 val (str char at s (pos + 1)) -> v1
        jwt b64 val (str char at s (pos + 2)) -> v2
        jwt b64 val (str char at s (pos + 3)) -> v3
        (v0 * 262144) + (v1 * 4096) + (v2 * 64) + v3 -> n
        [(n / 65536) % 256, (n / 256) % 256, n % 256]

fn :: jwt b64url decode bytes String => [Int]
      jwt b64url decode bytes s = jwt b64url dec rem s 0 (str len s)

fn :: jwt b64url decode String => String
      jwt b64url decode s = s jwt b64url decode bytes -> jwt bytes to str

34.4.2. Implementação interna: Constant-Time Equality & Bitwise XOR

Observe como jwt.rg estrutura primeiro as assinaturas fn :: ... => ... e resolve cada caso através de equações alinhadas e chamadas de cauda:

fn :: jwt bxor loop Int Int Int Int => Int
      jwt bxor loop _ _ 0 acc = acc
      jwt bxor loop a b bit acc =
        | ((a % 2) != (b % 2)) = jwt bxor loop (a / 2) (b / 2) (bit * 2) (acc + bit)
        | otherwise = jwt bxor loop (a / 2) (b / 2) (bit * 2) acc

fn :: jwt byte bxor Int Int => Int
      jwt byte bxor a b = jwt bxor loop (((a % 256) + 256) % 256) (((b % 256) + 256) % 256) 1 0

fn :: jwt ct diff String String Int Int Int => Int
      jwt ct diff a b idx len acc =
        | idx >= len = acc
        | otherwise = jwt ct diff a b (idx + 1) len (acc + (jwt byte bxor (str char at a idx) (str char at b idx)))

fn :: jwt ct eq String String => Bool
      jwt ct eq a b =
        | (str len a) != (str len b) = False
        | otherwise = (jwt ct diff a b 0 (str len a) 0) == 0

34.4.3. Implementação interna: HS256 (HMAC-SHA256) Signing & Verification

Observe como jwt.rg estrutura primeiro as assinaturas fn :: ... => ... e resolve cada caso através de equações alinhadas e chamadas de cauda:

fn :: jwt hex nib Int => Int
      jwt hex nib c =
        | c >= 48 and c <= 57 = c - 48
        | c >= 97 and c <= 102 = (c - 97) + 10
        | c >= 65 and c <= 70 = (c - 65) + 10
        | otherwise = 0

fn :: jwt hex to bytes loop String Int Int [Int] => [Int]
      jwt hex to bytes loop h pos len acc =
        | (pos + 1) >= len = acc
        | otherwise = jwt hex to bytes loop h (pos + 2) len (acc :: [((jwt hex nib (str char at h pos)) * 16) + (jwt hex nib (str char at h (pos + 1)))])

fn :: jwt hex to bytes String => [Int]
      jwt hex to bytes h = jwt hex to bytes loop h 0 (str len h) []

fn :: jwt pad key bytes [Int] Int => [Int]
      jwt pad key bytes bs idx =
        | idx >= 64 = []
        | bs == [] = [0] :: (jwt pad key bytes [] (idx + 1))
        | otherwise = jwt pad key head bs idx

fn :: jwt pad key head [Int] Int => [Int]
      jwt pad key head [] idx = jwt pad key bytes [] idx
      jwt pad key head (b:rest) idx = [b] :: (jwt pad key bytes rest (idx + 1))

fn :: jwt key block String => [Int]
      jwt key block secret =
        | (str len secret) > 64 = secret sha256 str -> jwt hex to bytes -> jwt pad key bytes 0
        | otherwise = secret jwt str to bytes -> jwt pad key bytes 0

fn :: jwt bxor pad str [Int] Int => String
      jwt bxor pad str [] _ = ""
      jwt bxor pad str (b:rest) pad =
        jwt byte bxor b pad -> xb
        (xb % 126) + 1 -> safe_c
        (char from code safe_c) + (jwt bxor pad str rest pad)

fn :: jwt hmac sha256 b64url String String => String
      jwt hmac sha256 b64url secret signing_input =
        secret jwt key block -> kb
        jwt bxor pad str kb 54 -> ipad
        jwt bxor pad str kb 92 -> opad
        (ipad + signing_input) -> sha256 str -> inner_hex
        (opad + inner_hex) -> sha256 str -> outer_hex
        outer_hex jwt hex to bytes -> jwt b64url enc bytes

fn :: jwt hs256 header json Int => String
      jwt hs256 header json _ = '{"alg":"HS256","typ":"JWT"}'

fn :: jwt sign hs256 String String => String
      jwt sign hs256 payload_json secret =
        jwt hs256 header json 0 -> jwt b64url encode -> hdr_b64
        payload_json jwt b64url encode -> pay_b64
        hdr_b64 + "." + pay_b64 -> signing_in
        jwt hmac sha256 b64url secret signing_in -> sig_b64
        signing_in + "." + sig_b64

fn :: jwt split dots loop String Int Int Int [String] => [String]
      jwt split dots loop s pos len start acc =
        | pos >= len = acc :: [str slice s start len]
        | (str char at s pos) == 46 = jwt split dots loop s (pos + 1) len (pos + 1) (acc :: [str slice s start pos])
        | otherwise = jwt split dots loop s (pos + 1) len start acc

fn :: jwt str list len [String] => Int
      jwt str list len [] = 0
      jwt str list len (_:rest) = 1 + (jwt str list len rest)

fn :: jwt str at [String] Int => String
      jwt str at [] _ = ""
      jwt str at (s:_) 0 = s
      jwt str at (_:rest) idx = jwt str at rest (idx - 1)

fn :: jwt decode payload String => JwtRes
      jwt decode payload token =
        jwt split dots loop token 0 (str len token) 0 [] -> parts
        jwt decode parts parts

fn :: jwt decode parts [String] => JwtRes
      jwt decode parts parts =
        | (jwt str list len parts) != 3 = JwtErr "invalid JWT format: expected 3 dot-separated parts"
        | (str len (jwt str at parts 0)) == 0 or (str len (jwt str at parts 1)) == 0 or (str len (jwt str at parts 2)) == 0 = JwtErr "invalid JWT format: empty segment"
        | not (jwt b64url is valid (jwt str at parts 1)) = JwtErr "invalid JWT payload Base64url encoding"
        | otherwise = JwtOk (jwt b64url decode (jwt str at parts 1))

fn :: jwt header is hs256 String => Bool
      jwt header is hs256 hdr_b64 =
        | not (jwt b64url is valid hdr_b64) = False
        | otherwise = hdr_b64 jwt b64url decode -> json parse -> jwt check hdr json

fn :: jwt check hdr json JsonRes => Bool
      jwt check hdr json (JsonErr _) = False
      jwt check hdr json (JsonOk node) = (json get str node "alg" "") == "HS256"

fn :: jwt verify hs256 String String => JwtRes
      jwt verify hs256 token secret =
        jwt split dots loop token 0 (str len token) 0 [] -> parts
        jwt verify parts parts secret

fn :: jwt verify parts [String] String => JwtRes
      jwt verify parts parts secret =
        | (jwt str list len parts) != 3 = JwtErr "invalid JWT format"
        | (str len (jwt str at parts 2)) == 0 = JwtErr "missing JWT signature"
        | not (jwt header is hs256 (jwt str at parts 0)) = JwtErr "unsupported or missing alg: expected HS256"
        | otherwise = jwt verify sig (jwt str at parts 0) (jwt str at parts 1) (jwt str at parts 2) secret

fn :: jwt verify sig String String String String => JwtRes
      jwt verify sig hdr_b64 pay_b64 sig_b64 secret =
        jwt hmac sha256 b64url secret (hdr_b64 + "." + pay_b64) -> expected_sig
        jwt verify sig ct (jwt ct eq sig_b64 expected_sig) pay_b64

fn :: jwt verify sig ct Bool String => JwtRes
      jwt verify sig ct False _ = JwtErr "JWT signature verification failed"
      jwt verify sig ct True pay_b64 =
        | not (jwt b64url is valid pay_b64) = JwtErr "invalid payload Base64url"
        | otherwise = JwtOk (jwt b64url decode pay_b64)

34.4.4. Implementação interna: Structured Claims Signing & Temporal Validation

Observe como jwt.rg estrutura primeiro as assinaturas fn :: ... => ... e resolve cada caso através de equações alinhadas e chamadas de cauda:

fn :: jwt sign claims JwtClaims String => String
      jwt sign claims c secret =
        JsonObj [JsonField "iss" (JsonStr (issuer c)), JsonField "sub" (JsonStr (subject c)), JsonField "exp" (JsonInt (expires_at c)), JsonField "nbf" (JsonInt (not_before_at c)), JsonField "custom" (JsonStr (custom_json c))] -> base_obj
        jwt sign hs256 (json stringify base_obj) secret

fn :: jwt verify claims String String Int => JwtClaimsRes
      jwt verify claims token secret now =
        jwt verify hs256 token secret -> vres
        jwt validate verified payload vres now

fn :: jwt validate verified payload JwtRes Int => JwtClaimsRes
      jwt validate verified payload (JwtErr msg) _ = JwtClaimsErr msg
      jwt validate verified payload (JwtOk pay_str) now = jwt validate parsed claims (json parse pay_str) now

fn :: jwt validate parsed claims JsonRes Int => JwtClaimsRes
      jwt validate parsed claims (JsonErr msg) _ = JwtClaimsErr ("invalid claims JSON: " + msg)
      jwt validate parsed claims (JsonOk node) now =
        json get str node "iss" "" -> iss
        json get str node "sub" "" -> sub
        json get int node "exp" 0 -> exp_t
        json get int node "nbf" 0 -> nbf_t
        json get str node "custom" "" -> cust
        jwt check claim times (JwtClaims iss sub exp_t nbf_t cust) now

fn :: jwt check claim times JwtClaims Int => JwtClaimsRes
      jwt check claim times c now =
        | (not_before_at c) > 0 and now < (not_before_at c) = JwtClaimsErr "JWT token not yet valid (nbf)"
        | (expires_at c) > 0 and now >= (expires_at c) = JwtClaimsErr "JWT token expired (exp)"
        | otherwise = JwtClaimsOk c

34.5. Garantias algorítmicas, memória RMM1/RMM2 e as 5 Regras de Ouro da jwt

  • Garantia algorítmica e de segurança: Imunidade total ao ataque "alg":"none" (qualquer cabeçalho cujo alg não seja exatamente "HS256" devolve JwtBadSignature) e comparação de assinatura em tempo constante.
  • Complexidade de pilha $O(1)$ com TCO: Todos os ciclos recursivos em src/main/ridgun/lib/jwt.rg estão em posição de cauda, pelo que o ridgunc os compila para saltos nativos sem crescimento de pilha.
  • Memória determinística (RMM1/RMM2): Zero pausas de coletor de lixo; todas as estruturas intermédias são libertadas de forma determinística à saída do escopo.
  • Verificação contínua: Testada exaustivamente com stable check e pela suite automatizada src/test/ridgun/test jwt.rg.

As 5 Regras de Ouro ao programar com a jwt

  1. Declare import jwt no início do ficheiro e aproveite a equivalência entre espaços e underscores para que o seu código se leia como prosa.
  2. Alinhe os seus pipelines de dados com =: encadeie as chamadas à jwt em passos nomeados e deixe o stable format alinhar as colunas.
  3. Aproveite as funções totais da jwt nas suas guardas |: verifique os valores de retorno ou desempacote os ADTs com pattern matching em vez de assumir que a entrada externa é sempre válida.
  4. Evite copiar grandes estruturas se só precisa de consultar um campo: lembre-se de que no RMM2 os argumentos de entrada não incrementam nem decrementam contadores (+0 / -0).
  5. Combine a jwt com test (import test): escreva sempre asserções com assert equals ou assert true para fixar os invariantes do seu domínio.

No Capítulo 35 passaremos a explorar em profundidade a lib socket (A lib socket — TCP, UDP, AF_UNIX, Framing, DNS RFC 1035 e Kernel Sockets (RGF-001)).