diff --git a/src/secret-wrap.ts b/src/secret-wrap.ts index 3d1c712..e8a2962 100644 --- a/src/secret-wrap.ts +++ b/src/secret-wrap.ts @@ -1,16 +1,21 @@ import logger from 'log' import {a2b64, b642a} from 'misc' -import {BIT_LENGTH, ENCRYPTION_ALGORITHM} from './const' +import {BIT_LENGTH, ECDH_PARAMETERS, ENCRYPTION_ALGORITHM, SIGNATURE_KEY} from './const' const log = logger('crypto:secret-wrap') +enum Type { + SecretBox, // secret, decrypt, encrypt + SecretWrap, // secret, wrap, unwrap + Private, // private, deriveBits + signature // private, sign +} + export default class SecretWrap { constructor( private readonly cipher: Uint8Array, - private readonly algorithm: KeyAlgorithm, - private readonly usages: KeyUsage[], - private readonly type: 'raw' | 'pkcs8', - private readonly iv: Uint8Array + private readonly iv: Uint8Array, + private readonly type: Type ) {} public static async gen(extractable: boolean = true): Promise { @@ -25,25 +30,30 @@ export default class SecretWrap { ) } - private static format(type: KeyType): 'raw' | 'pkcs8' | null { - switch (type) { - case 'private': - return 'pkcs8' - case 'secret': - return 'raw' - default: - log.warn('Trying to wrap public key') - return null + private static identify(data: CryptoKey): Type { + if (data.type === 'secret') { + if (data.usages.includes('wrapKey')) return Type.SecretWrap + else return Type.SecretBox + } else { + if (data.usages.includes('sign')) return Type.signature + else return Type.Private + } + } + private information(): {format: KeyFormat, algorithm: AesKeyAlgorithm | EcKeyImportParams, usages: KeyUsage[]} { + switch (this.type) { + case Type.SecretWrap: return {algorithm: {name: ENCRYPTION_ALGORITHM, length: BIT_LENGTH()}, format: 'raw', usages: ['wrapKey', 'unwrapKey']} + case Type.SecretBox: return {algorithm: {name: ENCRYPTION_ALGORITHM, length: BIT_LENGTH()}, format: 'raw', usages: ['encrypt', 'decrypt']} + case Type.signature: return {algorithm: SIGNATURE_KEY(), format: 'pkcs8', usages: ['sign']} + case Type.Private: return {algorithm: ECDH_PARAMETERS(), format: 'pkcs8', usages: ['deriveBits']} } } 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 type = this.identify(data) + const format = (type === Type.SecretBox || type === Type.SecretWrap) ? 'raw' : 'pkcs8' let box: ArrayBuffer try { @@ -54,19 +64,22 @@ export default class SecretWrap { return null } - return new SecretWrap(new Uint8Array(box), data.algorithm, data.usages, format, iv) + return new SecretWrap(new Uint8Array(box), iv, type) } public async unwrap(key: CryptoKey): Promise { log.trace('unwrap') + + const info = this.information() + try { return await crypto.subtle.unwrapKey( - this.type, + info.format, this.cipher, key, {name: ENCRYPTION_ALGORITHM, iv: this.iv}, - this.algorithm, + info.algorithm, true, - this.usages + info.usages ) } catch (e) { log.warn('Failed to unwrap') @@ -77,45 +90,39 @@ export default class SecretWrap { public toString(): string { log.trace('toString') + const cipher = a2b64(this.cipher) const iv = a2b64(this.iv) - return JSON.stringify({ - cipher: a2b64(this.cipher), - algorithm: this.algorithm, - usages: this.usages, - type: this.type, - iv - }) + return `${this.type}.${iv}.${cipher}` } public static fromString(data: string): SecretWrap | null { log.trace('fromString') - const obj = SecretWrap.parseJSON(data) - if (obj === null) return null + const parts = data.split('.') + if (parts.length !== 3) { + log.warn(`Part count doesn't match : ${parts.length} != 3`) + return null + } + let type: Type try { - const cipher = b642a(obj.cipher) - if (cipher === null) return null - - const iv = b642a(obj.iv) - if (iv === null) return null - - return new SecretWrap(cipher, obj.algorithm, obj.usages, obj.type, iv) + type = parseInt(parts[0]) } catch (e) { - log.warn('Probable access to undefined field') + log.warn('Failed to parse type') + log.debug('Type :', parts[0]) log.debug('Error :', e) return null } - } - - private static parseJSON( - data: string - ): {cipher: string; iv: string; algorithm: KeyAlgorithm; usages: KeyUsage[]; type: 'raw' | 'pkcs8'} | null { - log.trace('parseJSON') - try { - return JSON.parse(data) - } catch (e) { - log.warn('Failed to parse JSON') + if (!(type in Type)) { + log.warn('Unknown type', type) return null } + + const iv = b642a(parts[1]) + if (iv === null) return null + + const cipher = b642a(parts[2]) + if (cipher === null) return null + + return new this(cipher, iv, type) } }