UNPKG

ssb-keyring

Version:

A persistence store for encryption keys for scuttlebutt. It's purpose is to make easy to answer box2 encryption/ decryption questions.

369 lines (297 loc) 10.7 kB
const test = require('tape') const { keySchemes } = require('private-group-spec') const { promisify: p } = require('util') const na = require('sodium-universal') const pull = require('pull-stream') const ssbKeys = require('ssb-keys') const Keyring = require('../') // specifically 1.1.0, the last one ahau uses before the keyring format migration const OldKeyring = require('ssb-keyring') const { tmpPath, GroupKey, GroupId, GroupURIId, MsgId } = require('./helpers') test('keyring.group', async t => { const path = tmpPath() let keyring = await Keyring(path) let DESCRIPTION /* keys.group.add(groupId, keyInfo, cb) */ DESCRIPTION = 'group.add, bad groupId => error' await p(keyring.group.add)('junk', { key: GroupKey(), root: MsgId() }) .then(res => t.fail(DESCRIPTION)) .catch(err => t.match(err && err.message, /expected a groupId/, DESCRIPTION)) // ✓ DESCRIPTION = 'group.add, bad info.key => error' await p(keyring.group.add)(GroupId(), { key: 'junk', root: MsgId() }) .then(res => t.fail(DESCRIPTION)) .catch(err => t.match(err && err.message, /expected buffer of length 32/, DESCRIPTION)) // ✓ DESCRIPTION = 'group.add, bad info.root => error' await p(keyring.group.add)(GroupId(), { key: GroupKey(), root: 'dog' }) .then(res => t.fail(DESCRIPTION)) .catch(err => t.match(err && err.message, /expected info.root to be MsgId/, DESCRIPTION)) // ✓ const groupId = GroupId() const addInfo = { key: GroupKey(), root: MsgId() } DESCRIPTION = 'info.key must be na.crypto_secretbox_KEYBYTES long' t.equals(addInfo.key.length, na.crypto_secretbox_KEYBYTES, DESCRIPTION) DESCRIPTION = 'group.add, works!' await p(keyring.group.add)(groupId, addInfo) .then(() => t.pass(DESCRIPTION)) // ✓ .catch((err) => err && t.fail(err, DESCRIPTION)) /* keys.group.get(groupId) */ DESCRIPTION = 'group.get' const expectedKey = { key: addInfo.key, scheme: keySchemes.private_group } const expected = { writeKey: expectedKey, readKeys: [expectedKey], root: addInfo.root } t.deepEqual(keyring.group.get(groupId), expected, DESCRIPTION) t.deepEqual(keyring.group.get(GroupId()), undefined, DESCRIPTION + ' (unknown groupId)') DESCRIPTION = '...persists' await keyring.close() keyring = await Keyring(path) t.deepEqual(keyring.group.get(groupId), expected, DESCRIPTION) /* keys.group.has(groupId) */ DESCRIPTION = 'group.has' t.true(keyring.group.has(groupId), DESCRIPTION) t.false(keyring.group.has(GroupId()), DESCRIPTION + ' (unknown groupId)') /* keys.group.listSync() */ DESCRIPTION = 'group.listSync' t.deepEqual(keyring.group.listSync(), [groupId], DESCRIPTION) /* keys.group.list() */ DESCRIPTION = 'group.list' t.deepEqual( await pull(keyring.group.list(), pull.collectAsPromise()), [groupId], DESCRIPTION ) const groupIds = [] pull( keyring.group.list({ live: true }), pull.drain((groupId) => { groupIds.push(groupId) }, (err) => { if (err) t.fail(err) }) ) await p(setTimeout)(500) t.deepEqual( groupIds, [groupId], DESCRIPTION + ' live' ) DESCRIPTION = 'group.add second time works' const groupId2 = GroupId() const addInfo2 = { key: GroupKey(), root: MsgId() } await p(keyring.group.add)(groupId2, addInfo2) .then(() => t.pass(DESCRIPTION)) // ✓ .catch((err) => err && t.fail(err, DESCRIPTION)) await p(setTimeout)(500) t.deepEqual( groupIds, [groupId, groupId2], 'group.list live is actually live' ) await p(keyring.group.pickWriteKey)(groupId2, { key: addInfo2.key, scheme: keySchemes.private_group }) .catch((err) => t.fail(err)) await p(setTimeout)(500) t.deepEqual( groupIds, [groupId, groupId2], "group.pickWriteKey picking already picked key doesn't emit group update" ) keyring.close() t.end() }) test('keyring.group URI', async t => { const path = tmpPath() const keyring = await Keyring(path) const DESCRIPTION = 'group.add, accepts URI as root' await p(keyring.group.add)(GroupId(), { key: GroupKey(), root: 'ssb:message/buttwoo-v1/59QdMLwxJzm7WHrNjxMyN06UPmaKhvId5KO4P2XjKhg=' }) .then(() => t.pass(DESCRIPTION)) .catch(err => { console.error(err); t.fail(DESCRIPTION) }) keyring.close() t.end() }) test('accepts ssb:identity/group URIS', async t => { const path = tmpPath() const keyring = await Keyring(path) const DESCRIPTION = 'group.add, accepts URI as groupId' await p(keyring.group.add)(GroupURIId(), { key: GroupKey(), root: 'ssb:message/buttwoo-v1/59QdMLwxJzm7WHrNjxMyN06UPmaKhvId5KO4P2XjKhg=' }) .then(() => t.pass(DESCRIPTION)) .catch(err => { console.error(err); t.fail(DESCRIPTION) }) keyring.close() t.end() }) test('Can store multiple read keys and pick between them for writing', async t => { const path = tmpPath() const keyring = await Keyring(path) const groupId = GroupURIId() const root = MsgId() const key1 = { key: GroupKey(), scheme: keySchemes.private_group } const key2 = { key: GroupKey(), scheme: keySchemes.po_box } let DESCRIPTION = 'Adding the first key works (without scheme)' await p(keyring.group.add)(groupId, { key: key1.key, root }) .then(() => t.pass(DESCRIPTION)) .catch(err => { console.error(err); t.fail(DESCRIPTION) }) t.deepEquals(keyring.group.get(groupId), { writeKey: key1, readKeys: [key1], root }, 'Getting the first key works') DESCRIPTION = 'Adding the second key works' await p(keyring.group.add)(groupId, { ...key2 }) .then(() => t.pass(DESCRIPTION)) .catch(err => { console.error(err); t.fail(DESCRIPTION) }) t.deepEquals(keyring.group.get(groupId), { writeKey: key1, readKeys: [key1, key2], root }, 'Getting the second key works') DESCRIPTION = 'Picking a different write key works' await p(keyring.group.pickWriteKey)(groupId, key2) .then(() => t.pass(DESCRIPTION)) .catch(err => { console.error(err); t.fail(DESCRIPTION) }) t.deepEquals(keyring.group.get(groupId), { writeKey: key2, readKeys: [key1, key2], root }, 'Getting the newly picked key works') keyring.close() t.end() }) test('can be excluded from a group', async t => { const path = tmpPath() const keyring = await Keyring(path) const groupId = GroupURIId() const key = GroupKey() const root = MsgId() const DESCRIPTION = 'Adding the group works' await p(keyring.group.add)(groupId, { key, root }) .then(() => t.pass(DESCRIPTION)) .catch(err => { console.error(err); t.fail(DESCRIPTION) }) t.false(!!keyring.group.get(groupId).excluded, 'Not excluded from the group yet') await p(keyring.group.exclude)(groupId) t.deepEquals( keyring.group.get(groupId), { readKeys: [{ key, scheme: keySchemes.private_group }], root, excluded: true }, 'Been excluded from the group' ) const listExcluded = await pull( keyring.group.list({ excluded: true }), pull.collectAsPromise() ) t.deepEquals(listExcluded, [groupId], 'able to get group from list with excluded opt') const listNotExcluded = await pull( keyring.group.list(), pull.collectAsPromise() ) t.deepEquals(listNotExcluded, [], 'getting no groups when listing not excluded groups') t.deepEquals(keyring.group.listSync({ excluded: true }), [groupId], 'able to get group from listSync with excluded opt') t.deepEquals(keyring.group.listSync(), [], 'getting no groups when listSyncing not excluded groups') const key2 = GroupKey() await p(keyring.group.add)(groupId, { key: key2 }) t.deepEquals(keyring.group.listSync(), [groupId], 'adding a new key un-excluded us') await p(keyring.group.exclude)(groupId) t.deepEquals(keyring.group.listSync(), [], 'excluded again') await p(keyring.group.add)(groupId, { key: key2 }) t.deepEquals(keyring.group.listSync(), [groupId], 'adding an old key un-excluded us') t.deepEquals( keyring.group.get(groupId), { writeKey: { key: key2, scheme: keySchemes.private_group }, readKeys: [ { key, scheme: keySchemes.private_group }, { key: key2, scheme: keySchemes.private_group } ], root }, 'correct group info format after un-exclusion' ) }) test('getUpdates works', async t => { const path = tmpPath() const keyring = await Keyring(path) const groupId = GroupURIId() const key1 = GroupKey() const key2 = GroupKey() const root = MsgId() await p(keyring.group.add)(groupId, { key: key1, root }) const groupUpdates = [] pull( keyring.group.getUpdates(groupId), pull.drain((update) => { groupUpdates.push(update) }, (err) => { if (err) t.fail(err) }) ) await p(setTimeout)(500) t.equals(groupUpdates.length, 1, 'only initial info so far') t.equals( groupUpdates[0].writeKey.key.toString('base64'), key1.toString('base64'), 'writeKey is the one we added' ) await p(keyring.group.add)(groupId, { key: key2, root }) await p(setTimeout)(500) t.equals(groupUpdates.length, 2, 'got new key info') t.equals( groupUpdates[1].readKeys.length, 2, 'there are two readKeys now' ) await p(keyring.group.pickWriteKey)(groupId, { key: key2, scheme: keySchemes.private_group }) await p(setTimeout)(500) t.equals(groupUpdates.length, 3, 'got info about new key pick') t.equals( groupUpdates[2].writeKey.key.toString('base64'), key2.toString('base64'), 'the new writeKey is the one we switched to' ) await p(keyring.group.exclude)(groupId) await p(setTimeout)(500) t.deepEquals(groupUpdates[3].excluded, true, 'got live update about exclusion') const excludedUpdate = await pull( keyring.group.getUpdates(groupId), pull.take(1), pull.collectAsPromise() ) t.equals(excludedUpdate[0].excluded, true, 'got old update about exclusion') }) test('migrate from old db format works', async t => { const path = tmpPath() const groupId = GroupId() const key = GroupKey() const groupInfo = { key, scheme: keySchemes.private_group } const root = MsgId() const feedKeys = ssbKeys.generate() const oldKeyring = await p(OldKeyring)(path, feedKeys) await p(oldKeyring.group.register)(groupId, { key, root }) t.deepEqual(oldKeyring.group.get(groupId), { ...groupInfo, root }, 'group stored in old format' ) await p(oldKeyring.close)() const keyring = await Keyring(path) t.deepEqual(keyring.group.get(groupId), { writeKey: groupInfo, readKeys: [groupInfo], root }, 'group stored in old format has gotten converted' ) })