package tiny_libs

  1. Overview
  2. Docs
From-scratch libraries for teaching: graphics, audio, compression, crypto, networking and more

Install

dune-project
 Dependency

Authors

Maintainers

Sources

0.3.6.tar.gz
md5=7c636383d146d30ac6f2fa234a6253c8
sha512=c79f3823c5f8f57e5038eb640d487c61168b84aa07c61999d6622ef9fd0c890e2b03b4c6a7cdbbe9352a49e25dda00ac7bb14693cee8e3d7beeed251351a2af0

doc/src/tiny_libs.networking_unix/Tls_client.ml.html

Source file Tls_client.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
(* Claude Code
 *
 * Copyright (C) 2026 Yoann Padioleau
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public License
 * (LGPL) as published by the Free Software Foundation; either version
 * 2 of the License, or (at your option) any later version.
 *)

(* See Tls_client.mli *)

let bundles = [ "/etc/ssl/certs/ca-certificates.crt"; "/etc/pki/tls/certs/ca-bundle.crt"; "/etc/ssl/cert.pem"; "/etc/ssl/ca-bundle.pem" ]
let roots : X509.t list option ref = ref None

(* the roots and the randomness, read under the network's authority
   (Tls_client.mli says why) *)
let read_file (path : string) : string = In_channel.with_open_bin path In_channel.input_all

let system_roots (_ : < Cap.network ; .. >) : X509.t list =
  match !roots with
  | Some r -> r
  | None ->
      let r =
        match List.find_opt Sys.file_exists bundles with
        | Some path -> List.filter_map (fun der -> Result.to_option (X509.parse der)) (Pem.certificates (read_file path))
        | None -> []
      in
      roots := Some r;
      r

(* randomness from the kernel *)
let random (n : int) : string = In_channel.with_open_bin "/dev/urandom" (fun ic -> really_input_string ic n)

(* The chains already checked in this program -- the host and the
   certificates' bytes -- until the first of them expires: a page's
   twenty pictures from one host are one chain checked, not twenty
   (three signatures each, 30 ms apiece). The handshake's own signature,
   CertificateVerify, is checked every time: it proves the server has
   the key now. A mutex, since Commands fetches on several threads. *)
let verified : (string, float) Hashtbl.t = Hashtbl.create 16
let verified_lock = Mutex.create ()

let locked (f : unit -> 'a) : 'a =
  Mutex.lock verified_lock;
  Fun.protect ~finally:(fun () -> Mutex.unlock verified_lock) f

let verify_cached ~(trust : X509.t list) ~(host : string) (chain : X509.t list) : (unit, string) result =
  let now = Unix.gettimeofday () in
  let key = host ^ "\000" ^ String.concat "" (List.map (fun (c : X509.t) -> c.der) chain) in
  let known = locked (fun () -> Hashtbl.find_opt verified key) in
  match known with
  | Some until when now <= until -> Ok ()
  | _ ->
      let r = X509.verify ~trust ~now ~host chain in
      if r = Ok () then (
        let until = List.fold_left (fun t (c : X509.t) -> min t c.not_after) infinity chain in
        locked (fun () -> Hashtbl.replace verified key until));
      r

type t = {
  fd : Unix.file_descr;
  host : string;
  mutable machine : Tls13.t;
  mutable outbox : string; (* application data waiting for the handshake *)
  mutable eof : bool;
  mutable closed : bool;
}

let write (t : t) (bytes : string) : unit = if bytes <> "" && not t.closed then try Tcp.send_all t.fd bytes with Unix.Unix_error _ -> t.eof <- true

let connect (caps : < Cap.network ; .. >) ?trust ~(host : string) ~(port : int) () : (t, string) result =
  let trust = match trust with Some t -> t | None -> system_roots caps in
  match Tcp.connect caps ~host ~port () with
  | exception Unix.Unix_error (e, _, _) -> Error (Printf.sprintf "can't reach %s:%d: %s" host port (Unix.error_message e))
  | exception Failure why -> Error why
  | fd ->
      let r = random 96 in
      let verify chain = verify_cached ~trust ~host chain in
      let machine, hello = Tls13.client ~host ~random:(String.sub r 0 32) ~secret:(String.sub r 32 32) ~session_id:(String.sub r 64 32) ~verify in
      let t = { fd; host; machine; outbox = ""; eof = false; closed = false } in
      write t hello;
      Unix.set_nonblock fd;
      Ok t

let step (t : t) : unit =
  if not (t.eof || t.closed) then begin
    let buf = Bytes.create 65536 in
    let rec read acc =
      match Unix.read t.fd buf 0 (Bytes.length buf) with
      | 0 ->
          t.eof <- true;
          acc
      | n -> read (acc ^ Bytes.sub_string buf 0 n)
      | exception Unix.Unix_error ((Unix.EAGAIN | Unix.EWOULDBLOCK), _, _) -> acc
      | exception Unix.Unix_error _ ->
          t.eof <- true;
          acc
    in
    let bytes = read "" in
    if bytes <> "" then begin
      let m, answer = Tls13.received t.machine bytes in
      t.machine <- m;
      write t answer
    end;
    (* the handshake done: what was queued *)
    if Tls13.state t.machine = Open && t.outbox <> "" then begin
      let m, records = Tls13.write t.machine t.outbox in
      t.machine <- m;
      t.outbox <- "";
      write t records
    end
  end

let state (t : t) : Tls13.state =
  match Tls13.state t.machine with Handshaking when t.eof -> Failed "the connection closed during the handshake" | s -> s

let machine (t : t) : Tls13.t = t.machine

let send (t : t) (data : string) : unit =
  t.outbox <- t.outbox ^ data;
  step t

let receive (t : t) : string =
  step t;
  let m, data = Tls13.read t.machine in
  t.machine <- m;
  data

let close (t : t) : unit =
  if not t.closed then begin
    let m, alert = Tls13.close t.machine in
    t.machine <- m;
    write t alert;
    t.closed <- true;
    try Unix.close t.fd with Unix.Unix_error _ -> ()
  end

let lines (t : t) : Transport.t =
  let partial = ref "" in
  let receive () =
    let all = !partial ^ receive t in
    match String.rindex_opt all '\n' with
    | None ->
        partial := all;
        []
    | Some i ->
        partial := String.sub all (i + 1) (String.length all - i - 1);
        String.split_on_char '\n' (String.sub all 0 i) |> List.map (fun l -> if l <> "" && l.[String.length l - 1] = '\r' then String.sub l 0 (String.length l - 1) else l)
  in
  let status () =
    match state t with
    | Handshaking -> "TLS 1.3 handshake with " ^ t.host
    | Open -> Printf.sprintf "TLS 1.3 to %s (%s)" t.host (match Tls13.cipher t.machine with Some Chacha20_poly1305 -> "ChaCha20-Poly1305" | _ -> "AES-128-GCM")
    | Closed -> t.host ^ ": closed"
    | Failed why -> t.host ^ ": " ^ why
  in
  { Transport.send = (fun line -> send t (line ^ "\r\n")); receive; status; player = (fun () -> None) }

let connect_lines (caps : < Cap.network ; .. >) ~(host : string) ~(port : int) : (Transport.t, string) result =
  Result.map lines (connect caps ~host ~port ())

let exchange ?trust ?(timeout = 30.) (caps : < Cap.network ; .. >) ~(host : string) ~(port : int) (request : string) : (string, string) result =
  match connect caps ?trust ~host ~port () with
  | Error e -> Error e
  | Ok t ->
      send t request;
      let answer = Buffer.create 65536 in
      let rec wait silent =
        match state t with
        | Failed why -> Error why
        | Closed -> Ok ()
        | _ when t.eof -> Ok ()
        | _ when silent > timeout -> Error (Printf.sprintf "%s: no answer in %.0f s" host timeout)
        | _ ->
            let got = receive t in
            Buffer.add_string answer got;
            if got = "" then (
              ignore (Unix.select [ t.fd ] [] [] 0.05);
              wait (silent +. 0.05))
            else wait 0.
      in
      let r = wait 0. in
      Buffer.add_string answer (receive t);
      close t;
      Result.map (fun () -> Buffer.contents answer) r