[−][src]Crate opaque_ke
An implementation of the OPAQUE asymmetric password authentication key exchange protocol
Overview
OPAQUE is a protocol between a client and a server. They must first agree on a collection of primitives to be kept consistent throughout protocol execution. These include:
- an authenticated encryption scheme,
- a finite cyclic group along with a point representation, and
- a keypair type.
We will use the following choices in this example:
use chacha20poly1305::ChaCha20Poly1305; use curve25519_dalek::ristretto::RistrettoPoint; use opaque_ke::keypair::X25519KeyPair;
This implementation is in sync with draft-krawczyk-cfrg-opaque-05, with a concrete instantiation of the authenticated key exchange protocol using 3DH. In the future, we plan to add support for other KE protocols as well.
Setup
To setup the protocol, the server begins by generating a static keypair:
use rand_core::{OsRng, RngCore}; let mut rng = OsRng; let server_kp = X25519KeyPair::generate_random(&mut rng)?;
The server must persist this keypair for the registration and login steps, where the public component will be used by the client during both registration and login, and the private component will be used by the server during login.
Registration
The registration protocol between the client and server consists of four steps along with three messages, denoted
as r1
, r2
, and r3
. Before registration begins, it is expected that the server's static public key, server_kp.public()
,
has been transmitted to the client in an offline step. A successful execution of the registration protocol results in the
server producing a password file corresponding to the tuple combination of (password, pepper, server public key) provided by
the client. This password file is typically stored server-side, and retrieved upon future login attempts made by the client.
In the first step (client registration start), the client chooses a registration password and an optional "pepper", and
runs ClientRegistration::start
to produce a message r1
:
use rand_core::{OsRng, RngCore}; let mut client_rng = OsRng; let (r1, client_state) = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::start( b"password", Some(b"pepper"), &mut client_rng, )?;
r1
is sent to the server, and client_state
must be persisted on the client for the final step of client
registration.
In the second step (server registration start), the server takes as input the r1
message from the client and runs
ServerRegistration::start
to produce r2
:
let mut server_rng = OsRng; let (r2, server_state) = ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start( r1, &mut server_rng, )?;
r2
is returned to the client, and server_state
must be persisted on the server for the final step of server
registration.
In the third step (client registration finish), the client takes as input the r2
message from the server, along
with the server's static public key server_kp.public()
, and uses client_state
from the first step to run
finish
and produce a message r3
along with the key derivation key kd_key_registration
:
let (r3, kd_key_registration) = client_state.finish::<_, X25519KeyPair>(r2, server_kp.public(), &mut client_rng)?;
r3
is sent to the server, and the client can optionally use kd_key_registration
for applications that choose to
process user information beyond the OPAQUE functionality (e.g., additional secrets or credentials).
In the fourth step of registration, the server takes as input the r3
message from the client and uses
server_state
from the second step to run finish
and produce password_file
:
let password_file = server_state.finish(r3)?;
At this point, the client can be considered as successfully registered, and the server can store
password_file.to_bytes()
for use during the login protocol.
Login
The login protocol between a client and server also consists of four steps along with three messages, denoted as
l1
, l2
, and l3
. The server is expected to have access to the a password file corresponding to an output
of the registration phase. The login protocol will execute successfully only if the same tuple combination of
(password, pepper, server public key) is presented as was used in the registration phase that produced the
password file that the server is testing against.
In the first step (client login start), the client chooses a registration password and an optional "pepper", and runs
ClientLogin::start
to produce a message l1
:
let mut client_rng = OsRng; let (l1, client_state) = ClientLogin::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start( b"password", Some(b"pepper"), &mut client_rng, )?;
l1
is sent to the server, and client_state
must be persisted on the client for the final step of client login.
In the second step (server login start), the server takes as input the l1
message from the client, the server's
private key server_kp.private()
, along with a serialized version of the password file, password_file_bytes
, and
runs ServerLogin::start
to produce l2
:
use std::convert::TryFrom; let password_file = ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::try_from( &password_file_bytes[..], )?; let mut server_rng = OsRng; let (l2, server_state) = ServerLogin::start(password_file, &server_kp.private(), l1, &mut server_rng)?;
l2
is returned to the client, and server_state
must be persisted on the server for the final step of server login.
In the third step (client login finish), the client takes as input the l2
message from the server, along with the
server's static public key server_kp.public()
, and uses client_state
from the first step to run finish
and produce
a message l3
, the shared secret client_shared_secret
, and the key derivation key kd_key_login
:
let (l3, client_shared_secret, kd_key_login) = client_state.finish( l2, &server_kp.public(), &mut client_rng, )?; assert_eq!(kd_key_registration, kd_key_login);
Note that if the client supplies a tuple (password, pepper, server public key) that does not match the tuple
used to create the password file, then at this point the finish
algorithm outputs the error InvalidLoginError
.
If finish
completes successfully, then l3
is sent to the server, and (similarly to registration) the client
can use kd_key_login
for applications that can take advantage of the fact that this key is identical to
kd_key_registration
.
In the fourth step of login, the server takes as input the l3
message from the client and uses server_state
from
the second step to run finish
:
let server_shared_secret = server_state.finish(l3)?; assert_eq!(client_shared_secret, server_shared_secret);
If the protocol completes successfully, then the server obtains a server_shared_secret
which is guaranteed to
match client_shared_secret
. Otherwise, on failure, the finish
algorithm outputs the error InvalidLoginError
.
Modules
errors | A list of error types which are produced during an execution of the protocol |
keypair | Contains the keypair types that must be supplied for the OPAQUE API |
opaque | Provides the main OPAQUE API |