Add methods to generate keys using ECDH

This commit is contained in:
Jerko Steiner 2019-12-07 10:13:06 -03:00
parent d0f14f83f9
commit 67c38498f4
6 changed files with 305 additions and 69 deletions

View File

@ -1 +1,3 @@
require('fake-indexeddb/auto')
// eslint-disable-next-line
window.crypto = require('isomorphic-webcrypto')

242
package-lock.json generated
View File

@ -2279,41 +2279,37 @@
"@types/yargs": "^12.0.9"
}
},
"@trust/keyto": {
"version": "0.3.7",
"resolved": "https://registry.npmjs.org/@trust/keyto/-/keyto-0.3.7.tgz",
"integrity": "sha512-t5kWWCTkPgg24JWVuCTPMx7l13F7YHdxBeJkT1vmoHjROgiOIEAN8eeY+iRmP1Hwsx+S7U55HyuqSsECr08a8A==",
"@peculiar/asn1-schema": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-1.0.3.tgz",
"integrity": "sha512-Tfgj9eNJ6cTKEtEuidKenLHMx/Q5M8KEE9hnohHqvdpqHJXWYr5RlT3GjAHPjGXy5+mr7sSfuXfzE6aAkEGN7A==",
"dev": true,
"requires": {
"asn1.js": "^5.0.1",
"base64url": "^3.0.1",
"elliptic": "^6.4.1"
},
"dependencies": {
"asn1.js": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.2.0.tgz",
"integrity": "sha512-Q7hnYGGNYbcmGrCPulXfkEw7oW7qjWeM4ZTALmgpuIcZLxyqqKYWxCZg2UBm8bklrnB4m2mGyJPWfoktdORD8A==",
"dev": true,
"requires": {
"bn.js": "^4.0.0",
"inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0"
}
}
"asn1js": "^2.0.22",
"tslib": "^1.9.3"
}
},
"@trust/webcrypto": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/@trust/webcrypto/-/webcrypto-0.9.2.tgz",
"integrity": "sha512-5iMAVcGYKhqLJGjefB1nzuQSqUJTru0nG4CytpBT/GGp1Piz/MVnj2jORdYf4JBYzggCIa8WZUr2rchP2Ngn/w==",
"@peculiar/json-schema": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.6.tgz",
"integrity": "sha512-A8DM0ueA+LUqD/HuNPHDd8yMkhbRmnV0iosxyB/uOV1cfiKlCKXDeqkzHTOZpveRI05iCjZxqkPZ2+Nnw1wB4A==",
"dev": true,
"requires": {
"@trust/keyto": "^0.3.4",
"base64url": "^3.0.0",
"elliptic": "^6.4.0",
"node-rsa": "^0.4.0",
"text-encoding": "^0.6.1"
"tslib": "^1.10.0"
}
},
"@peculiar/webcrypto": {
"version": "1.0.21",
"resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.0.21.tgz",
"integrity": "sha512-dMQe+vTKSKDpiizQj5q7lFqU56zBgavrjcST4d8RMxEbmgoUOuAUOXlkI5DoqVy3ktcfAhk6CRV4YkaSUEXdAg==",
"dev": true,
"requires": {
"@peculiar/asn1-schema": "^1.0.3",
"@peculiar/json-schema": "^1.1.5",
"asn1js": "^2.0.26",
"pvtsutils": "^1.0.6",
"tslib": "^1.10.0",
"webcrypto-core": "^1.0.14"
}
},
"@types/babel__core": {
@ -2782,6 +2778,28 @@
}
}
},
"@unimodules/core": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@unimodules/core/-/core-4.0.0.tgz",
"integrity": "sha512-lHxRmCG9DK3/aA2lnBKPS32K95NpYE10mZQRp5dycSptgN0DIeWWHuE01SndcSUACGyEP+tDO+DnGo8mhLlt4Q==",
"dev": true,
"optional": true,
"requires": {
"compare-versions": "^3.4.0"
}
},
"@unimodules/react-native-adapter": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@unimodules/react-native-adapter/-/react-native-adapter-4.0.0.tgz",
"integrity": "sha512-zGAyDhqAEWvshdSxc523srP6OAZaSr95Cv5EuxLJbFGcJENHhK8o/qxhwS7/LYTF3LqtOlnSlwQta3v3y6kF4A==",
"dev": true,
"optional": true,
"requires": {
"invariant": "^2.2.4",
"lodash": "^4.5.0",
"prop-types": "^15.6.1"
}
},
"JSONStream": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz",
@ -3360,6 +3378,12 @@
"resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz",
"integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog=="
},
"asmcrypto.js": {
"version": "0.22.0",
"resolved": "https://registry.npmjs.org/asmcrypto.js/-/asmcrypto.js-0.22.0.tgz",
"integrity": "sha512-usgMoyXjMbx/ZPdzTSXExhMPur2FTdz/Vo5PVx2gIaBcdAAJNOFlsdgqveM8Cff7W0v+xrf9BwjOV26JSAF9qA==",
"dev": true
},
"asn1": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
@ -3380,6 +3404,15 @@
"minimalistic-assert": "^1.0.0"
}
},
"asn1js": {
"version": "2.0.26",
"resolved": "https://registry.npmjs.org/asn1js/-/asn1js-2.0.26.tgz",
"integrity": "sha512-yG89F0j9B4B0MKIcFyWWxnpZPLaNTjCj4tkE3fjbAoo0qmpGw0PYYqSbX/4ebnd9Icn8ZgK4K1fvDyEtW1JYtQ==",
"dev": true,
"requires": {
"pvutils": "^1.0.17"
}
},
"assert": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz",
@ -3466,6 +3499,24 @@
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==",
"dev": true
},
"b64-lite": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/b64-lite/-/b64-lite-1.4.0.tgz",
"integrity": "sha512-aHe97M7DXt+dkpa8fHlCcm1CnskAHrJqEfMI0KN7dwqlzml/aUe1AGt6lk51HzrSfVD67xOso84sOpr+0wIe2w==",
"dev": true,
"requires": {
"base-64": "^0.1.0"
}
},
"b64u-lite": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/b64u-lite/-/b64u-lite-1.1.0.tgz",
"integrity": "sha512-929qWGDVCRph7gQVTC6koHqQIpF4vtVaSbwLltFQo44B1bYUquALswZdBKFfrJCPEnsCOvWkJsPdQYZ/Ukhw8A==",
"dev": true,
"requires": {
"b64-lite": "^1.4.0"
}
},
"babel-core": {
"version": "7.0.0-bridge.0",
"resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz",
@ -3875,6 +3926,12 @@
}
}
},
"base-64": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
"integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs=",
"dev": true
},
"base64-arraybuffer": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz",
@ -3897,12 +3954,6 @@
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="
},
"base64url": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz",
"integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==",
"dev": true
},
"bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
@ -4692,6 +4743,13 @@
"dev": true,
"optional": true
},
"compare-versions": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.5.1.tgz",
"integrity": "sha512-9fGPIB7C6AyM18CJJBHt5EnCZDG3oiTJYy0NjfIAGjKpzv0tkxWko7TNQHF5ymqm7IH03tqmeuBxtvD+Izh6mg==",
"dev": true,
"optional": true
},
"component-bind": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz",
@ -5948,6 +6006,16 @@
}
}
},
"expo-random": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/expo-random/-/expo-random-7.0.0.tgz",
"integrity": "sha512-+Ajxz4ZwTl2xGIGOg24xaIAw/RvGfKbkDlpsnwKc1FQr7Eka3ZvdQ3q6XKCXtqMG99jXmk9w6YVcbSUMyakpag==",
"dev": true,
"optional": true,
"requires": {
"base64-js": "^1.3.0"
}
},
"express": {
"version": "4.17.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
@ -7939,6 +8007,25 @@
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
"dev": true
},
"isomorphic-webcrypto": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/isomorphic-webcrypto/-/isomorphic-webcrypto-2.3.2.tgz",
"integrity": "sha512-XbUC6ZwVvJfts2FYfAuNRU99tt89EzTknZcKZjxx6/6hxNeLgF6sIDbA/RdA3spbcNrXYyPOHa90khbUgZWarw==",
"dev": true,
"requires": {
"@peculiar/webcrypto": "^1.0.19",
"@unimodules/core": "*",
"@unimodules/react-native-adapter": "*",
"asmcrypto.js": "^0.22.0",
"b64-lite": "^1.3.1",
"b64u-lite": "^1.0.1",
"expo-random": "*",
"msrcrypto": "^1.5.6",
"react-native-securerandom": "^0.1.1",
"str2buf": "^1.3.0",
"webcrypto-shim": "^0.1.4"
}
},
"isstream": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
@ -9841,6 +9928,12 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"msrcrypto": {
"version": "1.5.7",
"resolved": "https://registry.npmjs.org/msrcrypto/-/msrcrypto-1.5.7.tgz",
"integrity": "sha512-vH/uVdMPgdtLrDCdR2gWps2fB10EYWjXYi67W9RzNSd5Jch3noWGUvNUXSIJA87VTDaE+wvjS7yRSN4gALTslg==",
"dev": true
},
"mute-stream": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
@ -9981,23 +10074,6 @@
}
}
},
"node-rsa": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-0.4.2.tgz",
"integrity": "sha1-1jkXKewWqDDtWjgEKzFX0tXXJTA=",
"dev": true,
"requires": {
"asn1": "0.2.3"
},
"dependencies": {
"asn1": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
"integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=",
"dev": true
}
}
},
"node-sass": {
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.13.0.tgz",
@ -10839,6 +10915,30 @@
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
"dev": true
},
"pvtsutils": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.0.6.tgz",
"integrity": "sha512-0yNrOdJyLE7FZzmeEHTKanwBr5XbmDAd020cKa4ZiTYuGMBYBZmq7vHOhcOqhVllh6gghDBbaz1lnVdOqiB7cw==",
"dev": true,
"requires": {
"@types/node": "^10.14.17",
"tslib": "^1.10.0"
},
"dependencies": {
"@types/node": {
"version": "10.17.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.6.tgz",
"integrity": "sha512-0a2X6cgN3RdPBL2MIlR6Lt0KlM7fOFsutuXcdglcOq6WvLnYXgPQSh0Mx6tO1KCAE8MxbHSOSTWDoUxRq+l3DA==",
"dev": true
}
}
},
"pvutils": {
"version": "1.0.17",
"resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.0.17.tgz",
"integrity": "sha512-wLHYUQxWaXVQvKnwIDWFVKDJku9XDCvyhhxoq8dc5MFdIlRenyPI9eSfEtcvgHgD7FlvCyGAlWgOzRnZD99GZQ==",
"dev": true
},
"qs": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
@ -10939,6 +11039,16 @@
"integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==",
"dev": true
},
"react-native-securerandom": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/react-native-securerandom/-/react-native-securerandom-0.1.1.tgz",
"integrity": "sha1-8TBiOkEsM4sK+t7bwgTFy7i/IHA=",
"dev": true,
"optional": true,
"requires": {
"base64-js": "*"
}
},
"react-redux": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.1.3.tgz",
@ -12447,6 +12557,12 @@
"integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=",
"dev": true
},
"str2buf": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/str2buf/-/str2buf-1.3.0.tgz",
"integrity": "sha512-xIBmHIUHYZDP4HyoXGHYNVmxlXLXDrtFHYT0eV6IOdEj3VO9ccaF1Ejl9Oq8iFjITllpT8FhaXb4KsNmw+3EuA==",
"dev": true
},
"stream-browserify": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
@ -12876,12 +12992,6 @@
}
}
},
"text-encoding": {
"version": "0.6.4",
"resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz",
"integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=",
"dev": true
},
"text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@ -13776,6 +13886,22 @@
}
}
},
"webcrypto-core": {
"version": "1.0.15",
"resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.0.15.tgz",
"integrity": "sha512-PA4VeKekgPxlmp18Opd4hrdOvtjsJCHyKpNfCyjLWEFIh/7M37QCFgCssx/MVBuNHBkzs9Q7W8Rm4BFgCKheUQ==",
"dev": true,
"requires": {
"pvtsutils": "^1.0.6",
"tslib": "^1.10.0"
}
},
"webcrypto-shim": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/webcrypto-shim/-/webcrypto-shim-0.1.5.tgz",
"integrity": "sha512-mE+E00gulvbLjHaAwl0kph60oOLQRsKyivEFgV9DMM/3Y05F1vZvGq12hAcNzHRnYxyEOABBT/XMtwGSg5xA7A==",
"dev": true
},
"webidl-conversions": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",

View File

@ -61,7 +61,6 @@
"@babel/core": "^7.7.2",
"@babel/polyfill": "^7.7.0",
"@babel/preset-env": "^7.7.1",
"@trust/webcrypto": "^0.9.2",
"@types/classnames": "^2.2.9",
"@types/debug": "^4.1.5",
"@types/ejs": "^2.6.3",
@ -93,6 +92,7 @@
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-react": "^7.16.0",
"fake-indexeddb": "^3.0.0",
"isomorphic-webcrypto": "^2.3.2",
"jest": "^24.9.0",
"loose-envify": "^1.4.0",
"node-sass": "^4.13.0",

View File

@ -0,0 +1,33 @@
import { encrypt, decrypt, generateECDHKeyPair, deriveECDHKey } from './index'
describe('crypto', () => {
let keypair1: CryptoKeyPair
let keypair2: CryptoKeyPair
let derived1: CryptoKey
let derived2: CryptoKey
beforeAll(async () => {
keypair1 = await generateECDHKeyPair()
keypair2 = await generateECDHKeyPair()
derived1 = await deriveECDHKey({
privateKey: keypair1.privateKey,
publicKey: keypair2.publicKey,
})
derived2 = await deriveECDHKey({
privateKey: keypair2.privateKey,
publicKey: keypair1.publicKey,
})
})
describe('encrypt and decrypt', () => {
it('can be encrypted with one pair and decrypted with other', async () => {
const message = 'test message'
const encrypted = await encrypt(derived1, message)
const decrypted = await decrypt(derived2, encrypted)
expect(decrypted).toEqual(message)
})
})
})

View File

@ -0,0 +1,75 @@
export async function generateECDHKeyPair() {
const key = await window.crypto.subtle.generateKey(
{
name: 'ECDH',
namedCurve: 'P-256',
},
/* extractable */ false,
['deriveKey'],
)
return key
}
export async function deriveECDHKey(params: {
privateKey: CryptoKey
publicKey: CryptoKey
}) {
const { privateKey, publicKey } = params
const derivedKey = await window.crypto.subtle.deriveKey(
{
name: 'ECDH',
public: publicKey,
},
privateKey,
{
name: 'AES-CTR',
length: 256,
},
/* extractable */ false,
['encrypt', 'decrypt'],
)
return derivedKey
}
export async function encrypt(key: CryptoKey, data: string): Promise<string> {
const encrypted = await window.crypto.subtle.encrypt(
{
name: 'AES-CTR',
counter: new Uint8Array(16),
length: 128,
},
key,
str2ab(data),
)
return ab2str(encrypted)
}
export async function decrypt(key: CryptoKey, data: string): Promise<string> {
const arrayBuffer = str2ab(data)
const decrypted = await window.crypto.subtle.decrypt(
{
name: 'AES-CTR',
counter: new ArrayBuffer(16),
length: 128,
},
key,
arrayBuffer,
)
return ab2str(decrypted)
}
function ab2str(buf: ArrayBuffer): string {
return String.fromCharCode.apply(
null, new Uint16Array(buf) as unknown as number[])
}
function str2ab(str: string): ArrayBuffer {
const buf = new ArrayBuffer(str.length*2) // 2 bytes for each char
const bufView = new Uint16Array(buf)
for (let i=0, strLen=str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i)
}
return buf
}

View File

@ -4,16 +4,16 @@ describe('db', () => {
const TEST_DB = 'TEST_DB'
async function getError(promise: Promise<unknown>): Promise<Error> {
let error: Error
try {
await promise
} catch (err) {
error = err
}
expect(error!).toBeTruthy()
return error!
}
// async function getError(promise: Promise<unknown>): Promise<Error> {
// let error: Error
// try {
// await promise
// } catch (err) {
// error = err
// }
// expect(error!).toBeTruthy()
// return error!
// }
afterEach(async () => {
db && db.close()