(* $Id: netmech_scram.mli 2195 2015-01-01 12:23:39Z gerd $ *)
(** SCRAM mechanism for authentication (RFC 5802) *)
(** This implements SCRAM for SASL and GSSAPI.
{b This module needs the SHA-1 hash function. In order to use it,
initialize crypto support, e.g. by including the [nettls-gnutls]
packages and calling {!Nettls_gnutls.init}.}
As for all SASL mechanisms in OCamlnet, SASLprep is not automatically
called. Users of SCRAM should pass user names and passwords through
{!Netsaslprep.saslprep}.
*)
type ptype = [ `GSSAPI | `SASL | `HTTP ]
(** Profile types:
- [`GSSAPI]: as defined in RFC 5802, the gs2-header is omitted
- [`SASL]: as defined in RFC 5802
- [`HTTP]: at the moment this follows draft-ietf-httpauth-scram-auth-03,
and uses a different [gs2-header]
*)
type profile =
{ ptype : ptype;
hash_function : Netsys_digests.iana_hash_fn; (** Which hash function *)
return_unknown_user : bool; (** Whether servers exhibit the fact that the
user is unknown *)
iteration_count_limit : int; (** Largest supported iteration number *)
}
(** Profile *)
type cb = Netsys_sasl_types.cb
(** Using the same channel binding type as for SASL *)
type server_error =
[ `Invalid_encoding
| `Extensions_not_supported
| `Invalid_proof
| `Channel_bindings_dont_match
| `Server_does_support_channel_binding
| `Channel_binding_not_supported
| `Unsupported_channel_binding_type
| `Unknown_user
| `Invalid_username_encoding
| `No_resources
| `Other_error
| `Extension of string
]
(** Error codes of this protocol *)
type client_session
(** Session context for clients *)
type server_session
(** Session context for servers *)
exception Invalid_encoding of string * string
(** Raised by clients when something cannot be decoded. First string
is an error message, the second string the raw message that cannot
be decoded
*)
exception Invalid_username_encoding of string * string
(** Raised by clients when the username does not match the requirements.
Arguments as for [Invalid_encoding].
*)
exception Extensions_not_supported of string * string
(** Raised by clients when the server enables an unsupported extension.
Arguments as for [Invalid_encoding].
*)
exception Protocol_error of string
(** Raised by clients when the server violates the protocol. The argument
is a message.
*)
exception Invalid_server_signature
(** Raised by clients when the signature sent by the server is invalid
(i.e. the server does not know the client password)
*)
exception Server_error of server_error
(** Raised by clients when the server sent an error code *)
val profile : ?return_unknown_user:bool -> ?iteration_count_limit:int ->
ptype -> Netsys_digests.iana_hash_fn -> profile
(** Creates a profile *)
val string_of_server_error : server_error -> string
val server_error_of_string : string -> server_error
(** Conversion *)
val mechanism_name : profile -> string
(** The official name of the mechanism *)
(** {2 Clients} *)
(** The idea is to create a client session [s] first. The functions
[client_emit_flag] and [client_recv_flag] indicate now whether
the client needs to emit a new message, or whether it needs to
receive a message, respectively. Emission is done by [client_emit_message],
reception by [client_recv_message]. If everything goes well, the
protocol state advances, and finally [client_finish_flag] is true.
This indicates that the client is authenticated and that the server
knows the client's password. If an error occurs, an exception is
raised (see above for possibilities), and [client_error_flag] signals
[true].
*)
val create_client_session :
?nonce: string ->
profile -> string -> string -> client_session
(** [create_client_session p username password]: Creates a new client
session for profile [p] so that the client authenticates as user
[username], and proves its identity with the given [password].
*)
val create_client_session2 :
?nonce:string ->
profile -> string -> string -> string -> client_session
(** [create_client_session p username authzname password]: Like
[create_client_session], but also sets the authorization name
(only processed for the SASL profile).
*)
val client_configure_channel_binding : client_session -> cb -> unit
(** Sets whether to request channel binding.
*)
val client_emit_flag : client_session -> bool
(** Whether [client_emit_message] can now be called *)
val client_recv_flag : client_session -> bool
(** Whether [client_recv_message] can now be called *)
val client_finish_flag : client_session -> bool
(** Whether the client is authenticated and the server verified *)
val client_error_flag : client_session -> bool
(** Whether an error occurred, and the protocol cannot advance anymore *)
val client_channel_binding : client_session -> cb
(** Returns the channel binding *)
val client_emit_message : client_session -> string
(** Emits the next message to be sent to the server *)
val client_emit_message_kv : client_session ->
string option * (string * string) list
(** Emits the next message to be sent to the server. The message is not
encoded as a single string, but as [(gs2_opt, kv)] where
[gs2_opt] is the optional GS2 header (the production [gs2-header] from
the RFC), and [kv] contains the parameters as key/value pairs.
*)
val client_recv_message : client_session -> string -> unit
(** Receives the next message from the server *)
val client_protocol_key : client_session -> string option
(** The 128-bit protocol key for encrypting messages. This is available
as soon as the second client message is emitted.
*)
val client_user_name : client_session -> string
(** The user name *)
val client_authz_name : client_session -> string
(** The authorization name *)
val client_password : client_session -> string
(** The password *)
val client_export : client_session -> string
val client_import : string -> client_session
(** Exports a client session as string, and imports the string again.
The export format is just a marshalled Ocaml value.
*)
val client_prop : client_session -> string -> string
(** Returns a property of the client (or Not_found):
- "snonce"
- "cnonce"
- "salt"
- "i" (iteration_count)
- "protocol_key"
*)
(** {2 Servers} *)
(** The idea is to create a server session [s] first. The functions
[server_emit_flag] and [server_recv_flag] indicate now whether
the server needs to emit a new message, or whether it needs to
receive a message, respectively. Emission is done by [server_emit_message],
reception by [server_recv_message]. If everything goes well, the
protocol state advances, and finally [server_finish_flag] is true.
This indicates that the client could be authenticated.
If an error occurs, {b no} exception is raised, and the protocol
advances nevertheless, and finally the server sends an error token
to the client. After this, [server_error_flag] returns true.
*)
type credentials =
[ `Salted_password of string * string * int
| `Stored_creds of string * string * string * int
]
(** Two forms of providing credentials:
- [`Salted_password(spw,salt,iteration_count)]: get the
salted password with
[spw = salt_password h password salt iteration_count]
- [`Stored(stkey, srvkey, salt, iteration_count)]: get the
pair (stkey, srvkey) with
[stored_key h password salt iteration_count]
*)
val create_server_session :
?nonce:string ->
profile -> (string -> credentials) -> server_session
(** [create_server_session p auth]: Creates a new server session with
profile [p] and authenticator function [auth].
The function is [auth] is called when the credentials of the
client have been received to check whether the client can be
authenticated. It is called as
{[
let credentials = auth username
]}
where [username] is the user name. The function can now raise
[Not_found] if the user is unknown, or it can return the
credentials. Note that the cleartext password needs not to
be known. The credentials contain a salt and an iteration count:
[salt] is a random string, and [iteration_count] a
security parameter that should be at least 4096. Whereas [salt]
should be different for each user, the [iteration_count] can be
chosen as a constant (e.g. 4096). Now [salted_password] can be
computed from the cleartext password and these two extra parameters.
See [salt_password] below.
*)
val create_server_session2 :
?nonce:string ->
profile -> (string -> string -> credentials) -> server_session
(** Same as [create_server_session], but the authentication callback
gets two arguments:
{[
let credentials = auth username authzname
]}
where [authzname] is the passed authorization name (or "" if na).
*)
val create_salt : unit -> string
(** Creates a random string suited as salt *)
val salt_password : Netsys_digests.iana_hash_fn ->
string -> string -> int -> string
(** [let salted_password = salt_password h password salt iteration_count]
Use this now as credentials
[`Salted_password(salted_password,salt,iteration_count)].
As we do not implement [SASLprep] only passwords consisting of
US-ASCII characters are accepted ([Invalid_encoding] otherwise).
*)
val stored_key : Netsys_digests.iana_hash_fn ->
string -> string -> int -> string * string
(** [let stkey,srvkey = stored_key h password salt iteration_count]
Use this now as credentials
[`Stored_creds(stkey,srvkey,salt,iteration_count)].
*)
val server_emit_flag : server_session -> bool
(** Whether [server_emit_message] can now be called *)
val server_recv_flag : server_session -> bool
(** Whether [server_recv_message] can now be called *)
val server_finish_flag : server_session -> bool
(** Whether the client is authenticated *)
val server_error_flag : server_session -> bool
(** Whether an error occurred, and the protocol cannot advance anymore *)
val server_emit_message : server_session -> string
(** Emits the next message to be sent to the client *)
val server_emit_message_kv : server_session -> (string * string) list
(** Emits the next message to be sent to the client. The message is returned
as a list of key/value pairs.
*)
val server_recv_message : server_session -> string -> unit
(** Receives the next message from the client *)
val server_protocol_key : server_session -> string option
(** The 128-bit protocol key for encrypting messages. This is available
as soon as the second client message has been received.
*)
val server_channel_binding : server_session -> cb
(** Returns the channel binding requirement. It is
up to the application to enforce the binding. This information is
available as soon as the second client message has been received
*)
val server_user_name : server_session -> string option
(** The user name as transmitted from the client. This is returned here
even before the authentication is completed!
*)
val server_authz_name : server_session -> string option
(** The authorization name as transmitted from the client. This is returned
here
even before the authentication is completed!
*)
val server_export : server_session -> string
val server_import : string -> server_session
val server_import_any : string -> (string -> credentials) ->
server_session
val server_import_any2 : string -> (string -> string -> credentials) ->
server_session
(** Exports a server session as string, and imports the string again.
[server_import] can only import established sessions.
[server_import_any] can also import unfinished sessions, but one needs
to pass the authentication function as for [server_create_session].
[server_import_any2] uses the modified auth function as in
[server_create_session2].
*)
val server_prop : server_session -> string -> string
(** Returns a property of the client (or Not_found):
- "snonce"
- "cnonce"
- "salt"
- "i" (iteration_count)
- "protocol_key"
*)
(** {2 Confidentiality} *)
type specific_keys =
{ kc : string;
ke : string;
ki : string
}
(** The specific keys to use *)
(** This module implements AES in Ciphertext Stealing mode (see RFC 3962) *)
module AES_CTS : sig
val c : int
val m : int
val encrypt : string -> string -> string
val encrypt_mstrings :
string -> Netxdr_mstring.mstring list -> Netxdr_mstring.mstring list
val decrypt : string -> string -> string
val decrypt_mstrings :
string -> Netxdr_mstring.mstring list -> Netxdr_mstring.mstring list
val tests : (string * string * string) list
val run_tests : unit -> bool
val run_mtests : unit -> bool
end
(** This is the cryptosystem as defined in RFC 3961, so far needed here.
This uses [AES_CTS] as cipher, and SHA1-96 for signing.
*)
module Cryptosystem : sig
exception Integrity_error
val derive_keys : string -> int -> specific_keys
(** [derive_keys protocol_key usage]: Returns the specific keys for
this [protocol_key] and this [usage] numbers. See RFC 4121 for
applicable usage numbers
*)
val encrypt_and_sign : specific_keys -> string -> string
(** Encrypts the plaintext message and adds a signature to the
ciphertext.
Returns [ciphertext_with_signature].
*)
val encrypt_and_sign_mstrings :
specific_keys -> Netxdr_mstring.mstring list -> Netxdr_mstring.mstring list
(** Same, but with data representation as [mstring list] *)
val decrypt_and_verify : specific_keys -> string -> string
(** Decrypts the ciphertext and verifies the attached signature.
Returns the restored plaintext.
For very short plaintexts (< 16 bytes) there will be some
padding at the end ("residue"), as returned as [ec] above.
We ignore this problem generally,
because GSS-API adds a 16-byte header to the plaintext anyway,
so these short messages do not occur.
If the signature is not valid, the exception [Integrity_error]
is raised.
*)
val decrypt_and_verify_mstrings :
specific_keys -> Netxdr_mstring.mstring list -> Netxdr_mstring.mstring list
(** Same, but with data representation as [mstring list] *)
val get_ec : specific_keys -> int -> int
(** [let ec = get_ec e_keys n]:
Returns the required value for the "extra count" field of
RFC 4121 if the plaintext message has size [n]. Here,
[n] is the size of the payload message plus the token
header of 16 bytes, i.e. the function is always called with
[n >= 16].
Here, the returned [ec] value is always 0.
*)
val get_mic : specific_keys -> string -> string
(** Returns a message integrity code *)
val get_mic_mstrings :
specific_keys -> Netxdr_mstring.mstring list -> string
(** Same, but with data representation as [mstring list] *)
end
module Debug : sig
val enable : bool ref
(** Enable debugging of this module *)
end