anastasis-core: support pin-type answers
This commit is contained in:
parent
83622bd65a
commit
6d6679e338
@ -10,6 +10,7 @@ import {
|
|||||||
crypto_sign_keyPair_fromSeed,
|
crypto_sign_keyPair_fromSeed,
|
||||||
stringToBytes,
|
stringToBytes,
|
||||||
secretbox_open,
|
secretbox_open,
|
||||||
|
hash,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { gzipSync } from "fflate";
|
import { gzipSync } from "fflate";
|
||||||
import { argon2id } from "hash-wasm";
|
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(
|
export async function secureAnswerHash(
|
||||||
answer: string,
|
answer: string,
|
||||||
truthUuid: TruthUuid,
|
truthUuid: TruthUuid,
|
||||||
|
@ -86,11 +86,19 @@ import {
|
|||||||
decryptKeyShare,
|
decryptKeyShare,
|
||||||
KeyShare,
|
KeyShare,
|
||||||
coreSecretRecover,
|
coreSecretRecover,
|
||||||
|
pinAnswerHash,
|
||||||
} from "./crypto.js";
|
} from "./crypto.js";
|
||||||
import { unzlibSync, zlibSync } from "fflate";
|
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 { 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({});
|
const { fetch } = fetchPonyfill({});
|
||||||
|
|
||||||
@ -473,7 +481,7 @@ async function uploadSecret(
|
|||||||
}
|
}
|
||||||
|
|
||||||
escrowMethods.push({
|
escrowMethods.push({
|
||||||
escrow_type: authMethod.type,
|
escrow_type: authMethod.type as any,
|
||||||
instructions: authMethod.instructions,
|
instructions: authMethod.instructions,
|
||||||
provider_salt: provider.salt,
|
provider_salt: provider.salt,
|
||||||
truth_salt: tm.truth_salt,
|
truth_salt: tm.truth_salt,
|
||||||
@ -697,11 +705,43 @@ async function requestTruth(
|
|||||||
const url = new URL(`/truth/${truth.uuid}`, truth.url);
|
const url = new URL(`/truth/${truth.uuid}`, truth.url);
|
||||||
|
|
||||||
if (solveRequest) {
|
if (solveRequest) {
|
||||||
// FIXME: This isn't correct for non-question truth responses.
|
let respHash: string;
|
||||||
url.searchParams.set(
|
switch (truth.escrow_type) {
|
||||||
"response",
|
case ChallengeType.Question:
|
||||||
await secureAnswerHash(solveRequest.answer, truth.uuid, truth.truth_salt),
|
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, {
|
const resp = await fetch(url.href, {
|
||||||
@ -711,10 +751,14 @@ async function requestTruth(
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (resp.status === HttpStatusCode.Ok) {
|
if (resp.status === HttpStatusCode.Ok) {
|
||||||
const answerSalt =
|
let answerSalt: string | undefined = undefined;
|
||||||
solveRequest && truth.escrow_type === "question"
|
if (
|
||||||
? solveRequest.answer
|
solveRequest &&
|
||||||
: undefined;
|
truth.escrow_type === "question" &&
|
||||||
|
"answer" in solveRequest
|
||||||
|
) {
|
||||||
|
answerSalt = solveRequest.answer;
|
||||||
|
}
|
||||||
|
|
||||||
const userId = await userIdentifierDerive(
|
const userId = await userIdentifierDerive(
|
||||||
state.identity_attributes,
|
state.identity_attributes,
|
||||||
|
@ -1,5 +1,14 @@
|
|||||||
import { TruthKey, TruthSalt, TruthUuid } from "./crypto.js";
|
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 {
|
export interface RecoveryDocument {
|
||||||
/**
|
/**
|
||||||
* Human-readable name of the secret
|
* Human-readable name of the secret
|
||||||
@ -56,7 +65,7 @@ export interface EscrowMethod {
|
|||||||
/**
|
/**
|
||||||
* Type of the escrow method (e.g. security question, SMS etc.).
|
* Type of the escrow method (e.g. security question, SMS etc.).
|
||||||
*/
|
*/
|
||||||
escrow_type: string;
|
escrow_type: ChallengeType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UUID of the escrow method.
|
* UUID of the escrow method.
|
||||||
|
@ -312,12 +312,44 @@ export interface ActionArgsSelectChallenge {
|
|||||||
uuid: string;
|
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 {
|
export interface SolveChallengeAnswerRequest {
|
||||||
answer: string;
|
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 {
|
export interface PolicyMember {
|
||||||
authentication_method: number;
|
authentication_method: number;
|
||||||
provider: string;
|
provider: string;
|
||||||
|
Loading…
Reference in New Issue
Block a user