diff --git a/src/lib_client_base_unix/client_config.ml b/src/lib_client_base_unix/client_config.ml index 7bf72d6ed..c4c30fede 100644 --- a/src/lib_client_base_unix/client_config.ml +++ b/src/lib_client_base_unix/client_config.ml @@ -12,6 +12,7 @@ type error += Invalid_block_argument of string type error += Invalid_protocol_argument of string type error += Invalid_port_arg of string +type error += Invalid_remote_signer_argument of string let () = register_error_kind `Branch @@ -45,11 +46,22 @@ let () = Format.fprintf ppf "Value %s is not a valid TCP port." s) Data_encoding.(obj1 (req "value" string)) (function Invalid_port_arg s -> Some s | _ -> None) - (fun s -> Invalid_port_arg s) + (fun s -> Invalid_port_arg s) ; + register_error_kind + `Branch + ~id: "invalid_remote_signer_argument" + ~title: "Unexpected URI of remote signer" + ~description: "The remote signer argument could not be parsed" + ~pp: + (fun ppf s -> + Format.fprintf ppf "Value '%s' is not a valid URI." s) + Data_encoding.(obj1 (req "value" string)) + (function Invalid_remote_signer_argument s -> Some s | _ -> None) + (fun s -> Invalid_remote_signer_argument s) +let home = try Sys.getenv "HOME" with Not_found -> "/root" let default_base_dir = - let home = try Sys.getenv "HOME" with Not_found -> "/root" in Filename.concat home ".tezos-client" let default_block = `Head 0 @@ -64,6 +76,7 @@ module Cfg_file = struct node_port: int ; tls: bool ; web_port: int ; + remote_signer: Uri.t option ; } let default = { @@ -72,27 +85,29 @@ module Cfg_file = struct node_port = 8732 ; tls = false ; web_port = 8080 ; + remote_signer = None ; } open Data_encoding let encoding = conv - (fun { base_dir ; node_addr ; node_port ; tls ; web_port } -> + (fun { base_dir ; node_addr ; node_port ; tls ; web_port ; remote_signer } -> (base_dir, Some node_addr, Some node_port, - Some tls, Some web_port)) - (fun (base_dir, node_addr, node_port, tls, web_port) -> + Some tls, Some web_port, remote_signer)) + (fun (base_dir, node_addr, node_port, tls, web_port, remote_signer) -> 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 }) - (obj5 + { base_dir ; node_addr ; node_port ; tls ; web_port ; remote_signer }) + (obj6 (req "base_dir" string) (opt "node_addr" string) (opt "node_port" int16) (opt "tls" bool) - (opt "web_port" int16)) + (opt "web_port" int16) + (opt "remote_signer" RPC_client.uri_encoding)) let from_json json = Data_encoding.Json.destruct encoding json @@ -218,6 +233,17 @@ let tls_switch () = ~short:'S' ~doc:"use TLS to connect to node." () +let remote_signer_arg () = + arg + ~long:"remote-signer" + ~short:'R' + ~placeholder:"uri" + ~doc:"URI of the remote signer" + (parameter + (fun _ x -> + (* TODO check scheme = 'unix/tcp/https' *) + try return (Uri.of_string x) + with _ -> fail (Invalid_remote_signer_argument x))) let read_config_file config_file = Lwt_utils_unix.Json.read_file config_file >>=? fun cfg_json -> @@ -292,7 +318,8 @@ let commands config_file cfg = ] let global_options () = - args9 (base_dir_arg ()) + args10 + (base_dir_arg ()) (config_file_arg ()) (timings_switch ()) (block_arg ()) @@ -301,6 +328,7 @@ let global_options () = (addr_arg ()) (port_arg ()) (tls_switch ()) + (remote_signer_arg ()) let parse_config_args (ctx : #Client_context.full) argv = parse_global_options @@ -315,7 +343,8 @@ let parse_config_args (ctx : #Client_context.full) argv = log_requests, node_addr, node_port, - tls), remaining) -> + tls, + remote_signer), remaining) -> begin match base_dir with | None -> let base_dir = default_base_dir in @@ -352,7 +381,8 @@ let parse_config_args (ctx : #Client_context.full) argv = let tls = cfg.tls || tls in let node_addr = Option.unopt ~default:cfg.node_addr node_addr in let node_port = Option.unopt ~default:cfg.node_port node_port in - let cfg = { cfg with tls ; node_port ; node_addr } in + let remote_signer = Option.first_some remote_signer cfg.remote_signer in + let cfg = { cfg with tls ; node_port ; node_addr ; remote_signer } 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 ; diff --git a/src/lib_client_base_unix/client_main_run.ml b/src/lib_client_base_unix/client_main_run.ml index 998d0cace..e6336e4d4 100644 --- a/src/lib_client_base_unix/client_main_run.ml +++ b/src/lib_client_base_unix/client_main_run.ml @@ -122,6 +122,12 @@ let main select_commands = ~block:parsed_args.block ~base_dir:parsed_config_file.base_dir ~rpc_config:rpc_config in + Option.iter parsed_config_file.remote_signer ~f: begin fun signer -> + Client_keys.register_signer + (module Tezos_signer_backends.Remote.Make(struct + let default = signer + end)) + end ; begin match autocomplete with | Some (prev_arg, cur_arg, script) -> Clic.autocompletion diff --git a/src/lib_client_commands/client_keys_commands.ml b/src/lib_client_commands/client_keys_commands.ml index dd0d4a495..08ed95715 100644 --- a/src/lib_client_commands/client_keys_commands.ml +++ b/src/lib_client_commands/client_keys_commands.ml @@ -15,8 +15,8 @@ let group = let encrypted_switch () = if List.exists - (fun (_, (module Signer : Client_keys.SIGNER)) -> - Signer.scheme = Tezos_signer_backends.Unencrypted.scheme) + (fun (scheme, _) -> + scheme = Tezos_signer_backends.Unencrypted.scheme) (Client_keys.registered_signers ()) then Clic.switch ~long:"encrypted" diff --git a/src/lib_rpc_http/RPC_client.mli b/src/lib_rpc_http/RPC_client.mli index e2089fc74..277e232d5 100644 --- a/src/lib_rpc_http/RPC_client.mli +++ b/src/lib_rpc_http/RPC_client.mli @@ -93,3 +93,4 @@ val generic_call : [< RPC_service.meth ] -> Uri.t -> (content, content) RPC_context.rest_result Lwt.t +val uri_encoding: Uri.t Data_encoding.t diff --git a/src/lib_signer_backends/remote.ml b/src/lib_signer_backends/remote.ml new file mode 100644 index 000000000..66f4cdc75 --- /dev/null +++ b/src/lib_signer_backends/remote.ml @@ -0,0 +1,83 @@ +(**************************************************************************) +(* *) +(* Copyright (c) 2014 - 2018. *) +(* Dynamic Ledger Solutions, Inc. *) +(* *) +(* All rights reserved. No warranty, explicit or implicit, provided. *) +(* *) +(**************************************************************************) + +open Client_keys + +let scheme = "remote" + +module Make(S : sig val default : Uri.t end) = struct + + let scheme = scheme + + let title = + "Built-in tezos-signer using remote wallet." + + let description = + "Valid locators are one of these two forms:\n\ + \ - unix [path to local signer socket] \n\ + \ - tcp [host] [port] \n\ + \ - https [host] [port] \n\ + All fields except the key can be of the form '$VAR', \ + in which case their value is taken from environment variable \ + VAR each time the key is accessed.\n\ + Not specifiyng fields sets them to $TEZOS_SIGNER_UNIX_PATH, \ + $TEZOS_SIGNER_TCP_HOST and $TEZOS_SIGNER_TCP_PORT, \ + $TEZOS_SIGNER_HTTPS_HOST and $TEZOS_SIGNER_HTTPS_PORT, \ + that get evaluated to default values '$HOME/.tezos-signer-socket', \ + localhost and 6732, and can be set later on." + + let get_remote () = + match Uri.scheme S.default with + | Some "unix" -> (module Socket.Unix : SIGNER) + | Some "tcp" -> (module Socket.Tcp : SIGNER) + | Some "https" -> (module Https : SIGNER) + | _ -> assert false + + module Remote = (val get_remote () : SIGNER) + let key = + match Uri.scheme S.default with + | Some "unix" | Some "tcp" -> + (fun uri -> + let key = Uri.path uri in + Uri.add_query_param S.default ("key", [key])) + | Some "https" -> + (fun uri -> + let key = Uri.path uri in + match Uri.path S.default with + | "" -> Uri.with_path S.default key + | path -> Uri.with_path S.default (path ^ "/" ^ key)) + | _ -> assert false + + let public_key pk_uri = + Remote.public_key + (Client_keys.make_pk_uri (key (pk_uri : pk_uri :> Uri.t))) + + let public_key_hash pk_uri = + Remote.public_key_hash + (Client_keys.make_pk_uri (key (pk_uri : pk_uri :> Uri.t))) + + let neuterize sk_uri = + Remote.neuterize + (Client_keys.make_sk_uri (key (sk_uri : sk_uri :> Uri.t))) + + let sign ?watermark sk_uri msg = + Remote.sign + ?watermark + (Client_keys.make_sk_uri (key (sk_uri : sk_uri :> Uri.t))) + msg + +end + +let make_sk sk = + Client_keys.make_sk_uri + (Uri.make ~scheme ~path:(Signature.Secret_key.to_b58check sk) ()) + +let make_pk pk = + Client_keys.make_pk_uri + (Uri.make ~scheme ~path:(Signature.Public_key.to_b58check pk) ()) diff --git a/src/lib_signer_backends/remote.mli b/src/lib_signer_backends/remote.mli new file mode 100644 index 000000000..b34a33362 --- /dev/null +++ b/src/lib_signer_backends/remote.mli @@ -0,0 +1,13 @@ +(**************************************************************************) +(* *) +(* Copyright (c) 2014 - 2017. *) +(* Dynamic Ledger Solutions, Inc. *) +(* *) +(* All rights reserved. No warranty, explicit or implicit, provided. *) +(* *) +(**************************************************************************) + +module Make(S : sig val default : Uri.t end) : Client_keys.SIGNER + +val make_pk: Signature.public_key -> Client_keys.pk_uri +val make_sk: Signature.secret_key -> Client_keys.sk_uri diff --git a/src/proto_alpha/lib_client_commands/client_proto_context_commands.ml b/src/proto_alpha/lib_client_commands/client_proto_context_commands.ml index 02546b0ca..021bcffdc 100644 --- a/src/proto_alpha/lib_client_commands/client_proto_context_commands.ml +++ b/src/proto_alpha/lib_client_commands/client_proto_context_commands.ml @@ -16,6 +16,11 @@ open Client_proto_programs open Client_keys open Client_proto_args +let encrypted_switch = + Clic.switch + ~long:"encrypted" + ~doc:("Encrypt the key on-disk") () + let report_michelson_errors ?(no_print_source=false) ~msg (cctxt : #Client_context.printer) = function | Error errs -> cctxt#warning "%a" @@ -264,7 +269,7 @@ let commands () = (args3 (Secret_key.force_switch ()) (Client_proto_args.no_confirmation) - (Client_keys_commands.encrypted_switch ())) + encrypted_switch) (prefixes [ "activate" ; "account" ] @@ Secret_key.fresh_alias_param @@ prefixes [ "with" ]