feat(#6263): offload RSA crypto to host Web Crypto API for WASM - #6265
feat(#6263): offload RSA crypto to host Web Crypto API for WASM#6265fullsend-ai-coder[bot] wants to merge 1 commit into
Conversation
Extract RSA signature verification and signing into platform-specific functions (verifyRS256Signature, signRS256WithPEM) behind build tags. On native builds (//go:build !js), these use Go's crypto/rsa and math/big as before. On WASM builds (//go:build js), they delegate to JavaScript callbacks registered by the Cloudflare Worker host, using the Web Crypto API (crypto.subtle.verify/sign). This removes crypto/rsa, crypto/x509, math/big, and encoding/pem from the WASM dependency tree — the largest source of mint-specific bloat (~24 packages). The JWKS cache now stores raw jwkKey entries instead of *rsa.PublicKey, deferring key parsing to verification time (native) or passing JWK JSON to the host (WASM). Changes: - New crypto_native.go: verifyRS256Signature, signRS256WithPEM, parseRSAPublicKey (moved from jwks_verifier.go) - New crypto_js.go: same signatures, delegating to host callbacks via RegisterHostCrypto + awaitPromise - jwks_verifier.go: cache stores jwkKey, calls verifyRS256Signature - github.go: GenerateAppJWT calls signRS256WithPEM - cmd/mint-wasm: initMint accepts 5 args (adds verifyRS256, signRS256) - Worker index.ts: createVerifyRS256Callback, createSignRS256Callback using crypto.subtle.importKey + verify/sign - Embed sync: crypto_native.go in embeddedMintFiles, crypto_js.go in gcfSkip; all .embed copies synced GCF and standalone mint paths unchanged — they use crypto_native.go. Note: pre-commit could not run (sandbox network policy blocks git fetch for hook setup). The post-script runs an authoritative pre-commit on the runner. lint-mint-embed-sync passes locally. Closes #6263
|
🤖 Finished Review · ✅ Success · Started 7:30 PM UTC · Completed 7:46 PM UTC Commit: |
ReviewFindingsHigh
Medium
Low
Labels: PR modifies mint crypto offload between WASM and Cloudflare Worker host Next steps:
|
There was a problem hiding this comment.
See the review comment for full details.
Note: The following inline comments could not be posted on the diff (GitHub returned 422) and are included here instead:
internal/dispatch/cf/workersrc/src/index.ts:302: [high] api-contract
createSignRS256Callback uses crypto.subtle.importKey("pkcs8", ...) exclusively, but GitHub App private keys are historically issued in PKCS1 format (-----BEGIN RSA PRIVATE KEY-----). The Web Crypto API's pkcs8 format only accepts PKCS8-encoded DER data. When given PKCS1 DER, importKey will throw a DataError. The native Go path (signRS256WithPEM in crypto_native.go) correctly handles both PKCS1 and PKCS8 with a try/fallback; the JS WASM host path does not, creating a behavioral asymmetry that would break token signing for GitHub Apps with PKCS1 keys.
Suggested fix: Either convert PKCS1 DER to PKCS8 DER before calling importKey (prepend the PKCS8 header bytes to wrap the PKCS1 RSAPrivateKey structure), or attempt importKey with pkcs8 first, catch the error, and retry after wrapping the DER in a PKCS8 envelope.
internal/mintcore/crypto_js.go:51: [low] key-size-validation-bypass
WASM build path delegates verification to the host without enforcing the 2048-bit minimum RSA key size that the native path enforces in parseRSAPublicKey. Previously refreshKeys() enforced this at cache-population time for all builds. Real-world exploitability is negligible since the JWKS source is GitHub's OIDC provider.
cmd/mint-wasm/main.go(file-level): Line 4 · [low] stale-reference
Package-level doc comment still shows old 3-argument signature mintcoreInitMint(configJSON, fetchCallback, pemCallback). The function-level doc on line 40 was correctly updated to the 5-argument signature.
Suggested fix: Update line 4 to show the new 5-argument signature.
internal/mintcore/crypto_js.go:81: [low] trust-boundary
signRS256WithPEM passes raw PEM private key data across the WASM-to-JS boundary. No net-new exposure since the PEM data was already accessible to JS via createPemCallback.
internal/dispatch/cf/workersrc/src/index.ts:255: [low] base64url-padding
Base64url-to-binary conversion uses atob() without adding padding. Correct for Cloudflare Workers V8 but could be hardened with explicit padding.
internal/mintcore/crypto_native_test.go:33: [low] code-organization
Tautological comment '// crypto.SHA256 = crypto.SHA256' is a development leftover.
Suggested fix: Remove the comment.
|
🤖 Finished Retro · ✅ Success · Started 9:41 PM UTC · Completed 9:55 PM UTC Commit: |
Retro: PR #6265 — offload RSA crypto to host Web Crypto API for WASMOutcome: Closed without merging. The underlying WASM size regression (issue #6262) was resolved by a Timeline
What went well
What could go better
Proposals filed
|
Summary
Offloads RSA crypto operations (OIDC JWT verification and GitHub App JWT signing) from the Go WASM binary to the Cloudflare Worker host via Web Crypto API (
crypto.subtle). This removescrypto/rsa,crypto/x509,math/big, andencoding/pemfrom the WASM dependency tree — the largest source of mint-specific binary bloat (~24 packages).Changes
crypto_native.go(//go:build !js): Platform-specific RSA functions using Go's standard crypto —verifyRS256Signature,signRS256WithPEM,parseRSAPublicKey(moved fromjwks_verifier.go)crypto_js.go(//go:build js): Same function signatures, delegating to JavaScript callbacks registered viaRegisterHostCryptoduring WASM initjwks_verifier.go: JWKS cache stores rawjwkKeyentries instead of*rsa.PublicKey; verification callsverifyRS256Signature(platform-dispatched)github.go:GenerateAppJWTdelegates signing tosignRS256WithPEM(platform-dispatched)cmd/mint-wasm/main.go:initMintnow accepts 5 arguments (addsverifyRS256Callback,signRS256Callback)index.ts: NewcreateVerifyRS256CallbackandcreateSignRS256Callbackusingcrypto.subtle.importKey+verify/signcrypto_native.goadded toembeddedMintFiles,crypto_js.goadded togcfSkip; all.embedcopies syncedDesign
Follows the existing host-bridge pattern (
HostFetchDoer,HostPEMAccessor): WASM-only code incrypto_js.gocalls registered JS callbacks viaawaitPromise; native code incrypto_native.gouses Go's standard library. No behavior change for GCF or standalone mint — they linkcrypto_native.govia the!jsbuild tag.Testing
go test -race ./...passes ininternal/mintcore/(all existing tests + newcrypto_native_test.go)TestEmbeddedMintSource_MatchesOriginalpasses (embed sync verified)lint-mint-embed-synchook passesverifyRS256Signature(valid + invalid),signRS256WithPEM(PKCS1 + PKCS8 + invalid PEM)make wasm-buildreports before/after)Closes #6263
Post-script verification
agent/6263-offload-rsa-crypto-wasm)dc87d7d6484555ba91661a3e94f126287a27246f..HEAD)