From b94dc8f9e26219b1f54e1456f8d7be00d333aa80 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Thu, 4 Nov 2021 20:16:11 +0100 Subject: [PATCH] anastasis-core: factor out recovery data computation into separate step --- packages/anastasis-core/src/index.ts | 114 ++++++++++++------- packages/anastasis-core/src/reducer-types.ts | 14 ++- 2 files changed, 82 insertions(+), 46 deletions(-) diff --git a/packages/anastasis-core/src/index.ts b/packages/anastasis-core/src/index.ts index 0bab510de..f77707ae1 100644 --- a/packages/anastasis-core/src/index.ts +++ b/packages/anastasis-core/src/index.ts @@ -343,14 +343,19 @@ async function uncompressRecoveryDoc(zippedRd: Uint8Array): Promise { return JSON.parse(bytesToString(res)); } -async function uploadSecret( +/** + * Prepare the recovery document and truth metadata based + * on the selected policies. + */ +async function prepareRecoveryData( state: ReducerStateBackup, -): Promise { +): Promise { const policies = state.policies!; const secretName = state.secret_name!; const coreSecret: OpaqueData = encodeCrock( stringToBytes(JSON.stringify(state.core_secret!)), ); + // Truth key is `${methodIndex}/${providerUrl}` const truthMetadataMap: Record = {}; @@ -391,17 +396,6 @@ async function uploadSecret( const csr = await coreSecretEncrypt(policyKeys, coreSecret); - const uidMap: Record = {}; - for (const prov of state.policy_providers!) { - const provider = state.authentication_providers![ - prov.provider_url - ] as AuthenticationProviderStatusOk; - uidMap[prov.provider_url] = await userIdentifierDerive( - state.identity_attributes!, - provider.salt, - ); - } - const escrowMethods: EscrowMethod[] = []; for (const truthKey of Object.keys(truthMetadataMap)) { @@ -413,6 +407,71 @@ async function uploadSecret( const provider = state.authentication_providers![ meth.provider ] as AuthenticationProviderStatusOk; + escrowMethods.push({ + escrow_type: authMethod.type as any, + instructions: authMethod.instructions, + provider_salt: provider.salt, + truth_salt: tm.truth_salt, + truth_key: tm.truth_key, + url: meth.provider, + uuid: tm.uuid, + }); + } + + const rd: RecoveryDocument = { + secret_name: secretName, + encrypted_core_secret: csr.encCoreSecret, + escrow_methods: escrowMethods, + policies: policies.map((x, i) => { + return { + master_key: csr.encMasterKeys[i], + uuids: policyUuids[i], + salt: policySalts[i], + }; + }), + }; + + return { + ...state, + recovery_data: { + recovery_document: rd, + truth_metadata: truthMetadataMap, + }, + }; +} + +async function uploadSecret( + state: ReducerStateBackup, +): Promise { + const uidMap: Record = {}; + for (const prov of state.policy_providers!) { + const provider = state.authentication_providers![ + prov.provider_url + ] as AuthenticationProviderStatusOk; + uidMap[prov.provider_url] = await userIdentifierDerive( + state.identity_attributes!, + provider.salt, + ); + } + + if (!state.recovery_data) { + state = await prepareRecoveryData(state); + } + + const recoveryData = state.recovery_data; + if (!recoveryData) { + throw Error("invariant failed"); + } + + const truthMetadataMap = recoveryData.truth_metadata; + const rd = recoveryData.recovery_document; + + for (const truthKey of Object.keys(truthMetadataMap)) { + const tm = truthMetadataMap[truthKey]; + const pol = state.policies![tm.policy_index]; + const meth = pol.methods[tm.pol_method_index]; + const authMethod = + state.authentication_methods![meth.authentication_method]; const truthValue = await getTruthValue(authMethod, tm.uuid, tm.truth_salt); const encryptedTruth = await encryptTruth( tm.nonce, @@ -448,35 +507,8 @@ async function uploadSecret( hint: `could not upload truth (HTTP status ${resp.status})`, }; } - - escrowMethods.push({ - escrow_type: authMethod.type as any, - instructions: authMethod.instructions, - provider_salt: provider.salt, - truth_salt: tm.truth_salt, - truth_key: tm.truth_key, - url: meth.provider, - uuid: tm.uuid, - }); } - // FIXME: We need to store the truth metadata in - // the state, since it's possible that we'll run into - // a provider that requests a payment. - - const rd: RecoveryDocument = { - secret_name: secretName, - encrypted_core_secret: csr.encCoreSecret, - escrow_methods: escrowMethods, - policies: policies.map((x, i) => { - return { - master_key: csr.encMasterKeys[i], - uuids: policyUuids[i], - salt: policySalts[i], - }; - }), - }; - const successDetails: SuccessDetails = {}; const policyPayUris: string[] = []; @@ -1092,6 +1124,8 @@ async function enterSecret( mime: args.secret.mime ?? "text/plain", value: args.secret.value, }, + // A new secret invalidates the existing recovery data. + recovery_data: undefined, }); } diff --git a/packages/anastasis-core/src/reducer-types.ts b/packages/anastasis-core/src/reducer-types.ts index 318e00f89..56b27898e 100644 --- a/packages/anastasis-core/src/reducer-types.ts +++ b/packages/anastasis-core/src/reducer-types.ts @@ -67,12 +67,14 @@ export interface ReducerStateBackup { secret_name?: string; policies?: Policy[]; - /** - * Map from truth key (`${methodIndex}/${providerUrl}`) to - * the truth metadata. - */ - truth_metadata?: Record; - recovery_document?: RecoveryDocument; + recovery_data?: { + /** + * Map from truth key (`${methodIndex}/${providerUrl}`) to + * the truth metadata. + */ + truth_metadata: Record; + recovery_document: RecoveryDocument; + }; /** * Policy providers are providers that we checked to be functional