feat: Encrypt either data, payload or both.

This commit is contained in:
Hayden Young 2024-05-21 23:28:29 +01:00
parent cce6a524e2
commit ac3011c873
10 changed files with 125 additions and 23 deletions

View File

@ -39,10 +39,12 @@ const defaultCacheSize = 1000
* automatically. Otherwise, false. * automatically. Otherwise, false.
* @param {function} [params.onUpdate] A function callback. Fired when an * @param {function} [params.onUpdate] A function callback. Fired when an
* entry is added to the oplog. * entry is added to the oplog.
* @param {Function} options.encryptFn An encryption function.
* @param {Function} options.decryptFn A decryption function.
* @return {module:Databases~Database} An instance of Database. * @return {module:Databases~Database} An instance of Database.
* @instance * @instance
*/ */
const Database = async ({ ipfs, identity, address, name, access, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically, onUpdate }) => { const Database = async ({ ipfs, identity, address, name, access, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically, onUpdate, encryptFn, decryptFn }) => {
/** /**
* @namespace module:Databases~Database * @namespace module:Databases~Database
* @description The instance returned by {@link module:Database~Database}. * @description The instance returned by {@link module:Database~Database}.
@ -108,7 +110,7 @@ const Database = async ({ ipfs, identity, address, name, access, directory, meta
await LevelStorage({ path: pathJoin(directory, '/log/_index/') }) await LevelStorage({ path: pathJoin(directory, '/log/_index/') })
) )
const log = await Log(identity, { logId: address, access, entryStorage, headsStorage, indexStorage }) const log = await Log(identity, { logId: address, access, entryStorage, headsStorage, indexStorage, encryptFn, decryptFn })
const events = new EventEmitter() const events = new EventEmitter()

View File

@ -25,8 +25,8 @@ const DefaultOptions = { indexBy: '_id' }
* @return {module:Databases.Databases-Documents} A Documents function. * @return {module:Databases.Databases-Documents} A Documents function.
* @memberof module:Databases * @memberof module:Databases
*/ */
const Documents = ({ indexBy } = DefaultOptions) => async ({ ipfs, identity, address, name, access, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically, onUpdate }) => { const Documents = ({ indexBy } = DefaultOptions) => async ({ ipfs, identity, address, name, access, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically, onUpdate, encryptFn, decryptFn }) => {
const database = await Database({ ipfs, identity, address, name, access, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically }) const database = await Database({ ipfs, identity, address, name, access, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically, encryptFn, decryptFn })
const { addOperation, log } = database const { addOperation, log } = database

View File

@ -7,6 +7,8 @@
* @augments module:Databases~Database * @augments module:Databases~Database
*/ */
import Database from '../database.js' import Database from '../database.js'
import { Operation } from './utils/operation.js'
import { Payload } from './utils/payload.js'
const type = 'events' const type = 'events'
@ -15,8 +17,8 @@ const type = 'events'
* @return {module:Databases.Databases-Events} A Events function. * @return {module:Databases.Databases-Events} A Events function.
* @memberof module:Databases * @memberof module:Databases
*/ */
const Events = () => async ({ ipfs, identity, address, name, access, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically, onUpdate }) => { const Events = () => async ({ ipfs, identity, address, name, access, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically, onUpdate, encryptFn, decryptFn }) => {
const database = await Database({ ipfs, identity, address, name, access, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically, onUpdate }) const database = await Database({ ipfs, identity, address, name, access, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically, onUpdate, encryptFn, decryptFn })
const { addOperation, log } = database const { addOperation, log } = database
@ -29,7 +31,8 @@ const Events = () => async ({ ipfs, identity, address, name, access, directory,
* @instance * @instance
*/ */
const add = async (value) => { const add = async (value) => {
return addOperation({ op: 'ADD', key: null, value }) const op = await Operation('ADD', null, value, { encryptFn, encryptValue: true, encryptOp: false })
return addOperation(op)
} }
/** /**
@ -42,7 +45,9 @@ const Events = () => async ({ ipfs, identity, address, name, access, directory,
*/ */
const get = async (hash) => { const get = async (hash) => {
const entry = await log.get(hash) const entry = await log.get(hash)
return entry.payload.value const { value } = await Payload(entry.payload, { decryptFn, decryptValue: true, decryptOp: false })
return value
} }
/** /**

View File

@ -109,7 +109,7 @@ const Index = ({ directory } = {}) => async () => {
* function. * function.
* @memberof module:Databases * @memberof module:Databases
*/ */
const KeyValueIndexed = () => async ({ ipfs, identity, address, name, access, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically, onUpdate }) => { const KeyValueIndexed = () => async ({ ipfs, identity, address, name, access, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically, onUpdate, encryptFn, decryptFn }) => {
// Set up the directory for an index // Set up the directory for an index
directory = pathJoin(directory || './orbitdb', `./${address}/_index/`) directory = pathJoin(directory || './orbitdb', `./${address}/_index/`)
@ -117,7 +117,7 @@ const KeyValueIndexed = () => async ({ ipfs, identity, address, name, access, di
const index = await Index({ directory })() const index = await Index({ directory })()
// Set up the underlying KeyValue database // Set up the underlying KeyValue database
const keyValueStore = await KeyValue()({ ipfs, identity, address, name, access, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically, onUpdate: index.update }) const keyValueStore = await KeyValue()({ ipfs, identity, address, name, access, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically, onUpdate: index.update, encryptFn, decryptFn })
/** /**
* Gets a value from the store by key. * Gets a value from the store by key.

View File

@ -15,8 +15,8 @@ const type = 'keyvalue'
* @return {module:Databases.Databases-KeyValue} A KeyValue function. * @return {module:Databases.Databases-KeyValue} A KeyValue function.
* @memberof module:Databases * @memberof module:Databases
*/ */
const KeyValue = () => async ({ ipfs, identity, address, name, access, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically, onUpdate }) => { const KeyValue = () => async ({ ipfs, identity, address, name, access, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically, onUpdate, encryptFn, decryptFn }) => {
const database = await Database({ ipfs, identity, address, name, access, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically, onUpdate }) const database = await Database({ ipfs, identity, address, name, access, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically, onUpdate, encryptFn, decryptFn })
const { addOperation, log } = database const { addOperation, log } = database

View File

@ -0,0 +1,15 @@
export const Operation = async (op, key, value, { encryptFn, encryptValue, encryptOp } = {}) => {
let operation = { op, key, value }
if (encryptFn) {
if (encryptValue) {
operation.value = await encryptFn(value)
}
if (encryptOp) {
operation = await encryptFn(JSON.stringify(operation))
}
}
return operation
}

View File

@ -0,0 +1,15 @@
export const Payload = async (payload, { decryptFn, decryptValue, decryptOp }) => {
if (decryptFn) {
if (decryptOp) {
payload = JSON.parse(await decryptFn(payload))
}
if (decryptValue) {
payload.value = await decryptFn(payload.value)
}
}
const { op, key, value } = payload
return { op, key, value }
}

View File

@ -56,7 +56,7 @@ const DefaultAccessController = async () => {
* @memberof module:Log * @memberof module:Log
* @instance * @instance
*/ */
const Log = async (identity, { logId, logHeads, access, entryStorage, headsStorage, indexStorage, sortFn, encryptFn, decryptFn } = {}) => { const Log = async (identity, { logId, logHeads, access, entryStorage, headsStorage, indexStorage, sortFn } = {}) => {
/** /**
* @namespace Log * @namespace Log
* @description The instance returned by {@link module:Log} * @description The instance returned by {@link module:Log}
@ -139,10 +139,6 @@ const Log = async (identity, { logId, logHeads, access, entryStorage, headsStora
if (bytes) { if (bytes) {
const entry = await Entry.decode(bytes) const entry = await Entry.decode(bytes)
if (decryptFn) {
entry.payload = await decryptFn(entry.payload)
}
return entry return entry
} }
} }
@ -176,10 +172,6 @@ const Log = async (identity, { logId, logHeads, access, entryStorage, headsStora
// (skips the heads which are covered by the next field) // (skips the heads which are covered by the next field)
const refs = await getReferences(heads_, options.referencesCount + heads_.length) const refs = await getReferences(heads_, options.referencesCount + heads_.length)
if (encryptFn) {
data = await encryptFn(data)
}
// Create the entry // Create the entry
const entry = await Entry.create( const entry = await Entry.create(
identity, identity,

View File

@ -112,7 +112,7 @@ const OrbitDB = async ({ ipfs, id, identity, identities, directory } = {}) => {
* @instance * @instance
* @async * @async
*/ */
const open = async (address, { type, meta, sync, Database, AccessController, headsStorage, entryStorage, indexStorage, referencesCount } = {}) => { const open = async (address, { type, meta, sync, Database, AccessController, headsStorage, entryStorage, indexStorage, referencesCount, encryptFn, decryptFn } = {}) => {
let name, manifest, accessController let name, manifest, accessController
if (databases[address]) { if (databases[address]) {
@ -153,7 +153,7 @@ const OrbitDB = async ({ ipfs, id, identity, identities, directory } = {}) => {
address = address.toString() address = address.toString()
const db = await Database({ ipfs, identity, address, name, access: accessController, directory, meta, syncAutomatically: sync, headsStorage, entryStorage, indexStorage, referencesCount }) const db = await Database({ ipfs, identity, address, name, access: accessController, directory, meta, syncAutomatically: sync, headsStorage, entryStorage, indexStorage, referencesCount, encryptFn, decryptFn })
db.events.on('close', onDatabaseClosed(address)) db.events.on('close', onDatabaseClosed(address))

View File

@ -0,0 +1,73 @@
import { strictEqual } from 'assert'
import { rimraf } from 'rimraf'
import path from 'path'
import OrbitDB from '../src/orbitdb.js'
// import waitFor from './utils/wait-for.js'
import connectPeers from './utils/connect-nodes.js'
// import IPFSAccessController from '../src/access-controllers/ipfs.js'
// import OrbitDBAccessController from '../src/access-controllers/orbitdb.js'
import createHelia from './utils/create-helia.js'
import { encrypt, decrypt } from './utils/encrypt.js'
const dbPath = './orbitdb/tests/write-permissions'
describe('Encryption/Decryption', function () {
this.timeout(20000)
let ipfs1, ipfs2
let orbitdb1, orbitdb2
let db1 /*, db2 */
before(async () => {
[ipfs1, ipfs2] = await Promise.all([createHelia(), createHelia()])
await connectPeers(ipfs1, ipfs2)
orbitdb1 = await OrbitDB({ ipfs: ipfs1, id: 'user1', directory: path.join(dbPath, '1') })
orbitdb2 = await OrbitDB({ ipfs: ipfs2, id: 'user2', directory: path.join(dbPath, '2') })
})
after(async () => {
if (orbitdb1) {
await orbitdb1.stop()
}
if (orbitdb2) {
await orbitdb2.stop()
}
if (ipfs1) {
await ipfs1.stop()
}
if (ipfs2) {
await ipfs2.stop()
}
await rimraf('./orbitdb')
await rimraf('./ipfs1')
await rimraf('./ipfs2')
})
afterEach(async () => {
await db1.drop()
await db1.close()
// await db2.drop()
// await db2.close()
})
it('can encrypt/decrypt data', async () => {
const keystore = orbitdb1.keystore
const keys = await keystore.createKey('encryption-test')
const privateKey = await keystore.getKey('encryption-test')
const publicKey = await keystore.getPublic(keys)
const encryptFn = encrypt({ publicKey })
const decryptFn = decrypt({ privateKey })
db1 = await orbitdb1.open('encryption-test-1', { encryptFn, decryptFn })
const hash = await db1.add('record 1')
strictEqual(await db1.get(hash), 'record 1')
})
})