@metamask-previews/passkey-controller 1.0.0-preview-53e60e1a5 → 1.0.0-preview-4e0ae1bc9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +30 -13
  3. package/dist/PasskeyController.cjs +178 -120
  4. package/dist/PasskeyController.cjs.map +1 -1
  5. package/dist/PasskeyController.d.cts +61 -60
  6. package/dist/PasskeyController.d.cts.map +1 -1
  7. package/dist/PasskeyController.d.mts +61 -60
  8. package/dist/PasskeyController.d.mts.map +1 -1
  9. package/dist/PasskeyController.mjs +180 -122
  10. package/dist/PasskeyController.mjs.map +1 -1
  11. package/dist/ceremony-manager.cjs +3 -2
  12. package/dist/ceremony-manager.cjs.map +1 -1
  13. package/dist/ceremony-manager.d.cts +3 -2
  14. package/dist/ceremony-manager.d.cts.map +1 -1
  15. package/dist/ceremony-manager.d.mts +3 -2
  16. package/dist/ceremony-manager.d.mts.map +1 -1
  17. package/dist/ceremony-manager.mjs +3 -2
  18. package/dist/ceremony-manager.mjs.map +1 -1
  19. package/dist/constants.cjs +2 -0
  20. package/dist/constants.cjs.map +1 -1
  21. package/dist/constants.d.cts +2 -0
  22. package/dist/constants.d.cts.map +1 -1
  23. package/dist/constants.d.mts +2 -0
  24. package/dist/constants.d.mts.map +1 -1
  25. package/dist/constants.mjs +2 -0
  26. package/dist/constants.mjs.map +1 -1
  27. package/dist/key-derivation.cjs +3 -35
  28. package/dist/key-derivation.cjs.map +1 -1
  29. package/dist/key-derivation.d.cts +5 -28
  30. package/dist/key-derivation.d.cts.map +1 -1
  31. package/dist/key-derivation.d.mts +5 -28
  32. package/dist/key-derivation.d.mts.map +1 -1
  33. package/dist/key-derivation.mjs +2 -33
  34. package/dist/key-derivation.mjs.map +1 -1
  35. package/dist/types.cjs.map +1 -1
  36. package/dist/types.d.cts +1 -1
  37. package/dist/types.d.cts.map +1 -1
  38. package/dist/types.d.mts +1 -1
  39. package/dist/types.d.mts.map +1 -1
  40. package/dist/types.mjs.map +1 -1
  41. package/dist/utils/crypto.cjs +12 -1
  42. package/dist/utils/crypto.cjs.map +1 -1
  43. package/dist/utils/crypto.d.cts +7 -0
  44. package/dist/utils/crypto.d.cts.map +1 -1
  45. package/dist/utils/crypto.d.mts +7 -0
  46. package/dist/utils/crypto.d.mts.map +1 -1
  47. package/dist/utils/crypto.mjs +10 -0
  48. package/dist/utils/crypto.mjs.map +1 -1
  49. package/dist/webauthn/types.cjs.map +1 -1
  50. package/dist/webauthn/types.d.cts +1 -1
  51. package/dist/webauthn/types.d.cts.map +1 -1
  52. package/dist/webauthn/types.d.mts +1 -1
  53. package/dist/webauthn/types.d.mts.map +1 -1
  54. package/dist/webauthn/types.mjs.map +1 -1
  55. package/dist/webauthn/verify-authentication-response.cjs +3 -4
  56. package/dist/webauthn/verify-authentication-response.cjs.map +1 -1
  57. package/dist/webauthn/verify-authentication-response.d.cts +3 -2
  58. package/dist/webauthn/verify-authentication-response.d.cts.map +1 -1
  59. package/dist/webauthn/verify-authentication-response.d.mts +3 -2
  60. package/dist/webauthn/verify-authentication-response.d.mts.map +1 -1
  61. package/dist/webauthn/verify-authentication-response.mjs +3 -4
  62. package/dist/webauthn/verify-authentication-response.mjs.map +1 -1
  63. package/dist/webauthn/verify-registration-response.cjs +8 -6
  64. package/dist/webauthn/verify-registration-response.cjs.map +1 -1
  65. package/dist/webauthn/verify-registration-response.d.cts +5 -5
  66. package/dist/webauthn/verify-registration-response.d.cts.map +1 -1
  67. package/dist/webauthn/verify-registration-response.d.mts +5 -5
  68. package/dist/webauthn/verify-registration-response.d.mts.map +1 -1
  69. package/dist/webauthn/verify-registration-response.mjs +8 -6
  70. package/dist/webauthn/verify-registration-response.mjs.map +1 -1
  71. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"PasskeyController.mjs","sourceRoot":"","sources":["../src/PasskeyController.ts"],"names":[],"mappings":";;;;;;;;;;;;AAKA,OAAO,EAAE,cAAc,EAAE,kCAAkC;AAE3D,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,wBAAwB;AACrE,OAAO,EAAE,WAAW,EAAE,iCAAiC;AAEvD,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,+BAA2B;AAC1E,OAAO,EACL,cAAc,EACd,0BAA0B,EAC1B,6BAA6B,EAC9B,wBAAoB;AACrB,OAAO,EAAE,sBAAsB,EAAE,qBAAiB;AAClD,OAAO,EACL,mCAAmC,EACnC,iCAAiC,EAClC,6BAAyB;AAC1B,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,qBAAiB;AAE7D,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,2BAAuB;AAChE,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,6BAAyB;AACtE,OAAO,EAAE,OAAO,EAAE,iCAA6B;AAC/C,OAAO,EAAE,oBAAoB,EAAE,+CAA2C;AAO1E,OAAO,EAAE,4BAA4B,EAAE,sDAAkD;AACzF,OAAO,EAAE,0BAA0B,EAAE,oDAAgD;AAoCrF;;;;GAIG;AACH,MAAM,UAAU,gCAAgC;IAC9C,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;AACjC,CAAC;AAED,MAAM,yBAAyB,GAAG;IAChC,aAAa,EAAE;QACb,OAAO,EAAE,IAAI;QACb,sBAAsB,EAAE,KAAK;QAC7B,kBAAkB,EAAE,KAAK;QACzB,QAAQ,EAAE,IAAI;KACf;CAC8C,CAAC;AAElD,MAAM,GAAG,GAAG,kBAAkB,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;AAE9D;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG;IACxC,uBAAuB,EAAE,CAAC,KAA6B,EAAW,EAAE,CAClE,KAAK,CAAC,aAAa,KAAK,IAAI;CAC/B,CAAC;AAEF;;;;;GAKG;AACH,MAAM,OAAO,iBAAkB,SAAQ,cAItC;IAaC;;;;;;;;;;;;;;;;;;OAkBG;IACH,YAAY,EACV,SAAS,EACT,KAAK,GAAG,EAAE,EACV,IAAI,EACJ,MAAM,EACN,cAAc,EACd,QAAQ,EACR,eAAe,GAShB;QACC,KAAK,CAAC;YACJ,SAAS;YACT,QAAQ,EAAE,yBAAyB;YACnC,IAAI,EAAE,cAAc;YACpB,KAAK,EAAE,EAAE,GAAG,gCAAgC,EAAE,EAAE,GAAG,KAAK,EAAE;SAC3D,CAAC,CAAC;;QArDI,6CAAmB,IAAI,eAAe,EAAE,EAAC;QAEzC,0CAAc;QAEd,4CAAgB;QAEhB,oDAAmC;QAEnC,8CAAkB;QAElB,qDAAyB;QA6ChC,uBAAA,IAAI,2BAAS,IAAI,MAAA,CAAC;QAClB,uBAAA,IAAI,6BAAW,MAAM,MAAA,CAAC;QACtB,uBAAA,IAAI,qCAAmB,cAAc,MAAA,CAAC;QACtC,uBAAA,IAAI,+BAAa,QAAQ,IAAI,MAAM,MAAA,CAAC;QACpC,uBAAA,IAAI,sCAAoB,eAAe,IAAI,MAAM,MAAA,CAAC;IACpD,CAAC;IAmBD;;;;OAIG;IACH,iBAAiB;QACf,OAAO,0BAA0B,CAAC,uBAAuB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxE,CAAC;IAED;;;;;;;;OAQG;IACH,2BAA2B,CAAC,qBAE3B;QACC,MAAM,UAAU,GAAG,qBAAqB,EAAE,YAAY,KAAK,KAAK,CAAC;QACjE,MAAM,OAAO,GAAG,UAAU;YACxB,CAAC,CAAC,gBAAgB,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;YAC3C,CAAC,CAAC,SAAS,CAAC;QACd,MAAM,UAAU,GAAG,gBAAgB,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;QAC7D,MAAM,SAAS,GAAG,gBAAgB,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;QAE5D,MAAM,UAAU,GAA4B,EAAE,CAAC;QAC/C,IAAI,OAAO,EAAE,CAAC;YACZ,UAAU,CAAC,GAAG,GAAG,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC;QAChD,CAAC;QAED,MAAM,OAAO,GAA+B;YAC1C,EAAE,EAAE,EAAE,IAAI,EAAE,uBAAA,IAAI,iCAAQ,EAAE,EAAE,EAAE,uBAAA,IAAI,+BAAM,EAAE;YAC1C,IAAI,EAAE;gBACJ,EAAE,EAAE,UAAU;gBACd,IAAI,EAAE,uBAAA,IAAI,mCAAU;gBACpB,WAAW,EAAE,uBAAA,IAAI,0CAAiB;aACnC;YACD,SAAS;YACT,gBAAgB,EAAE;gBAChB,EAAE,GAAG,EAAE,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE;gBAC1C,EAAE,GAAG,EAAE,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE;gBAC1C,EAAE,GAAG,EAAE,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE;aAC3C;YACD,OAAO,EAAE,mBAAmB;YAC5B,sBAAsB,EAAE;gBACtB,gBAAgB,EAAE,WAAW;gBAC7B,uBAAuB,EAAE,UAAU;gBACnC,WAAW,EAAE,WAAW;aACzB;YACD,KAAK,EAAE,CAAC,eAAe,EAAE,QAAQ,CAAC;YAClC,WAAW,EAAE,MAAM;YACnB,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9D,CAAC;QAEF,uBAAA,IAAI,0CAAiB,CAAC,wBAAwB,CAAC,SAAS,EAAE;YACxD,UAAU;YACV,OAAO,EAAE,OAAO,IAAI,EAAE;YACtB,SAAS;YACT,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;SACtB,CAAC,CAAC;QAEH,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;;;;;;OAOG;IACH,6BAA6B;QAC3B,MAAM,MAAM,GAAG,uBAAA,IAAI,wEAAiB,MAArB,IAAI,CAAmB,CAAC;QAEvC,MAAM,SAAS,GAAG,gBAAgB,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;QAE5D,MAAM,UAAU,GAA4B,EAAE,CAAC;QAC/C,IAAI,MAAM,CAAC,aAAa,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;YAC1C,UAAU,CAAC,GAAG,GAAG,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,aAAa,CAAC,OAAO,EAAE,EAAE,CAAC;QACrE,CAAC;QAED,MAAM,OAAO,GAAiC;YAC5C,SAAS;YACT,IAAI,EAAE,uBAAA,IAAI,+BAAM;YAChB,gBAAgB,EAAE;gBAChB;oBACE,EAAE,EAAE,MAAM,CAAC,UAAU,CAAC,EAAE;oBACxB,IAAI,EAAE,YAAY;oBAClB,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC,UAAU;iBACzC;aACF;YACD,gBAAgB,EAAE,WAAW;YAC7B,KAAK,EAAE,CAAC,eAAe,EAAE,QAAQ,CAAC;YAClC,OAAO,EAAE,mBAAmB;YAC5B,UAAU;SACX,CAAC;QAEF,uBAAA,IAAI,0CAAiB,CAAC,0BAA0B,CAAC,SAAS,EAAE;YAC1D,SAAS;YACT,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;SACtB,CAAC,CAAC;QAEH,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,0BAA0B,CAAC,MAGhC;QACC,MAAM,EAAE,oBAAoB,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC;QAElD,gBAAgB;QAChB,MAAM,SAAS,GAAG,uBAAA,IAAI,mFAA4B,MAAhC,IAAI,EACpB,oBAAoB,CAAC,QAAQ,CAAC,cAAc,CAC7C,CAAC;QACF,MAAM,oBAAoB,GACxB,uBAAA,IAAI,0CAAiB,CAAC,uBAAuB,CAAC,SAAS,CAAC,CAAC;QAC3D,IAAI,CAAC,oBAAoB,EAAE,CAAC;YAC1B,GAAG,CAAC,uDAAuD,CAAC,CAAC;YAC7D,MAAM,IAAI,sBAAsB,CAC9B,6BAA6B,CAAC,sBAAsB,EACpD,EAAE,IAAI,EAAE,0BAA0B,CAAC,sBAAsB,EAAE,CAC5D,CAAC;QACJ,CAAC;QAED,IAAI,CAAC;YACH,+BAA+B;YAC/B,MAAM,EAAE,QAAQ,EAAE,gBAAgB,EAAE,GAAG,MAAM,0BAA0B,CAAC;gBACtE,QAAQ,EAAE,oBAAoB;gBAC9B,iBAAiB,EAAE,oBAAoB,CAAC,SAAS;gBACjD,cAAc,EAAE,uBAAA,IAAI,yCAAgB;gBACpC,YAAY,EAAE,uBAAA,IAAI,+BAAM;gBACxB,uBAAuB,EAAE,KAAK;aAC/B,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;gBACjB,GAAG,CAAC,+CAA+C,EAAE,KAAK,CAAC,CAAC;gBAC5D,MAAM,IAAI,sBAAsB,CAC9B,6BAA6B,CAAC,8BAA8B,EAC5D;oBACE,IAAI,EAAE,0BAA0B,CAAC,8BAA8B;oBAC/D,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;iBACjE,CACF,CAAC;YACJ,CAAC,CAAC,CAAC;YACH,IAAI,CAAC,QAAQ,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBACnC,GAAG,CACD,oFAAoF,CACrF,CAAC;gBACF,MAAM,IAAI,sBAAsB,CAC9B,6BAA6B,CAAC,8BAA8B,EAC5D,EAAE,IAAI,EAAE,0BAA0B,CAAC,8BAA8B,EAAE,CACpE,CAAC;YACJ,CAAC;YAED,aAAa;YACb,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,iCAAiC,CACjE,oBAAoB,EACpB,oBAAoB,EACpB,gBAAgB,CAAC,YAAY,CAC9B,CAAC;YAEF,oBAAoB;YACpB,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE,GAAG,cAAc,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YAE5D,yBAAyB;YACzB,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;gBACpB,KAAK,CAAC,aAAa,GAAG;oBACpB,UAAU,EAAE;wBACV,EAAE,EAAE,gBAAgB,CAAC,YAAY;wBACjC,SAAS,EAAE,gBAAgB,CAAC,gBAAgB,CAAC,SAAS,CAAC;wBACvD,OAAO,EAAE,gBAAgB,CAAC,OAAO;wBACjC,UAAU,EAAE,gBAAgB,CAAC,UAAU;wBACvC,MAAM,EAAE,gBAAgB,CAAC,MAAM;qBAChC;oBACD,iBAAiB,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE;oBACrC,aAAa;iBACd,CAAC;YACJ,CAAC,CAAC,CAAC;QACL,CAAC;gBAAS,CAAC;YACT,uBAAA,IAAI,0CAAiB,CAAC,0BAA0B,CAAC,SAAS,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,2BAA2B,CAC/B,sBAAqD;QAErD,iCAAiC;QACjC,MAAM,uBAAA,IAAI,qFAA8B,MAAlC,IAAI,EAA+B,sBAAsB,CAAC,CAAC;QAEjE,iEAAiE;QACjE,MAAM,aAAa,GAAG,uBAAA,IAAI,wEAAiB,MAArB,IAAI,CAAmB,CAAC;QAC9C,MAAM,MAAM,GAAG,mCAAmC,CAChD,sBAAsB,EACtB,aAAa,CACd,CAAC;QAEF,oBAAoB;QACpB,IAAI,QAAgB,CAAC;QACrB,IAAI,CAAC;YACH,QAAQ,GAAG,cAAc,CACvB,aAAa,CAAC,iBAAiB,CAAC,UAAU,EAC1C,aAAa,CAAC,iBAAiB,CAAC,EAAE,EAClC,MAAM,CACP,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,GAAG,CACD,yCAAyC,EACzC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAC1D,CAAC;YACF,MAAM,IAAI,sBAAsB,CAC9B,6BAA6B,CAAC,wBAAwB,EACtD;gBACE,IAAI,EAAE,0BAA0B,CAAC,wBAAwB;gBACzD,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;aACjE,CACF,CAAC;QACJ,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED;;;;;;;;;;OAUG;IACH,KAAK,CAAC,2BAA2B,CAC/B,sBAAqD;QAErD,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,2BAA2B,CAAC,sBAAsB,CAAC,CAAC;YAC/D,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,IAAI,KAAK,YAAY,sBAAsB,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBACxE,OAAO,KAAK,CAAC;YACf,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACH,KAAK,CAAC,uBAAuB,CAAC,MAI7B;QACC,MAAM,EAAE,sBAAsB,EAAE,GAAG,MAAM,CAAC;QAC1C,MAAM,aAAa,GAAG,uBAAA,IAAI,wEAAiB,MAArB,IAAI,CAAmB,CAAC;QAE9C,aAAa;QACb,MAAM,MAAM,GAAG,mCAAmC,CAChD,sBAAsB,EACtB,aAAa,CACd,CAAC;QAEF,oBAAoB;QACpB,IAAI,iBAAyB,CAAC;QAC9B,IAAI,CAAC;YACH,iBAAiB,GAAG,cAAc,CAChC,aAAa,CAAC,iBAAiB,CAAC,UAAU,EAC1C,aAAa,CAAC,iBAAiB,CAAC,EAAE,EAClC,MAAM,CACP,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,GAAG,CACD,6DAA6D,EAC7D,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAC1D,CAAC;YACF,MAAM,IAAI,sBAAsB,CAC9B,6BAA6B,CAAC,wBAAwB,EACtD;gBACE,IAAI,EAAE,0BAA0B,CAAC,wBAAwB;gBACzD,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;aACjE,CACF,CAAC;QACJ,CAAC;QAED,6BAA6B;QAC7B,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC;QAC5C,IACE,CAAC,mBAAmB,CAClB,aAAa,CAAC,iBAAiB,CAAC,EAChC,aAAa,CAAC,WAAW,CAAC,CAC3B,EACD,CAAC;YACD,GAAG,CACD,0EAA0E,CAC3E,CAAC;YACF,MAAM,IAAI,sBAAsB,CAC9B,6BAA6B,CAAC,gBAAgB,EAC9C,EAAE,IAAI,EAAE,0BAA0B,CAAC,gBAAgB,EAAE,CACtD,CAAC;QACJ,CAAC;QAED,wBAAwB;QACxB,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE,GAAG,cAAc,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QAE/D,4EAA4E;QAC5E,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;YACpB,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE,CAAC;gBACzB,MAAM,IAAI,sBAAsB,CAC9B,6BAA6B,CAAC,WAAW,EACzC;oBACE,IAAI,EAAE,0BAA0B,CAAC,WAAW;iBAC7C,CACF,CAAC;YACJ,CAAC;YACD,KAAK,CAAC,aAAa,CAAC,iBAAiB,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;QAC7D,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,aAAa;QACX,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,gCAAgC,EAAE,CAAC,CAAC;QACtD,uBAAA,IAAI,0CAAiB,CAAC,KAAK,EAAE,CAAC;IAChC,CAAC;IAED;;OAEG;IACH,UAAU;QACR,IAAI,CAAC,aAAa,EAAE,CAAC;IACvB,CAAC;IAED;;OAEG;IACH,OAAO;QACL,uBAAA,IAAI,0CAAiB,CAAC,KAAK,EAAE,CAAC;QAC9B,KAAK,CAAC,OAAO,EAAE,CAAC;IAClB,CAAC;CAwFF;;IAzdG,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC;IACxC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,sBAAsB,CAC9B,6BAA6B,CAAC,WAAW,EACzC;YACE,IAAI,EAAE,0BAA0B,CAAC,WAAW;SAC7C,CACF,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC,yGAE2B,cAAsB;IAChD,OAAO,oBAAoB,CAAC,cAAc,CAAC,CAAC,SAAS,CAAC;AACxD,CAAC;AAqXD;;;;GAIG;AACH,KAAK,0DACH,sBAAqD;IAErD,IAAI,SAA6B,CAAC;IAClC,IAAI,CAAC;QACH,gBAAgB;QAChB,SAAS,GAAG,uBAAA,IAAI,mFAA4B,MAAhC,IAAI,EACd,sBAAsB,CAAC,QAAQ,CAAC,cAAc,CAC/C,CAAC;QAEF,qBAAqB;QACrB,MAAM,MAAM,GAAG,uBAAA,IAAI,wEAAiB,MAArB,IAAI,CAAmB,CAAC;QAEvC,8BAA8B;QAC9B,MAAM,sBAAsB,GAC1B,uBAAA,IAAI,0CAAiB,CAAC,yBAAyB,CAAC,SAAS,CAAC,CAAC;QAC7D,IAAI,CAAC,sBAAsB,EAAE,CAAC;YAC5B,GAAG,CAAC,yDAAyD,CAAC,CAAC;YAC/D,MAAM,IAAI,sBAAsB,CAC9B,6BAA6B,CAAC,wBAAwB,EACtD,EAAE,IAAI,EAAE,0BAA0B,CAAC,wBAAwB,EAAE,CAC9D,CAAC;QACJ,CAAC;QAED,iCAAiC;QACjC,MAAM,MAAM,GAAG,MAAM,4BAA4B,CAAC;YAChD,QAAQ,EAAE,sBAAsB;YAChC,iBAAiB,EAAE,sBAAsB,CAAC,SAAS;YACnD,cAAc,EAAE,uBAAA,IAAI,yCAAgB;YACpC,YAAY,EAAE,uBAAA,IAAI,+BAAM;YACxB,UAAU,EAAE;gBACV,EAAE,EAAE,MAAM,CAAC,UAAU,CAAC,EAAE;gBACxB,SAAS,EAAE,gBAAgB,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC;gBACxD,OAAO,EAAE,MAAM,CAAC,UAAU,CAAC,OAAO;gBAClC,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC,UAAU;aACzC;YACD,0EAA0E;YAC1E,uBAAuB,EAAE,KAAK;SAC/B,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;YACjB,GAAG,CACD,iDAAiD,EACjD,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAC1D,CAAC;YACF,MAAM,IAAI,sBAAsB,CAC9B,6BAA6B,CAAC,gCAAgC,EAC9D;gBACE,IAAI,EAAE,0BAA0B,CAAC,gCAAgC;gBACjE,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;aACjE,CACF,CAAC;QACJ,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;YACrB,GAAG,CAAC,yDAAyD,CAAC,CAAC;YAC/D,MAAM,IAAI,sBAAsB,CAC9B,6BAA6B,CAAC,gCAAgC,EAC9D;gBACE,IAAI,EAAE,0BAA0B,CAAC,gCAAgC;aAClE,CACF,CAAC;QACJ,CAAC;QAED,oFAAoF;QACpF,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;YACpB,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE,CAAC;gBACzB,MAAM,IAAI,sBAAsB,CAC9B,6BAA6B,CAAC,WAAW,EACzC,EAAE,IAAI,EAAE,0BAA0B,CAAC,WAAW,EAAE,CACjD,CAAC;YACJ,CAAC;YACD,MAAM,MAAM,GAAG,KAAK,CAAC,aAAa,CAAC;YACnC,MAAM,CAAC,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,CAClC,MAAM,CAAC,kBAAkB,CAAC,UAAU,EACpC,MAAM,CAAC,UAAU,CAAC,OAAO,CAC1B,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;YAAS,CAAC;QACT,IAAI,SAAS,EAAE,CAAC;YACd,uBAAA,IAAI,0CAAiB,CAAC,4BAA4B,CAAC,SAAS,CAAC,CAAC;QAChE,CAAC;IACH,CAAC;AACH,CAAC","sourcesContent":["import type {\n ControllerGetStateAction,\n ControllerStateChangedEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type { Messenger } from '@metamask/messenger';\nimport { areUint8ArraysEqual, stringToBytes } from '@metamask/utils';\nimport { randomBytes } from '@noble/ciphers/webcrypto';\n\nimport { WEBAUTHN_TIMEOUT_MS, CeremonyManager } from './ceremony-manager';\nimport {\n controllerName,\n PasskeyControllerErrorCode,\n PasskeyControllerErrorMessage,\n} from './constants';\nimport { PasskeyControllerError } from './errors';\nimport {\n deriveKeyFromAuthenticationResponse,\n deriveKeyFromRegistrationResponse,\n} from './key-derivation';\nimport { createModuleLogger, projectLogger } from './logger';\nimport type { PasskeyRecord } from './types';\nimport { decryptWithKey, encryptWithKey } from './utils/crypto';\nimport { base64URLToBytes, bytesToBase64URL } from './utils/encoding';\nimport { COSEALG } from './webauthn/constants';\nimport { decodeClientDataJSON } from './webauthn/decode-client-data-json';\nimport type {\n PasskeyAuthenticationOptions,\n PasskeyAuthenticationResponse,\n PasskeyRegistrationOptions,\n PasskeyRegistrationResponse,\n} from './webauthn/types';\nimport { verifyAuthenticationResponse } from './webauthn/verify-authentication-response';\nimport { verifyRegistrationResponse } from './webauthn/verify-registration-response';\n\nexport type PasskeyControllerState = {\n passkeyRecord: PasskeyRecord | null;\n};\n\nexport type PasskeyControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n PasskeyControllerState\n>;\n\n/**\n * Actions exposed by {@link PasskeyController} on its messenger.\n *\n * Only `:getState` is exposed. Derived enrollment status is available via\n * {@link passkeyControllerSelectors.selectIsPasskeyEnrolled}, and lifecycle\n * methods ({@link PasskeyController.generateRegistrationOptions},\n * {@link PasskeyController.protectVaultKeyWithPasskey}, etc.) accept or\n * return non-`Json` runtime values (WebAuthn `PublicKeyCredential` objects\n * and the vault key string), so they require a direct controller reference.\n */\nexport type PasskeyControllerActions = PasskeyControllerGetStateAction;\n\nexport type PasskeyControllerStateChangedEvent = ControllerStateChangedEvent<\n typeof controllerName,\n PasskeyControllerState\n>;\n\nexport type PasskeyControllerEvents = PasskeyControllerStateChangedEvent;\n\nexport type PasskeyControllerMessenger = Messenger<\n typeof controllerName,\n PasskeyControllerActions,\n PasskeyControllerEvents\n>;\n\n/**\n * Returns the default (empty) state for {@link PasskeyController}.\n *\n * @returns A fresh state object with no enrolled passkey.\n */\nexport function getDefaultPasskeyControllerState(): PasskeyControllerState {\n return { passkeyRecord: null };\n}\n\nconst passkeyControllerMetadata = {\n passkeyRecord: {\n persist: true,\n includeInDebugSnapshot: false,\n includeInStateLogs: false,\n usedInUi: true,\n },\n} satisfies StateMetadata<PasskeyControllerState>;\n\nconst log = createModuleLogger(projectLogger, controllerName);\n\n/**\n * Selectors for {@link PasskeyControllerState}.\n *\n * Use these instead of dedicated getter methods on the controller, so that\n * derived values can be consumed from Redux selectors and other places that\n * only have access to a state object.\n */\nexport const passkeyControllerSelectors = {\n selectIsPasskeyEnrolled: (state: PasskeyControllerState): boolean =>\n state.passkeyRecord !== null,\n};\n\n/**\n * Passkey-based protection for the vault encryption key (WebAuthn).\n *\n * Uses PRF-backed derivation when available; otherwise uses the credential\n * `userHandle`.\n */\nexport class PasskeyController extends BaseController<\n typeof controllerName,\n PasskeyControllerState,\n PasskeyControllerMessenger\n> {\n readonly #ceremonyManager = new CeremonyManager();\n\n readonly #rpID: string;\n\n readonly #rpName: string;\n\n readonly #expectedOrigin: string | string[];\n\n readonly #userName: string;\n\n readonly #userDisplayName: string;\n\n /**\n * Constructs a new {@link PasskeyController}.\n *\n * @param args - The constructor arguments.\n * @param args.messenger - The messenger suited for this controller.\n * @param args.state - Initial state. Missing properties are filled in with\n * defaults from {@link getDefaultPasskeyControllerState}.\n * @param args.rpID - WebAuthn Relying Party ID (typically the eTLD+1 of the\n * client origin, or `localhost` in dev).\n * @param args.rpName - Human-readable Relying Party name shown by the OS\n * passkey UI.\n * @param args.expectedOrigin - One or more acceptable origins for the\n * `clientDataJSON.origin` check (e.g. `chrome-extension://...`).\n * @param args.userName - Optional `user.name` shown by the OS passkey UI.\n * Defaults to `rpName` so client builds (Stable, Flask, etc.) can\n * differentiate without changes here.\n * @param args.userDisplayName - Optional `user.displayName` shown by the OS\n * passkey UI. Defaults to `rpName`.\n */\n constructor({\n messenger,\n state = {},\n rpID,\n rpName,\n expectedOrigin,\n userName,\n userDisplayName,\n }: {\n messenger: PasskeyControllerMessenger;\n state?: Partial<PasskeyControllerState>;\n rpID: string;\n rpName: string;\n expectedOrigin: string | string[];\n userName?: string;\n userDisplayName?: string;\n }) {\n super({\n messenger,\n metadata: passkeyControllerMetadata,\n name: controllerName,\n state: { ...getDefaultPasskeyControllerState(), ...state },\n });\n\n this.#rpID = rpID;\n this.#rpName = rpName;\n this.#expectedOrigin = expectedOrigin;\n this.#userName = userName ?? rpName;\n this.#userDisplayName = userDisplayName ?? rpName;\n }\n\n #requireEnrolled(): PasskeyRecord {\n const record = this.state.passkeyRecord;\n if (!record) {\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.NotEnrolled,\n {\n code: PasskeyControllerErrorCode.NotEnrolled,\n },\n );\n }\n return record;\n }\n\n #getChallengeFromClientData(clientDataJSON: string): string {\n return decodeClientDataJSON(clientDataJSON).challenge;\n }\n\n /**\n * Checks if the passkey is enrolled.\n *\n * @returns Whether the passkey is enrolled.\n */\n isPasskeyEnrolled(): boolean {\n return passkeyControllerSelectors.selectIsPasskeyEnrolled(this.state);\n }\n\n /**\n * Registration options for enrolling a passkey.\n *\n * Call before {@link protectVaultKeyWithPasskey}.\n *\n * @param creationOptionsConfig - Optional configuration.\n * @param creationOptionsConfig.prfAvailable - Omit PRF when `false`. Default `true`.\n * @returns Options for `navigator.credentials.create()`.\n */\n generateRegistrationOptions(creationOptionsConfig?: {\n prfAvailable?: boolean;\n }): PasskeyRegistrationOptions {\n const includePrf = creationOptionsConfig?.prfAvailable !== false;\n const prfSalt = includePrf\n ? bytesToBase64URL(randomBytes(32).slice())\n : undefined;\n const userHandle = bytesToBase64URL(randomBytes(64).slice());\n const challenge = bytesToBase64URL(randomBytes(32).slice());\n\n const extensions: Record<string, unknown> = {};\n if (prfSalt) {\n extensions.prf = { eval: { first: prfSalt } };\n }\n\n const options: PasskeyRegistrationOptions = {\n rp: { name: this.#rpName, id: this.#rpID },\n user: {\n id: userHandle,\n name: this.#userName,\n displayName: this.#userDisplayName,\n },\n challenge,\n pubKeyCredParams: [\n { alg: COSEALG.EdDSA, type: 'public-key' },\n { alg: COSEALG.ES256, type: 'public-key' },\n { alg: COSEALG.RS256, type: 'public-key' },\n ],\n timeout: WEBAUTHN_TIMEOUT_MS,\n authenticatorSelection: {\n userVerification: 'preferred',\n authenticatorAttachment: 'platform',\n residentKey: 'preferred',\n },\n hints: ['client-device', 'hybrid'],\n attestation: 'none',\n ...(Object.keys(extensions).length > 0 ? { extensions } : {}),\n };\n\n this.#ceremonyManager.saveRegistrationCeremony(challenge, {\n userHandle,\n prfSalt: prfSalt ?? '',\n challenge,\n createdAt: Date.now(),\n });\n\n return options;\n }\n\n /**\n * WebAuthn request options for authenticating with the enrolled passkey.\n *\n * Call before {@link retrieveVaultKeyWithPasskey},\n * {@link verifyPasskeyAuthentication}, or {@link renewVaultKeyProtection}.\n *\n * @returns Options for `navigator.credentials.get()`.\n */\n generateAuthenticationOptions(): PasskeyAuthenticationOptions {\n const record = this.#requireEnrolled();\n\n const challenge = bytesToBase64URL(randomBytes(32).slice());\n\n const extensions: Record<string, unknown> = {};\n if (record.keyDerivation.method === 'prf') {\n extensions.prf = { eval: { first: record.keyDerivation.prfSalt } };\n }\n\n const options: PasskeyAuthenticationOptions = {\n challenge,\n rpId: this.#rpID,\n allowCredentials: [\n {\n id: record.credential.id,\n type: 'public-key',\n transports: record.credential.transports,\n },\n ],\n userVerification: 'preferred',\n hints: ['client-device', 'hybrid'],\n timeout: WEBAUTHN_TIMEOUT_MS,\n extensions,\n };\n\n this.#ceremonyManager.saveAuthenticationCeremony(challenge, {\n challenge,\n createdAt: Date.now(),\n });\n\n return options;\n }\n\n /**\n * Completes enrollment and binds the vault key to the new passkey.\n *\n * @param params - Protection parameters.\n * @param params.registrationResponse - Credential from `navigator.credentials.create()`.\n * @param params.vaultKey - Vault encryption key to protect.\n */\n async protectVaultKeyWithPasskey(params: {\n registrationResponse: PasskeyRegistrationResponse;\n vaultKey: string;\n }): Promise<void> {\n const { registrationResponse, vaultKey } = params;\n\n // get challenge\n const challenge = this.#getChallengeFromClientData(\n registrationResponse.response.clientDataJSON,\n );\n const registrationCeremony =\n this.#ceremonyManager.getRegistrationCeremony(challenge);\n if (!registrationCeremony) {\n log('No active passkey registration ceremony for challenge');\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.NoRegistrationCeremony,\n { code: PasskeyControllerErrorCode.NoRegistrationCeremony },\n );\n }\n\n try {\n // verify registration response\n const { verified, registrationInfo } = await verifyRegistrationResponse({\n response: registrationResponse,\n expectedChallenge: registrationCeremony.challenge,\n expectedOrigin: this.#expectedOrigin,\n expectedRPID: this.#rpID,\n requireUserVerification: false,\n }).catch((error) => {\n log('Error verifying passkey registration response', error);\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.RegistrationVerificationFailed,\n {\n code: PasskeyControllerErrorCode.RegistrationVerificationFailed,\n cause: error instanceof Error ? error : new Error(String(error)),\n },\n );\n });\n if (!verified || !registrationInfo) {\n log(\n 'Passkey registration verification returned unverified or missing registration info',\n );\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.RegistrationVerificationFailed,\n { code: PasskeyControllerErrorCode.RegistrationVerificationFailed },\n );\n }\n\n // derive key\n const { encKey, keyDerivation } = deriveKeyFromRegistrationResponse(\n registrationResponse,\n registrationCeremony,\n registrationInfo.credentialId,\n );\n\n // encrypt vault key\n const { ciphertext, iv } = encryptWithKey(vaultKey, encKey);\n\n // persist passkey record\n this.update((state) => {\n state.passkeyRecord = {\n credential: {\n id: registrationInfo.credentialId,\n publicKey: bytesToBase64URL(registrationInfo.publicKey),\n counter: registrationInfo.counter,\n transports: registrationInfo.transports,\n aaguid: registrationInfo.aaguid,\n },\n encryptedVaultKey: { ciphertext, iv },\n keyDerivation,\n };\n });\n } finally {\n this.#ceremonyManager.deleteRegistrationCeremony(challenge);\n }\n }\n\n /**\n * Returns the decrypted vault encryption key from the passkey authentication\n * response.\n *\n * @param authenticationResponse - Credential from `navigator.credentials.get()`.\n * @returns The vault encryption key.\n */\n async retrieveVaultKeyWithPasskey(\n authenticationResponse: PasskeyAuthenticationResponse,\n ): Promise<string> {\n // verify authentication response\n await this.#verifyAuthenticationResponse(authenticationResponse);\n\n // derive key (#verifyAuthenticationResponse guarantees enrolled)\n const passkeyRecord = this.#requireEnrolled();\n const encKey = deriveKeyFromAuthenticationResponse(\n authenticationResponse,\n passkeyRecord,\n );\n\n // decrypt vault key\n let vaultKey: string;\n try {\n vaultKey = decryptWithKey(\n passkeyRecord.encryptedVaultKey.ciphertext,\n passkeyRecord.encryptedVaultKey.iv,\n encKey,\n );\n } catch (cause) {\n log(\n 'Error decrypting vault key with passkey',\n cause instanceof Error ? cause : new Error(String(cause)),\n );\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.VaultKeyDecryptionFailed,\n {\n code: PasskeyControllerErrorCode.VaultKeyDecryptionFailed,\n cause: cause instanceof Error ? cause : new Error(String(cause)),\n },\n );\n }\n\n return vaultKey;\n }\n\n /**\n * Returns whether passkey authentication succeeds for this credential (same\n * work as {@link retrieveVaultKeyWithPasskey} without exposing the vault key).\n *\n * Returns `false` only when the failure is a {@link PasskeyControllerError}\n * with a defined `code`. Unexpected errors (e.g. malformed `clientDataJSON`,\n * internal bugs) are rethrown.\n *\n * @param authenticationResponse - Credential from `navigator.credentials.get()`.\n * @returns `true` if authentication succeeds, otherwise `false`.\n */\n async verifyPasskeyAuthentication(\n authenticationResponse: PasskeyAuthenticationResponse,\n ): Promise<boolean> {\n try {\n await this.retrieveVaultKeyWithPasskey(authenticationResponse);\n return true;\n } catch (error: unknown) {\n if (error instanceof PasskeyControllerError && error.code !== undefined) {\n return false;\n }\n throw error;\n }\n }\n\n /**\n * Updates the vault encryption key for the same passkey (e.g. after a password change).\n *\n * Caller MUST first verify the assertion via {@link verifyPasskeyAuthentication}\n * or {@link retrieveVaultKeyWithPasskey}. This method does not re-verify\n * because the ceremony is single-use (deleted on verify) and the signature\n * counter is advanced (replay would be rejected). Authentication here is\n * enforced by the prior verification plus the `oldVaultKey` match below.\n *\n * @param params - Renewal parameters.\n * @param params.authenticationResponse - Credential from `navigator.credentials.get()`,\n * already verified by the caller.\n * @param params.oldVaultKey - Expected current vault key.\n * @param params.newVaultKey - New vault key to protect.\n */\n async renewVaultKeyProtection(params: {\n authenticationResponse: PasskeyAuthenticationResponse;\n oldVaultKey: string;\n newVaultKey: string;\n }): Promise<void> {\n const { authenticationResponse } = params;\n const passkeyRecord = this.#requireEnrolled();\n\n // derive key\n const encKey = deriveKeyFromAuthenticationResponse(\n authenticationResponse,\n passkeyRecord,\n );\n\n // decrypt vault key\n let decryptedVaultKey: string;\n try {\n decryptedVaultKey = decryptWithKey(\n passkeyRecord.encryptedVaultKey.ciphertext,\n passkeyRecord.encryptedVaultKey.iv,\n encKey,\n );\n } catch (error) {\n log(\n 'Error decrypting vault key during passkey vault key renewal',\n error instanceof Error ? error : new Error(String(error)),\n );\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.VaultKeyDecryptionFailed,\n {\n code: PasskeyControllerErrorCode.VaultKeyDecryptionFailed,\n cause: error instanceof Error ? error : new Error(String(error)),\n },\n );\n }\n\n // check if vault key matches\n const { oldVaultKey, newVaultKey } = params;\n if (\n !areUint8ArraysEqual(\n stringToBytes(decryptedVaultKey),\n stringToBytes(oldVaultKey),\n )\n ) {\n log(\n 'Passkey renewal rejected: decrypted vault key does not match oldVaultKey',\n );\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.VaultKeyMismatch,\n { code: PasskeyControllerErrorCode.VaultKeyMismatch },\n );\n }\n\n // encrypt new vault key\n const { ciphertext, iv } = encryptWithKey(newVaultKey, encKey);\n\n // persist passkey record (mutate current state only for vault key material)\n this.update((state) => {\n if (!state.passkeyRecord) {\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.NotEnrolled,\n {\n code: PasskeyControllerErrorCode.NotEnrolled,\n },\n );\n }\n state.passkeyRecord.encryptedVaultKey = { ciphertext, iv };\n });\n }\n\n /**\n * Unenrolls the passkey, removing the protected vault key material.\n */\n removePasskey(): void {\n this.update(() => getDefaultPasskeyControllerState());\n this.#ceremonyManager.clear();\n }\n\n /**\n * Resets state and clears in-flight registration/authentication ceremonies.\n */\n clearState(): void {\n this.removePasskey();\n }\n\n /**\n * Releases all in-flight ceremony state and tears down the messenger.\n */\n destroy(): void {\n this.#ceremonyManager.clear();\n super.destroy();\n }\n\n /**\n * Verifies an authentication response for the enrolled passkey.\n *\n * @param authenticationResponse - Authentication result JSON.\n */\n async #verifyAuthenticationResponse(\n authenticationResponse: PasskeyAuthenticationResponse,\n ): Promise<void> {\n let challenge: string | undefined;\n try {\n // get challenge\n challenge = this.#getChallengeFromClientData(\n authenticationResponse.response.clientDataJSON,\n );\n\n // get passkey record\n const record = this.#requireEnrolled();\n\n // get authentication ceremony\n const authenticationCeremony =\n this.#ceremonyManager.getAuthenticationCeremony(challenge);\n if (!authenticationCeremony) {\n log('No active passkey authentication ceremony for challenge');\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.NoAuthenticationCeremony,\n { code: PasskeyControllerErrorCode.NoAuthenticationCeremony },\n );\n }\n\n // verify authentication response\n const result = await verifyAuthenticationResponse({\n response: authenticationResponse,\n expectedChallenge: authenticationCeremony.challenge,\n expectedOrigin: this.#expectedOrigin,\n expectedRPID: this.#rpID,\n credential: {\n id: record.credential.id,\n publicKey: base64URLToBytes(record.credential.publicKey),\n counter: record.credential.counter,\n transports: record.credential.transports,\n },\n // UV optional for device compatibility; vault key remains password-gated.\n requireUserVerification: false,\n }).catch((error) => {\n log(\n 'Error verifying passkey authentication response',\n error instanceof Error ? error : new Error(String(error)),\n );\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.AuthenticationVerificationFailed,\n {\n code: PasskeyControllerErrorCode.AuthenticationVerificationFailed,\n cause: error instanceof Error ? error : new Error(String(error)),\n },\n );\n });\n if (!result.verified) {\n log('Passkey authentication verification returned unverified');\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.AuthenticationVerificationFailed,\n {\n code: PasskeyControllerErrorCode.AuthenticationVerificationFailed,\n },\n );\n }\n\n // persist passkey record with updated counter without clobbering concurrent updates\n this.update((state) => {\n if (!state.passkeyRecord) {\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.NotEnrolled,\n { code: PasskeyControllerErrorCode.NotEnrolled },\n );\n }\n const latest = state.passkeyRecord;\n latest.credential.counter = Math.max(\n result.authenticationInfo.newCounter,\n latest.credential.counter,\n );\n });\n } finally {\n if (challenge) {\n this.#ceremonyManager.deleteAuthenticationCeremony(challenge);\n }\n }\n }\n}\n"]}
1
+ {"version":3,"file":"PasskeyController.mjs","sourceRoot":"","sources":["../src/PasskeyController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ControllerGetStateAction,\n ControllerStateChangedEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type { Messenger } from '@metamask/messenger';\nimport { areUint8ArraysEqual, stringToBytes } from '@metamask/utils';\n\nimport { WEBAUTHN_TIMEOUT_MS, CeremonyManager } from './ceremony-manager';\nimport {\n controllerName,\n PasskeyControllerErrorCode,\n PasskeyControllerErrorMessage,\n} from './constants';\nimport { PasskeyControllerError } from './errors';\nimport { deriveKeyFromAuthenticationResponse } from './key-derivation';\nimport { createModuleLogger, projectLogger } from './logger';\nimport type {\n AuthenticatorTransportFuture,\n PasskeyCredentialInfo,\n PasskeyKeyDerivation,\n PasskeyRecord,\n PrfClientExtensionResults,\n} from './types';\nimport {\n decryptWithKey,\n encryptWithKey,\n randomBytesToBase64URL,\n} from './utils/crypto';\nimport { base64URLToBytes, bytesToBase64URL } from './utils/encoding';\nimport { COSEALG } from './webauthn/constants';\nimport { decodeClientDataJSON } from './webauthn/decode-client-data-json';\nimport type {\n PasskeyAuthenticationOptions,\n PasskeyAuthenticationResponse,\n PasskeyRegistrationOptions,\n PasskeyRegistrationResponse,\n} from './webauthn/types';\nimport { verifyAuthenticationResponse } from './webauthn/verify-authentication-response';\nimport { verifyRegistrationResponse } from './webauthn/verify-registration-response';\n\nexport type PasskeyControllerState = {\n passkeyRecord: PasskeyRecord | null;\n};\n\nexport type PasskeyControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n PasskeyControllerState\n>;\n\n/**\n * Actions exposed by {@link PasskeyController} on its messenger.\n *\n * Only `:getState` is exposed. Derived enrollment status is available via\n * {@link passkeyControllerSelectors.selectIsPasskeyEnrolled}, and lifecycle\n * methods ({@link PasskeyController.generateRegistrationOptions},\n * {@link PasskeyController.protectVaultKeyWithPasskey}, etc.) accept or\n * return non-`Json` runtime values (WebAuthn `PublicKeyCredential` objects\n * and the vault key string), so they require a direct controller reference.\n */\nexport type PasskeyControllerActions = PasskeyControllerGetStateAction;\n\nexport type PasskeyControllerStateChangedEvent = ControllerStateChangedEvent<\n typeof controllerName,\n PasskeyControllerState\n>;\n\nexport type PasskeyControllerEvents = PasskeyControllerStateChangedEvent;\n\nexport type PasskeyControllerMessenger = Messenger<\n typeof controllerName,\n PasskeyControllerActions,\n PasskeyControllerEvents\n>;\n\n/**\n * Returns the default (empty) state for {@link PasskeyController}.\n *\n * @returns A fresh state object with no enrolled passkey.\n */\nexport function getDefaultPasskeyControllerState(): PasskeyControllerState {\n return { passkeyRecord: null };\n}\n\nconst passkeyControllerMetadata = {\n passkeyRecord: {\n persist: true,\n includeInDebugSnapshot: false,\n includeInStateLogs: false,\n usedInUi: true,\n },\n} satisfies StateMetadata<PasskeyControllerState>;\n\nconst log = createModuleLogger(projectLogger, controllerName);\n\n/**\n * Selectors for {@link PasskeyControllerState}.\n *\n * Use these instead of dedicated getter methods on the controller, so that\n * derived values can be consumed from Redux selectors and other places that\n * only have access to a state object.\n */\nexport const passkeyControllerSelectors = {\n selectIsPasskeyEnrolled: (state: PasskeyControllerState): boolean =>\n state.passkeyRecord !== null,\n};\n\n/**\n * Controller that enrolls a WebAuthn passkey and uses it to protect and unlock\n * the vault encryption key.\n */\nexport class PasskeyController extends BaseController<\n typeof controllerName,\n PasskeyControllerState,\n PasskeyControllerMessenger\n> {\n readonly #ceremonyManager = new CeremonyManager();\n\n readonly #expectedRPIDs: string[];\n\n readonly #rpId: string | undefined;\n\n readonly #rpName: string;\n\n readonly #expectedOrigin: string | string[];\n\n readonly #userName: string;\n\n readonly #userDisplayName: string;\n\n /**\n * Creates a passkey controller with WebAuthn relying-party settings.\n *\n * @param args - Constructor options.\n * @param args.messenger - Controller messenger.\n * @param args.state - Partial initial state; merged with {@link getDefaultPasskeyControllerState}.\n * @param args.expectedRPID - Relying party ID(s) for verification (SHA-256 hash match in\n * authenticator data). Pass a string or array of strings; an empty array skips RP ID\n * allowlist checks in {@link verifyRegistrationResponse} / {@link verifyAuthenticationResponse}.\n * @param args.rpId - When set, included as `rp.id` on registration options and `rpId` on\n * authentication options. When omitted, those fields are left unset (client default RP ID).\n * @param args.rpName - Relying party name shown in the platform passkey UI.\n * @param args.expectedOrigin - Allowed value(s) for the WebAuthn client origin.\n * @param args.userName - Optional passkey user name; defaults to `rpName`.\n * @param args.userDisplayName - Optional display name; defaults to `rpName`.\n */\n constructor({\n messenger,\n state = {},\n rpId,\n expectedRPID,\n rpName,\n expectedOrigin,\n userName,\n userDisplayName,\n }: {\n messenger: PasskeyControllerMessenger;\n state?: Partial<PasskeyControllerState>;\n rpId?: string;\n expectedRPID: string | string[];\n rpName: string;\n expectedOrigin: string | string[];\n userName?: string;\n userDisplayName?: string;\n }) {\n super({\n messenger,\n metadata: passkeyControllerMetadata,\n name: controllerName,\n state: { ...getDefaultPasskeyControllerState(), ...state },\n });\n\n const expectedRPIDs = Array.isArray(expectedRPID)\n ? expectedRPID\n : [expectedRPID];\n this.#expectedRPIDs = [...expectedRPIDs];\n this.#rpId = rpId;\n this.#rpName = rpName;\n this.#expectedOrigin = expectedOrigin;\n this.#userName = userName ?? rpName;\n this.#userDisplayName = userDisplayName ?? rpName;\n }\n\n #requireEnrolled(): PasskeyRecord {\n const record = this.state.passkeyRecord;\n if (!record) {\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.NotEnrolled,\n {\n code: PasskeyControllerErrorCode.NotEnrolled,\n },\n );\n }\n return record;\n }\n\n #getChallengeFromClientData(clientDataJSON: string): string {\n return decodeClientDataJSON(clientDataJSON).challenge;\n }\n\n /**\n * Whether a passkey is enrolled and vault key material is stored.\n *\n * @returns `true` if enrolled, otherwise `false`.\n */\n isPasskeyEnrolled(): boolean {\n return passkeyControllerSelectors.selectIsPasskeyEnrolled(this.state);\n }\n\n /**\n * Builds WebAuthn credential creation options for passkey enrollment.\n *\n * @param creationOptionsConfig - Optional creation behavior.\n * @param creationOptionsConfig.prfAvailable - Request the PRF extension unless `false`. Defaults to `true`.\n * @returns Public key credential creation options for `navigator.credentials.create()`.\n */\n generateRegistrationOptions(creationOptionsConfig?: {\n prfAvailable?: boolean;\n }): PasskeyRegistrationOptions {\n if (this.isPasskeyEnrolled()) {\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.AlreadyEnrolled,\n { code: PasskeyControllerErrorCode.AlreadyEnrolled },\n );\n }\n\n const includePrf = creationOptionsConfig?.prfAvailable !== false;\n const prfSalt = includePrf ? randomBytesToBase64URL(32) : undefined;\n const userHandle = randomBytesToBase64URL(64);\n const challenge = randomBytesToBase64URL(32);\n\n const extensions: Record<string, unknown> = {};\n if (prfSalt) {\n extensions.prf = { eval: { first: prfSalt } };\n }\n\n const options: PasskeyRegistrationOptions = {\n rp: {\n name: this.#rpName,\n id: this.#rpId,\n },\n user: {\n id: userHandle,\n name: this.#userName,\n displayName: this.#userDisplayName,\n },\n challenge,\n pubKeyCredParams: [\n { alg: COSEALG.EdDSA, type: 'public-key' },\n { alg: COSEALG.ES256, type: 'public-key' },\n { alg: COSEALG.RS256, type: 'public-key' },\n ],\n timeout: WEBAUTHN_TIMEOUT_MS,\n authenticatorSelection: {\n userVerification: 'preferred',\n authenticatorAttachment: 'platform',\n residentKey: 'preferred',\n },\n hints: ['client-device', 'hybrid'],\n attestation: 'none',\n ...(Object.keys(extensions).length > 0 ? { extensions } : {}),\n };\n\n this.#ceremonyManager.saveRegistrationCeremony(challenge, {\n userHandle,\n prfSalt,\n challenge,\n createdAt: Date.now(),\n });\n\n return options;\n }\n\n /**\n * Builds WebAuthn credential request options for the post-registration\n * authentication step (between `create` and {@link protectVaultKeyWithPasskey}).\n *\n * @param params - Input for the pending registration ceremony.\n * @param params.registrationResponse - Result of `navigator.credentials.create()`.\n * @returns Public key credential request options for `navigator.credentials.get()`.\n */\n generatePostRegistrationAuthenticationOptions(params: {\n registrationResponse: PasskeyRegistrationResponse;\n }): PasskeyAuthenticationOptions {\n // get registration ceremony\n const { registrationResponse } = params;\n const regChallenge = this.#getChallengeFromClientData(\n registrationResponse.response.clientDataJSON,\n );\n const registrationCeremony =\n this.#ceremonyManager.getRegistrationCeremony(regChallenge);\n if (!registrationCeremony) {\n log('No active passkey registration ceremony for challenge');\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.NoRegistrationCeremony,\n { code: PasskeyControllerErrorCode.NoRegistrationCeremony },\n );\n }\n\n // build auth options\n const challenge = randomBytesToBase64URL(32);\n const extensions: Record<string, unknown> = {};\n if (registrationCeremony.prfSalt) {\n extensions.prf = { eval: { first: registrationCeremony.prfSalt } };\n }\n const options: PasskeyAuthenticationOptions = {\n challenge,\n rpId: this.#rpId,\n allowCredentials: [\n {\n id: registrationResponse.id,\n type: 'public-key',\n transports: registrationResponse.response.transports as\n | AuthenticatorTransportFuture[]\n | undefined,\n },\n ],\n userVerification: 'preferred',\n hints: ['client-device', 'hybrid'],\n timeout: WEBAUTHN_TIMEOUT_MS,\n extensions,\n };\n\n // save auth ceremony\n this.#ceremonyManager.saveAuthenticationCeremony(challenge, {\n challenge,\n createdAt: Date.now(),\n });\n\n return options;\n }\n\n /**\n * Builds WebAuthn credential request options for the enrolled passkey.\n *\n * @returns Public key credential request options for `navigator.credentials.get()`.\n */\n generateAuthenticationOptions(): PasskeyAuthenticationOptions {\n const record = this.#requireEnrolled();\n\n const challenge = randomBytesToBase64URL(32);\n\n const extensions: Record<string, unknown> = {};\n if (record.keyDerivation.method === 'prf') {\n extensions.prf = { eval: { first: record.keyDerivation.prfSalt } };\n }\n\n const options: PasskeyAuthenticationOptions = {\n challenge,\n rpId: this.#rpId,\n allowCredentials: [\n {\n id: record.credential.id,\n type: 'public-key',\n transports: record.credential.transports,\n },\n ],\n userVerification: 'preferred',\n hints: ['client-device', 'hybrid'],\n timeout: WEBAUTHN_TIMEOUT_MS,\n extensions,\n };\n\n this.#ceremonyManager.saveAuthenticationCeremony(challenge, {\n challenge,\n createdAt: Date.now(),\n });\n\n return options;\n }\n\n /**\n * Verifies registration and post-registration authentication, then stores the\n * vault key encrypted under the new passkey.\n *\n * @param params - Enrollment completion inputs.\n * @param params.registrationResponse - Result of `navigator.credentials.create()`.\n * @param params.authenticationResponse - Result of `navigator.credentials.get()` after {@link generatePostRegistrationAuthenticationOptions}.\n * @param params.vaultKey - Vault encryption key to encrypt and persist.\n */\n async protectVaultKeyWithPasskey(params: {\n registrationResponse: PasskeyRegistrationResponse;\n authenticationResponse: PasskeyAuthenticationResponse;\n vaultKey: string;\n }): Promise<void> {\n if (this.isPasskeyEnrolled()) {\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.AlreadyEnrolled,\n { code: PasskeyControllerErrorCode.AlreadyEnrolled },\n );\n }\n const { registrationResponse, authenticationResponse, vaultKey } = params;\n\n // get registration ceremony\n const challenge = this.#getChallengeFromClientData(\n registrationResponse.response.clientDataJSON,\n );\n const registrationCeremony =\n this.#ceremonyManager.getRegistrationCeremony(challenge);\n if (!registrationCeremony) {\n log('No active passkey registration ceremony for challenge');\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.NoRegistrationCeremony,\n { code: PasskeyControllerErrorCode.NoRegistrationCeremony },\n );\n }\n\n try {\n // verify registration response\n const { verified, registrationInfo } = await verifyRegistrationResponse({\n response: registrationResponse,\n expectedChallenge: registrationCeremony.challenge,\n expectedOrigin: this.#expectedOrigin,\n expectedRPIDs: this.#expectedRPIDs,\n requireUserVerification: false,\n }).catch((error) => {\n log('Error verifying passkey registration response', error);\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.RegistrationVerificationFailed,\n {\n code: PasskeyControllerErrorCode.RegistrationVerificationFailed,\n cause: error instanceof Error ? error : new Error(String(error)),\n },\n );\n });\n if (!verified || !registrationInfo) {\n log(\n 'Passkey registration verification returned unverified or missing registration info',\n );\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.RegistrationVerificationFailed,\n { code: PasskeyControllerErrorCode.RegistrationVerificationFailed },\n );\n }\n\n // verify authentication response\n const credential = {\n id: registrationInfo.credentialId,\n publicKey: bytesToBase64URL(registrationInfo.publicKey),\n counter: registrationInfo.counter,\n transports: registrationInfo.transports,\n aaguid: registrationInfo.aaguid,\n };\n const { newCounter } = await this.#verifyAuthenticationResponse(\n authenticationResponse,\n credential,\n );\n\n // determine key derivation method\n const prfFirst = (\n authenticationResponse.clientExtensionResults as PrfClientExtensionResults\n )?.prf?.results?.first;\n const authHasPrfOutput =\n typeof prfFirst === 'string' && prfFirst.length > 0;\n const keyDerivation: PasskeyKeyDerivation =\n authHasPrfOutput && registrationCeremony.prfSalt\n ? { method: 'prf', prfSalt: registrationCeremony.prfSalt }\n : { method: 'userHandle' };\n\n if (\n keyDerivation.method === 'userHandle' &&\n authenticationResponse.response.userHandle !==\n registrationCeremony.userHandle\n ) {\n log(\n 'Post-registration assertion userHandle does not match registration ceremony',\n );\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.AuthenticationVerificationFailed,\n { code: PasskeyControllerErrorCode.AuthenticationVerificationFailed },\n );\n }\n\n // derive key and encrypt vault key\n const encKey = deriveKeyFromAuthenticationResponse(\n authenticationResponse,\n { credential, keyDerivation },\n );\n const { ciphertext, iv } = encryptWithKey(vaultKey, encKey);\n\n // persist passkey record\n this.update((state) => {\n state.passkeyRecord = {\n credential: {\n ...credential,\n counter: Math.max(newCounter, credential.counter),\n },\n encryptedVaultKey: { ciphertext, iv },\n keyDerivation,\n };\n });\n } finally {\n // delete registration ceremony\n this.#ceremonyManager.deleteRegistrationCeremony(challenge);\n }\n }\n\n /**\n * Verifies an authentication assertion and returns the decrypted vault key.\n *\n * @param authenticationResponse - Result of `navigator.credentials.get()`.\n * @returns The plaintext vault encryption key.\n */\n async retrieveVaultKeyWithPasskey(\n authenticationResponse: PasskeyAuthenticationResponse,\n ): Promise<string> {\n const passkeyRecord = this.#requireEnrolled();\n\n // verify authentication response and update counter\n const { newCounter } = await this.#verifyAuthenticationResponse(\n authenticationResponse,\n passkeyRecord.credential,\n );\n this.update((state) => {\n if (!state.passkeyRecord) {\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.NotEnrolled,\n { code: PasskeyControllerErrorCode.NotEnrolled },\n );\n }\n state.passkeyRecord.credential.counter = Math.max(\n newCounter,\n state.passkeyRecord.credential.counter,\n );\n });\n\n // derive key\n const encKey = deriveKeyFromAuthenticationResponse(\n authenticationResponse,\n passkeyRecord,\n );\n\n // decrypt vault key\n try {\n const vaultKey = decryptWithKey(\n passkeyRecord.encryptedVaultKey.ciphertext,\n passkeyRecord.encryptedVaultKey.iv,\n encKey,\n );\n return vaultKey;\n } catch (cause) {\n log(\n 'Error decrypting vault key with passkey',\n cause instanceof Error ? cause : new Error(String(cause)),\n );\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.VaultKeyDecryptionFailed,\n {\n code: PasskeyControllerErrorCode.VaultKeyDecryptionFailed,\n cause: cause instanceof Error ? cause : new Error(String(cause)),\n },\n );\n }\n }\n\n /**\n * Checks whether the given authentication assertion is valid for the enrolled passkey.\n *\n * On failure, returns `false` for {@link PasskeyControllerError} with a `code`;\n * other errors propagate.\n *\n * @param authenticationResponse - Result of `navigator.credentials.get()`.\n * @returns `true` if verification succeeds, otherwise `false`.\n */\n async verifyPasskeyAuthentication(\n authenticationResponse: PasskeyAuthenticationResponse,\n ): Promise<boolean> {\n try {\n await this.retrieveVaultKeyWithPasskey(authenticationResponse);\n return true;\n } catch (error: unknown) {\n if (error instanceof PasskeyControllerError && error.code !== undefined) {\n return false;\n }\n throw error;\n }\n }\n\n /**\n * Re-wraps the vault key after rotation. Updates persisted `encryptedVaultKey` on success.\n *\n * Does not verify WebAuthn or ceremony state—call only after your layer has authenticated\n * the user (passkey `get()` + verified assertion, or verified password). On passkey paths,\n * pass the same `authenticationResponse` you just verified (e.g. from\n * {@link retrieveVaultKeyWithPasskey} / {@link verifyPasskeyAuthentication}).\n *\n * @param params - Re-wrap inputs.\n * @param params.authenticationResponse - Used to derive the wrapping key.\n * @param params.oldVaultKey - Expected current vault key.\n * @param params.newVaultKey - New vault key to encrypt under the passkey.\n */\n async renewVaultKeyProtection(params: {\n authenticationResponse: PasskeyAuthenticationResponse;\n oldVaultKey: string;\n newVaultKey: string;\n }): Promise<void> {\n const { authenticationResponse } = params;\n const passkeyRecord = this.#requireEnrolled();\n\n // derive key\n const encKey = deriveKeyFromAuthenticationResponse(\n authenticationResponse,\n passkeyRecord,\n );\n\n // decrypt vault key\n let decryptedVaultKey: string;\n try {\n decryptedVaultKey = decryptWithKey(\n passkeyRecord.encryptedVaultKey.ciphertext,\n passkeyRecord.encryptedVaultKey.iv,\n encKey,\n );\n } catch (error) {\n log(\n 'Error decrypting vault key during passkey vault key renewal',\n error instanceof Error ? error : new Error(String(error)),\n );\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.VaultKeyDecryptionFailed,\n {\n code: PasskeyControllerErrorCode.VaultKeyDecryptionFailed,\n cause: error instanceof Error ? error : new Error(String(error)),\n },\n );\n }\n\n // check if vault key matches\n const { oldVaultKey, newVaultKey } = params;\n if (\n !areUint8ArraysEqual(\n stringToBytes(decryptedVaultKey),\n stringToBytes(oldVaultKey),\n )\n ) {\n log(\n 'Passkey renewal rejected: decrypted vault key does not match oldVaultKey',\n );\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.VaultKeyMismatch,\n { code: PasskeyControllerErrorCode.VaultKeyMismatch },\n );\n }\n\n // encrypt new vault key\n const { ciphertext, iv } = encryptWithKey(newVaultKey, encKey);\n\n // persist passkey record (mutate current state only for vault key material)\n this.update((state) => {\n if (!state.passkeyRecord) {\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.NotEnrolled,\n {\n code: PasskeyControllerErrorCode.NotEnrolled,\n },\n );\n }\n state.passkeyRecord.encryptedVaultKey = { ciphertext, iv };\n });\n }\n\n /**\n * Clears enrolled passkey state and in-flight ceremonies. Call only after the same\n * auth gate as renewal (verified passkey assertion or password).\n */\n removePasskey(): void {\n this.update(() => getDefaultPasskeyControllerState());\n this.#ceremonyManager.clear();\n }\n\n /**\n * Resets state and clears in-flight registration/authentication ceremonies.\n */\n clearState(): void {\n this.removePasskey();\n }\n\n /**\n * Releases all in-flight ceremony state and tears down the messenger.\n */\n destroy(): void {\n this.#ceremonyManager.clear();\n super.destroy();\n }\n\n /**\n * Validates a WebAuthn authentication response against stored credential data.\n *\n * @param authenticationResponse - Parsed authentication response from the client.\n * @param credential - Credential identifiers and public key material for verification.\n * @returns Updated authenticator signature counter.\n */\n async #verifyAuthenticationResponse(\n authenticationResponse: PasskeyAuthenticationResponse,\n credential: PasskeyCredentialInfo,\n ): Promise<{ newCounter: number }> {\n // get challenge\n const challenge = this.#getChallengeFromClientData(\n authenticationResponse.response.clientDataJSON,\n );\n\n // get authentication ceremony\n const authenticationCeremony =\n this.#ceremonyManager.getAuthenticationCeremony(challenge);\n if (!authenticationCeremony) {\n log('No active passkey authentication ceremony for challenge');\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.NoAuthenticationCeremony,\n { code: PasskeyControllerErrorCode.NoAuthenticationCeremony },\n );\n }\n\n try {\n // verify authentication response\n const result = await verifyAuthenticationResponse({\n response: authenticationResponse,\n expectedChallenge: authenticationCeremony.challenge,\n expectedOrigin: this.#expectedOrigin,\n expectedRPIDs: this.#expectedRPIDs,\n credential: {\n id: credential.id,\n publicKey: base64URLToBytes(credential.publicKey),\n counter: credential.counter,\n transports: credential.transports,\n },\n // UV optional for device compatibility; vault key remains password-gated.\n requireUserVerification: false,\n }).catch((error) => {\n log(\n 'Error verifying passkey authentication response',\n error instanceof Error ? error : new Error(String(error)),\n );\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.AuthenticationVerificationFailed,\n {\n code: PasskeyControllerErrorCode.AuthenticationVerificationFailed,\n cause: error instanceof Error ? error : new Error(String(error)),\n },\n );\n });\n if (!result.verified) {\n log('Passkey authentication verification returned unverified');\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.AuthenticationVerificationFailed,\n {\n code: PasskeyControllerErrorCode.AuthenticationVerificationFailed,\n },\n );\n }\n\n return { newCounter: result.authenticationInfo.newCounter };\n } finally {\n // delete authentication ceremony\n this.#ceremonyManager.deleteAuthenticationCeremony(challenge);\n }\n }\n}\n"]}
@@ -11,9 +11,10 @@ exports.CeremonyManager = exports.MAX_CONCURRENT_PASSKEY_CEREMONIES = exports.CE
11
11
  exports.WEBAUTHN_TIMEOUT_MS = 60000;
12
12
  /**
13
13
  * Extra allowance beyond {@link WEBAUTHN_TIMEOUT_MS} before in-memory
14
- * ceremony state is discarded (covers slow UX and clock skew).
14
+ * ceremony state is discarded (covers slow UX, multi-step enrollment, and
15
+ * clock skew).
15
16
  */
16
- exports.CEREMONY_TTL_SLACK_MS = 15000;
17
+ exports.CEREMONY_TTL_SLACK_MS = 120000;
17
18
  /**
18
19
  * Maximum age for in-flight registration or authentication ceremony state
19
20
  * (between options and verified response). This bounds the lifetime of a
@@ -1 +1 @@
1
- {"version":3,"file":"ceremony-manager.cjs","sourceRoot":"","sources":["../src/ceremony-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n PasskeyAuthenticationCeremony,\n PasskeyRegistrationCeremony,\n} from './types';\n\n/** WebAuthn `timeout` for credential creation and assertion (ms). */\nexport const WEBAUTHN_TIMEOUT_MS = 60_000;\n\n/**\n * Extra allowance beyond {@link WEBAUTHN_TIMEOUT_MS} before in-memory\n * ceremony state is discarded (covers slow UX and clock skew).\n */\nexport const CEREMONY_TTL_SLACK_MS = 15_000;\n\n/**\n * Maximum age for in-flight registration or authentication ceremony state\n * (between options and verified response). This bounds the lifetime of a\n * single WebAuthn ceremony only; it is not a user login session timeout.\n */\nexport const CEREMONY_MAX_AGE_MS = WEBAUTHN_TIMEOUT_MS + CEREMONY_TTL_SLACK_MS;\n\n/**\n * Upper bound on concurrent in-memory ceremonies per flow type (registration\n * vs authentication), for abuse / leak protection.\n */\nexport const MAX_CONCURRENT_PASSKEY_CEREMONIES = 16;\n\ntype CeremonyFlow = 'registration' | 'authentication';\n\n/**\n * In-memory store for in-flight WebAuthn ceremonies (registration vs authentication),\n * keyed by base64url challenge. Enforces TTL and a per-flow size cap; not user session state.\n */\nexport class CeremonyManager {\n readonly #registrationMap = new Map<string, PasskeyRegistrationCeremony>();\n\n readonly #authenticationMap = new Map<\n string,\n PasskeyAuthenticationCeremony\n >();\n\n /**\n * Challenge-keyed map for prune/capacity helpers.\n *\n * @param ceremonyType - Which in-flight ceremony map to use.\n * @returns The registration or authentication ceremony map for the given flow.\n */\n #getMap(\n ceremonyType: CeremonyFlow,\n ): Map<string, PasskeyRegistrationCeremony | PasskeyAuthenticationCeremony> {\n return ceremonyType === 'registration'\n ? this.#registrationMap\n : this.#authenticationMap;\n }\n\n #pruneExpired(ceremonyType: CeremonyFlow): void {\n const now = Date.now();\n const map = this.#getMap(ceremonyType);\n for (const [key, ceremony] of map) {\n if (now - ceremony.createdAt > CEREMONY_MAX_AGE_MS) {\n map.delete(key);\n }\n }\n }\n\n /**\n * Removes the oldest entry (by `createdAt`) until size is below the cap.\n *\n * @param ceremonyType - Which in-flight ceremony map to evict from.\n */\n #enforceCapacity(ceremonyType: CeremonyFlow): void {\n const map = this.#getMap(ceremonyType);\n while (map.size >= MAX_CONCURRENT_PASSKEY_CEREMONIES) {\n let oldestKey: string | undefined;\n let oldestTime = Infinity;\n for (const [mapKey, ceremony] of map) {\n if (ceremony.createdAt < oldestTime) {\n oldestTime = ceremony.createdAt;\n oldestKey = mapKey;\n }\n }\n if (oldestKey === undefined) {\n break;\n }\n map.delete(oldestKey);\n }\n }\n\n /**\n * Records registration ceremony state after pruning expired rows and evicting oldest if at cap.\n *\n * @param challenge - Same base64url challenge as in the creation options `challenge` field.\n * @param ceremony - Payload to retrieve when the registration response returns.\n */\n saveRegistrationCeremony(\n challenge: string,\n ceremony: PasskeyRegistrationCeremony,\n ): void {\n this.#pruneExpired('registration');\n this.#enforceCapacity('registration');\n this.#registrationMap.set(challenge, ceremony);\n }\n\n /**\n * Records authentication ceremony state after pruning expired rows and evicting oldest if at cap.\n *\n * @param challenge - Same base64url challenge as in the request options `challenge` field.\n * @param ceremony - Payload to retrieve when the assertion response returns.\n */\n saveAuthenticationCeremony(\n challenge: string,\n ceremony: PasskeyAuthenticationCeremony,\n ): void {\n this.#pruneExpired('authentication');\n this.#enforceCapacity('authentication');\n this.#authenticationMap.set(challenge, ceremony);\n }\n\n /**\n * Returns registration ceremony for a challenge, pruning expired entries on this map first.\n *\n * @param challenge - Base64url challenge from decoded `clientDataJSON` (matches stored key).\n * @returns Stored ceremony, or `undefined` if none or expired.\n */\n getRegistrationCeremony(\n challenge: string,\n ): PasskeyRegistrationCeremony | undefined {\n this.#pruneExpired('registration');\n return this.#registrationMap.get(challenge);\n }\n\n /**\n * Returns authentication ceremony for a challenge, pruning expired entries on this map first.\n *\n * @param challenge - Base64url challenge from decoded `clientDataJSON` (matches stored key).\n * @returns Stored ceremony, or `undefined` if none or expired.\n */\n getAuthenticationCeremony(\n challenge: string,\n ): PasskeyAuthenticationCeremony | undefined {\n this.#pruneExpired('authentication');\n return this.#authenticationMap.get(challenge);\n }\n\n /**\n * Removes a registration ceremony by challenge.\n *\n * @param challenge - Map key for the ceremony to remove.\n * @returns Whether an entry was deleted.\n */\n deleteRegistrationCeremony(challenge: string): boolean {\n return this.#registrationMap.delete(challenge);\n }\n\n /**\n * Removes an authentication ceremony by challenge.\n *\n * @param challenge - Map key for the ceremony to remove.\n * @returns Whether an entry was deleted.\n */\n deleteAuthenticationCeremony(challenge: string): boolean {\n return this.#authenticationMap.delete(challenge);\n }\n\n /** Drops all in-flight registration and authentication ceremonies. */\n clear(): void {\n this.#registrationMap.clear();\n this.#authenticationMap.clear();\n }\n}\n"]}
1
+ {"version":3,"file":"ceremony-manager.cjs","sourceRoot":"","sources":["../src/ceremony-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n PasskeyAuthenticationCeremony,\n PasskeyRegistrationCeremony,\n} from './types';\n\n/** WebAuthn `timeout` for credential creation and assertion (ms). */\nexport const WEBAUTHN_TIMEOUT_MS = 60_000;\n\n/**\n * Extra allowance beyond {@link WEBAUTHN_TIMEOUT_MS} before in-memory\n * ceremony state is discarded (covers slow UX, multi-step enrollment, and\n * clock skew).\n */\nexport const CEREMONY_TTL_SLACK_MS = 120_000;\n\n/**\n * Maximum age for in-flight registration or authentication ceremony state\n * (between options and verified response). This bounds the lifetime of a\n * single WebAuthn ceremony only; it is not a user login session timeout.\n */\nexport const CEREMONY_MAX_AGE_MS = WEBAUTHN_TIMEOUT_MS + CEREMONY_TTL_SLACK_MS;\n\n/**\n * Upper bound on concurrent in-memory ceremonies per flow type (registration\n * vs authentication), for abuse / leak protection.\n */\nexport const MAX_CONCURRENT_PASSKEY_CEREMONIES = 16;\n\ntype CeremonyFlow = 'registration' | 'authentication';\n\n/**\n * In-memory store for in-flight WebAuthn ceremonies (registration vs authentication),\n * keyed by base64url challenge. Enforces TTL and a per-flow size cap; not user session state.\n */\nexport class CeremonyManager {\n readonly #registrationMap = new Map<string, PasskeyRegistrationCeremony>();\n\n readonly #authenticationMap = new Map<\n string,\n PasskeyAuthenticationCeremony\n >();\n\n /**\n * Challenge-keyed map for prune/capacity helpers.\n *\n * @param ceremonyType - Which in-flight ceremony map to use.\n * @returns The registration or authentication ceremony map for the given flow.\n */\n #getMap(\n ceremonyType: CeremonyFlow,\n ): Map<string, PasskeyRegistrationCeremony | PasskeyAuthenticationCeremony> {\n return ceremonyType === 'registration'\n ? this.#registrationMap\n : this.#authenticationMap;\n }\n\n #pruneExpired(ceremonyType: CeremonyFlow): void {\n const now = Date.now();\n const map = this.#getMap(ceremonyType);\n for (const [key, ceremony] of map) {\n if (now - ceremony.createdAt > CEREMONY_MAX_AGE_MS) {\n map.delete(key);\n }\n }\n }\n\n /**\n * Removes the oldest entry (by `createdAt`) until size is below the cap.\n *\n * @param ceremonyType - Which in-flight ceremony map to evict from.\n */\n #enforceCapacity(ceremonyType: CeremonyFlow): void {\n const map = this.#getMap(ceremonyType);\n while (map.size >= MAX_CONCURRENT_PASSKEY_CEREMONIES) {\n let oldestKey: string | undefined;\n let oldestTime = Infinity;\n for (const [mapKey, ceremony] of map) {\n if (ceremony.createdAt < oldestTime) {\n oldestTime = ceremony.createdAt;\n oldestKey = mapKey;\n }\n }\n if (oldestKey === undefined) {\n break;\n }\n map.delete(oldestKey);\n }\n }\n\n /**\n * Records registration ceremony state after pruning expired rows and evicting oldest if at cap.\n *\n * @param challenge - Same base64url challenge as in the creation options `challenge` field.\n * @param ceremony - Payload to retrieve when the registration response returns.\n */\n saveRegistrationCeremony(\n challenge: string,\n ceremony: PasskeyRegistrationCeremony,\n ): void {\n this.#pruneExpired('registration');\n this.#enforceCapacity('registration');\n this.#registrationMap.set(challenge, ceremony);\n }\n\n /**\n * Records authentication ceremony state after pruning expired rows and evicting oldest if at cap.\n *\n * @param challenge - Same base64url challenge as in the request options `challenge` field.\n * @param ceremony - Payload to retrieve when the assertion response returns.\n */\n saveAuthenticationCeremony(\n challenge: string,\n ceremony: PasskeyAuthenticationCeremony,\n ): void {\n this.#pruneExpired('authentication');\n this.#enforceCapacity('authentication');\n this.#authenticationMap.set(challenge, ceremony);\n }\n\n /**\n * Returns registration ceremony for a challenge, pruning expired entries on this map first.\n *\n * @param challenge - Base64url challenge from decoded `clientDataJSON` (matches stored key).\n * @returns Stored ceremony, or `undefined` if none or expired.\n */\n getRegistrationCeremony(\n challenge: string,\n ): PasskeyRegistrationCeremony | undefined {\n this.#pruneExpired('registration');\n return this.#registrationMap.get(challenge);\n }\n\n /**\n * Returns authentication ceremony for a challenge, pruning expired entries on this map first.\n *\n * @param challenge - Base64url challenge from decoded `clientDataJSON` (matches stored key).\n * @returns Stored ceremony, or `undefined` if none or expired.\n */\n getAuthenticationCeremony(\n challenge: string,\n ): PasskeyAuthenticationCeremony | undefined {\n this.#pruneExpired('authentication');\n return this.#authenticationMap.get(challenge);\n }\n\n /**\n * Removes a registration ceremony by challenge.\n *\n * @param challenge - Map key for the ceremony to remove.\n * @returns Whether an entry was deleted.\n */\n deleteRegistrationCeremony(challenge: string): boolean {\n return this.#registrationMap.delete(challenge);\n }\n\n /**\n * Removes an authentication ceremony by challenge.\n *\n * @param challenge - Map key for the ceremony to remove.\n * @returns Whether an entry was deleted.\n */\n deleteAuthenticationCeremony(challenge: string): boolean {\n return this.#authenticationMap.delete(challenge);\n }\n\n /** Drops all in-flight registration and authentication ceremonies. */\n clear(): void {\n this.#registrationMap.clear();\n this.#authenticationMap.clear();\n }\n}\n"]}
@@ -3,9 +3,10 @@ import type { PasskeyAuthenticationCeremony, PasskeyRegistrationCeremony } from
3
3
  export declare const WEBAUTHN_TIMEOUT_MS = 60000;
4
4
  /**
5
5
  * Extra allowance beyond {@link WEBAUTHN_TIMEOUT_MS} before in-memory
6
- * ceremony state is discarded (covers slow UX and clock skew).
6
+ * ceremony state is discarded (covers slow UX, multi-step enrollment, and
7
+ * clock skew).
7
8
  */
8
- export declare const CEREMONY_TTL_SLACK_MS = 15000;
9
+ export declare const CEREMONY_TTL_SLACK_MS = 120000;
9
10
  /**
10
11
  * Maximum age for in-flight registration or authentication ceremony state
11
12
  * (between options and verified response). This bounds the lifetime of a
@@ -1 +1 @@
1
- {"version":3,"file":"ceremony-manager.d.cts","sourceRoot":"","sources":["../src/ceremony-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,6BAA6B,EAC7B,2BAA2B,EAC5B,oBAAgB;AAEjB,qEAAqE;AACrE,eAAO,MAAM,mBAAmB,QAAS,CAAC;AAE1C;;;GAGG;AACH,eAAO,MAAM,qBAAqB,QAAS,CAAC;AAE5C;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,QAA8C,CAAC;AAE/E;;;GAGG;AACH,eAAO,MAAM,iCAAiC,KAAK,CAAC;AAIpD;;;GAGG;AACH,qBAAa,eAAe;;IAuD1B;;;;;OAKG;IACH,wBAAwB,CACtB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,2BAA2B,GACpC,IAAI;IAMP;;;;;OAKG;IACH,0BAA0B,CACxB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,6BAA6B,GACtC,IAAI;IAMP;;;;;OAKG;IACH,uBAAuB,CACrB,SAAS,EAAE,MAAM,GAChB,2BAA2B,GAAG,SAAS;IAK1C;;;;;OAKG;IACH,yBAAyB,CACvB,SAAS,EAAE,MAAM,GAChB,6BAA6B,GAAG,SAAS;IAK5C;;;;;OAKG;IACH,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAItD;;;;;OAKG;IACH,4BAA4B,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAIxD,sEAAsE;IACtE,KAAK,IAAI,IAAI;CAId"}
1
+ {"version":3,"file":"ceremony-manager.d.cts","sourceRoot":"","sources":["../src/ceremony-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,6BAA6B,EAC7B,2BAA2B,EAC5B,oBAAgB;AAEjB,qEAAqE;AACrE,eAAO,MAAM,mBAAmB,QAAS,CAAC;AAE1C;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,SAAU,CAAC;AAE7C;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,QAA8C,CAAC;AAE/E;;;GAGG;AACH,eAAO,MAAM,iCAAiC,KAAK,CAAC;AAIpD;;;GAGG;AACH,qBAAa,eAAe;;IAuD1B;;;;;OAKG;IACH,wBAAwB,CACtB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,2BAA2B,GACpC,IAAI;IAMP;;;;;OAKG;IACH,0BAA0B,CACxB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,6BAA6B,GACtC,IAAI;IAMP;;;;;OAKG;IACH,uBAAuB,CACrB,SAAS,EAAE,MAAM,GAChB,2BAA2B,GAAG,SAAS;IAK1C;;;;;OAKG;IACH,yBAAyB,CACvB,SAAS,EAAE,MAAM,GAChB,6BAA6B,GAAG,SAAS;IAK5C;;;;;OAKG;IACH,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAItD;;;;;OAKG;IACH,4BAA4B,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAIxD,sEAAsE;IACtE,KAAK,IAAI,IAAI;CAId"}
@@ -3,9 +3,10 @@ import type { PasskeyAuthenticationCeremony, PasskeyRegistrationCeremony } from
3
3
  export declare const WEBAUTHN_TIMEOUT_MS = 60000;
4
4
  /**
5
5
  * Extra allowance beyond {@link WEBAUTHN_TIMEOUT_MS} before in-memory
6
- * ceremony state is discarded (covers slow UX and clock skew).
6
+ * ceremony state is discarded (covers slow UX, multi-step enrollment, and
7
+ * clock skew).
7
8
  */
8
- export declare const CEREMONY_TTL_SLACK_MS = 15000;
9
+ export declare const CEREMONY_TTL_SLACK_MS = 120000;
9
10
  /**
10
11
  * Maximum age for in-flight registration or authentication ceremony state
11
12
  * (between options and verified response). This bounds the lifetime of a
@@ -1 +1 @@
1
- {"version":3,"file":"ceremony-manager.d.mts","sourceRoot":"","sources":["../src/ceremony-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,6BAA6B,EAC7B,2BAA2B,EAC5B,oBAAgB;AAEjB,qEAAqE;AACrE,eAAO,MAAM,mBAAmB,QAAS,CAAC;AAE1C;;;GAGG;AACH,eAAO,MAAM,qBAAqB,QAAS,CAAC;AAE5C;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,QAA8C,CAAC;AAE/E;;;GAGG;AACH,eAAO,MAAM,iCAAiC,KAAK,CAAC;AAIpD;;;GAGG;AACH,qBAAa,eAAe;;IAuD1B;;;;;OAKG;IACH,wBAAwB,CACtB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,2BAA2B,GACpC,IAAI;IAMP;;;;;OAKG;IACH,0BAA0B,CACxB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,6BAA6B,GACtC,IAAI;IAMP;;;;;OAKG;IACH,uBAAuB,CACrB,SAAS,EAAE,MAAM,GAChB,2BAA2B,GAAG,SAAS;IAK1C;;;;;OAKG;IACH,yBAAyB,CACvB,SAAS,EAAE,MAAM,GAChB,6BAA6B,GAAG,SAAS;IAK5C;;;;;OAKG;IACH,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAItD;;;;;OAKG;IACH,4BAA4B,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAIxD,sEAAsE;IACtE,KAAK,IAAI,IAAI;CAId"}
1
+ {"version":3,"file":"ceremony-manager.d.mts","sourceRoot":"","sources":["../src/ceremony-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,6BAA6B,EAC7B,2BAA2B,EAC5B,oBAAgB;AAEjB,qEAAqE;AACrE,eAAO,MAAM,mBAAmB,QAAS,CAAC;AAE1C;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,SAAU,CAAC;AAE7C;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,QAA8C,CAAC;AAE/E;;;GAGG;AACH,eAAO,MAAM,iCAAiC,KAAK,CAAC;AAIpD;;;GAGG;AACH,qBAAa,eAAe;;IAuD1B;;;;;OAKG;IACH,wBAAwB,CACtB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,2BAA2B,GACpC,IAAI;IAMP;;;;;OAKG;IACH,0BAA0B,CACxB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,6BAA6B,GACtC,IAAI;IAMP;;;;;OAKG;IACH,uBAAuB,CACrB,SAAS,EAAE,MAAM,GAChB,2BAA2B,GAAG,SAAS;IAK1C;;;;;OAKG;IACH,yBAAyB,CACvB,SAAS,EAAE,MAAM,GAChB,6BAA6B,GAAG,SAAS;IAK5C;;;;;OAKG;IACH,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAItD;;;;;OAKG;IACH,4BAA4B,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAIxD,sEAAsE;IACtE,KAAK,IAAI,IAAI;CAId"}
@@ -8,9 +8,10 @@ var _CeremonyManager_instances, _CeremonyManager_registrationMap, _CeremonyManag
8
8
  export const WEBAUTHN_TIMEOUT_MS = 60000;
9
9
  /**
10
10
  * Extra allowance beyond {@link WEBAUTHN_TIMEOUT_MS} before in-memory
11
- * ceremony state is discarded (covers slow UX and clock skew).
11
+ * ceremony state is discarded (covers slow UX, multi-step enrollment, and
12
+ * clock skew).
12
13
  */
13
- export const CEREMONY_TTL_SLACK_MS = 15000;
14
+ export const CEREMONY_TTL_SLACK_MS = 120000;
14
15
  /**
15
16
  * Maximum age for in-flight registration or authentication ceremony state
16
17
  * (between options and verified response). This bounds the lifetime of a
@@ -1 +1 @@
1
- {"version":3,"file":"ceremony-manager.mjs","sourceRoot":"","sources":["../src/ceremony-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n PasskeyAuthenticationCeremony,\n PasskeyRegistrationCeremony,\n} from './types';\n\n/** WebAuthn `timeout` for credential creation and assertion (ms). */\nexport const WEBAUTHN_TIMEOUT_MS = 60_000;\n\n/**\n * Extra allowance beyond {@link WEBAUTHN_TIMEOUT_MS} before in-memory\n * ceremony state is discarded (covers slow UX and clock skew).\n */\nexport const CEREMONY_TTL_SLACK_MS = 15_000;\n\n/**\n * Maximum age for in-flight registration or authentication ceremony state\n * (between options and verified response). This bounds the lifetime of a\n * single WebAuthn ceremony only; it is not a user login session timeout.\n */\nexport const CEREMONY_MAX_AGE_MS = WEBAUTHN_TIMEOUT_MS + CEREMONY_TTL_SLACK_MS;\n\n/**\n * Upper bound on concurrent in-memory ceremonies per flow type (registration\n * vs authentication), for abuse / leak protection.\n */\nexport const MAX_CONCURRENT_PASSKEY_CEREMONIES = 16;\n\ntype CeremonyFlow = 'registration' | 'authentication';\n\n/**\n * In-memory store for in-flight WebAuthn ceremonies (registration vs authentication),\n * keyed by base64url challenge. Enforces TTL and a per-flow size cap; not user session state.\n */\nexport class CeremonyManager {\n readonly #registrationMap = new Map<string, PasskeyRegistrationCeremony>();\n\n readonly #authenticationMap = new Map<\n string,\n PasskeyAuthenticationCeremony\n >();\n\n /**\n * Challenge-keyed map for prune/capacity helpers.\n *\n * @param ceremonyType - Which in-flight ceremony map to use.\n * @returns The registration or authentication ceremony map for the given flow.\n */\n #getMap(\n ceremonyType: CeremonyFlow,\n ): Map<string, PasskeyRegistrationCeremony | PasskeyAuthenticationCeremony> {\n return ceremonyType === 'registration'\n ? this.#registrationMap\n : this.#authenticationMap;\n }\n\n #pruneExpired(ceremonyType: CeremonyFlow): void {\n const now = Date.now();\n const map = this.#getMap(ceremonyType);\n for (const [key, ceremony] of map) {\n if (now - ceremony.createdAt > CEREMONY_MAX_AGE_MS) {\n map.delete(key);\n }\n }\n }\n\n /**\n * Removes the oldest entry (by `createdAt`) until size is below the cap.\n *\n * @param ceremonyType - Which in-flight ceremony map to evict from.\n */\n #enforceCapacity(ceremonyType: CeremonyFlow): void {\n const map = this.#getMap(ceremonyType);\n while (map.size >= MAX_CONCURRENT_PASSKEY_CEREMONIES) {\n let oldestKey: string | undefined;\n let oldestTime = Infinity;\n for (const [mapKey, ceremony] of map) {\n if (ceremony.createdAt < oldestTime) {\n oldestTime = ceremony.createdAt;\n oldestKey = mapKey;\n }\n }\n if (oldestKey === undefined) {\n break;\n }\n map.delete(oldestKey);\n }\n }\n\n /**\n * Records registration ceremony state after pruning expired rows and evicting oldest if at cap.\n *\n * @param challenge - Same base64url challenge as in the creation options `challenge` field.\n * @param ceremony - Payload to retrieve when the registration response returns.\n */\n saveRegistrationCeremony(\n challenge: string,\n ceremony: PasskeyRegistrationCeremony,\n ): void {\n this.#pruneExpired('registration');\n this.#enforceCapacity('registration');\n this.#registrationMap.set(challenge, ceremony);\n }\n\n /**\n * Records authentication ceremony state after pruning expired rows and evicting oldest if at cap.\n *\n * @param challenge - Same base64url challenge as in the request options `challenge` field.\n * @param ceremony - Payload to retrieve when the assertion response returns.\n */\n saveAuthenticationCeremony(\n challenge: string,\n ceremony: PasskeyAuthenticationCeremony,\n ): void {\n this.#pruneExpired('authentication');\n this.#enforceCapacity('authentication');\n this.#authenticationMap.set(challenge, ceremony);\n }\n\n /**\n * Returns registration ceremony for a challenge, pruning expired entries on this map first.\n *\n * @param challenge - Base64url challenge from decoded `clientDataJSON` (matches stored key).\n * @returns Stored ceremony, or `undefined` if none or expired.\n */\n getRegistrationCeremony(\n challenge: string,\n ): PasskeyRegistrationCeremony | undefined {\n this.#pruneExpired('registration');\n return this.#registrationMap.get(challenge);\n }\n\n /**\n * Returns authentication ceremony for a challenge, pruning expired entries on this map first.\n *\n * @param challenge - Base64url challenge from decoded `clientDataJSON` (matches stored key).\n * @returns Stored ceremony, or `undefined` if none or expired.\n */\n getAuthenticationCeremony(\n challenge: string,\n ): PasskeyAuthenticationCeremony | undefined {\n this.#pruneExpired('authentication');\n return this.#authenticationMap.get(challenge);\n }\n\n /**\n * Removes a registration ceremony by challenge.\n *\n * @param challenge - Map key for the ceremony to remove.\n * @returns Whether an entry was deleted.\n */\n deleteRegistrationCeremony(challenge: string): boolean {\n return this.#registrationMap.delete(challenge);\n }\n\n /**\n * Removes an authentication ceremony by challenge.\n *\n * @param challenge - Map key for the ceremony to remove.\n * @returns Whether an entry was deleted.\n */\n deleteAuthenticationCeremony(challenge: string): boolean {\n return this.#authenticationMap.delete(challenge);\n }\n\n /** Drops all in-flight registration and authentication ceremonies. */\n clear(): void {\n this.#registrationMap.clear();\n this.#authenticationMap.clear();\n }\n}\n"]}
1
+ {"version":3,"file":"ceremony-manager.mjs","sourceRoot":"","sources":["../src/ceremony-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n PasskeyAuthenticationCeremony,\n PasskeyRegistrationCeremony,\n} from './types';\n\n/** WebAuthn `timeout` for credential creation and assertion (ms). */\nexport const WEBAUTHN_TIMEOUT_MS = 60_000;\n\n/**\n * Extra allowance beyond {@link WEBAUTHN_TIMEOUT_MS} before in-memory\n * ceremony state is discarded (covers slow UX, multi-step enrollment, and\n * clock skew).\n */\nexport const CEREMONY_TTL_SLACK_MS = 120_000;\n\n/**\n * Maximum age for in-flight registration or authentication ceremony state\n * (between options and verified response). This bounds the lifetime of a\n * single WebAuthn ceremony only; it is not a user login session timeout.\n */\nexport const CEREMONY_MAX_AGE_MS = WEBAUTHN_TIMEOUT_MS + CEREMONY_TTL_SLACK_MS;\n\n/**\n * Upper bound on concurrent in-memory ceremonies per flow type (registration\n * vs authentication), for abuse / leak protection.\n */\nexport const MAX_CONCURRENT_PASSKEY_CEREMONIES = 16;\n\ntype CeremonyFlow = 'registration' | 'authentication';\n\n/**\n * In-memory store for in-flight WebAuthn ceremonies (registration vs authentication),\n * keyed by base64url challenge. Enforces TTL and a per-flow size cap; not user session state.\n */\nexport class CeremonyManager {\n readonly #registrationMap = new Map<string, PasskeyRegistrationCeremony>();\n\n readonly #authenticationMap = new Map<\n string,\n PasskeyAuthenticationCeremony\n >();\n\n /**\n * Challenge-keyed map for prune/capacity helpers.\n *\n * @param ceremonyType - Which in-flight ceremony map to use.\n * @returns The registration or authentication ceremony map for the given flow.\n */\n #getMap(\n ceremonyType: CeremonyFlow,\n ): Map<string, PasskeyRegistrationCeremony | PasskeyAuthenticationCeremony> {\n return ceremonyType === 'registration'\n ? this.#registrationMap\n : this.#authenticationMap;\n }\n\n #pruneExpired(ceremonyType: CeremonyFlow): void {\n const now = Date.now();\n const map = this.#getMap(ceremonyType);\n for (const [key, ceremony] of map) {\n if (now - ceremony.createdAt > CEREMONY_MAX_AGE_MS) {\n map.delete(key);\n }\n }\n }\n\n /**\n * Removes the oldest entry (by `createdAt`) until size is below the cap.\n *\n * @param ceremonyType - Which in-flight ceremony map to evict from.\n */\n #enforceCapacity(ceremonyType: CeremonyFlow): void {\n const map = this.#getMap(ceremonyType);\n while (map.size >= MAX_CONCURRENT_PASSKEY_CEREMONIES) {\n let oldestKey: string | undefined;\n let oldestTime = Infinity;\n for (const [mapKey, ceremony] of map) {\n if (ceremony.createdAt < oldestTime) {\n oldestTime = ceremony.createdAt;\n oldestKey = mapKey;\n }\n }\n if (oldestKey === undefined) {\n break;\n }\n map.delete(oldestKey);\n }\n }\n\n /**\n * Records registration ceremony state after pruning expired rows and evicting oldest if at cap.\n *\n * @param challenge - Same base64url challenge as in the creation options `challenge` field.\n * @param ceremony - Payload to retrieve when the registration response returns.\n */\n saveRegistrationCeremony(\n challenge: string,\n ceremony: PasskeyRegistrationCeremony,\n ): void {\n this.#pruneExpired('registration');\n this.#enforceCapacity('registration');\n this.#registrationMap.set(challenge, ceremony);\n }\n\n /**\n * Records authentication ceremony state after pruning expired rows and evicting oldest if at cap.\n *\n * @param challenge - Same base64url challenge as in the request options `challenge` field.\n * @param ceremony - Payload to retrieve when the assertion response returns.\n */\n saveAuthenticationCeremony(\n challenge: string,\n ceremony: PasskeyAuthenticationCeremony,\n ): void {\n this.#pruneExpired('authentication');\n this.#enforceCapacity('authentication');\n this.#authenticationMap.set(challenge, ceremony);\n }\n\n /**\n * Returns registration ceremony for a challenge, pruning expired entries on this map first.\n *\n * @param challenge - Base64url challenge from decoded `clientDataJSON` (matches stored key).\n * @returns Stored ceremony, or `undefined` if none or expired.\n */\n getRegistrationCeremony(\n challenge: string,\n ): PasskeyRegistrationCeremony | undefined {\n this.#pruneExpired('registration');\n return this.#registrationMap.get(challenge);\n }\n\n /**\n * Returns authentication ceremony for a challenge, pruning expired entries on this map first.\n *\n * @param challenge - Base64url challenge from decoded `clientDataJSON` (matches stored key).\n * @returns Stored ceremony, or `undefined` if none or expired.\n */\n getAuthenticationCeremony(\n challenge: string,\n ): PasskeyAuthenticationCeremony | undefined {\n this.#pruneExpired('authentication');\n return this.#authenticationMap.get(challenge);\n }\n\n /**\n * Removes a registration ceremony by challenge.\n *\n * @param challenge - Map key for the ceremony to remove.\n * @returns Whether an entry was deleted.\n */\n deleteRegistrationCeremony(challenge: string): boolean {\n return this.#registrationMap.delete(challenge);\n }\n\n /**\n * Removes an authentication ceremony by challenge.\n *\n * @param challenge - Map key for the ceremony to remove.\n * @returns Whether an entry was deleted.\n */\n deleteAuthenticationCeremony(challenge: string): boolean {\n return this.#authenticationMap.delete(challenge);\n }\n\n /** Drops all in-flight registration and authentication ceremonies. */\n clear(): void {\n this.#registrationMap.clear();\n this.#authenticationMap.clear();\n }\n}\n"]}
@@ -8,6 +8,7 @@ exports.controllerName = 'PasskeyController';
8
8
  */
9
9
  exports.PasskeyControllerErrorCode = {
10
10
  NotEnrolled: 'not_enrolled',
11
+ AlreadyEnrolled: 'already_enrolled',
11
12
  NoRegistrationCeremony: 'no_registration_ceremony',
12
13
  RegistrationVerificationFailed: 'registration_verification_failed',
13
14
  NoAuthenticationCeremony: 'no_authentication_ceremony',
@@ -22,6 +23,7 @@ exports.PasskeyControllerErrorCode = {
22
23
  var PasskeyControllerErrorMessage;
23
24
  (function (PasskeyControllerErrorMessage) {
24
25
  PasskeyControllerErrorMessage["NotEnrolled"] = "PasskeyController - Passkey is not enrolled";
26
+ PasskeyControllerErrorMessage["AlreadyEnrolled"] = "PasskeyController - Passkey is already enrolled";
25
27
  PasskeyControllerErrorMessage["NoRegistrationCeremony"] = "PasskeyController - No active passkey registration ceremony";
26
28
  PasskeyControllerErrorMessage["RegistrationVerificationFailed"] = "PasskeyController - Passkey registration verification failed";
27
29
  PasskeyControllerErrorMessage["NoAuthenticationCeremony"] = "PasskeyController - No active passkey authentication ceremony";
@@ -1 +1 @@
1
- {"version":3,"file":"constants.cjs","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":";;;AAAa,QAAA,cAAc,GAAG,mBAAmB,CAAC;AAElD;;;GAGG;AACU,QAAA,0BAA0B,GAAG;IACxC,WAAW,EAAE,cAAc;IAC3B,sBAAsB,EAAE,0BAA0B;IAClD,8BAA8B,EAAE,kCAAkC;IAClE,wBAAwB,EAAE,4BAA4B;IACtD,gCAAgC,EAAE,oCAAoC;IACtE,kBAAkB,EAAE,sBAAsB;IAC1C,wBAAwB,EAAE,6BAA6B;IACvD,gBAAgB,EAAE,oBAAoB;CAC9B,CAAC;AAKX;;GAEG;AACH,IAAY,6BASX;AATD,WAAY,6BAA6B;IACvC,4FAA2D,CAAA;IAC3D,uHAAsF,CAAA;IACtF,gIAA+F,CAAA;IAC/F,2HAA0F,CAAA;IAC1F,oIAAmG,CAAA;IACnG,2HAA0F,CAAA;IAC1F,qHAAoF,CAAA;IACpF,qIAAoG,CAAA;AACtG,CAAC,EATW,6BAA6B,6CAA7B,6BAA6B,QASxC","sourcesContent":["export const controllerName = 'PasskeyController';\n\n/**\n * Stable programmatic codes for {@link PasskeyControllerError}.\n * Use these instead of matching `message` strings.\n */\nexport const PasskeyControllerErrorCode = {\n NotEnrolled: 'not_enrolled',\n NoRegistrationCeremony: 'no_registration_ceremony',\n RegistrationVerificationFailed: 'registration_verification_failed',\n NoAuthenticationCeremony: 'no_authentication_ceremony',\n AuthenticationVerificationFailed: 'authentication_verification_failed',\n MissingKeyMaterial: 'missing_key_material',\n VaultKeyDecryptionFailed: 'vault_key_decryption_failed',\n VaultKeyMismatch: 'vault_key_mismatch',\n} as const;\n\nexport type PasskeyControllerErrorCode =\n (typeof PasskeyControllerErrorCode)[keyof typeof PasskeyControllerErrorCode];\n\n/**\n * Human-readable messages for {@link PasskeyControllerError}.\n */\nexport enum PasskeyControllerErrorMessage {\n NotEnrolled = `${controllerName} - Passkey is not enrolled`,\n NoRegistrationCeremony = `${controllerName} - No active passkey registration ceremony`,\n RegistrationVerificationFailed = `${controllerName} - Passkey registration verification failed`,\n NoAuthenticationCeremony = `${controllerName} - No active passkey authentication ceremony`,\n AuthenticationVerificationFailed = `${controllerName} - Passkey authentication verification failed`,\n MissingKeyMaterial = `${controllerName} - Passkey assertion missing required key material`,\n VaultKeyDecryptionFailed = `${controllerName} - Passkey vault key decryption failed`,\n VaultKeyMismatch = `${controllerName} - Passkey authentication does not match the current vault key`,\n}\n"]}
1
+ {"version":3,"file":"constants.cjs","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":";;;AAAa,QAAA,cAAc,GAAG,mBAAmB,CAAC;AAElD;;;GAGG;AACU,QAAA,0BAA0B,GAAG;IACxC,WAAW,EAAE,cAAc;IAC3B,eAAe,EAAE,kBAAkB;IACnC,sBAAsB,EAAE,0BAA0B;IAClD,8BAA8B,EAAE,kCAAkC;IAClE,wBAAwB,EAAE,4BAA4B;IACtD,gCAAgC,EAAE,oCAAoC;IACtE,kBAAkB,EAAE,sBAAsB;IAC1C,wBAAwB,EAAE,6BAA6B;IACvD,gBAAgB,EAAE,oBAAoB;CAC9B,CAAC;AAKX;;GAEG;AACH,IAAY,6BAUX;AAVD,WAAY,6BAA6B;IACvC,4FAA2D,CAAA;IAC3D,oGAAmE,CAAA;IACnE,uHAAsF,CAAA;IACtF,gIAA+F,CAAA;IAC/F,2HAA0F,CAAA;IAC1F,oIAAmG,CAAA;IACnG,2HAA0F,CAAA;IAC1F,qHAAoF,CAAA;IACpF,qIAAoG,CAAA;AACtG,CAAC,EAVW,6BAA6B,6CAA7B,6BAA6B,QAUxC","sourcesContent":["export const controllerName = 'PasskeyController';\n\n/**\n * Stable programmatic codes for {@link PasskeyControllerError}.\n * Use these instead of matching `message` strings.\n */\nexport const PasskeyControllerErrorCode = {\n NotEnrolled: 'not_enrolled',\n AlreadyEnrolled: 'already_enrolled',\n NoRegistrationCeremony: 'no_registration_ceremony',\n RegistrationVerificationFailed: 'registration_verification_failed',\n NoAuthenticationCeremony: 'no_authentication_ceremony',\n AuthenticationVerificationFailed: 'authentication_verification_failed',\n MissingKeyMaterial: 'missing_key_material',\n VaultKeyDecryptionFailed: 'vault_key_decryption_failed',\n VaultKeyMismatch: 'vault_key_mismatch',\n} as const;\n\nexport type PasskeyControllerErrorCode =\n (typeof PasskeyControllerErrorCode)[keyof typeof PasskeyControllerErrorCode];\n\n/**\n * Human-readable messages for {@link PasskeyControllerError}.\n */\nexport enum PasskeyControllerErrorMessage {\n NotEnrolled = `${controllerName} - Passkey is not enrolled`,\n AlreadyEnrolled = `${controllerName} - Passkey is already enrolled`,\n NoRegistrationCeremony = `${controllerName} - No active passkey registration ceremony`,\n RegistrationVerificationFailed = `${controllerName} - Passkey registration verification failed`,\n NoAuthenticationCeremony = `${controllerName} - No active passkey authentication ceremony`,\n AuthenticationVerificationFailed = `${controllerName} - Passkey authentication verification failed`,\n MissingKeyMaterial = `${controllerName} - Passkey assertion missing required key material`,\n VaultKeyDecryptionFailed = `${controllerName} - Passkey vault key decryption failed`,\n VaultKeyMismatch = `${controllerName} - Passkey authentication does not match the current vault key`,\n}\n"]}
@@ -5,6 +5,7 @@ export declare const controllerName = "PasskeyController";
5
5
  */
6
6
  export declare const PasskeyControllerErrorCode: {
7
7
  readonly NotEnrolled: "not_enrolled";
8
+ readonly AlreadyEnrolled: "already_enrolled";
8
9
  readonly NoRegistrationCeremony: "no_registration_ceremony";
9
10
  readonly RegistrationVerificationFailed: "registration_verification_failed";
10
11
  readonly NoAuthenticationCeremony: "no_authentication_ceremony";
@@ -19,6 +20,7 @@ export type PasskeyControllerErrorCode = (typeof PasskeyControllerErrorCode)[key
19
20
  */
20
21
  export declare enum PasskeyControllerErrorMessage {
21
22
  NotEnrolled = "PasskeyController - Passkey is not enrolled",
23
+ AlreadyEnrolled = "PasskeyController - Passkey is already enrolled",
22
24
  NoRegistrationCeremony = "PasskeyController - No active passkey registration ceremony",
23
25
  RegistrationVerificationFailed = "PasskeyController - Passkey registration verification failed",
24
26
  NoAuthenticationCeremony = "PasskeyController - No active passkey authentication ceremony",
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.cts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,sBAAsB,CAAC;AAElD;;;GAGG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;CAS7B,CAAC;AAEX,MAAM,MAAM,0BAA0B,GACpC,CAAC,OAAO,0BAA0B,CAAC,CAAC,MAAM,OAAO,0BAA0B,CAAC,CAAC;AAE/E;;GAEG;AACH,oBAAY,6BAA6B;IACvC,WAAW,gDAAgD;IAC3D,sBAAsB,gEAAgE;IACtF,8BAA8B,iEAAiE;IAC/F,wBAAwB,kEAAkE;IAC1F,gCAAgC,mEAAmE;IACnG,kBAAkB,wEAAwE;IAC1F,wBAAwB,4DAA4D;IACpF,gBAAgB,oFAAoF;CACrG"}
1
+ {"version":3,"file":"constants.d.cts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,sBAAsB,CAAC;AAElD;;;GAGG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;;CAU7B,CAAC;AAEX,MAAM,MAAM,0BAA0B,GACpC,CAAC,OAAO,0BAA0B,CAAC,CAAC,MAAM,OAAO,0BAA0B,CAAC,CAAC;AAE/E;;GAEG;AACH,oBAAY,6BAA6B;IACvC,WAAW,gDAAgD;IAC3D,eAAe,oDAAoD;IACnE,sBAAsB,gEAAgE;IACtF,8BAA8B,iEAAiE;IAC/F,wBAAwB,kEAAkE;IAC1F,gCAAgC,mEAAmE;IACnG,kBAAkB,wEAAwE;IAC1F,wBAAwB,4DAA4D;IACpF,gBAAgB,oFAAoF;CACrG"}
@@ -5,6 +5,7 @@ export declare const controllerName = "PasskeyController";
5
5
  */
6
6
  export declare const PasskeyControllerErrorCode: {
7
7
  readonly NotEnrolled: "not_enrolled";
8
+ readonly AlreadyEnrolled: "already_enrolled";
8
9
  readonly NoRegistrationCeremony: "no_registration_ceremony";
9
10
  readonly RegistrationVerificationFailed: "registration_verification_failed";
10
11
  readonly NoAuthenticationCeremony: "no_authentication_ceremony";
@@ -19,6 +20,7 @@ export type PasskeyControllerErrorCode = (typeof PasskeyControllerErrorCode)[key
19
20
  */
20
21
  export declare enum PasskeyControllerErrorMessage {
21
22
  NotEnrolled = "PasskeyController - Passkey is not enrolled",
23
+ AlreadyEnrolled = "PasskeyController - Passkey is already enrolled",
22
24
  NoRegistrationCeremony = "PasskeyController - No active passkey registration ceremony",
23
25
  RegistrationVerificationFailed = "PasskeyController - Passkey registration verification failed",
24
26
  NoAuthenticationCeremony = "PasskeyController - No active passkey authentication ceremony",
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.mts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,sBAAsB,CAAC;AAElD;;;GAGG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;CAS7B,CAAC;AAEX,MAAM,MAAM,0BAA0B,GACpC,CAAC,OAAO,0BAA0B,CAAC,CAAC,MAAM,OAAO,0BAA0B,CAAC,CAAC;AAE/E;;GAEG;AACH,oBAAY,6BAA6B;IACvC,WAAW,gDAAgD;IAC3D,sBAAsB,gEAAgE;IACtF,8BAA8B,iEAAiE;IAC/F,wBAAwB,kEAAkE;IAC1F,gCAAgC,mEAAmE;IACnG,kBAAkB,wEAAwE;IAC1F,wBAAwB,4DAA4D;IACpF,gBAAgB,oFAAoF;CACrG"}
1
+ {"version":3,"file":"constants.d.mts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,sBAAsB,CAAC;AAElD;;;GAGG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;;CAU7B,CAAC;AAEX,MAAM,MAAM,0BAA0B,GACpC,CAAC,OAAO,0BAA0B,CAAC,CAAC,MAAM,OAAO,0BAA0B,CAAC,CAAC;AAE/E;;GAEG;AACH,oBAAY,6BAA6B;IACvC,WAAW,gDAAgD;IAC3D,eAAe,oDAAoD;IACnE,sBAAsB,gEAAgE;IACtF,8BAA8B,iEAAiE;IAC/F,wBAAwB,kEAAkE;IAC1F,gCAAgC,mEAAmE;IACnG,kBAAkB,wEAAwE;IAC1F,wBAAwB,4DAA4D;IACpF,gBAAgB,oFAAoF;CACrG"}
@@ -5,6 +5,7 @@ export const controllerName = 'PasskeyController';
5
5
  */
6
6
  export const PasskeyControllerErrorCode = {
7
7
  NotEnrolled: 'not_enrolled',
8
+ AlreadyEnrolled: 'already_enrolled',
8
9
  NoRegistrationCeremony: 'no_registration_ceremony',
9
10
  RegistrationVerificationFailed: 'registration_verification_failed',
10
11
  NoAuthenticationCeremony: 'no_authentication_ceremony',
@@ -19,6 +20,7 @@ export const PasskeyControllerErrorCode = {
19
20
  export var PasskeyControllerErrorMessage;
20
21
  (function (PasskeyControllerErrorMessage) {
21
22
  PasskeyControllerErrorMessage["NotEnrolled"] = "PasskeyController - Passkey is not enrolled";
23
+ PasskeyControllerErrorMessage["AlreadyEnrolled"] = "PasskeyController - Passkey is already enrolled";
22
24
  PasskeyControllerErrorMessage["NoRegistrationCeremony"] = "PasskeyController - No active passkey registration ceremony";
23
25
  PasskeyControllerErrorMessage["RegistrationVerificationFailed"] = "PasskeyController - Passkey registration verification failed";
24
26
  PasskeyControllerErrorMessage["NoAuthenticationCeremony"] = "PasskeyController - No active passkey authentication ceremony";
@@ -1 +1 @@
1
- {"version":3,"file":"constants.mjs","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,cAAc,GAAG,mBAAmB,CAAC;AAElD;;;GAGG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG;IACxC,WAAW,EAAE,cAAc;IAC3B,sBAAsB,EAAE,0BAA0B;IAClD,8BAA8B,EAAE,kCAAkC;IAClE,wBAAwB,EAAE,4BAA4B;IACtD,gCAAgC,EAAE,oCAAoC;IACtE,kBAAkB,EAAE,sBAAsB;IAC1C,wBAAwB,EAAE,6BAA6B;IACvD,gBAAgB,EAAE,oBAAoB;CAC9B,CAAC;AAKX;;GAEG;AACH,MAAM,CAAN,IAAY,6BASX;AATD,WAAY,6BAA6B;IACvC,4FAA2D,CAAA;IAC3D,uHAAsF,CAAA;IACtF,gIAA+F,CAAA;IAC/F,2HAA0F,CAAA;IAC1F,oIAAmG,CAAA;IACnG,2HAA0F,CAAA;IAC1F,qHAAoF,CAAA;IACpF,qIAAoG,CAAA;AACtG,CAAC,EATW,6BAA6B,KAA7B,6BAA6B,QASxC","sourcesContent":["export const controllerName = 'PasskeyController';\n\n/**\n * Stable programmatic codes for {@link PasskeyControllerError}.\n * Use these instead of matching `message` strings.\n */\nexport const PasskeyControllerErrorCode = {\n NotEnrolled: 'not_enrolled',\n NoRegistrationCeremony: 'no_registration_ceremony',\n RegistrationVerificationFailed: 'registration_verification_failed',\n NoAuthenticationCeremony: 'no_authentication_ceremony',\n AuthenticationVerificationFailed: 'authentication_verification_failed',\n MissingKeyMaterial: 'missing_key_material',\n VaultKeyDecryptionFailed: 'vault_key_decryption_failed',\n VaultKeyMismatch: 'vault_key_mismatch',\n} as const;\n\nexport type PasskeyControllerErrorCode =\n (typeof PasskeyControllerErrorCode)[keyof typeof PasskeyControllerErrorCode];\n\n/**\n * Human-readable messages for {@link PasskeyControllerError}.\n */\nexport enum PasskeyControllerErrorMessage {\n NotEnrolled = `${controllerName} - Passkey is not enrolled`,\n NoRegistrationCeremony = `${controllerName} - No active passkey registration ceremony`,\n RegistrationVerificationFailed = `${controllerName} - Passkey registration verification failed`,\n NoAuthenticationCeremony = `${controllerName} - No active passkey authentication ceremony`,\n AuthenticationVerificationFailed = `${controllerName} - Passkey authentication verification failed`,\n MissingKeyMaterial = `${controllerName} - Passkey assertion missing required key material`,\n VaultKeyDecryptionFailed = `${controllerName} - Passkey vault key decryption failed`,\n VaultKeyMismatch = `${controllerName} - Passkey authentication does not match the current vault key`,\n}\n"]}
1
+ {"version":3,"file":"constants.mjs","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,cAAc,GAAG,mBAAmB,CAAC;AAElD;;;GAGG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG;IACxC,WAAW,EAAE,cAAc;IAC3B,eAAe,EAAE,kBAAkB;IACnC,sBAAsB,EAAE,0BAA0B;IAClD,8BAA8B,EAAE,kCAAkC;IAClE,wBAAwB,EAAE,4BAA4B;IACtD,gCAAgC,EAAE,oCAAoC;IACtE,kBAAkB,EAAE,sBAAsB;IAC1C,wBAAwB,EAAE,6BAA6B;IACvD,gBAAgB,EAAE,oBAAoB;CAC9B,CAAC;AAKX;;GAEG;AACH,MAAM,CAAN,IAAY,6BAUX;AAVD,WAAY,6BAA6B;IACvC,4FAA2D,CAAA;IAC3D,oGAAmE,CAAA;IACnE,uHAAsF,CAAA;IACtF,gIAA+F,CAAA;IAC/F,2HAA0F,CAAA;IAC1F,oIAAmG,CAAA;IACnG,2HAA0F,CAAA;IAC1F,qHAAoF,CAAA;IACpF,qIAAoG,CAAA;AACtG,CAAC,EAVW,6BAA6B,KAA7B,6BAA6B,QAUxC","sourcesContent":["export const controllerName = 'PasskeyController';\n\n/**\n * Stable programmatic codes for {@link PasskeyControllerError}.\n * Use these instead of matching `message` strings.\n */\nexport const PasskeyControllerErrorCode = {\n NotEnrolled: 'not_enrolled',\n AlreadyEnrolled: 'already_enrolled',\n NoRegistrationCeremony: 'no_registration_ceremony',\n RegistrationVerificationFailed: 'registration_verification_failed',\n NoAuthenticationCeremony: 'no_authentication_ceremony',\n AuthenticationVerificationFailed: 'authentication_verification_failed',\n MissingKeyMaterial: 'missing_key_material',\n VaultKeyDecryptionFailed: 'vault_key_decryption_failed',\n VaultKeyMismatch: 'vault_key_mismatch',\n} as const;\n\nexport type PasskeyControllerErrorCode =\n (typeof PasskeyControllerErrorCode)[keyof typeof PasskeyControllerErrorCode];\n\n/**\n * Human-readable messages for {@link PasskeyControllerError}.\n */\nexport enum PasskeyControllerErrorMessage {\n NotEnrolled = `${controllerName} - Passkey is not enrolled`,\n AlreadyEnrolled = `${controllerName} - Passkey is already enrolled`,\n NoRegistrationCeremony = `${controllerName} - No active passkey registration ceremony`,\n RegistrationVerificationFailed = `${controllerName} - Passkey registration verification failed`,\n NoAuthenticationCeremony = `${controllerName} - No active passkey authentication ceremony`,\n AuthenticationVerificationFailed = `${controllerName} - Passkey authentication verification failed`,\n MissingKeyMaterial = `${controllerName} - Passkey assertion missing required key material`,\n VaultKeyDecryptionFailed = `${controllerName} - Passkey vault key decryption failed`,\n VaultKeyMismatch = `${controllerName} - Passkey authentication does not match the current vault key`,\n}\n"]}
@@ -1,42 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.deriveKeyFromAuthenticationResponse = exports.deriveKeyFromRegistrationResponse = void 0;
3
+ exports.deriveKeyFromAuthenticationResponse = void 0;
4
4
  const constants_1 = require("./constants.cjs");
5
5
  const errors_1 = require("./errors.cjs");
6
6
  const crypto_1 = require("./utils/crypto.cjs");
7
7
  const encoding_1 = require("./utils/encoding.cjs");
8
- /**
9
- * Derives an AES-256 wrapping key from a WebAuthn registration ceremony
10
- * response.
11
- *
12
- * Uses the PRF output as HKDF input key material when
13
- * `clientExtensionResults.prf.results.first` is a non-empty string;
14
- * otherwise falls back to the random `userHandle` created during option
15
- * generation (including when PRF is enabled but no output is present).
16
- *
17
- * @param registrationResponse - The registration credential result from
18
- * `navigator.credentials.create()`.
19
- * @param registrationCeremony - In-flight registration ceremony state from
20
- * when `generateRegistrationOptions()` was called.
21
- * @param verifiedCredentialId - Base64url credential id from verified
22
- * authenticator data (same value as persisted `credential.id` after
23
- * `verifyRegistrationResponse`), not the client wrapper field alone.
24
- * @returns The derived 32-byte AES wrapping key and the
25
- * {@link PasskeyKeyDerivation} parameters needed to reproduce it.
26
- */
27
- function deriveKeyFromRegistrationResponse(registrationResponse, registrationCeremony, verifiedCredentialId) {
28
- const prfFirst = registrationResponse.clientExtensionResults?.prf?.results?.first;
29
- const hasPrfOutput = typeof prfFirst === 'string' && prfFirst.length > 0;
30
- const keyDerivation = hasPrfOutput
31
- ? { method: 'prf', prfSalt: registrationCeremony.prfSalt }
32
- : { method: 'userHandle' };
33
- const ikm = keyDerivation.method === 'prf'
34
- ? (0, encoding_1.base64URLToBytes)(prfFirst)
35
- : (0, encoding_1.base64URLToBytes)(registrationCeremony.userHandle);
36
- const encKey = (0, crypto_1.deriveEncryptionKey)(ikm, (0, encoding_1.base64URLToBytes)(verifiedCredentialId));
37
- return { encKey, keyDerivation };
38
- }
39
- exports.deriveKeyFromRegistrationResponse = deriveKeyFromRegistrationResponse;
40
8
  /**
41
9
  * Derives an AES-256 wrapping key from a WebAuthn authentication ceremony
42
10
  * response.
@@ -47,8 +15,8 @@ exports.deriveKeyFromRegistrationResponse = deriveKeyFromRegistrationResponse;
47
15
  *
48
16
  * @param authenticationResponse - The authentication credential result
49
17
  * from `navigator.credentials.get()`.
50
- * @param record - The persisted passkey record that was created during
51
- * enrollment.
18
+ * @param record - Credential id and key derivation parameters (ciphertext is
19
+ * not read).
52
20
  * @returns The derived 32-byte AES wrapping key.
53
21
  * @throws {@link PasskeyControllerError} with code `missing_key_material` if the
54
22
  * required key material (PRF result or userHandle) is missing from the response.
@@ -1 +1 @@
1
- {"version":3,"file":"key-derivation.cjs","sourceRoot":"","sources":["../src/key-derivation.ts"],"names":[],"mappings":";;;AAAA,+CAGqB;AACrB,yCAAkD;AAOlD,+CAAqD;AACrD,mDAAoD;AAMpD;;;;;;;;;;;;;;;;;;GAkBG;AACH,SAAgB,iCAAiC,CAC/C,oBAAiD,EACjD,oBAAiD,EACjD,oBAA4B;IAK5B,MAAM,QAAQ,GACZ,oBAAoB,CAAC,sBACtB,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,CAAC;IACvB,MAAM,YAAY,GAAG,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;IAEzE,MAAM,aAAa,GAAyB,YAAY;QACtD,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,oBAAoB,CAAC,OAAO,EAAE;QAC1D,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;IAC7B,MAAM,GAAG,GACP,aAAa,CAAC,MAAM,KAAK,KAAK;QAC5B,CAAC,CAAC,IAAA,2BAAgB,EAAC,QAAkB,CAAC;QACtC,CAAC,CAAC,IAAA,2BAAgB,EAAC,oBAAoB,CAAC,UAAU,CAAC,CAAC;IACxD,MAAM,MAAM,GAAG,IAAA,4BAAmB,EAChC,GAAG,EACH,IAAA,2BAAgB,EAAC,oBAAoB,CAAC,CACvC,CAAC;IAEF,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;AACnC,CAAC;AA1BD,8EA0BC;AAED;;;;;;;;;;;;;;;GAeG;AACH,SAAgB,mCAAmC,CACjD,sBAAqD,EACrD,MAAqB;IAErB,MAAM,EAAE,UAAU,EAAE,GAAG,sBAAsB,CAAC,QAAQ,CAAC;IACvD,MAAM,QAAQ,GACZ,sBAAsB,CAAC,sBACxB,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,CAAC;IACvB,MAAM,YAAY,GAAG,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;IAEzE,IAAI,GAAe,CAAC;IACpB,IAAI,MAAM,CAAC,aAAa,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;QAC1C,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,MAAM,IAAI,+BAAsB,CAC9B,yCAA6B,CAAC,kBAAkB,EAChD,EAAE,IAAI,EAAE,sCAA0B,CAAC,kBAAkB,EAAE,CACxD,CAAC;QACJ,CAAC;QACD,GAAG,GAAG,IAAA,2BAAgB,EAAC,QAAQ,CAAC,CAAC;IACnC,CAAC;SAAM,IAAI,UAAU,EAAE,CAAC;QACtB,GAAG,GAAG,IAAA,2BAAgB,EAAC,UAAU,CAAC,CAAC;IACrC,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,+BAAsB,CAC9B,yCAA6B,CAAC,kBAAkB,EAChD,EAAE,IAAI,EAAE,sCAA0B,CAAC,kBAAkB,EAAE,CACxD,CAAC;IACJ,CAAC;IAED,OAAO,IAAA,4BAAmB,EAAC,GAAG,EAAE,IAAA,2BAAgB,EAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC;AAC1E,CAAC;AA7BD,kFA6BC","sourcesContent":["import {\n PasskeyControllerErrorCode,\n PasskeyControllerErrorMessage,\n} from './constants';\nimport { PasskeyControllerError } from './errors';\nimport type {\n PasskeyKeyDerivation,\n PasskeyRecord,\n PasskeyRegistrationCeremony,\n PrfClientExtensionResults,\n} from './types';\nimport { deriveEncryptionKey } from './utils/crypto';\nimport { base64URLToBytes } from './utils/encoding';\nimport type {\n PasskeyAuthenticationResponse,\n PasskeyRegistrationResponse,\n} from './webauthn/types';\n\n/**\n * Derives an AES-256 wrapping key from a WebAuthn registration ceremony\n * response.\n *\n * Uses the PRF output as HKDF input key material when\n * `clientExtensionResults.prf.results.first` is a non-empty string;\n * otherwise falls back to the random `userHandle` created during option\n * generation (including when PRF is enabled but no output is present).\n *\n * @param registrationResponse - The registration credential result from\n * `navigator.credentials.create()`.\n * @param registrationCeremony - In-flight registration ceremony state from\n * when `generateRegistrationOptions()` was called.\n * @param verifiedCredentialId - Base64url credential id from verified\n * authenticator data (same value as persisted `credential.id` after\n * `verifyRegistrationResponse`), not the client wrapper field alone.\n * @returns The derived 32-byte AES wrapping key and the\n * {@link PasskeyKeyDerivation} parameters needed to reproduce it.\n */\nexport function deriveKeyFromRegistrationResponse(\n registrationResponse: PasskeyRegistrationResponse,\n registrationCeremony: PasskeyRegistrationCeremony,\n verifiedCredentialId: string,\n): {\n encKey: Uint8Array;\n keyDerivation: PasskeyKeyDerivation;\n} {\n const prfFirst = (\n registrationResponse.clientExtensionResults as PrfClientExtensionResults\n )?.prf?.results?.first;\n const hasPrfOutput = typeof prfFirst === 'string' && prfFirst.length > 0;\n\n const keyDerivation: PasskeyKeyDerivation = hasPrfOutput\n ? { method: 'prf', prfSalt: registrationCeremony.prfSalt }\n : { method: 'userHandle' };\n const ikm =\n keyDerivation.method === 'prf'\n ? base64URLToBytes(prfFirst as string)\n : base64URLToBytes(registrationCeremony.userHandle);\n const encKey = deriveEncryptionKey(\n ikm,\n base64URLToBytes(verifiedCredentialId),\n );\n\n return { encKey, keyDerivation };\n}\n\n/**\n * Derives an AES-256 wrapping key from a WebAuthn authentication ceremony\n * response.\n *\n * The derivation method is determined by `record.keyDerivation`:\n * - `prf` -- uses the PRF evaluation result from `clientExtensionResults`.\n * - `userHandle` -- uses the `userHandle` returned in the assertion.\n *\n * @param authenticationResponse - The authentication credential result\n * from `navigator.credentials.get()`.\n * @param record - The persisted passkey record that was created during\n * enrollment.\n * @returns The derived 32-byte AES wrapping key.\n * @throws {@link PasskeyControllerError} with code `missing_key_material` if the\n * required key material (PRF result or userHandle) is missing from the response.\n */\nexport function deriveKeyFromAuthenticationResponse(\n authenticationResponse: PasskeyAuthenticationResponse,\n record: PasskeyRecord,\n): Uint8Array {\n const { userHandle } = authenticationResponse.response;\n const prfFirst = (\n authenticationResponse.clientExtensionResults as PrfClientExtensionResults\n )?.prf?.results?.first;\n const hasPrfOutput = typeof prfFirst === 'string' && prfFirst.length > 0;\n\n let ikm: Uint8Array;\n if (record.keyDerivation.method === 'prf') {\n if (!hasPrfOutput) {\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.MissingKeyMaterial,\n { code: PasskeyControllerErrorCode.MissingKeyMaterial },\n );\n }\n ikm = base64URLToBytes(prfFirst);\n } else if (userHandle) {\n ikm = base64URLToBytes(userHandle);\n } else {\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.MissingKeyMaterial,\n { code: PasskeyControllerErrorCode.MissingKeyMaterial },\n );\n }\n\n return deriveEncryptionKey(ikm, base64URLToBytes(record.credential.id));\n}\n"]}
1
+ {"version":3,"file":"key-derivation.cjs","sourceRoot":"","sources":["../src/key-derivation.ts"],"names":[],"mappings":";;;AAAA,+CAGqB;AACrB,yCAAkD;AAElD,+CAAqD;AACrD,mDAAoD;AAGpD;;;;;;;;;;;;;;;GAeG;AACH,SAAgB,mCAAmC,CACjD,sBAAqD,EACrD,MAA2D;IAE3D,MAAM,EAAE,UAAU,EAAE,GAAG,sBAAsB,CAAC,QAAQ,CAAC;IACvD,MAAM,QAAQ,GACZ,sBAAsB,CAAC,sBACxB,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,CAAC;IACvB,MAAM,YAAY,GAAG,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;IAEzE,IAAI,GAAe,CAAC;IACpB,IAAI,MAAM,CAAC,aAAa,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;QAC1C,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,MAAM,IAAI,+BAAsB,CAC9B,yCAA6B,CAAC,kBAAkB,EAChD,EAAE,IAAI,EAAE,sCAA0B,CAAC,kBAAkB,EAAE,CACxD,CAAC;QACJ,CAAC;QACD,GAAG,GAAG,IAAA,2BAAgB,EAAC,QAAQ,CAAC,CAAC;IACnC,CAAC;SAAM,IAAI,UAAU,EAAE,CAAC;QACtB,GAAG,GAAG,IAAA,2BAAgB,EAAC,UAAU,CAAC,CAAC;IACrC,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,+BAAsB,CAC9B,yCAA6B,CAAC,kBAAkB,EAChD,EAAE,IAAI,EAAE,sCAA0B,CAAC,kBAAkB,EAAE,CACxD,CAAC;IACJ,CAAC;IAED,OAAO,IAAA,4BAAmB,EAAC,GAAG,EAAE,IAAA,2BAAgB,EAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC;AAC1E,CAAC;AA7BD,kFA6BC","sourcesContent":["import {\n PasskeyControllerErrorCode,\n PasskeyControllerErrorMessage,\n} from './constants';\nimport { PasskeyControllerError } from './errors';\nimport type { PasskeyRecord, PrfClientExtensionResults } from './types';\nimport { deriveEncryptionKey } from './utils/crypto';\nimport { base64URLToBytes } from './utils/encoding';\nimport type { PasskeyAuthenticationResponse } from './webauthn/types';\n\n/**\n * Derives an AES-256 wrapping key from a WebAuthn authentication ceremony\n * response.\n *\n * The derivation method is determined by `record.keyDerivation`:\n * - `prf` -- uses the PRF evaluation result from `clientExtensionResults`.\n * - `userHandle` -- uses the `userHandle` returned in the assertion.\n *\n * @param authenticationResponse - The authentication credential result\n * from `navigator.credentials.get()`.\n * @param record - Credential id and key derivation parameters (ciphertext is\n * not read).\n * @returns The derived 32-byte AES wrapping key.\n * @throws {@link PasskeyControllerError} with code `missing_key_material` if the\n * required key material (PRF result or userHandle) is missing from the response.\n */\nexport function deriveKeyFromAuthenticationResponse(\n authenticationResponse: PasskeyAuthenticationResponse,\n record: Pick<PasskeyRecord, 'credential' | 'keyDerivation'>,\n): Uint8Array {\n const { userHandle } = authenticationResponse.response;\n const prfFirst = (\n authenticationResponse.clientExtensionResults as PrfClientExtensionResults\n )?.prf?.results?.first;\n const hasPrfOutput = typeof prfFirst === 'string' && prfFirst.length > 0;\n\n let ikm: Uint8Array;\n if (record.keyDerivation.method === 'prf') {\n if (!hasPrfOutput) {\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.MissingKeyMaterial,\n { code: PasskeyControllerErrorCode.MissingKeyMaterial },\n );\n }\n ikm = base64URLToBytes(prfFirst);\n } else if (userHandle) {\n ikm = base64URLToBytes(userHandle);\n } else {\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.MissingKeyMaterial,\n { code: PasskeyControllerErrorCode.MissingKeyMaterial },\n );\n }\n\n return deriveEncryptionKey(ikm, base64URLToBytes(record.credential.id));\n}\n"]}
@@ -1,28 +1,5 @@
1
- import type { PasskeyKeyDerivation, PasskeyRecord, PasskeyRegistrationCeremony } from "./types.cjs";
2
- import type { PasskeyAuthenticationResponse, PasskeyRegistrationResponse } from "./webauthn/types.cjs";
3
- /**
4
- * Derives an AES-256 wrapping key from a WebAuthn registration ceremony
5
- * response.
6
- *
7
- * Uses the PRF output as HKDF input key material when
8
- * `clientExtensionResults.prf.results.first` is a non-empty string;
9
- * otherwise falls back to the random `userHandle` created during option
10
- * generation (including when PRF is enabled but no output is present).
11
- *
12
- * @param registrationResponse - The registration credential result from
13
- * `navigator.credentials.create()`.
14
- * @param registrationCeremony - In-flight registration ceremony state from
15
- * when `generateRegistrationOptions()` was called.
16
- * @param verifiedCredentialId - Base64url credential id from verified
17
- * authenticator data (same value as persisted `credential.id` after
18
- * `verifyRegistrationResponse`), not the client wrapper field alone.
19
- * @returns The derived 32-byte AES wrapping key and the
20
- * {@link PasskeyKeyDerivation} parameters needed to reproduce it.
21
- */
22
- export declare function deriveKeyFromRegistrationResponse(registrationResponse: PasskeyRegistrationResponse, registrationCeremony: PasskeyRegistrationCeremony, verifiedCredentialId: string): {
23
- encKey: Uint8Array;
24
- keyDerivation: PasskeyKeyDerivation;
25
- };
1
+ import type { PasskeyRecord } from "./types.cjs";
2
+ import type { PasskeyAuthenticationResponse } from "./webauthn/types.cjs";
26
3
  /**
27
4
  * Derives an AES-256 wrapping key from a WebAuthn authentication ceremony
28
5
  * response.
@@ -33,11 +10,11 @@ export declare function deriveKeyFromRegistrationResponse(registrationResponse:
33
10
  *
34
11
  * @param authenticationResponse - The authentication credential result
35
12
  * from `navigator.credentials.get()`.
36
- * @param record - The persisted passkey record that was created during
37
- * enrollment.
13
+ * @param record - Credential id and key derivation parameters (ciphertext is
14
+ * not read).
38
15
  * @returns The derived 32-byte AES wrapping key.
39
16
  * @throws {@link PasskeyControllerError} with code `missing_key_material` if the
40
17
  * required key material (PRF result or userHandle) is missing from the response.
41
18
  */
42
- export declare function deriveKeyFromAuthenticationResponse(authenticationResponse: PasskeyAuthenticationResponse, record: PasskeyRecord): Uint8Array;
19
+ export declare function deriveKeyFromAuthenticationResponse(authenticationResponse: PasskeyAuthenticationResponse, record: Pick<PasskeyRecord, 'credential' | 'keyDerivation'>): Uint8Array;
43
20
  //# sourceMappingURL=key-derivation.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"key-derivation.d.cts","sourceRoot":"","sources":["../src/key-derivation.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,oBAAoB,EACpB,aAAa,EACb,2BAA2B,EAE5B,oBAAgB;AAGjB,OAAO,KAAK,EACV,6BAA6B,EAC7B,2BAA2B,EAC5B,6BAAyB;AAE1B;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,iCAAiC,CAC/C,oBAAoB,EAAE,2BAA2B,EACjD,oBAAoB,EAAE,2BAA2B,EACjD,oBAAoB,EAAE,MAAM,GAC3B;IACD,MAAM,EAAE,UAAU,CAAC;IACnB,aAAa,EAAE,oBAAoB,CAAC;CACrC,CAmBA;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mCAAmC,CACjD,sBAAsB,EAAE,6BAA6B,EACrD,MAAM,EAAE,aAAa,GACpB,UAAU,CA0BZ"}
1
+ {"version":3,"file":"key-derivation.d.cts","sourceRoot":"","sources":["../src/key-derivation.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAA6B,oBAAgB;AAGxE,OAAO,KAAK,EAAE,6BAA6B,EAAE,6BAAyB;AAEtE;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mCAAmC,CACjD,sBAAsB,EAAE,6BAA6B,EACrD,MAAM,EAAE,IAAI,CAAC,aAAa,EAAE,YAAY,GAAG,eAAe,CAAC,GAC1D,UAAU,CA0BZ"}