(* $Id: nettls_gnutls.ml 2208 2015-01-12 23:40:51Z gerd $ *) open Printf module StrMap = Map.Make(String) module type GNUTLS_PROVIDER = sig include Netsys_crypto_types.TLS_PROVIDER val gnutls_session : endpoint -> Nettls_gnutls_bindings.gnutls_session_t val gnutls_credentials : credentials -> Nettls_gnutls_bindings.gnutls_credentials end module type GNUTLS_ENDPOINT = sig module TLS : GNUTLS_PROVIDER val endpoint : TLS.endpoint end exception I of (module GNUTLS_PROVIDER) module type SELF = sig val self : exn ref end module Make_TLS_1 (Self:SELF) (Exc:Netsys_crypto_types.TLS_EXCEPTIONS) : GNUTLS_PROVIDER = struct let implementation_name = "Nettls_gnutls.TLS" let implementation () = !Self.self module Exc = Exc module G = Nettls_gnutls_bindings type credentials = { gcred : G.gnutls_credentials; } type dh_params = [ `PKCS3_PEM_file of string | `PKCS3_DER of string | `Generate of int ] type crt_list = [`PEM_file of string | `DER of string list] type crl_list = [`PEM_file of string | `DER of string list] type private_key = [ `PEM_file of string | `RSA of string | `DSA of string | `EC of string | `PKCS8 of string | `PKCS8_encrypted of string ] type server_name = [ `Domain of string ] type state = [ `Start | `Handshake | `Data_rw | `Data_r | `Data_w | `Data_rs | `Switching | `Accepting | `Refusing | `End ] type raw_credentials = [ `X509 of string | `Anonymous ] type role = [ `Server | `Client ] type endpoint = { role : role; recv : (Netsys_types.memory -> int); send : (Netsys_types.memory -> int -> int); mutable config : config; session : G.gnutls_session_t; peer_name : string option; mutable our_cert : raw_credentials option; mutable state : state; mutable trans_eof : bool; } and config = { priority : G.gnutls_priority_t; dh_params : G.gnutls_dh_params_t option; peer_auth : [ `None | `Optional | `Required ]; credentials : credentials; verify : endpoint -> bool -> bool -> bool; } type serialized_session = { ser_data : string; (* GnuTLS packed session *) ser_our_cert : raw_credentials option; } let error_message code = match code with | "NETTLS_CERT_VERIFICATION_FAILED" -> "The certificate could not be verified against the list of \ trusted authorities" | "NETTLS_NAME_VERIFICATION_FAILED" -> "The name of the peer does not match the name of the certificate" | "NETTLS_USER_VERIFICATION_FAILED" -> "The user-supplied verification function did not succeed" | "NETTLS_UNEXPECTED_STATE" -> "The endpoint is in an unexpected state" | _ -> G.gnutls_strerror (G.b_error_of_name code) let () = Netexn.register_printer (Exc.TLS_error "") (function | Exc.TLS_error code -> sprintf "Nettls_gnutls.TLS.Error(%s)" code | _ -> assert false ) let () = Netexn.register_printer (Exc.TLS_warning "") (function | Exc.TLS_warning code -> sprintf "Nettls_gnutls.TLS.Warning(%s)" code | _ -> assert false ) let trans_exn f arg = try f arg with | G.Error code -> raise(Exc.TLS_error (G.gnutls_strerror_name code)) let parse_pem ?(empty_ok=false) header_tags file f = let spec = List.map (fun tag -> (tag, `Base64)) header_tags in let blocks = Netchannels.with_in_obj_channel (new Netchannels.input_channel(open_in file)) (fun ch -> Netascii_armor.parse spec ch) in if not empty_ok && blocks = [] then failwith ("Cannot find PEM-encoded objects in file: " ^ file); List.map (function | (tag, `Base64 body) -> f (tag,body#value) | _ -> assert false ) blocks let create_pem header_tag data = let b64 = Netencoding.Base64.encode ~linelength:80 data in "-----BEGIN " ^ header_tag ^ "-----\n" ^ b64 ^ "-----END " ^ header_tag ^ "-----\n" let create_config ?(algorithms="NORMAL") ?dh_params ?(verify=fun _ cert_ok name_ok -> cert_ok && name_ok) ~peer_auth ~credentials () = let f() = let priority = G.gnutls_priority_init algorithms in let dhp_opt = match dh_params with | None -> None | Some(`PKCS3_PEM_file file) -> let data = List.hd (parse_pem ["DH PARAMETERS"] file snd) in let dhp = G.gnutls_dh_params_init() in G.gnutls_dh_params_import_pkcs3 dhp data `Der; Some dhp | Some(`PKCS3_DER data) -> let dhp = G.gnutls_dh_params_init() in G.gnutls_dh_params_import_pkcs3 dhp data `Der; Some dhp | Some(`Generate bits) -> let dhp = G.gnutls_dh_params_init() in G.gnutls_dh_params_generate2 dhp bits; Some dhp in { priority; dh_params = dhp_opt; peer_auth; credentials; verify; } in trans_exn f () let create_x509_credentials_1 ~system_trust ~trust ~revoke ~keys () = let gcred = G.gnutls_certificate_allocate_credentials() in if system_trust then ( match Nettls_gnutls_config.system_trust with | `Gnutls -> G.gnutls_certificate_set_x509_system_trust gcred | `File path -> let certs = parse_pem [ "X509 CERTIFICATE"; "CERTIFICATE" ] path snd in List.iter (fun data -> G.gnutls_certificate_set_x509_trust_mem gcred data `Der ) certs ); List.iter (fun crt_spec -> let der_crts = match crt_spec with | `PEM_file file -> parse_pem [ "X509 CERTIFICATE"; "CERTIFICATE" ] file snd | `DER l -> l in List.iter (fun data -> G.gnutls_certificate_set_x509_trust_mem gcred data `Der ) der_crts ) trust; List.iter (fun crl_spec -> let der_crls = match crl_spec with | `PEM_file file -> parse_pem [ "X509 CRL" ] file snd | `DER l -> l in List.iter (fun data -> G.gnutls_certificate_set_x509_crl_mem gcred data `Der ) der_crls ) revoke; List.iter (fun (crts, pkey, pw_opt) -> let der_crts = match crts with | `PEM_file file -> parse_pem [ "X509 CERTIFICATE"; "CERTIFICATE" ] file snd | `DER l -> l in let gcrts = List.map (fun data -> let gcrt = G.gnutls_x509_crt_init() in G.gnutls_x509_crt_import gcrt data `Der; gcrt ) der_crts in let gpkey = G.gnutls_x509_privkey_init() in let pkey1 = match pkey with | `PEM_file file -> let p = parse_pem [ "RSA PRIVATE KEY"; "DSA PRIVATE KEY"; "EC PRIVATE KEY"; "PRIVATE KEY"; "ENCRYPTED PRIVATE KEY" ] file (fun (tag,data) -> match tag with | "RSA PRIVATE KEY" -> `RSA data | "DSA PRIVATE KEY" -> `DSA data | "EC PRIVATE KEY" -> `EC data | "PRIVATE KEY" -> `PKCS8 data | "ENCRYPTED PRIVATE KEY" -> `PKCS8_encrypted data | _ -> assert false ) in (List.hd p :> private_key) | other -> other in ( match pkey1 with | `PEM_file file -> assert false | `RSA data -> (* There is no entry point for parsing ONLY this format *) let pem = create_pem "RSA PRIVATE KEY" data in G.gnutls_x509_privkey_import gpkey pem `Pem | `DSA data -> (* There is no entry point for parsing ONLY this format *) let pem = create_pem "DSA PRIVATE KEY" data in G.gnutls_x509_privkey_import gpkey pem `Pem | `EC data -> (* There is no entry point for parsing ONLY this format *) let pem = create_pem "EC PRIVATE KEY" data in G.gnutls_x509_privkey_import gpkey pem `Pem | `PKCS8 data -> G.gnutls_x509_privkey_import_pkcs8 gpkey data `Der "" [`Plain] | `PKCS8_encrypted data -> ( match pw_opt with | None -> failwith "No password for encrypted PKCS8 data" | Some pw -> G.gnutls_x509_privkey_import_pkcs8 gpkey data `Der pw [] ) ); G.gnutls_certificate_set_x509_key gcred (Array.of_list gcrts) gpkey ) keys; G.gnutls_certificate_set_verify_flags gcred []; { gcred = `Certificate gcred } let create_x509_credentials ?(system_trust=false) ?(trust=[]) ?(revoke=[]) ?(keys=[]) () = trans_exn (create_x509_credentials_1 ~system_trust ~trust ~revoke ~keys) () let create_endpoint ~role ~recv ~send ~peer_name config = if peer_name=None && role=`Client && config.peer_auth <> `None then failwith "TLS configuration error: authentication required, \ but no peer_name set"; let f() = let flags = [ (role :> G.gnutls_init_flags_flag) ] in let session = G.gnutls_init flags in let ep = { role; recv; send; config; our_cert = None; session; peer_name; state = `Start; trans_eof = false; } in let recv1 mem = let n = recv mem in if Bigarray.Array1.dim mem > 0 && n=0 then ep.trans_eof <- true; n in G.b_set_pull_callback session recv1; G.b_set_push_callback session send; G.gnutls_priority_set session config.priority; G.gnutls_credentials_set session config.credentials.gcred; if role = `Client then ( match peer_name with | None -> () | Some n -> G.gnutls_server_name_set session `Dns n ); if role = `Server && config.peer_auth <> `None then G.gnutls_certificate_server_set_request session (match config.peer_auth with | `Optional -> `Request | `Required -> `Require | `None -> assert false ); ep in trans_exn f () exception Stashed of role * config * G.gnutls_session_t * string option * raw_credentials option * state * bool let stash_endpoint ep = G.b_set_pull_callback ep.session (fun _ -> 0); G.b_set_push_callback ep.session (fun _ _ -> 0); let exn = Stashed(ep.role, ep.config, ep.session, ep.peer_name, ep.our_cert, ep.state, ep.trans_eof) in ep.state <- `End; exn let restore_endpoint ~recv ~send exn = match exn with | Stashed(role,config,session,peer_name,our_cert,state,trans_eof) -> let ep = { role; recv; send; config; session; peer_name; our_cert; state; trans_eof } in let recv1 mem = let n = recv mem in if Bigarray.Array1.dim mem > 0 && n=0 then ep.trans_eof <- true; n in G.b_set_pull_callback session recv1; G.b_set_push_callback session send; ep | _ -> failwith "Nettls_gnutls.restore_endpoint: bad exception value" let resume_client ~recv ~send ~peer_name config data = let f() = let flags = [ `Client ] in let session = G.gnutls_init flags in G.gnutls_session_set_data session data; let ep = { role = `Client; recv; send; config; our_cert = None; session; peer_name; state = `Start; trans_eof = false; } in let recv1 mem = let n = recv mem in if Bigarray.Array1.dim mem > 0 && n=0 then ep.trans_eof <- true; n in G.b_set_pull_callback session recv1; G.b_set_push_callback session send; G.gnutls_priority_set session config.priority; G.gnutls_credentials_set session config.credentials.gcred; ep in trans_exn f () let get_state ep = ep.state let get_config ep = ep.config let at_transport_eof ep = ep.trans_eof let endpoint_exn ?(warnings=false) ep f arg = try f arg with | G.Error `Again -> if G.gnutls_record_get_direction ep.session then raise Exc.EAGAIN_WR else raise Exc.EAGAIN_RD | G.Error `Interrupted -> raise (Unix.Unix_error(Unix.EINTR, "Nettls_gnutls", "")) | G.Error `Rehandshake -> if ep.state = `Switching then raise (Exc.TLS_switch_response true) else raise Exc.TLS_switch_request | G.Error (`Warning_alert_received as code) -> if G.gnutls_alert_get ep.session = `No_renegotiation then raise (Exc.TLS_switch_response false) else let code' = G.gnutls_strerror_name code in if warnings then raise(Exc.TLS_warning code') else raise(Exc.TLS_error code') | G.Error code -> let code' = G.gnutls_strerror_name code in if warnings && not(G.gnutls_error_is_fatal code) then raise(Exc.TLS_warning code') else raise(Exc.TLS_error code') let unexpected_state() = raise(Exc.TLS_error "NETTLS_UNEXPECTED_STATE") let update_our_cert ep = (* our_cert: if the session is resumed, our_cert should already be filled in by the [retrieve] callback (because GnuTLS omit this certificate in its own serialization format) *) if ep.our_cert = None then (* So far only X509... *) trans_exn (fun () -> ep.our_cert <- Some (try `X509 (G.gnutls_certificate_get_ours ep.session) with | G.Null_pointer -> `Anonymous ) ) () let hello ep = if ep.state <> `Start && ep.state <> `Handshake && ep.state <> `Switching then unexpected_state(); ep.state <- `Handshake; endpoint_exn ~warnings:true ep G.gnutls_handshake ep.session; update_our_cert ep; ep.state <- `Data_rw let bye ep how = if ep.state <> `End then ( if ep.state <> `Data_rw && ep.state <> `Data_r && ep.state <> `Data_w then unexpected_state(); if how <> Unix.SHUTDOWN_RECEIVE then ( let ghow, new_state = match how with | Unix.SHUTDOWN_SEND -> `Wr, (if ep.state = `Data_w then `End else `Data_r) | Unix.SHUTDOWN_ALL -> `Rdwr, `End | Unix.SHUTDOWN_RECEIVE -> assert false in endpoint_exn ~warnings:true ep (G.gnutls_bye ep.session) ghow; ep.state <- new_state ) ) let verify ep = let f() = let cert_ok, name_ok = if G.gnutls_certificate_get_peers ep.session = [| |] then ( (* No certificate available *) if ep.config.peer_auth = `Required then raise(Exc.TLS_error (G.gnutls_strerror_name `No_certificate_found)); (true, true) ) else ( if ep.config.peer_auth = `None then (* Checks turned off *) (true, true) else let status_l = G.gnutls_certificate_verify_peers2 ep.session in let cert_ok = status_l = [] in (* failwith(sprintf "Certificate verification failed with codes: " ^ (String.concat ", " (List.map G.string_of_verification_status_flag status_l))); *) let name_ok = match ep.peer_name with | None -> false | Some pn -> let der_peer_certs = G.gnutls_certificate_get_peers ep.session in assert(der_peer_certs <> [| |]); let peer_cert = G.gnutls_x509_crt_init() in G.gnutls_x509_crt_import peer_cert der_peer_certs.(0) `Der; let ok = G.gnutls_x509_crt_check_hostname peer_cert pn in ok in (cert_ok, name_ok) ) in let ok = ep.config.verify ep cert_ok name_ok in if not ok then raise(Exc.TLS_error "NETTLS_VERIFICATION_FAILED"); () in trans_exn f () let get_endpoint_creds ep = match ep.our_cert with | Some c -> c | None -> failwith "get_endpoint_creds: unavailable" let get_peer_creds ep = (* So far only X509... *) trans_exn (fun () -> try let certs = G.gnutls_certificate_get_peers ep.session in if certs = [| |] then `Anonymous else `X509 certs.(0) with | G.Null_pointer -> `Anonymous ) () let get_peer_creds_list ep = (* So far only X509... *) trans_exn (fun () -> try let certs = G.gnutls_certificate_get_peers ep.session in if certs = [| |] then [ `Anonymous ] else List.map (fun c -> `X509 c) (Array.to_list certs) with | G.Null_pointer -> [ `Anonymous ] ) () let switch ep conf = if ep.state <> `Data_rw && ep.state <> `Data_w && ep.state <> `Switching then unexpected_state(); ep.state <- `Switching; ep.config <- conf; endpoint_exn ~warnings:true ep G.gnutls_rehandshake ep.session; ep.state <- `Data_rs let accept_switch ep conf = if ep.state <> `Data_rw && ep.state <> `Data_w && ep.state <> `Accepting then unexpected_state(); ep.state <- `Accepting; ep.config <- conf; endpoint_exn ~warnings:true ep G.gnutls_handshake ep.session; update_our_cert ep; ep.state <- `Data_rw let refuse_switch ep = if ep.state <> `Data_rw && ep.state <> `Data_w && ep.state <> `Refusing then unexpected_state(); ep.state <- `Refusing; endpoint_exn ~warnings:true ep (G.gnutls_alert_send ep.session `Warning) `No_renegotiation; ep.state <- `Data_rw let send ep buf n = if ep.state <> `Data_rw && ep.state <> `Data_w then unexpected_state(); endpoint_exn ~warnings:true ep (G.gnutls_record_send ep.session buf) n let recv ep buf = if ep.state = `Data_w || ep.state = `End then 0 else ( if ep.state <> `Data_rw && ep.state <> `Data_r && ep.state <> `Data_rs then unexpected_state(); let n = endpoint_exn ~warnings:true ep (G.gnutls_record_recv ep.session) buf in if Bigarray.Array1.dim buf > 0 && n=0 then ep.state <- (if ep.state = `Data_rw then `Data_w else `End); n ) let recv_will_not_block ep = let f() = G.gnutls_record_check_pending ep.session > 0 in trans_exn f () let get_session_id ep = trans_exn (fun () -> G.gnutls_session_get_id ep.session ) () let get_session_data ep = trans_exn (fun () -> G.gnutls_session_get_data ep.session ) () let get_cipher_suite_type ep = "X509" (* so far only this is supported *) let get_cipher_algo ep = let f() = G.gnutls_cipher_get_name (G.gnutls_cipher_get ep.session) in trans_exn f () let get_kx_algo ep = let f() = G.gnutls_kx_get_name (G.gnutls_kx_get ep.session) in trans_exn f () let get_mac_algo ep = let f() = G.gnutls_mac_get_name (G.gnutls_mac_get ep.session) in trans_exn f () let get_compression_algo ep = let f() = G.gnutls_compression_get_name (G.gnutls_compression_get ep.session) in trans_exn f () let get_cert_type ep = let f() = G.gnutls_certificate_type_get_name (G.gnutls_certificate_type_get ep.session) in trans_exn f () let get_protocol ep = let f() = G.gnutls_protocol_get_name (G.gnutls_protocol_get_version ep.session) in trans_exn f () let get_addressed_servers ep = let rec get k = try let n1, t = G.gnutls_server_name_get ep.session k in let n2 = match t with | `Dns -> `Domain n1 in n2 :: get(k+1) with | G.Error `Requested_data_not_available -> [] in trans_exn get 0 let set_session_cache ~store ~remove ~retrieve ep = let g_store key data = update_our_cert ep; let r = { ser_data = data; ser_our_cert = ep.our_cert } in store key (Marshal.to_string r []) in let g_retrieve key = let s = retrieve key in let r = (Marshal.from_string s 0 : serialized_session) in (* HACK: *) ep.our_cert <- r.ser_our_cert; r.ser_data in G.b_set_db_callbacks ep.session g_store remove g_retrieve let gnutls_credentials c = c.gcred let gnutls_session ep = ep.session end let make_tls (exc : (module Netsys_crypto_types.TLS_EXCEPTIONS)) = let module Self = struct let self = ref Not_found end in let module Exc = (val exc : Netsys_crypto_types.TLS_EXCEPTIONS) in let module Impl = Make_TLS_1(Self)(Exc) in let () = Self.self := I (module Impl) in (module Impl : GNUTLS_PROVIDER) (* module Make_TLS (Exc:Netsys_crypto_types.TLS_EXCEPTIONS) : GNUTLS_PROVIDER = (val make_tls (module Exc) : GNUTLS_PROVIDER) *) module GNUTLS = (val make_tls (module Netsys_types)) module TLS = (GNUTLS : Netsys_crypto_types.TLS_PROVIDER) let gnutls = (module GNUTLS : GNUTLS_PROVIDER) let tls = (module TLS : Netsys_crypto_types.TLS_PROVIDER) let endpoint ep = let module EP = struct module TLS = GNUTLS let endpoint = ep end in (module EP : GNUTLS_ENDPOINT) let downcast p = let module P = (val p : Netsys_crypto_types.TLS_PROVIDER) in match P.implementation() with | I tls -> tls | _ -> raise Not_found let downcast_endpoint ep_mod = let module EP = (val ep_mod : Netsys_crypto_types.TLS_ENDPOINT) in let module T = (val downcast (module EP.TLS)) in let module EP1 = struct module TLS = T let endpoint = (Obj.magic EP.endpoint) end in (module EP1 : GNUTLS_ENDPOINT) let rec filter_map f l = match l with | x :: l' -> ( try let x' = f x in x' :: filter_map f l' with | Not_found -> filter_map f l' ) | [] -> [] let of_list l = List.fold_left (fun acc (n,v) -> StrMap.add n v acc) StrMap.empty l module Basic_symmetric_crypto : Netsys_crypto_types.SYMMETRIC_CRYPTO = struct open Nettls_nettle_bindings open Nettls_gnutls_bindings open Netsys_crypto_modes.Symmetric_cipher let no_iv = [ 0, 0 ] (* The ciphers we access directly via the Nettle API. These are all in ECB or STREAM mode) *) let nettle_basic_props = [ "aes128", ("AES-128", "ECB", [ 16, 16 ], no_iv, 16); "aes192", ("AES-192", "ECB", [ 24, 24 ], no_iv, 16); "aes256", ("AES-256", "ECB", [ 32, 32 ], no_iv, 16); "arcfour128", ("ARCFOUR-128", "STREAM", [ 16, 16; 1, 256 ], no_iv, 1); "arctwo40", ("RC2-40", "ECB", [ 8, 8; 1, 256 ], no_iv, 8); "arctwo64", ("RC2-64", "ECB", [ 8, 8; 1, 256 ], no_iv, 8); "arctwo128", ("RC2-128", "ECB", [ 8, 8; 1, 256 ], no_iv, 8); "blowfish", ("BLOWFISH", "ECB", [ 16, 16; 8, 56 ], no_iv, 8); "camellia128", ("CAMELLIA-128", "ECB", [ 16, 16 ], no_iv, 16); "camellia192", ("CAMELLIA-192", "ECB", [ 24, 24 ], no_iv, 16); "camellia256", ("CAMELLIA-256", "ECB", [ 32, 32 ], no_iv, 16); "cast128", ("CAST-128", "ECB", [ 16, 16; 5, 16 ], no_iv, 8); (* "chacha" - does not fit in here (no way to set nonce) *) "des", ("DES-56", "ECB", [ 8, 8 ], no_iv, 8); "des3", ("3DES-112", "ECB", [ 24, 24 ], no_iv, 8); (* "salsa20" - does not fit in here (no way to set nonce) *) "serpent128", ("SERPENT-128", "ECB", [ 16, 16 ], no_iv, 16); "serpent192", ("SERPENT-192", "ECB", [ 24, 24 ], no_iv, 16); "serpent256", ("SERPENT-256", "ECB", [ 32, 32 ], no_iv, 16); "twofish128", ("TWOFISH-128", "ECB", [ 16, 16 ], no_iv, 16); "twofish192", ("TWOFISH-192", "ECB", [ 24, 24 ], no_iv, 16); "twofish256", ("TWOFISH-256", "ECB", [ 32, 32 ], no_iv, 16); (* "arctwo_gutmann128" is non-standard *) ] let nettle_basic_props_m = of_list nettle_basic_props let check_key l key_lengths = if not (List.exists (fun (min,max) -> l >= min && l <= max) key_lengths) then failwith "create: invalid key length for this cipher" let check_iv l iv_lengths = if not (List.exists (fun (min,max) -> l >= min && l <= max) iv_lengths) then failwith "create: invalid iv length for this cipher" let no_mac _ = failwith "mac: not supported by this cipher" let nettle_basic_ciphers = let l = Array.to_list (net_nettle_ciphers()) @ Array.to_list (net_ext_ciphers()) in filter_map (fun nc -> let (name,mode,key_lengths,iv_lengths,dc) = StrMap.find (net_nettle_cipher_name nc) nettle_basic_props_m in let set_iv s = if s <> "" then invalid_arg "set_iv: empty string expected" in let set_header s = () in let create key = let lkey = String.length key in check_key lkey key_lengths; let ctx = net_nettle_create_cipher_ctx nc in let first = ref true in let encrypt inbuf outbuf = let lbuf = Bigarray.Array1.dim inbuf in if lbuf <> Bigarray.Array1.dim outbuf then invalid_arg "encrypt: output buffer must have same size \ as input buffer"; if lbuf mod dc <> 0 then invalid_arg (sprintf "encrypt: buffers must be multiples \ of %d" dc); if !first then net_nettle_set_encrypt_key nc ctx key; first := false; net_nettle_encrypt nc ctx lbuf outbuf inbuf in let decrypt inbuf outbuf = let lbuf = Bigarray.Array1.dim inbuf in if lbuf <> Bigarray.Array1.dim outbuf then invalid_arg "decrypt: output buffer must have same size \ as input buffer"; if lbuf mod dc <> 0 then invalid_arg (sprintf "decrypt: buffers must be multiples \ of %d" dc); if !first then net_nettle_set_decrypt_key nc ctx key; first := false; net_nettle_decrypt nc ctx lbuf outbuf inbuf; true in { set_iv; set_header; encrypt; decrypt; mac = no_mac; } in { name; mode; key_lengths; iv_lengths; block_constraint = dc; supports_aead = false; create; } ) l (* GCM. This is optional. Later Nettle versions have also a generic API for aead: TODO. *) let iv_gcm = [ 12, 12; 0, 256 ] let nettle_gcm_aes_props = if net_have_gcm_aes() then [ ("AES-128", "GCM", [ 16, 16 ], iv_gcm, 1); ("AES-192", "GCM", [ 24, 24 ], iv_gcm, 1); ("AES-256", "GCM", [ 32, 32 ], iv_gcm, 1); ] else [] let nettle_gcm_aes_ciphers = List.map (fun (name, mode, key_lengths, iv_lengths, bs) -> let create key = let lkey = String.length key in check_key lkey key_lengths; let ctx = ref None in let iv = ref "" in let hdr = ref "" in let set_iv s = check_iv (String.length s) iv_lengths; iv := s in let set_header s = hdr := s in let get_ctx() = match !ctx with | None -> let c = net_nettle_gcm_aes_init() in nettle_gcm_aes_set_key c key; nettle_gcm_aes_set_iv c !iv; nettle_gcm_aes_update c !hdr; ctx := Some c; c | Some c -> c in let encrypt inbuf outbuf = let lbuf = Bigarray.Array1.dim inbuf in if lbuf <> Bigarray.Array1.dim outbuf then invalid_arg "encrypt: output buffer must have same size \ as input buffer"; if lbuf mod bs <> 0 then invalid_arg (sprintf "encrypt: buffers must be multiples \ of %d" bs); let c = get_ctx() in nettle_gcm_aes_encrypt c lbuf outbuf inbuf in let decrypt inbuf outbuf = let lbuf = Bigarray.Array1.dim inbuf in if lbuf <> Bigarray.Array1.dim outbuf then invalid_arg "decrypt: output buffer must have same size \ as input buffer"; if lbuf mod bs <> 0 then invalid_arg (sprintf "decrypt: buffers must be multiples \ of %d" bs); let c = get_ctx() in nettle_gcm_aes_decrypt c lbuf outbuf inbuf; true in let mac() = let c = get_ctx() in let s = String.make 16 'X' in nettle_gcm_aes_digest c s; s in { set_iv; set_header; encrypt; decrypt; mac; } in { name; mode; key_lengths; iv_lengths; block_constraint = bs; supports_aead = true; create; } ) nettle_gcm_aes_props (* The ciphers we access via the GnuTLS API. These are all CBC or GCM. GnuTLS has sometimes ways to accelerate the cipher, so prefer this. This is optional. *) let iv_16 = [ 16, 16 ] let iv_8 = [ 8, 8 ] let gnutls_basic_props = if net_have_crypto() then [ "AES-128-CBC", ("AES-128", "CBC", [ 16, 16 ], iv_16, 16); "AES-192-CBC", ("AES-192", "CBC", [ 24, 24 ], iv_16, 16); "AES-256-CBC", ("AES-256", "CBC", [ 32, 32 ], iv_16, 16); "CAMELLIA-128-CBC", ("CAMELLIA-128", "CBC", [ 16, 16 ], iv_16, 16); "CAMELLIA-128-GCM", ("CAMELLIA-128", "GCM", [ 16, 16 ], iv_gcm, 1); "CAMELLIA-192-CBC", ("CAMELLIA-192", "CBC", [ 24, 24 ], iv_16, 16); "CAMELLIA-192-GCM", ("CAMELLIA-192", "GCM", [ 24, 24 ], iv_gcm, 1); "CAMELLIA-256-CBC", ("CAMELLIA-256", "CBC", [ 32, 32 ], iv_16, 16); "CAMELLIA-256-GCM", ("CAMELLIA-256", "GCM", [ 32, 32 ], iv_gcm, 1); "DES-CBC", ("DES-56", "CBC", [ 8, 8 ], iv_8, 8); "3DES-CBC", ("3DES-112", "CBC", [ 24, 24 ], iv_8, 8); "SALSA20-256", ("SALSA20-256", "STREAM", [ 32, 32 ], iv_8, 1); ] else [] let gnutls_basic_props_m = of_list gnutls_basic_props let gnutls_basic_ciphers = let l = gnutls_cipher_list() in filter_map (fun algo -> let gname = gnutls_cipher_get_name algo in let (name,mode,key_lengths,iv_lengths,dc) = StrMap.find gname gnutls_basic_props_m in let create key = let lkey = String.length key in check_key lkey key_lengths; let ctx = ref None in let iv = ref "" in let hdr = ref "" in let set_iv s = check_iv (String.length s) iv_lengths; iv := s in let set_header s = hdr := s in let get_ctx() = match !ctx with | None -> let c = gnutls_cipher_init algo key !iv in if mode = "GCM" then gnutls_cipher_add_auth c !hdr; ctx := Some c; c | Some c -> c in let encrypt inbuf outbuf = let lbuf = Bigarray.Array1.dim inbuf in if lbuf <> Bigarray.Array1.dim outbuf then invalid_arg "encrypt: output buffer must have same size \ as input buffer"; if lbuf mod dc <> 0 then invalid_arg (sprintf "encrypt: buffers must be multiples \ of %d" dc); let c = get_ctx() in gnutls_cipher_encrypt2 c inbuf outbuf in let decrypt inbuf outbuf = let lbuf = Bigarray.Array1.dim inbuf in if lbuf <> Bigarray.Array1.dim outbuf then invalid_arg "decrypt: output buffer must have same size \ as input buffer"; if lbuf mod dc <> 0 then invalid_arg (sprintf "decrypt: buffers must be multiples \ of %d" dc); let c = get_ctx() in try gnutls_cipher_decrypt2 c inbuf outbuf; true with _ -> false in let mac() = match mode with | "GCM" -> let c = get_ctx() in let s = String.create 16 in gnutls_cipher_tag c s; s | _ -> no_mac() in { set_iv; set_header; encrypt; decrypt; mac; } in { name; mode; key_lengths; iv_lengths; block_constraint = dc; supports_aead = (mode = "GCM"); create; } ) l include Netsys_crypto_modes.Bundle(struct (* later defs override earlier defs *) let ciphers = nettle_basic_ciphers @ nettle_gcm_aes_ciphers @ gnutls_basic_ciphers end) end module Symmetric_crypto = Netsys_crypto_modes.Add_modes(Basic_symmetric_crypto) module Digests : Netsys_crypto_types.DIGESTS = struct open Nettls_nettle_bindings type digest_ctx = { add : Netsys_types.memory -> unit; finish : unit -> string } type digest = { name : string; size : int; block_length : int; create : unit -> digest_ctx; } let props = [ "md2", ( "MD2-128", 16, 16 ); "md4", ( "MD4-128", 16, 64 ); "md5", ( "MD5-128", 16, 64 ); "sha1", ( "SHA1-160", 20, 64 ); "sha256", ( "SHA2-256", 32, 64 ); "sha224", ( "SHA2-224", 28, 64 ); "sha384", ( "SHA2-384", 48, 128 ); "sha512", ( "SHA2-512", 64, 128 ); "sha3_256", ( "SHA3-256", 32, 136 ); "sha3_224", ( "SHA3-224", 28, 144 ); "sha3_384", ( "SHA3-384", 48, 104 ); "sha3_512", ( "SHA3-512", 64, 72 ); "ripemd160", ( "RIPEMD-160", 20, 64 ); "gosthash94", ( "GOSTHASH94-256", 32, 32 ); ] let props_m = of_list props let digests = filter_map (fun h -> let (name,size,blocklen) = StrMap.find (net_nettle_hash_name h) props_m in let create() = let ctx = net_nettle_create_hash_ctx h in let () = net_nettle_hash_init h ctx in let add mem = net_nettle_hash_update h ctx mem in let finish() = let s = String.make size 'X' in net_nettle_hash_digest h ctx s; s in { add; finish } in { name; size; block_length = blocklen; create } ) (Array.to_list (net_nettle_hashes())) let digests_m = of_list (List.map (fun dg -> dg.name, dg) digests) let find name = StrMap.find name digests_m let name dg = dg.name let size dg = dg.size let block_length dg = dg.block_length let create dg = dg.create() let add ctx mem = ctx.add mem let finish ctx = ctx.finish() end let init() = Nettls_gnutls_bindings.gnutls_global_init(); Netsys_crypto.set_current_tls (module TLS : Netsys_crypto_types.TLS_PROVIDER); Netsys_crypto.set_current_symmetric_crypto (module Symmetric_crypto : Netsys_crypto_types.SYMMETRIC_CRYPTO); Netsys_crypto.set_current_digests (module Digests : Netsys_crypto_types.DIGESTS) let () = init()