Jerko Steiner 023c2f640b Make private key extractable too.
Main reason: https://bugzilla.mozilla.org/show_bug.cgi?id=1133698

Unfortunately, because of this bug Firefox cannot save non-exportable
keys to IndexedDB. As a workaround, we call exportKey before saving and
store JsonWebKey instead. We use importKey again while reading the key
from the database.

Extra reason for this change - during testing isomorphic-webcrypto
complains about keys read from IndexedDB.

While being less secure, this could also help users export their keys if
they want to migrate their identities to another computer...
2020-01-07 18:11:22 +01:00

108 lines
2.4 KiB
TypeScript

export function hasWebCryptoAPI(): boolean {
return !!(window && window.crypto && window.crypto.subtle)
}
export function hasWebCryptoAPI2(): boolean {
return !!(window?.crypto?.subtle)
}
export async function generateECDHKeyPair() {
const key = await window.crypto.subtle.generateKey(
{
name: 'ECDH',
namedCurve: 'P-256',
},
/* extractable */ true,
['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 */ true,
['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)
}
export async function exportKey(key: CryptoKey) {
return await window.crypto.subtle.exportKey('jwk', key)
}
function ab2str(
buf: ArrayBuffer,
ArrayType: typeof Uint16Array | typeof Uint8Array = Uint16Array,
): string {
return String.fromCharCode.apply(
null, new ArrayType(buf) as unknown as number[])
}
export async function importKey(keyData: JsonWebKey) {
if (keyData.kty !== 'EC' || keyData.crv !== 'P-256') {
throw new Error(`Unsupported key type: ${keyData.kty}, crv: ${keyData.crv}`)
}
const key = await window.crypto.subtle.importKey(
'jwk',
keyData,
{
name: 'ECDH',
namedCurve: keyData.crv,
},
/* extractable */ true,
keyData.key_ops || [],
)
return key
}
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
}