PQC: RS256 JWT login-token signing has no ML-DSA/hybrid migration path
@tharindu1st is already working on this.
Since Sep 15, 2026.
- Dominant language
- Go
- Stars
- 71
- Forks
- 111
- Avg merge
- 1d 14h
- Merged PRs (30d)
- 110
Description
Summary
platform-api's local JWT auth (auth.mode = "internal_token" / "file") signs and verifies tokens with RS256 only, with no PQC/hybrid migration path — per .claude/rules/post-quantum-cryptography.md directive 1, the only prior acknowledgement of this was an inline // TODO(pqc): migrate comment, which the rule explicitly treats as equivalent to shipping the violation unfixed. This issue is the tracked remediation directive 1 requires in place of that comment.
Code:
platform-api/config/config.go— theJWTconfig struct (now documents the gap properly instead of a bare TODO; see the PR removing the comment) andLoadPublicKey/LoadPrivateKey, which only parse PEM-encoded RSA keys.platform-api/internal/handler/auth_login.go—Loginsigns withjwt.SigningMethodRS256unconditionally.platform-api/internal/middleware/auth.go—validateLocalJWTonly acceptsRS256/RS384/RS512(*jwt.SigningMethodRSA).platform-api/internal/server/server.go— wires the RSA public key into both theinternal_tokenandfile-mode middleware.
Key generation/provisioning is equally RSA-only, so a real fix isn't just the Go code — every path that provisions the keypair mounted at auth.jwt.public_key_file/private_key_file also hardcodes openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 with no alternative:
platform-api/scripts/setup-local-dev.shdistribution/all-in-one/docker-compose.yaml(platform-api-jwtkeygeninit-container)tests/integration-e2e/*.yaml,tests/ai-workspace-cli-e2e/docker-compose.yamlkubernetes/helm/api-portal-helm-chart/generate-secrets.sh,kubernetes/helm/ai-workspace-helm-chart/generate-secrets.shportals/scripts/setup.sh,samples/mcp-registry-catalog/setup.sh,portals/api-portal/it/Makefile,portals/api-portal/docs/administer/manage-organizations.md
Why this isn't fixed in the same PR that removes the TODO comment
Implementing real ML-DSA-65 (FIPS 204) signing requires a Go library — the standard library's crypto/internal/fips140/mldsa is unexported/unusable from application code. The only viable option found (github.com/cloudflare/circl) is not currently an approved dependency, and adding it needs to go through the standard vetting path this repo requires for any new Go dependency before it can be used in a security-critical auth path — see .claude/rules/dependency-management.md (latest-version check, govulncheck, go-licenses check, transitive-dependency diff, all before opening the PR that adds it) and the wso2/engineering-governance approved-dependency-registry gate in CI. That is real, separate work and shouldn't be bundled silently into the fix that removes the TODO.
Scope of the full fix (tracked here)
- Get an ML-DSA-65 (FIPS 204) Go library through the dependency-management.md vetting process and the engineering-governance registry. Leading candidate:
github.com/cloudflare/circl'ssign/mldsa/mldsa65package (the FIPS-204-final API — notsign/dilithium/mode3, which is the older, differently-encoded pre-standardization round-3 submission). - Add
Auth.JWT.Algorithmconfig ("RS256"default /"ML-DSA-65"), implement ajwt.SigningMethodfor ML-DSA-65, and wire it throughconfig.go(LoadPublicKey/LoadPrivateKey),auth_login.go(signing),internal/middleware/auth.go(validateLocalJWT's explicit algorithm allowlist — GO-AUTH-002/directive 6, never inferred from the token header), andinternal/server/server.go. - Update every key-generation script listed above to be able to mint an ML-DSA-65 keypair as an alternative to RSA-2048, and document the new
auth.jwt.algorithmoption. - Account for ML-DSA-65's larger key/signature sizes (~1952B public key, ~3309B signature) in any relevant size budgets (post-quantum-cryptography.md directive 4).
- Once tooling and ecosystem support are in place, evaluate flipping the default from RS256 to ML-DSA-65.
RS256 remains the correct, documented, config-gated fallback in the meantime per directive 1 — this is a phased migration, not a regression.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.