2024-10-23 00:53:56 +00:00
|
|
|
import { useCallback } from 'react'
|
|
|
|
import { useVaultConfigurator } from './use-vault-configurator'
|
|
|
|
import { fromHex, toHex } from '@/lib/hex'
|
|
|
|
|
|
|
|
export default function useVault () {
|
|
|
|
const { key } = useVaultConfigurator()
|
|
|
|
|
|
|
|
const encrypt = useCallback(async (value) => {
|
|
|
|
if (!key) throw new Error('no vault key set')
|
2024-10-27 07:43:45 +00:00
|
|
|
return await encryptValue(key.key, value)
|
2024-10-23 00:53:56 +00:00
|
|
|
}, [key])
|
|
|
|
|
2024-10-27 07:43:45 +00:00
|
|
|
const decrypt = useCallback(async ({ iv, value }) => {
|
2024-10-23 00:53:56 +00:00
|
|
|
if (!key) throw new Error('no vault key set')
|
2024-10-27 07:43:45 +00:00
|
|
|
return await decryptValue(key.key, { iv, value })
|
2024-10-23 00:53:56 +00:00
|
|
|
}, [key])
|
|
|
|
|
|
|
|
return { encrypt, decrypt, isActive: !!key }
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Encrypt data using AES-GCM
|
|
|
|
* @param {CryptoKey} sharedKey - the key to use for encryption
|
2024-10-27 07:43:45 +00:00
|
|
|
* @param {Object} value - the value to encrypt
|
|
|
|
* @returns {Promise<Object>} an object with iv and value properties, can be passed to decryptValue to get the original data back
|
2024-10-23 00:53:56 +00:00
|
|
|
*/
|
2024-10-27 07:43:45 +00:00
|
|
|
export async function encryptValue (sharedKey, value) {
|
2024-10-23 00:53:56 +00:00
|
|
|
// random IVs are _really_ important in GCM: reusing the IV once can lead to catastrophic failure
|
|
|
|
// see https://crypto.stackexchange.com/questions/26790/how-bad-it-is-using-the-same-iv-twice-with-aes-gcm
|
2024-10-27 07:43:45 +00:00
|
|
|
// 12 bytes (96 bits) is the recommended IV size for AES-GCM
|
2024-10-23 00:53:56 +00:00
|
|
|
const iv = window.crypto.getRandomValues(new Uint8Array(12))
|
2024-10-27 07:43:45 +00:00
|
|
|
const encoded = new TextEncoder().encode(JSON.stringify(value))
|
2024-10-23 00:53:56 +00:00
|
|
|
const encrypted = await window.crypto.subtle.encrypt(
|
|
|
|
{
|
|
|
|
name: 'AES-GCM',
|
|
|
|
iv
|
|
|
|
},
|
|
|
|
sharedKey,
|
|
|
|
encoded
|
|
|
|
)
|
2024-10-27 07:43:45 +00:00
|
|
|
return {
|
2024-10-23 00:53:56 +00:00
|
|
|
iv: toHex(iv.buffer),
|
2024-10-27 07:43:45 +00:00
|
|
|
value: toHex(encrypted)
|
|
|
|
}
|
2024-10-23 00:53:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Decrypt data using AES-GCM
|
|
|
|
* @param {CryptoKey} sharedKey - the key to use for decryption
|
2024-10-27 07:43:45 +00:00
|
|
|
* @param {Object} encryptedValue - the encrypted value as returned by encryptValue
|
2024-10-23 00:53:56 +00:00
|
|
|
* @returns {Promise<Object>} the original unencrypted data
|
|
|
|
*/
|
2024-10-27 07:43:45 +00:00
|
|
|
export async function decryptValue (sharedKey, { iv, value }) {
|
2024-10-23 00:53:56 +00:00
|
|
|
const decrypted = await window.crypto.subtle.decrypt(
|
|
|
|
{
|
|
|
|
name: 'AES-GCM',
|
|
|
|
iv: fromHex(iv)
|
|
|
|
},
|
|
|
|
sharedKey,
|
2024-10-27 07:43:45 +00:00
|
|
|
fromHex(value)
|
2024-10-23 00:53:56 +00:00
|
|
|
)
|
|
|
|
const decoded = new TextDecoder().decode(decrypted)
|
|
|
|
return JSON.parse(decoded)
|
|
|
|
}
|