ssb-keyring
Version:
A persistence store for encryption keys for scuttlebutt. It's purpose is to make easy to answer box2 encryption/ decryption questions.
245 lines (160 loc) • 9.13 kB
Markdown
# ssb-keyring
A persistence store for encryption keys for scuttlebutt.
It's purpose is to make easy to answer box2 encryption/ decryption questions.
_This module was extracted from `ssb-tribes`_
## Usage
```js
const Keyring = require('ssb-keyring')
const ssbKeys = require('ssb-keys')
const keys = ssbKeys.generate()
Keyring(`/tmp/keyring-demo-${Date.now()}`, (err, keyring) => {
keyring.signing.add(keys.id, keys)
// ...
})
```
## API
This module is responsible for recording key details:
- persisting encryption keys (async)
- persisting various signing keys (async)
- persisting who has access to keys (async)
Overview (methods most people should use):
```js
/* SETUP */
Keyring(dbPath, cb)
/* REGISTERING */
keyring.signing.add(keys.id, keys)
keyring.dm.add(myLeafFeedId, theirLeafFeedId, myDHKeys, theirDHKeys, cb)
keyring.group.add(groupId, keyInfo, cb)
/* QUERYING */
keyring.signing.get(keys.id)
keyring.dm.get(myLeafFeedId, theirLeafFeedId)
keyring.group.get(groupId)
keys.close(cb)
```
All querying methods are synchronous, so that encryption / decryption
never has to wait for IO to access keys.
All registering methods:
- synchronously return a boolean indicating whether the key is new
- all querying is done on an in-memory cache, which new items are added to
- allow a callback argument at the end if you want to know when the keyring database persisted the key
- the only asynchronous aspect is for persistence
### `Keyring(path, cb)`
where
- `path` *String* to location your keys will be persisted to on disk
- `cb` *function* calls back with `(err, keyring)` (where `keyring` is the keyring API)
- if `cb` not provided, function returns a Promise
---
### `keyring.signing.add(ssbKeys, cb) => Boolean`
Takes some signing keys `ssbKeys` (made by `ssb-keys` or similar), and adds them
as keys you can use for signing messages or other content.
- `ssbKeys` *Object* containing ed25519 keys
- `ssbKeys.id` *String* base64 encoded "ID" resembling the public part
- `ssbKeys.curve` *String* name of the cryptographic elliptic curve used
- `ssbKeys.public` *String* base64 encoded public part of the keypair
- `ssbKeys.private` *String* base64 encoded private part of the keypair
- `cb` *function* callback with signature `(err) => {}`, called after persistence
Returns `true` if this `ssbKeys` is new (not yet in the database), or returns
`false` if it was already in the database.
### `keyring.signing.addNamed(name, ssbKeys, cb) => Boolean`
Similar to `keyring.signing.add`, but uses a free-form `name` string to identify
the keys.
This `name` can be used in `keyring.signing.get(name)` to retrieve the keys.
### `keyring.signing.has(id) => Boolean`
Returns `true` if the keyring has a signing key for `id`, and `false` otherwise.
### `keyring.signing.get(id) => Object | null`
Returns the signing keys for `id`, or `null` if the keyring doesn't have any.
Alternatively, you can pass a `name` instead of `id` if you used
`keyring.signing.addNamed`.
---
### `keyring.dm.add(myId, theirId, myDHKeys, theirDHKeys, cb) => Boolean`
Takes a pair of Diffie-Hellman keys `myDHKeys` and `theirDHKeys` (made with
`ssb-private-group-keys`), and their identifiers `myId` and `theirId`, then
performs Diffie-Hellman key exchange to derive a shared secret, and adds it to
the keyring.
- `myId` *String* identifier for the local peer, usually a feed SSB URI
- `theirId` *String* identifier for the remote peer, usually a feed SSB URI
- `myDHKeys` *DHKeys* class instance (from `ssb-private-group-keys`)
- `theirDHKeys` *DHKeys* class instance (from `ssb-private-group-keys`)
- `cb` *function* callback with signature `(err) => {}`, called after persistence
Returns `true` if the shared secret is new (not yet in the database), or returns
`false` if it was already in the database.
### `keyring.dm.has(myId, theirId) => Boolean`
Returns `true` if the keyring has a shared secret for the pair of `myId` and
`theirId`, and `false` otherwise.
### `keyring.dm.get(myId, theirId) => Object | null`
Returns the shared secret for the Diffie-Hellman key exchange between `myId` and
`theirId`, or `null` if the keyring doesn't have any.
### `keyring.dm.addTriangle(xRootId, xLeafId, yLeafId, cb) => Boolean`
Takes the identifiers for a "triangle" of feed IDs in two metafeed trees, and
registers these in the keyring database.
- `xRootId` *String* identifier for the root feed of the first metafeed tree
- `xLeafId` *String* identifier for the leaf feed of the first metafeed tree
- `yLeafId` *String* identifier for the leaf feed of the second metafeed tree
- `cb` *function* callback with signature `(err) => {}`, called after persistence
Returns `true` if the triangle is new (not yet in the database), or returns
`false` if it was already in the database.
### `keyring.dm.triangulate(xRootId, yLeafId) => String | null`
Returns `xLeafId` if the keyring has a triangle between `xRootId` and `yLeafId`.
---
### `keyring.self.set(keyInfo, cb)`
Your keyring will always intialize and check if a self-key has been set.
You can use this method to override that default.
- `keyInfo` *Object* contains symmetric key
- `info.key` *String* base64 encoded symmetric key
- `info.scheme` *string* (optional)
- `cb` *function* callback with signature `(err)`
### `keyring.self.get() => keyInfo`
Returns the keyInfo for the self-key.
---
### `keyring.group.add(groupId, addInfo, cb)`
Adds a group key to the keyring, where
- `groupId` *String* a cloaked message Id which identifies the group
- `addInfo` *Object*:
- `addInfo.key` *Buffer* - the group encryption key (needed if `addInfo.scheme` is set)
- `addInfo.scheme` *String* - scheme of that encryption key (optional, there is only one option at the moment which we default to)
- `addInfo.root` *MessageId* the id of the `group/init` message (optional)
- `cb` *function* callback with signature `(err) => {}` called after persistence
Can be called multiple times to add more read keys. The first time you add a key it will be automatically picked as the write key. If you call `group.add` with `addInfo.key` on an excluded group, the group will automatically be un-excluded.
### `keyring.group.pickWriteKey(groupId, pickedKey, cb)
Allows you to pick one of the group's current `readKeys` and promote it to being the current `writeKey` that will be available in the object returned by `keyring.group.get`. `pickedKey` needs to exactly match one of the `readKeys`.
- `pickedKey` *Object*:
- `pickedKey.key` *Buffer* - a group encryption key
- `pickedKey.scheme` *String* - scheme of that encryption key
- `cb` *function* callback with signature `(err) => {}` called after persistence
### `keyring.group.has(groupId) => Boolean`
Returns `true` if the keyring has a group key for `groupId`, and `false` otherwise, if you haven't been in the group or if you've been excluded.
### `keyring.group.get(groupId) => groupInfo`
Returns the `groupInfo` that you've added with `keyring.group.add` for `groupId`, or `null` if the keyring doesn't have any. It has the format
- `groupInfo` *Object`:
- `groupInfo.writeKey` *GroupKey* the currently selected key for writing. Is always one of `groupInfo.readKeys`.
- `groupInfo.readKeys` *Array\<GroupKey\>* all keys that you've added to this `groupId`
- `groupInfo.root` *MessageId* the id of the `group/init` message.
where
- `groupKey` *Object*:
- `groupKey.key` *Buffer* - a group encryption key
- `groupKey.scheme` *String* - scheme of that encryption key
### `keyring.group.getUpdates(groupId) => PullStream<groupInfo>`
Like `keyring.group.get` but instead as a live pull stream, that outputs whenever there's been a change to that group's info.
Note, this includes all "old" updates and then continues to emit new updates.
### `keyring.group.exclude(groupId, cb)`
Marks group you've been in as excluded and removes its writeKey. This is useful if you or someone else excludes you from a group. The group info will lose the `writeKey` field and get an `excluded` field set to `true`.
### `keyring.group.list({ live, excluded }) => PullStream<groupId>`
Returns an pull stream of all the groupIds in the keyring.
If `live` is true then it outputs all existing group ids but also all new ones added.
If `excluded` is true (default false) then it only returns excluded groups instead of only non-excluded groups.
### `keyring.group.listSync({ excluded }) => [groupId]`
Returns an array of all the groupIds in the keyring
If `excluded` is true (default false) then it only returns excluded groups instead of only non-excluded groups.
---
### `keyring.poBox.add(poBoxId, info, cb)`
where
- `poBoxId` *String* is an SSB-URI for a P.O. Box
- `info` *Object*
- `info.key` *Buffer* - the private part of a diffie-hellman key
- `info.scheme` *String* the scheme associated with that key (currently optional)
### `keyring.poBox.has(poBoxId) => Boolean`
### `keyring.poBox.get(poBoxId) => keyInfo`
### `keyring.poBox.list(poBoxId) => [poBoxId]`
---
### `keys.close(cb)`
Closes the keyring database