Skip to main content

KeyVault

@webda/sealed-client


Class: KeyVault

Defined in: packages/sealed-client/src/vault.ts:156

Client side of sealed fields: holds the user keyring on this device and opens sealed values.

The keyring (every private key of the user, by kid) is encrypted with a random keyring key; the keyring key is wrapped by the device KEK in the local storage and by the password / recovery code escrows on the server. Private keys live in memory, non-extractable, only while unlocked.

Public key pinning: the fingerprint of the active public key is pinned in the storage (webda-sealed:pin:<userId>). A served active key with another fingerprint is refused with a KeyringPinError, unless this device rotated it, or verified the rotation: a keyring opened with the keyring key (refreshed at unlock, or through an escrow) that still holds the pinned private key. Besides its own rotation, an explicit KeyVault.reset is the only pin change a device makes by itself; KeyVault.forgetPin lets the user trust a new key on first use again.

Constructors​

Constructor​

new KeyVault(options): KeyVault

Defined in: packages/sealed-client/src/vault.ts:174

Create a new KeyVault instance

Parameters​

options​

KeyVaultOptions

user, server operations and local storage

Returns​

KeyVault

Properties​

options​

protected options: KeyVaultOptions

Defined in: packages/sealed-client/src/vault.ts:174

user, server operations and local storage

Accessors​

isUnlocked​

Get Signature​

get isUnlocked(): boolean

Defined in: packages/sealed-client/src/vault.ts:180

Get true while the private keys are in memory

Returns​

boolean

true while the private keys are in memory

Methods​

changePassword()​

changePassword(newPassword, opts?): Promise<void>

Defined in: packages/sealed-client/src/vault.ts:449

Re-wrap the keyring key under a new password and replace the password escrow with User.Keys.SetEscrow (see KeyVault.createPasswordEscrow for the unlock rules).

The password escrow must follow the login password: a host whose password change does not take the escrow changes both together (this call right after, or before, the login password change), otherwise the next device has to be added with the old password.

Parameters​

newPassword​

string

new password

opts?​

options

oldPassword?​

string

current password, needed while locked

Returns​

Promise<void>


createPasswordEscrow()​

createPasswordEscrow(newPassword, opts?): Promise<EscrowBlob>

Defined in: packages/sealed-client/src/vault.ts:422

Build the password escrow of a new password without sending it: the keyring key is re-wrapped under the new password, directly while unlocked, otherwise through the password escrow opened with the old password (a throttled getEscrow, checked against the pin). The escrow carries the active key id (kid): the server refuses it (412) when its active key changed meanwhile.

For a host whose login password change takes the escrow with it (Auth.Password.Change of @webda/auth refuses a change without it once a password escrow exists): send the blob as its escrow input, both are then stored together. KeyVault.changePassword sends it alone with User.Keys.SetEscrow.

While unlocked, the escrow is refused with a KeyringPinError when the keyring changed on another device since the unlock (its keyring key may have been replaced): unlock again first.

Parameters​

newPassword​

string

new password

opts?​

options

oldPassword?​

string

current password, needed while locked

Returns​

Promise<EscrowBlob>

the escrow of the keyring key under the new password


enroll()​

enroll(input): Promise<{ recoveryCode?: string; }>

Defined in: packages/sealed-client/src/vault.ts:212

First device: generate the user key pair and keyring, escrow the keyring key, keep it on this device and pin the public key. The device KEK is derived before anything is sent, so a failing KEK leaves nothing on the server; once the server accepted the keys, the device blob and the pin are stored before the device registration, which is retried by the next unlock when it fails.

Parameters​

input​

enrollment

device​

DeviceKek

device KEK

deviceName​

string

name registered for this device

password?​

string

password escrow (the login password, its KEK is derived locally with its own salt)

recovery?​

boolean

also create a recovery code escrow

Returns​

Promise<{ recoveryCode?: string; }>

the recovery code, to show once


forgetPin()​

forgetPin(): Promise<void>

Defined in: packages/sealed-client/src/vault.ts:544

Forget the public key pinned on this device and lock: the next KeyVault.unlockWithEscrow trusts the served key on first use, as on a new device.

This drops the only protection against a substituted public key, so it is for the user to call explicitly, never automatically on a KeyringPinError: after a KeyVault.reset done on another device (its unlock fails with KeyringPinError and the new key is refused), show KeyVault.pinnedFingerprint and KeyVault.servedFingerprint, let the user compare the served one out of band with the device that did the reset, then call forgetPin() and add the device again with the new escrow. A served fingerprint the user does not recognise means a substituted key: keep the pin and stop

Returns​

Promise<void>


lock()​

lock(): void

Defined in: packages/sealed-client/src/vault.ts:660

Drop the keys from memory

Returns​

void


open()​

open<T>(env, ctx): Promise<T>

Defined in: packages/sealed-client/src/vault.ts:375

Open a sealed value with the private key of its kid (retired keys stay readable). The private keys all come from a keyring authenticated against the pinned active key (or a verified rotation of it), each checked against its served public key

Type Parameters​

T​

T = unknown

Parameters​

env​

SealedEnvelope

sealed envelope

ctx​

SealedContext

model type, uuid and field it is stored in (AAD)

Returns​

Promise<T>

the value


pinnedFingerprint()​

pinnedFingerprint(): Promise<string | undefined>

Defined in: packages/sealed-client/src/vault.ts:519

Fingerprint of the public key pinned on this device (base64url SHA-256 of the raw P-256 point, the kid is its first 16 characters), to show to the user: compared out of band with the fingerprint shown on another device, it tells a legitimate rotation or reset from a substituted key

Returns​

Promise<string | undefined>

the pinned fingerprint, undefined on a device without pin (trust on first use at the next unlock)


removeRecoveryCode()​

removeRecoveryCode(): Promise<void>

Defined in: packages/sealed-client/src/vault.ts:473

Remove the recovery code escrow (User.Keys.RemoveEscrow). The server refuses to remove the last escrow while no device is registered (409, surfaced as the api error)

Returns​

Promise<void>


reset()​

reset(input): Promise<{ recoveryCode?: string; }>

Defined in: packages/sealed-client/src/vault.ts:493

Keys lost (no device left, password and recovery code forgotten): start over with a new key pair and a new keyring key, through User.Keys.Rotate (fresh authentication required, the vault does not need to be unlocked).

The new keyring holds the new private key only: values sealed to the older keys stay unreadable, new values open. The requested escrows replace the old ones, this device keeps the new keyring key and is registered (kept when already registered), and the pin moves to the new key: an explicit reset by the user is, with its own rotation, the only pin change a device makes by itself. The other devices are cut off (their unlock fails with a KeyringPinError); they are added again with the new escrow after KeyVault.forgetPin.

Parameters​

input​

EscrowRequest & object

reset

Returns​

Promise<{ recoveryCode?: string; }>

the recovery code when one was requested, to show once


rotate()​

rotate(options?): Promise<{ recoveryCode?: string; }>

Defined in: packages/sealed-client/src/vault.ts:561

Rotate: a new active key pair added to the keyring, the keyring re-encrypted for it, and the pin moved to it. Values sealed to older keys stay readable.

By default the keyring key is kept, so the escrows stay valid: a device that kept its blob (lost or revoked) can still open the keyring. With newKeyringKey, a fresh keyring key encrypts the keyring, the requested escrows are rebuilt and replace the old ones, and this device re-wraps its blob with its KEK: the answer to a lost or revoked device.

Parameters​

options?​

rotation options

newKeyringKey?​

EscrowRequest & object

replace the keyring key: the device KEK of this device and the escrows to create

Returns​

Promise<{ recoveryCode?: string; }>

the new recovery code when one was requested, to show once


seal()​

seal(value, ctx, recipient?): Promise<SealedEnvelope>

Defined in: packages/sealed-client/src/vault.ts:391

Seal a value to the user pinned public key (works while locked)

Parameters​

value​

unknown

JSON-encodable value

ctx​

SealedContext

model type, uuid and field it will be stored in (AAD)

recipient?​

PublicKeyEntry

public key to seal to, refused unless it is the pinned key (default: the active key)

Returns​

Promise<SealedEnvelope>

the envelope


servedFingerprint()​

servedFingerprint(): Promise<string>

Defined in: packages/sealed-client/src/vault.ts:529

Fingerprint of the active public key the server serves now, to compare with KeyVault.pinnedFingerprint before KeyVault.forgetPin: a device cut off by a reset shows both to the user, who checks the new one against the device that did the reset

Returns​

Promise<string>

the served active fingerprint


setRecoveryCode()​

setRecoveryCode(): Promise<string>

Defined in: packages/sealed-client/src/vault.ts:458

Create or replace the recovery code escrow; refused with a KeyringPinError when the keyring changed on another device since the unlock (unlock again first)

Returns​

Promise<string>

the recovery code, to show once


unlock()​

unlock(device): Promise<void>

Defined in: packages/sealed-client/src/vault.ts:245

Unlock this device with its KEK; the server is only asked for the public keys, checked against the pin, and the devices: a device revoked on the server deletes its local blob (DeviceRevokedError).

A stale local keyring (rotated on another device) is refreshed with User.Keys.GetKeyring: the served keyring must open with the keyring key of this device and still hold the pinned private key, otherwise the unlock fails with a KeyringPinError (the keyring key was replaced: unlock with an escrow).

Parameters​

device​

DeviceKek

device KEK

Returns​

Promise<void>


unlockWithEscrow()​

unlockWithEscrow(secret, register): Promise<void>

Defined in: packages/sealed-client/src/vault.ts:324

Open the keyring with an escrow and keep it on this device (adds a device, or verifies a rotation done on another device: the new keyring must hold the pinned private key)

Parameters​

secret​

EscrowSecret

password or recovery code

register​

device KEK and name

device​

DeviceKek

device KEK

deviceName​

string

name registered for this device

Returns​

Promise<void>