From 4bb28cd2852e58e2cf8e6044cc1731b362bf0cf2 Mon Sep 17 00:00:00 2001 From: Vincent Bernardoff Date: Thu, 27 Sep 2018 17:52:05 +0800 Subject: [PATCH] Ledger: implement authorized-path APDU --- src/lib_signer_backends/ledger.ml | 63 +++++++++++++++++++ src/lib_signer_backends/ledger.mli | 12 ++++ .../src/ledgerwallet_tezos.ml | 12 ++++ .../src/ledgerwallet_tezos.mli | 8 ++- 4 files changed, 94 insertions(+), 1 deletion(-) diff --git a/src/lib_signer_backends/ledger.ml b/src/lib_signer_backends/ledger.ml index 810f2018c..12a7ff52d 100644 --- a/src/lib_signer_backends/ledger.ml +++ b/src/lib_signer_backends/ledger.ml @@ -43,8 +43,47 @@ let description = all connected devices." let hard = Int32.logor 0x8000_0000l +let unhard = Int32.logand 0x7fff_ffffl +let is_hard n = Int32.logand 0x8000_0000l n <> 0l let tezos_root = [hard 44l ; hard 1729l] +module Bip32_path = struct + let node_of_string str = + match Int32.of_string_opt str with + | Some node -> Some node + | None -> + match Int32.of_string_opt String.(sub str 0 ((length str) - 1)) with + | None -> None + | Some node -> Some (hard node) + + let node_of_string_exn str = + match node_of_string str with + | None -> + invalid_arg (Printf.sprintf "node_of_string_exn: got %S" str) + | Some str -> str + + let pp_node ppf node = + match is_hard node with + | true -> Fmt.pf ppf "%ld'" (unhard node) + | false -> Fmt.pf ppf "%ld" node + + let string_of_node = Fmt.to_to_string pp_node + + let path_of_string_exn s = + match String.split_on_char '/' s with + | [""] -> [] + | nodes -> + List.map node_of_string_exn nodes + + let path_of_string s = + try Some (path_of_string_exn s) with _ -> None + + let pp_path = + Fmt.(list ~sep:(const char '/') pp_node) + + let string_of_path = Fmt.to_to_string pp_path +end + (* Those are always valid on Ledger Nano S with latest firmware. *) let vendor_id = 0x2c97 let product_id = 0x0001 @@ -454,6 +493,30 @@ let commands = return_unit ) ; + Clic.command ~group + ~desc: "Query the path of the authorized key" + no_options + (prefixes [ "get" ; "ledger" ; "authorized" ; "path" ; "for" ] + @@ Public_key.alias_param + @@ stop) + (fun () (name, (pk_uri, _)) (cctxt : Client_context.io_wallet) -> + pkh_of_pk_uri pk_uri >>=? fun root_pkh -> + with_ledger root_pkh begin fun h _version _of_curve _to_curve -> + wrap_ledger_cmd begin fun pp -> + Ledgerwallet_tezos.get_authorized_key ~pp h + end >>=? function + | [] -> + cctxt#message + "@[No baking key authorized for %s@]" name + >>= fun () -> + return_unit + | path -> + cctxt#message + "@[Authorized baking path: %a@]" + Bip32_path.pp_path path >>= fun () -> + return_unit + end) ; + Clic.command ~group ~desc: "Authorize a Ledger to bake for a key" no_options diff --git a/src/lib_signer_backends/ledger.mli b/src/lib_signer_backends/ledger.mli index 4912a94b5..86c608634 100644 --- a/src/lib_signer_backends/ledger.mli +++ b/src/lib_signer_backends/ledger.mli @@ -23,6 +23,18 @@ (* *) (*****************************************************************************) +module Bip32_path : sig + val node_of_string : string -> int32 option + val node_of_string_exn : string -> int32 + val pp_node : int32 Fmt.t + val string_of_node : int32 -> string + + val path_of_string : string -> int32 list option + val path_of_string_exn : string -> int32 list + val pp_path : int32 list Fmt.t + val string_of_path : int32 list -> string +end + include Client_keys.SIGNER val commands : unit -> Client_context.io_wallet Clic.command list diff --git a/vendors/ocaml-ledger-wallet/src/ledgerwallet_tezos.ml b/vendors/ocaml-ledger-wallet/src/ledgerwallet_tezos.ml index 21d6c0115..0b5bc8043 100644 --- a/vendors/ocaml-ledger-wallet/src/ledgerwallet_tezos.ml +++ b/vendors/ocaml-ledger-wallet/src/ledgerwallet_tezos.ml @@ -63,6 +63,7 @@ type ins = | Sign_unsafe | Reset_high_watermark | Query_high_watermark + | Get_authorized_key let int_of_ins = function | Version -> 0x00 @@ -74,6 +75,7 @@ let int_of_ins = function | Reset_high_watermark -> 0x06 | Query_high_watermark -> 0x08 | Git_commit -> 0x09 + | Get_authorized_key -> 0x07 type curve = | Ed25519 @@ -98,6 +100,16 @@ let get_git_commit ?pp ?buf h = Transport.apdu ~msg:"get_git_commit" ?pp ?buf h apdu >>| Cstruct.to_string +let get_authorized_key ?pp ?buf h = + let apdu = Apdu.create (wrap_ins Get_authorized_key) in + Transport.apdu ~msg:"get_authorized_key" ?pp ?buf h apdu >>| fun path -> + let rec read_numbers acc path = + if Cstruct.len path = 0 then List.rev acc + else + read_numbers (Cstruct.BE.get_uint32 path 0 :: acc) + (Cstruct.shift path 4) in + read_numbers [] (Cstruct.shift path 1) + let write_path cs path = ListLabels.fold_left path ~init:cs ~f:begin fun cs i -> Cstruct.BE.set_uint32 cs 0 i ; diff --git a/vendors/ocaml-ledger-wallet/src/ledgerwallet_tezos.mli b/vendors/ocaml-ledger-wallet/src/ledgerwallet_tezos.mli index fe51fcb8f..b648e4d9a 100644 --- a/vendors/ocaml-ledger-wallet/src/ledgerwallet_tezos.mli +++ b/vendors/ocaml-ledger-wallet/src/ledgerwallet_tezos.mli @@ -36,7 +36,13 @@ val get_git_commit : ?pp:Format.formatter -> ?buf:Cstruct.t -> Hidapi.t -> (string, Transport.error) result (** [get_git_commit ?pp ?buf ledger] is the git commit information of - the Ledger app running at [ledger]. *) + the Ledger app running at [ledger]. *) + +val get_authorized_key : + ?pp:Format.formatter -> ?buf:Cstruct.t -> + Hidapi.t -> (int32 list, Transport.error) result +(** [get_authorized_key ?pp ?buf ledger] is the BIP32 path of the key + authorized to bake on the Ledger app running at [ledger]. *) val get_public_key : ?prompt:bool ->