diff --git a/packages/daf-cli/package.json b/packages/daf-cli/package.json index 4e8adb9e6..b0f1d5010 100644 --- a/packages/daf-cli/package.json +++ b/packages/daf-cli/package.json @@ -8,6 +8,7 @@ "daf": "bin/daf.js" }, "scripts": { + "postinstall": "node build/postinstall.js", "build": "tsc", "watch": "tsc -b --watch" }, @@ -29,6 +30,7 @@ "daf-w3c": "^4.2.0", "date-fns": "^2.8.1", "debug": "^4.1.1", + "dotenv": "^8.2.0", "graphql": "^14.5.8", "inquirer": "^7.0.0", "lodash.merge": "^4.6.2", diff --git a/packages/daf-cli/src/cli.ts b/packages/daf-cli/src/cli.ts index c292dd733..16d200f3f 100644 --- a/packages/daf-cli/src/cli.ts +++ b/packages/daf-cli/src/cli.ts @@ -1,3 +1,6 @@ +import { config } from 'dotenv' +config({ path: process.env.HOME + '/.daf/.env'}) + import program from 'commander' import './identity-manager' import './did-resolver' @@ -8,6 +11,8 @@ import './graphql' import './sdr' import './msg' import './version' +import './crypto' + if (!process.argv.slice(2).length) { program.outputHelp() diff --git a/packages/daf-cli/src/crypto.ts b/packages/daf-cli/src/crypto.ts new file mode 100644 index 000000000..84d2771df --- /dev/null +++ b/packages/daf-cli/src/crypto.ts @@ -0,0 +1,15 @@ +import { SecretBox } from 'daf-libsodium' +import program from 'commander' + +program + .command('crypto') + .option('-s, --secret', 'Generate secret key') + .description('Crypto') + .action(async raw => { + try { + const secretKey = await SecretBox.createSecretKey() + console.log(secretKey) + } catch (e) { + console.error(e.message) + } + }) diff --git a/packages/daf-cli/src/postinstall.ts b/packages/daf-cli/src/postinstall.ts new file mode 100644 index 000000000..927870417 --- /dev/null +++ b/packages/daf-cli/src/postinstall.ts @@ -0,0 +1,26 @@ +const fs = require('fs') +import { SecretBox } from 'daf-libsodium' + +const defaultPath = process.env.HOME + '/.daf/' +const envFile = defaultPath + '.env' + +async function main() { + + if (!fs.existsSync(defaultPath)) { + fs.mkdirSync(defaultPath) + } + + if (!fs.existsSync(envFile)) { + console.log('Configuration file does not exist. Creating: ' + envFile) + let env = 'DAF_DATA_STORE=' + defaultPath + 'database-v3.sqlite3' + env += '\nDEBUG_DAF_DB=0' + env += '\nDAF_SECRET_KEY=' + await SecretBox.createSecretKey() + env += '\nDAF_INFURA_ID=5ffc47f65c4042ce847ef66a3fa70d4c' + env += '\n#DEBUG=daf:*' + + fs.writeFileSync(envFile, env) + } + +} + +main().catch(console.error) \ No newline at end of file diff --git a/packages/daf-cli/src/setup.ts b/packages/daf-cli/src/setup.ts index a054541eb..b268d7221 100644 --- a/packages/daf-cli/src/setup.ts +++ b/packages/daf-cli/src/setup.ts @@ -4,7 +4,7 @@ import { DafUniversalResolver } from 'daf-resolver-universal' import * as Daf from 'daf-core' import { JwtMessageHandler } from 'daf-did-jwt' import * as EthrDid from 'daf-ethr-did' -import * as DafLibSodium from 'daf-libsodium' +import { KeyManagementSystem, SecretBox } from 'daf-libsodium' import { W3cActionHandler, W3cMessageHandler } from 'daf-w3c' import { SdrActionHandler, SdrMessageHandler } from 'daf-selective-disclosure' @@ -15,17 +15,7 @@ import { createConnection } from 'typeorm' import ws from 'ws' -const defaultPath = process.env.HOME + '/.daf/' - -const dataStoreFilename = process.env.DAF_DATA_STORE ?? defaultPath + 'database-v2.sqlite' -const infuraProjectId = process.env.DAF_INFURA_ID ?? '5ffc47f65c4042ce847ef66a3fa70d4c' - -if (!process.env.DAF_IDENTITY_STORE || process.env.DAF_DATA_STORE) { - const fs = require('fs') - if (!fs.existsSync(defaultPath)) { - fs.mkdirSync(defaultPath) - } -} +const infuraProjectId = process.env.DAF_INFURA_ID // DID Document Resolver let didResolver: Daf.Resolver = new DafResolver({ @@ -44,16 +34,16 @@ TrustGraphServiceController.webSocketImpl = ws const dbConnection = createConnection({ type: 'sqlite', - database: dataStoreFilename, + database: process.env.DAF_DATA_STORE, synchronize: true, - logging: process.env.DEBUG_DAF_DB ? true : false, + logging: process.env.DAF_DEBUG_DB === 'true' ? true : false, entities: [...Daf.Entities], }) const identityProviders = [ new EthrDid.IdentityProvider({ identityStore: new Daf.IdentityStore('rinkeby-ethr', dbConnection), - kms: new DafLibSodium.KeyManagementSystem(new Daf.KeyStore(dbConnection)), + kms: new KeyManagementSystem(new Daf.KeyStore(dbConnection, new SecretBox(process.env.DAF_SECRET_KEY))), network: 'rinkeby', rpcUrl: 'https://rinkeby.infura.io/v3/' + infuraProjectId, }), diff --git a/packages/daf-core/src/identity/abstract-secret-box.ts b/packages/daf-core/src/identity/abstract-secret-box.ts new file mode 100644 index 000000000..a21d25379 --- /dev/null +++ b/packages/daf-core/src/identity/abstract-secret-box.ts @@ -0,0 +1,4 @@ +export abstract class AbstractSecretBox { + abstract encrypt(message: string): Promise + abstract decrypt(encryptedMessageHex: string): Promise +} diff --git a/packages/daf-core/src/identity/key-store.ts b/packages/daf-core/src/identity/key-store.ts index b84a187db..41a14f8df 100644 --- a/packages/daf-core/src/identity/key-store.ts +++ b/packages/daf-core/src/identity/key-store.ts @@ -1,5 +1,6 @@ import { SerializedKey } from './abstract-key-management-system' import { AbstractKeyStore } from './abstract-key-store' +import { AbstractSecretBox } from './abstract-secret-box' import { Connection } from 'typeorm' import { Key } from '../entities/key' @@ -8,13 +9,19 @@ import Debug from 'debug' const debug = Debug('daf:key-store') export class KeyStore extends AbstractKeyStore { - constructor(private dbConnection: Promise) { + constructor(private dbConnection: Promise, private secretBox?: AbstractSecretBox) { super() + if (!secretBox) { + console.warn('Please provide SecretBox to the KeyStore') + } } async get(kid: string) { const key = await (await this.dbConnection).getRepository(Key).findOne(kid) if (!key) throw Error('Key not found') + if (this.secretBox && key.privateKeyHex) { + key.privateKeyHex = await this.secretBox.decrypt(key.privateKeyHex) + } return key } @@ -30,6 +37,9 @@ export class KeyStore extends AbstractKeyStore { const key = new Key() key.kid = kid key.privateKeyHex = serializedKey.privateKeyHex + if (this.secretBox && key.privateKeyHex) { + key.privateKeyHex = await this.secretBox.encrypt(key.privateKeyHex) + } key.publicKeyHex = serializedKey.publicKeyHex key.type = serializedKey.type debug('Saving key', kid) diff --git a/packages/daf-core/src/index.ts b/packages/daf-core/src/index.ts index e7b20363d..94ce91d49 100644 --- a/packages/daf-core/src/index.ts +++ b/packages/daf-core/src/index.ts @@ -11,6 +11,7 @@ export { } from './identity/abstract-key-management-system' export { AbstractIdentityStore, SerializedIdentity } from './identity/abstract-identity-store' export { AbstractKeyStore } from './identity/abstract-key-store' +export { AbstractSecretBox } from './identity/abstract-secret-box' export { AbstractMessageHandler } from './message/abstract-message-handler' export { ServiceManager, LastMessageTimestampForInstance, ServiceEventTypes } from './service/service-manager' export { AbstractServiceController } from './service/abstract-service-controller' diff --git a/packages/daf-libsodium/src/__tests__/secret-box.test.ts b/packages/daf-libsodium/src/__tests__/secret-box.test.ts new file mode 100644 index 000000000..39d808d0e --- /dev/null +++ b/packages/daf-libsodium/src/__tests__/secret-box.test.ts @@ -0,0 +1,15 @@ +import { SecretBox } from '../secret-box' + +describe('daf-libsodium', () => { + + it('should encrypt and decrypt', async () => { + const secretKey = await SecretBox.createSecretKey() + const box = new SecretBox(secretKey) + const message = 'hello world' + + const encrypted = await box.encrypt(message) + const decrypted = await box.decrypt(encrypted) + expect(decrypted).toEqual(message) + }) + +}) diff --git a/packages/daf-libsodium/src/index.ts b/packages/daf-libsodium/src/index.ts index 6bc069a2d..72359293a 100644 --- a/packages/daf-libsodium/src/index.ts +++ b/packages/daf-libsodium/src/index.ts @@ -1 +1,2 @@ export { KeyManagementSystem } from './key-management-system' +export { SecretBox } from './secret-box' \ No newline at end of file diff --git a/packages/daf-libsodium/src/secret-box.ts b/packages/daf-libsodium/src/secret-box.ts new file mode 100644 index 000000000..55166764f --- /dev/null +++ b/packages/daf-libsodium/src/secret-box.ts @@ -0,0 +1,38 @@ +import { AbstractSecretBox } from 'daf-core' +import sodium from 'libsodium-wrappers' + +export class SecretBox extends AbstractSecretBox { + constructor(private secretKey: string) { + super() + if (!secretKey) { + throw Error('Secret key is required') + } + } + + static async createSecretKey(): Promise { + await sodium.ready + const boxKeyPair = sodium.crypto_box_keypair() + const secretKey = sodium.to_hex(boxKeyPair.privateKey) + return secretKey + } + + async encrypt(message: string): Promise { + await sodium.ready + const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES) + const cipherText = sodium.crypto_secretbox_easy(message, nonce, sodium.from_hex(this.secretKey)) + return sodium.to_hex(new Uint8Array([ ...nonce, ...cipherText ])) + } + + async decrypt(encryptedMessageHex: string): Promise { + await sodium.ready + const cipherTextWithNonce = sodium.from_hex(encryptedMessageHex) + const nonce = cipherTextWithNonce.slice(0, sodium.crypto_secretbox_NONCEBYTES) + const cipherText = cipherTextWithNonce.slice(sodium.crypto_secretbox_NONCEBYTES) + return sodium.to_string(sodium.crypto_secretbox_open_easy( + cipherText, + nonce, + sodium.from_hex(this.secretKey) + )) + } + +} \ No newline at end of file diff --git a/packages/daf-react-native-libsodium/src/index.ts b/packages/daf-react-native-libsodium/src/index.ts index 6bc069a2d..02ca222e0 100644 --- a/packages/daf-react-native-libsodium/src/index.ts +++ b/packages/daf-react-native-libsodium/src/index.ts @@ -1 +1,2 @@ export { KeyManagementSystem } from './key-management-system' +export { SecretBox } from './secret-box' diff --git a/packages/daf-react-native-libsodium/src/secret-box.ts b/packages/daf-react-native-libsodium/src/secret-box.ts new file mode 100644 index 000000000..fc83daa20 --- /dev/null +++ b/packages/daf-react-native-libsodium/src/secret-box.ts @@ -0,0 +1,38 @@ +import { AbstractSecretBox } from 'daf-core' +import sodium from 'react-native-sodium' + +export class SecretBox extends AbstractSecretBox { + constructor(private secretKey: string) { + super() + if (!secretKey) { + throw Error('Secret key is required') + } + } + + static async createSecretKey(): Promise { + await sodium.ready + const boxKeyPair = sodium.crypto_box_keypair() + const secretKey = sodium.to_hex(boxKeyPair.privateKey) + return secretKey + } + + async encrypt(message: string): Promise { + await sodium.ready + const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES) + const cipherText = sodium.crypto_secretbox_easy(message, nonce, sodium.from_hex(this.secretKey)) + return sodium.to_hex(new Uint8Array([ ...nonce, ...cipherText ])) + } + + async decrypt(encryptedMessageHex: string): Promise { + await sodium.ready + const cipherTextWithNonce = sodium.from_hex(encryptedMessageHex) + const nonce = cipherTextWithNonce.slice(0, sodium.crypto_secretbox_NONCEBYTES) + const cipherText = cipherTextWithNonce.slice(sodium.crypto_secretbox_NONCEBYTES) + return sodium.to_string(sodium.crypto_secretbox_open_easy( + cipherText, + nonce, + sodium.from_hex(this.secretKey) + )) + } + +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 09ebcb53d..5cb390ebf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4051,6 +4051,11 @@ dotenv@^6.2.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-6.2.0.tgz#941c0410535d942c8becf28d3f357dbd9d476064" integrity sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w== +dotenv@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" + integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== + duplexer2@~0.1.0: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1"