mirror of
https://github.com/orbitdb/orbitdb.git
synced 2025-10-07 22:57:07 +00:00
feat: Encrypt either data, payload or both.
This commit is contained in:
parent
cce6a524e2
commit
ac3011c873
@ -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()
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
15
src/databases/utils/operation.js
Normal file
15
src/databases/utils/operation.js
Normal 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
|
||||||
|
}
|
||||||
15
src/databases/utils/payload.js
Normal file
15
src/databases/utils/payload.js
Normal 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 }
|
||||||
|
}
|
||||||
@ -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,
|
||||||
|
|||||||
@ -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))
|
||||||
|
|
||||||
|
|||||||
73
test/orbitdb-encryption.test.js
Normal file
73
test/orbitdb-encryption.test.js
Normal 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')
|
||||||
|
})
|
||||||
|
})
|
||||||
Loading…
x
Reference in New Issue
Block a user