open State open Core open Handshake_common open Config let answer_server_hello state ch (sh : server_hello) secrets raw log = (* assume SH valid, version 1.3, extensions are subset *) match Ciphersuite.ciphersuite_to_ciphersuite13 sh.ciphersuite with | None -> Error (`Fatal (`Handshake (`Message "not a TLS 1.3 ciphersuite"))) | Some cipher -> let* () = guard (List.mem cipher (ciphers13 state.config)) (`Fatal (`Handshake (`Message "not a configured ciphersuite"))) in let* () = guard (String.length state.hs_fragment = 0) (`Fatal (`Handshake `Fragments)) in (* TODO: PSK *) (* TODO: early_secret elsewhere *) match Utils.map_find ~f:(function `KeyShare ks -> Some ks | _ -> None) sh.extensions with | None -> Error (`Fatal (`Handshake (`Message "missing key share extension"))) | Some (g, share) -> match List.find_opt (fun (g', _) -> g = g') secrets with | None -> Error (`Fatal (`Handshake (`Message "couldn't find our secret for the key share"))) | Some (_, secret) -> let* shared = Handshake_crypto13.dh_shared secret share in let hlen = let module H = (val Digestif.module_of_hash' (Ciphersuite.hash13 cipher)) in H.digest_size in let* psk, resumed = match Utils.map_find ~f:(function `PreSharedKey idx -> Some idx | _ -> None) sh.extensions, state.config.Config.cached_ticket with | None, _ | _, None -> Ok (String.make hlen '\x00', false) | Some idx, Some (psk, _epoch) -> let* () = guard (idx = 0) (`Fatal (`Handshake (`Message "resumed pre-shared idx not 0"))) in Ok (psk.secret, true) in let early_secret = Handshake_crypto13.(derive (empty cipher) psk) in let hs_secret = Handshake_crypto13.derive early_secret shared in let log = log ^ raw in let server_hs_secret, server_ctx, client_hs_secret, client_ctx = Handshake_crypto13.hs_ctx hs_secret log in let master_secret = Handshake_crypto13.derive hs_secret (String.make hlen '\x00') in let session = let base = empty_session13 cipher in let common_session_data13 = { base.common_session_data13 with server_random = sh.server_random ; client_random = ch.client_random ; master_secret = master_secret.secret } in { base with master_secret ; common_session_data13 ; resumed } in let st = AwaitServerEncryptedExtensions13 (session, server_hs_secret, client_hs_secret, log) in Ok ({ state with machina = Client13 st ; protocol_version = `TLS_1_3 }, [ `Change_enc client_ctx ; `Change_dec server_ctx ]) (* called from handshake_client.ml *) let answer_hello_retry_request state (ch : client_hello) hrr _secrets raw log = (* when is a HRR invalid / what do we need to check? -> we advertised the group and cipher -> TODO we did advertise such a keyshare already (does it matter?) *) let* () = guard (`TLS_1_3 = hrr.retry_version) (`Fatal (`Handshake (`Message "hello retry request with a version <> 1.3"))) in let* () = guard (List.mem hrr.selected_group state.config.groups) (`Fatal (`Handshake (`Message "hello retry request with group we didn't advertise"))) in let* () = guard (List.mem hrr.ciphersuite (ciphers13 state.config)) (`Fatal (`Handshake (`Message "hello retet request with ciphersuite we didn't advertise"))) in (* generate a fresh keyshare *) let secret, keyshare = let g = hrr.selected_group in let priv, share = Handshake_crypto13.dh_gen_key g in (g, priv), (group_to_named_group g, share) in (* append server extensions (i.e. cookie!) *) let cookie = match Utils.map_find ~f:(function `Cookie c -> Some c | _ -> None) hrr.extensions with | None -> [] | Some c -> [ `Cookie c ] in (* use the same extensions as in original CH, apart from PSK!? and early_data *) let other_exts = List.filter (function `KeyShare _ -> false | _ -> true) ch.extensions in let new_ch = { ch with extensions = `KeyShare [keyshare] :: other_exts @ cookie} in let new_ch_raw = Writer.assemble_handshake (ClientHello new_ch) in let ch0_data = let module H = (val Digestif.module_of_hash' (Ciphersuite.hash13 hrr.ciphersuite)) in H.(to_raw_string (digest_string log)) in let ch0_hdr = Writer.assemble_message_hash (String.length ch0_data) in let st = AwaitServerHello13 (new_ch, [secret], String.concat "" [ ch0_hdr ; ch0_data ; raw ; new_ch_raw ]) in Tracing.hs ~tag:"handshake-out" (ClientHello new_ch); Ok ({ state with machina = Client13 st ; protocol_version = `TLS_1_3 }, [`Record (Packet.HANDSHAKE, new_ch_raw)]) let answer_encrypted_extensions state (session : session_data13) server_hs_secret client_hs_secret ee raw log = (* TODO we now know: - hostname - early_data (preserve this in session!!) *) (* next message is either CertificateRequest or Certificate (or finished if PSK) *) let alpn_protocol = Utils.map_find ~f:(function `ALPN proto -> Some proto | _ -> None) ee in let session = let common_session_data13 = { session.common_session_data13 with alpn_protocol } in { session with common_session_data13 } in let st = if session.resumed then AwaitServerFinished13 (session, server_hs_secret, client_hs_secret, None, log ^ raw) else AwaitServerCertificateRequestOrCertificate13 (session, server_hs_secret, client_hs_secret, log ^ raw) in Ok ({ state with machina = Client13 st }, []) let answer_certificate state (session : session_data13) server_hs_secret client_hs_secret sigalgs certs raw log = (* certificates are (cs, ext) list - ext being statusrequest or signed_cert_timestamp *) let certs = List.map fst certs in let* peer_certificate, received_certificates, peer_certificate_chain, trust_anchor = validate_chain state.config.authenticator certs state.config.ip state.config.peer_name in let session = let common_session_data13 = { session.common_session_data13 with received_certificates ; peer_certificate_chain ; peer_certificate ; trust_anchor } in { session with common_session_data13 } in let st = AwaitServerCertificateVerify13 (session, server_hs_secret, client_hs_secret, sigalgs, log ^ raw) in Ok ({ state with machina = Client13 st }, []) let answer_certificate_verify (state : handshake_state) (session : session_data13) server_hs_secret client_hs_secret sigalgs cv raw log = let tbs = let module H = (val Digestif.module_of_hash' (Ciphersuite.hash13 session.ciphersuite13)) in H.(to_raw_string (digest_string log)) in let* () = verify_digitally_signed state.protocol_version ~context_string:"TLS 1.3, server CertificateVerify" state.config.signature_algorithms cv tbs session.common_session_data13.peer_certificate in let st = AwaitServerFinished13 (session, server_hs_secret, client_hs_secret, sigalgs, log ^ raw) in Ok ({ state with machina = Client13 st }, []) let answer_certificate_request (state : handshake_state) (session : session_data13) server_hs_secret client_hs_secret extensions raw log = (* TODO respect extensions (CA, OIDfilter)! *) let session = let common_session_data13 = { session.common_session_data13 with client_auth = true } in { session with common_session_data13 } in let sigalgs = Utils.map_find ~f:(function `SignatureAlgorithms s -> Some s | _ -> None) extensions in let st = AwaitServerCertificate13 (session, server_hs_secret, client_hs_secret, sigalgs, log ^ raw) in Ok ({ state with machina = Client13 st }, []) let answer_finished state (session : session_data13) server_hs_secret client_hs_secret sigalgs fin raw log = let hash = Ciphersuite.hash13 session.ciphersuite13 in let f_data = Handshake_crypto13.finished hash server_hs_secret log in let* () = guard (String.equal fin f_data) (`Fatal (`Handshake (`Message "couldn't verify finished"))) in let* () = guard (String.length state.hs_fragment = 0) (`Fatal (`Handshake `Fragments)) in let log = log ^ raw in let server_app_secret, server_app_ctx, client_app_secret, client_app_ctx = Handshake_crypto13.app_ctx session.master_secret log in let exporter_master_secret = Handshake_crypto13.exporter session.master_secret log in let* c_cv, log = if session.common_session_data13.client_auth then let own_certificate, own_private_key = match state.config.Config.own_certificates with | `Single (chain, priv) -> (chain, Some priv) | _ -> ([], None) in let certificate = let cs = List.map X509.Certificate.encode_der own_certificate in Certificate (Writer.assemble_certificates_1_3 "" cs) in let cert_raw = Writer.assemble_handshake certificate in Tracing.hs ~tag:"handshake-out" certificate ; let log = log ^ cert_raw in match own_private_key with | None -> Ok ([cert_raw], log) | Some priv -> let tbs = let module H = (val Digestif.module_of_hash' hash) in H.(to_raw_string (digest_string log)) in let* signed = signature `TLS_1_3 ~context_string:"TLS 1.3, client CertificateVerify" tbs sigalgs state.config.Config.signature_algorithms priv in let cv = CertificateVerify signed in Tracing.hs ~tag:"handshake-out" cv ; let cv_raw = Writer.assemble_handshake cv in Ok ([ cert_raw ; cv_raw ], log ^ cv_raw) else Ok ([], log) in 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 ; exporter_master_secret ; client_app_secret ; server_app_secret } in let machina = Client13 Established13 in Tracing.hs ~tag:"handshake-out" (Finished myfin); Ok ({ state with machina ; session = `TLS13 session :: state.session }, List.map (fun data -> `Record (Packet.HANDSHAKE, data)) c_cv @ [ `Record (Packet.HANDSHAKE, mfin) ; `Change_dec server_app_ctx ; `Change_enc client_app_ctx ]) let answer_session_ticket state st = (match state.config.ticket_cache with | None -> () | Some cache -> (* looks like we'll need the resumption secret in the state (we can compute once finished is done)! *) match state.session with | `TLS13 session :: _ -> let epoch = epoch_of_session false state.config.Config.peer_name `TLS_1_3 (`TLS13 session) in let secret = Handshake_crypto13.res_secret (Ciphersuite.hash13 session.ciphersuite13) session.resumption_secret st.nonce in let issued_at = cache.timestamp () in let early_data = match Utils.map_find ~f:(function `EarlyDataIndication x -> Some x | _ -> None) st.extensions with | None -> 0l | Some x -> x in let psk = { identifier = st.ticket ; obfuscation = st.age_add ; secret ; lifetime = st.lifetime ; early_data ; issued_at } in cache.ticket_granted psk epoch | _ -> ()); Ok (state, []) let handle_key_update state req = match state.session with | `TLS13 session :: _ -> let* () = guard (String.length state.hs_fragment = 0) (`Fatal (`Handshake `Fragments)) in let server_app_secret, server_ctx = Handshake_crypto13.app_secret_n_1 session.master_secret session.server_app_secret in let session' = { session with server_app_secret } in let session', out = match req with | Packet.UPDATE_NOT_REQUESTED -> session', [] | Packet.UPDATE_REQUESTED -> let client_app_secret, client_ctx = Handshake_crypto13.app_secret_n_1 session.master_secret session.client_app_secret in let ku = KeyUpdate Packet.UPDATE_NOT_REQUESTED in Tracing.hs ~tag:"handshake-out" ku ; let ku_raw = Writer.assemble_handshake ku in { session' with client_app_secret }, [ `Record (Packet.HANDSHAKE, ku_raw); `Change_enc client_ctx ] in let session = `TLS13 session' :: state.session in let state' = { state with machina = Server13 Established13 ; session } in Ok (state', `Change_dec server_ctx :: out) | _ -> Error (`Fatal (`Handshake (`Message "couldn't find an earlier session"))) let handle_handshake cs hs buf = let open Reader in let* handshake = map_reader_error (parse_handshake buf) in Tracing.hs ~tag:"handshake-in" handshake; match cs, handshake with | AwaitServerHello13 (ch, secrets, log), ServerHello sh -> answer_server_hello hs ch sh secrets buf log | AwaitServerEncryptedExtensions13 (sd, es, ss, log), EncryptedExtensions ee -> answer_encrypted_extensions hs sd es ss ee buf log | AwaitServerCertificateRequestOrCertificate13 (sd, es, ss, log), CertificateRequest cr -> let* ctx, exts = map_reader_error (parse_certificate_request_1_3 cr) in (* during handshake, context must be empty! *) let* () = guard (ctx = None) (`Fatal (`Handshake (`Message "certificate request context must be empty"))) in answer_certificate_request hs sd es ss exts buf log | AwaitServerCertificateRequestOrCertificate13 (sd, es, ss, log), Certificate cs -> let* con, cs = map_reader_error (parse_certificates_1_3 cs) in (* during handshake, context must be empty! and we'll not get any new certificate from server *) let* () = guard (String.length con = 0) (`Fatal (`Handshake (`Message "certificate context must be empty"))) in answer_certificate hs sd es ss None cs buf log | AwaitServerCertificate13 (sd, es, ss, sigalgs, log), Certificate cs -> let* con, cs = map_reader_error (parse_certificates_1_3 cs) in (* during handshake, context must be empty! and we'll not get any new certificate from server *) let* () = guard (String.length con = 0) (`Fatal (`Handshake (`Message "certificate context must be empty"))) in answer_certificate hs sd es ss sigalgs cs buf log | AwaitServerCertificateVerify13 (sd, es, ss, sigalgs, log), CertificateVerify cv -> answer_certificate_verify hs sd es ss sigalgs cv buf log | AwaitServerFinished13 (sd, es, ss, sigalgs, log), Finished fin -> answer_finished hs sd es ss sigalgs fin buf log | Established13, SessionTicket se -> answer_session_ticket hs se | Established13, CertificateRequest _ -> Error (`Fatal (`Unexpected (`Handshake handshake))) (* TODO send out C, CV, F *) | Established13, KeyUpdate req -> handle_key_update hs req | _, hs -> Error (`Fatal (`Unexpected (`Handshake hs)))