From Newsgroup: comp.lang.tcl
ANNOUNCE: jwt 1.1 released
==========================
jwt is a pure Tcl extension for JSON Web Tokens: it signs and verifies
compact JWT/JWS tokens, with HMAC (HS256, HS512) or NaCl onetime authentication. Written by Alexander Schoepe, it requires Tcl 8.6 or
newer, including Tcl 9, plus the C extensions nacl 1.3 or newer and rl_json.
1.1 is the first standalone release. The code was embedded in websh up
to version 3.7.7 and is now maintained as a package of its own, with a
TEA build, a tcltest suite and the BSD 3-Clause license. There is no C
code in it: one installed copy serves Tcl 8.6 and Tcl 9 alike.
Download / Repository:
https://fossil.sowaswie.de/jwt
English
-------
The API:
* ::jwt::sign header payload secret takes header and payload as JSON
text and returns the token as header.payload.signature in base64url. The algorithm is read from the header field alg, with HS256 as the default,
plus HS512 and NaCl.
* ::jwt::verify token secret ?-json? ?-claims? ?-leeway seconds? returns
a boolean by default and checks the signature only.
* ::jwt::base64url_encode and ::jwt::base64url_decode are public,
because callers need them for salts, keys and binary blobs of their own.
The secret is used at whatever length it has. For HS256 and HS512 it is
handed to nacl unchanged, which prepares the HMAC key as RFC 2104
section 2 defines it: a key longer than the hash block size is replaced
by its hash, a shorter one is padded with zero bytes, and there is no
fixed length anywhere in between. This matters because in JWT the key is rarely chosen by the verifying side. The Nextcloud app OIDC Identity
Provider, for one, generates client secrets of 64 characters and accepts
32 to 64 in its web interface; the same holds for webhook secrets from
GitHub, Stripe or GitLab. A token signed here with such a secret is now
byte for byte what PHP's hash_hmac, Python's hmac or jwt.io produce for
it. Only the NaCl algorithm keeps a fixed key length, because Poly1305
is defined with 32 bytes; a secret for that one is padded or truncated
as before.
This is why nacl 1.3 is a hard requirement rather than a recommendation.
Up to 1.2 the package accepted keys of exactly 32 bytes and rejected everything else, so with an older nacl the HMAC algorithms now fail with
an explicit error instead of quietly signing with a shortened key.
Signature comparison in verify happens inside nacl, in constant time via crypto_verify_32, rather than through a string comparison in Tcl.
Behaviour worth knowing:
* Time claims are opt-in. Only with -claims are exp and nbf evaluated according to RFC 7519; claims that are absent are not enforced. Without
the option an expired token still verifies, which keeps the
signature-only contract for existing callers.
* -leeway adds a tolerance in seconds against clock skew between the
signing and the verifying host. It is effective together with -claims
only; a non-integer or negative value is treated as zero.
* With -json the result is an rl_json document holding verify, header
and payload, the latter two as raw, re-parsable JSON text rather than
embedded objects. Together with -claims it also carries reason, one of
ok, signature, notbefore, expired or payload, so a forged token can be
told apart from one that merely ran out of time.
* The runtime dependencies are loaded lazily on the first sign or verify
call, so sourcing the package costs nothing if it ends up unused.
Build, test, install:
* ./configure picks up the default Tcl via tclConfig.sh, make test runs
the suite, make install places the package in lib/jwt1.1 of that Tcl installation. To serve a second installation as well, re-run configure
with --with-tcl against it.
* The build works out of tree: every source file is referenced through
srcdir, so an object directory beside the source tree is fine.
Tests:
* 30 tcltest cases covering signing, signature verification with the
right and the wrong secret, the shape of the -json result document, exp
and nbf handling under -claims, and the leeway window including the case
where -leeway is given without -claims.
* Six of them cover secret lengths and check HS256 and HS512 with a 64
byte key against RFC 2104 reference values, so a regression that trims
the key again fails the suite rather than surfacing as a mysterious
rejection at the other end.
* All of them are guarded by the jwtDeps constraint, which requires jwt,
nacl and rl_json to load. If one of them is missing, make test succeeds
with everything skipped, so it is worth reading the skipped count and
not only the exit status.
Limitation: characters beyond the BMP, above U+FFFF, have different
string representations in Tcl 8.6 and Tcl 9. Payloads containing them
are therefore not portable across Tcl versions when they are coupled to ciphertext or a CRC.
Deutsch
-------
Die API:
* ::jwt::sign header payload secret erwartet Header und Payload als
JSON-Text und liefert das Token als header.payload.signature in
base64url. Der Algorithmus stammt aus dem Header-Feld alg, Vorgabe ist
HS256, daneben stehen HS512 und NaCl zur Verf|+gung.
* ::jwt::verify token secret ?-json? ?-claims? ?-leeway sekunden?
liefert standardm|n|fig einen Boolean und pr|+ft ausschlie|flich die Signatur. * ::jwt::base64url_encode und ::jwt::base64url_decode sind ||ffentlich,
weil Aufrufer sie f|+r eigene Salts, Schl|+ssel und Bin|nrdaten ben||tigen.
Das Secret wird in der L|nnge verwendet, die es hat. Bei HS256 und HS512
geht es unver|nndert an nacl, das den HMAC-Schl|+ssel so vorbereitet, wie
RFC 2104 Abschnitt 2 es definiert: Ein Schl|+ssel l|nnger als die
Blockgr|||fe des Hash wird durch seinen Hash ersetzt, ein k|+rzerer mit Nullbytes aufgef|+llt, und dazwischen gibt es keine feste L|nnge. Das ist deshalb wichtig, weil der Schl|+ssel bei JWT selten von der pr|+fenden
Seite gew|nhlt wird. Die Nextcloud-App OIDC Identity Provider etwa
erzeugt Client-Secrets mit 64 Zeichen und akzeptiert in ihrer
Weboberfl|nche 32 bis 64; f|+r Webhook-Secrets von GitHub, Stripe oder
GitLab gilt dasselbe. Ein hier signiertes Token stimmt mit einem solchen Secret jetzt Byte f|+r Byte mit dem |+berein, was PHPs hash_hmac, Pythons
hmac oder jwt.io daf|+r erzeugen. Nur der Algorithmus NaCl beh|nlt eine
feste Schl|+ssell|nnge, weil Poly1305 mit 32 Byte definiert ist; ein
Secret daf|+r wird wie bisher aufgef|+llt oder gek|+rzt.
Deshalb ist nacl 1.3 zwingend und nicht blo|f empfohlen. Bis 1.2 nahm das Paket ausschlie|flich Schl|+ssel von genau 32 Byte an und wies alles
andere zur|+ck; mit einem |nlteren nacl brechen die HMAC-Algorithmen jetzt also mit einer klaren Fehlermeldung ab, statt still mit einem verk|+rzten Schl|+ssel zu signieren.
Der Signaturvergleich in verify findet innerhalb von nacl statt, in
konstanter Zeit |+ber crypto_verify_32, und nicht als Stringvergleich in Tcl.
Wissenswertes zum Verhalten:
* Die Zeitclaims sind opt-in. Nur mit -claims werden exp und nbf gem|n|f
RFC 7519 ausgewertet, nicht vorhandene Claims werden dabei nicht
erzwungen. Ohne die Option verifiziert auch ein abgelaufenes Token
weiterhin, womit der Vertrag der reinen Signaturpr|+fung f|+r bestehende Aufrufer erhalten bleibt.
* -leeway erg|nnzt eine Toleranz in Sekunden gegen Uhrenversatz zwischen signierendem und pr|+fendem Rechner. Sie wirkt nur zusammen mit -claims;
ein nicht ganzzahliger oder negativer Wert gilt als null.
* Mit -json ist das Ergebnis ein rl_json-Dokument mit verify, header und payload, wobei Header und Payload als roher, wieder parsbarer JSON-Text abgelegt sind und nicht als eingebettete Objekte. Zusammen mit -claims
kommt reason hinzu, mit einem der Werte ok, signature, notbefore,
expired oder payload, sodass sich ein gef|nlschtes Token von einem blo|f abgelaufenen unterscheiden l|nsst.
* Die Laufzeitabh|nngigkeiten werden erst beim ersten sign- oder
verify-Aufruf geladen, das Einbinden des Pakets kostet also nichts,
solange es ungenutzt bleibt.
Bauen, testen, installieren:
* ./configure ermittelt das voreingestellte Tcl |+ber tclConfig.sh, make
test f|+hrt die Suite aus, make install legt das Paket unter lib/jwt1.1
dieser Tcl-Installation ab. F|+r eine zweite Installation gen|+gt ein
erneutes configure mit --with-tcl.
* Der Build funktioniert au|ferhalb des Quellbaums: Alle Quelldateien
werden |+ber srcdir angesprochen, ein eigenes Build-Verzeichnis neben den Quellen ist damit m||glich.
Tests:
* 30 tcltest-F|nlle f|+r das Signieren, die Signaturpr|+fung mit richtigem
und falschem Secret, die Struktur des -json-Ergebnisdokuments, die
Behandlung von exp und nbf unter -claims sowie das Leeway-Fenster einschlie|flich des Falls, dass -leeway ohne -claims angegeben wird.
* Sechs davon betreffen die Secret-L|nngen und pr|+fen HS256 und HS512 mit einem 64-Byte-Schl|+ssel gegen Referenzwerte nach RFC 2104. Ein R|+ckfall,
der den Schl|+ssel wieder k|+rzt, l|nsst damit die Suite scheitern, statt
am anderen Ende als unerkl|nrliche Zur|+ckweisung aufzutauchen.
* Alle sind |+ber das Constraint jwtDeps abgesichert, das jwt, nacl und rl_json voraussetzt. Fehlt eines davon, meldet make test Erfolg bei
komplett |+bersprungener Suite; ein Blick auf die Anzahl der
|+bersprungenen Tests lohnt sich daher mehr als der Exit-Status allein.
Einschr|nnkung: Zeichen jenseits der BMP, also oberhalb von U+FFFF, haben
in Tcl 8.6 und Tcl 9 unterschiedliche String-Repr|nsentationen. Payloads
mit solchen Zeichen sind deshalb nicht zwischen den Tcl-Versionen
portabel, sobald sie an Ciphertext oder eine Pr|+fsumme gekoppelt sind.
--- Synchronet 3.22a-Linux NewsLink 1.2