dpop_client 2.0.0
dpop_client: ^2.0.0 copied to clipboard
DPoP proof JWTs for Dart (RFC 9449) — ES256 key handling, PEM storage, JWK thumbprints, access-token binding and server-supplied nonces.
2.0.0 #
Bug fixes to htu, a much cheaper signing path, and a dependency tree cut down
to what a proof signer actually needs.
Fixed #
htuno longer carries the userinfo component. A target URI (RFC 9110 §7.1) has none, so a URL of the formhttps://user:pass@host/pathproduced a claim the server could never match — and wrote the password into a JWT payload the server is free to log.htunormalises an empty path to/. RFC 9110 §7.1 has the client send/when the path is empty, so a proof forhttps://api.example.comwas compared against the server'shttps://api.example.com/and rejected.- The SEC 1 private key PEM pads the scalar to the full 32 octets. RFC 5915
§3 fixes the
privateKeyOCTET STRING at the curve's octet length; the previous encoder emitted a minimal-length field, so roughly one generated key in 256 was written to storage in a form strict parsers reject. Existing PEMs still read back correctly.
Added #
DPoPProof.headers()returns theAuthorizationheader too, under theDPoPscheme rather thanBearer, as RFC 9449 §7.1 requires for a bound token. The token is taken from the proof itself, so the header cannot disagree with theathclaim.DPoPProof.isTokenBoundreports whether a proof carries one.DPoPKeyPair.fromPrivateKeyPemaccepts PKCS#8PRIVATE KEYPEMs alongside the SEC 1EC PRIVATE KEYform, which is what most non-Dart tooling emits.- A key on any curve but P-256 is now refused with a clear message instead of being reinterpreted against P-256's parameters and failing later as an unexplained bad signature.
DPoPKeyPair.fromPemrejects a private and public half that do not belong together, rather than producing proofs whose embedded JWK cannot verify them.DPoPProofimplements==andhashCodeover its token.- A GitHub Actions workflow covering the SDK floor and
stable, a run against the oldest permitted dependency versions, and a publish dry run.
Changed #
- Signing a proof is roughly twice as fast. The private key was re-encoded to PEM and re-parsed on every single request, which cost more than the ECDSA signature itself; the signer now takes the parsed key directly, and the PEMs, the JWK and the thumbprint are each computed once per key pair.
basic_utilsis no longer a dependency, replaced bypointycastleandasn1libdirectly. This dropsarchive,ffi,http,http_parser,json_annotation,loggingandposixfrom the tree — a proof signer has no use for an HTTP client or an archive reader.privateKeyandpublicKeyremain the samepointycastletypes.keyPair.jwkis unmodifiable, now that one map is shared by every proof.- Unreadable key material throws
FormatExceptionconsistently. - Dependency lower bounds are widened to the oldest versions the package is tested against, so it resolves alongside more of the ecosystem. The SDK floor stays at 3.13.0.
1.0.0 #
Initial release, extracted from an internal application module and corrected against RFC 9449 on the way out.
DPoPKeyPair— ES256 (P-256) key handling:generate, PEM round-tripping viaprivateKeyPem, andfromPrivateKeyPem, which derives the public half from the private scalar so only one PEM has to be stored.DPoPKeyPair.jwk/jwkJson— the public key as a JWK, the form that travels in a proof's JWT header.DPoPKeyPair.thumbprint— the RFC 7638 SHA-256 JWK thumbprint, for checking an access token'scnf.jktagainst the key actually held.DPoPClient.createProof— a signeddpop+jwtper request, carryinghtm,htu,iatandjti, plusathwhen an access token is supplied andnoncewhen a server has demanded one.DPoPProof— the token with itsheaders()map, defaulting to the standardDPoPheader name and accepting a gateway-specific one.htunow excludes the query string and fragment, as RFC 9449 §4.2 requires. The internal original signed the full request URI, which a strict server rejects — and did so invisibly, since the offending query parameter was usually added by an interceptor after the call site.iatis truncated to whole seconds rather than rounded, so it cannot land up to half a second in the future and be refused by a server with no clock-skew allowance. This requirednoIssueAt: trueon the underlying signer, which otherwise overwritesiatwith its own clock — meaning the original's computed value never reached the token at all.- Dartdoc across the public API, a runnable
example/, and 36 tests covering the claim set, the header, signature verification,htunormalisation andathhashing.