2021-10-05 15:56:20 +00:00
type credential_id = string
2021-09-28 11:30:14 +00:00
2021-10-05 15:56:20 +00:00
type json_decoding_error = [ ` Json_decoding of string * string * string ]
2021-10-06 09:48:19 +00:00
type decoding_error = [
2021-10-05 15:56:20 +00:00
json_decoding_error
2021-09-29 14:34:09 +00:00
| ` Base64_decoding of string * string * string
2021-10-06 09:48:19 +00:00
| ` CBOR_decoding of string * string * string
| ` Unexpected_CBOR of string * string * CBOR . Simple . t
2024-09-13 09:26:38 +00:00
| ` Binary_decoding of string * string * string
2021-10-06 09:48:19 +00:00
| ` Attestation_object_decoding of string * string * string
]
type error = [
decoding_error
| ` Unsupported_key_type of int
| ` Unsupported_algorithm of int
| ` Unsupported_elliptic_curve of int
| ` Unsupported_attestation_format of string
| ` Invalid_public_key of string
2021-09-29 14:34:09 +00:00
| ` Client_data_type_mismatch of string
| ` Origin_mismatch of string * string
2021-10-05 15:56:20 +00:00
| ` Rpid_hash_mismatch of string * string
2021-09-29 14:34:09 +00:00
| ` Missing_credential_data
2021-10-06 09:48:19 +00:00
| ` Signature_verification of string
2021-09-28 11:30:14 +00:00
]
2021-09-29 14:34:09 +00:00
let pp_error ppf = function
| ` Json_decoding ( ctx , msg , json ) ->
Fmt . pf ppf " json decoding error in %s: %s (json: %s) " ctx msg json
2021-10-06 09:48:19 +00:00
| ` Base64_decoding ( ctx , msg , data ) ->
Fmt . pf ppf " base64 decoding error in %s: %s (data: %s) " ctx msg data
| ` CBOR_decoding ( ctx , msg , data ) ->
Fmt . pf ppf " cbor decoding error in %s: %s (data: %s) " ctx msg data
| ` Unexpected_CBOR ( ctx , msg , data ) ->
Fmt . pf ppf " unexpected cbor in %s: %s (data: %s) " ctx msg ( CBOR . Simple . to_diagnostic data )
| ` Binary_decoding ( ctx , msg , data ) ->
2024-09-13 09:26:38 +00:00
Fmt . pf ppf " binary decoding error in %s: %s (data: %a) " ctx msg ( Ohex . pp_hexdump () ) data
2021-10-06 09:48:19 +00:00
| ` Attestation_object_decoding ( ctx , msg , data ) ->
Fmt . pf ppf " attestation object decoding error in %s: %s (data: %s) " ctx msg data
| ` Unsupported_key_type i ->
Fmt . pf ppf " unsupported cose key type %d " i
| ` Unsupported_algorithm i ->
Fmt . pf ppf " unsupported cose algorithm %d " i
| ` Unsupported_elliptic_curve i ->
Fmt . pf ppf " unsupported cose elliptic curve %d " i
| ` Unsupported_attestation_format fmt ->
Fmt . pf ppf " unsupported attestation format %s " fmt
| ` Invalid_public_key msg ->
Fmt . pf ppf " invalid public key %s " msg
2021-09-29 14:34:09 +00:00
| ` Client_data_type_mismatch is ->
Fmt . pf ppf " client data type mismatch: received %s " is
| ` Origin_mismatch ( should , is ) ->
Fmt . pf ppf " origin mismatch: expected %s, received %s " should is
| ` Rpid_hash_mismatch ( should , is ) ->
2021-10-05 15:56:20 +00:00
Fmt . pf ppf " rpid hash mismatch: expected %s received %s "
( Base64 . encode_string should ) ( Base64 . encode_string is )
2021-09-29 14:34:09 +00:00
| ` Missing_credential_data -> Fmt . string ppf " missing credential data "
2021-10-06 09:48:19 +00:00
| ` Signature_verification msg -> Fmt . pf ppf " signature verification failed %s " msg
2021-09-28 11:30:14 +00:00
2021-09-29 14:34:09 +00:00
type t = {
origin : string ;
2021-10-06 10:12:47 +00:00
rpid : [ ` host ] Domain_name . t ;
2021-09-29 14:34:09 +00:00
}
2021-09-28 11:30:14 +00:00
type challenge = string
2021-10-05 15:56:20 +00:00
let generate_challenge ? ( size = 32 ) () =
if size < 16 then invalid_arg " size must be at least 16 bytes " ;
2024-09-13 09:26:38 +00:00
let ch = Mirage_crypto_rng . generate size in
2021-10-05 15:56:20 +00:00
ch , Base64 . encode_string ch
2021-09-29 14:34:09 +00:00
2021-10-05 15:56:20 +00:00
let challenge_to_string c = c
let challenge_of_string s = Some s
let challenge_equal = String . equal
2021-09-29 14:34:09 +00:00
let b64_dec thing s =
2021-10-05 15:56:20 +00:00
Result . map_error
2021-09-29 14:34:09 +00:00
( function ` Msg m -> ` Base64_decoding ( thing , m , s ) )
Base64 . ( decode ~ pad : false ~ alphabet : uri_safe_alphabet s )
let guard p e = if p then Ok () else Error e
let ( > > = ) v f = match v with Ok v -> f v | Error _ as e -> e
type base64url_string = string
let base64url_string_of_yojson = function
| ` String b64 ->
Base64 . ( decode ~ pad : false ~ alphabet : uri_safe_alphabet b64 )
| > Result . map_error ( function ` Msg m -> m )
| _ -> Error " base64url_string "
2021-10-06 09:48:19 +00:00
let extract_k_i ctx map k =
Option . to_result ~ none : ( ` Unexpected_CBOR ( ctx , " integer key not present: " ^ string_of_int k , ` Map map ) )
2021-09-29 14:34:09 +00:00
( Option . map snd
( List . find_opt ( fun ( l , _ ) -> match l with ` Int i -> i = k | _ -> false ) map ) )
2021-10-06 09:48:19 +00:00
let extract_k_str ctx map k =
Option . to_result ~ none : ( ` Unexpected_CBOR ( ctx , " string key not present: " ^ k , ` Map map ) )
2021-09-29 14:34:09 +00:00
( Option . map snd
( List . find_opt ( fun ( l , _ ) -> match l with ` Text s -> s = k | _ -> false ) map ) )
2021-10-06 09:48:19 +00:00
let extract_int ctx = function
2021-09-29 14:34:09 +00:00
| ` Int i -> Ok i
2021-10-06 09:48:19 +00:00
| c -> Error ( ` Unexpected_CBOR ( ctx , " not an integer " , c ) )
2021-09-29 14:34:09 +00:00
2021-10-06 09:48:19 +00:00
let extract_bytes ctx = function
2021-09-29 14:34:09 +00:00
| ` Bytes b -> Ok b
2021-10-06 09:48:19 +00:00
| c -> Error ( ` Unexpected_CBOR ( ctx , " not bytes " , c ) )
2021-09-29 14:34:09 +00:00
2021-10-06 09:48:19 +00:00
let extract_map ctx = function
2021-09-29 14:34:09 +00:00
| ` Map b -> Ok b
2021-10-06 09:48:19 +00:00
| c -> Error ( ` Unexpected_CBOR ( ctx , " not a map " , c ) )
2021-09-29 14:34:09 +00:00
2021-10-06 09:48:19 +00:00
let extract_array ctx = function
2021-09-29 14:34:09 +00:00
| ` Array b -> Ok b
2021-10-06 09:48:19 +00:00
| c -> Error ( ` Unexpected_CBOR ( ctx , " not an array " , c ) )
2021-09-29 14:34:09 +00:00
2021-10-06 09:48:19 +00:00
let extract_text ctx = function
2021-09-29 14:34:09 +00:00
| ` Text s -> Ok s
2021-10-06 09:48:19 +00:00
| c -> Error ( ` Unexpected_CBOR ( ctx , " not a text " , c ) )
2021-09-29 14:34:09 +00:00
let cose_pubkey cbor_data =
2021-10-06 09:48:19 +00:00
extract_map " cose pubkey " cbor_data > > = fun kv ->
extract_k_i " cose pubkey kty " kv 1 > > = extract_int " cose pubkey kty " > > = fun kty ->
guard ( kty = 2 ) ( ` Unsupported_key_type kty ) > > = fun () ->
extract_k_i " cose pubkey alg " kv 3 > > = extract_int " cose pubkey alg " > > = fun alg ->
guard ( alg = - 7 ) ( ` Unsupported_algorithm alg ) > > = fun () ->
extract_k_i " cose pubkey crv " kv ( - 1 ) > > = extract_int " cose pubkey crv " > > = fun crv ->
guard ( crv = 1 ) ( ` Unsupported_elliptic_curve crv ) > > = fun () ->
extract_k_i " cose pubkey x " kv ( - 2 ) > > = extract_bytes " cose pubkey x " > > = fun x ->
extract_k_i " cose pubkey y " kv ( - 3 ) > > = extract_bytes " cose pubkey y " > > = fun y ->
2024-09-13 09:26:38 +00:00
let str = String . concat " " [ " \004 " ; x ; y ] in
2021-10-06 09:48:19 +00:00
Result . map_error
( fun e -> ` Invalid_public_key ( Fmt . to_to_string Mirage_crypto_ec . pp_error e ) )
2024-09-13 09:26:38 +00:00
( Mirage_crypto_ec . P256 . Dsa . pub_of_octets str )
2021-09-29 14:34:09 +00:00
2021-10-06 09:48:19 +00:00
let decode_partial_cbor ctx data =
try Ok ( CBOR . Simple . decode_partial data )
with CBOR . Error m -> Error ( ` CBOR_decoding ( ctx , " failed to decode CBOR " ^ m , data ) )
let decode_cbor ctx data =
try Ok ( CBOR . Simple . decode data )
with CBOR . Error m -> Error ( ` CBOR_decoding ( ctx , " failed to decode CBOR " ^ m , data ) )
2024-09-13 09:26:38 +00:00
let guard_length ctx len str =
guard ( String . length str > = len )
( ` Binary_decoding ( ctx , " too short (< " ^ string_of_int len ^ " ) " , str ) )
2021-10-06 09:48:19 +00:00
2021-09-29 14:34:09 +00:00
let parse_attested_credential_data data =
2021-10-06 09:48:19 +00:00
guard_length " attested credential data " 18 data > > = fun () ->
2024-09-13 09:26:38 +00:00
let aaguid = String . sub data 0 16 in
let cid_len = String . get_uint16_be data 16 in
let rest = String . sub data 18 ( String . length data - 18 ) in
2021-10-06 09:48:19 +00:00
guard_length " attested credential data " cid_len rest > > = fun () ->
2024-09-13 09:26:38 +00:00
let cid , pubkey =
String . sub rest 0 cid_len ,
String . sub rest cid_len ( String . length rest - cid_len )
in
decode_partial_cbor " public key " pubkey > > = fun ( pubkey , rest ) ->
2021-09-29 14:34:09 +00:00
cose_pubkey pubkey > > = fun pubkey ->
2024-09-13 09:26:38 +00:00
Ok ( ( aaguid , cid , pubkey ) , rest )
2021-09-29 14:34:09 +00:00
2021-10-06 09:48:19 +00:00
let string_keys ctx kv =
2021-10-05 15:56:20 +00:00
List . fold_right ( fun ( k , v ) acc ->
match acc , k with
| Error _ as e , _ -> e
| Ok xs , ` Text t -> Ok ( ( t , v ) :: xs )
2021-10-06 09:48:19 +00:00
| _ , _ -> Error ( ` Unexpected_CBOR ( ctx , " Map does contain non-text keys " , ` Map kv ) ) )
2021-10-05 15:56:20 +00:00
kv ( Ok [] )
2021-09-29 14:34:09 +00:00
let parse_extension_data data =
2024-09-13 09:26:38 +00:00
decode_partial_cbor " extension data " data > > = fun ( data , rest ) ->
2021-10-06 09:48:19 +00:00
extract_map " extension data " data > > = fun kv ->
string_keys " extension data " kv > > = fun kv ->
2024-09-13 09:26:38 +00:00
Ok ( kv , rest )
2021-10-06 09:48:19 +00:00
2021-09-29 14:34:09 +00:00
type auth_data = {
2024-09-13 09:26:38 +00:00
rpid_hash : string ;
2021-09-29 14:34:09 +00:00
user_present : bool ;
user_verified : bool ;
sign_count : Int32 . t ;
2024-09-13 09:26:38 +00:00
attested_credential_data : ( string * string * Mirage_crypto_ec . P256 . Dsa . pub ) option ;
2021-10-05 15:56:20 +00:00
extension_data : ( string * CBOR . Simple . t ) list option ;
2021-09-29 14:34:09 +00:00
}
let flags byte =
let b i = byte land ( 1 lsl i ) < > 0 in
2021-10-06 09:48:19 +00:00
b 0 , b 2 , b 6 , b 7
2021-09-29 14:34:09 +00:00
let parse_auth_data data =
2021-10-06 09:48:19 +00:00
guard_length " authenticator data " 37 data > > = fun () ->
2024-09-13 09:26:38 +00:00
let rpid_hash = String . sub data 0 32 in
2021-09-29 14:34:09 +00:00
let user_present , user_verified , attested_data_included , extension_data_included =
2024-09-13 09:26:38 +00:00
flags ( String . get_uint8 data 32 )
2021-09-29 14:34:09 +00:00
in
2024-09-13 09:26:38 +00:00
let sign_count = String . get_int32_be data 33 in
let rest = String . sub data 37 ( String . length data - 37 ) in
2021-09-29 14:34:09 +00:00
( if attested_data_included then
Result . map ( fun ( d , r ) -> Some d , r ) ( parse_attested_credential_data rest )
else Ok ( None , rest ) ) > > = fun ( attested_credential_data , rest ) ->
( if extension_data_included then
Result . map ( fun ( d , r ) -> Some d , r ) ( parse_extension_data rest )
else Ok ( None , rest ) ) > > = fun ( extension_data , rest ) ->
2024-09-13 09:26:38 +00:00
guard ( String . length rest = 0 ) ( ` Binary_decoding ( " authenticator data " , " leftover " , rest ) ) > > = fun () ->
2021-09-29 14:34:09 +00:00
Ok { rpid_hash ; user_present ; user_verified ; sign_count ; attested_credential_data ; extension_data }
let parse_attestation_statement fmt data =
match fmt with
2021-10-06 09:48:19 +00:00
| " none " when data = [] -> Ok None
| " none " -> Error ( ` Unexpected_CBOR ( " attestion statement " , " format is none, map must be empty " , ` Map data ) )
2021-09-29 14:34:09 +00:00
| " fido-u2f " ->
2021-10-06 09:48:19 +00:00
extract_k_str " attestation statement " data " x5c " > > = extract_array " attestation statement x5c " > > = fun cert ->
extract_k_str " attestation statement " data " sig " > > = extract_bytes " attestation statemnt sig " > > = fun signature ->
2021-09-29 14:34:09 +00:00
begin match cert with
| [ c ] ->
2021-10-06 09:48:19 +00:00
extract_bytes " attestation statement x5c " c > > = fun c ->
Result . map_error
( function ` Msg m -> ` Attestation_object_decoding ( " attestation statement x5c " , m , String . escaped c ) )
2024-09-13 09:26:38 +00:00
( X509 . Certificate . decode_der c )
2021-10-06 09:48:19 +00:00
| cs -> Error ( ` Attestation_object_decoding ( " attestation statement x5c " , " expected single certificate " , String . concat " , " ( List . map CBOR . Simple . to_diagnostic cs ) ) )
2021-09-29 14:34:09 +00:00
end > > = fun cert ->
Ok ( Some ( cert , signature ) )
2021-10-06 09:48:19 +00:00
| x -> Error ( ` Unsupported_attestation_format x )
2021-09-29 14:34:09 +00:00
let parse_attestation_object data =
2021-10-06 09:48:19 +00:00
decode_cbor " attestation object " data > > = extract_map " attestation object " > > = fun kv ->
extract_k_str " attestation object " kv " fmt " > > = extract_text " attestation object fmt " > > = fun fmt ->
extract_k_str " attestation object " kv " authData " > > = extract_bytes " attestation object authData " > > = fun auth_data ->
extract_k_str " attestation object " kv " attStmt " > > = extract_map " attestation object attStmt " > > = fun attestation_statement ->
parse_auth_data auth_data > > = fun auth_data ->
parse_attestation_statement fmt attestation_statement > > = fun attestation_statement ->
Ok ( auth_data , attestation_statement )
2021-09-28 11:30:14 +00:00
2021-09-29 14:34:09 +00:00
let of_json_or_err thing p json =
2021-10-05 15:56:20 +00:00
Result . map_error
2021-09-29 14:34:09 +00:00
( fun msg -> ` Json_decoding ( thing , msg , Yojson . Safe . to_string json ) )
( p json )
2021-09-28 11:30:14 +00:00
2021-09-29 14:34:09 +00:00
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
2021-09-28 11:30:14 +00:00
2021-09-29 14:34:09 +00:00
let json_get member = function
| ` Assoc kv as json ->
List . assoc_opt member kv
| > Option . to_result ~ none : ( ` Json_decoding ( member , " missing key " , Yojson . Safe . to_string json ) )
| json -> Error ( ` Json_decoding ( member , " non-object " , Yojson . Safe . to_string json ) )
2021-09-28 11:30:14 +00:00
2021-09-29 14:34:09 +00:00
let json_string thing : Yojson . Safe . t -> ( string , _ ) result = function
| ` String s -> Ok s
| json -> Error ( ` Json_decoding ( thing , " non-string " , Yojson . Safe . to_string json ) )
2021-09-28 11:30:14 +00:00
2021-10-05 15:56:20 +00:00
let json_assoc thing : Yojson . Safe . t -> ( ( string * Yojson . Safe . t ) list , _ ) result = function
| ` Assoc s -> Ok s
2021-10-06 09:48:19 +00:00
| json -> Error ( ` Json_decoding ( thing , " non-assoc " , Yojson . Safe . to_string json ) )
2021-10-05 15:56:20 +00:00
2021-10-06 10:12:47 +00:00
let create origin =
match String . split_on_char '/' origin with
| [ " https: " ; " " ; host_port ] ->
let host_ok h =
match Domain_name . of_string h with
| Error ( ` Msg m ) -> Error ( " origin is not a domain name " ^ m ^ " (data: " ^ h ^ " ) " )
| Ok d -> match Domain_name . host d with
| Error ( ` Msg m ) -> Error ( " origin is not a host name " ^ m ^ " (data: " ^ h ^ " ) " )
| Ok host -> Ok host
in
begin
match
match String . split_on_char ':' host_port with
| [ host ] -> host_ok host
| [ host ; port ] ->
( match host_ok host with
| Error _ as e -> e
| Ok h -> ( try ignore ( int_of_string port ) ; Ok h
with Failure _ -> Error ( " invalid port " ^ port ) ) )
| _ -> Error ( " invalid origin host and port " ^ host_port )
with
| Ok host -> Ok { origin ; rpid = host }
| Error _ as e -> e
end
| _ -> Error ( " invalid origin " ^ origin )
let rpid t = Domain_name . to_string t . rpid
2021-09-28 11:30:14 +00:00
2021-10-05 15:56:20 +00:00
type credential_data = {
aaguid : string ;
credential_id : credential_id ;
public_key : Mirage_crypto_ec . P256 . Dsa . pub ;
}
type registration = {
user_present : bool ;
user_verified : bool ;
sign_count : Int32 . t ;
attested_credential_data : credential_data ;
authenticator_extensions : ( string * CBOR . Simple . t ) list option ;
2021-10-06 15:13:41 +00:00
client_extensions : ( string * Yojson . Safe . t ) list option ;
2021-10-05 15:56:20 +00:00
certificate : X509 . Certificate . t option ;
}
type register_response = {
attestation_object : base64url_string [ @ key " attestationObject " ] ;
client_data_json : base64url_string [ @ key " clientDataJSON " ] ;
} [ @@ deriving of_yojson ]
let register_response_of_string =
of_json " register response " register_response_of_yojson
let register t response =
2021-09-29 14:34:09 +00:00
(* XXX: credential.getClientExtensionResults ( ) *)
2024-09-13 09:26:38 +00:00
let client_data_hash =
Digestif . SHA256 . ( to_raw_string ( digest_string response . client_data_json ) )
in
2021-09-29 14:34:09 +00:00
begin try Ok ( Yojson . Safe . from_string response . client_data_json )
with Yojson . Json_error msg ->
Error ( ` Json_decoding ( " clientDataJSON " , msg , response . client_data_json ) )
end > > = fun client_data ->
json_get " type " client_data > > = json_string " type " > > =
( function
| " webauthn.create " -> Ok ()
| wrong_typ -> Error ( ` Client_data_type_mismatch wrong_typ ) ) > > = fun () ->
2021-10-05 13:09:35 +00:00
json_get " challenge " client_data > > = json_string " challenge " > > = fun challenge ->
b64_dec " response.ClientDataJSON.challenge " challenge > > = fun challenge ->
2021-09-29 14:34:09 +00:00
json_get " origin " client_data > > = json_string " origin " > > = fun origin ->
guard ( String . equal t . origin origin )
( ` Origin_mismatch ( t . origin , origin ) ) > > = fun () ->
2021-10-06 15:13:41 +00:00
let client_extensions = Result . to_option ( json_get " clientExtensions " client_data ) in
begin match client_extensions with
| Some client_extensions ->
json_assoc " clientExtensions " client_extensions > > = fun client_extensions ->
Ok ( Some client_extensions )
| None ->
Ok None
end > > = fun client_extensions ->
2021-10-06 09:48:19 +00:00
parse_attestation_object response . attestation_object > > = fun ( auth_data , attestation_statement ) ->
2024-09-13 09:26:38 +00:00
let rpid_hash =
Digestif . SHA256 . ( to_raw_string ( digest_string ( rpid t ) ) ) in
guard ( String . equal auth_data . rpid_hash rpid_hash )
( ` Rpid_hash_mismatch ( rpid_hash , auth_data . rpid_hash ) ) > > = fun () ->
2021-09-29 14:34:09 +00:00
(* verify user present, user verified flags in auth_data.flags *)
Option . to_result ~ none : ` Missing_credential_data
2021-10-05 15:56:20 +00:00
auth_data . attested_credential_data > > = fun ( aaguid , credential_id , public_key ) ->
2021-09-29 14:34:09 +00:00
begin match attestation_statement with
| None -> Ok None
| Some ( cert , signature ) ->
2024-09-13 09:26:38 +00:00
let pub_cs = Mirage_crypto_ec . P256 . Dsa . pub_to_octets public_key in
let sigdata = String . concat " " [
" \000 " ; rpid_hash ; client_data_hash ; credential_id ; pub_cs
2021-09-29 14:34:09 +00:00
] in
2024-09-13 09:26:38 +00:00
let pk = X509 . Certificate . public_key cert in
2021-10-06 09:48:19 +00:00
Result . map_error ( function ` Msg m -> ` Signature_verification m )
( X509 . Public_key . verify ` SHA256 ~ signature pk ( ` Message sigdata ) ) > > = fun () ->
2021-09-29 14:34:09 +00:00
Ok ( Some cert )
2021-10-05 15:56:20 +00:00
end > > = fun certificate ->
2021-09-29 14:34:09 +00:00
(* check attestation cert, maybe *)
(* check auth_data.attested_credential_data.credential_id is not registered ? *)
2021-10-05 15:56:20 +00:00
let registration =
let attested_credential_data = {
2024-09-13 09:26:38 +00:00
aaguid ;
credential_id ;
2021-10-05 15:56:20 +00:00
public_key
} in
{
user_present = auth_data . user_present ;
user_verified = auth_data . user_verified ;
sign_count = auth_data . sign_count ;
attested_credential_data ;
authenticator_extensions = auth_data . extension_data ;
client_extensions ;
certificate ;
}
in
Ok ( challenge , registration )
type authentication = {
user_present : bool ;
user_verified : bool ;
sign_count : Int32 . t ;
authenticator_extensions : ( string * CBOR . Simple . t ) list option ;
2021-10-06 15:13:41 +00:00
client_extensions : ( string * Yojson . Safe . t ) list option ;
2021-10-05 15:56:20 +00:00
}
2021-09-28 11:30:14 +00:00
2021-10-05 15:56:20 +00:00
type authenticate_response = {
2021-09-29 14:34:09 +00:00
authenticator_data : base64url_string [ @ key " authenticatorData " ] ;
client_data_json : base64url_string [ @ key " clientDataJSON " ] ;
signature : base64url_string ;
userHandle : base64url_string option ;
} [ @@ deriving of_yojson ]
2021-10-05 15:56:20 +00:00
let authenticate_response_of_string =
of_json " authenticate response " authenticate_response_of_yojson
2021-09-28 11:30:14 +00:00
2021-10-05 15:56:20 +00:00
let authenticate t public_key response =
2024-09-13 09:26:38 +00:00
let client_data_hash =
Digestif . SHA256 . ( to_raw_string ( digest_string response . client_data_json ) )
in
2021-09-29 14:34:09 +00:00
begin try Ok ( Yojson . Safe . from_string response . client_data_json )
with Yojson . Json_error msg ->
Error ( ` Json_decoding ( " clientDataJSON " , msg , response . client_data_json ) )
end > > = fun client_data ->
json_get " type " client_data > > = json_string " type " > > =
( function
| " webauthn.get " -> Ok ()
| wrong_typ -> Error ( ` Client_data_type_mismatch wrong_typ ) ) > > = fun () ->
2021-10-05 13:09:35 +00:00
json_get " challenge " client_data > > = json_string " challenge " > > = fun challenge ->
b64_dec " response.ClientDataJSON.challenge " challenge > > = fun challenge ->
2021-09-29 14:34:09 +00:00
json_get " origin " client_data > > = json_string " origin " > > = fun origin ->
guard ( String . equal t . origin origin )
( ` Origin_mismatch ( t . origin , origin ) ) > > = fun () ->
2021-10-06 15:13:41 +00:00
let client_extensions = Result . to_option ( json_get " clientExtensions " client_data ) in
begin match client_extensions with
| Some client_extensions ->
json_assoc " clientExtensions " client_extensions > > = fun client_extensions ->
Ok ( Some client_extensions )
| None ->
Ok None
end > > = fun client_extensions ->
2021-10-06 09:48:19 +00:00
parse_auth_data response . authenticator_data > > = fun auth_data ->
2024-09-13 09:26:38 +00:00
let rpid_hash = Digestif . SHA256 . ( to_raw_string ( digest_string ( rpid t ) ) ) in
guard ( String . equal auth_data . rpid_hash rpid_hash )
( ` Rpid_hash_mismatch ( rpid_hash , auth_data . rpid_hash ) ) > > = fun () ->
let sigdata = response . authenticator_data ^ client_data_hash
and signature = response . signature in
2021-10-06 09:48:19 +00:00
Result . map_error ( function ` Msg m -> ` Signature_verification m )
( X509 . Public_key . verify ` SHA256 ~ signature ( ` P256 public_key ) ( ` Message sigdata ) ) > > = fun () ->
2021-10-05 15:56:20 +00:00
let authentication = {
user_present = auth_data . user_present ;
user_verified = auth_data . user_verified ;
sign_count = auth_data . sign_count ;
authenticator_extensions = auth_data . extension_data ;
client_extensions ;
} in
Ok ( challenge , authentication )
2021-10-07 09:53:38 +00:00
let fido_u2f_transport_oid =
Asn . OID . ( base 1 3 < | 6 < | 1 < | 4 < | 1 < | 45724 < | 2 < | 1 < | 1 )
type transport = [
| ` Bluetooth_classic
| ` Bluetooth_low_energy
| ` Usb
| ` Nfc
| ` Usb_internal
]
let pp_transport ppf = function
| ` Bluetooth_classic -> Fmt . string ppf " Bluetooth classic "
| ` Bluetooth_low_energy -> Fmt . string ppf " Bluetooth low energy "
| ` Usb -> Fmt . string ppf " USB "
| ` Nfc -> Fmt . string ppf " NFC "
| ` Usb_internal -> Fmt . string ppf " USB internal "
let transports =
let opts = [
( 0 , ` Bluetooth_classic ) ;
( 1 , ` Bluetooth_low_energy ) ;
( 2 , ` Usb ) ;
( 3 , ` Nfc ) ;
( 4 , ` Usb_internal ) ;
] in
Asn . S . bit_string_flags opts
let decode_strict codec cs =
match Asn . decode codec cs with
| Ok ( a , cs ) ->
2024-09-13 09:26:38 +00:00
guard ( String . length cs = 0 ) ( ` Msg " trailing bytes " ) > > = fun () ->
2021-10-07 09:53:38 +00:00
Ok a
| Error ( ` Parse msg ) -> Error ( ` Msg msg )
let decode_transport =
decode_strict ( Asn . codec Asn . der transports )
let transports_of_cert c =
Result . bind
( Option . to_result ~ none : ( ` Msg " extension not present " )
( X509 . Extension . ( find ( Unsupported fido_u2f_transport_oid ) ( X509 . Certificate . extensions c ) ) ) )
( fun ( _ , data ) -> decode_transport data )