From 7aef4bed5035722c789fda329090b34236c7b640 Mon Sep 17 00:00:00 2001 From: Pascal Perrenoud Date: Wed, 11 Sep 2024 22:43:52 +0200 Subject: [PATCH] Allow to add context on key derivations --- src/kdf.ts | 24 +++++++++++++++++++----- src/private-box.ts | 10 +++++----- src/private-wrap.ts | 10 +++++----- src/pwd-box.ts | 16 ++++++++-------- src/pwd-wrap.ts | 16 ++++++++-------- 5 files changed, 45 insertions(+), 31 deletions(-) diff --git a/src/kdf.ts b/src/kdf.ts index 6b0ec53..e4249c2 100644 --- a/src/kdf.ts +++ b/src/kdf.ts @@ -13,7 +13,7 @@ export enum DHusage { wrap } -export async function hkdf(key: Uint8Array, usage: Usage): Promise { +export async function hkdf(key: Uint8Array, usage: Usage, context?: string): Promise { log.trace('HKDF') log.trace(`usage : ${usage}`) @@ -53,7 +53,7 @@ export async function hkdf(key: Uint8Array, usage: Usage): Promise { throw 'Unknown usage for HDKF' } } - const info = new TextEncoder().encode(info_txt) + const info = new TextEncoder().encode(info_txt + '-' + (context ?? 'key')) return await crypto.subtle.deriveKey( { @@ -87,15 +87,29 @@ 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 outputUsage: KeyUsage[] = usage === DHusage.box ? ['encrypt', 'decrypt'] : ['wrapKey', 'unwrapKey'] - return await crypto.subtle.deriveKey( + + const seed_bits = await crypto.subtle.deriveBits( { name: consts.ECDH.name, public: pubkey }, privkey, + 256 + ) + + const seed = await crypto.subtle.importKey('raw', seed_bits, 'HKDF', false, ['deriveKey']) + + const outputUsage: KeyUsage[] = usage === DHusage.box ? ['encrypt', 'decrypt'] : ['wrapKey', 'unwrapKey'] + return await crypto.subtle.deriveKey( + { + name: 'HKDF', + hash: 'SHA-512', + salt: new Uint8Array(16), + info: new TextEncoder().encode('ECDH-' + (context ?? 'key')) + }, + seed, { name: consts.ENCRYPTION, length: 256 diff --git a/src/private-box.ts b/src/private-box.ts index 8f42dab..8b6684d 100644 --- a/src/private-box.ts +++ b/src/private-box.ts @@ -14,19 +14,19 @@ export default class PrivateBox { public static async gen(extractable: boolean = true): Promise { log.trace('generate keypair') - return await crypto.subtle.generateKey(consts.ECDH, extractable, ['deriveKey']) as CryptoKeyPair + return await crypto.subtle.generateKey(consts.ECDH, extractable, ['deriveBits']) as CryptoKeyPair } - public static async encrypt(data: Uint8Array, pubkey: CryptoKey): 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) + const key = await ecdh(tmp_pair.privateKey, pubkey, DHusage.box, context) // TODO : null const box = await SecretBox.encrypt(data, key) return new this(tmp_pair.publicKey, box) } - public async decrypt(privkey: CryptoKey): Promise { + public async decrypt(privkey: CryptoKey, context?: string): Promise { log.trace('decrypt') - const key = await ecdh(privkey, this.pubkey, DHusage.box) + const key = await ecdh(privkey, this.pubkey, DHusage.box, context) // TODO : null return await this.box.decrypt(key) } diff --git a/src/private-wrap.ts b/src/private-wrap.ts index 7b90ed4..94b93e5 100644 --- a/src/private-wrap.ts +++ b/src/private-wrap.ts @@ -14,19 +14,19 @@ export default class PrivateWrap { public static async gen(extractable: boolean = true): Promise { log.trace('generate keypair') - return await crypto.subtle.generateKey(consts.ECDH, extractable, ['deriveKey']) as CryptoKeyPair + return await crypto.subtle.generateKey(consts.ECDH, extractable, ['deriveBits']) as CryptoKeyPair } - public static async wrap(data: CryptoKey, pubkey: CryptoKey): 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) + const kd = await ecdh(tmp_keypair.privateKey, pubkey, DHusage.wrap, context) // TODO : null const box = await SecretWrap.wrap(data, kd) return new this(box, tmp_keypair.publicKey) } - public async unwrap(privkey: CryptoKey): Promise { + public async unwrap(privkey: CryptoKey, context?: string): Promise { log.trace('unwrap') - const kd = await ecdh(privkey, this.pubkey, DHusage.wrap) + const kd = await ecdh(privkey, this.pubkey, DHusage.wrap, context) // TODO : null return await this.box.unwrap(kd) } diff --git a/src/pwd-box.ts b/src/pwd-box.ts index 09fc033..715bc6d 100644 --- a/src/pwd-box.ts +++ b/src/pwd-box.ts @@ -11,21 +11,21 @@ export default class PwdBox { private readonly salt: Uint8Array ) {} - private static async derive(pwd: string, salt: Uint8Array): Promise { - const k = await pbkdf(salt, pwd) - return await hkdf(k, Usage.box) as CryptoKey + 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 } - public static async encrypt(data: Uint8Array, pwd: 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) - const box = await SecretBox.encrypt(data, k) + const k = await PwdBox.derive(pwd, salt, context) // TODO : null + const box = await SecretBox.encrypt(data, k) // TODO : null return new PwdBox(box, salt) } - public async decrypt(pwd: string): Promise { + public async decrypt(pwd: string, context?: string): Promise { log.trace('decrypt') - const k = await PwdBox.derive(pwd, this.salt) + const k = await PwdBox.derive(pwd, this.salt, context) // TODO : null return await this.box.decrypt(k) } diff --git a/src/pwd-wrap.ts b/src/pwd-wrap.ts index 43c7995..8a479a7 100644 --- a/src/pwd-wrap.ts +++ b/src/pwd-wrap.ts @@ -11,21 +11,21 @@ export default class PwdWrap { private readonly salt: Uint8Array ) {} - private static async derive(pwd: string, salt: Uint8Array): Promise { - const k = await pbkdf(salt, pwd) - return await hkdf(k, Usage.wrap) as CryptoKey + 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 } - public static async wrap(data: CryptoKey, pwd: 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) - const box = await SecretWrap.wrap(data, k) + const k = await PwdWrap.derive(pwd, salt, context) // TODO : null + const box = await SecretWrap.wrap(data, k) // TODO : null return new PwdWrap(box, salt) } - public async unwrap(pwd: string): Promise { + public async unwrap(pwd: string, context?: string): Promise { log.trace('unwrap') - const k = await PwdWrap.derive(pwd, this.salt) + const k = await PwdWrap.derive(pwd, this.salt, context) // TODO : null return await this.box.unwrap(k) }