P2p: make nonces unpredictable when connecting

Avoid replay-attacks by preventing a node from determining one of the
nonces used in the encryption of a channel between two nodes.
This commit is contained in:
Fabrice Le Fessant 2018-05-18 16:57:43 +02:00 committed by Grégoire Henry
parent dcf27f48d9
commit 8986640a98
5 changed files with 57 additions and 15 deletions

View File

@ -64,6 +64,23 @@ let random_keypair () =
let zero_nonce = MBytes.make Nonce.bytes '\x00' let zero_nonce = MBytes.make Nonce.bytes '\x00'
let random_nonce = Nonce.gen let random_nonce = Nonce.gen
let increment_nonce = Nonce.increment let increment_nonce = Nonce.increment
let generate_nonce mbytes =
let hash = Blake2B.hash_bytes mbytes in
Nonce.of_bytes_exn @@ (Bigstring.sub (Blake2B.to_bytes hash) 0 Nonce.bytes)
let init_to_resp_seed = MBytes.of_string "Init -> Resp"
let resp_to_init_seed = MBytes.of_string "Resp -> Init"
let generate_nonces ~incoming ~sent_msg ~recv_msg =
let (init_msg, resp_msg, false)
| (resp_msg, init_msg, true) = (sent_msg, recv_msg, incoming) in
let nonce_init_to_resp =
generate_nonce [ init_msg ; resp_msg ; init_to_resp_seed ] in
let nonce_resp_to_init =
generate_nonce [ init_msg ; resp_msg ; resp_to_init_seed ] in
if incoming then
(nonce_init_to_resp, nonce_resp_to_init)
else
(nonce_resp_to_init, nonce_init_to_resp)
let precompute sk pk = Box.dh pk sk let precompute sk pk = Box.dh pk sk

View File

@ -16,6 +16,16 @@ val zero_nonce : nonce
val random_nonce : unit -> nonce val random_nonce : unit -> nonce
val increment_nonce : ?step:int -> nonce -> nonce val increment_nonce : ?step:int -> nonce -> nonce
(** [generate_nonces ~incoming ~sent_msg ~recv_msg] generates two
nonces by hashing (Blake2B) the arguments. The nonces should be
used to initialize the encryption on the communication
channels. Because an attacker cannot control both messages,
it cannot determine the nonces that will be used to encrypt
the messages. The sent message should contains a random nonce,
and we should never send the exact same message twice. *)
val generate_nonces :
incoming:bool -> sent_msg:MBytes.t -> recv_msg:MBytes.t -> nonce * nonce
module Secretbox : sig module Secretbox : sig
type key type key

View File

@ -125,23 +125,28 @@ module Connection_message = struct
| Some last -> | Some last ->
fail_unless (last = len) P2p_errors.Encoding_error >>=? fun () -> fail_unless (last = len) P2p_errors.Encoding_error >>=? fun () ->
MBytes.set_int16 buf 0 encoded_message_len ; MBytes.set_int16 buf 0 encoded_message_len ;
P2p_io_scheduler.write fd buf P2p_io_scheduler.write fd buf >>=? fun () ->
(* We return the raw message as it is used later to compute
the nonces *)
return buf
let read fd = let read fd =
let header_buf = MBytes.create Crypto.header_length in let header_buf = MBytes.create Crypto.header_length in
P2p_io_scheduler.read_full P2p_io_scheduler.read_full
~len:Crypto.header_length fd header_buf >>=? fun () -> ~len:Crypto.header_length fd header_buf >>=? fun () ->
let len = MBytes.get_uint16 header_buf 0 in let len = MBytes.get_uint16 header_buf 0 in
let buf = MBytes.create len in let pos = Crypto.header_length in
P2p_io_scheduler.read_full ~len fd buf >>=? fun () -> let buf = MBytes.create (pos + len) in
match Data_encoding.Binary.read encoding buf 0 len with MBytes.set_int16 buf 0 len ;
P2p_io_scheduler.read_full ~len ~pos fd buf >>=? fun () ->
match Data_encoding.Binary.read encoding buf pos len with
| None -> | None ->
fail P2p_errors.Decoding_error fail P2p_errors.Decoding_error
| Some (read_len, message) -> | Some (next_pos, message) ->
if read_len <> len then if next_pos <> pos+len then
fail P2p_errors.Decoding_error fail P2p_errors.Decoding_error
else else
return message return (message, buf)
end end
@ -176,15 +181,15 @@ let authenticate
~proof_of_work_target ~proof_of_work_target
~incoming fd (remote_addr, remote_socket_port as point) ~incoming fd (remote_addr, remote_socket_port as point)
?listening_port identity supported_versions = ?listening_port identity supported_versions =
let local_nonce = Crypto_box.random_nonce () in let local_nonce_seed = Crypto_box.random_nonce () in
lwt_debug "Sending authenfication to %a" P2p_point.Id.pp point >>= fun () -> lwt_debug "Sending authenfication to %a" P2p_point.Id.pp point >>= fun () ->
Connection_message.write fd Connection_message.write fd
{ public_key = identity.P2p_identity.public_key ; { public_key = identity.P2p_identity.public_key ;
proof_of_work_stamp = identity.proof_of_work_stamp ; proof_of_work_stamp = identity.proof_of_work_stamp ;
message_nonce = local_nonce ; message_nonce = local_nonce_seed ;
port = listening_port ; port = listening_port ;
versions = supported_versions } >>=? fun () -> versions = supported_versions } >>=? fun sent_msg ->
Connection_message.read fd >>=? fun msg -> Connection_message.read fd >>=? fun (msg, recv_msg) ->
let remote_listening_port = let remote_listening_port =
if incoming then msg.port else Some remote_socket_port in if incoming then msg.port else Some remote_socket_port in
let id_point = remote_addr, remote_listening_port in let id_point = remote_addr, remote_listening_port in
@ -198,13 +203,13 @@ let authenticate
(P2p_errors.Not_enough_proof_of_work remote_peer_id) >>=? fun () -> (P2p_errors.Not_enough_proof_of_work remote_peer_id) >>=? fun () ->
let channel_key = let channel_key =
Crypto_box.precompute identity.P2p_identity.secret_key msg.public_key in Crypto_box.precompute identity.P2p_identity.secret_key msg.public_key in
let (local_nonce, remote_nonce) =
Crypto_box.generate_nonces ~incoming ~sent_msg ~recv_msg in
let cryptobox_data = { Crypto.channel_key ; local_nonce ; remote_nonce } in
let info = let info =
{ P2p_connection.Info.peer_id = remote_peer_id ; { P2p_connection.Info.peer_id = remote_peer_id ;
versions = msg.versions ; incoming ; versions = msg.versions ; incoming ;
id_point ; remote_socket_port ;} in id_point ; remote_socket_port ;} in
let cryptobox_data =
{ Crypto.channel_key ; local_nonce ;
remote_nonce = msg.message_nonce } in
return (info, (fd, info, cryptobox_data)) return (info, (fd, info, cryptobox_data))
type connection = { type connection = {
@ -552,4 +557,3 @@ let close ?(wait = false) st =
Writer.shutdown st.writer >>= fun () -> Writer.shutdown st.writer >>= fun () ->
P2p_io_scheduler.close st.conn.fd >>= fun _ -> P2p_io_scheduler.close st.conn.fd >>= fun _ ->
Lwt.return_unit Lwt.return_unit

View File

@ -166,6 +166,15 @@ module Nonce = struct
Bigstring.blit nonce 0 new_nonce 0 24 ; Bigstring.blit nonce 0 new_nonce 0 24 ;
incr_byte new_nonce step 22 ; incr_byte new_nonce step 22 ;
new_nonce new_nonce
let of_bytes buf =
if Bigstring.length buf <> bytes then None else Some buf
let of_bytes_exn buf =
match of_bytes buf with
| Some s -> s
| None -> invalid_arg "Hacl.Nonce.of_bytes_exn: invalid length"
end end
module Secretbox = struct module Secretbox = struct

View File

@ -71,6 +71,8 @@ module Nonce : sig
val bytes : int val bytes : int
val gen : unit -> t val gen : unit -> t
val increment : ?step:int -> t -> t val increment : ?step:int -> t -> t
val of_bytes : Bigstring.t -> t option
val of_bytes_exn : Bigstring.t -> t
end end
module Secretbox : sig module Secretbox : sig