Skip to content

Commit

Permalink
pass in URL during vault template generation
Browse files Browse the repository at this point in the history
  • Loading branch information
overheadhunter committed May 21, 2024
1 parent 9b4f5ba commit 388426f
Show file tree
Hide file tree
Showing 6 changed files with 29 additions and 19 deletions.
3 changes: 2 additions & 1 deletion frontend/src/common/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@ export interface VaultTemplateProducing {

/**
* Produces a zip file containing the vault template.
* @param apiURL absolute base URL of the API
* @param vault The vault
*/
exportTemplate(vault: VaultDto): Promise<Blob>;
exportTemplate(apiURL: string, vault: VaultDto): Promise<Blob>;

}

Expand Down
20 changes: 14 additions & 6 deletions frontend/src/common/universalVaultFormat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,14 +281,16 @@ export class VaultMetadata {

/**
* Encrypts the vault metadata
* @param apiURL absolute base URL of the API
* @param vault the corresponding vault
* @param memberKey the vault members' AES wrapping key
* @param recoveryKey the public part of the recovery EC key pair
* @returns `vault.uvf` file contents
*/
public async encrypt(memberKey: MemberKey, recoveryKey: RecoveryKey): Promise<string> {
public async encrypt(apiURL: string, vault: VaultDto, memberKey: MemberKey, recoveryKey: RecoveryKey): Promise<string> {
const recoveryKeyID = `org.cryptomator.hub.recoverykey.${await getJwkThumbprint(recoveryKey.publicKey)}`;
const protectedHeader: JWEHeader = {
origin: `https://example.com/api/vaults/TODO/uvf/vault.uvf`, // TODO use ${absBackendBaseURL}. Couldn't do this, because tests fail for mysterious reasons
origin: `${apiURL}/vaults/${vault.id}/uvf/vault.uvf`,
jku: 'jwks.json' // URL relative to origin
};
const jwe = await JWE.build(this.payload(), protectedHeader).encrypt(Recipient.a256kw('org.cryptomator.hub.memberkey', memberKey.key), Recipient.ecdhEs(recoveryKeyID, recoveryKey.publicKey));
Expand Down Expand Up @@ -381,14 +383,20 @@ export class UniversalVaultFormat implements AccessTokenProducing, VaultTemplate
throw new Error('Recovery key not found in JWKS');
}

public async createMetadataFile(): Promise<string> {
return this.metadata.encrypt(this.memberKey, this.recoveryKey);
/**
* Creates the `vault.uvf` file
* @param apiURL absolute base URL of the API
* @param vault the vault
* @returns `vault.uvf` file contents
*/
public async createMetadataFile(apiURL: string, vault: VaultDto): Promise<string> {
return this.metadata.encrypt(apiURL, vault, this.memberKey, this.recoveryKey);
}

/** @inheritdoc */
public async exportTemplate(vault: VaultDto): Promise<Blob> {
public async exportTemplate(apiURL: string, vault: VaultDto): Promise<Blob> {
const zip = new JSZip();
zip.file('vault.uvf', this.createMetadataFile());
zip.file('vault.uvf', this.createMetadataFile(apiURL, vault));
// TODO: add root folder
//zip.folder('d')?.folder(rootDirHash.substring(0, 2))?.folder(rootDirHash.substring(2));
return zip.generateAsync({ type: 'blob' });
Expand Down
10 changes: 5 additions & 5 deletions frontend/src/common/vaultFormat8.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import JSZip from 'jszip';
import * as miscreant from 'miscreant';
import { base32, base64, base64url } from 'rfc4648';
import { VaultDto } from './backend';
import config, { absBackendBaseURL, absFrontendBaseURL } from './config';
import config, { absFrontendBaseURL } from './config';
import { AccessTokenProducing, GCM_NONCE_LEN, OtherVaultMember, UnwrapKeyError, UserKeys, VaultTemplateProducing } from './crypto';
import { CRC32, wordEncoder } from './util';

Expand Down Expand Up @@ -169,19 +169,19 @@ export class VaultFormat8 implements AccessTokenProducing, VaultTemplateProducin
}

/** @inheritdoc */
public async exportTemplate(vault: VaultDto): Promise<Blob> {
public async exportTemplate(apiURL: string, vault: VaultDto): Promise<Blob> {
const cfg = await config;

const kid = `hub+${absBackendBaseURL}vaults/${vault.id}`;
const kid = `hub+${apiURL}vaults/${vault.id}`;

const hubConfig: VaultConfigHeaderHub = {
clientId: cfg.keycloakClientIdCryptomator,
authEndpoint: cfg.keycloakAuthEndpoint,
tokenEndpoint: cfg.keycloakTokenEndpoint,
authSuccessUrl: `${absFrontendBaseURL}unlock-success?vault=${vault.id}`,
authErrorUrl: `${absFrontendBaseURL}unlock-error?vault=${vault.id}`,
apiBaseUrl: absBackendBaseURL,
devicesResourceUrl: `${absBackendBaseURL}devices/`,
apiBaseUrl: apiURL,
devicesResourceUrl: `${apiURL}devices/`,
};

const jwtPayload: VaultConfigPayload = {
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/components/CreateVault.vue
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ import { base64 } from 'rfc4648';
import { onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import backend, { AccessGrant, PaymentRequiredError, VaultDto } from '../common/backend';
import { absBackendBaseURL } from '../common/config';
import { VaultTemplateProducing } from '../common/crypto';
import { UniversalVaultFormat } from '../common/universalVaultFormat';
import { debounce } from '../common/util';
Expand Down Expand Up @@ -353,7 +354,7 @@ async function createVault() {
}
ownerGrant.token = await uvfVault.value.encryptForUser(base64.parse(owner.publicKey), true);
const recoveryPublicKey = await uvfVault.value.recoveryKey.serializePublicKey();
vault.value.uvfMetadataFile = await uvfVault.value.createMetadataFile();
vault.value.uvfMetadataFile = await uvfVault.value.createMetadataFile(absBackendBaseURL, vault.value);
vault.value.uvfKeySet = `{"keys": [${recoveryPublicKey}]}`;
break;
}
Expand Down Expand Up @@ -382,7 +383,7 @@ async function downloadVaultTemplate() {
onDownloadTemplateError.value = null;
try {
const templateProducer: VaultTemplateProducing = vaultFormat8.value || uvfVault.value!;
const blob = await templateProducer.exportTemplate(vault.value);
const blob = await templateProducer.exportTemplate(absBackendBaseURL, vault.value);
if (blob != null) {
saveAs(blob, `${vault.value.name}.zip`);
} else {
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/components/DownloadVaultTemplateDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import { saveAs } from 'file-saver';
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { VaultDto } from '../common/backend';
import { absBackendBaseURL } from '../common/config';
import { VaultTemplateProducing } from '../common/crypto';
const { t } = useI18n({ useScope: 'global' });
Expand Down Expand Up @@ -82,7 +83,7 @@ function show() {
async function downloadVault() {
onDownloadError.value = null;
try {
const blob = await props.vaultKeys.exportTemplate(props.vault);
const blob = await props.vaultKeys.exportTemplate(absBackendBaseURL, props.vault);
saveAs(blob, `${props.vault.name}.zip`);
open.value = false;
} catch (error) {
Expand Down
7 changes: 3 additions & 4 deletions frontend/test/common/universalVaultFormat.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { use as chaiUse, expect } from 'chai';
import chaiAsPromised from 'chai-as-promised';
import chaiBytes from 'chai-bytes';
import { before, describe } from 'mocha';
import { base64 } from 'rfc4648';
import { VaultDto } from '../../src/common/backend';
Expand All @@ -9,7 +8,6 @@ import { JsonJWE } from '../../src/common/jwe';
import { MemberKey, RecoveryKey, UniversalVaultFormat, VaultMetadata } from '../../src/common/universalVaultFormat';

chaiUse(chaiAsPromised);
chaiUse(chaiBytes);

// key coordinates from MDN examples:
const alicePublic: JsonWebKey = {
Expand Down Expand Up @@ -79,10 +77,11 @@ describe('UVF', () => {
});

it('decrypt(encrypt(orig)) == orig', async () => {
const dto: VaultDto = { id: '123', name: 'test', archived: false, creationTime: new Date() };
const vaultMemberKey = await MemberKey.create();
const recoveryKey = await RecoveryKey.create();

const uvfFile: string = await original.encrypt(vaultMemberKey, recoveryKey);
const uvfFile: string = await original.encrypt('https://example.com/api/', dto, vaultMemberKey, recoveryKey);
expect(uvfFile).to.be.not.null;
const json = JSON.parse(uvfFile);
expect(json).to.have.property('protected');
Expand Down Expand Up @@ -240,7 +239,7 @@ describe('UVF', () => {
});

it('createMetadataFile() creates a vault.uvf file', async () => {
const json = await uvf.createMetadataFile();
const json = await uvf.createMetadataFile('https.//example.com/api/', { id: '123', name: 'test', archived: false, creationTime: new Date() });
expect(json).to.be.not.null;
const jwe = JSON.parse(json) as JsonJWE;
expect(jwe.protected).to.not.be.empty;
Expand Down

0 comments on commit 388426f

Please sign in to comment.