@weaveprotocol/core 0.1.0
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.
- package/README.md +1106 -0
- package/dist/elements/auth-styles.d.ts +18 -0
- package/dist/elements/auth-styles.d.ts.map +1 -0
- package/dist/elements/auth-styles.js +150 -0
- package/dist/elements/auth-styles.js.map +1 -0
- package/dist/elements/dom.d.ts +28 -0
- package/dist/elements/dom.d.ts.map +1 -0
- package/dist/elements/dom.js +74 -0
- package/dist/elements/dom.js.map +1 -0
- package/dist/elements/index.d.ts +13 -0
- package/dist/elements/index.d.ts.map +1 -0
- package/dist/elements/index.js +11 -0
- package/dist/elements/index.js.map +1 -0
- package/dist/elements/weave-auth.d.ts +52 -0
- package/dist/elements/weave-auth.d.ts.map +1 -0
- package/dist/elements/weave-auth.js +427 -0
- package/dist/elements/weave-auth.js.map +1 -0
- package/dist/identity/account-store.d.ts +113 -0
- package/dist/identity/account-store.d.ts.map +1 -0
- package/dist/identity/account-store.js +305 -0
- package/dist/identity/account-store.js.map +1 -0
- package/dist/identity/account-vault.d.ts +199 -0
- package/dist/identity/account-vault.d.ts.map +1 -0
- package/dist/identity/account-vault.js +251 -0
- package/dist/identity/account-vault.js.map +1 -0
- package/dist/identity/agent-note.d.ts +19 -0
- package/dist/identity/agent-note.d.ts.map +1 -0
- package/dist/identity/agent-note.js +29 -0
- package/dist/identity/agent-note.js.map +1 -0
- package/dist/identity/crypto-p256.d.ts +7 -0
- package/dist/identity/crypto-p256.d.ts.map +1 -0
- package/dist/identity/crypto-p256.js +84 -0
- package/dist/identity/crypto-p256.js.map +1 -0
- package/dist/identity/device-key.d.ts +60 -0
- package/dist/identity/device-key.d.ts.map +1 -0
- package/dist/identity/device-key.js +103 -0
- package/dist/identity/device-key.js.map +1 -0
- package/dist/identity/did.d.ts +22 -0
- package/dist/identity/did.d.ts.map +1 -0
- package/dist/identity/did.js +37 -0
- package/dist/identity/did.js.map +1 -0
- package/dist/identity/folder-account.d.ts +65 -0
- package/dist/identity/folder-account.d.ts.map +1 -0
- package/dist/identity/folder-account.js +115 -0
- package/dist/identity/folder-account.js.map +1 -0
- package/dist/identity/identity-manager.d.ts +36 -0
- package/dist/identity/identity-manager.d.ts.map +1 -0
- package/dist/identity/identity-manager.js +115 -0
- package/dist/identity/identity-manager.js.map +1 -0
- package/dist/identity/index.d.ts +10 -0
- package/dist/identity/index.d.ts.map +1 -0
- package/dist/identity/index.js +10 -0
- package/dist/identity/index.js.map +1 -0
- package/dist/identity/keys.d.ts +24 -0
- package/dist/identity/keys.d.ts.map +1 -0
- package/dist/identity/keys.js +38 -0
- package/dist/identity/keys.js.map +1 -0
- package/dist/identity/pairing.d.ts +83 -0
- package/dist/identity/pairing.d.ts.map +1 -0
- package/dist/identity/pairing.js +120 -0
- package/dist/identity/pairing.js.map +1 -0
- package/dist/identity/passkey-diagnostics.d.ts +54 -0
- package/dist/identity/passkey-diagnostics.d.ts.map +1 -0
- package/dist/identity/passkey-diagnostics.js +178 -0
- package/dist/identity/passkey-diagnostics.js.map +1 -0
- package/dist/identity/recovery-code.d.ts +50 -0
- package/dist/identity/recovery-code.d.ts.map +1 -0
- package/dist/identity/recovery-code.js +116 -0
- package/dist/identity/recovery-code.js.map +1 -0
- package/dist/identity/root-signer.d.ts +59 -0
- package/dist/identity/root-signer.d.ts.map +1 -0
- package/dist/identity/root-signer.js +44 -0
- package/dist/identity/root-signer.js.map +1 -0
- package/dist/identity/ucan.d.ts +164 -0
- package/dist/identity/ucan.d.ts.map +1 -0
- package/dist/identity/ucan.js +273 -0
- package/dist/identity/ucan.js.map +1 -0
- package/dist/identity/webauthn.d.ts +87 -0
- package/dist/identity/webauthn.d.ts.map +1 -0
- package/dist/identity/webauthn.js +155 -0
- package/dist/identity/webauthn.js.map +1 -0
- package/dist/index.d.ts +116 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +85 -0
- package/dist/index.js.map +1 -0
- package/dist/network/index.d.ts +17 -0
- package/dist/network/index.d.ts.map +1 -0
- package/dist/network/index.js +10 -0
- package/dist/network/index.js.map +1 -0
- package/dist/network/introductions.d.ts +85 -0
- package/dist/network/introductions.d.ts.map +1 -0
- package/dist/network/introductions.js +94 -0
- package/dist/network/introductions.js.map +1 -0
- package/dist/network/local-transport.d.ts +22 -0
- package/dist/network/local-transport.d.ts.map +1 -0
- package/dist/network/local-transport.js +55 -0
- package/dist/network/local-transport.js.map +1 -0
- package/dist/network/mesh.d.ts +31 -0
- package/dist/network/mesh.d.ts.map +1 -0
- package/dist/network/mesh.js +332 -0
- package/dist/network/mesh.js.map +1 -0
- package/dist/network/multi-signaling.d.ts +29 -0
- package/dist/network/multi-signaling.d.ts.map +1 -0
- package/dist/network/multi-signaling.js +125 -0
- package/dist/network/multi-signaling.js.map +1 -0
- package/dist/network/network-manager.d.ts +37 -0
- package/dist/network/network-manager.d.ts.map +1 -0
- package/dist/network/network-manager.js +64 -0
- package/dist/network/network-manager.js.map +1 -0
- package/dist/network/peer-auth.d.ts +98 -0
- package/dist/network/peer-auth.d.ts.map +1 -0
- package/dist/network/peer-auth.js +86 -0
- package/dist/network/peer-auth.js.map +1 -0
- package/dist/network/rtc-transport.d.ts +18 -0
- package/dist/network/rtc-transport.d.ts.map +1 -0
- package/dist/network/rtc-transport.js +149 -0
- package/dist/network/rtc-transport.js.map +1 -0
- package/dist/network/signaling.d.ts +40 -0
- package/dist/network/signaling.d.ts.map +1 -0
- package/dist/network/signaling.js +124 -0
- package/dist/network/signaling.js.map +1 -0
- package/dist/network/transport.d.ts +50 -0
- package/dist/network/transport.d.ts.map +1 -0
- package/dist/network/transport.js +13 -0
- package/dist/network/transport.js.map +1 -0
- package/dist/network/ws-transport.d.ts +42 -0
- package/dist/network/ws-transport.d.ts.map +1 -0
- package/dist/network/ws-transport.js +194 -0
- package/dist/network/ws-transport.js.map +1 -0
- package/dist/node/actions.d.ts +62 -0
- package/dist/node/actions.d.ts.map +1 -0
- package/dist/node/actions.js +500 -0
- package/dist/node/actions.js.map +1 -0
- package/dist/node/carrier.d.ts +85 -0
- package/dist/node/carrier.d.ts.map +1 -0
- package/dist/node/carrier.js +192 -0
- package/dist/node/carrier.js.map +1 -0
- package/dist/node/copy.d.ts +38 -0
- package/dist/node/copy.d.ts.map +1 -0
- package/dist/node/copy.js +53 -0
- package/dist/node/copy.js.map +1 -0
- package/dist/node/index.d.ts +16 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +11 -0
- package/dist/node/index.js.map +1 -0
- package/dist/node/node.d.ts +12 -0
- package/dist/node/node.d.ts.map +1 -0
- package/dist/node/node.js +872 -0
- package/dist/node/node.js.map +1 -0
- package/dist/node/space-runtime.d.ts +144 -0
- package/dist/node/space-runtime.d.ts.map +1 -0
- package/dist/node/space-runtime.js +1221 -0
- package/dist/node/space-runtime.js.map +1 -0
- package/dist/node/stores.d.ts +45 -0
- package/dist/node/stores.d.ts.map +1 -0
- package/dist/node/stores.js +31 -0
- package/dist/node/stores.js.map +1 -0
- package/dist/node/types.d.ts +508 -0
- package/dist/node/types.d.ts.map +1 -0
- package/dist/node/types.js +2 -0
- package/dist/node/types.js.map +1 -0
- package/dist/privacy/index.d.ts +4 -0
- package/dist/privacy/index.d.ts.map +1 -0
- package/dist/privacy/index.js +4 -0
- package/dist/privacy/index.js.map +1 -0
- package/dist/privacy/key-distribution.d.ts +42 -0
- package/dist/privacy/key-distribution.d.ts.map +1 -0
- package/dist/privacy/key-distribution.js +50 -0
- package/dist/privacy/key-distribution.js.map +1 -0
- package/dist/privacy/privacy-guard.d.ts +24 -0
- package/dist/privacy/privacy-guard.d.ts.map +1 -0
- package/dist/privacy/privacy-guard.js +87 -0
- package/dist/privacy/privacy-guard.js.map +1 -0
- package/dist/privacy/space-encryption.d.ts +48 -0
- package/dist/privacy/space-encryption.d.ts.map +1 -0
- package/dist/privacy/space-encryption.js +61 -0
- package/dist/privacy/space-encryption.js.map +1 -0
- package/dist/query/engine.d.ts +27 -0
- package/dist/query/engine.d.ts.map +1 -0
- package/dist/query/engine.js +84 -0
- package/dist/query/engine.js.map +1 -0
- package/dist/query/filter.d.ts +15 -0
- package/dist/query/filter.d.ts.map +1 -0
- package/dist/query/filter.js +207 -0
- package/dist/query/filter.js.map +1 -0
- package/dist/query/types.d.ts +144 -0
- package/dist/query/types.d.ts.map +1 -0
- package/dist/query/types.js +22 -0
- package/dist/query/types.js.map +1 -0
- package/dist/react/context.d.ts +81 -0
- package/dist/react/context.d.ts.map +1 -0
- package/dist/react/context.js +95 -0
- package/dist/react/context.js.map +1 -0
- package/dist/react/index.d.ts +40 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +36 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/use-live.d.ts +16 -0
- package/dist/react/use-live.d.ts.map +1 -0
- package/dist/react/use-live.js +55 -0
- package/dist/react/use-live.js.map +1 -0
- package/dist/react/use-query.d.ts +17 -0
- package/dist/react/use-query.d.ts.map +1 -0
- package/dist/react/use-query.js +23 -0
- package/dist/react/use-query.js.map +1 -0
- package/dist/react/use-space.d.ts +29 -0
- package/dist/react/use-space.d.ts.map +1 -0
- package/dist/react/use-space.js +48 -0
- package/dist/react/use-space.js.map +1 -0
- package/dist/react/use-spaces.d.ts +19 -0
- package/dist/react/use-spaces.d.ts.map +1 -0
- package/dist/react/use-spaces.js +55 -0
- package/dist/react/use-spaces.js.map +1 -0
- package/dist/react/use-weave-auth.d.ts +10 -0
- package/dist/react/use-weave-auth.d.ts.map +1 -0
- package/dist/react/use-weave-auth.js +15 -0
- package/dist/react/use-weave-auth.js.map +1 -0
- package/dist/react/weave-auth.d.ts +17 -0
- package/dist/react/weave-auth.d.ts.map +1 -0
- package/dist/react/weave-auth.js +38 -0
- package/dist/react/weave-auth.js.map +1 -0
- package/dist/records/describe.d.ts +38 -0
- package/dist/records/describe.d.ts.map +1 -0
- package/dist/records/describe.js +101 -0
- package/dist/records/describe.js.map +1 -0
- package/dist/records/links.d.ts +14 -0
- package/dist/records/links.d.ts.map +1 -0
- package/dist/records/links.js +25 -0
- package/dist/records/links.js.map +1 -0
- package/dist/records/rules.d.ts +59 -0
- package/dist/records/rules.d.ts.map +1 -0
- package/dist/records/rules.js +118 -0
- package/dist/records/rules.js.map +1 -0
- package/dist/records/version.d.ts +54 -0
- package/dist/records/version.d.ts.map +1 -0
- package/dist/records/version.js +70 -0
- package/dist/records/version.js.map +1 -0
- package/dist/schema/collection-def.d.ts +96 -0
- package/dist/schema/collection-def.d.ts.map +1 -0
- package/dist/schema/collection-def.js +272 -0
- package/dist/schema/collection-def.js.map +1 -0
- package/dist/schema/expression.d.ts +69 -0
- package/dist/schema/expression.d.ts.map +1 -0
- package/dist/schema/expression.js +92 -0
- package/dist/schema/expression.js.map +1 -0
- package/dist/schema/index.d.ts +4 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +4 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/schema-engine.d.ts +42 -0
- package/dist/schema/schema-engine.d.ts.map +1 -0
- package/dist/schema/schema-engine.js +36 -0
- package/dist/schema/schema-engine.js.map +1 -0
- package/dist/schema/signer.d.ts +27 -0
- package/dist/schema/signer.d.ts.map +1 -0
- package/dist/schema/signer.js +36 -0
- package/dist/schema/signer.js.map +1 -0
- package/dist/schemas/apps.d.ts +88 -0
- package/dist/schemas/apps.d.ts.map +1 -0
- package/dist/schemas/apps.js +168 -0
- package/dist/schemas/apps.js.map +1 -0
- package/dist/schemas/index.d.ts +410 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +208 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/screens.d.ts +67 -0
- package/dist/schemas/screens.d.ts.map +1 -0
- package/dist/schemas/screens.js +242 -0
- package/dist/schemas/screens.js.map +1 -0
- package/dist/session/agent-link.d.ts +79 -0
- package/dist/session/agent-link.d.ts.map +1 -0
- package/dist/session/agent-link.js +251 -0
- package/dist/session/agent-link.js.map +1 -0
- package/dist/session/auth.d.ts +233 -0
- package/dist/session/auth.d.ts.map +1 -0
- package/dist/session/auth.js +783 -0
- package/dist/session/auth.js.map +1 -0
- package/dist/session/connect.d.ts +211 -0
- package/dist/session/connect.d.ts.map +1 -0
- package/dist/session/connect.js +355 -0
- package/dist/session/connect.js.map +1 -0
- package/dist/session/connection.d.ts +70 -0
- package/dist/session/connection.d.ts.map +1 -0
- package/dist/session/connection.js +120 -0
- package/dist/session/connection.js.map +1 -0
- package/dist/session/credentials.d.ts +44 -0
- package/dist/session/credentials.d.ts.map +1 -0
- package/dist/session/credentials.js +60 -0
- package/dist/session/credentials.js.map +1 -0
- package/dist/session/index.d.ts +25 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +18 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/pairing.d.ts +59 -0
- package/dist/session/pairing.d.ts.map +1 -0
- package/dist/session/pairing.js +144 -0
- package/dist/session/pairing.js.map +1 -0
- package/dist/session/places.d.ts +62 -0
- package/dist/session/places.d.ts.map +1 -0
- package/dist/session/places.js +101 -0
- package/dist/session/places.js.map +1 -0
- package/dist/session/stay-signed-in.d.ts +37 -0
- package/dist/session/stay-signed-in.d.ts.map +1 -0
- package/dist/session/stay-signed-in.js +124 -0
- package/dist/session/stay-signed-in.js.map +1 -0
- package/dist/space/account-registry.d.ts +64 -0
- package/dist/space/account-registry.d.ts.map +1 -0
- package/dist/space/account-registry.js +66 -0
- package/dist/space/account-registry.js.map +1 -0
- package/dist/space/index.d.ts +3 -0
- package/dist/space/index.d.ts.map +1 -0
- package/dist/space/index.js +3 -0
- package/dist/space/index.js.map +1 -0
- package/dist/space/pass.d.ts +63 -0
- package/dist/space/pass.d.ts.map +1 -0
- package/dist/space/pass.js +54 -0
- package/dist/space/pass.js.map +1 -0
- package/dist/space/presets.d.ts +31 -0
- package/dist/space/presets.d.ts.map +1 -0
- package/dist/space/presets.js +24 -0
- package/dist/space/presets.js.map +1 -0
- package/dist/space/roles.d.ts +186 -0
- package/dist/space/roles.d.ts.map +1 -0
- package/dist/space/roles.js +500 -0
- package/dist/space/roles.js.map +1 -0
- package/dist/space/space-access.d.ts +80 -0
- package/dist/space/space-access.d.ts.map +1 -0
- package/dist/space/space-access.js +135 -0
- package/dist/space/space-access.js.map +1 -0
- package/dist/space/space-manager.d.ts +87 -0
- package/dist/space/space-manager.d.ts.map +1 -0
- package/dist/space/space-manager.js +187 -0
- package/dist/space/space-manager.js.map +1 -0
- package/dist/storage/directory-access.d.ts +78 -0
- package/dist/storage/directory-access.d.ts.map +1 -0
- package/dist/storage/directory-access.js +164 -0
- package/dist/storage/directory-access.js.map +1 -0
- package/dist/storage/encrypted-adapter.d.ts +49 -0
- package/dist/storage/encrypted-adapter.d.ts.map +1 -0
- package/dist/storage/encrypted-adapter.js +106 -0
- package/dist/storage/encrypted-adapter.js.map +1 -0
- package/dist/storage/folder-adapter.d.ts +105 -0
- package/dist/storage/folder-adapter.d.ts.map +1 -0
- package/dist/storage/folder-adapter.js +280 -0
- package/dist/storage/folder-adapter.js.map +1 -0
- package/dist/storage/folder-reconcile.d.ts +53 -0
- package/dist/storage/folder-reconcile.d.ts.map +1 -0
- package/dist/storage/folder-reconcile.js +65 -0
- package/dist/storage/folder-reconcile.js.map +1 -0
- package/dist/storage/index.d.ts +10 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +9 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/indexeddb-adapter.d.ts +12 -0
- package/dist/storage/indexeddb-adapter.d.ts.map +1 -0
- package/dist/storage/indexeddb-adapter.js +190 -0
- package/dist/storage/indexeddb-adapter.js.map +1 -0
- package/dist/storage/mst.d.ts +121 -0
- package/dist/storage/mst.d.ts.map +1 -0
- package/dist/storage/mst.js +402 -0
- package/dist/storage/mst.js.map +1 -0
- package/dist/storage/storage-provider.d.ts +85 -0
- package/dist/storage/storage-provider.d.ts.map +1 -0
- package/dist/storage/storage-provider.js +220 -0
- package/dist/storage/storage-provider.js.map +1 -0
- package/dist/sync/anti-entropy.d.ts +49 -0
- package/dist/sync/anti-entropy.d.ts.map +1 -0
- package/dist/sync/anti-entropy.js +62 -0
- package/dist/sync/anti-entropy.js.map +1 -0
- package/dist/sync/index.d.ts +4 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +4 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/sync/sync-engine.d.ts +71 -0
- package/dist/sync/sync-engine.d.ts.map +1 -0
- package/dist/sync/sync-engine.js +309 -0
- package/dist/sync/sync-engine.js.map +1 -0
- package/dist/sync/sync-messages.d.ts +75 -0
- package/dist/sync/sync-messages.d.ts.map +1 -0
- package/dist/sync/sync-messages.js +12 -0
- package/dist/sync/sync-messages.js.map +1 -0
- package/dist/types.d.ts +243 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/encoding.d.ts +63 -0
- package/dist/utils/encoding.d.ts.map +1 -0
- package/dist/utils/encoding.js +130 -0
- package/dist/utils/encoding.js.map +1 -0
- package/dist/utils/errors.d.ts +43 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +28 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/events.d.ts +19 -0
- package/dist/utils/events.d.ts.map +1 -0
- package/dist/utils/events.js +27 -0
- package/dist/utils/events.js.map +1 -0
- package/dist/utils/hash.d.ts +23 -0
- package/dist/utils/hash.d.ts.map +1 -0
- package/dist/utils/hash.js +49 -0
- package/dist/utils/hash.js.map +1 -0
- package/dist/utils/index.d.ts +9 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +9 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/validation/capability-gate.d.ts +50 -0
- package/dist/validation/capability-gate.d.ts.map +1 -0
- package/dist/validation/capability-gate.js +71 -0
- package/dist/validation/capability-gate.js.map +1 -0
- package/dist/validation/crypto-gate.d.ts +16 -0
- package/dist/validation/crypto-gate.d.ts.map +1 -0
- package/dist/validation/crypto-gate.js +37 -0
- package/dist/validation/crypto-gate.js.map +1 -0
- package/dist/validation/index.d.ts +6 -0
- package/dist/validation/index.d.ts.map +1 -0
- package/dist/validation/index.js +6 -0
- package/dist/validation/index.js.map +1 -0
- package/dist/validation/stateful-gate.d.ts +15 -0
- package/dist/validation/stateful-gate.d.ts.map +1 -0
- package/dist/validation/stateful-gate.js +50 -0
- package/dist/validation/stateful-gate.js.map +1 -0
- package/dist/validation/structural-gate.d.ts +18 -0
- package/dist/validation/structural-gate.d.ts.map +1 -0
- package/dist/validation/structural-gate.js +54 -0
- package/dist/validation/structural-gate.js.map +1 -0
- package/dist/validation/validation-engine.d.ts +28 -0
- package/dist/validation/validation-engine.d.ts.map +1 -0
- package/dist/validation/validation-engine.js +38 -0
- package/dist/validation/validation-engine.js.map +1 -0
- package/package.json +107 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module account-vault
|
|
3
|
+
* The lock on a data folder.
|
|
4
|
+
*
|
|
5
|
+
* A folder that holds its seed in the clear is a bearer token: whoever copies it
|
|
6
|
+
* is you, and the AES key for every private space sits in the same directory as
|
|
7
|
+
* the ciphertext it opens. So the seed is never stored — only **wrapped copies**
|
|
8
|
+
* of it are, one per way of unlocking.
|
|
9
|
+
*
|
|
10
|
+
* ```
|
|
11
|
+
* account seed (16 bytes, never written in the clear)
|
|
12
|
+
* ├── HKDF → vault key ──encrypts──> space keys and space records at rest
|
|
13
|
+
* └── stored only as wraps:
|
|
14
|
+
* device a random key in this origin's storage, gated by a passkey
|
|
15
|
+
* passphrase PBKDF2-SHA256 → AES-GCM
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* Both wraps are shortcuts for one origin, and neither carries the account
|
|
19
|
+
* anywhere — that is what the code is for. Adding an app means adding a wrap
|
|
20
|
+
* there, and two shortcuts end up meaning one account without a delegation
|
|
21
|
+
* chain anywhere in sight.
|
|
22
|
+
*
|
|
23
|
+
* The recovery code needs no wrap at all: it *is* the seed, in printable form.
|
|
24
|
+
* It is shown once and never stored, and it is the way in when no wrap fits —
|
|
25
|
+
* a phone, Safari, or an origin that has never seen this folder.
|
|
26
|
+
*
|
|
27
|
+
* ## What this does not protect
|
|
28
|
+
*
|
|
29
|
+
* Expression files keep their author DID, timestamps and collection names in
|
|
30
|
+
* the clear, and public spaces keep their bodies that way by design. Locking
|
|
31
|
+
* those too would mean an opaque blob store, which would cost the property that
|
|
32
|
+
* makes a folder worth having — that you can open it and see what is in it.
|
|
33
|
+
*/
|
|
34
|
+
import { base64UrlEncode, base64UrlDecode, utf8Encode } from '../utils/encoding.js';
|
|
35
|
+
import { protocolError } from '../utils/errors.js';
|
|
36
|
+
/** AES-GCM wants 96 bits of nonce. */
|
|
37
|
+
const IV_BYTES = 12;
|
|
38
|
+
const SALT_BYTES = 16;
|
|
39
|
+
/**
|
|
40
|
+
* PBKDF2 rounds for a passphrase wrap.
|
|
41
|
+
*
|
|
42
|
+
* Tuned to roughly a quarter-second in a browser, on the reasoning that this
|
|
43
|
+
* runs once per unlock and the thing it guards is an identity rather than a
|
|
44
|
+
* session.
|
|
45
|
+
*/
|
|
46
|
+
export const PASSPHRASE_ITERATIONS = 600_000;
|
|
47
|
+
const VAULT_KEY_INFO = utf8Encode('weave-vault-key-v1');
|
|
48
|
+
/** Random bytes as base64url, for salts and nonces. */
|
|
49
|
+
function randomBytes(length) {
|
|
50
|
+
return globalThis.crypto.getRandomValues(new Uint8Array(length));
|
|
51
|
+
}
|
|
52
|
+
/** A short opaque id for a wrap. */
|
|
53
|
+
function wrapId() {
|
|
54
|
+
return base64UrlEncode(randomBytes(8));
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Encrypts a seed under a key.
|
|
58
|
+
* @param seed The seed to protect
|
|
59
|
+
* @param key An AES-GCM key
|
|
60
|
+
* @returns The nonce and ciphertext, base64url encoded
|
|
61
|
+
*/
|
|
62
|
+
async function seal(seed, key) {
|
|
63
|
+
const iv = randomBytes(IV_BYTES);
|
|
64
|
+
const ciphertext = await globalThis.crypto.subtle.encrypt({ name: 'AES-GCM', iv: iv }, key, seed);
|
|
65
|
+
return { iv: base64UrlEncode(iv), ciphertext: base64UrlEncode(new Uint8Array(ciphertext)) };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Decrypts a seed, turning the failure into something a UI can say out loud.
|
|
69
|
+
* @param wrap The wrap to open
|
|
70
|
+
* @param key The key it should be under
|
|
71
|
+
* @returns The seed
|
|
72
|
+
*/
|
|
73
|
+
async function open(wrap, key) {
|
|
74
|
+
try {
|
|
75
|
+
const plain = await globalThis.crypto.subtle.decrypt({ name: 'AES-GCM', iv: base64UrlDecode(wrap.iv) }, key, base64UrlDecode(wrap.ciphertext));
|
|
76
|
+
return new Uint8Array(plain);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// AES-GCM authenticates, so this is the wrong key rather than corruption.
|
|
80
|
+
throw protocolError('VAULT_UNLOCK_FAILED', 'That did not unlock the folder.', 'Check the passphrase, or use your recovery code instead.');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Stretches a passphrase into the key that wraps a seed.
|
|
85
|
+
* @param passphrase What the user typed
|
|
86
|
+
* @param salt Per-wrap salt
|
|
87
|
+
* @param iterations PBKDF2 rounds, read from the wrap so old ones stay openable
|
|
88
|
+
* @returns An AES-GCM key
|
|
89
|
+
*/
|
|
90
|
+
async function passphraseWrappingKey(passphrase, salt, iterations) {
|
|
91
|
+
const material = await globalThis.crypto.subtle.importKey('raw', utf8Encode(passphrase.normalize('NFKC')), { name: 'PBKDF2' }, false, ['deriveKey']);
|
|
92
|
+
return globalThis.crypto.subtle.deriveKey({ name: 'PBKDF2', hash: 'SHA-256', salt: salt, iterations }, material, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt']);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Wraps a seed under this device's key.
|
|
96
|
+
*
|
|
97
|
+
* The key is random and lives in this origin's storage; the passkey recorded
|
|
98
|
+
* alongside is the gate in front of it, not the source of it. See
|
|
99
|
+
* {@link module:device-key}.
|
|
100
|
+
*
|
|
101
|
+
* @param seed The account seed
|
|
102
|
+
* @param deviceKey The local key, from `createDeviceKey`
|
|
103
|
+
* @param meta Which origin this belongs to, and the passkey gating it
|
|
104
|
+
* @returns A wrap ready to append to the account file
|
|
105
|
+
*/
|
|
106
|
+
export async function wrapSeedWithDeviceKey(seed, deviceKey, meta) {
|
|
107
|
+
const sealed = await seal(seed, deviceKey.key);
|
|
108
|
+
return {
|
|
109
|
+
kind: 'device',
|
|
110
|
+
id: wrapId(),
|
|
111
|
+
label: meta.label ?? meta.rpId,
|
|
112
|
+
rpId: meta.rpId,
|
|
113
|
+
deviceKeyId: deviceKey.id,
|
|
114
|
+
...(meta.credentialId ? { credentialId: meta.credentialId } : {}),
|
|
115
|
+
...(meta.userHandle ? { userHandle: meta.userHandle } : {}),
|
|
116
|
+
addedAt: new Date().toISOString(),
|
|
117
|
+
// Nothing is stretched into this key, so there is no salt to record.
|
|
118
|
+
salt: '',
|
|
119
|
+
...sealed,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Opens a device wrap.
|
|
124
|
+
* @param wrap The wrap to open
|
|
125
|
+
* @param deviceKey The local key it names
|
|
126
|
+
* @returns The seed
|
|
127
|
+
*/
|
|
128
|
+
export async function unwrapSeedWithDeviceKey(wrap, deviceKey) {
|
|
129
|
+
return open(wrap, deviceKey);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Wraps a seed under a passphrase — the portable way back in.
|
|
133
|
+
* @param seed The account seed
|
|
134
|
+
* @param passphrase What the user chose
|
|
135
|
+
* @param label What to call this wrap
|
|
136
|
+
* @returns A wrap ready to append to the account file
|
|
137
|
+
*/
|
|
138
|
+
export async function wrapSeedWithPassphrase(seed, passphrase, label = 'Passphrase') {
|
|
139
|
+
const salt = randomBytes(SALT_BYTES);
|
|
140
|
+
const sealed = await seal(seed, await passphraseWrappingKey(passphrase, salt, PASSPHRASE_ITERATIONS));
|
|
141
|
+
return {
|
|
142
|
+
kind: 'passphrase',
|
|
143
|
+
id: wrapId(),
|
|
144
|
+
label,
|
|
145
|
+
addedAt: new Date().toISOString(),
|
|
146
|
+
salt: base64UrlEncode(salt),
|
|
147
|
+
iterations: PASSPHRASE_ITERATIONS,
|
|
148
|
+
...sealed,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Opens a passphrase wrap.
|
|
153
|
+
* @param wrap The wrap to open
|
|
154
|
+
* @param passphrase What the user typed
|
|
155
|
+
* @returns The seed
|
|
156
|
+
*/
|
|
157
|
+
export async function unwrapSeedWithPassphrase(wrap, passphrase) {
|
|
158
|
+
const key = await passphraseWrappingKey(passphrase, base64UrlDecode(wrap.salt), wrap.iterations);
|
|
159
|
+
return open(wrap, key);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* The vault key as raw bytes.
|
|
163
|
+
*
|
|
164
|
+
* {@link deriveVaultKey} returns a key that cannot be exported, which is right
|
|
165
|
+
* for a page — it should not be able to leak what it was given. A node derives
|
|
166
|
+
* the account registry from the key, so it is handed the bytes directly
|
|
167
|
+
* rather than the protocol relaxing that for everyone.
|
|
168
|
+
*
|
|
169
|
+
* Identical material to `deriveVaultKey`: same input, same info, same length.
|
|
170
|
+
* If those ever drift, the registry and the folder would be sealed under two
|
|
171
|
+
* different keys.
|
|
172
|
+
*
|
|
173
|
+
* @param seed The account seed
|
|
174
|
+
* @returns 32 bytes, ready to import as AES-GCM
|
|
175
|
+
*/
|
|
176
|
+
export async function deriveVaultKeyBytes(seed) {
|
|
177
|
+
const material = await globalThis.crypto.subtle.importKey('raw', seed, { name: 'HKDF' }, false, ['deriveBits']);
|
|
178
|
+
const bits = await globalThis.crypto.subtle.deriveBits({
|
|
179
|
+
name: 'HKDF',
|
|
180
|
+
hash: 'SHA-256',
|
|
181
|
+
salt: new Uint8Array(0),
|
|
182
|
+
info: VAULT_KEY_INFO,
|
|
183
|
+
}, material, 256);
|
|
184
|
+
return new Uint8Array(bits);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Derives the key that encrypts a folder's contents at rest.
|
|
188
|
+
*
|
|
189
|
+
* Separate from the signing key, and from every wrapping key, so that handing
|
|
190
|
+
* one out never implies the others.
|
|
191
|
+
*
|
|
192
|
+
* @param seed The account seed
|
|
193
|
+
* @returns An AES-GCM key for space keys and space records
|
|
194
|
+
*/
|
|
195
|
+
export async function deriveVaultKey(seed) {
|
|
196
|
+
const material = await globalThis.crypto.subtle.importKey('raw', seed, { name: 'HKDF' }, false, ['deriveKey']);
|
|
197
|
+
return globalThis.crypto.subtle.deriveKey({
|
|
198
|
+
name: 'HKDF',
|
|
199
|
+
hash: 'SHA-256',
|
|
200
|
+
salt: new Uint8Array(0),
|
|
201
|
+
info: VAULT_KEY_INFO,
|
|
202
|
+
}, material, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt']);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* The device wraps this origin could actually attempt.
|
|
206
|
+
*
|
|
207
|
+
* The key a wrap names lives in the storage of the origin that made it, so one
|
|
208
|
+
* from another app is not merely likely to fail — it is unreachable from here.
|
|
209
|
+
*
|
|
210
|
+
* @param vault The account file
|
|
211
|
+
* @param rpId This origin's relying-party id, usually its hostname
|
|
212
|
+
* @returns The device wraps belonging to this origin
|
|
213
|
+
*/
|
|
214
|
+
export function deviceWrapsFor(vault, rpId) {
|
|
215
|
+
return vault.wraps.filter((wrap) => wrap.kind === 'device' && wrap.rpId === rpId);
|
|
216
|
+
}
|
|
217
|
+
/** Whether a passphrase would get anyone in. */
|
|
218
|
+
export function hasPassphraseWrap(vault) {
|
|
219
|
+
return vault.wraps.some((wrap) => wrap.kind === 'passphrase');
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Adds a wrap, replacing any earlier one for the same passkey.
|
|
223
|
+
*
|
|
224
|
+
* Re-registering on an origin should leave one usable wrap rather than a pile of
|
|
225
|
+
* stale ones, and a wrap whose credential is gone is only clutter.
|
|
226
|
+
*
|
|
227
|
+
* @param vault The account file
|
|
228
|
+
* @param wrap The wrap to add
|
|
229
|
+
* @returns The updated account file
|
|
230
|
+
*/
|
|
231
|
+
export function withWrap(vault, wrap) {
|
|
232
|
+
// One device wrap per origin: re-adding the shortcut should replace it, not
|
|
233
|
+
// leave an older one pointing at a key nobody will use again.
|
|
234
|
+
const superseded = (existing) => existing.kind === 'device' && wrap.kind === 'device' && existing.rpId === wrap.rpId;
|
|
235
|
+
return { ...vault, wraps: [...vault.wraps.filter((existing) => !superseded(existing)), wrap] };
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Removes a wrap by id.
|
|
239
|
+
*
|
|
240
|
+
* Removing the last one is allowed, and leaves the account in the state a new
|
|
241
|
+
* one starts in: openable by its code and nothing else. The wraps are
|
|
242
|
+
* shortcuts, not the keys to the building.
|
|
243
|
+
*
|
|
244
|
+
* @param vault The account file
|
|
245
|
+
* @param id The wrap to remove
|
|
246
|
+
* @returns The updated account file
|
|
247
|
+
*/
|
|
248
|
+
export function withoutWrap(vault, id) {
|
|
249
|
+
return { ...vault, wraps: vault.wraps.filter((wrap) => wrap.id !== id) };
|
|
250
|
+
}
|
|
251
|
+
//# sourceMappingURL=account-vault.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account-vault.js","sourceRoot":"","sources":["../../src/identity/account-vault.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module identity/agent-note
|
|
3
|
+
* A note that says its key is an agent's.
|
|
4
|
+
*
|
|
5
|
+
* An agent isn't an identity of its own: it writes for a person, under a note
|
|
6
|
+
* from their account, like any app. What sets it apart is one fact the account
|
|
7
|
+
* signs into that note — so every peer can tell a record was written "via
|
|
8
|
+
* agent", and can refuse what an agent must never do alone: change the space's
|
|
9
|
+
* collections, or who may do what. Those always need a person.
|
|
10
|
+
*
|
|
11
|
+
* The fact is the account's word, carried in a note it signed. A note without
|
|
12
|
+
* it is an ordinary app's; nothing about the key itself says "agent".
|
|
13
|
+
*/
|
|
14
|
+
import { type Fact } from './ucan.js';
|
|
15
|
+
/** The fact an agent's note carries */
|
|
16
|
+
export declare const AGENT_FACT: Fact;
|
|
17
|
+
/** Whether a note (an encoded UCAN) was made out to an agent. False for anything unreadable. */
|
|
18
|
+
export declare function isAgentNote(encoded: string | null | undefined): boolean;
|
|
19
|
+
//# sourceMappingURL=agent-note.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-note.d.ts","sourceRoot":"","sources":["../../src/identity/agent-note.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAa,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjD,uCAAuC;AACvC,eAAO,MAAM,UAAU,EAAE,IAAwC,CAAC;AAElE,gGAAgG;AAChG,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAQvE"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module identity/agent-note
|
|
3
|
+
* A note that says its key is an agent's.
|
|
4
|
+
*
|
|
5
|
+
* An agent isn't an identity of its own: it writes for a person, under a note
|
|
6
|
+
* from their account, like any app. What sets it apart is one fact the account
|
|
7
|
+
* signs into that note — so every peer can tell a record was written "via
|
|
8
|
+
* agent", and can refuse what an agent must never do alone: change the space's
|
|
9
|
+
* collections, or who may do what. Those always need a person.
|
|
10
|
+
*
|
|
11
|
+
* The fact is the account's word, carried in a note it signed. A note without
|
|
12
|
+
* it is an ordinary app's; nothing about the key itself says "agent".
|
|
13
|
+
*/
|
|
14
|
+
import { parseUCAN } from './ucan.js';
|
|
15
|
+
/** The fact an agent's note carries */
|
|
16
|
+
export const AGENT_FACT = Object.freeze({ weave: 'agent' });
|
|
17
|
+
/** Whether a note (an encoded UCAN) was made out to an agent. False for anything unreadable. */
|
|
18
|
+
export function isAgentNote(encoded) {
|
|
19
|
+
if (!encoded)
|
|
20
|
+
return false;
|
|
21
|
+
try {
|
|
22
|
+
const facts = parseUCAN(encoded).payload.fct;
|
|
23
|
+
return Array.isArray(facts) && facts.some((fact) => fact?.weave === AGENT_FACT.weave);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=agent-note.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-note.js","sourceRoot":"","sources":["../../src/identity/agent-note.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,SAAS,EAAa,MAAM,WAAW,CAAC;AAEjD,uCAAuC;AACvC,MAAM,CAAC,MAAM,UAAU,GAAS,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;AAElE,gGAAgG;AAChG,MAAM,UAAU,WAAW,CAAC,OAAkC;IAC5D,IAAI,CAAC,OAAO;QAAE,OAAO,KAAK,CAAC;IAC3B,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC;QAC7C,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,UAAU,CAAC,KAAK,CAAC,CAAC;IACxF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { CryptoProvider } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a CryptoProvider using the Web Crypto API with ECDSA P-256.
|
|
4
|
+
* @returns {CryptoProvider} A crypto provider implementation for ECDSA P-256.
|
|
5
|
+
*/
|
|
6
|
+
export declare function createP256Provider(): CryptoProvider;
|
|
7
|
+
//# sourceMappingURL=crypto-p256.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto-p256.d.ts","sourceRoot":"","sources":["../../src/identity/crypto-p256.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAuB,MAAM,aAAa,CAAC;AAqBlE;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,cAAc,CAmHnD"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { base64UrlEncode } from '../utils/encoding.js';
|
|
2
|
+
import { p256 } from '@noble/curves/nist.js';
|
|
3
|
+
/** 48 bytes: the 32-byte group order plus 16 more, so reducing mod n is unbiased */
|
|
4
|
+
const P256_SEED_BYTES = 48;
|
|
5
|
+
/** Domain separation for identity keys. Changing it changes every derived DID. */
|
|
6
|
+
const P256_KEY_INFO = new TextEncoder().encode('weave/p256-identity-key/v1');
|
|
7
|
+
/** HKDF-SHA256 with an empty salt: the seed is already uniformly random. */
|
|
8
|
+
async function hkdf(ikm, info, length) {
|
|
9
|
+
const key = await globalThis.crypto.subtle.importKey('raw', ikm, 'HKDF', false, ['deriveBits']);
|
|
10
|
+
const bits = await globalThis.crypto.subtle.deriveBits({ name: 'HKDF', hash: 'SHA-256', salt: new Uint8Array(0), info: info }, key, length * 8);
|
|
11
|
+
return new Uint8Array(bits);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Creates a CryptoProvider using the Web Crypto API with ECDSA P-256.
|
|
15
|
+
* @returns {CryptoProvider} A crypto provider implementation for ECDSA P-256.
|
|
16
|
+
*/
|
|
17
|
+
export function createP256Provider() {
|
|
18
|
+
return Object.freeze({
|
|
19
|
+
algorithm: 'ECDSA-P256',
|
|
20
|
+
async generateKeyPair() {
|
|
21
|
+
const keyPair = await globalThis.crypto.subtle.generateKey({
|
|
22
|
+
name: 'ECDSA',
|
|
23
|
+
namedCurve: 'P-256'
|
|
24
|
+
},
|
|
25
|
+
// Not extractable: a script that gets into the page can sign with the
|
|
26
|
+
// key while it is there, but cannot carry it off and keep signing.
|
|
27
|
+
// The public half exports regardless.
|
|
28
|
+
false, ['sign', 'verify']);
|
|
29
|
+
return Object.freeze({
|
|
30
|
+
publicKey: keyPair.publicKey,
|
|
31
|
+
privateKey: keyPair.privateKey
|
|
32
|
+
});
|
|
33
|
+
},
|
|
34
|
+
async deriveKeyPairFromSeed(seed) {
|
|
35
|
+
// HKDF stretches the seed to the 48 bytes FIPS 186-5 (appendix A.2) asks
|
|
36
|
+
// for, and noble reduces them to a scalar in [1, n-1] with negligible bias.
|
|
37
|
+
// Web Crypto can sign with a scalar but cannot compute its public point,
|
|
38
|
+
// which is the one step noble supplies.
|
|
39
|
+
const expanded = await hkdf(seed, P256_KEY_INFO, P256_SEED_BYTES);
|
|
40
|
+
const secretKey = p256.utils.randomSecretKey(expanded);
|
|
41
|
+
const point = p256.getPublicKey(secretKey, false); // 0x04 ‖ x ‖ y
|
|
42
|
+
const x = base64UrlEncode(point.subarray(1, 33));
|
|
43
|
+
const y = base64UrlEncode(point.subarray(33, 65));
|
|
44
|
+
const d = base64UrlEncode(secretKey);
|
|
45
|
+
const algorithm = { name: 'ECDSA', namedCurve: 'P-256' };
|
|
46
|
+
const privateKey = await globalThis.crypto.subtle.importKey('jwk', { kty: 'EC', crv: 'P-256', x, y, d, ext: false, key_ops: ['sign'] }, algorithm, false, ['sign']);
|
|
47
|
+
const publicKey = await globalThis.crypto.subtle.importKey('jwk', { kty: 'EC', crv: 'P-256', x, y, ext: true, key_ops: ['verify'] }, algorithm, true, ['verify']);
|
|
48
|
+
return Object.freeze({ publicKey, privateKey });
|
|
49
|
+
},
|
|
50
|
+
async sign(privateKey, data) {
|
|
51
|
+
const signature = await globalThis.crypto.subtle.sign({
|
|
52
|
+
name: 'ECDSA',
|
|
53
|
+
hash: { name: 'SHA-256' }
|
|
54
|
+
}, privateKey, data);
|
|
55
|
+
return new Uint8Array(signature);
|
|
56
|
+
},
|
|
57
|
+
async verify(publicKey, signature, data) {
|
|
58
|
+
return await globalThis.crypto.subtle.verify({
|
|
59
|
+
name: 'ECDSA',
|
|
60
|
+
hash: { name: 'SHA-256' }
|
|
61
|
+
}, publicKey, signature, data);
|
|
62
|
+
},
|
|
63
|
+
/** The 33-byte compressed point — the form did:key specifies for P-256. */
|
|
64
|
+
async exportPublicKey(key) {
|
|
65
|
+
const raw = new Uint8Array(await globalThis.crypto.subtle.exportKey('raw', key));
|
|
66
|
+
return p256.Point.fromBytes(raw).toBytes(true);
|
|
67
|
+
},
|
|
68
|
+
/** Accepts a compressed or uncompressed point; Web Crypto only reliably imports the latter. */
|
|
69
|
+
async importPublicKey(bytes) {
|
|
70
|
+
const uncompressed = p256.Point.fromBytes(bytes).toBytes(false);
|
|
71
|
+
return await globalThis.crypto.subtle.importKey('raw', uncompressed, {
|
|
72
|
+
name: 'ECDSA',
|
|
73
|
+
namedCurve: 'P-256'
|
|
74
|
+
}, true, ['verify']);
|
|
75
|
+
},
|
|
76
|
+
async importPrivateKey(bytes) {
|
|
77
|
+
return await globalThis.crypto.subtle.importKey('pkcs8', bytes, {
|
|
78
|
+
name: 'ECDSA',
|
|
79
|
+
namedCurve: 'P-256'
|
|
80
|
+
}, false, ['sign']);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=crypto-p256.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto-p256.js","sourceRoot":"","sources":["../../src/identity/crypto-p256.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module device-key
|
|
3
|
+
* The key that unlocks an account on one device.
|
|
4
|
+
*
|
|
5
|
+
* A passkey can only hand back a secret through the PRF extension, and several
|
|
6
|
+
* widely used providers — Bitwarden among them — store passkeys without it. So
|
|
7
|
+
* the shortcut does not ask the passkey for key material at all. It keeps a
|
|
8
|
+
* random key here, and uses the passkey as the gate in front of it.
|
|
9
|
+
*
|
|
10
|
+
* ```
|
|
11
|
+
* random key, non-extractable, in this origin's storage
|
|
12
|
+
* └─ wraps the account seed, which lives in the vault
|
|
13
|
+
* a passkey assertion decides whether we reach for it
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* **Be clear about what that is.** The gate is enforced by this code, not by
|
|
17
|
+
* cryptography: anything that can reach this origin's storage can use the key
|
|
18
|
+
* without ever touching the passkey. What it does protect against is the case
|
|
19
|
+
* that motivates the vault in the first place — someone holding a copy of the
|
|
20
|
+
* folder, who has the wrapped seed and none of this.
|
|
21
|
+
*
|
|
22
|
+
* The key is generated non-extractable, so script can use it in place but
|
|
23
|
+
* cannot copy it out. Malware on the machine can decrypt; it cannot walk away
|
|
24
|
+
* with something that keeps working elsewhere.
|
|
25
|
+
*/
|
|
26
|
+
/** A local key, and the id a wrap records to find it again. */
|
|
27
|
+
export interface DeviceKey {
|
|
28
|
+
readonly id: string;
|
|
29
|
+
readonly key: CryptoKey;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Makes a key for this device and remembers it.
|
|
33
|
+
*
|
|
34
|
+
* Non-extractable on purpose: a CryptoKey survives being stored and fetched
|
|
35
|
+
* from IndexedDB, and staying non-extractable the whole time means no code
|
|
36
|
+
* path — ours or anyone's — can turn it back into bytes.
|
|
37
|
+
*
|
|
38
|
+
* @param dbName Overridable for tests
|
|
39
|
+
* @returns The key, and the id a wrap should record
|
|
40
|
+
*/
|
|
41
|
+
export declare function createDeviceKey(dbName?: string): Promise<DeviceKey>;
|
|
42
|
+
/**
|
|
43
|
+
* Finds a device key by id.
|
|
44
|
+
*
|
|
45
|
+
* Absent is a normal answer, not a failure: it means this wrap was made in
|
|
46
|
+
* another browser, or storage has been cleared since. The account is still
|
|
47
|
+
* openable by its password.
|
|
48
|
+
*
|
|
49
|
+
* @param id The id recorded in the wrap
|
|
50
|
+
* @param dbName Overridable for tests
|
|
51
|
+
* @returns The key, or null when this device does not have it
|
|
52
|
+
*/
|
|
53
|
+
export declare function getDeviceKey(id: string, dbName?: string): Promise<CryptoKey | null>;
|
|
54
|
+
/**
|
|
55
|
+
* Forgets a device key, which makes its wrap permanently unopenable.
|
|
56
|
+
* @param id The id recorded in the wrap
|
|
57
|
+
* @param dbName Overridable for tests
|
|
58
|
+
*/
|
|
59
|
+
export declare function deleteDeviceKey(id: string, dbName?: string): Promise<void>;
|
|
60
|
+
//# sourceMappingURL=device-key.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-key.d.ts","sourceRoot":"","sources":["../../src/identity/device-key.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAOH,+DAA+D;AAC/D,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC;CACzB;AAiCD;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CAAC,MAAM,GAAE,MAAgB,GAAG,OAAO,CAAC,SAAS,CAAC,CAWlF;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAChC,EAAE,EAAE,MAAM,EACV,MAAM,GAAE,MAAgB,GACvB,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAM3B;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,GAAE,MAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAMzF"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module device-key
|
|
3
|
+
* The key that unlocks an account on one device.
|
|
4
|
+
*
|
|
5
|
+
* A passkey can only hand back a secret through the PRF extension, and several
|
|
6
|
+
* widely used providers — Bitwarden among them — store passkeys without it. So
|
|
7
|
+
* the shortcut does not ask the passkey for key material at all. It keeps a
|
|
8
|
+
* random key here, and uses the passkey as the gate in front of it.
|
|
9
|
+
*
|
|
10
|
+
* ```
|
|
11
|
+
* random key, non-extractable, in this origin's storage
|
|
12
|
+
* └─ wraps the account seed, which lives in the vault
|
|
13
|
+
* a passkey assertion decides whether we reach for it
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* **Be clear about what that is.** The gate is enforced by this code, not by
|
|
17
|
+
* cryptography: anything that can reach this origin's storage can use the key
|
|
18
|
+
* without ever touching the passkey. What it does protect against is the case
|
|
19
|
+
* that motivates the vault in the first place — someone holding a copy of the
|
|
20
|
+
* folder, who has the wrapped seed and none of this.
|
|
21
|
+
*
|
|
22
|
+
* The key is generated non-extractable, so script can use it in place but
|
|
23
|
+
* cannot copy it out. Malware on the machine can decrypt; it cannot walk away
|
|
24
|
+
* with something that keeps working elsewhere.
|
|
25
|
+
*/
|
|
26
|
+
import { base64UrlEncode } from '../utils/encoding.js';
|
|
27
|
+
const DB_NAME = 'weave-device-keys';
|
|
28
|
+
const STORE = 'keys';
|
|
29
|
+
function openDb(dbName) {
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
const request = globalThis.indexedDB.open(dbName, 1);
|
|
32
|
+
request.onupgradeneeded = () => {
|
|
33
|
+
if (!request.result.objectStoreNames.contains(STORE)) {
|
|
34
|
+
request.result.createObjectStore(STORE);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
request.onsuccess = () => resolve(request.result);
|
|
38
|
+
request.onerror = () => reject(request.error);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
async function withStore(dbName, mode, run) {
|
|
42
|
+
const db = await openDb(dbName);
|
|
43
|
+
try {
|
|
44
|
+
return await new Promise((resolve, reject) => {
|
|
45
|
+
const tx = db.transaction(STORE, mode);
|
|
46
|
+
const request = run(tx.objectStore(STORE));
|
|
47
|
+
request.onsuccess = () => resolve(request.result);
|
|
48
|
+
request.onerror = () => reject(request.error);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
db.close();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Makes a key for this device and remembers it.
|
|
57
|
+
*
|
|
58
|
+
* Non-extractable on purpose: a CryptoKey survives being stored and fetched
|
|
59
|
+
* from IndexedDB, and staying non-extractable the whole time means no code
|
|
60
|
+
* path — ours or anyone's — can turn it back into bytes.
|
|
61
|
+
*
|
|
62
|
+
* @param dbName Overridable for tests
|
|
63
|
+
* @returns The key, and the id a wrap should record
|
|
64
|
+
*/
|
|
65
|
+
export async function createDeviceKey(dbName = DB_NAME) {
|
|
66
|
+
const key = await globalThis.crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt']);
|
|
67
|
+
const id = base64UrlEncode(globalThis.crypto.getRandomValues(new Uint8Array(12)));
|
|
68
|
+
await withStore(dbName, 'readwrite', (store) => store.put(key, id));
|
|
69
|
+
return { id, key };
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Finds a device key by id.
|
|
73
|
+
*
|
|
74
|
+
* Absent is a normal answer, not a failure: it means this wrap was made in
|
|
75
|
+
* another browser, or storage has been cleared since. The account is still
|
|
76
|
+
* openable by its password.
|
|
77
|
+
*
|
|
78
|
+
* @param id The id recorded in the wrap
|
|
79
|
+
* @param dbName Overridable for tests
|
|
80
|
+
* @returns The key, or null when this device does not have it
|
|
81
|
+
*/
|
|
82
|
+
export async function getDeviceKey(id, dbName = DB_NAME) {
|
|
83
|
+
try {
|
|
84
|
+
return (await withStore(dbName, 'readonly', (store) => store.get(id))) ?? null;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Forgets a device key, which makes its wrap permanently unopenable.
|
|
92
|
+
* @param id The id recorded in the wrap
|
|
93
|
+
* @param dbName Overridable for tests
|
|
94
|
+
*/
|
|
95
|
+
export async function deleteDeviceKey(id, dbName = DB_NAME) {
|
|
96
|
+
try {
|
|
97
|
+
await withStore(dbName, 'readwrite', (store) => store.delete(id));
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// Already gone.
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=device-key.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-key.js","sourceRoot":"","sources":["../../src/identity/device-key.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multicodec `p256-pub` (0x1200) as a varint. Per the did:key spec the key that
|
|
3
|
+
* follows is the 33-byte compressed point, which is why P-256 DIDs start `zDn`.
|
|
4
|
+
*/
|
|
5
|
+
export declare const P256_MULTICODEC: Uint8Array<ArrayBuffer>;
|
|
6
|
+
/**
|
|
7
|
+
* Converts a public key to a did:key string.
|
|
8
|
+
* @param {Uint8Array} publicKeyBytes The public key bytes.
|
|
9
|
+
* @param {Uint8Array} multicodecPrefix The multicodec prefix for the key type.
|
|
10
|
+
* @returns {string} The formatted did:key string.
|
|
11
|
+
*/
|
|
12
|
+
export declare function publicKeyToDid(publicKeyBytes: Uint8Array, multicodecPrefix: Uint8Array): string;
|
|
13
|
+
/**
|
|
14
|
+
* Parses a did:key string into its public key and multicodec prefix.
|
|
15
|
+
* @param {string} did The did:key string.
|
|
16
|
+
* @returns {{ publicKeyBytes: Uint8Array; multicodecPrefix: Uint8Array }} The parsed components.
|
|
17
|
+
*/
|
|
18
|
+
export declare function didToPublicKey(did: string): {
|
|
19
|
+
publicKeyBytes: Uint8Array;
|
|
20
|
+
multicodecPrefix: Uint8Array;
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=did.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"did.d.ts","sourceRoot":"","sources":["../../src/identity/did.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,eAAO,MAAM,eAAe,yBAA+B,CAAC;AAE5D;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,cAAc,EAAE,UAAU,EAAE,gBAAgB,EAAE,UAAU,GAAG,MAAM,CAG/F;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,cAAc,EAAE,UAAU,CAAC;IAAC,gBAAgB,EAAE,UAAU,CAAA;CAAE,CAiBxG"}
|