Generate ECDH key pairs on application startup.
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Potential TODO: - share the keys with other peers. - think about if it makes sense to encrypt signal messages.
This commit is contained in:
parent
023c2f640b
commit
db738e978a
@ -1,9 +1,15 @@
|
|||||||
|
import _debug from 'debug'
|
||||||
import socket from '../socket'
|
import socket from '../socket'
|
||||||
import { Dispatch, ThunkResult } from '../store'
|
import { Dispatch, ThunkResult } from '../store'
|
||||||
import { callId } from '../window'
|
import { callId } from '../window'
|
||||||
import { ClientSocket } from '../socket'
|
import { ClientSocket } from '../socket'
|
||||||
import * as NotifyActions from './NotifyActions'
|
import * as NotifyActions from './NotifyActions'
|
||||||
import * as SocketActions from './SocketActions'
|
import * as SocketActions from './SocketActions'
|
||||||
|
import { generateECDHKeyPair, exportKey } from '../crypto'
|
||||||
|
import { getIdentity, Identity, saveIdentity } from '../db'
|
||||||
|
import { ME } from '../constants'
|
||||||
|
|
||||||
|
const debug = _debug('peercalls')
|
||||||
|
|
||||||
export interface InitAction {
|
export interface InitAction {
|
||||||
type: 'INIT'
|
type: 'INIT'
|
||||||
@ -22,6 +28,11 @@ export const init = (): ThunkResult<Promise<void>> =>
|
|||||||
async (dispatch, getState) => {
|
async (dispatch, getState) => {
|
||||||
const socket = await dispatch(connect())
|
const socket = await dispatch(connect())
|
||||||
|
|
||||||
|
const keyPair = await dispatch(findOrCreateEncryptionKeys())
|
||||||
|
const publicKey = keyPair && await exportKey(keyPair.publicKey)
|
||||||
|
// TODO use publicKey
|
||||||
|
publicKey
|
||||||
|
|
||||||
dispatch(SocketActions.handshake({
|
dispatch(SocketActions.handshake({
|
||||||
socket,
|
socket,
|
||||||
roomName: callId,
|
roomName: callId,
|
||||||
@ -43,3 +54,45 @@ export const connect = () => (dispatch: Dispatch) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const findOrCreateEncryptionKeys = () => async (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
): Promise<CryptoKeyPair | undefined> => {
|
||||||
|
|
||||||
|
let identity: Identity | undefined
|
||||||
|
try {
|
||||||
|
identity = await getIdentity(ME)
|
||||||
|
} catch (err) {
|
||||||
|
dispatch(NotifyActions.error('This browser does not support IndexedDB'))
|
||||||
|
debug('IndexedDB error: %s', err)
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
if (identity && identity.privateKey) {
|
||||||
|
dispatch(NotifyActions.info('Using encryption keys from IndexedDB'))
|
||||||
|
return {
|
||||||
|
privateKey: identity.privateKey,
|
||||||
|
publicKey: identity.publicKey,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let keyPair: CryptoKeyPair
|
||||||
|
try {
|
||||||
|
keyPair = await generateECDHKeyPair()
|
||||||
|
} catch (err) {
|
||||||
|
dispatch(NotifyActions.error('Unable to generate encryption keys'))
|
||||||
|
debug('Unable to generate encryption keys: %s', err)
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
const { privateKey, publicKey } = keyPair
|
||||||
|
|
||||||
|
identity = {
|
||||||
|
id: ME,
|
||||||
|
privateKey,
|
||||||
|
publicKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
await saveIdentity(identity)
|
||||||
|
|
||||||
|
return keyPair
|
||||||
|
}
|
||||||
|
|||||||
34
src/client/db/identities.test.ts
Normal file
34
src/client/db/identities.test.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { getIdentity, saveIdentity } from './identities'
|
||||||
|
import { promisify } from './promisify'
|
||||||
|
import { generateECDHKeyPair } from '../crypto'
|
||||||
|
|
||||||
|
describe('identities', () => {
|
||||||
|
|
||||||
|
const TEST_DB = 'TEST_DB'
|
||||||
|
|
||||||
|
let keyPair: CryptoKeyPair
|
||||||
|
beforeEach(async () => {
|
||||||
|
keyPair = await generateECDHKeyPair()
|
||||||
|
})
|
||||||
|
afterEach(async () => {
|
||||||
|
await promisify(window.indexedDB.deleteDatabase(TEST_DB))
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('getIdentity', () => {
|
||||||
|
it('reads saved identity', async () => {
|
||||||
|
const identity = {
|
||||||
|
id: 'one',
|
||||||
|
...keyPair,
|
||||||
|
}
|
||||||
|
await saveIdentity(identity)
|
||||||
|
const identity2 = await getIdentity(identity.id)
|
||||||
|
expect(identity2).not.toBe(identity)
|
||||||
|
expect(identity2).toEqual(identity)
|
||||||
|
})
|
||||||
|
it('does not fail when not found', async () => {
|
||||||
|
const identity = await getIdentity('notfound')
|
||||||
|
expect(identity).toBe(undefined)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
53
src/client/db/identities.ts
Normal file
53
src/client/db/identities.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { open } from './open'
|
||||||
|
import { promisify } from './promisify'
|
||||||
|
import { exportKey, importKey } from '../crypto'
|
||||||
|
|
||||||
|
export interface Identity {
|
||||||
|
id: string
|
||||||
|
publicKey: CryptoKey
|
||||||
|
privateKey?: CryptoKey
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StoredIdentity {
|
||||||
|
id: string
|
||||||
|
publicKey: JsonWebKey
|
||||||
|
privateKey?: JsonWebKey
|
||||||
|
}
|
||||||
|
|
||||||
|
const DB = 'PEERCALLS'
|
||||||
|
|
||||||
|
export async function getIdentity(id: string): Promise<Identity | undefined> {
|
||||||
|
const db = await open(DB, 1)
|
||||||
|
let stored: StoredIdentity | undefined
|
||||||
|
try {
|
||||||
|
const tx = db.transaction('identities', 'readonly')
|
||||||
|
const store = tx.objectStore('identities')
|
||||||
|
stored = await promisify(store.get(id))
|
||||||
|
} finally {
|
||||||
|
db.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return stored && {
|
||||||
|
id: stored.id,
|
||||||
|
privateKey: stored.privateKey && await importKey(stored.privateKey),
|
||||||
|
publicKey: await importKey(stored.publicKey),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function saveIdentity(identity: Identity): Promise<void> {
|
||||||
|
const stored = {
|
||||||
|
id: identity.id,
|
||||||
|
privateKey: identity.privateKey && await exportKey(identity.privateKey),
|
||||||
|
publicKey: await exportKey(identity.publicKey),
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = await open(DB, 1)
|
||||||
|
try {
|
||||||
|
const tx = db.transaction('identities', 'readwrite')
|
||||||
|
const store = tx.objectStore('identities')
|
||||||
|
await promisify(store.put(stored))
|
||||||
|
await promisify(tx)
|
||||||
|
} finally {
|
||||||
|
db.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,2 +1,3 @@
|
|||||||
export { promisify } from './promisify'
|
export { promisify } from './promisify'
|
||||||
export { open } from './open'
|
export { open } from './open'
|
||||||
|
export * from './identities'
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user