From 6bd7f5bc14acc04d83fc2deb76690942119ff11d Mon Sep 17 00:00:00 2001 From: Andrei Regiani Date: Thu, 28 Nov 2024 06:19:33 +0100 Subject: [PATCH] New collections `permits` and `encryption-keys` --- hyperdb/db/db.json | 26 +++++++++ hyperdb/db/index.js | 112 +++++++++++++++++++++++++++++++++++- hyperdb/db/messages.js | 93 +++++++++++++++++++++++++++--- hyperdb/generate.js | 39 +++++++++++++ hyperdb/schema/index.js | 42 ++++++++++++++ hyperdb/schema/schema.json | 34 +++++++++++ subsystems/sidecar/index.js | 35 ++++------- 7 files changed, 349 insertions(+), 32 deletions(-) diff --git a/hyperdb/db/db.json b/hyperdb/db/db.json index 9c6afbd3b..5a0ad6738 100644 --- a/hyperdb/db/db.json +++ b/hyperdb/db/db.json @@ -23,6 +23,32 @@ "derived": false, "key": [], "trigger": null + }, + { + "name": "permits", + "namespace": "pear", + "id": 2, + "type": 1, + "indexes": [], + "schema": "@pear/permits", + "derived": false, + "key": [ + "key" + ], + "trigger": null + }, + { + "name": "encryption-keys", + "namespace": "pear", + "id": 3, + "type": 1, + "indexes": [], + "schema": "@pear/encryption-keys", + "derived": false, + "key": [ + "key" + ], + "trigger": null } ] } \ No newline at end of file diff --git a/hyperdb/db/index.js b/hyperdb/db/index.js index 0ba3a416e..ada1aa40e 100644 --- a/hyperdb/db/index.js +++ b/hyperdb/db/index.js @@ -91,11 +91,119 @@ const collection1 = { indexes: [] } +// '@pear/permits' collection key +const collection2_key = new IndexEncoder([ + IndexEncoder.BUFFER +], { prefix: 2 }) + +function collection2_indexify (record) { + const a = record.key + return a === undefined ? [] : [a] +} + +// '@pear/permits' reconstruction function +function collection2_reconstruct (version, keyBuf, valueBuf) { + const key = collection2_key.decode(keyBuf) + const value = c.decode(resolveStruct('@pear/permits/value', version), valueBuf) + // TODO: This should be fully code generated + return { + key: key[0], + ...value + } +} +// '@pear/permits' key reconstruction function +function collection2_reconstruct_key (keyBuf) { + const key = collection2_key.decode(keyBuf) + return { + key: key[0] + } +} + +// '@pear/permits' +const collection2 = { + name: '@pear/permits', + id: 2, + encodeKey (record) { + const key = [record.key] + return collection2_key.encode(key) + }, + encodeKeyRange ({ gt, lt, gte, lte } = {}) { + return collection2_key.encodeRange({ + gt: gt ? collection2_indexify(gt) : null, + lt: lt ? collection2_indexify(lt) : null, + gte: gte ? collection2_indexify(gte) : null, + lte: lte ? collection2_indexify(lte) : null + }) + }, + encodeValue (version, record) { + return c.encode(resolveStruct('@pear/permits/value', version), record) + }, + trigger: null, + reconstruct: collection2_reconstruct, + reconstructKey: collection2_reconstruct_key, + indexes: [] +} + +// '@pear/encryption-keys' collection key +const collection3_key = new IndexEncoder([ + IndexEncoder.BUFFER +], { prefix: 3 }) + +function collection3_indexify (record) { + const a = record.key + return a === undefined ? [] : [a] +} + +// '@pear/encryption-keys' reconstruction function +function collection3_reconstruct (version, keyBuf, valueBuf) { + const key = collection3_key.decode(keyBuf) + const value = c.decode(resolveStruct('@pear/encryption-keys/value', version), valueBuf) + // TODO: This should be fully code generated + return { + key: key[0], + ...value + } +} +// '@pear/encryption-keys' key reconstruction function +function collection3_reconstruct_key (keyBuf) { + const key = collection3_key.decode(keyBuf) + return { + key: key[0] + } +} + +// '@pear/encryption-keys' +const collection3 = { + name: '@pear/encryption-keys', + id: 3, + encodeKey (record) { + const key = [record.key] + return collection3_key.encode(key) + }, + encodeKeyRange ({ gt, lt, gte, lte } = {}) { + return collection3_key.encodeRange({ + gt: gt ? collection3_indexify(gt) : null, + lt: lt ? collection3_indexify(lt) : null, + gte: gte ? collection3_indexify(gte) : null, + lte: lte ? collection3_indexify(lte) : null + }) + }, + encodeValue (version, record) { + return c.encode(resolveStruct('@pear/encryption-keys/value', version), record) + }, + trigger: null, + reconstruct: collection3_reconstruct, + reconstructKey: collection3_reconstruct_key, + indexes: [] +} + module.exports = { version, collections: [ collection0, - collection1 + collection1, + collection2, + collection3 ], indexes: [ ], @@ -107,6 +215,8 @@ function resolveCollection (name) { switch (name) { case '@pear/bundle': return collection0 case '@pear/dht': return collection1 + case '@pear/permits': return collection2 + case '@pear/encryption-keys': return collection3 default: return null } } diff --git a/hyperdb/db/messages.js b/hyperdb/db/messages.js index 821728ba6..cd8b4d83c 100644 --- a/hyperdb/db/messages.js +++ b/hyperdb/db/messages.js @@ -78,8 +78,48 @@ const encoding2 = { } } -// @pear/bundle/value +// @pear/permits const encoding3 = { + preencode (state, m) { + c.fixed32.preencode(state, m.key) + }, + encode (state, m) { + c.fixed32.encode(state, m.key) + }, + decode (state) { + const res = {} + res.key = null + + res.key = c.fixed32.decode(state) + + return res + } +} + +// @pear/encryption-keys +const encoding4 = { + preencode (state, m) { + c.fixed32.preencode(state, m.key) + c.string.preencode(state, m.encryptionKey) + }, + encode (state, m) { + c.fixed32.encode(state, m.key) + c.string.encode(state, m.encryptionKey) + }, + decode (state) { + const res = {} + res.key = null + res.encryptionKey = null + + res.key = c.fixed32.decode(state) + res.encryptionKey = c.string.decode(state) + + return res + } +} + +// @pear/bundle/value +const encoding5 = { preencode (state, m) { }, @@ -94,17 +134,17 @@ const encoding3 = { } // @pear/dht/value.nodes -const encoding4_0 = c.frame(c.array(encoding0)) +const encoding6_0 = c.frame(c.array(encoding0)) // @pear/dht/value -const encoding4 = { +const encoding6 = { preencode (state, m) { let flags = 0 if (m.nodes) flags |= 1 c.uint.preencode(state, flags) - if (m.nodes) encoding4_0.preencode(state, m.nodes) + if (m.nodes) encoding6_0.preencode(state, m.nodes) }, encode (state, m) { let flags = 0 @@ -112,14 +152,47 @@ const encoding4 = { c.uint.encode(state, flags) - if (m.nodes) encoding4_0.encode(state, m.nodes) + if (m.nodes) encoding6_0.encode(state, m.nodes) }, decode (state) { const res = {} res.nodes = null const flags = state.start < state.end ? c.uint.decode(state) : 0 - if ((flags & 1) !== 0) res.nodes = encoding4_0.decode(state) + if ((flags & 1) !== 0) res.nodes = encoding6_0.decode(state) + + return res + } +} + +// @pear/permits/value +const encoding7 = { + preencode (state, m) { + + }, + encode (state, m) { + + }, + decode (state) { + const res = {} + + return res + } +} + +// @pear/encryption-keys/value +const encoding8 = { + preencode (state, m) { + c.string.preencode(state, m.encryptionKey) + }, + encode (state, m) { + c.string.encode(state, m.encryptionKey) + }, + decode (state) { + const res = {} + res.encryptionKey = null + + res.encryptionKey = c.string.decode(state) return res } @@ -130,8 +203,12 @@ function getStructByName (name) { case '@pear/node': return encoding0 case '@pear/bundle': return encoding1 case '@pear/dht': return encoding2 - case '@pear/bundle/value': return encoding3 - case '@pear/dht/value': return encoding4 + case '@pear/permits': return encoding3 + case '@pear/encryption-keys': return encoding4 + case '@pear/bundle/value': return encoding5 + case '@pear/dht/value': return encoding6 + case '@pear/permits/value': return encoding7 + case '@pear/encryption-keys/value': return encoding8 default: throw new Error('Encoder not found ' + name) } } diff --git a/hyperdb/generate.js b/hyperdb/generate.js index 7ca6fadaa..d73334907 100644 --- a/hyperdb/generate.js +++ b/hyperdb/generate.js @@ -43,6 +43,33 @@ pearSchema.register({ ] }) +pearSchema.register({ + name: 'permits', + fields: [ + { + name: 'key', + type: 'fixed32', + required: true + } + ] +}) + +pearSchema.register({ + name: 'encryption-keys', + fields: [ + { + name: 'key', + type: 'fixed32', + required: true + }, + { + name: 'encryptionKey', + type: 'string', + required: true + } + ] +}) + Hyperschema.toDisk(schema) // hyperdb/db @@ -59,4 +86,16 @@ pearDB.collections.register({ schema: '@pear/dht' }) +pearDB.collections.register({ + name: 'permits', + schema: '@pear/permits', + key: ['key'] +}) + +pearDB.collections.register({ + name: 'encryption-keys', + schema: '@pear/encryption-keys', + key: ['key'] +}) + Builder.toDisk(db) diff --git a/hyperdb/schema/index.js b/hyperdb/schema/index.js index 5ff5685d2..bc6a672a5 100644 --- a/hyperdb/schema/index.js +++ b/hyperdb/schema/index.js @@ -78,11 +78,53 @@ const encoding2 = { } } +// @pear/permits +const encoding3 = { + preencode (state, m) { + c.fixed32.preencode(state, m.key) + }, + encode (state, m) { + c.fixed32.encode(state, m.key) + }, + decode (state) { + const res = {} + res.key = null + + res.key = c.fixed32.decode(state) + + return res + } +} + +// @pear/encryption-keys +const encoding4 = { + preencode (state, m) { + c.fixed32.preencode(state, m.key) + c.string.preencode(state, m.encryptionKey) + }, + encode (state, m) { + c.fixed32.encode(state, m.key) + c.string.encode(state, m.encryptionKey) + }, + decode (state) { + const res = {} + res.key = null + res.encryptionKey = null + + res.key = c.fixed32.decode(state) + res.encryptionKey = c.string.decode(state) + + return res + } +} + function getStructByName (name) { switch (name) { case '@pear/node': return encoding0 case '@pear/bundle': return encoding1 case '@pear/dht': return encoding2 + case '@pear/permits': return encoding3 + case '@pear/encryption-keys': return encoding4 default: throw new Error('Encoder not found ' + name) } } diff --git a/hyperdb/schema/schema.json b/hyperdb/schema/schema.json index 9601b791f..5f1b44539 100644 --- a/hyperdb/schema/schema.json +++ b/hyperdb/schema/schema.json @@ -41,6 +41,40 @@ "version": 1 } ] + }, + { + "name": "permits", + "namespace": "pear", + "compact": false, + "flagsPosition": -1, + "fields": [ + { + "name": "key", + "required": true, + "type": "fixed32", + "version": 1 + } + ] + }, + { + "name": "encryption-keys", + "namespace": "pear", + "compact": false, + "flagsPosition": -1, + "fields": [ + { + "name": "key", + "required": true, + "type": "fixed32", + "version": 1 + }, + { + "name": "encryptionKey", + "required": true, + "type": "string", + "version": 1 + } + ] } ] } \ No newline at end of file diff --git a/subsystems/sidecar/index.js b/subsystems/sidecar/index.js index 3cc22e603..2922807e4 100644 --- a/subsystems/sidecar/index.js +++ b/subsystems/sidecar/index.js @@ -40,7 +40,6 @@ const { ERR_INTERNAL_ERROR, ERR_PERMISSION_REQUIRED } = require('../../errors') const definition = require('../../hyperdb/db') const db = HyperDB.rocks(PLATFORM_HYPERDB, definition) const identity = new Store('identity') -const encryptionKeys = new Store('encryption-keys') const SharedState = require('../../state') const State = require('./state') const { preferences } = State @@ -56,14 +55,6 @@ const ops = { Touch: require('./ops/touch') } -class PermitStore extends Store { - async get (key) { - return await super.get(key) || await preferences.get(key) - } -} - -const permits = new PermitStore('permits') - // ensure that we are registered as a link handler registerUrlHandler(WAKEUP) @@ -458,23 +449,21 @@ class Sidecar extends ReadyResource { async permit (params) { if (params.password || params.encryptionKey) { - const encryptionKey = params.encryptionKey || await deriveEncryptionKey(params.password, SALT) - const encryptionKeys = await permits.get('encryption-keys') || {} - encryptionKeys[hypercoreid.normalize(params.key)] = encryptionKey.toString('hex') - await permits.set('encryption-keys', encryptionKeys) + const key = hypercoreid.normalize(params.key) + const encryptionKey = (params.encryptionKey || await deriveEncryptionKey(params.password, SALT)).toString('hex') + await db.insert('@pear/encryption-keys', { key, encryptionKey }) } - const trusted = new Set((await permits.get('trusted')) || []) if (params.key !== null) { const z32 = hypercoreid.encode(params.key) - trusted.add(z32) + await db.insert('@pear/permits', { key: z32 }) } - return permits.set('trusted', Array.from(trusted)) + + return true // legacy from ```return permits.set('trusted', Array.from(trusted))``` returned true } async trusted (key) { const z32 = hypercoreid.encode(key) - const trusted = await permits.get('trusted') || [] - return trusted.includes(z32) + return !!(await db.get('@pear/permits', { key: z32 })) } async detached ({ link, key, storage, appdev }) { @@ -692,21 +681,21 @@ class Sidecar extends ReadyResource { if (unsafeClearPreferences) { LOG.info(LOG_RUN_LINK, 'clearing preferences') await preferences.clear() - await permits.clear() + await db.delete('@pear/permits', {}) + await db.flush() } let encryptionKey if (flags.encryptionKey) { LOG.info(LOG_RUN_LINK, id, 'getting encryption key per flag') - encryptionKey = (await encryptionKeys.get(flags.encryptionKey)) + encryptionKey = await db.get('@pear/encryption-keys', { key: flags.encryptionKey }) encryptionKey = encryptionKey ? Buffer.from(encryptionKey, 'hex') : null } else { const { drive } = parseLink(link) let storedEncryptedKey if (drive.key) { LOG.info(LOG_RUN_LINK, id, 'loading encryption keys') - const encryptionKeys = await permits.get('encryption-keys') || {} - storedEncryptedKey = encryptionKeys[hypercoreid.normalize(drive.key)] + storedEncryptedKey = await db.get('@pear/encryption-keys', { key: hypercoreid.normalize(drive.key) }) } else { storedEncryptedKey = null } @@ -771,7 +760,7 @@ class Sidecar extends ReadyResource { if (!flags.trusted) { const aliases = Object.values(ALIASES).map(hypercoreid.encode) LOG.info(LOG_RUN_LINK, id, 'loading trusted links') - const trusted = new Set([...aliases, ...((await permits.get('trusted')) || [])]) + const trusted = new Set([...aliases, ...(await db.get('@pear/permits').toArray() || [])]) const z32 = hypercoreid.encode(state.key) if (trusted.has(z32) === false) { const err = new ERR_PERMISSION_REQUIRED('Permission required to run key', { key: state.key })