Commit 347a5523 authored by Benjamin Canou's avatar Benjamin Canou

Signer: add authorized keys mechanism

parent a8b2ab32
......@@ -9,11 +9,35 @@
let log = Signer_logging.lwt_log_notice
let sign (cctxt : #Client_context.wallet) pkh data =
module Authorized_key =
Client_aliases.Alias (struct
include Signature.Public_key
let name = "authorized_key"
let to_source s = return (to_b58check s)
let of_source t = Lwt.return (of_b58check t)
end)
let sign
(cctxt : #Client_context.wallet)
Signer_messages.Sign.Request.{ pkh ; data ; signature } ~require_auth =
log "Request for signing %d bytes of data for key %a, magic byte = %02X"
(MBytes.length data)
Signature.Public_key_hash.pp pkh
(MBytes.get_uint8 data 0) >>= fun () ->
begin match require_auth, signature with
| false, _ -> return ()
| true, None -> failwith "missing authentication signature field"
| true, Some signature ->
let to_sign = Signer_messages.Sign.Request.to_sign ~pkh ~data in
Authorized_key.load cctxt >>=? fun keys ->
if List.fold_left
(fun acc (_, key) -> acc || Signature.check key signature to_sign)
false keys
then
return ()
else
failwith "invalid authentication signature"
end >>=? fun () ->
Client_keys.get_key cctxt pkh >>=? fun (name, _pkh, sk_uri) ->
log "Signing data for key %s" name >>= fun () ->
Client_keys.sign sk_uri data >>=? fun signature ->
......
......@@ -9,19 +9,27 @@
let log = Signer_logging.lwt_log_notice
let run (cctxt : #Client_context.wallet) ~host ~port ~cert ~key =
let run (cctxt : #Client_context.wallet) ~host ~port ~cert ~key ~require_auth =
log "Accepting HTTPS requests on port %d" port >>= fun () ->
let mode : Conduit_lwt_unix.server =
`TLS (`Crt_file_path cert, `Key_file_path key, `No_password, `Port port) in
let dir = RPC_directory.empty in
let dir =
RPC_directory.register1 dir Signer_services.sign begin fun pkh () data ->
Handler.sign cctxt pkh data
RPC_directory.register1 dir Signer_services.sign begin fun pkh signature data ->
Handler.sign cctxt { pkh ; data ; signature } ~require_auth
end in
let dir =
RPC_directory.register1 dir Signer_services.public_key begin fun pkh () () ->
Handler.public_key cctxt pkh
end in
let dir =
RPC_directory.register0 dir Signer_services.authorized_keys begin fun () () ->
if require_auth then
return None
else
Handler.Authorized_key.load cctxt >>=? fun keys ->
return (Some (keys |> List.split |> snd |> List.map Signature.Public_key.hash))
end in
Lwt.catch
(fun () ->
RPC_server.launch ~host mode dir
......
......@@ -9,4 +9,6 @@
val run:
#Client_context.io_wallet ->
host:string -> port:int -> cert:string -> key:string -> 'a tzresult Lwt.t
host:string -> port:int -> cert:string -> key:string ->
require_auth: bool ->
'a tzresult Lwt.t
......@@ -38,7 +38,7 @@ let group =
{ Clic.name = "signer" ;
title = "Commands specific to the signing daemon" }
let commands =
let commands base_dir require_auth =
Client_keys_commands.commands () @
[ command ~group
~desc: "Launch a signer daemon over a TCP socket."
......@@ -63,7 +63,7 @@ let commands =
(prefixes [ "launch" ; "socket" ; "signer" ] @@ stop)
(fun (host, port) cctxt ->
Tezos_signer_backends.Encrypted.decrypt_all cctxt >>=? fun () ->
Socket_daemon.run cctxt (Tcp (host, port))) ;
Socket_daemon.run cctxt (Tcp (host, port)) ~require_auth) ;
command ~group
~desc: "Launch a signer daemon over a local Unix socket."
(args1
......@@ -72,12 +72,12 @@ let commands =
~short: 's'
~long: "socket"
~placeholder: "path"
~default: default_unix_path
~default: (Filename.concat base_dir "socket")
(parameter (fun _ s -> return s))))
(prefixes [ "launch" ; "local" ; "signer" ] @@ stop)
(fun path cctxt ->
Tezos_signer_backends.Encrypted.decrypt_all cctxt >>=? fun () ->
Socket_daemon.run cctxt (Unix path)) ;
Socket_daemon.run cctxt (Unix path) ~require_auth) ;
command ~group
~desc: "Launch a signer daemon over HTTPS."
(args2
......@@ -109,9 +109,31 @@ let commands =
(parameter (fun _ s -> return s)) @@ stop)
(fun (host, port) cert key cctxt ->
Tezos_signer_backends.Encrypted.decrypt_all cctxt >>=? fun () ->
Https_daemon.run cctxt ~host ~port ~cert ~key) ;
Https_daemon.run cctxt ~host ~port ~cert ~key ~require_auth) ;
command ~group
~desc: "Authorize a given public key to perform signing requests."
(args1
(arg
~doc: "an optional name for the key (defaults to the hash)"
~short: 'N'
~long: "name"
~placeholder: "name"
(parameter (fun _ s -> return s))))
(prefixes [ "add" ; "authorized" ; "key" ] @@
param
~name:"pk"
~desc: "full public key (Base58 encoded)"
(parameter (fun _ s -> Lwt.return (Signature.Public_key.of_b58check s))) @@
stop)
(fun name key cctxt ->
let pkh = Signature.Public_key.hash key in
let name = match name with
| Some name -> name
| None -> Signature.Public_key_hash.to_b58check pkh in
Handler.Authorized_key.add ~force:false cctxt name key)
]
let home = try Sys.getenv "HOME" with Not_found -> "/root"
let default_base_dir =
......@@ -132,9 +154,17 @@ let base_dir_arg () =
By default: '" ^ default_base_dir ^"'.")
(string_parameter ())
let require_auth_arg () =
switch
~long:"require-authentication"
~short:'A'
~doc:"Require a signature from the caller to sign."
()
let global_options () =
args1
args2
(base_dir_arg ())
(require_auth_arg ())
(* Main (lwt) entry *)
let main () =
......@@ -158,7 +188,7 @@ let main () =
begin
begin
parse_global_options
(global_options ()) () original_args >>=? fun (base_dir, remaining) ->
(global_options ()) () original_args >>=? fun ((base_dir, require_auth), remaining) ->
let base_dir = Option.unopt ~default:default_base_dir base_dir in
let cctxt = object
inherit Client_context_unix.unix_logger ~base_dir
......@@ -177,7 +207,7 @@ let main () =
~global_options:(global_options ())
(if Unix.isatty Unix.stdout then Clic.Ansi else Clic.Plain)
Format.std_formatter
commands in
(commands base_dir require_auth) in
begin match autocomplete with
| Some (prev_arg, cur_arg, script) ->
Clic.autocompletion
......
......@@ -11,7 +11,7 @@ open Signer_messages
let log = Signer_logging.lwt_log_notice
let run (cctxt : #Client_context.wallet) path =
let run (cctxt : #Client_context.wallet) path ~require_auth =
Lwt_utils_unix.Socket.bind path >>=? fun fd ->
let rec loop () =
Lwt_unix.accept fd >>= fun (fd, _) ->
......@@ -19,7 +19,7 @@ let run (cctxt : #Client_context.wallet) path =
Lwt_utils_unix.Socket.recv fd Request.encoding >>=? function
| Sign req ->
let encoding = result_encoding Sign.Response.encoding in
Handler.sign cctxt req.pkh req.data >>= fun res ->
Handler.sign cctxt req ~require_auth >>= fun res ->
Lwt_utils_unix.Socket.send fd encoding res >>= fun _ ->
Lwt_unix.close fd >>= fun () ->
return ()
......@@ -29,6 +29,17 @@ let run (cctxt : #Client_context.wallet) path =
Lwt_utils_unix.Socket.send fd encoding res >>= fun _ ->
Lwt_unix.close fd >>= fun () ->
return ()
| Authorized_keys ->
let encoding = result_encoding Authorized_keys.Response.encoding in
begin if require_auth then
Handler.Authorized_key.load cctxt >>=? fun keys ->
return (Authorized_keys.Response.Authorized_keys
(keys |> List.split |> snd |> List.map Signature.Public_key.hash))
else return Authorized_keys.Response.No_authentication
end >>= fun res ->
Lwt_utils_unix.Socket.send fd encoding res >>= fun _ ->
Lwt_unix.close fd >>= fun () ->
return ()
end ;
loop ()
in
......
......@@ -9,4 +9,6 @@
val run:
#Client_context.io_wallet ->
Lwt_utils_unix.Socket.addr -> 'a tzresult Lwt.t
Lwt_utils_unix.Socket.addr ->
require_auth: bool ->
'a tzresult Lwt.t
......@@ -74,18 +74,6 @@ let main select_commands =
ignore Clic.(setup_formatter Format.err_formatter
(if Unix.isatty Unix.stderr then Ansi else Plain) Short) ;
init_logger () >>= fun () ->
Client_keys.register_signer
(module Tezos_signer_backends.Unencrypted) ;
Client_keys.register_signer
(module Tezos_signer_backends.Encrypted.Make(struct
let cctxt = new Client_context_unix.unix_prompter
end)) ;
Client_keys.register_signer
(module Tezos_signer_backends.Https) ;
Client_keys.register_signer
(module Tezos_signer_backends.Socket.Unix) ;
Client_keys.register_signer
(module Tezos_signer_backends.Socket.Tcp) ;
Lwt.catch begin fun () -> begin
Client_config.parse_config_args
(new unix_full
......@@ -102,14 +90,6 @@ let main select_commands =
tls = parsed_config_file.tls ;
} in
let ctxt = new RPC_client.http_ctxt rpc_config Media_type.all_media_types in
select_commands ctxt parsed_args >>=? fun commands ->
let commands =
Clic.add_manual
~executable_name
~global_options
(if Unix.isatty Unix.stdout then Clic.Ansi else Clic.Plain)
Format.std_formatter
(config_commands @ builtin_commands @ commands) in
let rpc_config =
if parsed_args.print_timings then
{ rpc_config with
......@@ -124,12 +104,48 @@ let main select_commands =
~confirmations:parsed_args.confirmations
~base_dir:parsed_config_file.base_dir
~rpc_config:rpc_config in
Client_keys.register_signer
(module Tezos_signer_backends.Unencrypted) ;
Client_keys.register_signer
(module Tezos_signer_backends.Encrypted.Make(struct
let cctxt = (client_config :> Client_context.prompter)
end)) ;
let module Remote_authenticator = struct
let authenticate pkhs payload =
Client_keys.list_keys client_config >>=? fun keys ->
match List.filter_map
(function
| (_, known_pkh, _, Some known_sk_uri)
when List.exists (fun pkh -> Signature.Public_key_hash.equal pkh known_pkh) pkhs ->
Some known_sk_uri
| _ -> None)
keys with
| sk_uri :: _ ->
Client_keys.sign sk_uri payload
| [] -> failwith
"remote signer expects authentication signature, \
but no authorized key was found in the wallet"
end in
let module Https = Tezos_signer_backends.Https.Make(Remote_authenticator) in
let module Socket = Tezos_signer_backends.Socket.Make(Remote_authenticator) in
Client_keys.register_signer (module Https) ;
Client_keys.register_signer (module Socket.Unix) ;
Client_keys.register_signer (module Socket.Tcp) ;
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
include Remote_authenticator
end))
end ;
select_commands ctxt parsed_args >>=? fun commands ->
let commands =
Clic.add_manual
~executable_name
~global_options
(if Unix.isatty Unix.stdout then Clic.Ansi else Clic.Plain)
Format.std_formatter
(config_commands @ builtin_commands @ commands) in
begin match autocomplete with
| Some (prev_arg, cur_arg, script) ->
Clic.autocompletion
......
......@@ -11,52 +11,73 @@ open Client_keys
let scheme = "https"
let title =
"Built-in tezos-signer using remote signer through hardcoded https requests."
let description =
"Valid locators are of this form:\n\
\ - https://host/tz1...\n\
\ - https://host:port/path/to/service/tz1...\n"
let parse uri =
(* extract `tz1..` from the last component of the path *)
assert (Uri.scheme uri = Some scheme) ;
let path = Uri.path uri in
begin match String.rindex_opt path '/' with
| None ->
failwith "Invalid locator %a" Uri.pp_hum uri
| Some i ->
let pkh = String.sub path (i + 1) (String.length path - i - 1) in
let path = String.sub path 0 i in
return (Uri.with_path uri path, pkh)
end >>=? fun (base, pkh) ->
Lwt.return (Signature.Public_key_hash.of_b58check pkh) >>=? fun pkh ->
return (base, pkh)
let public_key uri =
parse (uri : pk_uri :> Uri.t) >>=? fun (base, pkh) ->
RPC_client.call_service
Media_type.all_media_types
~base Signer_services.public_key ((), pkh) () ()
let neuterize uri =
return (Client_keys.make_pk_uri (uri : sk_uri :> Uri.t))
let public_key_hash uri =
public_key uri >>=? fun pk ->
return (Signature.Public_key.hash pk)
let sign ?watermark uri msg =
parse (uri : sk_uri :> Uri.t) >>=? fun (base, pkh) ->
let msg =
match watermark with
| None -> msg
| Some watermark ->
MBytes.concat "" [ Signature.bytes_of_watermark watermark ; msg ] in
RPC_client.call_service
Media_type.all_media_types
~base Signer_services.sign ((), pkh) () msg
module Make(P : sig
val authenticate: Signature.Public_key_hash.t list -> MBytes.t -> Signature.t tzresult Lwt.t
end) = struct
let scheme = scheme
let title =
"Built-in tezos-signer using remote signer through hardcoded https requests."
let description =
"Valid locators are of this form:\n\
\ - https://host/tz1...\n\
\ - https://host:port/path/to/service/tz1...\n"
let parse uri =
(* extract `tz1..` from the last component of the path *)
assert (Uri.scheme uri = Some scheme) ;
let path = Uri.path uri in
begin match String.rindex_opt path '/' with
| None ->
failwith "Invalid locator %a" Uri.pp_hum uri
| Some i ->
let pkh = String.sub path (i + 1) (String.length path - i - 1) in
let path = String.sub path 0 i in
return (Uri.with_path uri path, pkh)
end >>=? fun (base, pkh) ->
Lwt.return (Signature.Public_key_hash.of_b58check pkh) >>=? fun pkh ->
return (base, pkh)
let public_key uri =
parse (uri : pk_uri :> Uri.t) >>=? fun (base, pkh) ->
RPC_client.call_service
Media_type.all_media_types
~base Signer_services.public_key ((), pkh) () ()
let neuterize uri =
return (Client_keys.make_pk_uri (uri : sk_uri :> Uri.t))
let public_key_hash uri =
public_key uri >>=? fun pk ->
return (Signature.Public_key.hash pk)
let sign ?watermark uri msg =
parse (uri : sk_uri :> Uri.t) >>=? fun (base, pkh) ->
let msg =
match watermark with
| None -> msg
| Some watermark ->
MBytes.concat "" [ Signature.bytes_of_watermark watermark ; msg ] in
RPC_client.call_service
Media_type.all_media_types
~base Signer_services.authorized_keys () () () >>=? fun authorized_keys ->
begin match authorized_keys with
| Some authorized_keys ->
P.authenticate
authorized_keys
(Signer_messages.Sign.Request.to_sign ~pkh ~data:msg) >>=? fun signature ->
return (Some signature)
| None -> return None
end >>=? fun signature ->
RPC_client.call_service
Media_type.all_media_types
~base Signer_services.sign ((), pkh)
signature
msg
end
let make_base host port =
Uri.make ~scheme ~host ~port ()
......@@ -7,6 +7,9 @@
(* *)
(**************************************************************************)
include Client_keys.SIGNER
module Make(P : sig
val authenticate: Signature.Public_key_hash.t list -> MBytes.t -> Signature.t tzresult Lwt.t
end)
: Client_keys.SIGNER
val make_base: string -> int -> Uri.t
......@@ -11,7 +11,10 @@ open Client_keys
let scheme = "remote"
module Make(S : sig val default : Uri.t end) = struct
module Make(S : sig
val default : Uri.t
val authenticate: Signature.Public_key_hash.t list -> MBytes.t -> Signature.t tzresult Lwt.t
end) = struct
let scheme = scheme
......@@ -27,6 +30,9 @@ module Make(S : sig val default : Uri.t end) = struct
- $TEZOS_SIGNER_TCP_HOST and $TEZOS_SIGNER_TCP_PORT (default: 7732),\n\
- $TEZOS_SIGNER_HTTPS_HOST and $TEZOS_SIGNER_HTTPS_PORT (default: 443)."
module Socket = Socket.Make(S)
module Https = Https.Make(S)
let get_remote () =
match Uri.scheme S.default with
| Some "unix" -> (module Socket.Unix : SIGNER)
......
......@@ -7,7 +7,10 @@
(* *)
(**************************************************************************)
module Make(S : sig val default : Uri.t end) : Client_keys.SIGNER
module Make(S : sig
val default : Uri.t
val authenticate: Signature.Public_key_hash.t list -> MBytes.t -> Signature.t tzresult Lwt.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
......
......@@ -10,108 +10,129 @@
open Client_keys
open Signer_messages
let sign ?watermark path pkh msg =
let msg =
match watermark with
| None -> msg
| Some watermark ->
MBytes.concat "" [ Signature.bytes_of_watermark watermark ; msg ] in
let req = { Sign.Request.pkh ; data = msg } in
Lwt_utils_unix.Socket.connect path >>=? fun conn ->
Lwt_utils_unix.Socket.send
conn Request.encoding (Request.Sign req) >>=? fun () ->
let encoding = result_encoding Sign.Response.encoding in
Lwt_utils_unix.Socket.recv conn encoding >>=? fun res ->
Lwt_unix.close conn >>= fun () ->
Lwt.return res
let public_key path pkh =
Lwt_utils_unix.Socket.connect path >>=? fun conn ->
Lwt_utils_unix.Socket.send
conn Request.encoding (Request.Public_key pkh) >>=? fun () ->
let encoding = result_encoding Public_key.Response.encoding in
Lwt_utils_unix.Socket.recv conn encoding >>=? fun res ->
Lwt_unix.close conn >>= fun () ->
Lwt.return res
module Unix = struct
let scheme = "unix"
let title =
"Built-in tezos-signer using remote signer through hardcoded unix socket."
let description =
"Valid locators are of this form: unix:///path/to/socket?pkh=tz1..."
let parse uri =
assert (Uri.scheme uri = Some scheme) ;
trace (Invalid_uri uri) @@
match Uri.get_query_param uri "pkh" with
| None -> failwith "Missing the query parameter: 'pkh=tz1...'"
| Some key ->
Lwt.return (Signature.Public_key_hash.of_b58check key) >>=? fun key ->
return (Lwt_utils_unix.Socket.Unix (Uri.path uri), key)
let public_key uri =
parse (uri : pk_uri :> Uri.t) >>=? fun (path, pkh) ->
public_key path pkh
let neuterize uri =
return (Client_keys.make_pk_uri (uri : sk_uri :> Uri.t))
let public_key_hash uri =
public_key uri >>=? fun pk ->
return (Signature.Public_key.hash pk)
let sign ?watermark uri msg =
parse (uri : sk_uri :> Uri.t) >>=? fun (path, pkh) ->
sign ?watermark path pkh msg
let tcp_scheme = "tcp"
let unix_scheme = "unix"
module Make(P : sig
val authenticate: Signature.Public_key_hash.t list -> MBytes.t -> Signature.t tzresult Lwt.t
end) = struct
let sign ?watermark path pkh msg =
let msg =
match watermark with
| None -> msg
| Some watermark ->
MBytes.concat "" [ Signature.bytes_of_watermark watermark ; msg ] in
Lwt_utils_unix.Socket.connect path >>=? fun conn ->
Lwt_utils_unix.Socket.send
conn Request.encoding Request.Authorized_keys >>=? fun () ->
Lwt_utils_unix.Socket.recv conn
Authorized_keys.Response.encoding >>=? fun authorized_keys ->
begin match authorized_keys with
| No_authentication -> return None
| Authorized_keys authorized_keys ->
P.authenticate authorized_keys
(Sign.Request.to_sign ~pkh ~data:msg) >>=? fun signature ->
return (Some signature)
end >>=? fun signature ->
let req = { Sign.Request.pkh ; data = msg ; signature } in
Lwt_utils_unix.Socket.send
conn Request.encoding (Request.Sign req) >>=? fun () ->
Lwt_utils_unix.Socket.recv conn
(result_encoding Sign.Response.encoding) >>=? fun res ->
Lwt_unix.close conn >>= fun () ->
Lwt.return res
let public_key path pkh =
Lwt_utils_unix.Socket.connect path >>=? fun conn ->
Lwt_utils_unix.Socket.send
conn Request.encoding (Request.Public_key pkh) >>=? fun () ->
let encoding = result_encoding Public_key.Response.encoding in
Lwt_utils_unix.Socket.recv conn encoding >>=? fun res ->
Lwt_unix.close conn >>= fun () ->
Lwt.return res
module Unix = struct
let scheme = unix_scheme
let title =
"Built-in tezos-signer using remote signer through hardcoded unix socket."
let description =
"Valid locators are of this form: unix:///path/to/socket?pkh=tz1..."
let parse uri =
assert (Uri.scheme uri = Some scheme) ;
trace (Invalid_uri uri) @@
match Uri.get_query_param uri "pkh" with
| None -> failwith "Missing the query parameter: 'pkh=tz1...'"
| Some key ->
Lwt.return (Signature.Public_key_hash.of_b58check key) >>=? fun key ->
return (Lwt_utils_unix.Socket.Unix (Uri.path uri), key)
let public_key uri =
parse (uri : pk_uri :> Uri.t) >>=? fun (path, pkh) ->
public_key path pkh
let neuterize uri =
return (Client_keys.make_pk_uri (uri : sk_uri :> Uri.t))
let public_key_hash uri =
public_key uri >>=? fun pk ->
return (Signature.Public_key.hash pk)
let sign ?watermark uri msg =
parse (uri : sk_uri :> Uri.t) >>=? fun (path, pkh) ->
sign ?watermark path pkh msg
end
module Tcp = struct
let scheme = tcp_scheme
let title =
"Built-in tezos-signer using remote signer through hardcoded tcp socket."
let description =
"Valid locators are of this form: tcp://host:port/tz1..."
let parse uri =
assert (Uri.scheme uri = Some scheme) ;
trace (Invalid_uri uri) @@
match Uri.host uri, Uri.port uri with
| None, _ ->
failwith "Missing host address"
| _, None ->
failwith "Missing host port"
| Some path, Some port ->
Lwt.return
(Signature.Public_key_hash.of_b58check (Uri.path uri)) >>=? fun pkh ->
return (Lwt_utils_unix.Socket.Tcp (path, port), pkh)
let public_key uri =
parse (uri : pk_uri :> Uri.t) >>=? fun (path, pkh) ->
public_key path pkh
let neuterize uri =
return (Client_keys.make_pk_uri (uri : sk_uri :> Uri.t))
let public_key_hash uri =
public_key uri >>=? fun pk ->
return (Signature.Public_key.hash pk)
let sign ?watermark uri msg =
parse (uri : sk_uri :> Uri.t) >>=? fun (path, pkh) ->
sign ?watermark path pkh msg
end
end
module Tcp = struct
let scheme = "tcp"
let title =
"Built-in tezos-signer using remote signer through hardcoded tcp socket."
let description =
"Valid locators are of this form: tcp://host:port/tz1..."
let parse uri =
assert (Uri.scheme uri = Some scheme) ;
trace (Invalid_uri uri) @@
match Uri.host uri, Uri.port uri with
| None, _ ->
failwith "Missing host address"
| _, None ->
failwith "Missing host port"
| Some path, Some port ->
Lwt.return
(Signature.Public_key_hash.of_b58check (Uri.path uri)) >>=? fun pkh ->
return (Lwt_utils_unix.Socket.Tcp (path, port), pkh)
let public_key uri =
parse (uri : pk_uri :> Uri.t) >>=? fun (path, pkh) ->
public_key path pkh
let neuterize uri =
return (Client_keys.make_pk_uri (uri : sk_uri :> Uri.t))
let public_key_hash uri =
public_key uri >>=? fun pk ->
return (Signature.Public_key.hash pk)
let sign ?watermark uri msg =
parse (uri : sk_uri :> Uri.t) >>=? fun (path, pkh) ->
sign ?watermark path pkh msg
end