JOSE profile with the standard library instead of a JOSE library¶
Context and Problem Statement¶
The server builds and verifies compact JWS in several places: access tokens (EdDSA, purpose
access_token), invocation tokens for agents (purpose invocation), checkpoints of the audit log
(purpose audit_checkpoint), DPoP proofs and step-up signatures of the devices (ES256, EdDSA),
ID tokens of the OIDC providers (RS256, PS256, ES256, EdDSA) and client assertions for
private_key_jwt (08-identity.md, sections 2.2, 6.2, 7.2 and 7.3;
19-security.md, section 12). These places are in different
modules (identity, instance, audit, runs), and domain packages may import only packages from
kernel-core, no third-party libraries (02-architecture.md,
section 5). Without a ruling, two build tickets of the same wave (PLAN M1-32 and M1-33) would
each have chosen their own solution. What does the server use to build and verify JWS?
Decision Drivers¶
- RFC 8725 requires that the caller specifies the allowed algorithms and that no others are used (section 3.1 "Perform Algorithm Verification"), and recommends an explicit type (section 3.11 "Use Explicit Typing") (https://www.rfc-editor.org/rfc/rfc8725.html, retrieved 2026-10-10) [verified].
- One implementation for all modules; signing and verifying must not drift apart.
- Usable from domain packages, that is, without a third-party library.
- Small attack surface: only the compact form, a few algorithms and JWK fingerprints per RFC 7638 are needed; JWE, JSON serialization and keys from headers are not.
- Few dependencies in a security path (supply chain, SEC-SC-1).
Considered Options¶
- Own JOSE profile with the Go standard library in the package
internal/kernel/jose - A JOSE library (for example
github.com/go-jose/go-jose) in the adapters - A comprehensive JOSE library (for example
github.com/lestrrat-go/jwx) in the adapters - Each module builds its JWS itself
Decision Outcome¶
Chosen: "Own JOSE profile with the standard library in internal/kernel/jose", because it is the
only option usable from domain packages, covers exactly the profile that is needed and enforces the
algorithm list on every call.
Profile (normative together with 08):
- Only the compact serialization of JWS (RFC 7515). No JWE, no JSON serialization.
- Every call names the allowed algorithms and the expected
typ.none,HS*and every algorithm that is not named are rejected, as are headers withcrit,jku,x5u,x5corzip. Algorithms of the profile:EdDSA(Ed25519),ES256(signature asR || Swith 64 bytes per RFC 7518, section 3.4, never DER),RS256andPS256(only for verifying ID tokens and signing client assertions). - The caller always supplies the verification key (
kidfrom its key list). The only exception is the publicjwkin the header of a DPoP proof (RFC 9449); ajwkwith private parts is rejected. - JWK fingerprints per RFC 7638 for
EC(P-256),OKP(Ed25519) andRSA. - Signing takes a signer over the signing input; private keys stay with the
caller (for server keys the port
instance.Keys, ADR 0066).
Who builds what: PLAN M1-33 creates the package with parsing, verifying (EdDSA, ES256),
fingerprints and assembling compact JWS; M1-34 uses it to assemble the access tokens; M1-41 adds
RS256 and PS256 (RSA keys with 2048 to 8192 bits only through ParseVerificationJWK, never for
device keys) and, when signing the client assertion, the header x5t#S256 that Entra requires for
certificates; verifying does not read it. Every extension of the profile (an algorithm, a header) needs a
change to this ADR.
Consequences¶
- Good, because every JWS path goes through the same algorithm check and no module introduces a second implementation.
- Good, because domain packages can verify JWS without breaking the layer rule.
- Good, because no third-party library is in the login path.
- Bad, because own code in the area of cryptography is a risk. Mitigation: only the structure and the validation of the format are own code, all cryptographic algorithms come from the standard library; test vectors from the RFCs, negative tests and a fuzz test (Confirmation).
- Bad, because a later need for JWE or further algorithms needs a new decision. That is intended.
Confirmation¶
- Unit tests in
internal/kernel/josewith the examples from RFC 7515 appendix A.3 (ES256), RFC 8037 appendix A.4 (Ed25519) and RFC 7638 section 3.1 (fingerprint). - Negative tests:
algequal tonone,HS256or not in the allowed list, wrongtyp,ES256signature in DER form,critin the header,jwkwith a private part, extra dots or characters outside base64url; all rejected. - Go fuzz test of the parser with a seed corpus in the normal test run (PLAN M1-33).
- The architecture gate (
make arch) allowsinternal/kernel/joseonly imports from the standard library (02-architecture.md, section 7.2).
Pros and Cons of the Options¶
Own profile with the standard library¶
- Good, because it is small, verifiable and usable from every layer.
- Good, because the list of allowed algorithms is a required parameter of every verification function.
- Bad, because the format code is written and maintained in-house.
go-jose in the adapters¶
- Good, because it is widespread and specialized in JOSE.
- Bad, because domain packages must not import it; verification would then be in adapters, separated from the rules it needs.
- Bad, because it supports more formats and algorithms than the product needs.
jwx in the adapters¶
- Good, because it brings JWT, JWK sets and caches.
- Bad, because it brings even more scope than needed and hits the same layer boundary.
Each module builds its own¶
- Good, because it is possible without coordination.
- Bad, because several implementations drift apart; a missing algorithm check in one place is enough for an attack.
More Information¶
- Related: ADR 0020 (DPoP), ADR 0066 (signing keys), ADR 0021 (audit checkpoints).
- Normative places: 08-identity.md, sections 7.2 and 7.3; 02-architecture.md, section 4.5.