491 lines
18 KiB
OCaml
491 lines
18 KiB
OCaml
open Packet
|
|
open Core
|
|
|
|
let assemble_protocol_version_int buf off version =
|
|
let major, minor = pair_of_tls_version version in
|
|
Bytes.set_uint8 buf off major;
|
|
Bytes.set_uint8 buf (off + 1) minor
|
|
|
|
let assemble_protocol_version ?(buf= Bytes.create 2) version =
|
|
assemble_protocol_version_int buf 0 version;
|
|
Bytes.unsafe_to_string buf
|
|
|
|
let assemble_any_protocol_version_into buf off version =
|
|
let major, minor = pair_of_tls_any_version version in
|
|
Bytes.set_uint8 buf off major;
|
|
Bytes.set_uint8 buf (off + 1) minor
|
|
|
|
let assemble_any_protocol_version version =
|
|
let buf = Bytes.create 2 in
|
|
assemble_any_protocol_version_into buf 0 version;
|
|
Bytes.unsafe_to_string buf
|
|
|
|
let assemble_hdr version (content_type, payload) =
|
|
let buf = Bytes.create 5 in
|
|
Bytes.set_uint8 buf 0 (content_type_to_int content_type);
|
|
assemble_protocol_version_int buf 1 version;
|
|
Bytes.set_uint16_be buf 3 (String.length payload);
|
|
Bytes.unsafe_to_string buf ^ payload
|
|
|
|
type len = One | Two | Three
|
|
|
|
let assemble_list ?none_if_empty lenb f elements =
|
|
let length body =
|
|
match lenb with
|
|
| One ->
|
|
let l = Bytes.create 1 in
|
|
Bytes.set_uint8 l 0 (String.length body) ;
|
|
Bytes.unsafe_to_string l
|
|
| Two ->
|
|
let l = Bytes.create 2 in
|
|
Bytes.set_uint16_be l 0 (String.length body) ;
|
|
Bytes.unsafe_to_string l
|
|
| Three ->
|
|
let l = Bytes.create 3 in
|
|
set_uint24_len ~off:0 l (String.length body) ;
|
|
Bytes.unsafe_to_string l
|
|
in
|
|
let b es = String.concat "" (List.map f es) in
|
|
let full es =
|
|
let body = b es in
|
|
length body ^ body
|
|
in
|
|
match none_if_empty with
|
|
| Some _ -> (match elements with
|
|
| [] -> ""
|
|
| eles -> full eles)
|
|
| None -> full elements
|
|
|
|
let assemble_certificate c =
|
|
let length = String.length c in
|
|
let buf = Bytes.create 3 in
|
|
set_uint24_len ~off:0 buf length;
|
|
Bytes.unsafe_to_string buf ^ c
|
|
|
|
let assemble_certificates cs =
|
|
assemble_list Three assemble_certificate cs
|
|
|
|
let assemble_compression_method m =
|
|
String.make 1 (Char.unsafe_chr (compression_method_to_int m))
|
|
|
|
let assemble_compression_methods ms =
|
|
assemble_list One assemble_compression_method ms
|
|
|
|
let assemble_any_ciphersuite c =
|
|
let buf = Bytes.create 2 in
|
|
Bytes.set_uint16_be buf 0 (any_ciphersuite_to_int c);
|
|
Bytes.unsafe_to_string buf
|
|
|
|
let assemble_any_ciphersuites cs =
|
|
assemble_list Two assemble_any_ciphersuite cs
|
|
|
|
let assemble_ciphersuite c =
|
|
let acs = Ciphersuite.ciphersuite_to_any_ciphersuite c in
|
|
assemble_any_ciphersuite acs
|
|
|
|
let assemble_hostname host =
|
|
let host = Domain_name.to_string host in
|
|
(* 8 bit hostname type; 16 bit length; value *)
|
|
let vallength = String.length host in
|
|
let buf = Bytes.create 3 in
|
|
Bytes.set_uint8 buf 0 0; (* type, only 0 registered *)
|
|
Bytes.set_uint16_be buf 1 vallength;
|
|
Bytes.unsafe_to_string buf ^ host
|
|
|
|
let assemble_hostnames hosts =
|
|
assemble_list Two assemble_hostname hosts
|
|
|
|
let assemble_hash_signature sigalg =
|
|
let buf = Bytes.create 2 in
|
|
Bytes.set_uint16_be buf 0 (signature_alg_to_int (to_signature_alg sigalg)) ;
|
|
Bytes.unsafe_to_string buf
|
|
|
|
let assemble_signature_algorithms s =
|
|
assemble_list Two assemble_hash_signature s
|
|
|
|
let assemble_certificate_types ts =
|
|
let ass x =
|
|
String.make 1 (Char.unsafe_chr (client_certificate_type_to_int x))
|
|
in
|
|
assemble_list One ass ts
|
|
|
|
let assemble_cas cas =
|
|
let ass x =
|
|
let buf = Bytes.create 2 in
|
|
Bytes.set_uint16_be buf 0 (String.length x) ;
|
|
Bytes.unsafe_to_string buf ^ x
|
|
in
|
|
assemble_list Two ass cas
|
|
|
|
let assemble_certificate_request ts cas =
|
|
assemble_certificate_types ts ^ assemble_cas cas
|
|
|
|
let assemble_certificate_request_1_2 ts sigalgs cas =
|
|
String.concat "" [
|
|
assemble_certificate_types ts;
|
|
assemble_signature_algorithms sigalgs;
|
|
assemble_cas cas
|
|
]
|
|
|
|
let assemble_named_group g =
|
|
let buf = Bytes.create 2 in
|
|
Bytes.set_uint16_be buf 0 (named_group_to_int g);
|
|
Bytes.unsafe_to_string buf
|
|
|
|
let assemble_group g =
|
|
assemble_named_group (group_to_named_group g)
|
|
|
|
let assemble_supported_groups groups =
|
|
assemble_list Two assemble_named_group groups
|
|
|
|
let assemble_keyshare_entry (ng, ks) =
|
|
let g = assemble_named_group ng in
|
|
let l = Bytes.create 2 in
|
|
Bytes.set_uint16_be l 0 (String.length ks) ;
|
|
String.concat "" [ g ; Bytes.unsafe_to_string l ; ks ]
|
|
|
|
let assemble_psk_id (id, age) =
|
|
let id_len = Bytes.create 2 in
|
|
Bytes.set_uint16_be id_len 0 (String.length id) ;
|
|
let age_buf = Bytes.create 4 in
|
|
Bytes.set_int32_be age_buf 0 age ;
|
|
String.concat "" [ Bytes.unsafe_to_string id_len ; id ; Bytes.unsafe_to_string age_buf ]
|
|
|
|
let assemble_binder b =
|
|
let b_len = String.make 1 (Char.unsafe_chr (String.length b)) in
|
|
b_len ^ b
|
|
|
|
let assemble_client_psks psks =
|
|
let ids, binders = List.split psks in
|
|
let ids_buf = assemble_list Two assemble_psk_id ids in
|
|
let binders_buf = assemble_list Two assemble_binder binders in
|
|
ids_buf ^ binders_buf
|
|
|
|
let assemble_alpn_protocol p =
|
|
let buf = String.make 1 (Char.unsafe_chr (String.length p)) in
|
|
buf ^ p
|
|
|
|
let assemble_alpn_protocols protocols =
|
|
assemble_list Two assemble_alpn_protocol protocols
|
|
|
|
let assemble_supported_versions vs =
|
|
assemble_list One assemble_any_protocol_version vs
|
|
|
|
let assemble_extension = function
|
|
| `SecureRenegotiation x ->
|
|
let buf = String.make 1 (Char.unsafe_chr (String.length x)) in
|
|
(buf ^ x, RENEGOTIATION_INFO)
|
|
| `ExtendedMasterSecret -> ("", EXTENDED_MASTER_SECRET)
|
|
| `ECPointFormats ->
|
|
(* a list of point formats, we support type 0 = uncompressed unconditionally *)
|
|
let data = Bytes.make 2 '\x00' in
|
|
Bytes.set_uint8 data 0 1;
|
|
(Bytes.unsafe_to_string data, EC_POINT_FORMATS)
|
|
| _ -> invalid_arg "unknown extension"
|
|
|
|
let assemble_cookie c =
|
|
let l = Bytes.create 2 in
|
|
Bytes.set_uint16_be l 0 (String.length c) ;
|
|
Bytes.unsafe_to_string l ^ c
|
|
|
|
let assemble_psk_key_exchange_mode mode =
|
|
String.make 1 (Char.unsafe_chr (psk_key_exchange_mode_to_int mode))
|
|
|
|
let assemble_psk_key_exchange_modes modes =
|
|
assemble_list One assemble_psk_key_exchange_mode modes
|
|
|
|
let assemble_ext (pay, typ) =
|
|
let buf = Bytes.create 4 in
|
|
Bytes.set_uint16_be buf 0 (extension_type_to_int typ);
|
|
Bytes.set_uint16_be buf 2 (String.length pay);
|
|
Bytes.unsafe_to_string buf ^ pay
|
|
|
|
let assemble_extensions ?none_if_empty assemble_e es =
|
|
assemble_list ?none_if_empty Two assemble_e es
|
|
|
|
let assemble_ca ca =
|
|
let lenbuf = Bytes.create 2 in
|
|
let data = X509.Distinguished_name.encode_der ca in
|
|
Bytes.set_uint16_be lenbuf 0 (String.length data) ;
|
|
Bytes.unsafe_to_string lenbuf ^ data
|
|
|
|
let assemble_certificate_authorities cas =
|
|
assemble_list Two assemble_ca cas
|
|
|
|
let assemble_certificate_request_extension e =
|
|
assemble_ext @@ match e with
|
|
| `SignatureAlgorithms s ->
|
|
(assemble_signature_algorithms s, SIGNATURE_ALGORITHMS)
|
|
| `CertificateAuthorities cas ->
|
|
(assemble_certificate_authorities cas, CERTIFICATE_AUTHORITIES)
|
|
| _ -> invalid_arg "unknown extension"
|
|
|
|
let assemble_certificate_request_1_3 ?(context = "") exts =
|
|
let clen = String.make 1 (Char.unsafe_chr (String.length context)) in
|
|
let exts = assemble_extensions assemble_certificate_request_extension exts in
|
|
String.concat "" [ clen ; context ; exts ]
|
|
|
|
let assemble_client_extension e =
|
|
assemble_ext @@ match e with
|
|
| `SupportedGroups groups ->
|
|
(assemble_supported_groups groups, SUPPORTED_GROUPS)
|
|
| `Hostname name -> (assemble_hostnames [name], SERVER_NAME)
|
|
| `Padding x -> (String.make x '\x00', PADDING)
|
|
| `SignatureAlgorithms s ->
|
|
(assemble_signature_algorithms s, SIGNATURE_ALGORITHMS)
|
|
| `ALPN protocols ->
|
|
(assemble_alpn_protocols protocols, APPLICATION_LAYER_PROTOCOL_NEGOTIATION)
|
|
| `KeyShare ks ->
|
|
(assemble_list Two assemble_keyshare_entry ks, KEY_SHARE)
|
|
| `PreSharedKeys ids ->
|
|
(assemble_client_psks ids, PRE_SHARED_KEY)
|
|
| `EarlyDataIndication ->
|
|
("", EARLY_DATA)
|
|
| `SupportedVersions vs ->
|
|
(assemble_supported_versions vs, SUPPORTED_VERSIONS)
|
|
| `PostHandshakeAuthentication ->
|
|
("", POST_HANDSHAKE_AUTH)
|
|
| `Cookie c ->
|
|
(assemble_cookie c, COOKIE)
|
|
| `PskKeyExchangeModes modes ->
|
|
(assemble_psk_key_exchange_modes modes, PSK_KEY_EXCHANGE_MODES)
|
|
| x -> assemble_extension x
|
|
|
|
let assemble_server_extension e =
|
|
assemble_ext @@ match e with
|
|
| `Hostname -> ("", SERVER_NAME)
|
|
| `ALPN protocol ->
|
|
(assemble_alpn_protocols [protocol], APPLICATION_LAYER_PROTOCOL_NEGOTIATION)
|
|
| `KeyShare (g, ks) ->
|
|
let ng = group_to_named_group g in
|
|
(assemble_keyshare_entry (ng, ks), KEY_SHARE)
|
|
| `PreSharedKey id ->
|
|
let data = Bytes.create 2 in
|
|
Bytes.set_uint16_be data 0 id ;
|
|
(Bytes.unsafe_to_string data, PRE_SHARED_KEY)
|
|
| `SelectedVersion v -> (assemble_protocol_version v, SUPPORTED_VERSIONS)
|
|
| x -> assemble_extension x
|
|
|
|
let assemble_encrypted_extension e =
|
|
assemble_ext @@ match e with
|
|
| `Hostname -> ("", SERVER_NAME)
|
|
| `ALPN protocol ->
|
|
(assemble_alpn_protocols [protocol], APPLICATION_LAYER_PROTOCOL_NEGOTIATION)
|
|
| `SupportedGroups groups ->
|
|
(assemble_supported_groups (List.map group_to_named_group groups), SUPPORTED_GROUPS)
|
|
| `EarlyDataIndication -> ("", EARLY_DATA)
|
|
| _ -> invalid_arg "unknown extension"
|
|
|
|
let assemble_retry_extension e =
|
|
assemble_ext @@ match e with
|
|
| `SelectedGroup g -> (assemble_group g, KEY_SHARE)
|
|
| `Cookie c -> (assemble_cookie c, COOKIE)
|
|
| `SelectedVersion v -> (assemble_protocol_version v, SUPPORTED_VERSIONS)
|
|
| `UnknownExtension _ -> invalid_arg "unknown retry extension"
|
|
|
|
let assemble_cert_ext (certificate, extensions) =
|
|
let cert = assemble_certificate certificate
|
|
and exts = assemble_list Two assemble_server_extension extensions
|
|
in
|
|
cert ^ exts
|
|
|
|
let assemble_certs_exts cs =
|
|
assemble_list Three assemble_cert_ext cs
|
|
|
|
let assemble_certificates_1_3 context certs =
|
|
let l = String.make 1 (Char.unsafe_chr (String.length context)) in
|
|
String.concat "" [ l ; context ; assemble_certs_exts (List.map (fun c -> c, []) certs) ]
|
|
|
|
let assemble_sid sid =
|
|
match sid with
|
|
| None -> String.make 1 '\x00'
|
|
| Some s -> String.make 1 (Char.unsafe_chr (String.length s)) ^ s
|
|
|
|
let assemble_client_hello (cl : client_hello) : string =
|
|
let version = match cl.client_version with
|
|
| `TLS_1_3 -> `TLS_1_2 (* keep 0x03 0x03 on wire *)
|
|
| x -> x
|
|
in
|
|
let v = assemble_any_protocol_version version in
|
|
let sid = assemble_sid cl.sessionid in
|
|
let css = assemble_any_ciphersuites cl.ciphersuites in
|
|
(* compression methods, completely useless *)
|
|
let cms = assemble_compression_methods [NULL] in
|
|
let bbuf = String.concat "" [ v ; cl.client_random ; sid ; css ; cms ] in
|
|
let extensions = assemble_extensions ~none_if_empty:true assemble_client_extension cl.extensions in
|
|
(* some widely deployed firewalls drop ClientHello messages which are
|
|
> 256 and < 511 byte, insert PADDING extension for these *)
|
|
(* from draft-ietf-tls-padding-00:
|
|
As an example, consider a client that wishes to avoid sending a
|
|
ClientHello with a record size between 256 and 511 bytes (inclusive).
|
|
This case is considered because at least one TLS implementation is
|
|
known to hang the connection when such a ClientHello record is
|
|
received.
|
|
|
|
After building a ClientHello as normal, the client can add four to
|
|
the length (to account for the "msg_type" and "length" fields of the
|
|
handshake protocol) and test whether the resulting length falls into
|
|
that range. If it does, a padding extension can be added in order to
|
|
push the length to (at least) 512 bytes. *)
|
|
let extrapadding =
|
|
(* since PreSharedKeys _must_ be the last extension, don't bother padding
|
|
when it is present. rationale from ietf-tls WG
|
|
"Padding extension and 0-RTT" thread (2016-10-30) *)
|
|
if List.exists (function `PreSharedKeys _ -> true | _ -> false) cl.extensions then
|
|
""
|
|
else
|
|
let buflen = String.length bbuf + String.length extensions + 4 (* see above, header *) in
|
|
if buflen >= 256 && buflen <= 511 then
|
|
match String.length extensions with
|
|
| 0 -> (* need to construct a 2 byte extension length as well *)
|
|
let l = 512 (* desired length *) - 2 (* extension length *) - 4 (* padding extension header *) - buflen in
|
|
let l = max l 0 in (* negative size is not good *)
|
|
let padding = assemble_client_extension (`Padding l) in
|
|
let extension_length = Bytes.create 2 in
|
|
Bytes.set_uint16_be extension_length 0 (String.length padding);
|
|
Bytes.unsafe_to_string extension_length ^ padding
|
|
| _ ->
|
|
let l = 512 - 4 (* padding extension header *) - buflen in
|
|
let l = max l 0 in
|
|
let padding = assemble_client_extension (`Padding l) in
|
|
(* extensions include the 16 bit extension length field *)
|
|
let elen = String.length extensions + String.length padding - 2 (* the 16 bit length field *) in
|
|
Bytes.set_uint16_be (Bytes.unsafe_of_string extensions) 0 elen;
|
|
padding
|
|
else
|
|
""
|
|
in
|
|
String.concat "" [ bbuf ; extensions ; extrapadding ]
|
|
|
|
let assemble_server_hello (sh : server_hello) : string =
|
|
let version, exts = match sh.server_version with
|
|
| `TLS_1_3 -> `TLS_1_2, `SelectedVersion `TLS_1_3 :: sh.extensions
|
|
| x -> x, sh.extensions
|
|
in
|
|
let v = assemble_protocol_version version in
|
|
let sid = assemble_sid sh.sessionid in
|
|
let cs = assemble_ciphersuite sh.ciphersuite in
|
|
(* useless compression method *)
|
|
let cm = assemble_compression_method NULL in
|
|
let extensions = assemble_extensions ~none_if_empty:true assemble_server_extension exts in
|
|
String.concat "" [ v ; sh.server_random ; sid ; cs ; cm ; extensions ]
|
|
|
|
let assemble_dh_parameters p =
|
|
let plen, glen, yslen = (String.length p.dh_p, String.length p.dh_g, String.length p.dh_Ys) in
|
|
let buf = Bytes.create (2 + 2 + 2 + plen + glen + yslen) in
|
|
Bytes.set_uint16_be buf 0 plen;
|
|
Bytes.blit_string p.dh_p 0 buf 2 plen;
|
|
Bytes.set_uint16_be buf (2 + plen) glen;
|
|
Bytes.blit_string p.dh_g 0 buf (4 + plen) glen;
|
|
Bytes.set_uint16_be buf (4 + plen + glen) yslen;
|
|
Bytes.blit_string p.dh_Ys 0 buf (6 + plen + glen) yslen;
|
|
Bytes.unsafe_to_string buf
|
|
|
|
let assemble_ec_parameters named_curve point =
|
|
let hdr = Bytes.create 4 in
|
|
Bytes.set_uint8 hdr 0 (ec_curve_type_to_int NAMED_CURVE);
|
|
Bytes.set_uint16_be hdr 1 (named_group_to_int (group_to_named_group named_curve));
|
|
Bytes.set_uint8 hdr 3 (String.length point);
|
|
Bytes.unsafe_to_string hdr ^ point
|
|
|
|
let assemble_digitally_signed signature =
|
|
let lenbuf = Bytes.create 2 in
|
|
Bytes.set_uint16_be lenbuf 0 (String.length signature);
|
|
Bytes.unsafe_to_string lenbuf ^ signature
|
|
|
|
let assemble_digitally_signed_1_2 sigalg signature =
|
|
(assemble_hash_signature sigalg) ^ (assemble_digitally_signed signature)
|
|
|
|
let assemble_session_ticket_extension e =
|
|
assemble_ext @@ match e with
|
|
| `EarlyDataIndication max ->
|
|
let buf = Bytes.create 4 in
|
|
Bytes.set_int32_be buf 0 max ;
|
|
(Bytes.unsafe_to_string buf, EARLY_DATA)
|
|
| _ -> invalid_arg "unknown extension"
|
|
|
|
let assemble_session_ticket (se : session_ticket) =
|
|
let buf = Bytes.create 9 in
|
|
Bytes.set_int32_be buf 0 se.lifetime ;
|
|
Bytes.set_int32_be buf 4 se.age_add ;
|
|
Bytes.set_uint8 buf 8 (String.length se.nonce) ;
|
|
let ticketlen = Bytes.create 2 in
|
|
Bytes.set_uint16_be ticketlen 0 (String.length se.ticket) ;
|
|
let exts = assemble_extensions assemble_session_ticket_extension se.extensions in
|
|
String.concat "" [ Bytes.unsafe_to_string buf ; se.nonce ; Bytes.unsafe_to_string ticketlen ; se.ticket ; exts ]
|
|
|
|
let assemble_client_dh_key_exchange kex =
|
|
let len = String.length kex in
|
|
let buf = Bytes.create (len + 2) in
|
|
Bytes.set_uint16_be buf 0 len;
|
|
Bytes.blit_string kex 0 buf 2 len;
|
|
Bytes.unsafe_to_string buf
|
|
|
|
let assemble_client_ec_key_exchange kex =
|
|
let len = String.length kex in
|
|
let buf = Bytes.create (len + 1) in
|
|
Bytes.set_uint8 buf 0 len;
|
|
Bytes.blit_string kex 0 buf 1 len;
|
|
Bytes.unsafe_to_string buf
|
|
|
|
let assemble_hello_retry_request hrr =
|
|
let exts = `SelectedGroup hrr.selected_group :: hrr.extensions in
|
|
let version, exts = match hrr.retry_version with
|
|
| `TLS_1_3 -> `TLS_1_2, `SelectedVersion `TLS_1_3 :: exts
|
|
| x -> x, exts
|
|
in
|
|
let v = assemble_protocol_version version in
|
|
let sid = assemble_sid hrr.sessionid in
|
|
let cs = assemble_ciphersuite (hrr.ciphersuite :> Ciphersuite.ciphersuite) in
|
|
(* useless compression method *)
|
|
let cm = String.make 1 '\x00' in
|
|
let extensions = assemble_extensions ~none_if_empty:true assemble_retry_extension exts in
|
|
String.concat "" [ v ; helloretryrequest ; sid ; cs ; cm ; extensions ]
|
|
|
|
let assemble_hs typ len =
|
|
let buf = Bytes.create 4 in
|
|
Bytes.set_uint8 buf 0 (handshake_type_to_int typ);
|
|
set_uint24_len ~off:1 buf len;
|
|
Bytes.unsafe_to_string buf
|
|
|
|
let assemble_message_hash len =
|
|
assemble_hs MESSAGE_HASH len
|
|
|
|
let assemble_key_update req =
|
|
String.make 1 (Char.unsafe_chr (key_update_request_type_to_int req))
|
|
|
|
let assemble_handshake hs =
|
|
let (payload, payload_type) =
|
|
match hs with
|
|
| ClientHello ch -> (assemble_client_hello ch, CLIENT_HELLO)
|
|
| ServerHello sh -> (assemble_server_hello sh, SERVER_HELLO)
|
|
| HelloRetryRequest hr -> (assemble_hello_retry_request hr, SERVER_HELLO)
|
|
| Certificate cs -> (cs, CERTIFICATE)
|
|
| CertificateRequest cr -> (cr, CERTIFICATE_REQUEST)
|
|
| CertificateVerify c -> (c, CERTIFICATE_VERIFY)
|
|
| ServerKeyExchange kex -> (kex, SERVER_KEY_EXCHANGE)
|
|
| ClientKeyExchange kex -> (kex, CLIENT_KEY_EXCHANGE)
|
|
| ServerHelloDone -> ("", SERVER_HELLO_DONE)
|
|
| HelloRequest -> ("", HELLO_REQUEST)
|
|
| Finished fs -> (fs, FINISHED)
|
|
| SessionTicket st -> (assemble_session_ticket st, SESSION_TICKET)
|
|
| EncryptedExtensions ee ->
|
|
let cs = assemble_extensions assemble_encrypted_extension ee in
|
|
(cs, ENCRYPTED_EXTENSIONS)
|
|
| KeyUpdate req ->
|
|
let cs = assemble_key_update req in
|
|
(cs, KEY_UPDATE)
|
|
| EndOfEarlyData -> ("", END_OF_EARLY_DATA)
|
|
in
|
|
let pay_len = String.length payload in
|
|
let buf = assemble_hs payload_type pay_len in
|
|
buf ^ payload
|
|
|
|
let assemble_alert ?(level = Packet.FATAL) typ =
|
|
let buf = Bytes.create 2 in
|
|
Bytes.set_uint8 buf 1 (alert_type_to_int typ);
|
|
Bytes.set_uint8 buf 0 (alert_level_to_int level) ;
|
|
Bytes.unsafe_to_string buf
|
|
|
|
let assemble_change_cipher_spec =
|
|
String.make 1 '\x01'
|