summaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/anastasis-core/src/crypto.ts41
-rw-r--r--packages/anastasis-core/src/index.ts15
-rw-r--r--packages/taler-util/src/index.ts7
-rw-r--r--packages/taler-util/src/kdf.ts19
-rw-r--r--packages/taler-util/src/nacl-fast.ts5
-rw-r--r--packages/taler-util/src/talerCrypto.test.ts2
-rw-r--r--packages/taler-util/src/talerCrypto.ts57
-rw-r--r--packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts93
-rw-r--r--packages/taler-wallet-core/src/operations/pay.ts2
-rw-r--r--packages/taler-wallet-core/src/util/contractTerms.ts8
10 files changed, 170 insertions, 79 deletions
diff --git a/packages/anastasis-core/src/crypto.ts b/packages/anastasis-core/src/crypto.ts
index 5da3a4cce..32cf470c4 100644
--- a/packages/anastasis-core/src/crypto.ts
+++ b/packages/anastasis-core/src/crypto.ts
@@ -5,7 +5,9 @@ import {
encodeCrock,
getRandomBytes,
kdf,
+ kdfKw,
secretbox,
+ crypto_sign_keyPair_fromSeed,
stringToBytes,
} from "@gnu-taler/taler-util";
import { argon2id } from "hash-wasm";
@@ -25,6 +27,8 @@ export type EncryptedKeyShare = Flavor<string, "EncryptedKeyShare">;
export type EncryptedTruth = Flavor<string, "EncryptedTruth">;
export type EncryptedCoreSecret = Flavor<string, "EncryptedCoreSecret">;
export type EncryptedMasterKey = Flavor<string, "EncryptedMasterKey">;
+export type EddsaPublicKey = Flavor<string, "EddsaPublicKey">;
+export type EddsaPrivateKey = Flavor<string, "EddsaPrivateKey">;
/**
* Truth key, found in the recovery document.
*/
@@ -53,6 +57,43 @@ export async function userIdentifierDerive(
return encodeCrock(result);
}
+export interface AccountKeyPair {
+ priv: EddsaPrivateKey;
+ pub: EddsaPublicKey;
+}
+
+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),
+ });
+ // 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),
+ pub: encodeCrock(pair.publicKey),
+ };
+}
+
+export async function encryptRecoveryDocument(
+ userId: UserIdentifier,
+ recoveryDoc: any,
+): Promise<OpaqueData> {
+ const plaintext = stringToBytes(JSON.stringify(recoveryDoc));
+ const nonce = encodeCrock(getRandomBytes(nonceSize));
+ return anastasisEncrypt(
+ nonce,
+ asOpaque(userId),
+ encodeCrock(plaintext),
+ "erd",
+ );
+}
+
function taConcat(chunks: Uint8Array[]): Uint8Array {
let payloadLen = 0;
for (const c of chunks) {
diff --git a/packages/anastasis-core/src/index.ts b/packages/anastasis-core/src/index.ts
index f33a0be46..8921433b7 100644
--- a/packages/anastasis-core/src/index.ts
+++ b/packages/anastasis-core/src/index.ts
@@ -36,8 +36,10 @@ import {
} from "./reducer-types.js";
import fetchPonyfill from "fetch-ponyfill";
import {
+ accountKeypairDerive,
coreSecretEncrypt,
encryptKeyshare,
+ encryptRecoveryDocument,
encryptTruth,
PolicyKey,
policyKeyDerive,
@@ -492,14 +494,25 @@ async function uploadSecret(
policies: policies.map((x, i) => {
return {
master_key: csr.encMasterKeys[i],
+ // FIXME: ...
uuid: [],
- salt:
+ salt: undefined as any,
};
}),
};
for (const prov of state.policy_providers!) {
+ const uid = uidMap[prov.provider_url]
+ const acctKeypair = accountKeypairDerive(uid);
+ const encRecoveryDoc = await encryptRecoveryDocument(uid, rd);
// FIXME: Upload recovery document.
+ const resp = await fetch(
+ new URL(`policy/${acctKeypair.pub}`, prov.provider_url).href,
+ {
+ method: "POST",
+ body: decodeCrock(encRecoveryDoc),
+ },
+ );
}
return {
diff --git a/packages/taler-util/src/index.ts b/packages/taler-util/src/index.ts
index ccb917f6e..4ad752954 100644
--- a/packages/taler-util/src/index.ts
+++ b/packages/taler-util/src/index.ts
@@ -22,4 +22,9 @@ export * from "./url.js";
export { fnutil } from "./fnutils.js";
export * from "./kdf.js";
export * from "./talerCrypto.js";
-export { randomBytes, secretbox, secretbox_open } from "./nacl-fast.js";
+export {
+ randomBytes,
+ secretbox,
+ secretbox_open,
+ crypto_sign_keyPair_fromSeed,
+} from "./nacl-fast.js";
diff --git a/packages/taler-util/src/kdf.ts b/packages/taler-util/src/kdf.ts
index af4d05035..7710de90c 100644
--- a/packages/taler-util/src/kdf.ts
+++ b/packages/taler-util/src/kdf.ts
@@ -59,15 +59,30 @@ export function hmacSha256(key: Uint8Array, message: Uint8Array): Uint8Array {
return hmac(sha256, 64, key, message);
}
+/**
+ * HMAC-SHA512-SHA256 (see RFC 5869).
+ */
+export function kdfKw(args: {
+ outputLength: number;
+ ikm: Uint8Array;
+ salt?: Uint8Array;
+ info?: Uint8Array;
+}) {
+ return kdf(args.outputLength, args.ikm, args.salt, args.info);
+}
+
export function kdf(
outputLength: number,
ikm: Uint8Array,
- salt: Uint8Array,
- info: Uint8Array,
+ salt?: Uint8Array,
+ info?: Uint8Array,
): Uint8Array {
+ salt = salt ?? new Uint8Array(64);
// extract
const prk = hmacSha512(salt, ikm);
+ info = info ?? new Uint8Array(0);
+
// expand
const N = Math.ceil(outputLength / 32);
const output = new Uint8Array(N * 32);
diff --git a/packages/taler-util/src/nacl-fast.ts b/packages/taler-util/src/nacl-fast.ts
index 909c6a60a..6e721f32c 100644
--- a/packages/taler-util/src/nacl-fast.ts
+++ b/packages/taler-util/src/nacl-fast.ts
@@ -2894,7 +2894,6 @@ export function x25519_edwards_keyPair_fromSecretKey(
throw new Error("bad secret key size");
}
d.set(secretKey, 0);
- //crypto_hash(d, secretKey, 32);
d[0] &= 248;
d[31] &= 127;
@@ -2906,7 +2905,7 @@ export function x25519_edwards_keyPair_fromSecretKey(
return pk;
}
-export function sign_keyPair_fromSecretKey(
+export function crypto_sign_keyPair_fromSecretKey(
secretKey: Uint8Array,
): {
publicKey: Uint8Array;
@@ -2920,7 +2919,7 @@ export function sign_keyPair_fromSecretKey(
return { publicKey: pk, secretKey: new Uint8Array(secretKey) };
}
-export function sign_keyPair_fromSeed(
+export function crypto_sign_keyPair_fromSeed(
seed: Uint8Array,
): {
publicKey: Uint8Array;
diff --git a/packages/taler-util/src/talerCrypto.test.ts b/packages/taler-util/src/talerCrypto.test.ts
index ffd1d25cd..1e3ceef61 100644
--- a/packages/taler-util/src/talerCrypto.test.ts
+++ b/packages/taler-util/src/talerCrypto.test.ts
@@ -69,7 +69,7 @@ test("taler-exchange-tvg eddsa key", (t) => {
const priv = "9TM70AKDTS57AWY9JK2J4TMBTMW6K62WHHGZWYDG0VM5ABPZKD40";
const pub = "8GSJZ649T2PXMKZC01Y4ANNBE7MF14QVK9SQEC4E46ZHKCVG8AS0";
- const pair = nacl.sign_keyPair_fromSeed(decodeCrock(priv));
+ const pair = nacl.crypto_sign_keyPair_fromSeed(decodeCrock(priv));
t.deepEqual(encodeCrock(pair.publicKey), pub);
});
diff --git a/packages/taler-util/src/talerCrypto.ts b/packages/taler-util/src/talerCrypto.ts
index efa92a953..536c4dc48 100644
--- a/packages/taler-util/src/talerCrypto.ts
+++ b/packages/taler-util/src/talerCrypto.ts
@@ -126,7 +126,7 @@ export function decodeCrock(encoded: string): Uint8Array {
}
export function eddsaGetPublic(eddsaPriv: Uint8Array): Uint8Array {
- const pair = nacl.sign_keyPair_fromSeed(eddsaPriv);
+ const pair = nacl.crypto_sign_keyPair_fromSeed(eddsaPriv);
return pair.publicKey;
}
@@ -353,7 +353,7 @@ export function hash(d: Uint8Array): Uint8Array {
}
export function eddsaSign(msg: Uint8Array, eddsaPriv: Uint8Array): Uint8Array {
- const pair = nacl.sign_keyPair_fromSeed(eddsaPriv);
+ const pair = nacl.crypto_sign_keyPair_fromSeed(eddsaPriv);
return nacl.sign_detached(msg, pair.secretKey);
}
@@ -447,3 +447,56 @@ export function setupRefreshTransferPub(
ecdhePub: ecdheGetPublic(out),
};
}
+
+export enum TalerSignaturePurpose {
+ MERCHANT_TRACK_TRANSACTION = 1103,
+ WALLET_RESERVE_WITHDRAW = 1200,
+ WALLET_COIN_DEPOSIT = 1201,
+ MASTER_DENOMINATION_KEY_VALIDITY = 1025,
+ MASTER_WIRE_FEES = 1028,
+ MASTER_WIRE_DETAILS = 1030,
+ WALLET_COIN_MELT = 1202,
+ TEST = 4242,
+ MERCHANT_PAYMENT_OK = 1104,
+ MERCHANT_CONTRACT = 1101,
+ WALLET_COIN_RECOUP = 1203,
+ WALLET_COIN_LINK = 1204,
+ EXCHANGE_CONFIRM_RECOUP = 1039,
+ EXCHANGE_CONFIRM_RECOUP_REFRESH = 1041,
+ ANASTASIS_POLICY_UPLOAD = 1400,
+ ANASTASIS_POLICY_DOWNLOAD = 1401,
+ SYNC_BACKUP_UPLOAD = 1450,
+}
+
+export class SignaturePurposeBuilder {
+ private chunks: Uint8Array[] = [];
+
+ constructor(private purposeNum: number) {}
+
+ put(bytes: Uint8Array): SignaturePurposeBuilder {
+ this.chunks.push(Uint8Array.from(bytes));
+ return this;
+ }
+
+ build(): Uint8Array {
+ let payloadLen = 0;
+ for (const c of this.chunks) {
+ payloadLen += c.byteLength;
+ }
+ const buf = new ArrayBuffer(4 + 4 + payloadLen);
+ const u8buf = new Uint8Array(buf);
+ let p = 8;
+ for (const c of this.chunks) {
+ u8buf.set(c, p);
+ p += c.byteLength;
+ }
+ const dvbuf = new DataView(buf);
+ dvbuf.setUint32(0, payloadLen + 4 + 4);
+ dvbuf.setUint32(4, this.purposeNum);
+ return u8buf;
+ }
+}
+
+export function buildSigPS(purposeNum: number): SignaturePurposeBuilder {
+ return new SignaturePurposeBuilder(purposeNum);
+}
diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts
index 4ca0576b0..c42ece778 100644
--- a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts
+++ b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts
@@ -34,7 +34,14 @@ import {
CoinSourceType,
} from "../../db.js";
-import { CoinDepositPermission, RecoupRequest, RefreshPlanchetInfo } from "@gnu-taler/taler-util";
+import {
+ buildSigPS,
+ CoinDepositPermission,
+ RecoupRequest,
+ RefreshPlanchetInfo,
+ SignaturePurposeBuilder,
+ TalerSignaturePurpose,
+} from "@gnu-taler/taler-util";
// FIXME: These types should be internal to the wallet!
import {
BenchmarkResult,
@@ -80,24 +87,6 @@ import bigint from "big-integer";
const logger = new Logger("cryptoImplementation.ts");
-enum SignaturePurpose {
- MERCHANT_TRACK_TRANSACTION = 1103,
- WALLET_RESERVE_WITHDRAW = 1200,
- WALLET_COIN_DEPOSIT = 1201,
- MASTER_DENOMINATION_KEY_VALIDITY = 1025,
- MASTER_WIRE_FEES = 1028,
- MASTER_WIRE_DETAILS = 1030,
- WALLET_COIN_MELT = 1202,
- TEST = 4242,
- MERCHANT_PAYMENT_OK = 1104,
- MERCHANT_CONTRACT = 1101,
- WALLET_COIN_RECOUP = 1203,
- WALLET_COIN_LINK = 1204,
- EXCHANGE_CONFIRM_RECOUP = 1039,
- EXCHANGE_CONFIRM_RECOUP_REFRESH = 1041,
- SYNC_BACKUP_UPLOAD = 1450,
-}
-
function amountToBuffer(amount: AmountJson): Uint8Array {
const buffer = new ArrayBuffer(8 + 4 + 12);
const dvbuf = new DataView(buffer);
@@ -139,38 +128,6 @@ function timestampRoundedToBuffer(ts: Timestamp): Uint8Array {
return new Uint8Array(b);
}
-class SignaturePurposeBuilder {
- private chunks: Uint8Array[] = [];
-
- constructor(private purposeNum: number) { }
-
- put(bytes: Uint8Array): SignaturePurposeBuilder {
- this.chunks.push(Uint8Array.from(bytes));
- return this;
- }
-
- build(): Uint8Array {
- let payloadLen = 0;
- for (const c of this.chunks) {
- payloadLen += c.byteLength;
- }
- const buf = new ArrayBuffer(4 + 4 + payloadLen);
- const u8buf = new Uint8Array(buf);
- let p = 8;
- for (const c of this.chunks) {
- u8buf.set(c, p);
- p += c.byteLength;
- }
- const dvbuf = new DataView(buf);
- dvbuf.setUint32(0, payloadLen + 4 + 4);
- dvbuf.setUint32(4, this.purposeNum);
- return u8buf;
- }
-}
-
-function buildSigPS(purposeNum: number): SignaturePurposeBuilder {
- return new SignaturePurposeBuilder(purposeNum);
-}
export class CryptoImplementation {
static enableTracing = false;
@@ -192,7 +149,9 @@ export class CryptoImplementation {
const denomPubHash = hash(denomPub);
const evHash = hash(ev);
- const withdrawRequest = buildSigPS(SignaturePurpose.WALLET_RESERVE_WITHDRAW)
+ const withdrawRequest = buildSigPS(
+ TalerSignaturePurpose.WALLET_RESERVE_WITHDRAW,
+ )
.put(reservePub)
.put(amountToBuffer(amountWithFee))
.put(denomPubHash)
@@ -236,7 +195,7 @@ export class CryptoImplementation {
}
signTrackTransaction(req: SignTrackTransactionRequest): string {
- const p = buildSigPS(SignaturePurpose.MERCHANT_TRACK_TRANSACTION)
+ const p = buildSigPS(TalerSignaturePurpose.MERCHANT_TRACK_TRANSACTION)
.put(decodeCrock(req.contractTermsHash))
.put(decodeCrock(req.wireHash))
.put(decodeCrock(req.merchantPub))
@@ -249,7 +208,7 @@ export class CryptoImplementation {
* Create and sign a message to recoup a coin.
*/
createRecoupRequest(coin: CoinRecord): RecoupRequest {
- const p = buildSigPS(SignaturePurpose.WALLET_COIN_RECOUP)
+ const p = buildSigPS(TalerSignaturePurpose.WALLET_COIN_RECOUP)
.put(decodeCrock(coin.coinPub))
.put(decodeCrock(coin.denomPubHash))
.put(decodeCrock(coin.blindingKey))
@@ -276,7 +235,7 @@ export class CryptoImplementation {
contractHash: string,
merchantPub: string,
): boolean {
- const p = buildSigPS(SignaturePurpose.MERCHANT_PAYMENT_OK)
+ const p = buildSigPS(TalerSignaturePurpose.MERCHANT_PAYMENT_OK)
.put(decodeCrock(contractHash))
.build();
const sigBytes = decodeCrock(sig);
@@ -288,7 +247,7 @@ export class CryptoImplementation {
* Check if a wire fee is correctly signed.
*/
isValidWireFee(type: string, wf: WireFee, masterPub: string): boolean {
- const p = buildSigPS(SignaturePurpose.MASTER_WIRE_FEES)
+ const p = buildSigPS(TalerSignaturePurpose.MASTER_WIRE_FEES)
.put(hash(stringToBytes(type + "\0")))
.put(timestampRoundedToBuffer(wf.startStamp))
.put(timestampRoundedToBuffer(wf.endStamp))
@@ -304,7 +263,7 @@ export class CryptoImplementation {
* Check if the signature of a denomination is valid.
*/
isValidDenom(denom: DenominationRecord, masterPub: string): boolean {
- const p = buildSigPS(SignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY)
+ const p = buildSigPS(TalerSignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY)
.put(decodeCrock(masterPub))
.put(timestampRoundedToBuffer(denom.stampStart))
.put(timestampRoundedToBuffer(denom.stampExpireWithdraw))
@@ -334,7 +293,9 @@ export class CryptoImplementation {
stringToBytes(paytoUri + "\0"),
new Uint8Array(0),
);
- const p = buildSigPS(SignaturePurpose.MASTER_WIRE_DETAILS).put(h).build();
+ const p = buildSigPS(TalerSignaturePurpose.MASTER_WIRE_DETAILS)
+ .put(h)
+ .build();
return eddsaVerify(p, decodeCrock(sig), decodeCrock(masterPub));
}
@@ -344,7 +305,7 @@ export class CryptoImplementation {
merchantPub: string,
): boolean {
const cthDec = decodeCrock(contractTermsHash);
- const p = buildSigPS(SignaturePurpose.MERCHANT_CONTRACT)
+ const p = buildSigPS(TalerSignaturePurpose.MERCHANT_CONTRACT)
.put(cthDec)
.build();
return eddsaVerify(p, decodeCrock(sig), decodeCrock(merchantPub));
@@ -364,8 +325,8 @@ export class CryptoImplementation {
eddsaGetPublic(key: string): { priv: string; pub: string } {
return {
priv: key,
- pub: encodeCrock(eddsaGetPublic(decodeCrock(key)))
- }
+ pub: encodeCrock(eddsaGetPublic(decodeCrock(key))),
+ };
}
/**
@@ -392,7 +353,7 @@ export class CryptoImplementation {
* and deposit permissions for each given coin.
*/
signDepositPermission(depositInfo: DepositInfo): CoinDepositPermission {
- const d = buildSigPS(SignaturePurpose.WALLET_COIN_DEPOSIT)
+ const d = buildSigPS(TalerSignaturePurpose.WALLET_COIN_DEPOSIT)
.put(decodeCrock(depositInfo.contractTermsHash))
.put(decodeCrock(depositInfo.wireInfoHash))
.put(decodeCrock(depositInfo.denomPubHash))
@@ -503,7 +464,7 @@ export class CryptoImplementation {
}
const sessionHash = sessionHc.finish();
- const confirmData = buildSigPS(SignaturePurpose.WALLET_COIN_MELT)
+ const confirmData = buildSigPS(TalerSignaturePurpose.WALLET_COIN_MELT)
.put(sessionHash)
.put(decodeCrock(meltCoinDenomPubHash))
.put(amountToBuffer(valueWithFee))
@@ -549,7 +510,7 @@ export class CryptoImplementation {
coinEv: string,
): string {
const coinEvHash = hash(decodeCrock(coinEv));
- const coinLink = buildSigPS(SignaturePurpose.WALLET_COIN_LINK)
+ const coinLink = buildSigPS(TalerSignaturePurpose.WALLET_COIN_LINK)
.put(decodeCrock(newDenomHash))
.put(decodeCrock(transferPub))
.put(coinEvHash)
@@ -622,9 +583,7 @@ export class CryptoImplementation {
} else {
hOld = new Uint8Array(64);
}
- const sigBlob = new SignaturePurposeBuilder(
- SignaturePurpose.SYNC_BACKUP_UPLOAD,
- )
+ const sigBlob = buildSigPS(TalerSignaturePurpose.SYNC_BACKUP_UPLOAD)
.put(hOld)
.put(hNew)
.build();
diff --git a/packages/taler-wallet-core/src/operations/pay.ts b/packages/taler-wallet-core/src/operations/pay.ts
index 079ffcd9a..8fad55994 100644
--- a/packages/taler-wallet-core/src/operations/pay.ts
+++ b/packages/taler-wallet-core/src/operations/pay.ts
@@ -763,6 +763,8 @@ async function processDownloadProposalImpl(
proposalResp.contract_terms,
);
+ logger.info(`Contract terms hash: ${contractTermsHash}`);
+
let parsedContractTerms: ContractTerms;
try {
diff --git a/packages/taler-wallet-core/src/util/contractTerms.ts b/packages/taler-wallet-core/src/util/contractTerms.ts
index 652ef707d..b064079e9 100644
--- a/packages/taler-wallet-core/src/util/contractTerms.ts
+++ b/packages/taler-wallet-core/src/util/contractTerms.ts
@@ -14,7 +14,7 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { canonicalJson } from "@gnu-taler/taler-util";
+import { canonicalJson, Logger } from "@gnu-taler/taler-util";
import { kdf } from "@gnu-taler/taler-util";
import {
decodeCrock,
@@ -24,6 +24,8 @@ import {
stringToBytes,
} from "@gnu-taler/taler-util";
+const logger = new Logger("contractTerms.ts");
+
export namespace ContractTermsUtil {
export type PathPredicate = (path: string[]) => boolean;
@@ -222,6 +224,8 @@ export namespace ContractTermsUtil {
export function hashContractTerms(contractTerms: unknown): string {
const cleaned = scrub(contractTerms);
const canon = canonicalJson(cleaned) + "\0";
- return encodeCrock(hash(stringToBytes(canon)));
+ const bytes = stringToBytes(canon);
+ logger.info(`contract terms before hashing: ${encodeCrock(bytes)}`);
+ return encodeCrock(hash(bytes));
}
}