anastasis-core: question hashing and policy expiration

This commit is contained in:
Florian Dold 2021-10-19 20:51:31 +02:00
parent 51d54fdd91
commit 5dc0089392
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
3 changed files with 58 additions and 21 deletions

View File

@ -33,7 +33,10 @@ export type EddsaPublicKey = Flavor<string, "EddsaPublicKey">;
export type EddsaPrivateKey = Flavor<string, "EddsaPrivateKey">;
export type TruthUuid = Flavor<string, "TruthUuid">;
export type SecureAnswerHash = Flavor<string, "SecureAnswerHash">;
export type QuestionSalt = Flavor<string, "QuestionSalt">;
/**
* Truth-specific randomness, also called question salt sometimes.
*/
export type TruthSalt = Flavor<string, "TruthSalt">;
/**
* 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<SecureAnswerHash> {
const powResult = await argon2id({
hashLength: 64,

View File

@ -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<OpaqueData> {
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,
};
}

View File

@ -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;
};
}