Skip to content

refactor: remove duplicated logic and parse each token once - #29

Merged
bsg62 merged 6 commits into
mainfrom
refactor/simplify-cleanups
Aug 27, 2026
Merged

bsg62 merged 6 commits into
mainfrom
refactor/simplify-cleanups

Conversation

@bsg62

@bsg62 bsg62 commented Aug 27, 2026

Copy link
Copy Markdown
Member

Quality-only pass over the Go sources: reuse, simplification, efficiency, and altitude. No user-facing behavior changes except the one noted below. 624 insertions, 968 deletions.

Production code

Parse once per run. decodeJWTHuman parsed the token, verifySignature parsed it again, and verifyClaims a third time, so a single --key --verify-claims run decoded the same segments four times and built eight maps to show three. parseUnverifiedJWT now returns a parsedJWT (raw string, token, segments, claims) threaded through the decode, signature, and claim steps.

The re-parse existed because formatTimestamps rewrites claim values in place, leaving the parsed map fit only for printing. printParsedJWT now formats a maps.Clone, so the parse stays authoritative and reusable.

One authority for key precedence. classifyKeyArg described itself as mirroring loadKeyForKID's ladder, but a mirror is a second copy. loadKeyForKID now switches on classifyKeyArg and delegates to loadSymmetricKeyFile/loadKeyFile/loadInlineKey, so the reading the CLI reports is by construction the reading that happens.

Deduplication.

  • parseAnyDER replaces the six-parser DER cascade that was written out verbatim twice.
  • parseKeyData's SSH branch was unreachable — every caller discarded its error and rebuilt the message via unsupportedKeyError, which does its own SSH detection. Removed, so the hint exists once.
  • partSize formats base64URLLen's result instead of re-decoding; jweEncryptedParts gives both output paths one splitter.
  • printVerdict renders the VALID/INVALID line for the signature and claim checks, which had it in duplicate. The two verdicts stay independent; only the rendering is shared.
  • Decrypted payloads decode once into any and type-switch, instead of an object parse followed by an array parse; printSection widened to any so the array case stops re-implementing it.
  • isJWT next to isJWE, so token-shape dispatch has one definition per form.
  • Fast path in the escape helpers, so unescaped output allocates nothing.

Tests

Suite time 4.2s → 1.7s. generateRSAKey generated a fresh 2048-bit key ~60 times per run; it is now generated once via sync.OnceValues and shared, with generateDistinctRSAKey for the ten sites that need a second key that must differ.

  • Six near-identical "unparseable key must not become an HMAC secret" tests → one 16-row table, every input kept.
  • Seven JWE algorithm-family tests → shared assertJWEHeaderOnly/assertJWEDecrypts; same 62 subtests over the same algorithms and payloads. jwe_test.go drops 434 lines.
  • Five key-file writers and twelve inline filepath.Join(t.TempDir(), …) sites → writeTempFile/writePEMFile/writePublicKeyFile/writeJWKFile.
  • Cross-file fixtures pinTime and decodeJWTJSONMap moved into helpers_test.go per AGENTS.md.

Behavior change

An existing but unreadable key file now reports reading key file "x": permission denied instead of falling through to a base64 attempt on its own path and reporting "neither a valid file path nor base64-encoded data". No test pinned the old behavior; documented in AGENTS.md.

Verification

gofmt, go vet, and go test -count=1 ./... clean. Beyond the suite:

  • Consolidated tests confirmed not to be silently passing: 62 JWE subtests before and after, and an injected bogus expectation fails as it should.
  • printEncryptedParts column alignment pinned against a literal (the existing test only checks substrings).
  • Built binary smoke-tested for human output, --json, wrong-key exit 1, and the new unreadable-file error.
  • /code-review over main..HEAD found no correctness bugs. It independently fuzzed the escapeFormattedJSONControls fast path against the old rune loop over 20k random byte strings including invalid UTF-8 (byte-identical), and checked all 60 generateRSAKey sites for the distinct-key requirement. Its two low findings are fixed in a66b0da.

Not done

The json.Valid guard in formatTimestamps was flagged as dead code by two reviewers. It is not — a test feeds hand-constructed json.Number("1/2"), "0x10", "1p2", which big.Rat.SetString accepts. Left in place with a comment explaining why.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Q26LwBBSBPUpbf1kd4Ep3z

bsg62 and others added 6 commits August 27, 2026 21:09
Extract parseAnyDER for the DER parser cascade that was written out twice,
and drop parseKeyData's unreachable SSH branch so unsupportedKeyError is the
only place that phrases a rejection.

Share the JWE part splitting and sizing between the human and --json paths,
collapse printEncryptedParts' four copy-pasted blocks into a loop, and add
printVerdict for the VALID/INVALID rendering the signature and claim checks
had in duplicate. The verdicts stay independent; only the rendering is shared.

Decode the decrypted payload once and type-switch instead of attempting an
object parse and then an array parse, widening printSection to any so the
array case stops re-implementing it. Add isJWT next to isJWE so token-shape
dispatch has one definition per form, and give the escape helpers a fast
path so unescaped output allocates nothing.

In tests, delete signHS256 (a twin of signJWTWithHMAC), collapse six
near-identical "unparseable key must not become an HMAC secret" tests into
one table keeping every input, and move the cross-file pinTime and
decodeJWTJSONMap fixtures into helpers_test.go per AGENTS.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q26LwBBSBPUpbf1kd4Ep3z
decodeJWTHuman parsed the token, verifySignature parsed it again, and
verifyClaims a third time, so a single run decoded the same segments up to
four times and built eight maps to show three. parseUnverifiedJWT now returns
a parsedJWT (raw string, token, segments, claims) that is threaded through the
decode, signature, and claim steps; the string-taking verifySignature and
verifyClaims remain as wrappers for callers that hold only the compact token.

The re-parse existed because formatTimestamps rewrites claim values in place,
leaving the parsed map fit only for printing. printParsedJWT now formats a
maps.Clone of the claims, so the parse stays authoritative and reusable.

classifyKeyArg described itself as mirroring loadKeyForKID's precedence, but a
mirror is a second copy: loadKeyForKID now switches on classifyKeyArg and
delegates to loadSymmetricKeyFile/loadKeyFile/loadInlineKey, so the reading the
CLI reports is by construction the reading that happens. One behavior change
falls out: an existing but unreadable file now reports the read error instead
of falling through to a base64 attempt on its own path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q26LwBBSBPUpbf1kd4Ep3z
Every generateRSAKey call generated a fresh 2048-bit key, ~60 of them per
run, which dominated the suite's wall time. The key is only ever read, so it
is now generated once with sync.OnceValues and shared; the ten call sites
that need a second key that must differ from the first ask for it explicitly
via generateDistinctRSAKey. Suite time drops from ~4.2s to ~1.9s.

The five key-file writers each open-coded marshal, PEM-encode, create, encode,
and twelve more sites open-coded filepath.Join(t.TempDir(), …) + os.WriteFile
with their own failure message. They now go through writeTempFile and
writePEMFile, with writePublicKeyFile covering RSA, ECDSA, and Ed25519 alike
(MarshalPKIXPublicKey handles all three) and writeJWKFile/writeJWKSetFile
covering the marshal-then-write JWK sites.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q26LwBBSBPUpbf1kd4Ep3z
Seven algorithm-coverage tests repeated the same two subtest bodies —
decode without a key and assert the header sections, decode with one and
assert the payload — differing only in how that family's key is made. They
now call assertJWEHeaderOnly/assertJWEDecrypts, and the AESKW and AESGCMKW
tests merge into one table since they differed only in key size and content
encryption. Key setup also moves out of the subtests, so each family builds
its key once instead of once per subtest.

Same 62 subtests over the same algorithms and payloads; the header-only
cases now additionally assert the Encrypted Content section, which the
per-family copies asserted inconsistently.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q26LwBBSBPUpbf1kd4Ep3z
The helper's doc promised the output "once on a miss" but printed it per
missing substring, so a regression dropping a whole section dumped the same
output up to four times. Misses are now collected and reported together,
which also names every value the section took with it in one place.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q26LwBBSBPUpbf1kd4Ep3z
printVerdict formatted the trailing newline inside the color wrapper, so the
bold attribute spanned the line break; output truncated there (piped into
head, say) left the terminal with the attribute active. Fprintln puts the
newline after the reset, restoring the bytes the verdict lines had before
printVerdict was extracted.

Also move verifySignature into helpers_test.go. Production code always holds
a parsedJWT and calls printSignatureVerdict, so the string-taking wrapper had
no callers outside the tests and should not sit in the package as if it were
an entry point.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q26LwBBSBPUpbf1kd4Ep3z
@bsg62
bsg62 merged commit 5f39e06 into main Aug 27, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant