package u2f

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file u2f.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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
type t = {
  version : string ;
  application_id : string ;
}

let create application_id =
  { version = "U2F_V2" ; application_id }

type protocol_error =
  [ `Other_error | `Bad_request | `Configuration_unsupported
  | `Device_ineligible | `Timeout | `Unrecognized of int ]

let pp_protocol_error ppf = function
  | `Other_error -> Format.pp_print_string ppf "other error"
  | `Bad_request -> Format.pp_print_string ppf "bad request"
  | `Configuration_unsupported ->
    Format.pp_print_string ppf "configuration unsupported"
  | `Device_ineligible ->
    Format.pp_print_string ppf "device ineligible"
  | `Timeout -> Format.pp_print_string ppf "timeout reached"
  | `Unrecognized n -> Format.fprintf ppf "unrecognized %d" n

type error = [
  | `Protocol of protocol_error
  | `Json_decoding of string * string * string
  | `Base64_decoding of string * string * string
  | `Binary_decoding of string * string * string
  | `Version_mismatch of string * string
  | `Typ_mismatch of string * string
  | `Challenge_mismatch of string * string
  | `Unknown_key_handle of string
  | `Signature_verification of string
  | `Origin_mismatch of string * string
]

let pp_error ppf = function
  | `Protocol p -> pp_protocol_error ppf p
  | `Json_decoding (name, err, value) ->
    Format.fprintf ppf "json decoding of %s failed with %S (input %S)"
      name err value
  | `Base64_decoding (name, err, value) ->
    Format.fprintf ppf "base64 decoding of %s failed with %S (input %S)"
      name err value
  | `Binary_decoding (name, err, value) ->
    Format.fprintf ppf "binary decoding of %s failed with %S (input %a)"
      name err Cstruct.hexdump_pp (Cstruct.of_string value)
  | `Version_mismatch (expected, received) ->
    Format.fprintf ppf "version mismatch, expected %S, received %S"
      expected received
  | `Typ_mismatch (expected, received) ->
    Format.fprintf ppf "typ mismatch, expected %S, received %S"
      expected received
  | `Challenge_mismatch (expected, received) ->
    Format.fprintf ppf "challenge mismatch, expected %S, received %S"
      expected received
  | `Unknown_key_handle received ->
    Format.fprintf ppf "unknown key handle %S" received
  | `Signature_verification msg ->
    Format.fprintf ppf "signature verification failed %s" msg
  | `Origin_mismatch (expected, received) ->
    Format.fprintf ppf "origin mismatch, expected %S, received %S"
      expected received

type challenge = string

type key_handle = string

let b64_enc = Base64.(encode_string ~pad:false ~alphabet:uri_safe_alphabet)

let lift_err f = function Ok _ as a -> a | Error x -> Error (f x)

let b64_dec thing s =
  lift_err
    (function `Msg m -> `Base64_decoding (thing, m, s))
    Base64.(decode ~pad:false ~alphabet:uri_safe_alphabet s)

type register_request = {
  version : string ;
  challenge : string ;
} [@@deriving yojson]

type registered_key = {
  version : string ;
  keyHandle : string ;
} [@@deriving yojson]

type u2f_register_request = {
  appId : string ;
  registerRequests : register_request list ;
  registeredKeys : registered_key list ;
} [@@deriving yojson]

let challenge () =
  let random = Cstruct.to_string (Mirage_crypto_rng.generate 32) in
  b64_enc random

let register_request ?(key_handles = []) { version ; application_id } =
  let challenge = challenge () in
  let reg_req = {
    appId = application_id ;
    registerRequests = [ { version ; challenge } ] ;
    registeredKeys = List.map (fun keyHandle -> { version ; keyHandle }) key_handles
  } in
  challenge,
  Yojson.Safe.to_string (u2f_register_request_to_yojson reg_req)

let res_typ_to_string = function
  | `Sign -> "navigator.id.getAssertion"
  | `Register -> "navigator.id.finishEnrollment"

let res_typ = function
  | "navigator.id.getAssertion" -> Ok `Sign
  | "navigator.id.finishEnrollment" -> Ok `Register
  | x -> Error (`Msg ("unknown type " ^ x))

type clientData = {
  challenge : string ;
  origin : string ;
  typ : string ;
} [@@deriving yojson]

let error_code_of_int = function
  | 0 -> Ok ()
  | 1 -> Error `Other_error
  | 2 -> Error `Bad_request
  | 3 -> Error `Configuration_unsupported
  | 4 -> Error `Device_ineligible
  | 5 -> Error `Timeout
  | n -> Error (`Unrecognized n)

type u2f_register_response = {
  clientData : string ;
  errorCode : int ;
  registrationData : string ;
  version : string ;
} [@@deriving yojson]

let (>>=) v f = match v with Ok v -> f v | Error _ as e -> e

let guard p e = if p then Ok () else Error e

(* manually extract the certificate length to split <cert> <signature> *)
let seq_len cs =
  let open Rresult.R.Infix in
  guard (Cstruct.get_uint8 cs 0 = 0x30)
    (`Msg "Certificate is not an ASN.1 sequence") >>= fun () ->
  let first_len = Cstruct.get_uint8 cs 1 in
  if first_len > 0x80 then
    let len_bytes = first_len - 0x80 in
    guard (Cstruct.len cs > len_bytes + 2)
      (`Msg "Certificate with too few data") >>= fun () ->
    let rec read_more acc off =
      if off = len_bytes then
        Ok (acc + 2 + len_bytes)
      else
        let v = acc * 256 + Cstruct.get_uint8 cs (off + 2) in
        read_more v (off + 1)
    in
    read_more 0 0
  else
    Ok (first_len + 2)

let decode_reg_data data =
  let cs = Cstruct.of_string data in
  guard (Cstruct.len cs >= 67)
    (`Msg "registration data too small (< 67)") >>= fun () ->
  guard (Cstruct.get_uint8 cs 0 = 0x05)
    (`Msg "registration data first byte must be 0x05") >>= fun () ->
  let pubkey, rest = Cstruct.(split (shift cs 1) 65) in
  let kh_len = Cstruct.get_uint8 rest 0 in
  guard (Cstruct.len rest > kh_len)
    (`Msg ("registration data too small (< kh_len)")) >>= fun () ->
  let kh, rest = Cstruct.(split (shift rest 1) kh_len) in
  seq_len rest >>= fun clen ->
  guard (Cstruct.len rest > clen)
    (`Msg ("registration data too small (< clen)")) >>= fun () ->
  let cert_data, signature = Cstruct.split rest clen in
  X509.Certificate.decode_der cert_data >>= fun cert ->
  match Mirage_crypto_ec.P256.Dsa.pub_of_cstruct pubkey with
  | Ok key -> Ok (key, kh, cert, signature)
  | Error err ->
    let err = Format.asprintf "%a" Mirage_crypto_ec.pp_error err in
    Error (`Msg err)

let verify_sig pub ~signature data =
  match X509.Public_key.verify `SHA256 ~signature pub (`Message data) with
  | Error `Msg m -> Error (`Signature_verification m)
  | Ok () -> Ok ()

let verify_reg_sig cert app client_data kh key signature =
  let h s = Mirage_crypto.Hash.SHA256.digest (Cstruct.of_string s) in
  let data =
    Cstruct.concat [
      Cstruct.create 1 ;
      h app ;
      h client_data ;
      kh ;
      Mirage_crypto_ec.P256.Dsa.pub_to_cstruct key
    ]
  in
  verify_sig (X509.Certificate.public_key cert) ~signature data

let verify_auth_sig key app presence counter client_data signature =
  let data =
    let h s = Mirage_crypto.Hash.SHA256.digest (Cstruct.of_string s) in
    let p_c =
      let b = Cstruct.create 5 in
      if presence then Cstruct.set_uint8 b 0 1;
      Cstruct.BE.set_uint32 b 1 counter;
      b
    in
    Cstruct.concat [ h app ; p_c ; h client_data ]
  in
  verify_sig (`P256 key) ~signature data

let of_json_or_err thing p json =
  lift_err
    (fun msg -> `Json_decoding (thing, msg, Yojson.Safe.to_string json))
    (p json)

let of_json thing p s =
  (try Ok (Yojson.Safe.from_string s)
   with Yojson.Json_error msg ->
     Error (`Json_decoding (thing, msg, s))) >>=
  of_json_or_err thing p

let register_response (t : t) challenge data =
  of_json "RegisterResponse"
    u2f_register_response_of_yojson data >>= fun reg_resp ->
  lift_err (fun p -> `Protocol p)
    (error_code_of_int reg_resp.errorCode) >>= fun () ->
  guard (String.equal t.version reg_resp.version)
    (`Version_mismatch (t.version, reg_resp.version)) >>= fun () ->
  b64_dec "clientData" reg_resp.clientData >>= fun client_data_json ->
  b64_dec "registrationData" reg_resp.registrationData >>= fun reg_data ->
  lift_err
    (function `Msg m -> `Binary_decoding ("registrationData", m, reg_data))
    (decode_reg_data reg_data) >>= fun (key, key_handle, certificate, signature) ->
  of_json "clientData" clientData_of_yojson client_data_json >>= fun client_data ->
  guard (res_typ client_data.typ = Ok `Register)
    (`Typ_mismatch (res_typ_to_string `Register, client_data.typ)) >>= fun () ->
  guard (String.equal challenge client_data.challenge)
    (`Challenge_mismatch (challenge, client_data.challenge)) >>= fun () ->
  verify_reg_sig certificate t.application_id client_data_json
    key_handle key signature >>= fun () ->
  Ok (key, b64_enc (Cstruct.to_string key_handle), certificate)

type u2f_authentication_request = {
  appId : string ;
  challenge : string ;
  registeredKeys : registered_key list ;
} [@@deriving yojson]

let authentication_request { version ; application_id } key_handles =
  let challenge = challenge () in
  let ar = {
    appId = application_id ;
    challenge ;
    registeredKeys = List.map (fun keyHandle -> { version ; keyHandle }) key_handles
  } in
  challenge,
  Yojson.Safe.to_string (u2f_authentication_request_to_yojson ar)

type u2f_authentication_response = {
  clientData : string ;
  errorCode : int ;
  keyHandle : string ;
  signatureData : string ;
} [@@deriving yojson]

let decode_sigdata data =
  let cs = Cstruct.of_string data in
  guard (Cstruct.len cs > 5)
    (`Msg "sigData too small") >>= fun () ->
  let user_presence = Cstruct.get_uint8 cs 0 = 1 in
  let counter = Cstruct.BE.get_uint32 cs 1 in
  let signature = Cstruct.shift cs 5 in
  Ok (user_presence, counter, signature)

let authentication_response (t : t) key_handle_keys challenge data =
  of_json "AuthenticationResponse"
    u2f_authentication_response_of_yojson data >>= fun sig_resp ->
  lift_err (fun p -> `Protocol p)
    (error_code_of_int sig_resp.errorCode) >>= fun () ->
  b64_dec "clientData" sig_resp.clientData >>= fun client_data_json ->
  b64_dec "signatureData" sig_resp.signatureData >>= fun sigdata ->
  lift_err
    (function `Msg m -> `Binary_decoding ("signatureData", m, sigdata))
    (decode_sigdata sigdata) >>= fun (user_present, counter, signature) ->
  of_json "clientData"
    clientData_of_yojson client_data_json >>= fun client_data ->
  guard (res_typ client_data.typ = Ok `Sign)
    (`Typ_mismatch (res_typ_to_string `Sign, client_data.typ)) >>= fun () ->
  guard (String.equal challenge client_data.challenge)
    (`Challenge_mismatch (challenge, client_data.challenge)) >>= fun () ->
  guard (String.equal t.application_id client_data.origin)
    (`Origin_mismatch (t.application_id, client_data.origin)) >>= fun () ->
  List.fold_left (fun acc (_, pubkey) ->
    match acc with
    | Ok key -> Ok key
    | Error _ ->
      verify_auth_sig pubkey t.application_id user_present counter
        client_data_json signature >>= fun () ->
      Ok pubkey)
   (Error (`Unknown_key_handle sig_resp.keyHandle))
   (List.filter (fun (kh, _) -> String.equal kh sig_resp.keyHandle) key_handle_keys)
  >>= fun pubkey ->
  Ok ((sig_resp.keyHandle, pubkey), user_present, counter)

OCaml

Innovation. Community. Security.