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 EddsaPrivateKey = Flavor<string, "EddsaPrivateKey">;
export type TruthUuid = Flavor<string, "TruthUuid">; export type TruthUuid = Flavor<string, "TruthUuid">;
export type SecureAnswerHash = Flavor<string, "SecureAnswerHash">; 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. * Truth key, found in the recovery document.
*/ */
@ -150,7 +153,7 @@ async function anastasisEncrypt(
return encodeCrock(taConcat([nonceBuf, cipherText])); 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 asEncryptedKeyShare = (x: OpaqueData): EncryptedKeyShare => x as string;
const asEncryptedTruth = (x: OpaqueData): EncryptedTruth => x as string; const asEncryptedTruth = (x: OpaqueData): EncryptedTruth => x as string;
@ -216,7 +219,7 @@ export async function coreSecretEncrypt(
export async function secureAnswerHash( export async function secureAnswerHash(
answer: string, answer: string,
truthUuid: TruthUuid, truthUuid: TruthUuid,
questionSalt: QuestionSalt, questionSalt: TruthSalt,
): Promise<SecureAnswerHash> { ): Promise<SecureAnswerHash> {
const powResult = await argon2id({ const powResult = await argon2id({
hashLength: 64, hashLength: 64,

View File

@ -1,14 +1,15 @@
import { import {
AmountString, AmountString,
buildSigPS, buildSigPS,
codecForGetExchangeWithdrawalInfo,
decodeCrock, decodeCrock,
eddsaSign, eddsaSign,
encodeCrock, encodeCrock,
getRandomBytes, getRandomBytes,
hash, hash,
stringToBytes,
TalerErrorCode, TalerErrorCode,
TalerSignaturePurpose, TalerSignaturePurpose,
Timestamp,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { anastasisData } from "./anastasis-data.js"; import { anastasisData } from "./anastasis-data.js";
import { import {
@ -42,13 +43,19 @@ import {
import fetchPonyfill from "fetch-ponyfill"; import fetchPonyfill from "fetch-ponyfill";
import { import {
accountKeypairDerive, accountKeypairDerive,
asOpaque,
coreSecretEncrypt, coreSecretEncrypt,
encryptKeyshare, encryptKeyshare,
encryptRecoveryDocument, encryptRecoveryDocument,
encryptTruth, encryptTruth,
OpaqueData,
PolicyKey, PolicyKey,
policyKeyDerive, policyKeyDerive,
PolicySalt, PolicySalt,
TruthSalt,
secureAnswerHash,
TruthKey,
TruthUuid,
UserIdentifier, UserIdentifier,
userIdentifierDerive, userIdentifierDerive,
} from "./crypto.js"; } from "./crypto.js";
@ -100,13 +107,13 @@ interface EscrowMethod {
*/ */
escrow_type: string; escrow_type: string;
// UUID of the escrow method (see /truth/ API below). // UUID of the escrow method.
// 16 bytes base32-crock encoded. // 16 bytes base32-crock encoded.
uuid: string; uuid: TruthUuid;
// Key used to encrypt the Truth this EscrowMethod is related to. // Key used to encrypt the Truth this EscrowMethod is related to.
// Client has to provide this key to the server when using /truth/. // 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 used to encrypt the truth on the Anastasis server.
salt: string; salt: string;
@ -117,11 +124,6 @@ interface EscrowMethod {
// The instructions to give to the user (i.e. the security question // The instructions to give to the user (i.e. the security question
// if this is challenge-response). // 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; instructions: string;
} }
@ -388,7 +390,28 @@ interface TruthMetaData {
/** /**
* Truth-specific salt. * 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( async function uploadSecret(
@ -421,7 +444,7 @@ async function uploadSecret(
const tm: TruthMetaData = { const tm: TruthMetaData = {
key_share: keyShare, key_share: keyShare,
nonce: encodeCrock(getRandomBytes(24)), nonce: encodeCrock(getRandomBytes(24)),
salt: encodeCrock(getRandomBytes(16)), truth_salt: encodeCrock(getRandomBytes(16)),
truth_key: encodeCrock(getRandomBytes(32)), truth_key: encodeCrock(getRandomBytes(32)),
uuid: encodeCrock(getRandomBytes(32)), uuid: encodeCrock(getRandomBytes(32)),
pol_method_index: methIndex, pol_method_index: methIndex,
@ -459,13 +482,18 @@ async function uploadSecret(
const provider = state.authentication_providers![ const provider = state.authentication_providers![
meth.provider meth.provider
] as AuthenticationProviderStatusOk; ] as AuthenticationProviderStatusOk;
const truthValue = await getTruthValue(authMethod, tm.uuid, tm.truth_salt);
const encryptedTruth = await encryptTruth( const encryptedTruth = await encryptTruth(
tm.nonce, tm.nonce,
tm.truth_key, tm.truth_key,
authMethod.challenge, truthValue,
); );
const uid = uidMap[meth.provider]; 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( console.log(
"encrypted key share len", "encrypted key share len",
decodeCrock(encryptedKeyShare).length, decodeCrock(encryptedKeyShare).length,
@ -496,7 +524,7 @@ async function uploadSecret(
escrow_type: authMethod.type, escrow_type: authMethod.type,
instructions: authMethod.instructions, instructions: authMethod.instructions,
provider_salt: provider.salt, provider_salt: provider.salt,
salt: tm.salt, salt: tm.truth_salt,
truth_key: tm.truth_key, truth_key: tm.truth_key,
url: meth.provider, url: meth.provider,
uuid: tm.uuid, uuid: tm.uuid,
@ -549,14 +577,19 @@ async function uploadSecret(
}; };
} }
let policyVersion = 0; let policyVersion = 0;
console.log(resp); let policyExpiration: Timestamp = { t_ms: 0 };
console.log(resp.headers);
console.log(resp.headers.get("Anastasis-Version"));
try { try {
policyVersion = Number(resp.headers.get("Anastasis-Version") ?? "0"); policyVersion = Number(resp.headers.get("Anastasis-Version") ?? "0");
} catch (e) {} } catch (e) {}
try {
policyExpiration = {
t_ms:
1000 * Number(resp.headers.get("Anastasis-Policy-Expiration") ?? "0"),
};
} catch (e) {}
successDetails[prov.provider_url] = { successDetails[prov.provider_url] = {
policy_version: policyVersion, 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 = export type ReducerState =
| ReducerStateBackup | ReducerStateBackup
@ -30,6 +30,7 @@ export interface PolicyProvider {
export interface SuccessDetails { export interface SuccessDetails {
[provider_url: string]: { [provider_url: string]: {
policy_version: number; policy_version: number;
policy_expiration: Timestamp;
}; };
} }