Description
Block 1 from #194 (comment)
Aims to provide support for a Polykey instance to run unattended (i.e. without user prompt) when both the PK_RECOVERY_CODE
and PK_PASSWORD
variables are set.
Requirements of this design
- User has to supply a root password in order to encrypt a root key that is automatically generated
- If the user loses or forgets the root password, there is no way to decrypt the root key, there is no "Forget Password" functionality for decentralised application
- Provider an alternative way of recovering the root key
- The alternative is to store a BIP39 seed phrase that is used to generate the root key. If the user can supply a BIP39 seed phrase, this can be used to deterministically generate the root key, which doesn't require decrypting the root key and thus unlocking the keynode
- In an extreme case, the root key ciphertext file on disk is corrupted, and so decrypting it produces an incorrect root key, the seed phrase represents a backup of the root key
- Users must be notified to securely backup/store their root key, offer users to print it out as a "paper key" (like https://www.jabberwocky.com/software/paperkey/, we can even do funny things like QR codes)
Additional context
- https://gitlab.com/MatrixAI/Engineering/Polykey/Polykey/-/issues/75
- Deterministic generation of the
pki.rsa.generateKeyPair
digitalbazaar/forge#865
Note that the core function to generate a root key from a BIP39 seed is already available in our keys/utils.ts
, see the function: generateDeterministicKeyPair
. However it is not currently used by KeyManager, and not well tested.
Screenshot from Figma GUI design:
Specification
- Get the KeyManager to use the
generateDeterministicKeyPair
- Make sure that this function
generateDeterministicKeyPair
is well tested - Users are presented with a 24 word phrase to store and remember (prompt the user to store/print it)
- During the startup of the PK agent, the user has to supply the root password OR the seed phrase
- During session unlock, the user can supply the root password OR the seed phrase
- When a seed phrase is supplied, the root key is regenerated from the seed phrase, the root key ciphertext is not touched
- We must have a root key acceptance condition to know whether the root key has legitmately opened up a vault
- If it has, we can save/rewrite the root key as ciphertext over the ciphertext in the node state, which will resolve any root key ciphertext corruption
Regarding the root key acceptance, consider this idea:
- Create a signed-message
- Encrypt the message and save the message
- If you can later decrypt the message, and check the the signature was signed by the root key and the message is what you expect
- Then the root key is correct!
- This message will need to be stored in the DB (as this is what is decrypted by the root key)
CLI command variables
Variables for CLI commands can be retrieved from:
- file parameters
- environment variables
src/config.ts
- user prompt in STDIN
We require functions to retrieve the variables from each of these locations (created in src/bin/utils
).
Variable precedence should be as follows: file parameters > environment variables/src/config.ts
> default (STDIN prompt). We can use a sequence of coalescing operations to emulate this: for example, to retrieve the password, password = getFromParams() ?? getFromEnv ?? getFromStdin()
. getFromStdin()
here is expected to be the default, where we assume that a value will always be provided. If a default cannot exist for a particular variable, then throw an exception if none of the sources of variables can produce one.
See point 1 at #202 (comment) for further information.
Bootstrap process
Some components of the bootstrapping process need to be simplified:
bootstrapPolykeyState
(see 3 Enable un-attended bootstrapping forpk agent start
andpk bootstrap
withPK_RECOVERY_CODE
andPK_PASSWORD
#202 (comment))- ensure that the lockfile is created in the appropriate way (see
writeToken
inSession.ts
- discuss with @CMCDragonkai - then, for checking whether there's an existing agent process, do the following:
- If the lock file exists:
- Attempt to acquire the lock
- If we cannot acquire the lock, then something is running. If this is the case, we can simply abort the process. No need to await until the lock is released.
- i.e. no PID checks required
- ensure that the lockfile is created in the appropriate way (see
checkKeynodeState
(see 4 Enable un-attended bootstrapping forpk agent start
andpk bootstrap
withPK_RECOVERY_CODE
andPK_PASSWORD
#202 (comment))- make this more coarse (less fine-grained) on the contents of the
nodePath
: if any state exists besides the lockfile, throw an exception
- make this more coarse (less fine-grained) on the contents of the
Sub-Issues & Sub-PRs created
checklist
- Generate the keypair from the BIP menmonic
- Generate a BIP mnemonic
- generate keypair from menmonic
- update keyManager to use new key generation method.
- add a check that the mnemonic matches the nodeId for the node. if so we update the encrypted rootkey with the new password.
- fix KeyManager to do the recovery process when a recovery code is provided with a password when node state exists.
- tests
- generating a keypair.
- mnemonic deterministically recreates keypair.
- can tell if mnemonic matches keypair/nodeId
- can create new node from scratch, recreates nodeId and keypair.
- can recover a keynode.
- env variables implementation
-
bin/utils
functions for variable retrieval- ~~
getFromParams()
: get variable from file parameter. Usestr.trim()
to remove trailing newlines/whitespace from the file (see 2 at Enable un-attended bootstrapping forpk agent start
andpk bootstrap
withPK_RECOVERY_CODE
andPK_PASSWORD
#202 (comment) for further information) ~~ getFromEnv()
: get variable from environment variablegetFromConfig()
: get variable fromsrc/config.ts
getFromStdin()
: get variable from user prompt- changed how this is handled, there are
binUtils.getPassword
andbinUtils.getRecoveryCode
. options for hosts and ports was created insrc/bin/options.ts
with their own defaults and env.
- ~~
-
bootstrap
process- use ENV variables for password and recovery code.
- simplify
bootstrapPolykeyState
- tests for:
- concurrent execution of
2x pk bootstrap
- concurrent execution of
2x pk agent start
- concurrent execution of
pk bootstrap
+pk agent start
- concurrent execution of
- simplify
checkKeynodeState
- implement
--fresh
flag: forces new keynode state, even if it already exists-
pk agent start
-
pk bootstrap
-