diff --git a/frontend/src/common/crypto.ts b/frontend/src/common/crypto.ts index f022ac5b7..e7d792872 100644 --- a/frontend/src/common/crypto.ts +++ b/frontend/src/common/crypto.ts @@ -32,10 +32,15 @@ export interface VaultConfigHeaderHub { devicesResourceUrl: string } -interface JWEPayload { +interface UserKeysJWEPayload { key: string } +interface VaultKeysWEPayload { + key: string + uvfKey: string +} + const GCM_NONCE_LEN = 12; export class VaultKeys { @@ -49,16 +54,19 @@ export class VaultKeys { length: 512 }; - + // in uvf setting, the vault masterKey is used to encrypt the vault metadata JWE using A256KW + private static readonly UVF_MASTERKEY_KEY_DESIGNATION = { name: 'AES-KW', length: 256 }; readonly masterKey: CryptoKey; + readonly uvfMasterKey: CryptoKey; - protected constructor(masterkey: CryptoKey) { - this.masterKey = masterkey; + protected constructor(masterKey: CryptoKey, uvfMasterKey: CryptoKey) { + this.masterKey = masterKey; + this.uvfMasterKey = uvfMasterKey; } /** - * Creates a new masterkey + * Creates a new masterkey (vault8 and uvf) * @returns A new masterkey */ public static async create(): Promise { @@ -67,23 +75,31 @@ export class VaultKeys { true, ['sign'] ); - return new VaultKeys(await key); + const uvfKey = crypto.subtle.generateKey( + VaultKeys.UVF_MASTERKEY_KEY_DESIGNATION, + true, + ['wrapKey', 'unwrapKey'] + ); + return new VaultKeys(await key, await uvfKey); } /** - * Decrypts the vault's masterkey using the user's private key + * Decrypts the vault's masterkey (vault8 and uvf) using the user's private key * @param jwe JWE containing the vault key * @param userPrivateKey The user's private key * @returns The masterkey */ public static async decryptWithUserKey(jwe: string, userPrivateKey: CryptoKey): Promise { let rawKey = new Uint8Array(); + let rawUvfKey = new Uint8Array(); try { - const payload: JWEPayload = await JWEParser.parse(jwe).decryptEcdhEs(userPrivateKey); + const payload: VaultKeysWEPayload = await JWEParser.parse(jwe).decryptEcdhEs(userPrivateKey); rawKey = base64.parse(payload.key); - const masterkey = crypto.subtle.importKey('raw', rawKey, VaultKeys.MASTERKEY_KEY_DESIGNATION, true, ['sign']); - return new VaultKeys(await masterkey); + rawUvfKey = base64.parse(payload.uvfKey); + const masterKey = crypto.subtle.importKey('raw', rawKey, VaultKeys.MASTERKEY_KEY_DESIGNATION, true, ['sign']); + const uvfMasterKey = crypto.subtle.importKey('raw', rawUvfKey, VaultKeys.UVF_MASTERKEY_KEY_DESIGNATION, true, ['sign']); + return new VaultKeys(await masterKey, await uvfMasterKey); } finally { rawKey.fill(0x00); } @@ -147,7 +163,8 @@ export class VaultKeys { true, ['verify'] ); - return [new VaultKeys(await masterkey), { privateKey: await privKey, publicKey: await pubKey }]; + // TODO https://github.com/encryption-alliance/unified-vault-format/pull/19 upstream legacy integration for uvf? + return [new VaultKeys(await masterkey, await masterkey), { privateKey: await privKey, publicKey: await pubKey }]; } catch (error) { throw new UnwrapKeyError(error); } @@ -180,7 +197,8 @@ export class VaultKeys { true, ['sign'] ); - return new VaultKeys(await key); + // TODO https://github.com/encryption-alliance/unified-vault-format/pull/19 upstream legacy integration for uvf? + return new VaultKeys(await key, await key); } public async createVaultConfig(kid: string, hubConfig: VaultConfigHeaderHub, payload: VaultConfigPayload): Promise { @@ -221,16 +239,18 @@ export class VaultKeys { } /** - * Encrypts this masterkey using the given public key + * Encrypts this masterkey (vault8 and uvf) using the given public key * @param userPublicKey The recipient's public key (DER-encoded) * @returns a JWE containing this Masterkey */ public async encryptForUser(userPublicKey: Uint8Array): Promise { const publicKey = await crypto.subtle.importKey('spki', userPublicKey, UserKeys.KEY_DESIGNATION, false, []); const rawkey = new Uint8Array(await crypto.subtle.exportKey('raw', this.masterKey)); + const rawUvfKey = new Uint8Array(await crypto.subtle.exportKey('raw', this.uvfMasterKey)); try { - const payload: JWEPayload = { - key: base64.stringify(rawkey) + const payload: VaultKeysWEPayload = { + key: base64.stringify(rawkey), + uvfKey: base64.stringify(rawUvfKey) }; return JWEBuilder.ecdhEs(publicKey).encrypt(payload); } finally { @@ -288,7 +308,7 @@ export class UserKeys { * @throws {UnwrapKeyError} when attempting to decrypt the private key using an incorrect setupCode */ public static async recover(encodedPublicKey: string, encryptedPrivateKey: string, setupCode: string): Promise { - const jwe: JWEPayload = await JWEParser.parse(encryptedPrivateKey).decryptPbes2(setupCode); + const jwe: UserKeysJWEPayload = await JWEParser.parse(encryptedPrivateKey).decryptPbes2(setupCode); const decodedPublicKey = base64.parse(encodedPublicKey, { loose: true }); const decodedPrivateKey = base64.parse(jwe.key, { loose: true }); const privateKey = crypto.subtle.importKey('pkcs8', decodedPrivateKey, UserKeys.KEY_DESIGNATION, true, UserKeys.KEY_USAGES); @@ -314,7 +334,7 @@ export class UserKeys { public async encryptedPrivateKey(setupCode: string): Promise { const rawkey = new Uint8Array(await crypto.subtle.exportKey('pkcs8', this.keyPair.privateKey)); try { - const payload: JWEPayload = { + const payload: UserKeysJWEPayload = { key: base64.stringify(rawkey) }; return await JWEBuilder.pbes2(setupCode).encrypt(payload); @@ -333,7 +353,7 @@ export class UserKeys { const publicKey = await UserKeys.publicKey(devicePublicKey); const rawkey = new Uint8Array(await crypto.subtle.exportKey('pkcs8', this.keyPair.privateKey)); try { - const payload: JWEPayload = { + const payload: UserKeysJWEPayload = { key: base64.stringify(rawkey) }; return JWEBuilder.ecdhEs(publicKey).encrypt(payload); @@ -353,7 +373,7 @@ export class UserKeys { const publicKey = await UserKeys.publicKey(userPublicKey); let rawKey = new Uint8Array(); try { - const payload: JWEPayload = await JWEParser.parse(jwe).decryptEcdhEs(browserPrivateKey); + const payload: UserKeysJWEPayload = await JWEParser.parse(jwe).decryptEcdhEs(browserPrivateKey); rawKey = base64.parse(payload.key); const privateKey = await crypto.subtle.importKey('pkcs8', rawKey, UserKeys.KEY_DESIGNATION, true, UserKeys.KEY_USAGES); return new UserKeys({ publicKey: publicKey, privateKey: privateKey }); @@ -371,221 +391,6 @@ export class UserKeys { } } - -export class UvfVaultKeys { - // in uvf setting, the vault masterKey is used to encrypt the vault metadata JWE using A256KW - private static readonly MASTERKEY_KEY_DESIGNATION = { name: 'AES-KW', length: 256 }; - - readonly masterKey: CryptoKey; - - protected constructor(masterkey: CryptoKey) { - this.masterKey = masterkey; - } - - /** - * Creates a new masterkey - * @returns A new masterkey - */ - public static async create(): Promise { - const key = crypto.subtle.generateKey( - UvfVaultKeys.MASTERKEY_KEY_DESIGNATION, - true, - // TODO https://github.com/encryption-alliance/unified-vault-format/pull/19 is this correct? - ['wrapKey', 'unwrapKey'] - ); - return new UvfVaultKeys(await key); - } - - /** - * Decrypts the vault's masterkey using the user's private key - * @param jwe JWE containing the vault key - * @param userPrivateKey The user's private key - * @returns The masterkey - */ - public static async decryptWithUserKey(jwe: string, userPrivateKey: CryptoKey): Promise { - let rawKey = new Uint8Array(); - try { - const payload: JWEPayload = await JWEParser.parse(jwe).decryptEcdhEs(userPrivateKey); - rawKey = base64.parse(payload.key); - const masterkey = crypto.subtle.importKey('raw', rawKey, UvfVaultKeys.MASTERKEY_KEY_DESIGNATION, true, - // TODO https://github.com/encryption-alliance/unified-vault-format/pull/19 is this correct? - ['wrapKey', 'unwrapKey'] - ); - - return new UvfVaultKeys(await masterkey); - } finally { - rawKey.fill(0x00); - } - } - - /** - * Unwraps keys protected by the legacy "Vault Admin Password". - * @param vaultAdminPassword Vault Admin Password - * @param wrappedMasterkey The wrapped masterkey - * @param wrappedOwnerPrivateKey The wrapped owner private key - * @param ownerPublicKey The owner public key - * @param salt PBKDF2 Salt - * @param iterations PBKDF2 Iterations - * @returns The unwrapped key material. - * @throws WrongPasswordError, if the wrong password is used - * @deprecated Only used during "claim vault ownership" workflow for legacy vaults - */ - public static async decryptWithAdminPassword(vaultAdminPassword: string, wrappedMasterkey: string, wrappedOwnerPrivateKey: string, ownerPublicKey: string, salt: string, iterations: number): Promise<[UvfVaultKeys, CryptoKeyPair]> { - // pbkdf2: - const encodedPw = new TextEncoder().encode(vaultAdminPassword); - const pwKey = crypto.subtle.importKey('raw', encodedPw, 'PBKDF2', false, ['deriveKey']); - const kek = crypto.subtle.deriveKey( - { - name: 'PBKDF2', - hash: 'SHA-256', - salt: base64.parse(salt, { loose: true }), - iterations: iterations - }, - await pwKey, - { name: 'AES-GCM', length: 256 }, - false, - ['unwrapKey'] - ); - // unwrapping - const decodedMasterKey = base64.parse(wrappedMasterkey, { loose: true }); - const decodedPrivateKey = base64.parse(wrappedOwnerPrivateKey, { loose: true }); - const decodedPublicKey = base64.parse(ownerPublicKey, { loose: true }); - try { - const masterkey = crypto.subtle.unwrapKey( - 'raw', - decodedMasterKey.slice(GCM_NONCE_LEN), - await kek, - { name: 'AES-GCM', iv: decodedMasterKey.slice(0, GCM_NONCE_LEN) }, - UvfVaultKeys.MASTERKEY_KEY_DESIGNATION, - true, - ['sign'] - ); - const privKey = crypto.subtle.unwrapKey( - 'pkcs8', - decodedPrivateKey.slice(GCM_NONCE_LEN), - await kek, - { name: 'AES-GCM', iv: decodedPrivateKey.slice(0, GCM_NONCE_LEN) }, - { name: 'ECDSA', namedCurve: 'P-384' }, - false, - ['sign'] - ); - const pubKey = crypto.subtle.importKey( - 'spki', - decodedPublicKey, - { name: 'ECDSA', namedCurve: 'P-384' }, - true, - ['verify'] - ); - return [new UvfVaultKeys(await masterkey), { privateKey: await privKey, publicKey: await pubKey }]; - } catch (error) { - throw new UnwrapKeyError(error); - } - } - - /** - * Restore the master key from a given recovery key, create a new admin signature key pair. - * @param recoveryKey The recovery key - * @returns The recovered master key - * @throws Error, if passing a malformed recovery key - */ - public static async recover(recoveryKey: string): Promise { - // decode and check recovery key: - const decoded = wordEncoder.decode(recoveryKey); - if (decoded.length !== 66) { - throw new Error('Invalid recovery key length.'); - } - const decodedKey = decoded.subarray(0, 64); - const crc32 = CRC32.compute(decodedKey); - if (decoded[64] !== (crc32 & 0xFF) - || decoded[65] !== (crc32 >> 8 & 0xFF)) { - throw new Error('Invalid recovery key checksum.'); - } - - // construct new UvfVaultKeys from recovered key - const key = crypto.subtle.importKey( - 'raw', - decodedKey, - UvfVaultKeys.MASTERKEY_KEY_DESIGNATION, - true, - ['sign'] - ); - return new UvfVaultKeys(await key); - } - - public async createVaultConfig(kid: string, hubConfig: VaultConfigHeaderHub, payload: VaultConfigPayload): Promise { - const header = JSON.stringify({ - kid: kid, - typ: 'jwt', - alg: 'HS256', - hub: hubConfig - }); - const payloadJson = JSON.stringify(payload); - const encoder = new TextEncoder(); - const unsignedToken = base64url.stringify(encoder.encode(header), { pad: false }) + '.' + base64url.stringify(encoder.encode(payloadJson), { pad: false }); - const encodedUnsignedToken = new TextEncoder().encode(unsignedToken); - const signature = await crypto.subtle.sign( - 'HMAC', - this.masterKey, - encodedUnsignedToken - ); - return unsignedToken + '.' + base64url.stringify(new Uint8Array(signature), { pad: false }); - } - - public async hashDirectoryId(cleartextDirectoryId: string): Promise { - const dirHash = new TextEncoder().encode(cleartextDirectoryId); - const rawkey = new Uint8Array(await crypto.subtle.exportKey('raw', this.masterKey)); - try { - // miscreant lib requires mac key first and then the enc key - const encKey = rawkey.subarray(0, rawkey.length / 2 | 0); - const macKey = rawkey.subarray(rawkey.length / 2 | 0); - const shiftedRawKey = new Uint8Array([...macKey, ...encKey]); - const key = await miscreant.SIV.importKey(shiftedRawKey, 'AES-SIV'); - const ciphertext = await key.seal(dirHash, []); - // hash is only used as deterministic scheme for the root dir - const hash = await crypto.subtle.digest('SHA-1', ciphertext); - return base32.stringify(new Uint8Array(hash)); - } finally { - rawkey.fill(0x00); - } - } - - /** - * Encrypts this masterkey using the given public key - * @param userPublicKey The recipient's public key (DER-encoded) - * @returns a JWE containing this Masterkey - */ - public async encryptForUser(userPublicKey: Uint8Array): Promise { - const publicKey = await crypto.subtle.importKey('spki', userPublicKey, UserKeys.KEY_DESIGNATION, false, []); - const rawkey = new Uint8Array(await crypto.subtle.exportKey('raw', this.masterKey)); - try { - const payload: JWEPayload = { - key: base64.stringify(rawkey) - }; - - return JWEBuilder.ecdhEs(publicKey).encrypt(payload); - } finally { - rawkey.fill(0x00); - } - } - - /** - * Encode masterkey for offline backup purposes, allowing re-importing the key for recovery purposes - */ - public async createRecoveryKey(): Promise { - const rawkey = new Uint8Array(await crypto.subtle.exportKey('raw', this.masterKey)); - - // add 16 bit checksum: - const crc32 = CRC32.compute(rawkey); - const checksum = new Uint8Array(2); - checksum[0] = crc32 & 0xff; // append the least significant byte of the crc - checksum[1] = crc32 >> 8 & 0xff; // followed by the second-least significant byte - const combined = new Uint8Array([...rawkey, ...checksum]); - - // encode using human-readable words: - return wordEncoder.encodePadded(combined); - } -} - export class BrowserKeys { public static readonly KEY_USAGES: KeyUsage[] = ['deriveBits']; diff --git a/frontend/src/common/vaultconfig.ts b/frontend/src/common/vaultconfig.ts index 71ec39adc..7ba183c04 100644 --- a/frontend/src/common/vaultconfig.ts +++ b/frontend/src/common/vaultconfig.ts @@ -42,15 +42,10 @@ export class VaultConfig { public async exportTemplate(): Promise { const zip = new JSZip(); + // TODO https://github.com/encryption-alliance/unified-vault-format/pull/19 what about vault.uvf? Not in template but only in vault dto? zip.file('vault.cryptomator', this.vaultConfigToken); + zip.file('vault.uvf', this.vaultUvf); zip.folder('d')?.folder(this.rootDirHash.substring(0, 2))?.folder(this.rootDirHash.substring(2)); return zip.generateAsync({ type: 'blob' }); } - - public async exportMetadataTemplate(): Promise { - const zip = new JSZip(); - zip.file('vault.uvf', this.vaultUvf); - zip.folder('d')?.folder(this.rootDirHash.substring(0, 2))?.folder(this.rootDirHash.substring(2)); - return zip.generateAsync({ type: 'blob' }); - } } diff --git a/frontend/src/components/CreateVault.vue b/frontend/src/components/CreateVault.vue index 303b35335..93181b03b 100644 --- a/frontend/src/components/CreateVault.vue +++ b/frontend/src/components/CreateVault.vue @@ -185,7 +185,7 @@ import { base64 } from 'rfc4648'; import { onMounted, ref } from 'vue'; import { useI18n } from 'vue-i18n'; import backend, { PaymentRequiredError } from '../common/backend'; -import { VaultKeys } from '../common/crypto'; +import { VaultKeys, VaultMetadata } from '../common/crypto'; import { debounce } from '../common/util'; import { VaultConfig } from '../common/vaultconfig'; @@ -292,11 +292,14 @@ async function createVault() { throw new Error('Invalid state'); } const vaultId = crypto.randomUUID(); - // TODO https://github.com/encryption-alliance/unified-vault-format/pull/19 upstream integration - vaultConfig.value = await VaultConfig.create(vaultId, vaultKeys.value, ""); + const vaultMetadata: VaultMetadata = await VaultMetadata.create({ + enabled: true, + maxWotDepth: -1 + }); + const vaultMetadataEncrypted = await vaultMetadata.encryptWithMasterKey(vaultKeys.value.uvfMasterKey); + vaultConfig.value = await VaultConfig.create(vaultId, vaultKeys.value, vaultMetadataEncrypted); const ownerJwe = await vaultKeys.value.encryptForUser(base64.parse(owner.publicKey)); - // TODO https://github.com/encryption-alliance/unified-vault-format/pull/19 upstream integration - await backend.vaults.createOrUpdateVault(vaultId, vaultName.value, false, "", vaultDescription.value); + await backend.vaults.createOrUpdateVault(vaultId, vaultName.value, false, vaultMetadataEncrypted, vaultDescription.value); await backend.vaults.grantAccess(vaultId, { userId: owner.id, token: ownerJwe }); state.value = State.Finished; } catch (error) { diff --git a/frontend/src/components/DownloadVaultTemplateDialog.vue b/frontend/src/components/DownloadVaultTemplateDialog.vue index 7298231bb..b12400ac6 100644 --- a/frontend/src/components/DownloadVaultTemplateDialog.vue +++ b/frontend/src/components/DownloadVaultTemplateDialog.vue @@ -93,8 +93,7 @@ async function downloadVault() { } async function generateVaultZip(): Promise { - // TODO https://github.com/encryption-alliance/unified-vault-format/pull/19 upstream integration? - const config = await VaultConfig.create(props.vault.id, props.vaultKeys, ""); + const config = await VaultConfig.create(props.vault.id, props.vaultKeys, props.vault.metadata); return await config.exportTemplate(); } diff --git a/frontend/test/common/crypto.spec.ts b/frontend/test/common/crypto.spec.ts index 083a3d461..557f450d5 100644 --- a/frontend/test/common/crypto.spec.ts +++ b/frontend/test/common/crypto.spec.ts @@ -3,7 +3,7 @@ import chaiAsPromised from 'chai-as-promised'; import { before, describe } from 'mocha'; import { base64 } from 'rfc4648'; import { UnwrapKeyError, UserKeys, VaultKeys } from '../../src/common/crypto'; -import { UvfVaultKeys, VaultMetadata } from '../../src/common/crypto'; +import { VaultMetadata } from '../../src/common/crypto'; import { VaultMetadataJWEAutomaticAccessGrantDto } from '../../src/common/backend'; import { JWEParser } from '../../src/common/jwe'; @@ -147,104 +147,6 @@ describe('crypto', () => { }); }); }); - describe('UvfVaultKeys', () => { - it('create()', async () => { - const orig = await UvfVaultKeys.create(); - - expect(orig).to.be.not.null; - }); - - // TODO https://github.com/encryption-alliance/unified-vault-format/pull/19 fails - what to do with it? -// it('recover() succeeds for valid key', async () => { -// let recoveryKey = ` -// pathway lift abuse plenty export texture gentleman landscape beyond ceiling around leaf cafe charity -// border breakdown victory surely computer cat linger restrict infer crowd live computer true written amazed -// investor boot depth left theory snow whereby terminal weekly reject happiness circuit partial cup ad -// `; -// -// const recovered = await VaultKeys.recover(recoveryKey); -// -// const newMasterKey = await crypto.subtle.exportKey('jwk', recovered.masterKey); -// expect(newMasterKey).to.deep.include({ -// 'k': 'uwHiVreDbmv47K7oZzlwZbHcEql2Z29brbgFxKA7i54pXVPoHoxKK5rzZS3VEhPxHegQKCwa5Mk4ep7OsYutAw' -// }); -// }); - - it('recover() fails for invalid recovery key', async () => { - const noMultipleOfTwo = UvfVaultKeys.recover('pathway'); - const notInDict = UvfVaultKeys.recover('hallo bonjour'); - const wrongLength = UvfVaultKeys.recover('pathway lift'); - const invalidCrc = UvfVaultKeys.recover(` - pathway lift abuse plenty export texture gentleman landscape beyond ceiling around leaf cafe charity - border breakdown victory surely computer cat linger restrict infer crowd live computer true written amazed - investor boot depth left theory snow whereby terminal weekly reject happiness circuit partial cup wrong - `); - - return Promise.all([ - expect(noMultipleOfTwo).to.be.rejectedWith(Error, /input needs to be a multiple of two words/), - expect(notInDict).to.be.rejectedWith(Error, /Word not in dictionary/), - expect(wrongLength).to.be.rejectedWith(Error, /Invalid recovery key length/), - expect(invalidCrc).to.be.rejectedWith(Error, /Invalid recovery key checksum/), - ]); - }); - - describe('Prove Vault Ownership using Vault Admin Password', () => { - const wrapped = { - wrappedMasterkey: 'CMPyJiiOQXBZ8FVvFZs6UOh0kW83+eALeK3bwXfFF2CWsguJZIgCJch94liWCh9xTqW84LUZPyo6IDWbSALqbbdiwDcztT8M81/pgadhTETVtHO5Q1CFNLJ9UvY=', - wrappedOwnerPrivateKey: 'O9snY73/eVElnWRLgM404KH7WwO/Ed30Y0UrQQw6x3vxOdroJcjvPdJeSqLD2x4lVP7ceTjVt3IT2N9Mx+jhUQzqrb1E2EvEYlXrTaID1jSdBXZ6ScrI1RvU0iH9cfXf2cRy2x8QZvJyVMr34gLJ3Di/XGrnc/BrOm+aF2K4F9FJXvJFen3CnAs9ewB3Vk0A1wRLX3hW/Wx7eXt/0i1gxB8T/NcLu7xIU3+uusTHh9uajFkA5+z1+JgNHURaa1bT8j5WTtNWIHYT/sw+erMn6S0Uj1vL', - ownerPublicKey: 'MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAESzrRXmyI8VWFJg1dPUNbFcc9jZvjZEfH7ulKI1UkXAltd7RGWrcfFxqyGPcwu6AQhHUag3OvDzEr0uUQND4PXHQTXP5IDGdYhJhL+WLKjnGjQAw0rNGy5V29+aV+yseW', - salt: 'IdXyKICznXKm41gSb5OqfQ', - iterations: 1 - }; - - it('decryptWithAdminPassword() with wrong pw', () => { - return expect(UvfVaultKeys.decryptWithAdminPassword('wrong', wrapped.wrappedMasterkey, wrapped.wrappedOwnerPrivateKey, wrapped.ownerPublicKey, wrapped.salt, wrapped.iterations)).to.eventually.be.rejectedWith(UnwrapKeyError); - }); - // TODO https://github.com/encryption-alliance/unified-vault-format/pull/19 fails - what to do with it? -// it('decryptWithAdminPassword() with correct pw', () => { -// return expect(UvfVaultKeys.decryptWithAdminPassword('pass', wrapped.wrappedMasterkey, wrapped.wrappedOwnerPrivateKey, wrapped.ownerPublicKey, wrapped.salt, wrapped.iterations)).to.eventually.be.fulfilled; -// }); - }); - - describe('After creating new key material', () => { - let vaultKeys: UvfVaultKeys; - - beforeEach(async () => { - vaultKeys = await TestUvfVaultKeys.create(); - }); - - it('encryptForUser()', async () => { - const userKey = base64.parse('MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAERxQR+NRN6Wga01370uBBzr2NHDbKIC56tPUEq2HX64RhITGhii8Zzbkb1HnRmdF0aq6uqmUy4jUhuxnKxsv59A6JeK7Unn+mpmm3pQAygjoGc9wrvoH4HWJSQYUlsXDu'); - - const encrypted = await vaultKeys.encryptForUser(userKey); - expect(encrypted).to.be.not.null; - }); - - it('createRecoveryKey()', async () => { - const recoveryKey = await vaultKeys.createRecoveryKey(); - - expect(recoveryKey).to.eql('water water water water water water water water water water water water water water water water water water water water water asset partly partly partly partly partly partly partly partly partly partly partly partly partly partly partly partly partly partly partly partly option twist'); - }); - - // TODO https://github.com/encryption-alliance/unified-vault-format/pull/19 fails - what to do with it? -// describe('After creating a valid recovery key', () => { -// let recoveryKey: string; -// -// beforeEach(async () => { -// recoveryKey = await vaultKeys.createRecoveryKey(); -// }); -// it('recover() imports original key', async () => { -// const recovered = await UvfVaultKeys.recover(recoveryKey); -// -// const oldMasterKey = await crypto.subtle.exportKey('jwk', vaultKeys.masterKey); -// const newMasterKey = await crypto.subtle.exportKey('jwk', recovered.masterKey); -// expect(newMasterKey).to.deep.include({ -// 'k': oldMasterKey.k -// }); -// }); -// }); - }); - }); describe('UserKeys', () => { it('create()', async () => { const orig = await UserKeys.create(); @@ -271,19 +173,19 @@ describe('crypto', () => { describe('VaultMetadata', () => { // TODO review @sebi what else should we test? it('encryptWithMasterKey() and decryptWithMasterKey()', async () => { - const vaultKeys = await UvfVaultKeys.create(); - const masterKey: CryptoKey = vaultKeys.masterKey; + const vaultKeys = await VaultKeys.create(); + const uvfMasterKey: CryptoKey = vaultKeys.uvfMasterKey; const automaticAccessGrant: VaultMetadataJWEAutomaticAccessGrantDto ={ "enabled": true, "maxWotDepth": -1 } const orig = await VaultMetadata.create(automaticAccessGrant); expect(orig).to.be.not.null; - const jwe: string = await orig.encryptWithMasterKey(masterKey); + const jwe: string = await orig.encryptWithMasterKey(uvfMasterKey); expect(jwe).to.be.not.null; - const decrypted: VaultMetadata = await VaultMetadata.decryptWithMasterKey(jwe,masterKey); + const decrypted: VaultMetadata = await VaultMetadata.decryptWithMasterKey(jwe,uvfMasterKey); expect(JSON.stringify(decrypted.automaticAccessGrant)).to.eq(JSON.stringify(automaticAccessGrant)); - const decryptedRaw: any = await JWEParser.parse(jwe).decryptA256kw(masterKey); + const decryptedRaw: any = await JWEParser.parse(jwe).decryptA256kw(uvfMasterKey); expect(decryptedRaw.fileFormat).to.eq("AES-256-GCM-32k"); expect(decryptedRaw.latestFileKey).to.eq(orig.latestFileKey); expect(decryptedRaw.nameKey).to.eq(orig.nameKey); @@ -337,32 +239,8 @@ describe('crypto', () => { /* ---------- MOCKS ---------- */ class TestVaultKeys extends VaultKeys { - constructor(key: CryptoKey) { - super(key); - } - - static async create() { - const raw = new Uint8Array(64); - raw.fill(0x55, 0, 32); - raw.fill(0x77, 32, 64); - const key = await crypto.subtle.importKey( - 'raw', - raw, - { - name: 'HMAC', - hash: 'SHA-256', - length: 512 - }, - true, - ['sign'] - ); - return new TestVaultKeys(key); - } -} - -class TestUvfVaultKeys extends UvfVaultKeys { - constructor(key: CryptoKey) { - super(key); + constructor(masterKey: CryptoKey, uvfMasterKey: CryptoKey) { + super(masterKey, uvfMasterKey); } static async create() { @@ -380,7 +258,7 @@ class TestUvfVaultKeys extends UvfVaultKeys { true, ['sign'] ); - return new TestUvfVaultKeys(key); + return new TestVaultKeys(key, key); } }