open Core open State let src = Logs.Src.create "handshake" ~doc:"TLS handshake" module Log = (val Logs.src_log src : Logs.LOG) let trace_cipher cipher = Tracing.debug (fun m -> m "%a" Ciphersuite.pp_ciphersuite cipher) let empty = function [] -> true | _ -> false let change_cipher_spec = (Packet.CHANGE_CIPHER_SPEC, Writer.assemble_change_cipher_spec) let hostname (h : client_hello) : [ `host ] Domain_name.t option = Utils.map_find ~f:(function `Hostname s -> Some s | _ -> None) h.extensions let groups (h : client_hello) = match Utils.map_find ~f:(function `SupportedGroups g -> Some g | _ -> None) h.extensions with | Some xs -> List.fold_left (fun acc g -> match named_group_to_group g with Some g -> g :: acc | _ -> acc) [] xs | None -> [] let rec find_matching host certs = match certs with | (s::_, _) as chain ::xs -> if X509.Certificate.supports_hostname s host then Some chain else find_matching host xs | _::xs -> find_matching host xs (* this should never happen! *) | [] -> None let agreed_cert certs ?f ?signature_algorithms hostname = let match_host ?default host certs = match find_matching host certs with | Some x -> Ok x | None -> Option.to_result ~none:(`Error (`NoMatchingCertificateFound (Domain_name.to_string host))) default in let filter = function | ([], _) -> false (* cannot happen, TODO: adapt types to avoid this case *) | (s :: _, _) -> match f with | None -> true | Some f -> f s in let filter_sigalg c = match signature_algorithms with | None -> true | Some s -> List.exists (pk_matches_sa (snd c)) s in match certs, hostname with | `None, _ -> Error (`Error `CouldntSelectCertificate) | `Single c, _ -> if filter c && filter_sigalg c then Ok c else Error (`Error `CouldntSelectCertificate) | `Multiple_default (c, _), None -> if filter c && filter_sigalg c then Ok c else Error (`Error `CouldntSelectCertificate) | `Multiple_default (c, cs), Some h -> let default = if filter c && filter_sigalg c then Some c else None in begin match default, List.filter (fun c -> filter c && filter_sigalg c) cs with | Some d, cs -> match_host ~default:d h cs | None, c :: cs -> match_host ~default:c h (c::cs) | None, [] -> Error (`Error `CouldntSelectCertificate) end | `Multiple cs, None -> begin match List.filter (fun c -> filter c && filter_sigalg c) cs with | cert :: _ -> Ok cert | _ -> Error (`Error `CouldntSelectCertificate) end | `Multiple cs, Some h -> match List.filter (fun c -> filter c && filter_sigalg c) cs with | [ cert ] -> Ok cert | c :: cs -> match_host ~default:c h (c :: cs) | [] -> Error (`Error `CouldntSelectCertificate) let get_secure_renegotiation exts = Utils.map_find exts ~f:(function `SecureRenegotiation data -> Some data | _ -> None) let get_alpn_protocols (ch : client_hello) = Utils.map_find ~f:(function `ALPN protocols -> Some protocols | _ -> None) ch.extensions let alpn_protocol config ch = match config.Config.alpn_protocols, get_alpn_protocols ch with | _, None | [], _ -> Ok None | configured, Some client -> match Utils.first_match client configured with | Some proto -> Ok (Some proto) | None -> (* RFC7301 Section 3.2: In the event that the server supports no protocols that the client advertises, then the server SHALL respond with a fatal "no_application_protocol" alert. *) Error (`Fatal `No_application_protocol) let get_alpn_protocol (sh : server_hello) = Utils.map_find ~f:(function `ALPN protocol -> Some protocol | _ -> None) sh.extensions let empty_common_session_data = { server_random = "" ; client_random = "" ; peer_certificate_chain = [] ; peer_certificate = None ; trust_anchor = None ; received_certificates = [] ; own_certificate = [] ; own_private_key = None ; own_name = None ; client_auth = false ; master_secret = "" ; alpn_protocol = None ; } let empty_session = { common_session_data = empty_common_session_data ; client_version = `TLS_1_2 ; ciphersuite = `DHE_RSA_WITH_AES_256_CBC_SHA ; group = Some `FFDHE2048 ; renegotiation = "", "" ; session_id = "" ; extended_ms = false ; tls_unique = "" ; } let empty_session13 cipher = { common_session_data13 = empty_common_session_data ; ciphersuite13 = cipher ; master_secret = Handshake_crypto13.empty cipher ; exporter_master_secret = "" ; resumption_secret = "" ; state = `Established ; resumed = false ; client_app_secret = "" ; server_app_secret = "" ; } let common_session_data_of_epoch (epoch : epoch_data) common_session_data = { common_session_data with peer_certificate = epoch.peer_certificate ; trust_anchor = epoch.trust_anchor ; own_certificate = epoch.own_certificate ; own_private_key = epoch.own_private_key ; received_certificates = epoch.received_certificates ; peer_certificate_chain = epoch.peer_certificate_chain ; master_secret = epoch.master_secret ; own_name = epoch.own_name ; alpn_protocol = epoch.alpn_protocol ; } let session_of_epoch (epoch : epoch_data) : session_data = let empty = empty_session in let common_session_data = common_session_data_of_epoch epoch empty.common_session_data in { empty with common_session_data ; ciphersuite = epoch.ciphersuite ; session_id = epoch.session_id ; extended_ms = epoch.extended_ms ; } let session13_of_epoch cipher (epoch : epoch_data) : session_data13 = let empty = empty_session13 cipher in let common_session_data13 = common_session_data_of_epoch epoch empty.common_session_data13 in { empty with common_session_data13 ; ciphersuite13 = cipher ; state = epoch.state ; exporter_master_secret = epoch.exporter_master_secret ; } let supported_protocol_version (min, max) v = if compare_tls_version min v > 0 then None else if compare_tls_version v max > 0 then None else Some v let to_client_ext_type = function | `Hostname _ -> `Hostname | `MaxFragmentLength _ -> `MaxFragmentLength | `SupportedGroups _ -> `SupportedGroups | `ECPointFormats -> `ECPointFormats | `SecureRenegotiation _ -> `SecureRenegotiation | `Padding _ -> `Padding | `SignatureAlgorithms _ -> `SignatureAlgorithms | `UnknownExtension _ -> `UnknownExtension | `ExtendedMasterSecret -> `ExtendedMasterSecret | `ALPN _ -> `ALPN | `KeyShare _ -> `KeyShare | `EarlyDataIndication -> `EarlyDataIndication | `PreSharedKeys _ -> `PreSharedKey | `Draft _ -> `Draft | `SupportedVersions _ -> `SupportedVersion | `PostHandshakeAuthentication -> `PostHandshakeAuthentication | `Cookie _ -> `Cookie | `PskKeyExchangeModes _ -> `PskKeyExchangeMode let to_server_ext_type = function | `Hostname -> `Hostname | `MaxFragmentLength _ -> `MaxFragmentLength | `ECPointFormats -> `ECPointFormats | `SecureRenegotiation _ -> `SecureRenegotiation | `UnknownExtension _ -> `UnknownExtension | `ExtendedMasterSecret -> `ExtendedMasterSecret | `ALPN _ -> `ALPN | `KeyShare _ -> `KeyShare | `EarlyDataIndication -> `EarlyDataIndication | `PreSharedKey _ -> `PreSharedKey | `Draft _ -> `Draft | `SelectedVersion _ -> `SupportedVersion let extension_types t exts = List.( exts |> map t |> filter @@ function `UnknownExtension -> false | _ -> true ) (* a server hello may only contain extensions which are also in the client hello *) (* RFC5246, 7.4.7.1 An extension type MUST NOT appear in the ServerHello unless the same extension type appeared in the corresponding ClientHello. If a client receives an extension type in ServerHello that it did not request in the associated ClientHello, it MUST abort the handshake with an unsupported_extension fatal alert. *) let server_exts_subset_of_client sexts cexts = let (sexts', cexts') = (extension_types to_server_ext_type sexts, extension_types to_client_ext_type cexts) in Utils.List_set.subset sexts' (`Cookie :: cexts') module Group = struct type t = Packet.named_group let compare = Stdlib.compare end module GroupSet = Set.Make(Group) (* Set.of_list appeared only in 4.02, for 4.01 compatibility *) let of_list xs = List.fold_right GroupSet.add xs GroupSet.empty let client_hello_valid version (ch : client_hello) = (* match ch.version with | TLS_1_0 -> if List.mem TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA ch.ciphersuites then return () else fail HANDSHAKE_FAILURE | TLS_1_1 -> if List.mem TLS_RSA_WITH_3DES_EDE_CBC_SHA ch.ciphersuites then return () else fail HANDSHAKE_FAILURE | TLS_1_2 -> if List.mem TLS_RSA_WITH_AES_128_CBC_SHA ch.ciphersuites then return () else fail HANDSHAKE_FAILURE *) let sig_alg = Utils.map_find ~f:(function `SignatureAlgorithms sa -> Some sa | _ -> None) ch.extensions and key_share = Utils.map_find ~f:(function `KeyShare ks -> Some ks | _ -> None) ch.extensions and groups = Utils.map_find ~f:(function `SupportedGroups gs -> Some gs | _ -> None) ch.extensions in let version_good = match version with | `TLS_1_2 | `TLS_1_X _ -> Ok () | `TLS_1_3 -> ( let good_sig_alg = List.exists (fun sa -> List.mem sa Config.supported_signature_algorithms) in match sig_alg with | None -> Error (`Fatal (`Missing_extension "signature algorithms")) | Some sig_alg when good_sig_alg sig_alg -> ( match key_share, groups with | None, _ -> Error (`Fatal (`Missing_extension "key share")) | _, None -> Error (`Fatal (`Missing_extension "supported group")) | Some ks, Some gs -> match Utils.List_set.is_proper_set gs, Utils.List_set.is_proper_set (List.map fst ks), GroupSet.subset (of_list (List.map fst ks)) (of_list gs) with | true, true, true -> Ok () | false, _, _ -> Error (`Fatal (`Handshake (`Message "supported group is not a set"))) | _, false, _ -> Error (`Fatal (`Handshake (`Message "key share is not a set"))) | _, _, false -> Error (`Fatal (`Handshake (`Message "key share is not a subset of supported group")) )) | Some _ -> Error (`Fatal (`Handshake (`Message "no good signature algorithms"))) ) | `SSL_3 | `TLS_1_0 | `TLS_1_1 -> Ok () in let share_ciphers = match Utils.first_match (List.filter_map Ciphersuite.any_ciphersuite_to_ciphersuite ch.ciphersuites) Config.Ciphers.supported with | None -> false | Some _ -> true in match not (empty ch.ciphersuites), share_ciphers, Utils.List_set.is_proper_set (extension_types to_client_ext_type ch.extensions) with | true, true, true -> version_good | false, _, _ -> Error (`Fatal (`Handshake (`Message "ciphersuites is empty"))) | _, false, _ -> Error (`Fatal (`Handshake (`Message "no supported ciphersuite"))) | _, _, false -> Error (`Fatal (`Handshake (`Message "extensions is not a set"))) let server_hello_valid (sh : server_hello) = (* let open Ciphersuite in *) Utils.List_set.is_proper_set (extension_types to_server_ext_type sh.extensions) (* TODO: - EC stuff must be present if EC ciphersuite chosen *) let to_sign_1_3 context_string = (* input is prepended by 64 * 0x20 (to avoid cross-version attacks) *) (* input for signature now contains also a context string *) let len = match context_string with | None -> 64 + 1 | Some v -> 64 + String.length v + 1 in let buf = Bytes.create len in Bytes.fill buf 0 64 '\x20'; begin match context_string with | None -> () | Some v -> Bytes.blit_string v 0 buf 64 (String.length v) end; Bytes.set buf (Bytes.length buf - 1) '\x00'; Bytes.unsafe_to_string buf let signature version ?context_string data client_sig_algs signature_algorithms (private_key : X509.Private_key.t) = match version with | `TLS_1_0 | `TLS_1_1 -> let* signed = match private_key with | `RSA key -> begin try let data = Digestif.(MD5.(to_raw_string (digest_string data)) ^ SHA1.(to_raw_string (digest_string data))) in Ok (Mirage_crypto_pk.Rsa.PKCS1.sig_encode ~key data) with Mirage_crypto_pk.Rsa.Insufficient_key -> Error (`Fatal (`Bad_certificate "RSA key too small")) end | k -> (* not passing ~scheme: only non-RSA keys sig scheme is trivial *) Result.map_error (function `Msg m -> `Fatal (`Handshake (`Message ("signing failed: " ^ m)))) (X509.Private_key.sign `SHA1 k (`Message data)) in Ok (Writer.assemble_digitally_signed signed) | `TLS_1_2 -> let* sig_alg = match client_sig_algs with | None -> Ok (match private_key with | `RSA _ -> `RSA_PKCS1_SHA1 | `ED25519 _ -> `ED25519 | _ -> `ECDSA_SECP256R1_SHA1) | Some client_algos -> Option.to_result ~none:(`Error (`NoConfiguredSignatureAlgorithm client_algos)) (Utils.first_match client_algos (List.filter (pk_matches_sa private_key) signature_algorithms)) in let scheme = signature_scheme_of_signature_algorithm sig_alg and hash = hash_of_signature_algorithm sig_alg in let* signature = Result.map_error (function `Msg m -> `Fatal (`Handshake (`Message ("signing failed: " ^ m)))) (X509.Private_key.sign hash ~scheme private_key (`Message data)) in Ok (Writer.assemble_digitally_signed_1_2 sig_alg signature) | `TLS_1_3 -> let to_sign = let prefix = to_sign_1_3 context_string in prefix ^ data in let* sig_alg = let* client_algos = (* 8446 4.2.3 "client MUST send signatureAlgorithms" *) Option.to_result ~none:(`Error (`NoConfiguredSignatureAlgorithm [])) client_sig_algs in let sa = List.filter tls13_sigalg signature_algorithms in let sa = List.filter (pk_matches_sa private_key) sa in Option.to_result ~none:(`Error (`NoConfiguredSignatureAlgorithm client_algos)) (Utils.first_match client_algos sa) in let scheme = signature_scheme_of_signature_algorithm sig_alg and hash = hash_of_signature_algorithm sig_alg in let* signature = Result.map_error (function `Msg m -> `Fatal (`Handshake (`Message ("signing failed: " ^ m)))) (X509.Private_key.sign hash ~scheme private_key (`Message to_sign)) in Ok (Writer.assemble_digitally_signed_1_2 sig_alg signature) let peer_key = function | None -> Error (`Fatal (`Bad_certificate "none received")) | Some cert -> Ok (X509.Certificate.public_key cert) let verify_digitally_signed version ?context_string sig_algs data signature_data certificate = let* pubkey = peer_key certificate in match version with | `TLS_1_0 | `TLS_1_1 -> let* signature = map_reader_error (Reader.parse_digitally_signed data) in begin match pubkey with | `RSA key -> let* raw = Option.to_result ~none:(`Fatal (`Handshake (`Message "couldn't decode PKCS1"))) (Mirage_crypto_pk.Rsa.PKCS1.sig_decode ~key signature) in let computed = Digestif.(MD5.(to_raw_string (digest_string signature_data)) ^ SHA1.(to_raw_string (digest_string signature_data))) in guard (String.equal raw computed) (`Fatal (`Handshake (`Message "RSA PKCS1 raw <> computed"))) | key -> Result.map_error (function `Msg m -> `Fatal (`Handshake (`Message ("signature verification failed: " ^ m)))) (X509.Public_key.verify `SHA1 ~signature key (`Message signature_data)) end | `TLS_1_2 -> let* sig_alg, signature = map_reader_error (Reader.parse_digitally_signed_1_2 data) in let* () = guard (List.mem sig_alg sig_algs) (`Error (`NoConfiguredSignatureAlgorithm sig_algs)) in let hash = hash_of_signature_algorithm sig_alg and scheme = signature_scheme_of_signature_algorithm sig_alg in Result.map_error (function `Msg m -> `Fatal (`Handshake (`Message ("signature verification failed: " ^ m)))) (X509.Public_key.verify hash ~scheme ~signature pubkey (`Message signature_data)) | `TLS_1_3 -> let* sig_alg, signature = map_reader_error (Reader.parse_digitally_signed_1_2 data) in let* () = guard (List.mem sig_alg sig_algs) (`Error (`NoConfiguredSignatureAlgorithm sig_algs)) in let hash = hash_of_signature_algorithm sig_alg and scheme = signature_scheme_of_signature_algorithm sig_alg and data = let prefix = to_sign_1_3 context_string in prefix ^ signature_data in Result.map_error (function `Msg m -> `Fatal (`Handshake (`Message ("signature verification failed: " ^ m)))) (X509.Public_key.verify hash ~scheme ~signature pubkey (`Message data)) let validate_chain authenticator certificates ip hostname = let authenticate authenticator host certificates = Result.map_error (fun err -> `Error (`AuthenticationFailure err)) (authenticator ?ip ~host certificates) and key_size min cs = let check c = match X509.Certificate.public_key c with | `RSA key -> Mirage_crypto_pk.Rsa.pub_bits key >= min | _ -> true in guard (List.for_all check cs) (`Fatal (`Bad_certificate "key too small")) and parse_certificates certs = let certificates = let f cs = match X509.Certificate.decode_der cs with | Ok c -> Some c | Error `Msg msg -> Log.warn (fun m -> m "cannot decode certificate %s:@.%a" msg (Ohex.pp_hexdump ()) cs); None in List.filter_map f certs in let* () = guard (List.length certs = List.length certificates) (`Fatal (`Bad_certificate "couldn't decode some certificates")) in Ok certificates in (* RFC5246: must be x509v3, take signaturealgorithms into account! *) (* RFC2246/4346: is generally x509v3, signing algorithm for certificate _must_ be same as algorithm for certificate key *) let* certs = parse_certificates certificates in let server = match certs with | s::_ -> Some s | [] -> None in match authenticator with | None -> Ok (server, certs, [], None) | Some authenticator -> let* anchor = authenticate authenticator hostname certs in let* () = key_size Config.min_rsa_key_size certs in Ok (Option.fold ~none:(server, certs, [], None) ~some:(fun (chain, anchor) -> (server, certs, chain, Some anchor)) anchor) let output_key_update ~request state = let hs = state.handshake in match hs.session with | `TLS13 session :: _ -> let* session', encryptor = match hs.machina with | Client13 Established13 -> let client_app_secret, client_ctx = Handshake_crypto13.app_secret_n_1 session.master_secret session.client_app_secret in Ok ({ session with client_app_secret }, client_ctx) | Server13 Established13 -> let server_app_secret, server_ctx = Handshake_crypto13.app_secret_n_1 session.master_secret session.server_app_secret in Ok ({ session with server_app_secret }, server_ctx) | _ -> Error (`Fatal (`Handshake (`Message "invalid state for key update"))) in let handshake = { hs with session = `TLS13 session' :: hs.session } in let ku = let p = Packet.(if request then UPDATE_REQUESTED else UPDATE_NOT_REQUESTED) in KeyUpdate p in let out = Writer.assemble_handshake ku in Ok ({ state with encryptor = Some encryptor ; handshake }, (Packet.HANDSHAKE, out)) | _ -> Error (`Fatal (`Handshake (`Message "no earlier session found")))