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...
108 lines
2.4 KiB
TypeScript
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
|
|
}
|