Adding cert to Cohttp_lwt_unix client?

i am just two days on Ocaml…
i want to query two different rest-services and match the results.
First one is done :slight_smile:
The second one is missing something in the cert chain

openssl says
Verify return code: 21 (unable to verify the first certificate)

In java i can add the servers cert to a truststore, is something like that available Cohttp_lwt_unix?

It seems i can create a ssl_config with something like this…
open Tls.Config
let ssl_config = (Tls.Config.client…
but how to wire it up?
A bit of an example would be nice, since i am scratching my head over the docs for several hours :wink:

Btw. i can query with the curly package so a not so strict certification check would also do…

Ok unless i missed something this seems not work…

utop # match p with
|Ok cert -> Tls.Config.client ~certificates:(`Single cert)
| Error (`Msg msg) ->
        failwith ("Failed to load certificate: " ^ msg);;
Error: This expression has type X509.Certificate.t
       but an expression was expected of type
         Tls.Config.certchain = X509.Certificate.t list * X509.Private_key.t

Since i have no private key for this…

Someone has a suggestion for a http client lib where this is possible?

A certificate is by definition public. Anyone can get, let’s say, the certificate. If you want to have an HTTPS server that authenticates itself, you MUST have the associated private key. This private key should be generated with your certificate by your CA (certificate authority). (You may have more complex protocol where you generate a private key with a certificate request, then the CA gives you the corresponding certificate… but you still have a private key)

The OCaml expression which gives you this key is typically:

read_file "key.pem" |> X509.Private_key.decode_pem

I have an Apache server with certificates/keys provided by Letsencrypt. The /etc/letsencrypt/live/__my_host__/ contains fullchain.pem, the certificate, but also privkey.pem, the private key.

I note a certificate LIST is expected. I don’t now if a list with just your certificate would be enough. A certificate is already a chain of certification from a root certificate to the “subject” of the certificate. The fullchain.pem from Letsencrypt is such a certificate. But Letsencrypt also provides partial certificates like cert.pem which need to be completed by an intermediate certificate.

And if you generate a self-signed certificate by openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 365 -out certificate.pem, you have the key in a key.pem file.

TL;DR… you should have a private key somewhere…

Hi Frederic,
thanks for the explanation!
Maybe i confuse something or didn’t get the TLS to the full point…

I am on the client side…
In my understanding i should obtain the public part with openssl or from the browser and tell the client to trust this.
Let’s say the server i want to query is (with that improper cert)… i would never get the private key…

What i did in java btw. clojure downloaded the public cert and imported it into a keystore.

openssl s_client -connect server:443 2>&1 </dev/null | sed -ne '/BEGIN CERT/,/END CERT/p' | openssl x509 -outform der > server.der
keytool -import -noprompt -alias Server -keystore $KEYSTORE -storepass mypass -file server.der

For go i can do something like

tr := &http.Transport{
        TLSClientConfig: &tls.Config{InsecureSkipVerify: true},

which is maybe how curl works, just a guess since i can query with curl.
Since i have no experience in ocaml i tried everything on my hands :wink:

I would prefer to have a proper cert on the server… but that is not in my hands…

Still i think one of these approaches is available in ocaml.

Maybe the

Tls.Config.client ~certificates ...

Is just the wrong place to look at ?

Ah! Sorry, sure for a client, we should not always need a certificate/key pair.

I have read the following snippet in file in the root directory of Tls sources.

let main host port =
  lwt authenticator = X509_lwt.authenticator (`Ca_dir nss_trusted_ca_dir) in
  lwt (ic, oc)      = Tls_lwt.connect ~authenticator (host, port) in
  let req = String.concat "\r\n" [
    "GET / HTTP/1.1" ; "Host: " ^ host ; "Connection: close" ; "" ; ""
  ] in
  Lwt_io.(write oc req >>= fun () -> read ic >>= print)

I guess that you can control server certificates trust with this parameter. There are also other variants: `Ca_file, `Key_fingerprint and some other.

But I have no clue about Cohttp, nor Tls.Config.client.

Thank you very much for this example!
With some minor tweaks i got it running

These drove me crazy
lwt authenticator
thought that it was a typo and should be let :open_mouth:

So for other total newcomers to ocaml here is the updated code
Adding this to dune file…
(pps lwt_ppx))

this works

open Lwt
let main host port =
  let%lwt authenticator = X509_lwt.authenticator (`Ca_file "/dir/test.pem") in
  let%lwt (ic, oc)      = Tls_lwt.connect authenticator  (host, port) in
  let req = String.concat "\r\n" [
    "GET / HTTP/1.1" ; "Host: " ^ host ; "Connection: close" ; "" ; ""
  ] in
  Lwt_io.(write oc req >>= fun () -> read ic >>= print)

let () =
  let host = "" in
  let port = 443 in (main host port)

Additionally i had to dig up the root cert and put to the downloaded one to get a full chain…

root cert

Next thing is to hook this to the cohttp client… i don’t want to do the http protocoll :slight_smile:

After reading this

i was a bit discouraged

So i looked into Http_lwt_client

This seems to work with

let httpcall2 =
  let%lwt authenticator = X509_lwt.authenticator (`Ca_file "/.../test.pem") in
  let reply, body = response_accumulator () in
  let%lwt _ = Http_lwt_client.request ~authenticator:authenticator ~meth:`GET "https://myserver" reply () in
  Lwt.return (reply,body)

Got only DNS timeouts with this lib yesterday while the Tls_lwt.connect still worked, today it magically works…

1 Like

We know that we currently have an outcome that may be more about happy-eyeballs. If you could do an issue on this so we can reproduce and try to find a solution :slight_smile: .

:slight_smile: Yes i found an Semgrep article about timeouts with happy-eyeballs…

If i have something reproducable i will open an issue…
Yesterday i tried
https://myserver - timeout - timeout

i am not certain if tried google and it worked… will try this later…

But as i said today it works fine… i am in the office now… better internet connection?.. different DNS

Yes, it depends on the DNS resolver used, its cache, and of course the speed between you and the resolver. “A better connection” may indeed solve the problem :slight_smile: . That’s why we want to have some concrete cases, because we suspect our implementation has a bug.

Do you have anything in mind that i can provide…

did a

;; Query time: 6 msec

right now
will do the same at home…

FWIW http-lwt-client (or really the underlying happy-eyeballs implementation) does not work well with the DNS resolver on my home router. The router listens on tcp port 53 but does not respond. This is an annoying failure mode that could be handled more nicely. I will open an issue on that.