Code Snippet

pem.fsx

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
#!/usr/bin/env -S dotnet fsi --langversion:8.0 --optimize --warnaserror+:25,26

#nowarn "60" 
(* Override implementations in augmentations are now deprecated. Override
   implementations should be given as part of the initial declaration of a type.
*)

#time "on"

[<RequireQualifiedAccess>]
module HTTP =
  
  [<RequireQualifiedAccess>]
  module PEM =
    
    open System
    open System.Net.Http
    open System.Net.Security
    open System.Security.Cryptography.X509Certificates
    open System.Threading
    
    let get (domain:string) =
      
      let mutable b64 = String.Empty
      let         sem = ref 0
      
      use h = new HttpClientHandler()
      h.ServerCertificateCustomValidationCallback <-
        ( fun _ cert _ errs ->
            (* Update mutable value with base64 encoded certificate *)
            b64 <-
              Convert.ToBase64String
                ( cert.Export(X509ContentType.Cert)
                , Base64FormattingOptions.None
                )
            (* Set semaphore to ONE *)
            let _ = Interlocked.Exchange(sem, 1)
            errs = SslPolicyErrors.None
        )
      
      use web = new HttpClient(handler = h)
    
      let rec wait () =
        async {
          (* Wait 10 milliseconds and check if semaphore is ONE *)
          do! Async.Sleep(10)
          (* Lock and check for value not ZERO *)
          if (0 = Interlocked.Exchange(sem, 1)) then
            (* If still ZERO, unlock and release inmediatly *)
            let _ = Interlocked.Exchange(sem, 0)
            return! wait ()
          else
            (* Once value is set to ONE by other thread
               we are done *)
            return ()
        }
      async {
        let! _ =
          (* Make a HTTPS HEAD request to the provided to trigger the
             `ServerCertificateCustomValidationCallback` and wait for it to
             update the mutable b64 value *)
          web.SendAsync
            ( new HttpRequestMessage
                ( HttpMethod.Head
                , sprintf "https://%s" domain
                )
            )
          |> Async.AwaitTask
        return! wait ()
      }
      |> Async.RunSynchronously
      
      (* Once the b64 mutable value has been updated, return the PEM base64
         encoded in lines of 64 chars pre- and post- fixed with BEGIN and END
         tags *)
      let ls =
        seq {
          yield "-----BEGIN CERTIFICATE-----"
          yield 
            ( b64
              |> Seq.chunkBySize 64
              |> Seq.map (Array.map string >> Array.reduce (+))
              |> Seq.reduce (
                fun x y ->
                  sprintf "%s%s%s" x Environment.NewLine y
              )
            )
          yield "-----END CERTIFICATE-----"
        }
      String.Join
        ( separator = Environment.NewLine
        , values = ls
        )

let _ =

  "spisemisu.com"
  |> HTTP.PEM.get
  |> printfn "%s"

  00

Code Output:

[nix-shell:~/code/dotnet/src/pem]$ clear && ./pem.fsx

-----BEGIN CERTIFICATE-----
MIIDqDCCA06gAwIBAgIQb5sV31LPBGwR6vbVBwcVOzAKBggqhkjOPQQDAjA7MQsw
CQYDVQQGEwJVUzEeMBwGA1UEChMVR29vZ2xlIFRydXN0IFNlcnZpY2VzMQwwCgYD
VQQDEwNXRTEwHhcNMjQxMjA2MTUzNjUyWhcNMjUwMzA2MTUzNjUxWjAYMRYwFAYD
VQQDEw1zcGlzZW1pc3UuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuxDf
LBBZlqlIruPTHUXDAt2yMly+l767rbvyarHqn09paAyzLE8Bv0F7GClZUROqlbTx
XgHOq0I9KjwGTCAbfaOCAlUwggJRMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAK
BggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBSRYZdKsjfcNMrGoVbo
9oDiBHeQHTAfBgNVHSMEGDAWgBSQd5I1Z8T/qMyp5nvZgHl7zJP5ODBeBggrBgEF
BQcBAQRSMFAwJwYIKwYBBQUHMAGGG2h0dHA6Ly9vLnBraS5nb29nL3Mvd2UxL2I1
czAlBggrBgEFBQcwAoYZaHR0cDovL2kucGtpLmdvb2cvd2UxLmNydDApBgNVHREE
IjAggg1zcGlzZW1pc3UuY29tgg8qLnNwaXNlbWlzdS5jb20wEwYDVR0gBAwwCjAI
BgZngQwBAgEwNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2MucGtpLmdvb2cvd2Ux
L18tNGlGd2ZDYWNNLmNybDCCAQIGCisGAQQB1nkCBAIEgfMEgfAA7gB1AM8RVu7V
Lnyv84db2Wkum+kacWdKsBfsrAHSW3fOzDsIAAABk5zU0XQAAAQDAEYwRAIgPDZn
e/Gzv1XXd+FRsgu96YkG+qvU51u9W7qLySuTOSUCICHpgSzoAH8R9T5yEw+L0/q1
dvtTZPTfbwzBVODfxHSPAHUAcyAiDwgWivnzxKaLCrJqmkoA7vV3hYoITQUA1KVC
RFkAAAGTnNTRYQAABAMARjBEAiBvY0fgy0MaqEDn+s/cIODmHnfb7aOc1CDdYkGQ
Pe+svQIgbS4NIw4BkAY95aTL/woPjVg3CNxBIf9ribIOcc+hgCwwCgYIKoZIzj0E
AwIDSAAwRQIgHGX9TRYSXyQldrlT79b8xXkDvvZdgrkDgv7m7nVoNyICIQDKX9BE
AXbgiwus5KqRfCo1ltBwfj1QyCqn54NcEbDtRw==
-----END CERTIFICATE-----
Real: 00:00:00.729, CPU: 00:00:00.139, GC gen0: 0, gen1: 0, gen2: 0

[nix-shell:~/code/dotnet/src/pem]$

References: