Add safety with nulls

This commit is contained in:
2024-09-11 23:13:43 +02:00
parent 68cb002951
commit a0ffa2f682
7 changed files with 150 additions and 53 deletions
+45 -13
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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')