From 96c36f169882dbb3155d9bb53c808ce4496771af Mon Sep 17 00:00:00 2001 From: Benjamin Canou Date: Wed, 4 Jul 2018 21:34:45 +0200 Subject: [PATCH] Add high watermark checking for blocks and endorsements --- src/bin_signer/handler.ml | 63 +++++++++++++++++++++++++++++++- src/bin_signer/http_daemon.ml | 12 +++--- src/bin_signer/http_daemon.mli | 2 + src/bin_signer/main_signer.ml | 39 ++++++++++++++------ src/bin_signer/socket_daemon.ml | 8 ++-- src/bin_signer/socket_daemon.mli | 1 + 6 files changed, 102 insertions(+), 23 deletions(-) diff --git a/src/bin_signer/handler.ml b/src/bin_signer/handler.ml index 6362c0b7d..0ebddb9fd 100644 --- a/src/bin_signer/handler.ml +++ b/src/bin_signer/handler.ml @@ -27,6 +27,63 @@ open Signer_logging let log = lwt_log_notice +module High_watermark = struct + let encoding = + let open Data_encoding in + let raw_hash = + conv Blake2B.to_bytes Blake2B.of_bytes_exn bytes in + conv + (List.map (fun (chain_id, marks) -> Chain_id.to_b58check chain_id, marks)) + (List.map (fun (chain_id, marks) -> Chain_id.of_b58check_exn chain_id, marks)) @@ + assoc @@ + conv + (List.map (fun (pkh, mark) -> Signature.Public_key_hash.to_b58check pkh, mark)) + (List.map (fun (pkh, mark) -> Signature.Public_key_hash.of_b58check_exn pkh, mark)) @@ + assoc @@ + obj2 + (req "level" int32) + (req "hash" raw_hash) + + let mark_if_block_or_endorsement (cctxt : #Client_context.wallet) pkh bytes = + let mark art name get_level = + let file = name ^ "_high_watermark" in + cctxt#with_lock @@ fun () -> + cctxt#load file ~default:[] encoding >>=? fun all -> + if MBytes.length bytes < 9 then + failwith "byte sequence too short to be %s %s" art name + else + let hash = Blake2B.hash_bytes [ bytes ] in + let chain_id = Chain_id.of_bytes_exn (MBytes.sub bytes 1 4) in + let level = get_level () in + begin match List.assoc_opt chain_id all with + | None -> return () + | Some marks -> + match List.assoc_opt pkh marks with + | None -> return () + | Some (previous_level, previous_hash) -> + if previous_level > level then + failwith "%s level %ld below high watermark %ld" name level previous_level + else if previous_level = level && previous_hash <> hash then + failwith "%s level %ld already signed with a different header" name level + else + return () + end >>=? fun () -> + let rec update = function + | [] -> [ chain_id, [ pkh, (level, hash) ] ] + | (e_chain_id, marks) :: rest -> + if chain_id = e_chain_id then + let marks = (pkh, (level, hash)) :: List.filter (fun (pkh', _) -> pkh <> pkh') marks in + (e_chain_id, marks) :: rest + else + (e_chain_id, marks) :: update rest in + cctxt#write file (update all) encoding in + if MBytes.length bytes > 0 && MBytes.get_uint8 bytes 0 = 0x01 then + mark "a" "block" (fun () -> MBytes.get_int32 bytes 5) + else if MBytes.length bytes > 0 && MBytes.get_uint8 bytes 0 = 0x02 then + mark "an" "endorsement" (fun () -> MBytes.get_int32 bytes (MBytes.length bytes - 4)) + else return () + +end module Authorized_key = Client_aliases.Alias (struct @@ -50,7 +107,7 @@ let check_magic_byte magic_bytes data = let sign (cctxt : #Client_context.wallet) Signer_messages.Sign.Request.{ pkh ; data ; signature } - ?magic_bytes ~require_auth = + ?magic_bytes ~check_high_watermark ~require_auth = log Tag.DSL.(fun f -> f "Request for signing %d bytes of data for key %a, magic byte = %02X" -% t event "request_for_signing" @@ -77,6 +134,10 @@ let sign f "Signing data for key %s" -% t event "signing_data" -% s Client_keys.Logging.tag name) >>= fun () -> + begin if check_high_watermark then + High_watermark.mark_if_block_or_endorsement cctxt pkh data + else return () + end >>=? fun () -> Client_keys.sign cctxt sk_uri data >>=? fun signature -> return signature diff --git a/src/bin_signer/http_daemon.ml b/src/bin_signer/http_daemon.ml index be391c03c..9b8747059 100644 --- a/src/bin_signer/http_daemon.ml +++ b/src/bin_signer/http_daemon.ml @@ -26,11 +26,11 @@ let log = Signer_logging.lwt_log_notice open Signer_logging -let run (cctxt : #Client_context.wallet) ~hosts ?magic_bytes ~require_auth mode = +let run (cctxt : #Client_context.wallet) ~hosts ?magic_bytes ~check_high_watermark ~require_auth mode = let dir = RPC_directory.empty in let dir = RPC_directory.register1 dir Signer_services.sign begin fun pkh signature data -> - Handler.sign cctxt { pkh ; data ; signature } ?magic_bytes ~require_auth + Handler.sign cctxt { pkh ; data ; signature } ?magic_bytes ~check_high_watermark ~require_auth end in let dir = RPC_directory.register1 dir Signer_services.public_key begin fun pkh () () -> @@ -63,7 +63,7 @@ let run (cctxt : #Client_context.wallet) ~hosts ?magic_bytes ~require_auth mode failwith "Port already in use." | exn -> Lwt.return (error_exn exn)) -let run_https (cctxt : #Client_context.wallet) ~host ~port ~cert ~key ?magic_bytes ~require_auth = +let run_https (cctxt : #Client_context.wallet) ~host ~port ~cert ~key ?magic_bytes ~check_high_watermark ~require_auth = Lwt_utils_unix.getaddrinfo ~passive:true ~node:host ~service:(string_of_int port) >>= function | []-> failwith "Cannot resolve listening address: %S" host @@ -75,9 +75,9 @@ let run_https (cctxt : #Client_context.wallet) ~host ~port ~cert ~key ?magic_byt -% s port_number port) >>= fun () -> let mode : Conduit_lwt_unix.server = `TLS (`Crt_file_path cert, `Key_file_path key, `No_password, `Port port) in - run (cctxt : #Client_context.wallet) ~hosts ?magic_bytes ~require_auth mode + run (cctxt : #Client_context.wallet) ~hosts ?magic_bytes ~check_high_watermark ~require_auth mode -let run_http (cctxt : #Client_context.wallet) ~host ~port ?magic_bytes ~require_auth = +let run_http (cctxt : #Client_context.wallet) ~host ~port ?magic_bytes ~check_high_watermark ~require_auth = Lwt_utils_unix.getaddrinfo ~passive:true ~node:host ~service:(string_of_int port) >>= function | [] -> failwith "Cannot resolve listening address: %S" host @@ -89,4 +89,4 @@ let run_http (cctxt : #Client_context.wallet) ~host ~port ?magic_bytes ~require_ -% s port_number port) >>= fun () -> let mode : Conduit_lwt_unix.server = `TCP (`Port port) in - run (cctxt : #Client_context.wallet) ~hosts ?magic_bytes ~require_auth mode + run (cctxt : #Client_context.wallet) ~hosts ?magic_bytes ~check_high_watermark ~require_auth mode diff --git a/src/bin_signer/http_daemon.mli b/src/bin_signer/http_daemon.mli index b25a73125..e07043652 100644 --- a/src/bin_signer/http_daemon.mli +++ b/src/bin_signer/http_daemon.mli @@ -27,6 +27,7 @@ val run_https: #Client_context.io_wallet -> host:string -> port:int -> cert:string -> key:string -> ?magic_bytes: int list -> + check_high_watermark: bool -> require_auth: bool -> 'a tzresult Lwt.t @@ -34,5 +35,6 @@ val run_http: #Client_context.io_wallet -> host:string -> port:int -> ?magic_bytes: int list -> + check_high_watermark: bool -> require_auth: bool -> 'a tzresult Lwt.t diff --git a/src/bin_signer/main_signer.ml b/src/bin_signer/main_signer.ml index 453bf3f8d..969aef45b 100644 --- a/src/bin_signer/main_signer.ml +++ b/src/bin_signer/main_signer.ml @@ -82,13 +82,25 @@ let magic_bytes_arg = failwith "Bad format for magic bytes, a series of numbers \ is expected, separated by commas.")) +let high_watermark_switch = + Clic.switch + ~doc: "high watermark restriction\n\ + Stores the highest level signed for blocks and endorsements \ + for each address, and forbids to sign a level that is \ + inferior or equal afterwards, except for the exact same \ + input data." + ~short: 'W' + ~long: "check-high-watermark" + () + let commands base_dir require_auth = Client_keys_commands.commands None @ Tezos_signer_backends.Ledger.commands () @ [ command ~group ~desc: "Launch a signer daemon over a TCP socket." - (args3 + (args4 magic_bytes_arg + high_watermark_switch (default_arg ~doc: "listening address or host name" ~short: 'a' @@ -104,16 +116,17 @@ let commands base_dir require_auth = ~default: default_tcp_port (parameter (fun _ s -> return s)))) (prefixes [ "launch" ; "socket" ; "signer" ] @@ stop) - (fun (magic_bytes, host, port) cctxt -> + (fun (magic_bytes, check_high_watermark, host, port) cctxt -> Tezos_signer_backends.Encrypted.decrypt_all cctxt >>=? fun () -> Socket_daemon.run cctxt (Tcp (host, port, [AI_SOCKTYPE SOCK_STREAM])) - ?magic_bytes ~require_auth >>=? fun _ -> + ?magic_bytes ~check_high_watermark ~require_auth >>=? fun _ -> return_unit) ; command ~group ~desc: "Launch a signer daemon over a local Unix socket." - (args2 + (args3 magic_bytes_arg + high_watermark_switch (default_arg ~doc: "path to the local socket file" ~short: 's' @@ -122,15 +135,16 @@ let commands base_dir require_auth = ~default: (Filename.concat base_dir "socket") (parameter (fun _ s -> return s)))) (prefixes [ "launch" ; "local" ; "signer" ] @@ stop) - (fun (magic_bytes, path) cctxt -> + (fun (magic_bytes, check_high_watermark, path) cctxt -> Tezos_signer_backends.Encrypted.decrypt_all cctxt >>=? fun () -> Socket_daemon.run - cctxt (Unix path) ?magic_bytes ~require_auth >>=? fun _ -> + cctxt (Unix path) ?magic_bytes ~check_high_watermark ~require_auth >>=? fun _ -> return_unit) ; command ~group ~desc: "Launch a signer daemon over HTTP." - (args3 + (args4 magic_bytes_arg + high_watermark_switch (default_arg ~doc: "listening address or host name" ~short: 'a' @@ -149,13 +163,14 @@ let commands base_dir require_auth = try return (int_of_string x) with Failure _ -> failwith "Invalid port %s" x)))) (prefixes [ "launch" ; "http" ; "signer" ] @@ stop) - (fun (magic_bytes, host, port) cctxt -> + (fun (magic_bytes, check_high_watermark, host, port) cctxt -> Tezos_signer_backends.Encrypted.decrypt_all cctxt >>=? fun () -> - Http_daemon.run_http cctxt ~host ~port ?magic_bytes ~require_auth) ; + Http_daemon.run_http cctxt ~host ~port ?magic_bytes ~check_high_watermark ~require_auth) ; command ~group ~desc: "Launch a signer daemon over HTTPS." - (args3 + (args4 magic_bytes_arg + high_watermark_switch (default_arg ~doc: "listening address or host name" ~short: 'a' @@ -190,9 +205,9 @@ let commands base_dir require_auth = failwith "No such TLS key file %s" s else return s)) @@ stop) - (fun (magic_bytes, host, port) cert key cctxt -> + (fun (magic_bytes, check_high_watermark, host, port) cert key cctxt -> Tezos_signer_backends.Encrypted.decrypt_all cctxt >>=? fun () -> - Http_daemon.run_https cctxt ~host ~port ~cert ~key ?magic_bytes ~require_auth) ; + Http_daemon.run_https cctxt ~host ~port ~cert ~key ?magic_bytes ~check_high_watermark ~require_auth) ; command ~group ~desc: "Authorize a given public key to perform signing requests." (args1 diff --git a/src/bin_signer/socket_daemon.ml b/src/bin_signer/socket_daemon.ml index 708803c7e..baff8e901 100644 --- a/src/bin_signer/socket_daemon.ml +++ b/src/bin_signer/socket_daemon.ml @@ -28,11 +28,11 @@ open Signer_messages let log = lwt_log_notice -let handle_client ?magic_bytes ~require_auth cctxt fd = +let handle_client ?magic_bytes ~check_high_watermark ~require_auth cctxt fd = Lwt_utils_unix.Socket.recv fd Request.encoding >>=? function | Sign req -> let encoding = result_encoding Sign.Response.encoding in - Handler.sign cctxt req ?magic_bytes ~require_auth >>= fun res -> + Handler.sign cctxt req ?magic_bytes ~check_high_watermark ~require_auth >>= fun res -> Lwt_utils_unix.Socket.send fd encoding res >>= fun _ -> Lwt_unix.close fd >>= fun () -> return_unit @@ -54,7 +54,7 @@ let handle_client ?magic_bytes ~require_auth cctxt fd = Lwt_unix.close fd >>= fun () -> return_unit -let run (cctxt : #Client_context.wallet) path ?magic_bytes ~require_auth = +let run (cctxt : #Client_context.wallet) path ?magic_bytes ~check_high_watermark ~require_auth = let open Lwt_utils_unix.Socket in begin match path with @@ -84,7 +84,7 @@ let run (cctxt : #Client_context.wallet) path ?magic_bytes ~require_auth = | [Exn End_of_file] -> return_unit | errs -> Lwt.return (Error errs)) (fun () -> - handle_client ?magic_bytes ~require_auth cctxt cfd) + handle_client ?magic_bytes ~check_high_watermark ~require_auth cctxt cfd) end ; loop fd in diff --git a/src/bin_signer/socket_daemon.mli b/src/bin_signer/socket_daemon.mli index 09af061ad..c54c2374c 100644 --- a/src/bin_signer/socket_daemon.mli +++ b/src/bin_signer/socket_daemon.mli @@ -27,5 +27,6 @@ val run: #Client_context.io_wallet -> Lwt_utils_unix.Socket.addr -> ?magic_bytes: int list -> + check_high_watermark: bool -> require_auth: bool -> 'a list tzresult Lwt.t