Library
Module
Module type
Parameter
Class
Class type
Spoke is an implementation of SPAKE2+EE, a Password-Authenticated Key Agreement. It permits to find an agreement between two people who share a weak password with a strong key via an exchange of few information. From the shared strong key, 2 people can initiate a communication via, for example, a symmetric cryptographic method such as GCM or ChaCha20 Poly1305.
This module wants to implement the necessary cryptographic primitives for this agreement.
We identify 2 persons, a server
and a client
. The server will generate some values and a salt and send it to the client with generate
. 2 values are then generated, secret
and public
. The first must be strictly known only by the server. The second must be transferred to the client.
The client can manipulate public
with hello
and generate a value to be passed to the server. The value is named X
. hello
returns a client
value which must be kept by the client.
The server can then manipulate this received value with server_compute
to produce 2 values (which can be concatenated) to send to the client. These values are: Y
and client_validator
. The first participates to the handshake, the second checks the shared key on the client side. server_compute
returns a server
which must be kept by the server and used later.
The client can finalise the agreement with client_compute
by finally calculating the
ype:shared_keys |
. It requires the Y
value and the client_validator
value as well as the client
value returned previously. It will then send a final value to ensure that the server can correctly produce the said shared key. The name of this value is the server_validator
.
Finally, the server can commit the agreement by checking the value transmitted by the client as well as the server
value generated previously and in turn generating the shared key.
The user is able to choose:
KDF
function used to generate values (see algorithm
)cipher
s which will be used by the client and the serverhash
algorithm used to craft internal valuesFollowing the handshake explanation above, here is an example of the order in which the primitives should be executed:
let run ~password =
let secret, public = Spoke.generate ~password ~algorithm:Pbkdf2 16 in
let+ client, _X = Spoke.hello ~public password in
let+ server, (_Y, client_validator) = Spoke.server_compute ~secret
~identity:("Bob", "Alice") _X in
let+ sk0, server_validator = Spoke.client_compute ~client
~identity:("Bob", "Alice") _Y client_validator in
let+ sk1 = Spoke.server_finalize ~server server_validator in
assert (sk0 = sk1)
The KDF
(Key Derivation Function) used to generate common informations between client & server.
type _ aead =
| GCM : Mirage_crypto.Cipher_block.AES.GCM.key aead
| CCM16 : Mirage_crypto.Cipher_block.AES.CCM16.key aead
| ChaCha20_Poly1305 : Mirage_crypto.Chacha20.key aead
The type of Authenticated Encryptions with Associated Data.
The type of shared keys.
type error = [
| `Point_is_not_on_prime_order_subgroup
| `Invalid_client_validator
| `Invalid_server_validator
| `Invalid_public_packet
| `Invalid_secret_packet
]
The type of errors.
val public_to_string : public -> string
public_to_string public
serializes the public
information into bytes. Therefore, the public information can be transmitted to a client throught a (secured?) channel.
public_of_string str
tries to deserialize a serie of bytes to a public information.
ciphers_of_public str
returns ciphers announced by the public
information serialized.
ciphers_of_client client
returns ciphers from a client
value.
ciphers_of_secret secret
returns ciphers from a secret
value.
val server_compute :
?g:Stdlib.Random.State.t ->
secret:secret ->
identity:(string * string) ->
string ->
(server * (string * string), [> error ]) Stdlib.result
server_compute ?g ~secret ~identity:(client, server) _X
tries to validate _X
with the given secret
information and identities. It returns a server
information if it succeed as well as a curve point _Y
and a client validator. _Y
and client_validator
should be transmitted to the client.
NOTE: identities is something known to both parties. The client must recognise the server with a unique identifier (like "Bob"
) and the server must recognise the client with a unique identifier (like "Alice"
). But more concretely, the identifier can be the IP address as well as the port of each of the two peers.
val client_compute :
client:client ->
identity:(string * string) ->
string ->
string ->
(shared_keys * string, [> error ]) Stdlib.result
client_compute ~client ~identity:(client, server) _Y client_validator
tries to validate _Y
and the client_validator
with the given client
information and identities (for more details, about identities, you can look at the note for server_compute
). It returns shared_keys
and the server validator if it succeed. The server_validator
should be transmitted to the server.
val server_finalize :
server:server ->
string ->
(shared_keys, [> error ]) Stdlib.result
server_finalize ~server server_validator
finalizes the handshake and tries to validate the given server_validator
with the given server
information. If it succeed, it returns the shared_keys
. Then, the user is able to initiate a secure communication with the given client.