Add safety with nulls
This commit is contained in:
+45
-13
@@ -13,11 +13,24 @@ export enum DHusage {
|
||||
wrap
|
||||
}
|
||||
|
||||
export async function hkdf(key: Uint8Array, usage: Usage, context?: string): Promise<CryptoKey> {
|
||||
/** Minimum seed size : 32 bytes */
|
||||
export async function hkdf(key: Uint8Array, usage: Usage, context?: string): Promise<CryptoKey | null> {
|
||||
log.trace('HKDF')
|
||||
log.trace(`usage : ${usage}`)
|
||||
|
||||
const material = await crypto.subtle.importKey('raw', key, 'HKDF', false, ['deriveKey', 'deriveBits'])
|
||||
if (key.length < 32) {
|
||||
log.warn("Seed's size is less than 32")
|
||||
return null
|
||||
}
|
||||
|
||||
let material: CryptoKey;
|
||||
try {
|
||||
material = await crypto.subtle.importKey('raw', key, 'HKDF', false, ['deriveKey'])
|
||||
} catch (e) {
|
||||
log.warn('Failed to import seed')
|
||||
log.debug('Error :', e)
|
||||
return null
|
||||
}
|
||||
|
||||
let algo
|
||||
let usages: KeyUsage[]
|
||||
@@ -50,11 +63,12 @@ export async function hkdf(key: Uint8Array, usage: Usage, context?: string): Pro
|
||||
break
|
||||
default: {
|
||||
log.warn(`Unknown usage '${usage}'`)
|
||||
throw 'Unknown usage for HDKF'
|
||||
return null
|
||||
}
|
||||
}
|
||||
const info = new TextEncoder().encode(info_txt + '-' + (context ?? 'key'))
|
||||
|
||||
// I don't see how a user could make this throw
|
||||
return await crypto.subtle.deriveKey(
|
||||
{
|
||||
name: 'HKDF',
|
||||
@@ -68,12 +82,23 @@ export async function hkdf(key: Uint8Array, usage: Usage, context?: string): Pro
|
||||
usages
|
||||
)
|
||||
}
|
||||
export async function pbkdf(salt: Uint8Array, password: string): Promise<Uint8Array> {
|
||||
|
||||
/** Minimum salt size : 16 bytes */
|
||||
export async function pbkdf(salt: Uint8Array, password: string): Promise<Uint8Array | null> {
|
||||
log.trace('PBKDF')
|
||||
|
||||
if (salt.length < 16) {
|
||||
log.warn("Seed's size is less than 16 bytes")
|
||||
return null
|
||||
}
|
||||
|
||||
// Could this throw ?
|
||||
const material = await crypto.subtle.importKey('raw', new TextEncoder().encode(password), 'PBKDF2', false, [
|
||||
'deriveBits'
|
||||
])
|
||||
|
||||
// TODO : Strength selection with enum
|
||||
// I don't think this could crash
|
||||
const buffer = await crypto.subtle.deriveBits(
|
||||
{
|
||||
name: 'PBKDF2',
|
||||
@@ -87,17 +112,24 @@ export async function pbkdf(salt: Uint8Array, password: string): Promise<Uint8Ar
|
||||
|
||||
return new Uint8Array(buffer)
|
||||
}
|
||||
export async function ecdh(privkey: CryptoKey, pubkey: CryptoKey, usage: DHusage, context?: string): Promise<CryptoKey> {
|
||||
export async function ecdh(privkey: CryptoKey, pubkey: CryptoKey, usage: DHusage, context?: string): Promise<CryptoKey | null> {
|
||||
log.trace('ecdh')
|
||||
|
||||
const seed_bits = await crypto.subtle.deriveBits(
|
||||
{
|
||||
name: consts.ECDH.name,
|
||||
public: pubkey
|
||||
},
|
||||
privkey,
|
||||
256
|
||||
)
|
||||
let seed_bits: ArrayBuffer;
|
||||
try {
|
||||
seed_bits = await crypto.subtle.deriveBits(
|
||||
{
|
||||
name: consts.ECDH.name,
|
||||
public: pubkey
|
||||
},
|
||||
privkey,
|
||||
256
|
||||
)
|
||||
} catch (e) {
|
||||
log.warn('Failed to derive bits from keypair')
|
||||
log.debug('Error :', e)
|
||||
return null
|
||||
}
|
||||
|
||||
const seed = await crypto.subtle.importKey('raw', seed_bits, 'HKDF', false, ['deriveKey'])
|
||||
|
||||
|
||||
+10
-3
@@ -17,16 +17,23 @@ export default class PrivateBox {
|
||||
return await crypto.subtle.generateKey(consts.ECDH, extractable, ['deriveBits']) as CryptoKeyPair
|
||||
}
|
||||
|
||||
public static async encrypt(data: Uint8Array, pubkey: CryptoKey, context?: string): Promise<PrivateBox> {
|
||||
public static async encrypt(data: Uint8Array, pubkey: CryptoKey, context?: string): Promise<PrivateBox | null> {
|
||||
log.trace('encrypt')
|
||||
|
||||
const tmp_pair = await PrivateBox.gen(false)
|
||||
const key = await ecdh(tmp_pair.privateKey, pubkey, DHusage.box, context) // TODO : null
|
||||
|
||||
const key = await ecdh(tmp_pair.privateKey, pubkey, DHusage.box, context)
|
||||
if (key === null) return null
|
||||
|
||||
const box = await SecretBox.encrypt(data, key)
|
||||
if (box === null) return null
|
||||
|
||||
return new this(tmp_pair.publicKey, box)
|
||||
}
|
||||
public async decrypt(privkey: CryptoKey, context?: string): Promise<Uint8Array | null> {
|
||||
log.trace('decrypt')
|
||||
const key = await ecdh(privkey, this.pubkey, DHusage.box, context) // TODO : null
|
||||
const key = await ecdh(privkey, this.pubkey, DHusage.box, context)
|
||||
if (key === null) return null
|
||||
return await this.box.decrypt(key)
|
||||
}
|
||||
|
||||
|
||||
+13
-4
@@ -17,16 +17,25 @@ export default class PrivateWrap {
|
||||
return await crypto.subtle.generateKey(consts.ECDH, extractable, ['deriveBits']) as CryptoKeyPair
|
||||
}
|
||||
|
||||
public static async wrap(data: CryptoKey, pubkey: CryptoKey, context?: string): Promise<PrivateWrap> {
|
||||
public static async wrap(data: CryptoKey, pubkey: CryptoKey, context?: string): Promise<PrivateWrap | null> {
|
||||
log.trace('wrap')
|
||||
|
||||
const tmp_keypair = await PrivateWrap.gen()
|
||||
const kd = await ecdh(tmp_keypair.privateKey, pubkey, DHusage.wrap, context) // TODO : null
|
||||
const box = await SecretWrap.wrap(data, kd)
|
||||
|
||||
const shared = await ecdh(tmp_keypair.privateKey, pubkey, DHusage.wrap, context)
|
||||
if (shared === null) return null
|
||||
|
||||
const box = await SecretWrap.wrap(data, shared)
|
||||
if (box === null) return null
|
||||
|
||||
return new this(box, tmp_keypair.publicKey)
|
||||
}
|
||||
public async unwrap(privkey: CryptoKey, context?: string): Promise<CryptoKey | null> {
|
||||
log.trace('unwrap')
|
||||
const kd = await ecdh(privkey, this.pubkey, DHusage.wrap, context) // TODO : null
|
||||
|
||||
const kd = await ecdh(privkey, this.pubkey, DHusage.wrap, context)
|
||||
if (kd === null) return null
|
||||
|
||||
return await this.box.unwrap(kd)
|
||||
}
|
||||
|
||||
|
||||
+17
-7
@@ -11,21 +11,31 @@ export default class PwdBox {
|
||||
private readonly salt: Uint8Array
|
||||
) {}
|
||||
|
||||
private static async derive(pwd: string, salt: Uint8Array, context?: string): Promise<CryptoKey> {
|
||||
const k = await pbkdf(salt, pwd) // TODO : null
|
||||
return await hkdf(k, Usage.box, context) as CryptoKey // TODO : null
|
||||
private static async derive(pwd: string, salt: Uint8Array, context?: string): Promise<CryptoKey | null> {
|
||||
const k = await pbkdf(salt, pwd)
|
||||
if (k === null) return null
|
||||
return await hkdf(k, Usage.box, context) as CryptoKey
|
||||
}
|
||||
|
||||
public static async encrypt(data: Uint8Array, pwd: string, context?: string): Promise<PwdBox> {
|
||||
public static async encrypt(data: Uint8Array, pwd: string, context?: string): Promise<PwdBox | null> {
|
||||
log.trace('encrypt')
|
||||
|
||||
const salt = crypto.getRandomValues(new Uint8Array(16))
|
||||
const k = await PwdBox.derive(pwd, salt, context) // TODO : null
|
||||
const box = await SecretBox.encrypt(data, k) // TODO : null
|
||||
|
||||
const k = await PwdBox.derive(pwd, salt, context)
|
||||
if (k === null) return null
|
||||
|
||||
const box = await SecretBox.encrypt(data, k)
|
||||
if (box === null) return null
|
||||
|
||||
return new PwdBox(box, salt)
|
||||
}
|
||||
public async decrypt(pwd: string, context?: string): Promise<Uint8Array | null> {
|
||||
log.trace('decrypt')
|
||||
const k = await PwdBox.derive(pwd, this.salt, context) // TODO : null
|
||||
|
||||
const k = await PwdBox.derive(pwd, this.salt, context)
|
||||
if (k === null) return null
|
||||
|
||||
return await this.box.decrypt(k)
|
||||
}
|
||||
|
||||
|
||||
+18
-7
@@ -11,21 +11,32 @@ export default class PwdWrap {
|
||||
private readonly salt: Uint8Array
|
||||
) {}
|
||||
|
||||
private static async derive(pwd: string, salt: Uint8Array, context?: string): Promise<CryptoKey> {
|
||||
const k = await pbkdf(salt, pwd) // TODO : null
|
||||
return await hkdf(k, Usage.wrap, context) as CryptoKey // TODO : null
|
||||
private static async derive(pwd: string, salt: Uint8Array, context?: string): Promise<CryptoKey | null> {
|
||||
const k = await pbkdf(salt, pwd)
|
||||
if (k === null) return null
|
||||
|
||||
return await hkdf(k, Usage.wrap, context)
|
||||
}
|
||||
|
||||
public static async wrap(data: CryptoKey, pwd: string, context?: string): Promise<PwdWrap> {
|
||||
public static async wrap(data: CryptoKey, pwd: string, context?: string): Promise<PwdWrap | null> {
|
||||
log.trace('wrap')
|
||||
|
||||
const salt = crypto.getRandomValues(new Uint8Array(16))
|
||||
const k = await PwdWrap.derive(pwd, salt, context) // TODO : null
|
||||
const box = await SecretWrap.wrap(data, k) // TODO : null
|
||||
|
||||
const k = await PwdWrap.derive(pwd, salt, context)
|
||||
if (k === null) return null
|
||||
|
||||
const box = await SecretWrap.wrap(data, k)
|
||||
if (box === null) return null
|
||||
|
||||
return new PwdWrap(box, salt)
|
||||
}
|
||||
public async unwrap(pwd: string, context?: string): Promise<CryptoKey | null> {
|
||||
log.trace('unwrap')
|
||||
const k = await PwdWrap.derive(pwd, this.salt, context) // TODO : null
|
||||
|
||||
const k = await PwdWrap.derive(pwd, this.salt, context)
|
||||
if (k === null) return null
|
||||
|
||||
return await this.box.unwrap(k)
|
||||
}
|
||||
|
||||
|
||||
+19
-9
@@ -22,17 +22,27 @@ export default class SecretBox {
|
||||
)
|
||||
}
|
||||
|
||||
public static async encrypt(data: Uint8Array, key: CryptoKey): Promise<SecretBox> {
|
||||
public static async encrypt(data: Uint8Array, key: CryptoKey): Promise<SecretBox | null> {
|
||||
log.trace('encrypt')
|
||||
|
||||
const iv = crypto.getRandomValues(new Uint8Array(12))
|
||||
const cipher = await crypto.subtle.encrypt(
|
||||
{
|
||||
name: consts.ENCRYPTION,
|
||||
iv
|
||||
},
|
||||
key,
|
||||
data
|
||||
)
|
||||
|
||||
let cipher: ArrayBuffer;
|
||||
try {
|
||||
cipher = await crypto.subtle.encrypt(
|
||||
{
|
||||
name: consts.ENCRYPTION,
|
||||
iv
|
||||
},
|
||||
key,
|
||||
data
|
||||
)
|
||||
} catch (e) {
|
||||
log.warn('Failed to encrypt')
|
||||
log.debug('Error :', e)
|
||||
return null
|
||||
}
|
||||
|
||||
return new SecretBox(iv, new Uint8Array(cipher))
|
||||
}
|
||||
public async decrypt(key: CryptoKey): Promise<Uint8Array | null> {
|
||||
|
||||
+28
-10
@@ -25,22 +25,35 @@ export default class SecretWrap {
|
||||
)
|
||||
}
|
||||
|
||||
private static format(type: KeyType): 'raw' | 'pkcs8' {
|
||||
private static format(type: KeyType): 'raw' | 'pkcs8' | null {
|
||||
switch (type) {
|
||||
case 'private':
|
||||
return 'pkcs8'
|
||||
case 'secret':
|
||||
return 'raw'
|
||||
default:
|
||||
throw "Don't wrap public keys please..."
|
||||
log.warn('Trying to wrap public key')
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
public static async wrap(data: CryptoKey, key: CryptoKey): Promise<SecretWrap> {
|
||||
public static async wrap(data: CryptoKey, key: CryptoKey): Promise<SecretWrap | null> {
|
||||
log.trace('wrap')
|
||||
|
||||
const format = SecretWrap.format(data.type)
|
||||
if (format === null) return null
|
||||
|
||||
const iv = crypto.getRandomValues(new Uint8Array(12))
|
||||
const box = await crypto.subtle.wrapKey(format, data, key, {name: consts.ENCRYPTION, iv})
|
||||
|
||||
let box: ArrayBuffer;
|
||||
try {
|
||||
box = await crypto.subtle.wrapKey(format, data, key, {name: consts.ENCRYPTION, iv})
|
||||
} catch (e) {
|
||||
log.warn('Failed to wrap key')
|
||||
log.debug('Error :', e)
|
||||
return null
|
||||
}
|
||||
|
||||
return new SecretWrap(new Uint8Array(box), data.algorithm, data.usages, format, iv)
|
||||
}
|
||||
public async unwrap(key: CryptoKey): Promise<CryptoKey | null> {
|
||||
@@ -79,13 +92,19 @@ export default class SecretWrap {
|
||||
const obj = SecretWrap.parseJSON(data)
|
||||
if (obj === null) return null
|
||||
|
||||
const cipher = b642a(obj.cipher)
|
||||
if (cipher === null) return null
|
||||
try {
|
||||
const cipher = b642a(obj.cipher)
|
||||
if (cipher === null) return null
|
||||
|
||||
const iv = b642a(obj.iv)
|
||||
if (iv === null) return null
|
||||
const iv = b642a(obj.iv)
|
||||
if (iv === null) return null
|
||||
|
||||
return new SecretWrap(cipher, obj.algorithm, obj.usages, obj.type, iv)
|
||||
return new SecretWrap(cipher, obj.algorithm, obj.usages, obj.type, iv)
|
||||
} catch (e) {
|
||||
log.warn('Probable access to undefined field')
|
||||
log.debug('Error :', e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private static parseJSON(
|
||||
@@ -93,7 +112,6 @@ export default class SecretWrap {
|
||||
): {cipher: string; iv: string; algorithm: KeyAlgorithm; usages: KeyUsage[]; type: 'raw' | 'pkcs8'} | null {
|
||||
log.trace('parseJSON')
|
||||
try {
|
||||
// TODO : Check the fields
|
||||
return JSON.parse(data)
|
||||
} catch (e) {
|
||||
log.warn('Failed to parse JSON')
|
||||
|
||||
Reference in New Issue
Block a user