diff --git a/src/bin_signer/main_signer.ml b/src/bin_signer/main_signer.ml index 1cd28c4a0..d2140f6ef 100644 --- a/src/bin_signer/main_signer.ml +++ b/src/bin_signer/main_signer.ml @@ -259,10 +259,19 @@ let require_auth_arg () = ~doc:"Require a signature from the caller to sign." () +let password_filename_arg () = + arg + ~long:"password-file" + ~short:'f' + ~placeholder:"filename" + ~doc:"Absolute path of the password file" + (string_parameter ()) + let global_options () = - args2 + args3 (base_dir_arg ()) (require_auth_arg ()) + (password_filename_arg ()) (* Main (lwt) entry *) let main () = @@ -286,12 +295,13 @@ let main () = begin begin parse_global_options - (global_options ()) () original_args >>=? fun ((base_dir, require_auth), remaining) -> + (global_options ()) () original_args >>=? + fun ((base_dir, require_auth, password_filename), remaining) -> let base_dir = Option.unopt ~default:default_base_dir base_dir in let cctxt = object inherit Client_context_unix.unix_logger ~base_dir inherit Client_context_unix.unix_prompter - inherit Client_context_unix.unix_wallet ~base_dir + inherit Client_context_unix.unix_wallet ~base_dir ~password_filename end in Client_keys.register_signer (module Tezos_signer_backends.Encrypted.Make(struct diff --git a/src/lib_client_base/client_context.ml b/src/lib_client_base/client_context.ml index b2dcc9866..7cfb3245a 100644 --- a/src/lib_client_base/client_context.ml +++ b/src/lib_client_base/client_context.ml @@ -65,6 +65,7 @@ class simple_printer log = end class type wallet = object + method password_filename : string option method with_lock : (unit -> 'a Lwt.t) -> 'a Lwt.t method load : string -> default:'a -> 'a Data_encoding.encoding -> 'a tzresult Lwt.t method write : string -> 'a -> 'a Data_encoding.encoding -> unit tzresult Lwt.t @@ -96,6 +97,7 @@ class type full = object end class proxy_context (obj : full) = object + method password_filename = obj#password_filename method base = obj#base method block = obj#block method confirmations = obj#confirmations diff --git a/src/lib_client_base/client_context.mli b/src/lib_client_base/client_context.mli index 317631206..6036c77ba 100644 --- a/src/lib_client_base/client_context.mli +++ b/src/lib_client_base/client_context.mli @@ -45,6 +45,7 @@ class type io = object end class type wallet = object + method password_filename : string option method with_lock : (unit -> 'a Lwt.t) -> 'a Lwt.t method load : string -> default:'a -> 'a Data_encoding.encoding -> 'a tzresult Lwt.t method write : string -> 'a -> 'a Data_encoding.encoding -> unit tzresult Lwt.t diff --git a/src/lib_client_base_unix/client_config.ml b/src/lib_client_base_unix/client_config.ml index 92255619a..434b2f8af 100644 --- a/src/lib_client_base_unix/client_config.ml +++ b/src/lib_client_base_unix/client_config.ml @@ -106,6 +106,7 @@ module Cfg_file = struct web_port: int ; remote_signer: Uri.t option ; confirmations: int option ; + password_filename: string option ; } let default = { @@ -116,6 +117,7 @@ module Cfg_file = struct web_port = 8080 ; remote_signer = None ; confirmations = Some 0 ; + password_filename = None ; } open Data_encoding @@ -123,25 +125,27 @@ module Cfg_file = struct let encoding = conv (fun { base_dir ; node_addr ; node_port ; tls ; web_port ; - remote_signer ; confirmations } -> + remote_signer ; confirmations ; password_filename } -> (base_dir, Some node_addr, Some node_port, - Some tls, Some web_port, remote_signer, confirmations)) + Some tls, Some web_port, remote_signer, confirmations, password_filename )) (fun (base_dir, node_addr, node_port, tls, web_port, - remote_signer, confirmations) -> + remote_signer, confirmations, password_filename) -> let node_addr = Option.unopt ~default:default.node_addr node_addr in let node_port = Option.unopt ~default:default.node_port node_port in let tls = Option.unopt ~default:default.tls tls in let web_port = Option.unopt ~default:default.web_port web_port in { base_dir ; node_addr ; node_port ; tls ; web_port ; - remote_signer ; confirmations }) - (obj7 + remote_signer ; confirmations ; password_filename }) + (obj8 (req "base_dir" string) (opt "node_addr" string) (opt "node_port" int16) (opt "tls" bool) (opt "web_port" int16) (opt "remote_signer" RPC_client.uri_encoding) - (opt "confirmations" int8)) + (opt "confirmations" int8) + (opt "password_filename" string) + ) let from_json json = Data_encoding.Json.destruct encoding json @@ -159,6 +163,7 @@ end type cli_args = { block: Shell_services.block ; confirmations: int option ; + password_filename: string option ; protocol: Protocol_hash.t option ; print_timings: bool ; log_requests: bool ; @@ -167,6 +172,7 @@ type cli_args = { let default_cli_args = { block = default_block ; confirmations = Some 0 ; + password_filename = None ; protocol = None ; print_timings = false ; log_requests = false ; @@ -298,6 +304,13 @@ let remote_signer_arg () = ~doc:"URI of the remote signer" (parameter (fun _ x -> Tezos_signer_backends.Remote.parse_base_uri x)) +let password_filename_arg () = + arg + ~long:"password-filename" + ~short:'f' + ~placeholder:"filename" + ~doc:"path to the passowrd filename" + (string_parameter ()) let read_config_file config_file = Lwt_utils_unix.Json.read_file config_file >>=? fun cfg_json -> @@ -372,7 +385,7 @@ let commands config_file cfg = ] let global_options () = - args11 + args12 (base_dir_arg ()) (config_file_arg ()) (timings_switch ()) @@ -384,6 +397,7 @@ let global_options () = (port_arg ()) (tls_switch ()) (remote_signer_arg ()) + (password_filename_arg ()) let parse_config_args (ctx : #Client_context.full) argv = parse_global_options @@ -400,7 +414,8 @@ let parse_config_args (ctx : #Client_context.full) argv = node_addr, node_port, tls, - remote_signer), remaining) -> + remote_signer, + password_filename), remaining) -> begin match base_dir with | None -> let base_dir = default_base_dir in @@ -443,7 +458,7 @@ let parse_config_args (ctx : #Client_context.full) argv = (Option.first_some remote_signer_env cfg.remote_signer) in let confirmations = Option.unopt ~default:cfg.confirmations confirmations in let cfg = { cfg with tls ; node_port ; node_addr ; - remote_signer ; confirmations } in + remote_signer ; confirmations ; password_filename } in if Sys.file_exists base_dir && not (Sys.is_directory base_dir) then begin Format.eprintf "%s is not a directory.@." base_dir ; exit 1 ; @@ -455,6 +470,6 @@ let parse_config_args (ctx : #Client_context.full) argv = Lwt_utils_unix.create_dir config_dir >>= fun () -> return (cfg, - { block ; confirmations ; + { block ; confirmations ; password_filename ; print_timings = timings ; log_requests ; protocol }, commands config_file cfg, remaining) diff --git a/src/lib_client_base_unix/client_context_unix.ml b/src/lib_client_base_unix/client_context_unix.ml index b92e81b2f..dc8311438 100644 --- a/src/lib_client_base_unix/client_context_unix.ml +++ b/src/lib_client_base_unix/client_context_unix.ml @@ -28,7 +28,9 @@ include Tezos_stdlib.Logging.Make_semantic(struct let name = "client.context.uni let filename_tag = Tag.def ~doc:"Filename" "filename" Format.pp_print_string -class unix_wallet ~base_dir : wallet = object (self) +class unix_wallet ~base_dir ~password_filename : wallet = object (self) + + method password_filename = password_filename method private filename alias_name = Filename.concat @@ -130,11 +132,11 @@ class unix_logger ~base_dir = inherit Client_context.simple_printer log end -class unix_full ~base_dir ~block ~confirmations ~rpc_config : Client_context.full = +class unix_full ~base_dir ~block ~confirmations ~password_filename ~rpc_config : Client_context.full = object inherit unix_logger ~base_dir inherit unix_prompter - inherit unix_wallet ~base_dir + inherit unix_wallet ~base_dir ~password_filename inherit RPC_client.http_ctxt rpc_config Media_type.all_media_types method block = block method confirmations = confirmations diff --git a/src/lib_client_base_unix/client_context_unix.mli b/src/lib_client_base_unix/client_context_unix.mli index 5e5d71a41..7c86c5621 100644 --- a/src/lib_client_base_unix/client_context_unix.mli +++ b/src/lib_client_base_unix/client_context_unix.mli @@ -25,6 +25,7 @@ class unix_wallet : base_dir:string -> + password_filename: string option -> Client_context.wallet class unix_prompter : Client_context.prompter @@ -35,5 +36,6 @@ class unix_full : base_dir:string -> block:Shell_services.block -> confirmations:int option -> + password_filename: string option -> rpc_config:RPC_client.config -> Client_context.full diff --git a/src/lib_client_base_unix/client_main_run.ml b/src/lib_client_base_unix/client_main_run.ml index 149d6759e..891d99059 100644 --- a/src/lib_client_base_unix/client_main_run.ml +++ b/src/lib_client_base_unix/client_main_run.ml @@ -67,6 +67,7 @@ let main select_commands = (new unix_full ~block:Client_config.default_block ~confirmations:None + ~password_filename:None ~base_dir:Client_config.default_base_dir ~rpc_config:RPC_client.default_config) original_args @@ -90,6 +91,7 @@ let main select_commands = new unix_full ~block:parsed_args.block ~confirmations:parsed_args.confirmations + ~password_filename: parsed_args.password_filename ~base_dir:parsed_config_file.base_dir ~rpc_config:rpc_config in Client_keys.register_signer diff --git a/src/lib_signer_backends/encrypted.ml b/src/lib_signer_backends/encrypted.ml index da127093f..599cf49ea 100644 --- a/src/lib_signer_backends/encrypted.ml +++ b/src/lib_signer_backends/encrypted.ml @@ -132,6 +132,9 @@ module Encodings = struct end let decrypted = Hashtbl.create 13 + +(* we cache the password in this list to avoid + asking the user all the time *) let passwords = ref [] let rec interactive_decrypt_loop @@ -152,6 +155,31 @@ let rec interactive_decrypt_loop | None -> interactive_decrypt_loop cctxt ?name ~encrypted_sk algo +(* FixMe : this could be done more elegantly *) +let read_all_lines filename = + let lines = ref [] in + let chan = open_in filename in + try + while true; do + lines := input_line chan :: !lines + done; !lines + with End_of_file -> + close_in chan; + List.rev !lines + +(* add all passwords in [filename] to the list of known passwords *) +let password_file_load = function + |Some filename -> + if Sys.file_exists filename then begin + let l = read_all_lines filename in + let l = List.map MBytes.of_string l in + passwords := !passwords @ l ; + return_unit + end + else + return_unit + | None -> return_unit + let rec noninteractive_decrypt_loop algo ~encrypted_sk = function | [] -> return_none | password :: passwords -> @@ -181,6 +209,7 @@ let decrypt (cctxt : #Client_context.prompter) ?name sk_uri = let decrypt_all (cctxt : #Client_context.io_wallet) = Secret_key.load cctxt >>=? fun sks -> + password_file_load cctxt#password_filename >>=? fun () -> iter_s begin fun (name, sk_uri) -> if Uri.scheme (sk_uri : sk_uri :> Uri.t) <> Some scheme then return_unit @@ -191,6 +220,7 @@ let decrypt_all (cctxt : #Client_context.io_wallet) = let decrypt_list (cctxt : #Client_context.io_wallet) keys = Secret_key.load cctxt >>=? fun sks -> + password_file_load cctxt#password_filename >>=? fun () -> iter_s begin fun (name, sk_uri) -> if Uri.scheme (sk_uri : sk_uri :> Uri.t) = Some scheme && (keys = [] || List.mem name keys) then diff --git a/src/proto_alpha/lib_delegate/test/proto_alpha_helpers.ml b/src/proto_alpha/lib_delegate/test/proto_alpha_helpers.ml index 00fdd6b6f..70fbfc79b 100644 --- a/src/proto_alpha/lib_delegate/test/proto_alpha_helpers.ml +++ b/src/proto_alpha/lib_delegate/test/proto_alpha_helpers.ml @@ -56,6 +56,7 @@ let no_write_context ?(block = `Head 0) config : #Client_context.full = object method with_lock : type a. (unit -> a Lwt.t) -> a Lwt.t = fun f -> f () method block = block method confirmations = None + method password_filename = None method prompt : type a. (a, string tzresult) Client_context.lwt_format -> a = Format.kasprintf (fun _ -> return "") method prompt_password : type a. (a, MBytes.t tzresult) Client_context.lwt_format -> a =