add test/validate_response.ml

This commit is contained in:
swrup 2026-02-27 17:49:54 +01:00 committed by Swrup
parent b984190d6d
commit cbf54c16df
6 changed files with 277 additions and 12 deletions

View file

@ -915,6 +915,10 @@ module SignKey = struct
Signkey.{ pub; stamp_start; stamp_expire; stamp_end; master_sig } = Signkey.{ pub; stamp_start; stamp_expire; stamp_end; master_sig } =
{ key= pub; stamp_start; stamp_expire; stamp_end; master_sig } { key= pub; stamp_start; stamp_expire; stamp_end; master_sig }
let to_signkey { key= pub; stamp_start; stamp_expire; stamp_end; master_sig }
=
Signkey.{ pub; stamp_start; stamp_expire; stamp_end; master_sig }
let jsont = let jsont =
let make key stamp_start stamp_expire stamp_end master_sig = let make key stamp_start stamp_expire stamp_end master_sig =
{ key; stamp_start; stamp_expire; stamp_end; master_sig } { key; stamp_start; stamp_expire; stamp_end; master_sig }
@ -1261,7 +1265,7 @@ module ExchangeKeysResponse = struct
stefan_lin: Float.t; stefan_lin: Float.t;
asset_type: string; asset_type: string;
accounts: ExchangeWireAccount.t list; accounts: ExchangeWireAccount.t list;
wire_fees: AggregateTransferFee.t list Stdlib.Map.Make(Stdlib.String).t; wire_fees: AggregateTransferFee.t list String_map.t;
wads: ExchangePartnerListEntry.t list; wads: ExchangePartnerListEntry.t list;
kyc_enabled: bool; kyc_enabled: bool;
disable_direct_deposit: bool; disable_direct_deposit: bool;
@ -1278,7 +1282,7 @@ module ExchangeKeysResponse = struct
list_issue_date: Timestamp.t; list_issue_date: Timestamp.t;
auditors: AuditorKeys.t list; auditors: AuditorKeys.t list;
signkeys: SignKey.t list; signkeys: SignKey.t list;
extensions: ExtensionManifest.t Stdlib.Map.Make(Stdlib.String).t option; extensions: ExtensionManifest.t String_map.t option;
(* Signature by the exchange master key of the SHA-256 hash of the (* Signature by the exchange master key of the SHA-256 hash of the
normalized JSON-object of field extensions, if it was set. normalized JSON-object of field extensions, if it was set.
The signature has purpose TALER_SIGNATURE_MASTER_EXTENSIONS. *) The signature has purpose TALER_SIGNATURE_MASTER_EXTENSIONS. *)

View file

@ -110,3 +110,60 @@ let make_denom_group_sorted denominations =
|> List.sort cmp_group |> List.sort cmp_group
in in
l l
let denoms_of_denomgroups l =
let open Api in
l
|> List.concat_map
(fun
(DenomGroup.Rsa
RsaDenomGroup.
{
denoms;
value;
fee_withdraw;
fee_deposit;
fee_refresh;
fee_refund;
})
->
let open RsaDenom in
denoms
|> List.map
(fun
{
rsa_pub;
master_sig;
stamp_start;
stamp_expire_withdraw;
stamp_expire_deposit;
stamp_expire_legal;
lost= _;
}
->
let h_pub =
DenominationHash.hash (Crypto.RsaPublicKey.to_octets rsa_pub)
in
{
pub= rsa_pub;
value;
stamp_start;
stamp_expire_withdraw;
stamp_expire_deposit;
stamp_expire_legal;
fee_withdraw;
fee_deposit;
fee_refresh;
fee_refund;
age_mask= 0;
h_pub;
master_sig;
}))
let hash_over_master_sigs denominations =
denominations
|> List.map (fun (Api.DenomGroup.Rsa g) -> g.Api.RsaDenomGroup.denoms)
|> List.concat_map (List.map (fun dn -> dn.Api.RsaDenom.master_sig))
|> List.map Signatures.DenominationKeyValidity.to_octets
|> String.concat ""
|> Hash.H64.hash

View file

@ -104,16 +104,13 @@ let mk_keys ~db_conn (module Keys : Keys.S) ~last_issue_date =
(* ! depends on denominations order *) (* ! depends on denominations order *)
let exchange_sig = let exchange_sig =
let hc =
denominations
|> List.map (fun (DenomGroup.Rsa g) -> g.RsaDenomGroup.denoms)
|> List.concat_map (List.map (fun dn -> dn.RsaDenom.master_sig))
|> List.map Signatures.DenominationKeyValidity.to_octets
|> String.concat ""
|> Hash.H64.hash
in
let open Signatures.ExchangeKeySet in let open Signatures.ExchangeKeySet in
signf (Keys.sign exchange_pub) R.{ list_issue_date; hc } signf (Keys.sign exchange_pub)
R.
{
list_issue_date;
hc= Denomination.hash_over_master_sigs denominations;
}
in in
let recoup = (* /recoup *) [] in let recoup = (* /recoup *) [] in

View file

@ -7,3 +7,8 @@
(name test_crypto) (name test_crypto)
(modules test_crypto) (modules test_crypto)
(libraries mte fmt)) (libraries mte fmt))
(executable
(name validate_response)
(modules validate_response)
(libraries mte fmt bos))

View file

@ -60,7 +60,7 @@ echo "[OK] /management/auditors/\$AUDITOR_PUB/disable"
offline_tool wire-fee \ offline_tool wire-fee \
--master_key $master_key \ --master_key $master_key \
--output $b \ --output $b \
--wire_method "xxx" \ --wire_method "x-taler-bank" \
--fee_start 0 \ --fee_start 0 \
--fee_end 99999999 \ --fee_end 99999999 \
--closing_fee $zero_kudos \ --closing_fee $zero_kudos \

202
test/validate_response.ml Normal file
View file

@ -0,0 +1,202 @@
open Syntax
let keys content =
let open Api in
let open ExchangeKeysResponse in
let* v = Api.decode jsont content in
let* () =
if
Libtool_version.is_compatible
~implementation:Libtool_version.mte_protocol_version v.version
then Ok ()
else Fmt.error "version incompatible"
in
(* TODO need hash over json..
validate ExchangeWireAccount *)
let* () =
let open AggregateTransferFee in
v.wire_fees
|> String_map.bindings
|> List.concat_map (fun (wire_method, l) ->
List.map (fun item -> (wire_method, item)) l)
|> list_iter
(fun
(wire_method, { wire_fee; closing_fee; start_date; end_date; sig_ })
->
let open Signatures.MasterWireFee in
verify v.master_public_key sig_
{
h_wire_method= Hash.Cstring.H64.hash wire_method;
start_date;
end_date;
wire_fee;
closing_fee;
})
in
let* () =
let open SignKey in
v.signkeys
|> list_iter
(fun { key; stamp_start; stamp_expire; stamp_end; master_sig } ->
let open Signatures.ExchangeSigningKeyValidity in
verify v.master_public_key master_sig
{
R.start= stamp_start;
expire= stamp_expire;
end_= stamp_end;
signkey_pub= key;
})
in
let* () =
let opt =
List.find_opt (fun sk -> sk.SignKey.key = v.exchange_pub) v.signkeys
in
match opt with
| None -> Fmt.error "exchange_pub is not in signkeys list"
| Some sk ->
let sk = SignKey.to_signkey sk in
(* TODO will need to fake time to validate expired data *)
let now = Timestamp.of_ptime (Ptime_clock.now ()) in
if Signkey.is_valid_at ~timestamp:now sk then Ok ()
else Fmt.error "exchange_pub is not valid at the current time"
in
let* () =
let open Signatures.ExchangeKeySet in
verify v.exchange_pub v.exchange_sig
R.
{
list_issue_date= v.list_issue_date;
hc= Denomination.hash_over_master_sigs v.denominations;
}
in
let denom_l = v.denominations |> Denomination.denoms_of_denomgroups in
let* () =
let open Denomination in
denom_l
|> list_iter (fun dn ->
let open Signatures.DenominationKeyValidity in
verify v.master_public_key dn.master_sig
{
R.master= v.master_public_key;
start= dn.stamp_start;
expire_withdraw= dn.stamp_expire_withdraw;
expire_spend= dn.stamp_expire_deposit;
expire_legal= dn.stamp_expire_legal;
value= dn.value;
fee_withdraw= dn.fee_withdraw;
fee_deposit= dn.fee_deposit;
fee_refresh= dn.fee_refresh;
fee_refund= dn.fee_refund;
denom_hash= dn.h_pub;
})
in
let* () =
let last_issue_date =
denom_l
|> List.map (fun dn -> dn.Denomination.stamp_start)
|> List.fold_left Timestamp.max Timestamp.zero
in
if last_issue_date = v.list_issue_date then Ok ()
else Fmt.error "list_issue_date is wrong"
in
let* () =
v.global_fees
|> list_iter
(fun
GlobalFees.
{
start_date;
end_date;
history_fee;
account_fee;
purse_fee;
history_expiration;
purse_account_limit;
purse_timeout;
master_sig;
}
->
let open Signatures.GlobalFees in
verify v.master_public_key master_sig
{
start_date;
end_date;
purse_timeout;
history_expiration;
history_fee;
account_fee;
purse_fee;
purse_account_limit;
})
in
let* () =
(* TODO O(n^2) *)
v.auditors
|> list_iter
(fun
AuditorKeys.
{ auditor_pub; auditor_url; auditor_name= _; denomination_keys }
->
let auditor_url_hash = Hash.H64.hash auditor_url in
denomination_keys
|> list_iter
(fun AuditorDenominationKey.{ denom_pub_h; auditor_sig } ->
let open Denomination in
let opt =
denom_l |> List.find_opt (fun dn -> dn.h_pub = denom_pub_h)
in
match opt with
| None -> Fmt.error "auditor denomination key not found"
| Some dn ->
let open Signatures.ExchangeKeyValidity in
verify auditor_pub auditor_sig
{
auditor_url_hash;
master= auditor_pub;
start= dn.stamp_start;
expire_withdraw= dn.stamp_expire_withdraw;
expire_spend= dn.stamp_expire_deposit;
expire_legal= dn.stamp_expire_legal;
value= dn.value;
fee_withdraw= dn.fee_withdraw;
fee_deposit= dn.fee_deposit;
fee_refresh= dn.fee_refresh;
denom_hash= denom_pub_h;
}))
in
Ok ()
(* --- *)
open Cmdliner
open Cmdliner.Term.Syntax
let keys_cmd =
let input =
let doc = "input file" in
Arg.(required & opt (some filepath) None & info [ "i"; "input" ] ~doc)
in
let doc = "validate a /keys JSON response" in
Cmd.make (Cmd.info "keys" ~doc)
@@
let+ input = input in
let* content =
Result.bind (Fpath.of_string input) Bos.OS.File.read |> unwrap_err_msg
in
keys content
let cli =
let info =
let doc = "Testing tool to validate exchange responses" in
Cmd.info "validate" ~doc
in
Cmd.group info [ keys_cmd ]
let main () = Cmd.eval_result cli
let () = if !Sys.interactive then () else exit (main ())