Skip to content

Prototype: use jose with breaking async/WebCrypto APIs - #39783

Draft
Jeremy Meng (jeremymeng) wants to merge 2 commits into
Azure:mainfrom
jeremymeng:jeremymeng-prototype-jose-migration
Draft

Prototype: use jose with breaking async/WebCrypto APIs#39783
Jeremy Meng (jeremymeng) wants to merge 2 commits into
Azure:mainfrom
jeremymeng:jeremymeng-prototype-jose-migration

Conversation

@jeremymeng

Copy link
Copy Markdown
Member

Copilot agent :copilot: (on behalf of Jeremy Meng (@jeremymeng)): This is a throwaway alternative prototype for issue #39762 that evaluates replacing the @azure/attestation production JWS implementation with jose when major-version breaking API and platform changes are allowed. It is intentionally a draft exploration, not a production-ready replacement while unpolyfilled React Native remains supported.

Addresses #39762. Compare with the merge-ready synchronous compatibility approach in #39766.

Breaking API changes and propagation

  • AttestationToken.getTokenProblems(...) changes from string[] to Promise<string[]>.
  • createAttestationPolicyToken(...) changes from AttestationPolicyToken to Promise<AttestationPolicyToken>.
  • AttestationTokenImpl.create, JWS creation/verification, signing-key validation, and certificate key-type detection become asynchronous.
  • Async propagation reaches two AttestationClient response-validation paths, six AttestationAdministrationClient response-validation paths, four administration request-token creation paths, two certificate-management key-type checks, samples, snippets/README, and tests.
  • Public client methods retain their existing Promise-returning signatures. The validation callback, token constructor, getBody(), serialize(), and token property getters remain synchronous.

jose and X.509 design

  • RS256 and ES256 use importPKCS8, importX509, CompactSign, and compactVerify with explicit algorithm allowlists.
  • Key/certificate matching verifies the newly created signature with the certificate public key, without exporting private key material.
  • alg: none uses jose base64url primitives because jose compact signing rejects none, while UnsecuredJWT would reserialize claims and cannot preserve raw or empty payloads.
  • importX509 covers public-key import and RSA/P-256 detection, so @peculiar/x509 is not required.
  • jose accepts a certificate truncated after its SPKI, so preserving malformed-certificate rejection requires a small canonical DER-envelope length check. The prior custom cryptographic/key DER parser is removed. Full trust, validity, or chain validation would still require a dedicated X.509 library and remains out of scope.
  • Cached dynamic jose subpath imports preserve the package's Node 22.0 CommonJS entry point even though jose v6 is ESM-only.

Evidence

  • Production source: +167 / -584 lines, net -417.
  • Runtime crypto dependencies: @noble/curves + node-forge -> zero-dependency MIT jose@^6.2.9.
  • Comparable browser ESM, tree-shaken, minified, gzip-9 measurement: 50,503 bytes for Noble/Forge vs 8,532 bytes for jose, a reduction of 41,971 bytes / 83.1%. This aligns with Replace deprecated jsrsasign dependency in @azure/attestation #39766's reported approximately 51 KB baseline.
  • Noble/Forge and esbuild remain dev-only in this throwaway branch to reproduce baseline measurements and existing test helpers.

Runtime and validation

  • Node ESM and CommonJS load successfully.
  • Turbo build: 18/18 package and dependency tasks, including browser, React Native, ESM, CommonJS, and API extraction.
  • Node tests: 44 passed, 5 skipped.
  • Chromium tests: 49 passed, 5 skipped.
  • Samples typecheck; format/check-format pass; lint has only four pre-existing warnings.
  • The deterministic harness covers RSA and EC signing/verification, unsecured tokens, RSA/EC mismatched keys, non-canonical signatures, API changes, dependencies, production LOC, and bundle measurements.

Run all prototype evidence from the repository root:

pnpm --dir sdk/attestation/attestation prototype:jose

Platform and security decision

The browser and Node paths are viable and materially smaller. React Native 0.84 does not provide crypto.subtle: its target compiles, but runtime signing and verification require a tested WebCrypto native bridge/polyfill or a platform-specific fallback. A jose-only replacement therefore cannot preserve out-of-box React Native support.

Security benefits include maintained JOSE code, native WebCrypto, explicit RS256/ES256 allowlists, no private-key export, canonical compact-JWS checks, and substantially less custom crypto/DER code. Tradeoffs include runtime-dependent WebCrypto behavior, retained unsecured-token semantics, no certificate trust validation, and an extra verification operation during signing to prove key/certificate ownership.

This draft remains pending decisions on the major-version async API change and whether WebCrypto may become a platform prerequisite, React Native may be dropped, or a tested React Native bridge/fallback is acceptable.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1c3d3029-218c-4152-ba5f-09e19ef12976
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1c3d3029-218c-4152-ba5f-09e19ef12976
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
9 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant