From 73b7fc89a554d62d570deadfd5e677cf8a1e1a3c Mon Sep 17 00:00:00 2001 From: Pietro Date: Tue, 22 May 2018 18:04:37 +0200 Subject: [PATCH] Client: introduce tezos-signer --- .gitignore | 1 + Makefile | 2 + src/bin_client/jbuild | 4 +- src/bin_client/main_signer.ml | 88 +++++++ src/bin_client/tezos-init-sandboxed-client.sh | 8 + src/lib_client_base/jbuild | 1 + .../client_signer_remote.ml | 128 ++++++++++ .../client_signer_remote_messages.ml | 225 ++++++++++++++++++ .../client_signer_remote_messages.mli | 65 +++++ src/lib_stdlib/logging.ml | 1 + src/lib_stdlib/logging.mli | 1 + 11 files changed, 522 insertions(+), 2 deletions(-) create mode 100644 src/bin_client/main_signer.ml create mode 100644 src/lib_client_base_unix/client_signer_remote.ml create mode 100644 src/lib_client_base_unix/client_signer_remote_messages.ml create mode 100644 src/lib_client_base_unix/client_signer_remote_messages.mli diff --git a/.gitignore b/.gitignore index 44b0673bb..79099dc4e 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ __pycache__ /tezos-client /tezos-admin-client /tezos-alpha-baker +/tezos-signer /scripts/opam-test-all.sh.DONE /scripts/create_genesis/src diff --git a/Makefile b/Makefile index a4ae2e51d..b268871e1 100644 --- a/Makefile +++ b/Makefile @@ -14,11 +14,13 @@ all: src/bin_node/main.exe \ src/bin_client/main_client.exe \ src/bin_client/main_admin.exe \ + src/bin_client/main_signer.exe \ src/lib_protocol_compiler/main_native.exe \ src/proto_alpha/bin_baker/main_baker_alpha.exe @cp _build/default/src/bin_node/main.exe tezos-node @cp _build/default/src/bin_client/main_client.exe tezos-client @cp _build/default/src/bin_client/main_admin.exe tezos-admin-client + @cp _build/default/src/bin_client/main_signer.exe tezos-signer @cp _build/default/src/lib_protocol_compiler/main_native.exe tezos-protocol-compiler @cp _build/default/src/proto_alpha/bin_baker/main_baker_alpha.exe tezos-alpha-baker diff --git a/src/bin_client/jbuild b/src/bin_client/jbuild index dc517a264..8b9cd5e8a 100644 --- a/src/bin_client/jbuild +++ b/src/bin_client/jbuild @@ -1,8 +1,8 @@ (jbuild_version 1) (executables - ((names (main_client main_admin)) - (public_names (tezos-client tezos-admin-client)) + ((names (main_client main_admin main_signer)) + (public_names (tezos-client tezos-admin-client tezos-signer)) (libraries (tezos-base tezos-rpc-http tezos-shell-services diff --git a/src/bin_client/main_signer.ml b/src/bin_client/main_signer.ml new file mode 100644 index 000000000..6543efcf8 --- /dev/null +++ b/src/bin_client/main_signer.ml @@ -0,0 +1,88 @@ +(**************************************************************************) +(* *) +(* Copyright (c) 2014 - 2018. *) +(* Dynamic Ledger Solutions, Inc. *) +(* *) +(* All rights reserved. No warranty, explicit or implicit, provided. *) +(* *) +(**************************************************************************) + +open Client_signer_remote_messages + +let run_daemon (cctxt : #Client_context.full) _delegates = + let uri = Uri.of_string "tezos:/localhost:9000" in + Connection.bind uri >>= fun fd -> + cctxt#message "Accepting request on %s" (Uri.to_string uri) >>= fun () -> + let rec loop () = + Lwt_unix.accept fd >>= fun (fd, _) -> + Lwt.async (fun () -> + cctxt#message "Receiving" >>= fun () -> + recv fd Request.encoding >>=? function + | Sign req -> + cctxt#message "Signer: Request for siging data" >>= fun () -> + Client_keys.alias_keys cctxt req.key >>=? begin function + | Some (_, _, Some skloc) -> + cctxt#message "Signer: signing data" >>= fun () -> + Client_keys.sign cctxt skloc req.data >>=? fun signature -> + send fd Sign.Response.encoding (ok { Sign.Response.signature = signature }) + | _ -> + send fd Public_key.Response.encoding (error (Unkwnon_alias_key req.key)) >>=? fun _ -> + cctxt#warning "Singer: Cannot get alias for key %s" req.key >>= fun () -> + return () + end + + | Public_key req -> + cctxt#message "Singer: Request for public key %s" req.key >>= fun () -> + Client_keys.alias_keys cctxt req.key >>= begin function + | Error err -> + send fd Public_key.Response.encoding (Error err) >>=? fun _ -> + cctxt#warning "Singer: Cannot get alias for key %s" req.key >>= fun () -> + return () + | Ok value -> + begin match value with + | Some (public_key_hash, _, _) -> + cctxt#message "Signer: Hash Public Key %a" Signature.Public_key_hash.pp public_key_hash >>= fun () -> + Client_keys.get_key cctxt public_key_hash >>= begin function + | (Error err) -> + send fd Public_key.Response.encoding (Error err) >>=? fun _ -> + cctxt#warning "Singer: cannot get key %s" req.key >>= fun () -> + return () + | Ok (_, public_key, _) -> + cctxt#message "Signer: Send Public Key %a" Signature.Public_key.pp public_key >>= fun () -> + send fd Public_key.Response.encoding + (ok { Public_key.Response.public_key = public_key }) >>=? fun _ -> + return () + end + | _ -> begin + send fd Public_key.Response.encoding (error (Unkwnon_alias_key req.key)) >>=? fun _ -> + cctxt#warning "Signer cannot find key %s" req.key >>= fun () -> + return () + end + end + end + ); + loop () + in + Lwt_unix.listen fd 10; + cctxt#message "Listening" >>= fun () -> + loop () + +open Clic + +let group = + { Clic.name = "signer" ; + title = "Commands specific to the signing daemon" } + +let select_commands _ _ = + return + (List.flatten + [ Client_keys_commands.commands () ; + [ command ~group ~desc: "Launch the signer daemon." + no_options + (prefixes [ "signer" ; "daemon" ] + @@ seq_of_param Client_keys.Public_key_hash.alias_param) + (fun () delegates cctxt -> + run_daemon cctxt delegates) ; + ]]) + +let () = Client_main_run.run select_commands diff --git a/src/bin_client/tezos-init-sandboxed-client.sh b/src/bin_client/tezos-init-sandboxed-client.sh index 17267cba3..4de639b46 100755 --- a/src/bin_client/tezos-init-sandboxed-client.sh +++ b/src/bin_client/tezos-init-sandboxed-client.sh @@ -16,10 +16,12 @@ init_sandboxed_client() { client="$local_client -S -base-dir $client_dir -addr 127.0.0.1 -port $rpc" admin_client="$local_admin_client -S -base-dir $client_dir -addr 127.0.0.1 -port $rpc" alpha_baker="$local_alpha_baker -S -base-dir $client_dir -addr 127.0.0.1 -port $rpc" + signer="$local_signer -S -base-dir $client_dir -addr 127.0.0.1 -port $rpc" else client="$local_client -base-dir $client_dir -addr 127.0.0.1 -port $rpc" admin_client="$local_admin_client -base-dir $client_dir -addr 127.0.0.1 -port $rpc" alpha_baker="$local_alpha_baker -base-dir $client_dir -addr 127.0.0.1 -port $rpc" + signer="$local_signer -base-dir $client_dir -addr 127.0.0.1 -port $rpc" fi parameters_file="${parameters_file:-$client_dir/protocol_parameters.json}" @@ -246,12 +248,14 @@ main () { local_client="${local_client:-$bin_dir/../../_build/default/src/bin_client/main_client.exe}" local_admin_client="${local_admin_client:-$bin_dir/../../_build/default/src/bin_client/main_admin.exe}" local_alpha_baker="${local_alpha_baker:-$bin_dir/../../_build/default/src/proto_alpha/bin_baker/main_baker_alpha.exe}" + local_signer="${local_signer:-$bin_dir/../../_build/default/src/bin_client/main_signer.exe}" parameters_file="${parameters_file:-$bin_dir/../../scripts/protocol_parameters.json}" else # we assume a clean install with tezos-(admin-)client in the path local_client="${local_client:-$(which tezos-client)}" local_admin_client="${local_admin_client:-$(which tezos-admin-client)}" local_alpha_baker="${local_alpha_baker:-$(which tezos-alpha-baker)}" + local_signer="${local_signer:-$(which tezos-signer)}" fi if [ $# -lt 1 ] || [ "$1" -le 0 ] || [ 10 -le "$1" ]; then @@ -277,6 +281,10 @@ main () { echo "exec $alpha_baker \"\$@\"" >> $client_dir/bin/tezos-alpha-baker chmod +x $client_dir/bin/tezos-alpha-baker + echo '#!/bin/sh' > $client_dir/bin/tezos-signer + echo "exec $signer \"\$@\"" >> $client_dir/bin/tezos-signer + chmod +x $client_dir/bin/tezos-signer + cat </dev/null 2>&1 ; then tezos-client-reset; fi ; PATH="$client_dir/bin:\$PATH" ; export PATH ; diff --git a/src/lib_client_base/jbuild b/src/lib_client_base/jbuild index f6b369d42..e275395d1 100644 --- a/src/lib_client_base/jbuild +++ b/src/lib_client_base/jbuild @@ -12,6 +12,7 @@ -safe-string -open Tezos_base__TzPervasives -open Tezos_rpc + -open Tezos_stdlib_unix -open Tezos_shell_services)))) (alias diff --git a/src/lib_client_base_unix/client_signer_remote.ml b/src/lib_client_base_unix/client_signer_remote.ml new file mode 100644 index 000000000..a847bd254 --- /dev/null +++ b/src/lib_client_base_unix/client_signer_remote.ml @@ -0,0 +1,128 @@ +(**************************************************************************) +(* *) +(* Copyright (c) 2014 - 2018. *) +(* Dynamic Ledger Solutions, Inc. *) +(* *) +(* All rights reserved. No warranty, explicit or implicit, provided. *) +(* *) +(**************************************************************************) + +open Client_keys +open Client_signer_remote_messages + +let sign conn key data = + let req = { Sign.Request.key = key ; data } in + send conn Request.encoding (Request.Sign req) >>=? fun () -> + recv conn Sign.Response.encoding >>=? function + | Error err -> Lwt.return (Error err) + | Ok res -> return res.signature + +let public_key conn key = + let req = { Public_key.Request.key = key } in + send conn Request.encoding (Request.Public_key req) >>=? fun () -> + recv conn Public_key.Response.encoding >>=? function + | Error err -> Lwt.return (Error err) + | Ok res -> return res.public_key + + +module Remote_signer : SIGNER = struct + let scheme = "remote" + + let title = + "Built-in signer using remote wallet." + + let description = "" + + (* secret key is the identifier of the location key identifier *) + type secret_key = Uri.t * string + (* public key is the key itself *) + type public_key = Signature.Public_key.t + + let pks : (secret_key,public_key) Hashtbl.t = Hashtbl.create 53 + + (* XXX : I want to reuse the connection, but this doesn't work + let conn_table = Hashtbl.create 53 + let connect uri = + match Hashtbl.find_opt conn_table uri with + | None -> + Connection.connect uri >>= fun conn -> + Hashtbl.add conn_table uri conn; + Lwt.return conn + | Some conn -> Lwt.return conn + *) + + (* load and init the remote wallet. initialize the connection *) + let init _cctxt = return () + + let pk_locator_of_human_input _cctxt = function + | [] -> failwith "Remote Schema : Missing public key argument" + | uri :: key :: _ -> + return ( + Public_key_locator.create + ~scheme ~location:[String.trim uri; String.trim key] + ) + | l -> failwith + "Remote Schema : Wrong location type %a" + Format.(pp_print_list ~pp_sep:pp_print_cut pp_print_string) l + + let sk_to_locator (uri,key) = + Lwt.return ( + Secret_key_locator.create + ~scheme ~location:[Uri.to_string uri; String.trim key] + ) + + let sk_locator_of_human_input _cctxt = function + | [] -> failwith "Remote Schema : Missing secret key argument" + | uri_string :: key :: _ -> + let uri = Uri.of_string uri_string in + Connection.connect uri >>= fun conn -> + public_key conn key >>=? fun pk -> + Hashtbl.replace pks (uri,key) pk ; + sk_to_locator (uri,key) >>= fun locator -> + return locator + | l -> failwith + "Remote Schema : Missing secret key argument %a" + Format.(pp_print_list ~pp_sep:pp_print_cut pp_print_string) l + + let sk_of_locator = function + | (Sk_locator { location = (uri :: key :: _) }) -> + return (Uri.of_string uri, key) + | skloc -> + failwith "Remote Schema : sk_of_locator Wrong locator type: %s" + (Secret_key_locator.to_string skloc) + + let pk_of_locator = function + | (Pk_locator { location = ( location :: _ ) }) -> + Lwt.return (Signature.Public_key.of_b58check location) + | pkloc -> + failwith "Remote Schema : pk_of_locator Wrong locator type: %s" + (Public_key_locator.to_string pkloc) + + let pk_to_locator pk = + Public_key_locator.create + ~scheme ~location:[Signature.Public_key.to_b58check pk] |> + Lwt.return + + let neuterize ((uri, key) as sk) = + match Hashtbl.find_opt pks sk with + | Some pk -> Lwt.return pk + | None -> begin + Connection.connect uri >>= fun conn -> + public_key conn key >>= function + | Error _ -> Lwt.fail_with "Remote : Cannot obtain public key from remote signer" + | Ok pk -> begin + Hashtbl.replace pks sk pk ; + Lwt.return pk + end + end + + let public_key x = return x + let public_key_hash x = return (Signature.Public_key.hash x) + + let sign (uri, key) msg = + Connection.connect uri >>= fun conn -> + sign conn key msg +end + +let () = + register_signer (module Remote_signer) diff --git a/src/lib_client_base_unix/client_signer_remote_messages.ml b/src/lib_client_base_unix/client_signer_remote_messages.ml new file mode 100644 index 000000000..aafdf084c --- /dev/null +++ b/src/lib_client_base_unix/client_signer_remote_messages.ml @@ -0,0 +1,225 @@ +(**************************************************************************) +(* *) +(* Copyright (c) 2014 - 2018. *) +(* Dynamic Ledger Solutions, Inc. *) +(* *) +(* All rights reserved. No warranty, explicit or implicit, provided. *) +(* *) +(**************************************************************************) + +type error += + | Encoding_error + | Decoding_error + | Unkwnon_alias_key of string + | Unkwnon_request_kind + +let () = + register_error_kind `Permanent + ~id: "signer.unknown_alias_key" + ~title: "Unkwnon_alias_key" + ~description: "A remote key does not exists" + ~pp: (fun ppf s -> + Format.fprintf ppf "The key %s does not is not known on the remote signer" s) + Data_encoding.(obj1 (req "value" string)) + (function Unkwnon_alias_key s -> Some s | _ -> None) + (fun s -> Unkwnon_alias_key s) ; + register_error_kind `Permanent + ~id: "signer.unknown_request_kind" + ~title: "Unkwnon_request_kind" + ~description: "A request is not not understood by the remote signer" + ~pp: (fun ppf () -> + Format.fprintf ppf "The request is not not understood by the remote signer" ) + Data_encoding.empty + (function Unkwnon_request_kind -> Some () | _ -> None) + (fun () -> Unkwnon_request_kind) ; + register_error_kind `Permanent + ~id: "signer.encoding_error" + ~title: "Encoding_error" + ~description: "Error while encoding a request to the remote signer" + ~pp: (fun ppf () -> + Format.fprintf ppf "Could not encode a request to the remote signer") + Data_encoding.empty + (function Encoding_error -> Some () | _ -> None) + (fun () -> Encoding_error) ; + register_error_kind `Permanent + ~id: "signer.decoding_error" + ~title: "Decoding_error" + ~description: "Error while decoding a request to the remote signer" + ~pp: (fun ppf () -> + Format.fprintf ppf "Could not decode a request to the remote signer") + Data_encoding.empty + (function Decoding_error -> Some () | _ -> None) + (fun () -> Decoding_error) + +type key = string + +module Connection = struct + + type t = Lwt_unix.file_descr + + let backlog = 10 + let default_port = 9000 + let localhost = Ipaddr.V4 (Ipaddr.V4.localhost) + + let getaddr uri = + match Uri.scheme uri with + | Some "file" -> + let path = Uri.path uri in + Lwt.catch + (fun () -> Lwt_unix.unlink path) + (fun _ -> Lwt.return ()) + >>= fun () -> + Lwt.return (Lwt_unix.ADDR_UNIX path) + | Some "tezos" -> begin + match Uri.host uri, Uri.port uri with + | Some host, port_opt -> + begin match Ipaddr.of_string host with + |Some host -> + let h = Ipaddr_unix.to_inet_addr host in + let p = Option.unopt ~default:default_port port_opt in + Lwt.return (Lwt_unix.ADDR_INET(h,p)) + | None -> + Lwt.fail_with ("Cannot parse host " ^ (Uri.to_string uri)) + end + | None, _ -> + let h = Ipaddr_unix.to_inet_addr localhost in + Lwt.return (Lwt_unix.ADDR_INET(h, default_port)) + end + | _ -> Lwt.fail_with ("Cannot parse URI " ^ (Uri.to_string uri)) + + let bind remote = + getaddr remote >>= fun addr -> + let sock = Lwt_unix.socket PF_INET SOCK_STREAM 0 in + Lwt_unix.setsockopt sock SO_REUSEADDR true; + Lwt_unix.bind sock @@ addr >|= fun () -> + Lwt_unix.listen sock backlog; + sock + + let connect remote = + getaddr remote >>= fun addr -> + let sock = Lwt_unix.socket PF_INET SOCK_STREAM 0 in + Lwt_unix.connect sock @@ addr >|= fun () -> + sock + + let read ~len fd buf = + Lwt_utils_unix.read_mbytes ~len fd buf >>= return + + let write fd buf = + Lwt_utils_unix.write_mbytes fd buf >>= return + +end + +let message_len_size = 2 + +let send fd encoding message = + let encoded_message_len = + Data_encoding.Binary.length encoding message in + fail_unless + (encoded_message_len < 1 lsl (message_len_size * 8)) + Encoding_error >>=? fun () -> + (* len is the length of int16 plus the length of the message we want to send *) + let len = message_len_size + encoded_message_len in + let buf = MBytes.create len in + match Data_encoding.Binary.write + encoding message buf message_len_size with + | None -> + fail Encoding_error + | Some last -> + fail_unless (last = len) Encoding_error >>=? fun () -> + (* we set the beginning of the buf with the length of what is next *) + MBytes.set_int16 buf 0 encoded_message_len ; + Connection.write fd buf + +let recv fd encoding = + let header_buf = MBytes.create message_len_size in + Connection.read ~len:message_len_size fd header_buf >>=? fun () -> + let len = MBytes.get_uint16 header_buf 0 in + let buf = MBytes.create len in + Connection.read ~len fd buf >>=? fun () -> + match Data_encoding.Binary.read encoding buf 0 len with + | None -> + fail Decoding_error + | Some (read_len, message) -> + if read_len <> len then + fail Decoding_error + else + return message + +module Sign = struct + module Request = struct + type t = { + key : string ; + data: MBytes.t ; + } + + let encoding = + let open Data_encoding in + conv + (fun { key ; data } -> + ( key, data)) + (fun (key, data) -> + { key ; data }) + (obj2 + (req "key" string) + (req "data" bytes)) + end + + module Response = struct + type t = { + signature : Signature.t + } + + let encoding = + let open Data_encoding in + result_encoding @@ + conv + (fun { signature } -> (signature)) + (fun (signature) -> { signature }) + (obj1 (req "signature" Signature.encoding)) + end +end + +module Public_key = struct + module Request = struct + type t = { + key : string + } + + let encoding = + let open Data_encoding in + conv + (fun { key } -> key) + (fun key -> { key }) + (obj1 (req "key" string)) + end + + module Response = struct + type t = { + public_key : Signature.Public_key.t + } + + let encoding = + let open Data_encoding in + result_encoding @@ + conv + (fun { public_key } -> public_key) + (fun public_key -> { public_key }) + (obj1 (req "pubkey" Signature.Public_key.encoding)) + end +end + +module Request = struct + type t = + | Sign of Sign.Request.t + | Public_key of Public_key.Request.t + + let encoding = + let open Data_encoding in + union + [ case (Tag 0) (merge_objs (obj1 (req "kind" (constant "sign"))) Sign.Request.encoding) + (function Sign req -> Some ((), req) | _ -> None) + (fun ((), req) -> Sign req) ; + case (Tag 1) (merge_objs (obj1 (req "kind" (constant "public_key"))) Public_key.Request.encoding) + (function Public_key req -> Some ((), req) | _ -> None) + (fun ((), req) -> Public_key req) ] +end diff --git a/src/lib_client_base_unix/client_signer_remote_messages.mli b/src/lib_client_base_unix/client_signer_remote_messages.mli new file mode 100644 index 000000000..5c25eecb1 --- /dev/null +++ b/src/lib_client_base_unix/client_signer_remote_messages.mli @@ -0,0 +1,65 @@ +(**************************************************************************) +(* *) +(* Copyright (c) 2014 - 2018. *) +(* Dynamic Ledger Solutions, Inc. *) +(* *) +(* All rights reserved. No warranty, explicit or implicit, provided. *) +(* *) +(**************************************************************************) + +type error += + | Encoding_error + | Decoding_error + | Unkwnon_alias_key of string + | Unkwnon_request_kind + +type key = string + +module Connection : sig + type t = Lwt_unix.file_descr + val bind : Uri.t -> t Lwt.t + val connect : Uri.t -> t Lwt.t + val read : len:int -> t -> MBytes.t -> unit tzresult Lwt.t + val write : t -> MBytes.t -> unit tzresult Lwt.t +end + +module Sign : sig + module Request : sig + type t = { + key : string ; + data: MBytes.t ; + } + val encoding : t Data_encoding.t + end + module Response : sig + type t = { + signature : Signature.t ; + } + val encoding : t tzresult Data_encoding.t + end +end + +module Public_key : sig + module Request : sig + type t = { + key : string + } + val encoding : t Data_encoding.t + end + module Response : sig + type t = { + public_key : Signature.Public_key.t + } + val encoding : t tzresult Data_encoding.t + end +end + +module Request : sig + type t = + | Sign of Sign.Request.t + | Public_key of Public_key.Request.t + val encoding : t Data_encoding.t +end + +val send : Connection.t -> 'a Data_encoding.t -> 'a -> unit tzresult Lwt.t +val recv : Connection.t -> 'a Data_encoding.t -> 'a tzresult Lwt.t diff --git a/src/lib_stdlib/logging.ml b/src/lib_stdlib/logging.ml index 7cd74a938..1dc439bac 100644 --- a/src/lib_stdlib/logging.ml +++ b/src/lib_stdlib/logging.ml @@ -85,6 +85,7 @@ module Client = struct module Endorsement = Make(struct let name = "client.endorsement" end) module Revelation = Make(struct let name = "client.revealation" end) module Denunciation = Make(struct let name = "client.denunciation" end) + module Sign = Make(struct let name = "client.signer" end) end type level = Lwt_log_core.level = diff --git a/src/lib_stdlib/logging.mli b/src/lib_stdlib/logging.mli index 0410bda70..3dd66a7c6 100644 --- a/src/lib_stdlib/logging.mli +++ b/src/lib_stdlib/logging.mli @@ -44,6 +44,7 @@ module Client : sig module Endorsement : LOG module Revelation : LOG module Denunciation : LOG + module Sign : LOG end module Make(S: sig val name: string end) : LOG