anastasis-core: question hashing and policy expiration
This commit is contained in:
parent
51d54fdd91
commit
5dc0089392
@ -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,
|
||||||
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user