diff options
-rw-r--r-- | packages/anastasis-core/src/crypto.ts | 5 | ||||
-rw-r--r-- | packages/anastasis-core/src/index.ts | 68 | ||||
-rw-r--r-- | packages/anastasis-core/src/recovery-document-types.ts | 13 | ||||
-rw-r--r-- | packages/anastasis-core/src/reducer-types.ts | 36 |
4 files changed, 106 insertions, 16 deletions
diff --git a/packages/anastasis-core/src/crypto.ts b/packages/anastasis-core/src/crypto.ts index da8338636..9689e4f2d 100644 --- a/packages/anastasis-core/src/crypto.ts +++ b/packages/anastasis-core/src/crypto.ts @@ -10,6 +10,7 @@ import { crypto_sign_keyPair_fromSeed, stringToBytes, secretbox_open, + hash, } from "@gnu-taler/taler-util"; import { gzipSync } from "fflate"; import { argon2id } from "hash-wasm"; @@ -283,6 +284,10 @@ export async function coreSecretEncrypt( }; } +export async function pinAnswerHash(pin: number): Promise<SecureAnswerHash> { + return encodeCrock(hash(stringToBytes(pin.toString()))); +} + export async function secureAnswerHash( answer: string, truthUuid: TruthUuid, diff --git a/packages/anastasis-core/src/index.ts b/packages/anastasis-core/src/index.ts index fd04eb4d7..4c8ac0d31 100644 --- a/packages/anastasis-core/src/index.ts +++ b/packages/anastasis-core/src/index.ts @@ -86,11 +86,19 @@ import { decryptKeyShare, KeyShare, coreSecretRecover, + pinAnswerHash, } from "./crypto.js"; import { unzlibSync, zlibSync } from "fflate"; -import { EscrowMethod, RecoveryDocument } from "./recovery-document-types.js"; +import { + ChallengeType, + EscrowMethod, + RecoveryDocument, +} from "./recovery-document-types.js"; import { ProviderInfo, suggestPolicies } from "./policy-suggestion.js"; -import { ChallengeFeedback, ChallengeFeedbackStatus } from "./challenge-feedback-types.js"; +import { + ChallengeFeedback, + ChallengeFeedbackStatus, +} from "./challenge-feedback-types.js"; const { fetch } = fetchPonyfill({}); @@ -473,7 +481,7 @@ async function uploadSecret( } escrowMethods.push({ - escrow_type: authMethod.type, + escrow_type: authMethod.type as any, instructions: authMethod.instructions, provider_salt: provider.salt, truth_salt: tm.truth_salt, @@ -697,11 +705,43 @@ async function requestTruth( const url = new URL(`/truth/${truth.uuid}`, truth.url); if (solveRequest) { - // FIXME: This isn't correct for non-question truth responses. - url.searchParams.set( - "response", - await secureAnswerHash(solveRequest.answer, truth.uuid, truth.truth_salt), - ); + let respHash: string; + switch (truth.escrow_type) { + case ChallengeType.Question: + if ("answer" in solveRequest) { + respHash = await secureAnswerHash( + solveRequest.answer, + truth.uuid, + truth.truth_salt, + ); + } else { + throw Error("unsupported answer request"); + } + break; + case ChallengeType.Email: + case ChallengeType.Sms: + case ChallengeType.Post: + case ChallengeType.Totp: { + if ("answer" in solveRequest) { + const s = solveRequest.answer.trim().replace(/^A-/, ""); + let pin: number; + try { + pin = Number.parseInt(s); + } catch (e) { + throw Error("invalid pin format"); + } + respHash = await pinAnswerHash(pin); + } else if ("pin" in solveRequest) { + respHash = await pinAnswerHash(solveRequest.pin); + } else { + throw Error("unsupported answer request"); + } + break; + } + default: + throw Error("unsupported challenge type"); + } + url.searchParams.set("response", respHash); } const resp = await fetch(url.href, { @@ -711,10 +751,14 @@ async function requestTruth( }); if (resp.status === HttpStatusCode.Ok) { - const answerSalt = - solveRequest && truth.escrow_type === "question" - ? solveRequest.answer - : undefined; + let answerSalt: string | undefined = undefined; + if ( + solveRequest && + truth.escrow_type === "question" && + "answer" in solveRequest + ) { + answerSalt = solveRequest.answer; + } const userId = await userIdentifierDerive( state.identity_attributes, diff --git a/packages/anastasis-core/src/recovery-document-types.ts b/packages/anastasis-core/src/recovery-document-types.ts index 74003ccb1..3dc4481ff 100644 --- a/packages/anastasis-core/src/recovery-document-types.ts +++ b/packages/anastasis-core/src/recovery-document-types.ts @@ -1,5 +1,14 @@ import { TruthKey, TruthSalt, TruthUuid } from "./crypto.js"; +export enum ChallengeType { + Question = "question", + Sms = "sms", + Email = "email", + Post = "post", + Totp = "totp", + Iban = "iban", +} + export interface RecoveryDocument { /** * Human-readable name of the secret @@ -9,7 +18,7 @@ export interface RecoveryDocument { /** * Encrypted core secret. - * + * * Variable-size length, base32-crock encoded. */ encrypted_core_secret: string; @@ -56,7 +65,7 @@ export interface EscrowMethod { /** * Type of the escrow method (e.g. security question, SMS etc.). */ - escrow_type: string; + escrow_type: ChallengeType; /** * UUID of the escrow method. diff --git a/packages/anastasis-core/src/reducer-types.ts b/packages/anastasis-core/src/reducer-types.ts index e0d311e88..19f7d431a 100644 --- a/packages/anastasis-core/src/reducer-types.ts +++ b/packages/anastasis-core/src/reducer-types.ts @@ -312,12 +312,44 @@ export interface ActionArgsSelectChallenge { uuid: string; } -export type ActionArgsSolveChallengeRequest = SolveChallengeAnswerRequest; - +export type ActionArgsSolveChallengeRequest = + | SolveChallengeAnswerRequest + | SolveChallengePinRequest + | SolveChallengeHashRequest; + +/** + * Answer to a challenge. + * + * For "question" challenges, this is a string with the answer. + * + * For "sms" / "email" / "post" this is a numeric code with optionally + * the "A-" prefix. + */ export interface SolveChallengeAnswerRequest { answer: string; } +/** + * Answer to a challenge that requires a numeric response. + * + * XXX: Should be deprecated in favor of just "answer". + */ +export interface SolveChallengePinRequest { + pin: number; +} + +/** + * Answer to a challenge by directly providing the hash. + * + * XXX: When / why is this even used? + */ + export interface SolveChallengeHashRequest { + /** + * Base32-crock encoded hash code. + */ + hash: string; +} + export interface PolicyMember { authentication_method: number; provider: string; |