465 lines
13 KiB
OCaml
465 lines
13 KiB
OCaml
open Syntax
|
|
open Time
|
|
open Hash
|
|
|
|
let () = Mirage_crypto_rng_unix.use_default ()
|
|
|
|
(* TODO monotonic time:
|
|
add something for tests / broken monotonic time
|
|
|
|
https://docs.taler.net/manpages/taler-exchange-offline.1.html#security-considerations *)
|
|
let now_s () =
|
|
let ns = Mtime_clock.now_ns () in
|
|
Int64.unsigned_div ns 1_000_000_000L
|
|
|
|
module Future_keys = struct
|
|
open Api
|
|
|
|
let verify offline_master_public_key
|
|
FutureKeysResponse.
|
|
{
|
|
future_denoms;
|
|
future_signkeys;
|
|
master_pub;
|
|
denom_secmod_public_key;
|
|
signkey_secmod_public_key;
|
|
} =
|
|
let verify_future_denom ~sm_denom_pub
|
|
FutureDenom.
|
|
{
|
|
section_name;
|
|
value= _;
|
|
stamp_start;
|
|
stamp_expire_withdraw;
|
|
stamp_expire_deposit= _;
|
|
stamp_expire_legal= _;
|
|
denom_pub;
|
|
fee_withdraw= _;
|
|
fee_deposit= _;
|
|
fee_refresh= _;
|
|
fee_refund= _;
|
|
denom_secmod_sig;
|
|
} =
|
|
let rsa_pub =
|
|
match denom_pub with DenominationKey.Rsa denom -> denom.rsa_pub
|
|
in
|
|
let h_denom_pub = DenominationHash.hash rsa_pub in
|
|
let h_section_name = H64_cstring.hash section_name in
|
|
let anchor_time = stamp_start in
|
|
let duration_withdraw =
|
|
Timestamp.diff stamp_start stamp_expire_withdraw
|
|
in
|
|
let open Signatures.DenominationKeyAnnouncement in
|
|
verify sm_denom_pub denom_secmod_sig
|
|
{ h_denom_pub; h_section_name; anchor_time; duration_withdraw }
|
|
in
|
|
let verify_future_signkey ~sm_signkey_pub
|
|
FutureSignKey.
|
|
{ key; stamp_start; stamp_expire; stamp_end= _; signkey_secmod_sig } =
|
|
let exchange_pub = key in
|
|
let anchor_time = stamp_start in
|
|
let duration = Timestamp.diff stamp_start stamp_expire in
|
|
let open Signatures.SigningKeyAnnouncement in
|
|
verify sm_signkey_pub signkey_secmod_sig
|
|
{ exchange_pub; anchor_time; duration }
|
|
in
|
|
let* () =
|
|
match master_pub = offline_master_public_key with
|
|
| false ->
|
|
Fmt.error_msg
|
|
"master public key of the future key response does not match ours"
|
|
| true -> Ok ()
|
|
in
|
|
let* () =
|
|
list_iter
|
|
(verify_future_denom ~sm_denom_pub:denom_secmod_public_key)
|
|
future_denoms
|
|
in
|
|
let* () =
|
|
list_iter
|
|
(verify_future_signkey ~sm_signkey_pub:signkey_secmod_public_key)
|
|
future_signkeys
|
|
in
|
|
Ok ()
|
|
|
|
let make ~master_key
|
|
FutureKeysResponse.
|
|
{
|
|
future_denoms;
|
|
future_signkeys;
|
|
master_pub= _;
|
|
denom_secmod_public_key= _;
|
|
signkey_secmod_public_key= _;
|
|
} =
|
|
let denom_signature
|
|
FutureDenom.
|
|
{
|
|
section_name= _;
|
|
value;
|
|
stamp_start;
|
|
stamp_expire_withdraw;
|
|
stamp_expire_deposit;
|
|
stamp_expire_legal;
|
|
denom_pub;
|
|
fee_withdraw;
|
|
fee_deposit;
|
|
fee_refresh;
|
|
fee_refund;
|
|
denom_secmod_sig= _;
|
|
} =
|
|
let rsa_pub =
|
|
match denom_pub with DenominationKey.Rsa denom -> denom.rsa_pub
|
|
in
|
|
let h_denom_pub = DenominationHash.hash rsa_pub in
|
|
let master_sig =
|
|
let open Signatures.DenominationKeyValidity in
|
|
let master = Eddsa.pub_of_priv master_key in
|
|
signf
|
|
(Eddsa.sign ~key:master_key)
|
|
{
|
|
master;
|
|
start= stamp_start;
|
|
expire_withdraw= stamp_expire_withdraw;
|
|
expire_spend= stamp_expire_deposit;
|
|
expire_legal= stamp_expire_legal;
|
|
value;
|
|
fee_withdraw;
|
|
fee_deposit;
|
|
fee_refresh;
|
|
fee_refund;
|
|
denom_hash= h_denom_pub;
|
|
}
|
|
in
|
|
DenomSignature.{ h_denom_pub; master_sig }
|
|
in
|
|
let signkey_signature
|
|
FutureSignKey.
|
|
{ key; stamp_start; stamp_expire; stamp_end; signkey_secmod_sig= _ } =
|
|
let master_sig =
|
|
let open Signatures.ExchangeSigningKeyValidity in
|
|
signf
|
|
(Eddsa.sign ~key:master_key)
|
|
{
|
|
start= stamp_start;
|
|
expire= stamp_expire;
|
|
end_= stamp_end;
|
|
signkey_pub= key;
|
|
}
|
|
in
|
|
SignKeySignature.{ key; master_sig }
|
|
in
|
|
let denom_sigs = List.map denom_signature future_denoms in
|
|
let signkey_sigs = List.map signkey_signature future_signkeys in
|
|
MasterSignatures.{ denom_sigs; signkey_sigs }
|
|
end
|
|
|
|
(* -- *)
|
|
|
|
let read_file fname = Bos.OS.File.read (Fpath.v fname)
|
|
let write_file fname content = Bos.OS.File.write (Fpath.v fname) content
|
|
|
|
let write_json_file fname jsont v =
|
|
let* content = Api.encode jsont v in
|
|
write_file fname content
|
|
|
|
let read_master_key_file filename =
|
|
let* master_key = read_file filename in
|
|
Eddsa.priv_of_octets master_key |> Result.map_error (fun e -> `Bin_decode e)
|
|
|
|
let download ~output ~url =
|
|
Result.unwrap_err
|
|
@@
|
|
let open Bos in
|
|
OS.Cmd.run
|
|
Cmd.(
|
|
v "curl"
|
|
% "--silent"
|
|
% "--show-error"
|
|
% "--fail"
|
|
% "-o"
|
|
% output
|
|
% "-X"
|
|
% "GET"
|
|
% url)
|
|
|
|
let upload ~input ~url =
|
|
Result.unwrap_err
|
|
@@
|
|
let open Bos in
|
|
OS.Cmd.run
|
|
Cmd.(
|
|
v "curl"
|
|
% "--silent"
|
|
% "--show-error"
|
|
% "--fail"
|
|
% "-i"
|
|
% "-X"
|
|
% "POST"
|
|
% "-H"
|
|
% "Content-Type: application/json"
|
|
% "--data"
|
|
% ("@" ^ input)
|
|
% url)
|
|
|
|
let generate_eddsa_key ~output ~output_pubkey =
|
|
Result.unwrap_err
|
|
@@
|
|
let priv, pub = Mirage_crypto_ec.Ed25519.generate () in
|
|
let priv_data = Mirage_crypto_ec.Ed25519.priv_to_octets priv in
|
|
let pub_data =
|
|
Mirage_crypto_ec.Ed25519.pub_to_octets pub |> Crockford.encode
|
|
in
|
|
let* () = write_file output priv_data in
|
|
let* () = write_file output_pubkey pub_data in
|
|
Ok ()
|
|
|
|
let sign ~master_key ~input ~output =
|
|
Result.unwrap_err
|
|
@@
|
|
let* master_key = read_master_key_file master_key in
|
|
let* input = read_file input in
|
|
let master_pub = Eddsa.pub_of_priv master_key in
|
|
let* future_keys_response = Api.decode Api.FutureKeysResponse.jsont input in
|
|
let* () = Future_keys.verify master_pub future_keys_response in
|
|
let master_signatures = Future_keys.make ~master_key future_keys_response in
|
|
write_json_file output Api.MasterSignatures.jsont master_signatures
|
|
|
|
let revoke_denom ~output ~master_key ~h_denom =
|
|
Result.unwrap_err
|
|
@@
|
|
let* key = read_master_key_file master_key in
|
|
let* h_denom_pub =
|
|
DenominationHash.of_crockford h_denom |> Result.map_error (fun e -> `Msg e)
|
|
in
|
|
let denom_revoke =
|
|
let master_sig =
|
|
let open Signatures.MasterDenominationKeyRevocation in
|
|
signf (Eddsa.sign ~key) { h_denom_pub }
|
|
in
|
|
Api.DenomRevocationSignature.{ master_sig }
|
|
in
|
|
write_json_file output Api.DenomRevocationSignature.jsont denom_revoke
|
|
|
|
let revoke_signkey ~output ~master_key ~signkey =
|
|
Result.unwrap_err
|
|
@@
|
|
let* key = read_master_key_file master_key in
|
|
let signkey_revoke =
|
|
let master_sig =
|
|
let open Signatures.MasterSigningKeyRevocation in
|
|
signf (Eddsa.sign ~key) { exchange_pub= signkey }
|
|
in
|
|
Api.SignkeyRevocationSignature.{ master_sig }
|
|
in
|
|
write_json_file output Api.SignkeyRevocationSignature.jsont signkey_revoke
|
|
|
|
let global_fees ~output ~master_key ~start_date ~end_date ~history_fee
|
|
~account_fee ~purse_fee ~history_expiration ~purse_account_limit
|
|
~purse_timeout =
|
|
Result.unwrap_err
|
|
@@
|
|
let* key = read_master_key_file master_key in
|
|
let* purse_account_limit =
|
|
match
|
|
purse_account_limit >= 0
|
|
&& purse_account_limit <= Int32.to_int Int32.max_int
|
|
with
|
|
| false -> Fmt.error_msg "invalid purse_account_limit value"
|
|
| true -> Ok (Int32.of_int purse_account_limit)
|
|
in
|
|
let master_sig =
|
|
let open Signatures.GlobalFees in
|
|
signf (Eddsa.sign ~key)
|
|
{
|
|
start_date;
|
|
end_date;
|
|
purse_timeout;
|
|
history_expiration;
|
|
history_fee;
|
|
account_fee;
|
|
purse_fee;
|
|
purse_account_limit;
|
|
}
|
|
in
|
|
let global_fees =
|
|
Api.GlobalFees.
|
|
{
|
|
start_date;
|
|
end_date;
|
|
purse_timeout;
|
|
history_expiration;
|
|
history_fee;
|
|
account_fee;
|
|
purse_fee;
|
|
purse_account_limit;
|
|
master_sig;
|
|
}
|
|
in
|
|
write_json_file output Api.GlobalFees.jsont global_fees
|
|
|
|
let enable_auditor ~output ~master_key ~auditor_url ~auditor_name ~auditor_pub =
|
|
Result.unwrap_err
|
|
@@
|
|
let* key = read_master_key_file master_key in
|
|
let validity_start = Timestamp.of_s (now_s ()) in
|
|
let master_sig =
|
|
let open Signatures.MasterAddAuditor in
|
|
signf (Eddsa.sign ~key)
|
|
{
|
|
start_date= validity_start;
|
|
auditor_pub;
|
|
h_auditor_url= H64_cstring.hash auditor_url;
|
|
}
|
|
in
|
|
let v =
|
|
Api.AuditorSetupMessage.
|
|
{ auditor_url; auditor_name; auditor_pub; master_sig; validity_start }
|
|
in
|
|
write_json_file output Api.AuditorSetupMessage.jsont v
|
|
|
|
let disable_auditor ~output ~master_key ~auditor_pub =
|
|
Result.unwrap_err
|
|
@@
|
|
let* key = read_master_key_file master_key in
|
|
let validity_end = TimeAbsolute.of_s (now_s ()) in
|
|
(* hack for tests: +1sec to be sure it overwrite previous timestamp *)
|
|
let validity_end =
|
|
Timestamp.of_absolute (TimeAbsolute.add validity_end TimeRelative.(of_s 1L))
|
|
in
|
|
let master_sig =
|
|
let open Signatures.MasterDelAuditor in
|
|
signf (Eddsa.sign ~key) { end_date= validity_end; auditor_pub }
|
|
in
|
|
let v = Api.AuditorTeardownMessage.{ master_sig; validity_end } in
|
|
write_json_file output Api.AuditorTeardownMessage.jsont v
|
|
|
|
let wire_fee ~output ~master_key ~wire_method ~fee_start ~fee_end ~closing_fee
|
|
~wire_fee =
|
|
Result.unwrap_err
|
|
@@
|
|
let* key = read_master_key_file master_key in
|
|
let master_sig_wire =
|
|
let open Signatures.MasterWireFee in
|
|
signf (Eddsa.sign ~key)
|
|
{
|
|
h_wire_method= H64_cstring.hash wire_method;
|
|
start_date= fee_start;
|
|
end_date= fee_end;
|
|
closing_fee;
|
|
wire_fee;
|
|
}
|
|
in
|
|
let v =
|
|
Api.WireFeeSetupMessage.
|
|
{
|
|
wire_method;
|
|
fee_start;
|
|
fee_end;
|
|
closing_fee;
|
|
wire_fee;
|
|
master_sig_wire;
|
|
}
|
|
in
|
|
write_json_file output Api.WireFeeSetupMessage.jsont v
|
|
|
|
let enable_wire ~output ~master_key ~payto_uri ~bank_label ~priority =
|
|
Result.unwrap_err
|
|
@@
|
|
(* TODO wire *)
|
|
let conversion_url = None in
|
|
let credit_restrictions = [] in
|
|
let debit_restrictions = [] in
|
|
let h_conversion_url =
|
|
H64_cstring.hash ((* ?? *) Option.value ~default:"" conversion_url)
|
|
in
|
|
let h_credit_restrictions = H64_cstring.hash "" in
|
|
let h_debit_restrictions = H64_cstring.hash "" in
|
|
(* - *)
|
|
let* key = read_master_key_file master_key in
|
|
let validity_start = Timestamp.of_s (now_s ()) in
|
|
let h_wire_details = FullPaytoHash.hash payto_uri in
|
|
let master_sig_wire =
|
|
let open Signatures.MasterWireDetails in
|
|
signf (Eddsa.sign ~key)
|
|
{
|
|
h_wire_details;
|
|
h_conversion_url;
|
|
h_credit_restrictions;
|
|
h_debit_restrictions;
|
|
}
|
|
in
|
|
let master_sig_add =
|
|
let open Signatures.MasterAddWire in
|
|
signf (Eddsa.sign ~key)
|
|
{
|
|
start_date= validity_start;
|
|
h_wire= h_wire_details;
|
|
h_conversion_url;
|
|
h_credit_restrictions;
|
|
h_debit_restrictions;
|
|
}
|
|
in
|
|
let v =
|
|
Api.WireSetupMessage.
|
|
{
|
|
master_sig_wire;
|
|
master_sig_add;
|
|
payto_uri;
|
|
conversion_url;
|
|
credit_restrictions;
|
|
debit_restrictions;
|
|
validity_start;
|
|
bank_label;
|
|
priority;
|
|
}
|
|
in
|
|
write_json_file output Api.WireSetupMessage.jsont v
|
|
|
|
let disable_wire ~output ~master_key ~payto_uri =
|
|
Result.unwrap_err
|
|
@@
|
|
let* key = read_master_key_file master_key in
|
|
let validity_end = TimeAbsolute.of_s (now_s ()) in
|
|
(* hack for tests: +1sec to be sure it overwrite previous timestamp *)
|
|
let validity_end =
|
|
Timestamp.of_absolute (TimeAbsolute.add validity_end TimeRelative.(of_s 1L))
|
|
in
|
|
let h_wire = FullPaytoHash.hash payto_uri in
|
|
let master_sig_del =
|
|
let open Signatures.MasterDelWire in
|
|
signf (Eddsa.sign ~key) { end_date= validity_end; h_wire }
|
|
in
|
|
let v = Api.WireTeardownMessage.{ payto_uri; master_sig_del; validity_end } in
|
|
write_json_file output Api.WireTeardownMessage.jsont v
|
|
|
|
let drain ~output ~master_key ~debit_account_section ~credit_payto_uri
|
|
~wtid_octets ~date ~amount =
|
|
Result.unwrap_err
|
|
@@
|
|
let* key = read_master_key_file master_key in
|
|
let* wtid =
|
|
Api.Bytes32.of_octets wtid_octets |> Result.map_error (fun e -> `Msg e)
|
|
in
|
|
let master_sig =
|
|
let open Signatures.MasterDrainProfit in
|
|
signf (Eddsa.sign ~key)
|
|
{
|
|
wtid;
|
|
date;
|
|
amount;
|
|
h_section= H64_cstring.hash debit_account_section;
|
|
h_payto= FullPaytoHash.hash credit_payto_uri;
|
|
}
|
|
in
|
|
let v =
|
|
Api.DrainProfitsMessage.
|
|
{
|
|
debit_account_section;
|
|
credit_payto_uri;
|
|
wtid;
|
|
master_sig;
|
|
date;
|
|
amount;
|
|
}
|
|
in
|
|
write_json_file output Api.DrainProfitsMessage.jsont v
|