From d83bfa9fc8293ef1d1f59db1a090f70f09e178b5 Mon Sep 17 00:00:00 2001 From: haad Date: Sun, 7 Jul 2024 10:09:09 +0300 Subject: [PATCH] Refactor and add separate PasswordEncryption module --- .../encryption/aes-gcm-pbkdf2.js | 42 +- src/encryption/index.js | 6 + src/encryption/password.js | 29 ++ src/index.js | 4 + src/oplog/entry.js | 5 +- src/oplog/log.js | 2 +- src/oplog/oplog-index.js | 8 +- src/sync.js | 4 +- test/database.test.js | 59 ++- test/oplog/entry.test.js | 13 +- test/oplog/log.test.js | 75 ++-- test/oplog/replicate.test.js | 2 +- test/orbitdb-encryption.test.js | 388 ++++++++++++++---- test/sync.test.js | 22 +- 14 files changed, 465 insertions(+), 194 deletions(-) rename test/utils/encrypt.js => src/encryption/aes-gcm-pbkdf2.js (85%) create mode 100644 src/encryption/index.js create mode 100644 src/encryption/password.js diff --git a/test/utils/encrypt.js b/src/encryption/aes-gcm-pbkdf2.js similarity index 85% rename from test/utils/encrypt.js rename to src/encryption/aes-gcm-pbkdf2.js index 1a97d16..360d99c 100644 --- a/test/utils/encrypt.js +++ b/src/encryption/aes-gcm-pbkdf2.js @@ -1,41 +1,22 @@ -import crypto from 'crypto' - -// From: -// https://github.com/libp2p/js-libp2p/blob/0b55625d146940994a306101650a55ee58e32f6c/packages/crypto/src/ciphers/aes-gcm.browser.ts - -import { concat } from 'uint8arrays/concat' -import { fromString } from 'uint8arrays/from-string' -// import { create } from '@libp2p/crypto/ciphers/aes-gcm' - /* - Sources: + Source: + https://github.com/libp2p/js-libp2p/blob/0b55625d146940994a306101650a55ee58e32f6c/packages/crypto/src/ciphers/aes-gcm.browser.ts + + More information: - https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto - https://github.com/bradyjoslin/webcrypto-example/blob/master/script.js - https://github.com/mdn/dom-examples/blob/main/web-crypto/encrypt-decrypt/aes-gcm.js - - https://github.com/libp2p/js-libp2p/blob/0b55625d146940994a306101650a55ee58e32f6c/packages/crypto/src/ciphers/aes-gcm.browser.ts */ -const cipher = createAes() +// import crypto from 'crypto' +import { concat } from 'uint8arrays/concat' +import { fromString } from 'uint8arrays/from-string' -const encrypt = ({ password }) => async (value) => { - return cipher.encrypt(value, password) +// Polyfill fix for browsers +const getCrypto = () => { + return global.crypto } -const decrypt = ({ password }) => async (value) => { - return cipher.decrypt(value, password) -} - -const generatePassword = async (length = 256) => { - return crypto.getRandomValues(new Uint8Array(length)) -} - -export { - encrypt, - decrypt, - generatePassword -} -// import webcrypto from '../webcrypto.js'; - // WebKit on Linux does not support deriving a key from an empty PBKDF2 key. // So, as a workaround, we provide the generated key as a constant. We test that // this generated key is accurate in test/workaround.spec.ts @@ -50,7 +31,7 @@ export const derivedEmptyPasswordKey = { alg: 'A128GCM', ext: true, k: 'scm9jmO_ // Based off of code from https://github.com/luke-park/SecureCompatibleEncryptionExamples -export function createAes (opts) { +export function AES (opts) { const algorithm = opts?.algorithm ?? 'AES-GCM' let keyLength = opts?.keyLength ?? 16 const nonceLength = opts?.nonceLength ?? 12 @@ -58,6 +39,7 @@ export function createAes (opts) { const saltLength = opts?.saltLength ?? 16 const iterations = opts?.iterations ?? 32767 // const crypto = webcrypto.get(); + const crypto = getCrypto() keyLength *= 8 // Browser crypto uses bits instead of bytes /** * Uses the provided password to derive a pbkdf2 key. The key diff --git a/src/encryption/index.js b/src/encryption/index.js new file mode 100644 index 0000000..8c752c3 --- /dev/null +++ b/src/encryption/index.js @@ -0,0 +1,6 @@ +/** + * @module Encryption + * @description + * Encryption modules for OrbitDB. + */ +export { default as PasswordEncryption } from './password.js' diff --git a/src/encryption/password.js b/src/encryption/password.js new file mode 100644 index 0000000..8896f82 --- /dev/null +++ b/src/encryption/password.js @@ -0,0 +1,29 @@ +/** + * @namespace Encryption-Password + * @memberof module:Encryption + * @description + * Password encryption module encrypts data using AES-GCM PBKDF2. + */ + +import { AES } from './aes-gcm-pbkdf2.js' + +const PasswordEncryption = async ({ password, aesOptions }) => { + aesOptions = aesOptions || {} + + const aes = AES(aesOptions) + + const encrypt = (value) => { + return aes.encrypt(value, password) + } + + const decrypt = (value) => { + return aes.decrypt(value, password) + } + + return { + encrypt, + decrypt + } +} + +export default PasswordEncryption diff --git a/src/index.js b/src/index.js index ec833ca..4b6e0f5 100644 --- a/src/index.js +++ b/src/index.js @@ -41,3 +41,7 @@ export { MemoryStorage, ComposedStorage } from './storage/index.js' + +export { + PasswordEncryption +} from './encryption/index.js' diff --git a/src/oplog/entry.js b/src/oplog/entry.js index 1183383..035b235 100644 --- a/src/oplog/entry.js +++ b/src/oplog/entry.js @@ -86,7 +86,10 @@ const create = async (identity, id, payload, encryptPayloadFn, clock = null, nex entry.identity = identity.hash entry.sig = signature entry.payload = payload - entry._payload = encryptedPayload + + if (encryptPayloadFn) { + entry._payload = encryptedPayload + } return entry } diff --git a/src/oplog/log.js b/src/oplog/log.js index 51cf0b5..87cc287 100644 --- a/src/oplog/log.js +++ b/src/oplog/log.js @@ -70,7 +70,7 @@ const Log = async (identity, { logId, logHeads, access, entryStorage, headsStora // Encryption of entries and payloads encryption = encryption || {} - const { encryptPayloadFn } = encryption + const encryptPayloadFn = encryption.data?.encrypt // Access Controller access = access || await DefaultAccessController() diff --git a/src/oplog/oplog-index.js b/src/oplog/oplog-index.js index 4ded634..5e4dc2d 100644 --- a/src/oplog/oplog-index.js +++ b/src/oplog/oplog-index.js @@ -6,9 +6,11 @@ import MemoryStorage from '../storage/memory.js' const DefaultStorage = MemoryStorage const OplogIndex = async ({ logHeads, entryStorage, headsStorage, indexStorage, encryption }) => { - encryption = encryption || {} - const { encryptPayloadFn, decryptPayloadFn, encryptEntryFn, decryptEntryFn } = encryption - + // Setup encryption and decryption functions + const encryptEntryFn = encryption?.replication?.encrypt + const decryptEntryFn = encryption?.replication?.decrypt + const encryptPayloadFn = encryption?.data?.encrypt + const decryptPayloadFn = encryption?.data?.decrypt // Oplog entry storage const _entries = entryStorage || await DefaultStorage() // Entry index for keeping track which entries are already in the log diff --git a/src/sync.js b/src/sync.js index 21fe4c5..5d9639e 100644 --- a/src/sync.js +++ b/src/sync.js @@ -158,7 +158,7 @@ const Sync = async ({ ipfs, log, events, onSynced, start, timeout }) => { for await (const value of source) { const headBytes = value.subarray() if (headBytes && onSynced) { - const entry = await Entry.decode(headBytes, log.encryption.decryptEntryFn, log.encryption.decryptPayloadFn) + const entry = await Entry.decode(headBytes, log.encryption.replication?.decrypt, log.encryption.data?.decrypt) await onSynced(entry) } } @@ -223,7 +223,7 @@ const Sync = async ({ ipfs, log, events, onSynced, start, timeout }) => { const task = async () => { try { if (data && onSynced) { - const entry = await Entry.decode(data, log.encryption.decryptEntryFn, log.encryption.decryptPayloadFn) + const entry = await Entry.decode(data, log.encryption.replication?.decrypt, log.encryption.data?.decrypt) await onSynced(entry) } } catch (e) { diff --git a/test/database.test.js b/test/database.test.js index 5677b58..700eb8d 100644 --- a/test/database.test.js +++ b/test/database.test.js @@ -1,9 +1,9 @@ -import { strictEqual, deepStrictEqual } from 'assert' +import { strictEqual, deepStrictEqual, notEqual } from 'assert' import { rimraf } from 'rimraf' import { existsSync } from 'fs' import { copy } from 'fs-extra' import Path from 'path' -import { Database, Entry, KeyStore, Identities } from '../src/index.js' +import { Database, KeyStore, Identities } from '../src/index.js' import LevelStorage from '../src/storage/level.js' import MemoryStorage from '../src/storage/memory.js' import testKeysPath from './fixtures/test-keys-path.js' @@ -68,8 +68,12 @@ describe('Database', function () { describe('Options', () => { it('uses default directory for headsStorage', async () => { db = await Database({ ipfs, identity: testIdentity, address: databaseId, accessController }) - const op = { op: 'PUT', key: 1, value: 'record 1 on db 1' } - const hash = await db.addOperation(op) + + const op1 = { op: 'PUT', key: 1, value: 'record 1 on db 1 version 1' } + const op2 = { op: 'PUT', key: 1, value: 'record 1 on db 1 version 2' } + + await db.addOperation(op1) + const hash = await db.addOperation(op2) const headsPath = Path.join('./orbitdb/', `${databaseId}/`, '/log/_heads/') @@ -79,7 +83,9 @@ describe('Database', function () { const headsStorage = await LevelStorage({ path: headsPath }) - deepStrictEqual((await Entry.decode(await headsStorage.get(hash))).payload, op) + const bytes = Uint8Array.from(await headsStorage.get(hash)) + + notEqual(bytes.length, 0) await headsStorage.close() @@ -88,8 +94,11 @@ describe('Database', function () { it('uses given directory for headsStorage', async () => { db = await Database({ ipfs, identity: testIdentity, address: databaseId, accessController, directory: './custom-directory' }) - const op = { op: 'PUT', key: 1, value: 'record 1 on db 1' } - const hash = await db.addOperation(op) + const op1 = { op: 'PUT', key: 1, value: 'record 1 on db 1 version 1' } + const op2 = { op: 'PUT', key: 1, value: 'record 1 on db 1 version 2' } + + await db.addOperation(op1) + const hash = await db.addOperation(op2) const headsPath = Path.join('./custom-directory/', `${databaseId}/`, '/log/_heads/') @@ -99,7 +108,9 @@ describe('Database', function () { const headsStorage = await LevelStorage({ path: headsPath }) - deepStrictEqual((await Entry.decode(await headsStorage.get(hash))).payload, op) + const bytes = Uint8Array.from(await headsStorage.get(hash)) + + notEqual(bytes.length, 0) await headsStorage.close() @@ -110,23 +121,41 @@ describe('Database', function () { it('uses given MemoryStorage for headsStorage', async () => { const headsStorage = await MemoryStorage() db = await Database({ ipfs, identity: testIdentity, address: databaseId, accessController, directory: './orbitdb', headsStorage }) - const op = { op: 'PUT', key: 1, value: 'record 1 on db 1' } - const hash = await db.addOperation(op) + const op1 = { op: 'PUT', key: 1, value: 'record 1 on db 1 version 1' } + const op2 = { op: 'PUT', key: 1, value: 'record 1 on db 1 version 2' } - deepStrictEqual((await Entry.decode(await headsStorage.get(hash))).payload, op) + await db.addOperation(op1) + const hash = await db.addOperation(op2) + + const bytes = Uint8Array.from(await headsStorage.get(hash)) + + notEqual(bytes.length, 0) await db.close() + + await headsStorage.close() + await rimraf('./orbitdb') }) it('uses given MemoryStorage for entryStorage', async () => { const entryStorage = await MemoryStorage() - db = await Database({ ipfs, identity: testIdentity, address: databaseId, accessController, directory: './orbitdb', entryStorage }) - const op = { op: 'PUT', key: 1, value: 'record 1 on db 1' } - const hash = await db.addOperation(op) + const headsStorage = await MemoryStorage() + db = await Database({ ipfs, identity: testIdentity, address: databaseId, accessController, directory: './orbitdb', headsStorage, entryStorage }) + const op1 = { op: 'PUT', key: 1, value: 'record 1 on db 1 version 1' } + const op2 = { op: 'PUT', key: 1, value: 'record 1 on db 1 version 2' } - deepStrictEqual((await Entry.decode(await entryStorage.get(hash))).payload, op) + await db.addOperation(op1) + const hash = await db.addOperation(op2) + + const e = await entryStorage.get(hash) + const bytes = Uint8Array.from(e) + notEqual(bytes.length, 0) await db.close() + + await entryStorage.close() + await headsStorage.close() + await rimraf('./orbitdb') }) }) diff --git a/test/oplog/entry.test.js b/test/oplog/entry.test.js index be4d835..5dc6195 100644 --- a/test/oplog/entry.test.js +++ b/test/oplog/entry.test.js @@ -33,7 +33,8 @@ describe('Entry', function () { it('creates a an empty entry', async () => { const expectedHash = 'zdpuAsKzwUEa8cz9pkJxxFMxLuP3cutA9PDGoLZytrg4RSVEa' const entry = await create(testIdentity, 'A', 'hello') - strictEqual(entry.hash, expectedHash) + const { hash } = await Entry.encode(entry) + strictEqual(hash, expectedHash) strictEqual(entry.id, 'A') strictEqual(entry.clock.id, testIdentity.publicKey) strictEqual(entry.clock.time, 0) @@ -47,7 +48,8 @@ describe('Entry', function () { const expectedHash = 'zdpuAmthfqpHRQjdSpKN5etr1GrreJb7QcU1Hshm6pERnzsxi' const payload = 'hello world' const entry = await create(testIdentity, 'A', payload) - strictEqual(entry.hash, expectedHash) + const { hash } = await Entry.encode(entry) + strictEqual(hash, expectedHash) strictEqual(entry.payload, payload) strictEqual(entry.id, 'A') strictEqual(entry.clock.id, testIdentity.publicKey) @@ -81,7 +83,7 @@ describe('Entry', function () { const payload2 = 'hello again' const entry1 = await create(testIdentity, 'A', payload1) entry1.clock = tickClock(entry1.clock) - const entry2 = await create(testIdentity, 'A', payload2, entry1.clock, [entry1]) + const entry2 = await create(testIdentity, 'A', payload2, null, entry1.clock, [entry1]) strictEqual(entry2.payload, payload2) strictEqual(entry2.next.length, 1) // strictEqual(entry2.hash, expectedHash) @@ -91,7 +93,8 @@ describe('Entry', function () { it('`next` parameter can be an array of strings', async () => { const entry1 = await create(testIdentity, 'A', 'hello1') - const entry2 = await create(testIdentity, 'A', 'hello2', null, [entry1.hash]) + const { hash } = await Entry.encode(entry1) + const entry2 = await create(testIdentity, 'A', 'hello2', null, null, [hash]) strictEqual(typeof entry2.next[0] === 'string', true) }) @@ -138,7 +141,7 @@ describe('Entry', function () { it('throws an error if next is not an array', async () => { let err try { - await create(testIdentity, 'A', 'hello', null, {}) + await create(testIdentity, 'A', 'hello', null, null, {}) } catch (e) { err = e } diff --git a/test/oplog/log.test.js b/test/oplog/log.test.js index 043c970..9467125 100644 --- a/test/oplog/log.test.js +++ b/test/oplog/log.test.js @@ -3,7 +3,6 @@ import { rimraf } from 'rimraf' import { copy } from 'fs-extra' import { Log, Entry, Identities, KeyStore, MemoryStorage } from '../../src/index.js' import testKeysPath from '../fixtures/test-keys-path.js' -import { encrypt, decrypt } from '../utils/encrypt.js' const { create } = Entry @@ -61,15 +60,21 @@ describe('Log', function () { }) it('sets one head if multiple are given as params', async () => { - const one = await create(testIdentity, 'A', 'entryA', null, []) - const two = await create(testIdentity, 'A', 'entryB', null, [one.hash]) - const three = await create(testIdentity, 'A', 'entryC', null, [two.hash]) - const four = await create(testIdentity, 'A', 'entryD', null, [two.hash]) + const one = await create(testIdentity, 'A', 'entryA', null, null, []) + const { hash: hash1, bytes: bytes1 } = await Entry.encode(one) + const two = await create(testIdentity, 'A', 'entryB', null, null, [hash1]) + const { hash: hash2, bytes: bytes2 } = await Entry.encode(two) + const three = await create(testIdentity, 'A', 'entryC', null, null, [hash2]) + const { hash: hash3, bytes: bytes3 } = await Entry.encode(three) + const four = await create(testIdentity, 'A', 'entryD', null, null, [hash3]) + const { hash: hash4, bytes: bytes4 } = await Entry.encode(four) const entryStorage = await MemoryStorage() - await entryStorage.put(one.hash, one.bytes) - await entryStorage.put(two.hash, two.bytes) - await entryStorage.put(three.hash, three.bytes) - await entryStorage.put(four.hash, four.bytes) + await entryStorage.put(hash1, bytes1) + await entryStorage.put(hash2, bytes2) + await entryStorage.put(hash3, bytes3) + await entryStorage.put(hash4, bytes4) + three.hash = hash3 + two.hash = hash2 const log = await Log(testIdentity, { logId: 'A', logHeads: [three, three, two, two], entryStorage }) const values = await log.values() const heads = await log.heads() @@ -79,15 +84,22 @@ describe('Log', function () { }) it('sets two heads if two given as params', async () => { - const one = await create(testIdentity, 'A', 'entryA', null, []) - const two = await create(testIdentity, 'A', 'entryB', null, [one.hash]) - const three = await create(testIdentity, 'A', 'entryC', null, [two.hash]) - const four = await create(testIdentity, 'A', 'entryD', null, [two.hash]) + const one = await create(testIdentity, 'A', 'entryA', null, null, []) + const { hash: hash1, bytes: bytes1 } = await Entry.encode(one) + const two = await create(testIdentity, 'A', 'entryB', null, null, [hash1]) + const { hash: hash2, bytes: bytes2 } = await Entry.encode(two) + const three = await create(testIdentity, 'A', 'entryC', null, null, [hash2]) + const { hash: hash3, bytes: bytes3 } = await Entry.encode(three) + const four = await create(testIdentity, 'A', 'entryD', null, null, [hash2]) + const { hash: hash4, bytes: bytes4 } = await Entry.encode(four) const entryStorage = await MemoryStorage() - await entryStorage.put(one.hash, one.bytes) - await entryStorage.put(two.hash, two.bytes) - await entryStorage.put(three.hash, three.bytes) - await entryStorage.put(four.hash, four.bytes) + await entryStorage.put(hash1, bytes1) + await entryStorage.put(hash2, bytes2) + await entryStorage.put(hash3, bytes3) + await entryStorage.put(hash4, bytes4) + three.hash = hash3 + four.hash = hash4 + two.hash = hash2 const log = await Log(testIdentity, { logId: 'A', logHeads: [three, four, two], entryStorage }) const values = await log.values() const heads = await log.heads() @@ -143,34 +155,5 @@ describe('Log', function () { strictEqual(values[1].payload, 'hello2') strictEqual(values[2].payload, 'hello3') }) - - it.skip('encrypts a log entry when the payload is a string', async () => { - // const keys = await keystore.createKey('hello1') - - // const privateKey = await keystore.getKey('hello1') - // const publicKey = await keystore.getPublic(keys) - - const encryptPayloadFn = encrypt({ password: 'hello world' }) - const decryptPayloadFn = decrypt({ password: 'hello world' }) - const log = await Log(testIdentity, { encryption: { encryptPayloadFn, decryptPayloadFn } }) - const entry = await log.append('hello1') - const value = await log.get(entry.hash) - strictEqual(value.payload, 'hello1') - }) - - it.skip('encrypts a log entry when the payload is an object', async () => { - // const keys = await keystore.createKey('hello1') - - // const privateKey = await keystore.getKey('hello1') - // const publicKey = await keystore.getPublic(keys) - - const encryptPayloadFn = encrypt({ password: 'hello world' }) - const decryptPayloadFn = decrypt({ password: 'hello world' }) - const log = await Log(testIdentity, { encryption: { encryptPayloadFn, decryptPayloadFn } }) - const entry = await log.append({ test: 'hello1' }) - const value = await log.get(entry.hash) - - deepStrictEqual(value.payload, { test: 'hello1' }) - }) }) }) diff --git a/test/oplog/replicate.test.js b/test/oplog/replicate.test.js index c70c9f4..7c9dfa4 100644 --- a/test/oplog/replicate.test.js +++ b/test/oplog/replicate.test.js @@ -9,7 +9,7 @@ import createHelia from '../utils/create-helia.js' const keysPath = './testkeys' -describe.only('Log - Replication', function () { +describe('Log - Replication', function () { let ipfs1, ipfs2 let id1, id2 let keystore diff --git a/test/orbitdb-encryption.test.js b/test/orbitdb-encryption.test.js index f93a53a..a91bba7 100644 --- a/test/orbitdb-encryption.test.js +++ b/test/orbitdb-encryption.test.js @@ -1,24 +1,30 @@ -import { strictEqual } from 'assert' +import { strictEqual, notEqual } from 'assert' import { rimraf } from 'rimraf' import path from 'path' -import OrbitDB from '../src/orbitdb.js' -// import waitFor from './utils/wait-for.js' +import { createOrbitDB, PasswordEncryption } from '../src/index.js' +// import { encrypt, decrypt, generatePassword } from './utils/encrypt.js' import connectPeers from './utils/connect-nodes.js' import waitFor from './utils/wait-for.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, generatePassword } from './utils/encrypt.js' + +import * as Block from 'multiformats/block' +import * as dagCbor from '@ipld/dag-cbor' +import { sha256 } from 'multiformats/hashes/sha2' + +const codec = dagCbor +const hasher = sha256 const dbPath = './orbitdb/tests/write-permissions' -describe.only('Encryption/Decryption', function () { +describe('Encryption', function () { this.timeout(5000) let ipfs1, ipfs2 let orbitdb1, orbitdb2 let db1, db2 - let encryptionPassword + + let replicationEncryption + let dataEncryption before(async () => { [ipfs1, ipfs2] = await Promise.all([createHelia(), createHelia()]) @@ -26,10 +32,11 @@ describe.only('Encryption/Decryption', function () { await rimraf('./orbitdb') - orbitdb1 = await OrbitDB({ ipfs: ipfs1, id: 'user1', directory: path.join(dbPath, '1') }) - orbitdb2 = await OrbitDB({ ipfs: ipfs2, id: 'user2', directory: path.join(dbPath, '2') }) + orbitdb1 = await createOrbitDB({ ipfs: ipfs1, id: 'user1', directory: path.join(dbPath, '1') }) + orbitdb2 = await createOrbitDB({ ipfs: ipfs2, id: 'user2', directory: path.join(dbPath, '2') }) - encryptionPassword = await generatePassword() + replicationEncryption = await PasswordEncryption({ password: 'hello' }) + dataEncryption = await PasswordEncryption({ password: 'world' }) }) after(async () => { @@ -54,90 +61,315 @@ describe.only('Encryption/Decryption', function () { await rimraf('./ipfs2') }) - afterEach(async () => { - await db1.drop() - await db1.close() - await db2.drop() - await db2.close() + describe('Data is encrypted when replicated to peers', async () => { + afterEach(async () => { + if (db1) { + await db1.drop() + await db1.close() + } + if (db2) { + await db2.drop() + await db2.close() + } + }) + + it('encrypts/decrypts data', async () => { + let connected = false + let updated = false + let error = false + + const encryption = { + data: dataEncryption + } + + db1 = await orbitdb1.open('encryption-test-1', { encryption }) + db2 = await orbitdb2.open(db1.address, { encryption }) + + const onJoin = async (peerId, heads) => { + connected = true + } + db2.events.on('join', onJoin) + + await waitFor(() => connected, () => true) + + const onUpdate = async (peerId, heads) => { + updated = true + } + db2.events.on('update', onUpdate) + + const onError = async (err) => { + // Catch "Could not decrypt entry" errors + console.log(err) + error = true + } + db2.events.on('error', onError) + + const hash1 = await db1.add('record 1') + const hash2 = await db1.add('record 2') + + strictEqual(await db1.get(hash1), 'record 1') + strictEqual(await db1.get(hash2), 'record 2') + + await waitFor(() => updated || error, () => true) + + const all = await db2.all() + + strictEqual(all.length, 2) + strictEqual(all[0].value, 'record 1') + strictEqual(all[1].value, 'record 2') + }) + + it('encrypts/decrypts log', async () => { + let connected = false + let updated = false + let error = false + + const encryption = { + replication: replicationEncryption + } + + db1 = await orbitdb1.open('encryption-test-1', { encryption }) + db2 = await orbitdb2.open(db1.address, { encryption }) + + const onJoin = async (peerId, heads) => { + connected = true + } + db2.events.on('join', onJoin) + + await waitFor(() => connected, () => true) + + const onUpdate = async (peerId, heads) => { + updated = true + } + db2.events.on('update', onUpdate) + + const onError = async (err) => { + // Catch "Could not decrypt entry" errors + console.log(err) + error = true + } + db2.events.on('error', onError) + + const hash1 = await db1.add('record 1') + const hash2 = await db1.add('record 2') + + strictEqual(await db1.get(hash1), 'record 1') + strictEqual(await db1.get(hash2), 'record 2') + + await waitFor(() => updated || error, () => true) + + const all = await db2.all() + + strictEqual(all.length, 2) + strictEqual(all[0].value, 'record 1') + strictEqual(all[1].value, 'record 2') + }) + + it('encrypts/decrypts log and data', async () => { + let connected = false + let updated = false + let error = false + + const encryption = { + replication: replicationEncryption, + data: dataEncryption + } + + db1 = await orbitdb1.open('encryption-test-1', { encryption }) + db2 = await orbitdb2.open(db1.address, { encryption }) + + const onJoin = async (peerId, heads) => { + connected = true + } + db2.events.on('join', onJoin) + + await waitFor(() => connected, () => true) + + const onUpdate = async (peerId, heads) => { + updated = true + } + db2.events.on('update', onUpdate) + + const onError = async (err) => { + // Catch "Could not decrypt entry" errors + console.log(err) + error = true + } + db2.events.on('error', onError) + + const hash1 = await db1.add('record 1') + const hash2 = await db1.add('record 2') + + strictEqual(await db1.get(hash1), 'record 1') + strictEqual(await db1.get(hash2), 'record 2') + + await waitFor(() => updated || error, () => true) + + const all = await db2.all() + + strictEqual(all.length, 2) + strictEqual(all[0].value, 'record 1') + strictEqual(all[1].value, 'record 2') + }) + + it('throws an error if log can\'t be decrypted', async () => { + let connected = false + let hasError = false + let error + + const replicationEncryptionWithWrongPassword = await PasswordEncryption({ password: 'olleh' }) + + const encryption = { + replication: replicationEncryption + } + + const encryptionWithWrongPassword = { + replication: replicationEncryptionWithWrongPassword + } + + db1 = await orbitdb1.open('encryption-test-1', { encryption }) + db2 = await orbitdb2.open(db1.address, { encryption: encryptionWithWrongPassword }) + + const onJoin = async (peerId, heads) => { + connected = true + } + db2.events.on('join', onJoin) + + await waitFor(() => connected, () => true) + + const onError = async (err) => { + // Catch "Could not decrypt entry" errors + error = err + hasError = true + } + db2.events.on('error', onError) + + await db1.add('record 1') + + await waitFor(() => hasError, () => true) + + strictEqual(error.message, 'Could not decrypt entry') + + const all = await db2.all() + + strictEqual(all.length, 0) + }) + + it('throws an error if data can\'t be decrypted', async () => { + let connected = false + let hasError = false + let error + + const dataEncryptionWithWrongPassword = await PasswordEncryption({ password: 'olleh' }) + + const encryption = { + data: dataEncryption + } + + const encryptionWithWrongPassword = { + data: dataEncryptionWithWrongPassword + } + + db1 = await orbitdb1.open('encryption-test-1', { encryption }) + db2 = await orbitdb2.open(db1.address, { encryption: encryptionWithWrongPassword }) + + const onJoin = async (peerId, heads) => { + connected = true + } + db2.events.on('join', onJoin) + + await waitFor(() => connected, () => true) + + const onError = async (err) => { + // Catch "Could not decrypt entry" errors + error = err + hasError = true + } + db2.events.on('error', onError) + + await db1.add('record 1') + + await waitFor(() => hasError, () => true) + + strictEqual(error.message, 'Could not decrypt payload') + + const all = await db2.all() + + strictEqual(all.length, 0) + }) }) - it.skip('encrypts/decrypts payload', async () => { - const encryptPayloadFn = encrypt({ password: encryptionPassword }) - const decryptPayloadFn = decrypt({ password: encryptionPassword }) + describe('Data is encrypted in storage', async () => { + afterEach(async () => { + if (db1) { + await db1.drop() + await db1.close() + } + }) - db1 = await orbitdb1.open('encryption-test-1', { encryption: { encryptPayloadFn, decryptPayloadFn } }) + it('payload bytes are encrypted in storage', async () => { + let error - const hash = await db1.add('record 1') + const encryption = { + data: dataEncryption + } - for await (const e of db1.log.iterator()) { - console.log('>', e) - } + db1 = await orbitdb1.open('encryption-test-1', { encryption }) - strictEqual(await db1.get(hash), 'record 1') - }) + const onError = async (err) => { + // Catch "Could not decrypt entry" errors + console.log(err) + error = true + } + db1.events.on('error', onError) - it('encrypts/decrypts entry', async () => { - let connected = false - let updated = false - let error = false + const hash1 = await db1.add('record 1') - const encryptPayloadFn = encrypt({ password: encryptionPassword }) - const decryptPayloadFn = decrypt({ password: encryptionPassword }) + const bytes = await db1.log.storage.get(hash1) + const { value } = await Block.decode({ bytes, codec, hasher }) + const payload = value.payload - const encryptEntryFn = encrypt({ password: encryptionPassword }) - const decryptEntryFn = decrypt({ password: encryptionPassword }) + strictEqual(payload.constructor, Uint8Array) - // const decryptPayloadFn2 = encrypt({ password: encryptionPassword + '1' }) - // const decryptEntryFn2 = decrypt({ password: encryptionPassword + '2' }) + try { + await Block.decode({ bytes: payload, codec, hasher }) + } catch (e) { + error = e + } - db1 = await orbitdb1.open('encryption-test-1', { encryption: { encryptEntryFn, decryptEntryFn, encryptPayloadFn, decryptPayloadFn } }) - db2 = await orbitdb2.open(db1.address, { encryption: { encryptEntryFn, decryptEntryFn, encryptPayloadFn, decryptPayloadFn } }) - // db1 = await orbitdb1.open('encryption-test-1', { encryption: { encryptEntryFn, decryptEntryFn } }) - // db2 = await orbitdb2.open(db1.address, { encryption: { encryptEntryFn, decryptEntryFn } }) - // db1 = await orbitdb1.open('encryption-test-1', { encryption: { encryptPayloadFn, decryptPayloadFn } }) - // db2 = await orbitdb2.open(db1.address, { encryption: { encryptPayloadFn, decryptPayloadFn } }) - // db1 = await orbitdb1.open('encryption-test-1') - // db2 = await orbitdb2.open(db1.address) + strictEqual(error.message.startsWith('CBOR decode error'), true) + }) - console.log('connect') + it('entry bytes are encrypted in storage', async () => { + let error - const onJoin = async (peerId, heads) => { - console.log('connected') - connected = true - } - db2.events.on('join', onJoin) + const encryption = { + replication: replicationEncryption + } - await waitFor(() => connected, () => true) + db1 = await orbitdb1.open('encryption-test-1', { encryption }) - const onUpdate = async (peerId, heads) => { - console.log('updated') - updated = true - } - db2.events.on('update', onUpdate) + const onError = async (err) => { + // Catch "Could not decrypt entry" errors + console.log(err) + error = true + } + db1.events.on('error', onError) - const onError = async (err) => { - // Catch "Could not decrypt entry" errors - console.log(err) - error = true - } - db2.events.on('error', onError) + const hash1 = await db1.add('record 1') + let decodedBytes - console.log('write') - const hash1 = await db1.add('record 1') - console.log('hash1', hash1) - const hash2 = await db1.add('record 2') - console.log('hash2', hash2) + try { + const bytes = await db1.log.storage.get(hash1) + decodedBytes = await Block.decode({ bytes, codec, hasher }) + await Block.decode({ bytes: decodedBytes, codec, hasher }) + } catch (e) { + error = e + } - strictEqual(await db1.get(hash1), 'record 1') - strictEqual(await db1.get(hash2), 'record 2') - - await waitFor(() => updated || error, () => true) - - const all = await db2.all() - console.log('all', all) - - strictEqual(all.length, 2) - strictEqual(all[0].value, 'record 1') - strictEqual(all[1].value, 'record 2') + notEqual(error, undefined) + strictEqual(error.message.startsWith('CBOR decode error'), true) + strictEqual(decodedBytes.value.constructor, Uint8Array) + }) }) }) diff --git a/test/sync.test.js b/test/sync.test.js index b3d2190..6d7eb28 100644 --- a/test/sync.test.js +++ b/test/sync.test.js @@ -139,8 +139,7 @@ describe('Sync protocol', function () { log1 = await Log(testIdentity1, { logId: 'synclog111', entryStorage: entryStorage1 }) log2 = await Log(testIdentity2, { logId: 'synclog111', entryStorage: entryStorage2 }) - const onSynced = async (bytes) => { - const entry = await Entry.decode(bytes) + const onSynced = async (entry) => { if (await log2.joinEntry(entry)) { syncedHead = entry syncedEventFired = true @@ -207,8 +206,7 @@ describe('Sync protocol', function () { log1 = await Log(testIdentity1, { logId: 'synclog7', entryStorage: entryStorage1 }) log2 = await Log(testIdentity2, { logId: 'synclog7', entryStorage: entryStorage2 }) - const onSynced = async (bytes) => { - const entry = await Entry.decode(bytes) + const onSynced = async (entry) => { if (await log2.joinEntry(entry)) { syncedHead = entry } @@ -291,8 +289,8 @@ describe('Sync protocol', function () { log1 = await Log(testIdentity1, { logId: 'synclog1' }) log2 = await Log(testIdentity2, { logId: 'synclog1' }) - const onSynced = async (bytes) => { - syncedHead = await Entry.decode(bytes) + const onSynced = async (entry) => { + syncedHead = entry syncedEventFired = expectedEntry.hash === syncedHead.hash } @@ -348,8 +346,8 @@ describe('Sync protocol', function () { log1 = await Log(testIdentity1, { logId: 'synclog1' }) log2 = await Log(testIdentity2, { logId: 'synclog1' }) - const onSynced = async (bytes) => { - syncedHead = await Entry.decode(bytes) + const onSynced = async (entry) => { + syncedHead = entry if (expectedEntry) { syncedEventFired = expectedEntry.hash === syncedHead.hash } @@ -434,9 +432,9 @@ describe('Sync protocol', function () { log1 = await Log(testIdentity1, { logId: 'synclog1' }) log2 = await Log(testIdentity2, { logId: 'synclog1' }) - const onSynced = async (bytes) => { + const onSynced = async (entry) => { if (expectedEntry && !syncedEventFired) { - syncedHead = await Entry.decode(bytes) + syncedHead = entry syncedEventFired = expectedEntry.hash === syncedHead.hash } } @@ -518,8 +516,8 @@ describe('Sync protocol', function () { log1 = await Log(testIdentity1, { logId: 'synclog2' }) log2 = await Log(testIdentity2, { logId: 'synclog2' }) - const onSynced = async (bytes) => { - syncedHead = await Entry.decode(bytes) + const onSynced = async (entry) => { + syncedHead = entry if (expectedEntry) { syncedEventFired = expectedEntry ? expectedEntry.hash === syncedHead.hash : false }