Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
9 check-ins
|
2026-07-31
| ||
| 23:36 | Version 1.2 leaf check-in: 1176718bd1 user: alex tags: trunk, version-1.2 | |
| 23:34 | Enforce the minimum key size RFC 7518 requires for the HMAC algorithms - RFC 7518 section 3.2 states that a key of at least the size of the hash output MUST be used, so 32 bytes for HS256, 48 for HS384 and 64 for HS512. jwt accepted any length, a nine byte secret included, and produced a token that interoperates perfectly and is simply weaker than the specification allows - the new minKeyBytes returns those three sizes and 0 for NaCl, which is no RFC 7518 algorithm and keeps its fixed 32 byte key through checkSecret - sign and verify both raise for a key below the minimum rather than failing the token. A short key is a local misconfiguration and not a property of what arrived, and returning false for it would hide the cause behind a wrong signature - in verify the check records the case in weakKey and raises after the catch block, because an error inside it would be reported as a malformed token. The part count and the other form checks still run first, so a broken token stays malformed and never reaches the key - unlike the earlier corrections this one breaks setups that were working: a secret shorter than the hash output signs and verifies no longer, where before it produced tokens every peer accepted. This is the deliberate choice of failing loudly over staying compatible with a weaker key - the test secret was itself illegal at nine bytes and is now 64, the wrong secret used in the negative cases likewise. Every reference value depending on it was recomputed with the Python hmac module rather than carried over - jwt-keylen-13.1 to 13.5 cover the rejection one byte below the limit for each algorithm, acceptance exactly at it, the same for verify, and that a malformed token is still reported as malformed instead of complaining about the key - jwt-conform-12.10 was rewritten because it was unsound: it mapped the characters - and _ in the signature and therefore silently tested nothing once a signature happened to contain neither, which is what the new secret produced. It now replaces a fixed position and covers plus, slash, equals and a space - tests/jwt.test grows to 81 tests, all passing on Tcl 9.0.4, and on 8.6.18 with the one astral character test skipped through the utf8Astral constraint check-in: 2da27d16e7 user: alex tags: trunk | |
| 23:24 | Encode header and payload as UTF-8 and require both to be JSON objects - RFC 7515 section 2 defines the signing input as BASE64URL of UTF8 of the header, and jwt handed the Tcl string straight to binary encode base64, which uses the internal one byte representation. A payload carrying Mueller was written with the umlaut as a single 0xfc instead of the two bytes 0xc3 0xbc, so the payload was not valid UTF-8 and the signature covered different octets than any peer would compute. Measured identically on Tcl 8.6.18, 9.0.4 and 9.1b0, so it was never a version artefact - sign now runs header and payload through utf8Encode and verify through utf8Decode. The signature is never converted, it is raw bytes. A token whose header or payload is not valid UTF-8 is rejected as malformed - the conversion deliberately does not live in base64url_encode and base64url_decode: those are public API and callers put salts, keys and binary blobs through them, which no character encoding survives - utf8Decode compares the round trip in addition to decoding, because Tcl 9 raises on an invalid sequence by itself while Tcl 8.6 substitutes it silently. The comparison catches it on both, without a version test - RFC 7519 section 7.2 steps 4 and 10 require the JOSE header and the claims set to be JSON objects. sign refuses a header or payload that is not one, and verify rejects a signed non object payload with reason payload and a header that is an array as malformed. The payload is checked after the signature, the order the RFC lists the steps in, so nothing unauthenticated is inspected for content - tokens made of ASCII are unchanged to the byte, since UTF-8 and ASCII coincide there. What breaks is what was already broken: a token whose header or payload carried anything outside ASCII could not be read by another implementation before - known limitation, now stated correctly in the README: Tcl 8.6 cannot convert characters beyond the BMP to UTF-8 at all, it substitutes U+FFFD and the character is lost. Tcl 9 encodes them correctly. Everything inside the BMP, umlauts and CJK included, is identical on both. The earlier wording blamed differing string representations, which was not the mechanism - tests/jwt.test grows to 76 tests with jwt-conform-12.12 to 12.19, checking the umlaut and CJK tokens against reference values from Python, the round trip, invalid UTF-8, and the JSON object rules. The new constraint utf8Astral probes whether the interpreter can encode beyond the BMP, so that one test skips on Tcl 8.6 instead of failing. All pass on Tcl 8.6.18 and 9.0.4 check-in: 5ed58d7010 user: alex tags: trunk | |
| 23:14 | Require nacl 1.4, add HS384, and close four gaps against the JWT RFCs - package require nacl 1.4- replaces the runtime probe of the tag length. The probe existed because two nacl builds both called themselves 1.3, one with -hmac512 and one without; the version now states it, and the helper requireHmac512 together with the needHmac512 detour through the catch block in verify is gone - the reason for the version floor is recorded at the require itself: Tcl matches option names by prefix, so on an older nacl the string -hmac512 is an unambiguous prefix of -hmac512256 and the call succeeds with 32 bytes where 64 were asked for, which no caller can detect at the call site - HS384 is implemented over nacl -hmac384 with its 48 byte tag, so the three HMAC algorithms of RFC 7518 section 3.2 are covered natively. Never substitute -hmac512256 for HS512: it is NaCl crypto_auth, the same computation cut to 32 bytes, and its tag is the first half of an -hmac512 tag - verify gains -alg, naming the algorithms the caller accepts, as RFC 8725 section 3.1 requires of a library. Without the option every implemented algorithm is accepted, which is what callers had before, so the option adds a capability without taking one away - alg is mandatory now, per RFC 7515 section 4.1.1. sign raises for a header without it instead of defaulting to HS256, and verify fails such a token with reason alg. The same path rejects the unsecured alg none of RFC 7518 section 3.6 - a crit header invalidates the token, per RFC 7515 section 4.1.11. This package implements no header extension at all, so a crit list can never be satisfied; it is evaluated before the signature, hence a crit token reports crit even when the secret is wrong as well - the three parts must be canonical base64url as defined in RFC 7515 section 2: no padding and no character outside the URL-safe alphabet. The check sits in the new isBase64url and deliberately not in base64url_decode, which is public API that callers feed padded data on purpose - reason gains the values alg and crit, the precedence is malformed, alg, crit, signature, notbefore, expired - three of these change behaviour for existing callers, which is unavoidable because that behaviour was the violation: a header without alg no longer signs, a token carrying crit no longer verifies, and a token with padding or the standard alphabet in any part no longer verifies - unchanged on purpose: -claims stays opt-in, so without it an expired token still verifies as true. That is the remaining distance to RFC 7519 section 4.1.4 and it is named as such in the README - tests/jwt.test grows to 67 tests, all passing, with the new cases jwt-conform-12.1 to 12.11 for the four rules and jwt-hs384-11.1 to 11.7 for HS384 against reference values from an outside HMAC implementation - README describes -alg, the mandatory alg, the crit rule, the base64url requirement and the full reason vocabulary check-in: afcdeb8e8b user: alex tags: trunk | |
| 19:55 | Version 1.1 check-in: 63c63c6e66 user: alex tags: trunk, version-1.1 | |
| 19:55 | Use HMAC secrets at their given length instead of forcing them to 32 bytes - jwt.tcl - checkSecret is no longer applied before the algorithm switch in sign and verify; HS256 and HS512 now hand the secret to nacl::auth unchanged, because RFC 2104 section 2 makes the preparation of the key part of HMAC itself and nacl 1.3 performs it for every key length - the NaCl branch keeps calling checkSecret at the point of use, since Poly1305 is defined with a fixed 32 byte key and nacl::onetimeauth still rejects anything else; the procedure carries a comment saying why HMAC must not go through it - package require nacl 1.3- makes the requirement explicit - with an older nacl the HMAC algorithms now fail with a clear error instead of silently signing with a shortened key - this changes behaviour for secrets whose length is not exactly 32 bytes - such tokens used to be signed with a NUL-padded or truncated key and were never interoperable, so a token issued before this change no longer verifies afterwards - the case that motivated it is a client secret of 64 characters as generated by the Nextcloud app OIDC Identity Provider, where the truncated key produced a signature that no other JWT implementation accepts; the same applies to webhook secrets from GitHub, Stripe or GitLab, since a secret chosen by the other side has no length one may assume - tests/jwt.test - six cases jwt-secret-6.1 to 6.6 covering HS256 and HS512 with a 64 byte key against RFC 2104 reference values computed with the Python hmac module, a short secret that is no longer NUL-padded, proof that a long secret is not truncated to its first 32 bytes, a verify round trip, and the NaCl branch as a counter check; the suite grows from 24 to 30 tests and passes under Tcl 8.6.18 with nacl 1.3 and rl_json 0.17.6 - README documents the secret handling per algorithm and the new minimum version of nacl check-in: 382be1611c user: alex tags: trunk | |
| 16:58 | Support out-of-tree builds and ignore the usual binary-extension configure options - Makefile.in - every source file is now referenced through $(srcdir), so the package can be built from a separate build directory while in-source builds are unchanged with srcdir set to the current directory; the rules for configure, Makefile, pkgIndex.tcl and manifest.txt take their prerequisites from the source tree, and autoconf is run there as well - manifest.txt is no longer appended to PKG_TCL_SOURCES, because that variable is now prefixed with $(srcdir) while manifest.txt is generated into the build directory; the install target therefore copies the Tcl sources and license.terms from the source tree and manifest.txt and pkgIndex.tcl from the build directory, and the archive target passes manifest.txt explicitly - mkversion.tcl and archive.sh are invoked via $(srcdir), with CYGPATH applied to the mkversion.tcl path for consistency with the other helper calls - tools/mkversion.tcl changes into the source tree before reading manifest, manifest.uuid and configure.ac, so it produces the same output no matter which build directory it is called from - configure.ac accepts and ignores --enable-shared and --enable-threads, so a shared build script that passes these options to a pure-Tcl package no longer triggers a warning about unrecognised arguments - configure regenerated from configure.ac check-in: d2ee938b04 user: alex tags: trunk | |
| 16:29 | Initial import of the jwt 1.1 package - jwt.tcl - pure Tcl package extracted from websh, providing ::jwt::sign and ::jwt::verify plus the public helpers base64url_encode and base64url_decode; the algorithm is taken from the header field alg, with HS256 as the default alongside HS512 and NaCl, and secrets are normalised to 32 bytes - signature checking stays the default - the time claims exp and nbf are evaluated only with the option -claims, while -leeway adds clock-skew tolerance and takes effect together with -claims only - option -json returns an rl_json document holding verify, header and payload as raw re-parsable JSON text, and together with -claims an additional field reason carrying ok, signature, notbefore, expired or payload - the runtime dependencies nacl and rl_json are loaded lazily on the first call through ::jwt::init - TEA build system - configure.ac, Makefile.in, pkgIndex.tcl.in, aclocal.m4, helper.tcl and the tclconfig directory; make test runs the tcltest suite, make install places the package under lib/jwt1.1, and one installed copy serves both Tcl 8.6 and Tcl 9 - configure.ac restores the user-supplied exec_prefix after TEA_PREFIX, otherwise the package installs into the Tcl tree regardless of --prefix, see ticket 1a64a34982 - tests/all.tcl and tests/jwt.test - regression tests for signature, claims, leeway and the shape of the JSON result, guarded by the constraint jwtDeps which requires nacl and rl_json - tools/mkversion.tcl generates manifest.txt from manifest, manifest.uuid and configure.ac, while tools/archive.sh builds the release archives used by make release and make publish - README documenting the API and the build steps, license.terms carrying the BSD 3-Clause license - .fossil-settings with manifest on and an ignore-glob covering the generated files Makefile, pkgIndex.tcl, manifest.txt, the config artefacts and autom4te.cache check-in: 46cf6af216 user: alex tags: trunk | |
| 15:27 | initial empty check-in check-in: 2107f30318 user: alex tags: trunk | |