Skip to content

Commit

Permalink
Merge pull request #482 from hannesm/exporter-secret
Browse files Browse the repository at this point in the history
Support exporter secrets, as defined in RFC 5705 & 8446
  • Loading branch information
hannesm committed Nov 20, 2023
2 parents 1175137 + f29a929 commit 8c4594b
Show file tree
Hide file tree
Showing 10 changed files with 74 additions and 16 deletions.
2 changes: 2 additions & 0 deletions lib/core.ml
Expand Up @@ -465,6 +465,7 @@ type epoch_state = [ `ZeroRTT | `Established ]

(** information about an open session *)
type epoch_data = {
side : [ `Client | `Server ] ;
state : epoch_state ;
protocol_version : tls_version ;
ciphersuite : Ciphersuite.ciphersuite ;
Expand All @@ -479,6 +480,7 @@ type epoch_data = {
own_private_key : X509.Private_key.t option ;
own_name : [`host] Domain_name.t option ;
master_secret : master_secret ;
exporter_master_secret : master_secret ;
session_id : SessionID.t ;
extended_ms : bool ;
alpn_protocol : string option ;
Expand Down
33 changes: 33 additions & 0 deletions lib/engine.ml
Expand Up @@ -744,3 +744,36 @@ let epoch state =
match epoch_of_hs state.handshake with
| None -> `InitialEpoch
| Some e -> `Epoch e

let export_key_material (e : epoch_data) ?context label length =
match e.protocol_version with
| `TLS_1_3 ->
let hash =
let cipher = Option.get (Ciphersuite.ciphersuite_to_ciphersuite13 e.ciphersuite) in
Ciphersuite.hash13 cipher
in
let ems = e.exporter_master_secret in
let prk =
let ctx = Mirage_crypto.Hash.digest hash Cstruct.empty in
Handshake_crypto13.derive_secret_no_hash hash ems ~ctx label
in
let ctx = Option.(value ~default:Cstruct.empty (map Cstruct.of_string context)) in
Handshake_crypto13.derive_secret_no_hash
hash prk ~ctx:(Mirage_crypto.Hash.digest hash ctx)
~length "exporter"
| #tls_before_13 as v ->
let seed =
let base =
match e.side with
| `Server -> Cstruct.append e.peer_random e.own_random
| `Client -> Cstruct.append e.own_random e.peer_random
in
match context with
| None -> base
| Some data ->
let len = Cstruct.create 2 in
Cstruct.BE.set_uint16 len 0 (String.length data);
Cstruct.concat [ base ; len ; Cstruct.of_string data ]
in
Handshake_crypto.pseudo_random_function v e.ciphersuite
length e.master_secret label seed
6 changes: 6 additions & 0 deletions lib/engine.mli
Expand Up @@ -200,3 +200,9 @@ type epoch = [
(** [epoch state] is [epoch], which contains the session
information. *)
val epoch : state -> epoch

(** [export_key_material epoch_data ?context label length] is the RFC 5705
exported key material of [length] bytes using [label] and, if provided,
[context]. *)
val export_key_material : Core.epoch_data -> ?context:string -> string -> int ->
Cstruct.t
5 changes: 3 additions & 2 deletions lib/handshake_client13.ml
Expand Up @@ -181,8 +181,9 @@ let answer_finished state (session : session_data13) server_hs_secret client_hs_
let myfin = Handshake_crypto13.finished hash client_hs_secret log in
let mfin = Writer.assemble_handshake (Finished myfin) in

let resumption_secret = Handshake_crypto13.resumption session.master_secret (log <+> mfin) in
let session = { session with resumption_secret ; client_app_secret ; server_app_secret } in
let exporter_master_secret = Handshake_crypto13.exporter session.master_secret log in
let resumption_secret = Handshake_crypto13.resumption session.master_secret (log <+> mfin) in
let session = { session with resumption_secret ; exporter_master_secret ; client_app_secret ; server_app_secret } in
let machina = Client13 Established13 in

Tracing.hs ~tag:"handshake-out" (Finished myfin);
Expand Down
18 changes: 10 additions & 8 deletions lib/handshake_common.ml
Expand Up @@ -129,14 +129,15 @@ let empty_session = {
}

let empty_session13 cipher = {
common_session_data13 = empty_common_session_data ;
ciphersuite13 = cipher ;
master_secret = Handshake_crypto13.empty cipher ;
resumption_secret = Cstruct.empty ;
state = `Established ;
resumed = false ;
client_app_secret = Cstruct.empty ;
server_app_secret = Cstruct.empty ;
common_session_data13 = empty_common_session_data ;
ciphersuite13 = cipher ;
master_secret = Handshake_crypto13.empty cipher ;
exporter_master_secret = Cstruct.empty ;
resumption_secret = Cstruct.empty ;
state = `Established ;
resumed = false ;
client_app_secret = Cstruct.empty ;
server_app_secret = Cstruct.empty ;
}

let common_session_data_of_epoch (epoch : epoch_data) common_session_data =
Expand Down Expand Up @@ -170,6 +171,7 @@ let session13_of_epoch cipher (epoch : epoch_data) : session_data13 =
common_session_data13 ;
ciphersuite13 = cipher ;
state = epoch.state ;
exporter_master_secret = epoch.exporter_master_secret ;
}

let supported_protocol_version (min, max) v =
Expand Down
4 changes: 4 additions & 0 deletions lib/handshake_crypto.mli
Expand Up @@ -3,3 +3,7 @@ open State
val derive_master_secret : Core.tls_before_13 -> session_data -> Cstruct.t -> Cstruct.t list -> Core.master_secret
val initialise_crypto_ctx : Core.tls_before_13 -> session_data -> (crypto_context * crypto_context)
val finished : Core.tls_before_13 -> Ciphersuite.ciphersuite -> Cstruct.t -> string -> Cstruct.t list -> Cstruct.t

(** [pseudo_random_function version cipher length secret label seed] *)
val pseudo_random_function : Core.tls_before_13 -> Ciphersuite.ciphersuite ->
int -> Cstruct.t -> string -> Cstruct.t -> Cstruct.t
3 changes: 2 additions & 1 deletion lib/handshake_server13.ml
Expand Up @@ -342,7 +342,8 @@ let answer_client_hello ~hrr state ch raw =

let session =
let common_session_data13 = { session'.common_session_data13 with master_secret = master_secret.secret } in
{ session' with common_session_data13 ; master_secret (* TODO ; exporter_secret *) }
let exporter_master_secret = Handshake_crypto13.exporter session.master_secret log in
{ session' with common_session_data13 ; master_secret ; exporter_master_secret }
in
let st, session =
if can_use_early_data then
Expand Down
7 changes: 6 additions & 1 deletion lib/state.ml
Expand Up @@ -123,6 +123,7 @@ type session_data13 = {
common_session_data13 : common_session_data ;
ciphersuite13 : Ciphersuite.ciphersuite13 ;
master_secret : kdf ;
exporter_master_secret : Cstruct.t ;
resumption_secret : Cstruct.t ;
state : epoch_state ;
resumed : bool ;
Expand Down Expand Up @@ -377,7 +378,8 @@ let common_data_to_epoch common is_server peer_name =
common.client_random, common.server_random
in
let epoch : epoch_data =
{ state = `Established ;
{ side = if is_server then `Server else `Client ;
state = `Established ;
protocol_version = `TLS_1_0 ;
ciphersuite = `DHE_RSA_WITH_AES_256_CBC_SHA ;
peer_random ;
Expand All @@ -391,6 +393,7 @@ let common_data_to_epoch common is_server peer_name =
own_name = common.own_name ;
received_certificates = common.received_certificates ;
master_secret = common.master_secret ;
exporter_master_secret = Cstruct.empty ;
alpn_protocol = common.alpn_protocol ;
session_id = Cstruct.empty ;
extended_ms = false ;
Expand All @@ -411,9 +414,11 @@ let epoch_of_session server peer_name protocol_version = function
let epoch : epoch_data = common_data_to_epoch session.common_session_data13 server peer_name in
{
epoch with
protocol_version = protocol_version ;
ciphersuite = (session.ciphersuite13 :> Ciphersuite.ciphersuite) ;
extended_ms = true ; (* RFC 8446, Appendix D, last paragraph *)
state = session.state ;
exporter_master_secret = session.exporter_master_secret ;
}

let epoch_of_hs hs =
Expand Down
6 changes: 4 additions & 2 deletions lwt/examples/echo_client.ml
Expand Up @@ -5,7 +5,8 @@ open Lwt
let cached_session : Tls.Core.epoch_data =
let hex = Cstruct.of_hex in
{
Tls.Core.protocol_version = `TLS_1_3 ;
Tls.Core.side = `Client ;
protocol_version = `TLS_1_3 ;
ciphersuite = `DHE_RSA_WITH_AES_128_GCM_SHA256 ;
peer_random = hex "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" ;
peer_certificate = None ;
Expand All @@ -18,7 +19,8 @@ let cached_session : Tls.Core.epoch_data =
own_private_key = None ;
own_name = None ;
master_secret = hex "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" ;
session_id = Cstruct.create 0 ;
exporter_master_secret = Cstruct.empty ;
session_id = Cstruct.empty ;
extended_ms = true ;
alpn_protocol = None ;
state = `Established ;
Expand Down
6 changes: 4 additions & 2 deletions lwt/examples/resume_echo_server.ml
Expand Up @@ -30,7 +30,8 @@ let serve_ssl port callback =
let hex = Cstruct.of_hex in
let epoch =
{
Tls.Core.state = `Established ;
Tls.Core.side = `Client ;
state = `Established ;
protocol_version = `TLS_1_3 ;
ciphersuite = `DHE_RSA_WITH_AES_128_GCM_SHA256 ;
peer_random = hex "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" ;
Expand All @@ -44,7 +45,8 @@ let serve_ssl port callback =
own_private_key = Some (snd cert) ;
own_name = Some Domain_name.(host_exn (of_string_exn "tls13test.nqsb.io")) ;
master_secret = hex "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" ;
session_id = Cstruct.create 0 ;
exporter_master_secret = Cstruct.empty ;
session_id = Cstruct.empty ;
extended_ms = true ;
alpn_protocol = None ;
}
Expand Down

0 comments on commit 8c4594b

Please sign in to comment.