diff --git a/src/kdf.ts b/src/kdf.ts index dbd62b8..51cac13 100644 --- a/src/kdf.ts +++ b/src/kdf.ts @@ -13,11 +13,24 @@ export enum DHusage { wrap } -export async function hkdf(key: Uint8Array, usage: Usage, context?: string): Promise { +/** Minimum seed size : 32 bytes */ +export async function hkdf(key: Uint8Array, usage: Usage, context?: string): Promise { 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 { + +/** Minimum salt size : 16 bytes */ +export async function pbkdf(salt: Uint8Array, password: string): Promise { 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 { +export async function ecdh(privkey: CryptoKey, pubkey: CryptoKey, usage: DHusage, context?: string): Promise { 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']) diff --git a/src/private-box.ts b/src/private-box.ts index 8b6684d..218c9f0 100644 --- a/src/private-box.ts +++ b/src/private-box.ts @@ -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 { + public static async encrypt(data: Uint8Array, pubkey: CryptoKey, context?: string): Promise { 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 { 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) } diff --git a/src/private-wrap.ts b/src/private-wrap.ts index 94b93e5..5f2a926 100644 --- a/src/private-wrap.ts +++ b/src/private-wrap.ts @@ -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 { + public static async wrap(data: CryptoKey, pubkey: CryptoKey, context?: string): Promise { 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 { 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) } diff --git a/src/pwd-box.ts b/src/pwd-box.ts index 715bc6d..5849a9f 100644 --- a/src/pwd-box.ts +++ b/src/pwd-box.ts @@ -11,21 +11,31 @@ export default class PwdBox { private readonly salt: Uint8Array ) {} - private static async derive(pwd: string, salt: Uint8Array, context?: string): Promise { - 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 { + 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 { + public static async encrypt(data: Uint8Array, pwd: string, context?: string): Promise { 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 { 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) } diff --git a/src/pwd-wrap.ts b/src/pwd-wrap.ts index 8a479a7..18f5b2f 100644 --- a/src/pwd-wrap.ts +++ b/src/pwd-wrap.ts @@ -11,21 +11,32 @@ export default class PwdWrap { private readonly salt: Uint8Array ) {} - private static async derive(pwd: string, salt: Uint8Array, context?: string): Promise { - 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 { + 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 { + public static async wrap(data: CryptoKey, pwd: string, context?: string): Promise { 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 { 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) } diff --git a/src/secret-box.ts b/src/secret-box.ts index ea56b5e..60762a9 100644 --- a/src/secret-box.ts +++ b/src/secret-box.ts @@ -22,17 +22,27 @@ export default class SecretBox { ) } - public static async encrypt(data: Uint8Array, key: CryptoKey): Promise { + public static async encrypt(data: Uint8Array, key: CryptoKey): Promise { 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 { diff --git a/src/secret-wrap.ts b/src/secret-wrap.ts index e7e6a4c..2aa2e93 100644 --- a/src/secret-wrap.ts +++ b/src/secret-wrap.ts @@ -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 { + public static async wrap(data: CryptoKey, key: CryptoKey): Promise { 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 { @@ -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')