Skip to content

Commit

Permalink
feat: Encrypting private keys with SecretBox
Browse files Browse the repository at this point in the history
  • Loading branch information
simonas-notcat committed Apr 24, 2020
1 parent 306389b commit b8cbdd4
Show file tree
Hide file tree
Showing 14 changed files with 167 additions and 16 deletions.
2 changes: 2 additions & 0 deletions packages/daf-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"daf": "bin/daf.js"
},
"scripts": {
"postinstall": "node build/postinstall.js",
"build": "tsc",
"watch": "tsc -b --watch"
},
Expand All @@ -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",
Expand Down
5 changes: 5 additions & 0 deletions packages/daf-cli/src/cli.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -8,6 +11,8 @@ import './graphql'
import './sdr'
import './msg'
import './version'
import './crypto'


if (!process.argv.slice(2).length) {
program.outputHelp()
Expand Down
15 changes: 15 additions & 0 deletions packages/daf-cli/src/crypto.ts
Original file line number Diff line number Diff line change
@@ -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)
}
})
26 changes: 26 additions & 0 deletions packages/daf-cli/src/postinstall.ts
Original file line number Diff line number Diff line change
@@ -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)
20 changes: 5 additions & 15 deletions packages/daf-cli/src/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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({
Expand All @@ -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,
}),
Expand Down
4 changes: 4 additions & 0 deletions packages/daf-core/src/identity/abstract-secret-box.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export abstract class AbstractSecretBox {
abstract encrypt(message: string): Promise<string>
abstract decrypt(encryptedMessageHex: string): Promise<string>
}
12 changes: 11 additions & 1 deletion packages/daf-core/src/identity/key-store.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -8,13 +9,19 @@ import Debug from 'debug'
const debug = Debug('daf:key-store')

export class KeyStore extends AbstractKeyStore {
constructor(private dbConnection: Promise<Connection>) {
constructor(private dbConnection: Promise<Connection>, 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
}

Expand All @@ -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)
Expand Down
1 change: 1 addition & 0 deletions packages/daf-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
15 changes: 15 additions & 0 deletions packages/daf-libsodium/src/__tests__/secret-box.test.ts
Original file line number Diff line number Diff line change
@@ -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)
})

})
1 change: 1 addition & 0 deletions packages/daf-libsodium/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { KeyManagementSystem } from './key-management-system'
export { SecretBox } from './secret-box'
38 changes: 38 additions & 0 deletions packages/daf-libsodium/src/secret-box.ts
Original file line number Diff line number Diff line change
@@ -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<string> {
await sodium.ready
const boxKeyPair = sodium.crypto_box_keypair()
const secretKey = sodium.to_hex(boxKeyPair.privateKey)
return secretKey
}

async encrypt(message: string): Promise<string> {
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<string> {
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)
))
}

}
1 change: 1 addition & 0 deletions packages/daf-react-native-libsodium/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { KeyManagementSystem } from './key-management-system'
export { SecretBox } from './secret-box'
38 changes: 38 additions & 0 deletions packages/daf-react-native-libsodium/src/secret-box.ts
Original file line number Diff line number Diff line change
@@ -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<string> {
await sodium.ready
const boxKeyPair = sodium.crypto_box_keypair()
const secretKey = sodium.to_hex(boxKeyPair.privateKey)
return secretKey
}

async encrypt(message: string): Promise<string> {
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<string> {
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)
))
}

}
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit b8cbdd4

Please sign in to comment.