package awa

  1. Overview
  2. Docs

Source file keys.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
open Util

let src = Logs.Src.create "awa.authenticator" ~doc:"AWA authenticator"
module Log = (val Logs.src_log src : Logs.LOG)

type typ = [ `Rsa | `Ed25519 ]

let typ_of_string s =
  match String.lowercase_ascii s with
  | "rsa" -> Ok `Rsa
  | "ed25519" -> Ok `Ed25519
  | _ -> Error ("unknown key type " ^ s)

type authenticator = [
  | `No_authentication
  | `Key of Hostkey.pub
  | `Fingerprint of typ * string
]

let hostkey_matches a key =
  match a with
  | `No_authentication ->
    Log.warn (fun m -> m "NO AUTHENTICATOR");
    true
  | `Key pub' ->
    if key = pub' then begin
      Log.app (fun m -> m "host key verification successful!");
      true
    end else begin
      Log.err (fun m -> m "host key verification failed");
      false
    end
  | `Fingerprint (typ, s) ->
    let hash = Mirage_crypto.Hash.SHA256.digest (Wire.blob_of_pubkey key) in
    Log.app (fun m -> m "authenticating server fingerprint SHA256:%s"
                (Base64.encode_string ~pad:false (Cstruct.to_string hash)));
    let typ_matches = match typ, key with
      | `Ed25519, Hostkey.Ed25519_pub _ -> true
      | `Rsa, Hostkey.Rsa_pub _ -> true
      | _ -> false
    and fp_matches = Cstruct.(equal (of_string s) hash)
    in
    if typ_matches && fp_matches then begin
      Log.app (fun m -> m "host fingerprint verification successful!");
      true
    end else begin
      Log.err (fun m -> m "host fingerprint verification failed");
      false
    end

let authenticator_of_string str =
  if str = "" then
    Ok `No_authentication
  else
    match String.split_on_char ':' str with
    | [ y ; fp ] ->
      let* t =
        match y with
        | "SHA256" -> Ok `Rsa
        | y -> typ_of_string y
      in
      begin match Base64.decode ~pad:false fp with
        | Error (`Msg m) ->
          Error ("invalid authenticator (bad b64 in fingerprint): " ^ m)
        | Ok fp -> Ok (`Fingerprint (t, fp))
      end
    | _ ->
      match Base64.decode ~pad:false str with
      | Ok k ->
        let* key = Wire.pubkey_of_blob (Cstruct.of_string k) in
        Ok (`Key key)
      | Error (`Msg msg) ->
        Error (str ^ " is invalid or unsupported authenticator, b64 failed: " ^ msg)

let of_seed ?bits typ seed =
  let typ = match typ with `Rsa -> `RSA | `Ed25519 -> `ED25519 in
  match X509.Private_key.generate ~seed:(Cstruct.of_string seed) ?bits typ with
  | `RSA k ->
    let pub = Mirage_crypto_pk.Rsa.pub_of_priv k in
    let pubkey = Wire.blob_of_pubkey (Hostkey.Rsa_pub pub) in
    Log.info (fun m -> m "using ssh-rsa %s"
                 (Cstruct.to_string pubkey |> Base64.encode_string));
    Hostkey.Rsa_priv k
  | `ED25519 k ->
    let pub = Mirage_crypto_ec.Ed25519.pub_of_priv k in
    let pubkey = Wire.blob_of_pubkey (Hostkey.Ed25519_pub pub) in
    Log.info (fun m -> m "using ssh-ed25519 %s"
                 (Cstruct.to_string pubkey |> Base64.encode_string));
    Hostkey.Ed25519_priv k
  | _ -> assert false (* XXX(dinosaure): should never occur, may be a GADT is needed here! *)

let of_string str =
  match String.split_on_char ':' str with
  | [ typ; data; ] ->
    let* typ = Result.map_error (fun m -> `Msg m) (typ_of_string typ) in
    let typ = match typ with `Rsa -> `RSA | `Ed25519 -> `ED25519 in
    let* res = X509.Private_key.of_string typ data in
    (match res with
     | `RSA k -> Ok (Hostkey.Rsa_priv k)
     | `ED25519 k -> Ok (Hostkey.Ed25519_priv k)
     | _ -> assert false)
  | _ -> Error (`Msg "Invalid SSH key format (type:key)")
OCaml

Innovation. Community. Security.