From 5dc008939237c29fdfd146ddb1adc09054950459 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 19 Oct 2021 20:51:31 +0200 Subject: anastasis-core: question hashing and policy expiration --- packages/anastasis-core/src/crypto.ts | 9 ++-- packages/anastasis-core/src/index.ts | 67 +++++++++++++++++++++------- packages/anastasis-core/src/reducer-types.ts | 3 +- 3 files changed, 58 insertions(+), 21 deletions(-) (limited to 'packages') diff --git a/packages/anastasis-core/src/crypto.ts b/packages/anastasis-core/src/crypto.ts index a5594288b..f7cfa9654 100644 --- a/packages/anastasis-core/src/crypto.ts +++ b/packages/anastasis-core/src/crypto.ts @@ -33,7 +33,10 @@ export type EddsaPublicKey = Flavor; export type EddsaPrivateKey = Flavor; export type TruthUuid = Flavor; export type SecureAnswerHash = Flavor; -export type QuestionSalt = Flavor; +/** + * Truth-specific randomness, also called question salt sometimes. + */ +export type TruthSalt = Flavor; /** * Truth key, found in the recovery document. */ @@ -150,7 +153,7 @@ async function anastasisEncrypt( return encodeCrock(taConcat([nonceBuf, cipherText])); } -const asOpaque = (x: string): OpaqueData => x; +export const asOpaque = (x: string): OpaqueData => x; const asEncryptedKeyShare = (x: OpaqueData): EncryptedKeyShare => x as string; const asEncryptedTruth = (x: OpaqueData): EncryptedTruth => x as string; @@ -216,7 +219,7 @@ export async function coreSecretEncrypt( export async function secureAnswerHash( answer: string, truthUuid: TruthUuid, - questionSalt: QuestionSalt, + questionSalt: TruthSalt, ): Promise { const powResult = await argon2id({ hashLength: 64, diff --git a/packages/anastasis-core/src/index.ts b/packages/anastasis-core/src/index.ts index 7206f9122..d8071e996 100644 --- a/packages/anastasis-core/src/index.ts +++ b/packages/anastasis-core/src/index.ts @@ -1,14 +1,15 @@ import { AmountString, buildSigPS, - codecForGetExchangeWithdrawalInfo, decodeCrock, eddsaSign, encodeCrock, getRandomBytes, hash, + stringToBytes, TalerErrorCode, TalerSignaturePurpose, + Timestamp, } from "@gnu-taler/taler-util"; import { anastasisData } from "./anastasis-data.js"; import { @@ -42,13 +43,19 @@ import { import fetchPonyfill from "fetch-ponyfill"; import { accountKeypairDerive, + asOpaque, coreSecretEncrypt, encryptKeyshare, encryptRecoveryDocument, encryptTruth, + OpaqueData, PolicyKey, policyKeyDerive, PolicySalt, + TruthSalt, + secureAnswerHash, + TruthKey, + TruthUuid, UserIdentifier, userIdentifierDerive, } from "./crypto.js"; @@ -100,13 +107,13 @@ interface EscrowMethod { */ escrow_type: string; - // UUID of the escrow method (see /truth/ API below). + // UUID of the escrow method. // 16 bytes base32-crock encoded. - uuid: string; + uuid: TruthUuid; // Key used to encrypt the Truth this EscrowMethod is related to. // Client has to provide this key to the server when using /truth/. - truth_key: string; + truth_key: TruthKey; // Salt used to encrypt the truth on the Anastasis server. salt: string; @@ -117,11 +124,6 @@ interface EscrowMethod { // The instructions to give to the user (i.e. the security question // if this is challenge-response). - // (Q: as string in base32 encoding?) - // (Q: what is the mime-type of this value?) - // - // The plaintext challenge is not revealed to the - // Anastasis server. instructions: string; } @@ -388,7 +390,28 @@ interface TruthMetaData { /** * Truth-specific salt. */ - salt: string; + truth_salt: string; +} + +async function getTruthValue( + authMethod: AuthMethod, + truthUuid: string, + questionSalt: TruthSalt, +): Promise { + switch (authMethod.type) { + case "question": { + return asOpaque( + await secureAnswerHash(authMethod.challenge, truthUuid, questionSalt), + ); + } + case "sms": + case "email": + case "totp": + case "iban": + return encodeCrock(stringToBytes(authMethod.type)); + default: + throw Error("unknown auth type"); + } } async function uploadSecret( @@ -421,7 +444,7 @@ async function uploadSecret( const tm: TruthMetaData = { key_share: keyShare, nonce: encodeCrock(getRandomBytes(24)), - salt: encodeCrock(getRandomBytes(16)), + truth_salt: encodeCrock(getRandomBytes(16)), truth_key: encodeCrock(getRandomBytes(32)), uuid: encodeCrock(getRandomBytes(32)), pol_method_index: methIndex, @@ -459,13 +482,18 @@ async function uploadSecret( const provider = state.authentication_providers![ meth.provider ] as AuthenticationProviderStatusOk; + const truthValue = await getTruthValue(authMethod, tm.uuid, tm.truth_salt); const encryptedTruth = await encryptTruth( tm.nonce, tm.truth_key, - authMethod.challenge, + truthValue, ); const uid = uidMap[meth.provider]; - const encryptedKeyShare = await encryptKeyshare(tm.key_share, uid, tm.salt); + const encryptedKeyShare = await encryptKeyshare( + tm.key_share, + uid, + tm.truth_salt, + ); console.log( "encrypted key share len", decodeCrock(encryptedKeyShare).length, @@ -496,7 +524,7 @@ async function uploadSecret( escrow_type: authMethod.type, instructions: authMethod.instructions, provider_salt: provider.salt, - salt: tm.salt, + salt: tm.truth_salt, truth_key: tm.truth_key, url: meth.provider, uuid: tm.uuid, @@ -549,14 +577,19 @@ async function uploadSecret( }; } let policyVersion = 0; - console.log(resp); - console.log(resp.headers); - console.log(resp.headers.get("Anastasis-Version")); + let policyExpiration: Timestamp = { t_ms: 0 }; try { policyVersion = Number(resp.headers.get("Anastasis-Version") ?? "0"); } catch (e) {} + try { + policyExpiration = { + t_ms: + 1000 * Number(resp.headers.get("Anastasis-Policy-Expiration") ?? "0"), + }; + } catch (e) {} successDetails[prov.provider_url] = { policy_version: policyVersion, + policy_expiration: policyExpiration, }; } diff --git a/packages/anastasis-core/src/reducer-types.ts b/packages/anastasis-core/src/reducer-types.ts index 1f4a2abea..92b1c532d 100644 --- a/packages/anastasis-core/src/reducer-types.ts +++ b/packages/anastasis-core/src/reducer-types.ts @@ -1,4 +1,4 @@ -import { Duration } from "@gnu-taler/taler-util"; +import { Duration, Timestamp } from "@gnu-taler/taler-util"; export type ReducerState = | ReducerStateBackup @@ -30,6 +30,7 @@ export interface PolicyProvider { export interface SuccessDetails { [provider_url: string]: { policy_version: number; + policy_expiration: Timestamp; }; } -- cgit v1.2.3