[][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