taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit 61157df2531fed7db753bbce30e2a5faaef9fb77
parent 36233a934b60f007af34d7f80a3452c12a95a584
Author: Florian Dold <dold@taler.net>
Date:   Tue, 21 Jul 2026 02:16:13 +0200

util: reject wrong-length Crockford input for fixed-size values

decodeCrock derives the output length from the input, so a truncated or
over-long string yields a buffer of the wrong size. Add decodeCrockFixed,
which rejects any input that does not encode the expected number of bytes,
and use it for the AML decision h_payto that is put into a signed blob.

Diffstat:
Mpackages/taler-util/src/taler-crypto.ts | 23+++++++++++++++++++++++
Mpackages/taler-util/src/taler-signatures.ts | 4++--
2 files changed, 25 insertions(+), 2 deletions(-)

diff --git a/packages/taler-util/src/taler-crypto.ts b/packages/taler-util/src/taler-crypto.ts @@ -335,6 +335,29 @@ export function decodeCrock(encoded: string): Uint8Array { return out; } +/** + * Decode a Crockford base32 string that must encode exactly expectedLen bytes. + * + * decodeCrock derives the output length from the input, so a truncated or + * over-long string silently yields a buffer of the wrong size. GNUnet's + * string_to_data instead decodes into a fixed-size buffer and rejects any input + * whose length does not match; use this wherever the decoded size is known. + */ +export function decodeCrockFixed( + encoded: string, + expectedLen: number, +): Uint8Array { + const expectedChars = Math.ceil((expectedLen * 8) / 5); + if (encoded.length !== expectedChars) { + throw new EncodingError(); + } + const out = decodeCrock(encoded); + if (out.length !== expectedLen) { + throw new EncodingError(); + } + return out; +} + export async function hashArgon2id( password: Uint8Array, salt: Uint8Array, diff --git a/packages/taler-util/src/taler-signatures.ts b/packages/taler-util/src/taler-signatures.ts @@ -20,7 +20,7 @@ import { bufferFromAmount, bufferForUint64, buildSigPS, - decodeCrock, + decodeCrockFixed, EddsaPrivP, eddsaSign, EddsaSigP, @@ -56,7 +56,7 @@ export function signAmlDecision( decision.attributes_expiration ?? TalerProtocolTimestamp.fromSeconds(0), ), ); - builder.put(decodeCrock(decision.h_payto)); + builder.put(decodeCrockFixed(decision.h_payto, 64)); builder.put(hash(stringToBytes(decision.justification))); builder.put(hash(stringToBytes(canonicalJson(decision.properties) + "\0"))); builder.put(hash(stringToBytes(canonicalJson(decision.new_rules) + "\0")));