Summary
| Change date | Mayo 18, 2026 |
| File changed | html/js/calendar/crypto-worker.js |
| Function affected | derivePasskeyKEK() |
| Algorithm | HKDF-SHA-256 → AES-GCM-256 (unchanged) |
| Reason | Iwasan ang known deriveKey() Worker hang path sa Safari / WebKit |
| Cryptographic impact | Wala. Parehong key material ang ginagawa ng dalawang path. |
| Verification | Kinukumpirma ng parity test tests/crypto/hkdf-equivalence.mjs ang identical decrypt behavior |
Background: Passkey KEK architecture
Pinoprotektahan ng PayCal ang per-user data gamit ang data encryption key (DEK) na naka-store nang encrypted, hindi kailanman plain text. Para ma-decrypt ito sa login time, nagde-derive ang PayCal ng key encryption key (KEK) mula sa passkey credential ng user.
Tumatakbo ang derivation sa loob ng Web Worker, isang sandboxed background thread na humahawak ng cryptographic operations nang hiwalay sa main browser thread. Tinatawag ng Worker ang WebCrypto API ng browser (crypto.subtle) para gawin ang HKDF key derivation step.
Ang input sa derivation ay ang stable credentialId mula sa WebAuthn passkey credential, hindi ang signature. Non-deterministic ang ECDSA signatures, kaya ang KEK na derived mula sa signature ay mag-iiba sa bawat login at mabibigo sa pag-unwrap ng DEK. Ang paggamit ng credentialId ang gumagarantiya na parehong KEK ang ginagawa tuwing nag-a-authenticate ang user gamit ang parehong device.
Ang HKDF parameters ay:
- Algorithm: HKDF-SHA-256
- IKM: UTF-8 encoding ng
credentialId - Salt: Per-user random 32-byte value na naka-store server-side
- Info: Fixed domain string
paycal-passkey-kek - Output: 256-bit AES-GCM key (non-extractable)
WebCrypto API: dalawang path papunta sa parehong key
May dalawang paraan ang WebCrypto API para gumawa ng derived key mula sa HKDF input:
crypto.subtle.deriveKey(algorithm, baseKey, derivedKeyType, extractable, usages)- Gumagawa ng HKDF derivation at direktang nagbabalik ng
CryptoKeyobject. Ito ang mas compact na call: isinasama ng caller ang output key algorithm, gaya ng AES-GCM-256, sa isang step. crypto.subtle.deriveBits(algorithm, baseKey, length)+crypto.subtle.importKey(format, keyData, algorithm, extractable, usages)- Gumagawa ng HKDF derivation at raw bytes muna ang ibinabalik. Pagkatapos, ini-import ng caller ang bytes bilang
CryptoKeygamit ang pangalawangimportKeycall. Two-step equivalent ito: identical ang resulting key object sa type, length, extractability, at permitted usages.
Parehong HKDF-SHA-256 function ang ginagamit ng dalawang path, parehong IKM, salt, at info parameters ang kino-consume, at parehong 256 bits ng key material ang ginagawa. Ang kaibahan lang ay kung paano pinoproseso internally ng Web Worker runtime ang call.
Ang problema: observed deriveKey() hang sa Safari / WebKit Web Workers
May operationally observed deriveKey() hang path sa Safari / WebKit kapag ang call ay ginawa mula sa loob ng Web Worker. Maaaring makaranas ang Safari users ng login flow na bigla na lang tumitigil, walang error, walang timeout, at walang visible feedback, habang hinihintay matapos ng Worker ang derivation.
Ang deriveBits() call, na ginagamit para gumawa ng parehong output, ay hindi nagpapakita ng hang na ito. Specific ang issue sa deriveKey() code path sa loob ng Worker execution context; hindi nito naaapektuhan ang underlying HKDF algorithm.
Parehong first-class WebCrypto APIs ang deriveKey() at deriveBits(), documented ng Apple at specified ng W3C. Identical ang semantics nila para sa use case na ito. Runtime regression ang hang, hindi design difference ng dalawang call.
Walang canonical public Apple bug ticket na indexed para sa exact Worker + HKDF + deriveKey() combination na ito. Tugma ito sa class ng Safari/WebKit edge-case crypto regressions na nadidiskubre operationally, nira-round around sa production, at hindi formally tracked sa public ticket. Engineering decision namin na iwasan ang problematic call path nang buo, anuman ang WebKit version, sa halip na mag-detect ng browser state at runtime.
Ano ang binago namin
In-update ang derivePasskeyKEK() function sa html/js/calendar/crypto-worker.js para gamitin ang deriveBits() + importKey() bilang sole derivation path. Tuluyang inalis ang deriveKey() call.
Ang bagong derivation sequence ay:
- I-import ang IKM bytes bilang HKDF base key gamit ang
importKey('raw', ikmMaterial, 'HKDF', false, ['deriveBits']) - Mag-derive ng 256 raw bits gamit ang
deriveBits(hkdfParams, ikm, 256) - I-import ang bits na iyon bilang AES-GCM-256 key gamit ang
importKey('raw', keyBits, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt'])
Hindi nagbago ang HKDF parameters (SHA-256 hash, per-user salt, fixed info string paycal-passkey-kek). Hindi nagbago ang output key type, length, at non-extractable flag. Fully compatible ang existing wrapped DEKs ng users na dating gumamit ng old code path: walang re-wrapping o re-enrollment na kailangan.
Cryptographic equivalence proof
Hindi lang reasoning ang basehan namin sa equivalence. Ang file tests/crypto/hkdf-equivalence.mjs ay may parity test na:
- Nagde-derive ng key via Path A (
deriveKey()) gamit ang parehong HKDF parameters - Nagde-derive ng key via Path B (
deriveBits()+importKey()) gamit ang parehong HKDF parameters - Nag-e-encrypt ng test payload gamit ang Path A key
- Nagde-decrypt ng ciphertext gamit ang Path B key
- Ina-assert na exact match ang decrypted bytes sa original plaintext
- Inuulit nang pabaliktad: encrypt with Path B, decrypt with Path A
Kung parehong successful ang decryptions at parehong bytes ang lumabas, operationally identical ang keys. Passing ang test sa Node.js 18+ at referenced ito sa aming SOC 2 false-positive adjudication ledger bilang evidence item CRYPTO-001.
Ano ang hindi nagbago
- HKDF-SHA-256 pa rin ang key derivation algorithm
- 256-bit AES-GCM pa rin ang output key
- Non-extractable pa rin ang output key mula sa Worker context
- Stable
credentialIdpa rin ang IKM source (hindi ECDSA signature) paycal-passkey-kekpa rin ang HKDF info string- Unchanged ang per-user salts
- Walang user action, re-enrollment, o credential rotation na kailangan
- Fully compatible ang data encryption key (DEK) na wrapped sa lumang KEK sa bagong KEK: parehong key ang bytes
Bakit namin ito ipinapublish
Hindi naaapektuhan ng pagbabagong ito ang security. Pero naaapektuhan nito kung paano kumikilos ang code namin sa isang specific browser engine, at hinahawakan nito ang pinaka-security-sensitive na bahagi ng client-side cryptography stack namin.
Ipinapublish namin ito dahil kailangan ng transparency policy namin na anumang pagbabago sa key derivation logic, kahit behaviorally neutral, ay publicly documented kasama ang technical reasoning at verification evidence para mareview ng users at auditors.
Hindi dapat maging invisible ang cryptographic changes, kahit intentionally inert ang mga ito.
Mga reference
-
WebKit Blog — Update on Web Cryptography
webkit.org/blog/7790/update-on-web-cryptography/
Official Apple/WebKit announcement adding HKDF and related WebCrypto improvements. Authoritative baseline for HKDF support in WebKit. -
Apple Developer — SubtleCrypto Documentation
developer.apple.com/documentation/webkitjs/subtlecrypto
Confirms bothderiveBits()andderiveKey()are first-class APIs in WebKit. Relevant to the architectural justification for usingderiveBits(). -
MDN — SubtleCrypto.deriveBits()
developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveBits
W3C-aligned specification reference forderiveBits(), including Worker support. Confirms this call is a valid and fully supported path in Worker contexts. -
MDN — SubtleCrypto.deriveKey()
developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveKey
W3C-aligned specification reference forderiveKey(). Comparing this with thederiveBits()spec confirms both are specified to work in Workers; the observed Safari hang is a runtime regression, not a spec divergence. -
Stack Overflow — HKDF output length and
deriveKeyvsderiveBitssemantics
stackoverflow.com/questions/79365928/how-to-specify-desired-hkdf-output-length-with-crypto-subtle-derivekey
Practical community discussion showing why developers intentionally preferderiveBits()for deterministic control and cross-browser compatibility when deriving HKDF keys.