anastasis-core: crypto fixes

This commit is contained in:
Florian Dold 2021-10-19 18:39:38 +02:00
parent aac2bc389a
commit 26738d14f1
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
4 changed files with 78 additions and 21 deletions

View File

@ -66,16 +66,12 @@ export function accountKeypairDerive(userId: UserIdentifier): AccountKeyPair {
// FIXME: the KDF invocation looks fishy, but that's what the C code presently does.
const d = kdfKw({
outputLength: 32,
ikm: stringToBytes("ver"),
salt: decodeCrock(userId),
ikm: decodeCrock(userId),
info: stringToBytes("ver"),
});
// FIXME: This bit twiddling seems wrong/unnecessary.
d[0] &= 248;
d[31] &= 127;
d[31] |= 64;
const pair = crypto_sign_keyPair_fromSeed(d);
return {
priv: encodeCrock(pair.secretKey),
priv: encodeCrock(d),
pub: encodeCrock(pair.publicKey),
};
}

View File

@ -1,10 +1,14 @@
import {
AmountString,
buildSigPS,
codecForGetExchangeWithdrawalInfo,
decodeCrock,
eddsaSign,
encodeCrock,
getRandomBytes,
hash,
TalerErrorCode,
TalerSignaturePurpose,
} from "@gnu-taler/taler-util";
import { anastasisData } from "./anastasis-data.js";
import {
@ -33,6 +37,7 @@ import {
ReducerStateBackupUserAttributesCollecting,
ReducerStateError,
ReducerStateRecovery,
SuccessDetails,
} from "./reducer-types.js";
import fetchPonyfill from "fetch-ponyfill";
import {
@ -43,6 +48,7 @@ import {
encryptTruth,
PolicyKey,
policyKeyDerive,
PolicySalt,
UserIdentifier,
userIdentifierDerive,
} from "./crypto.js";
@ -393,12 +399,17 @@ async function uploadSecret(
const coreSecret = state.core_secret?.value!;
// Truth key is `${methodIndex}/${providerUrl}`
const truthMetadataMap: Record<string, TruthMetaData> = {};
const policyKeys: PolicyKey[] = [];
const policySalts: PolicySalt[] = [];
// truth UUIDs for every policy.
const policyUuids: string[][] = [];
for (let policyIndex = 0; policyIndex < policies.length; policyIndex++) {
const pol = policies[policyIndex];
const policySalt = encodeCrock(getRandomBytes(64));
const keyShares: string[] = [];
const methUuids: string[] = [];
for (let methIndex = 0; methIndex < pol.methods.length; methIndex++) {
const meth = pol.methods[methIndex];
const truthKey = `${meth.authentication_method}:${meth.provider}`;
@ -416,10 +427,12 @@ async function uploadSecret(
pol_method_index: methIndex,
policy_index: policyIndex,
};
methUuids.push(tm.uuid);
truthMetadataMap[truthKey] = tm;
}
const policyKey = await policyKeyDerive(keyShares, policySalt);
policyKeys.push(policyKey);
policySalts.push(policySalt);
}
const csr = await coreSecretEncrypt(policyKeys, coreSecret);
@ -472,6 +485,13 @@ async function uploadSecret(
body: JSON.stringify(tur),
});
if (resp.status !== 204) {
return {
code: TalerErrorCode.ANASTASIS_REDUCER_NETWORK_FAILED,
hint: "could not upload policy",
};
}
escrowMethods.push({
escrow_type: authMethod.type,
instructions: authMethod.instructions,
@ -494,30 +514,56 @@ async function uploadSecret(
policies: policies.map((x, i) => {
return {
master_key: csr.encMasterKeys[i],
// FIXME: ...
uuid: [],
salt: undefined as any,
uuid: policyUuids[i],
salt: policySalts[i],
};
}),
};
const successDetails: SuccessDetails = {};
for (const prov of state.policy_providers!) {
const uid = uidMap[prov.provider_url]
const uid = uidMap[prov.provider_url];
const acctKeypair = accountKeypairDerive(uid);
const encRecoveryDoc = await encryptRecoveryDocument(uid, rd);
// FIXME: Upload recovery document.
const bodyHash = hash(decodeCrock(encRecoveryDoc));
const sigPS = buildSigPS(TalerSignaturePurpose.ANASTASIS_POLICY_UPLOAD)
.put(bodyHash)
.build();
const sig = eddsaSign(sigPS, decodeCrock(acctKeypair.priv));
const resp = await fetch(
new URL(`policy/${acctKeypair.pub}`, prov.provider_url).href,
{
method: "POST",
headers: {
"Anastasis-Policy-Signature": encodeCrock(sig),
"If-None-Match": encodeCrock(bodyHash),
},
body: decodeCrock(encRecoveryDoc),
},
);
if (resp.status !== 204) {
return {
code: TalerErrorCode.ANASTASIS_REDUCER_NETWORK_FAILED,
hint: "could not upload policy",
};
}
let policyVersion = 0;
console.log(resp);
console.log(resp.headers);
console.log(resp.headers.get("Anastasis-Version"));
try {
policyVersion = Number(resp.headers.get("Anastasis-Version") ?? "0");
} catch (e) {}
successDetails[prov.provider_url] = {
policy_version: policyVersion,
};
}
return {
code: 123,
hint: "not implemented",
...state,
backup_state: BackupStates.BackupFinished,
success_details: successDetails,
};
}
@ -703,6 +749,19 @@ export async function reduceAction(
};
}
}
if (state.backup_state === BackupStates.BackupFinished) {
if (action === "back") {
return {
...state,
backup_state: BackupStates.SecretEditing,
};
} else {
return {
code: TalerErrorCode.ANASTASIS_REDUCER_ACTION_INVALID,
hint: `Unsupported action '${action}'`,
};
}
}
return {
code: TalerErrorCode.ANASTASIS_REDUCER_ACTION_INVALID,
hint: "Reducer action invalid",

View File

@ -27,6 +27,12 @@ export interface PolicyProvider {
provider_url: string;
}
export interface SuccessDetails {
[provider_url: string]: {
policy_version: number;
};
}
export interface ReducerStateBackup {
recovery_state?: undefined;
backup_state: BackupStates;
@ -47,11 +53,7 @@ export interface ReducerStateBackup {
* and that are actually used in policies.
*/
policy_providers?: PolicyProvider[];
success_details?: {
[provider_url: string]: {
policy_version: number;
};
};
success_details?: SuccessDetails;
payments?: string[];
policy_payment_requests?: {
payto: string;

View File

@ -2926,7 +2926,8 @@ export function crypto_sign_keyPair_fromSeed(
secretKey: Uint8Array;
} {
checkArrayTypes(seed);
if (seed.length !== crypto_sign_SEEDBYTES) throw new Error("bad seed size");
if (seed.length !== crypto_sign_SEEDBYTES)
throw new Error(`bad seed size: ${seed.length}`);
const pk = new Uint8Array(crypto_sign_PUBLICKEYBYTES);
const sk = new Uint8Array(crypto_sign_SECRETKEYBYTES);
for (let i = 0; i < 32; i++) sk[i] = seed[i];
@ -3015,4 +3016,3 @@ export function secretbox_open(
if (crypto_secretbox_open(m, c, c.length, nonce, key) !== 0) return undefined;
return m.subarray(crypto_secretbox_ZEROBYTES);
}