Crate opaque_ke[][src]

Expand description

An implementation of the OPAQUE asymmetric password authentication key exchange protocol

Note: This implementation is in sync with draft-irtf-cfrg-opaque-03, but this specification is subject to change, until the final version published by the IETF.

⚠️ Warning!

This library does not provide any mitigations against client enumeration attacks, where an adversary attempts to learn about whether or not a client has registered using OPAQUE with a server. The latest version of the OPAQUE CFRG draft (draft-irtf-cfrg-opaque-06) supports mitigations against client enumeration, but the version that this implementation is synced to does not.

Therefore, consumers of this library should either:

  • Ensure that client enumeration attacks are mitigated through other means in any applications that use this library as a login mechanism, or
  • Consider switching to a future version of this implementation that implements draft-irtf-cfrg-opaque-06 which handles client enumeration attacks.

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:

  • a finite cyclic group along with a point representation,
  • a key exchange protocol,
  • a hashing function, and
  • a slow hashing function.

We will use the following choices in this example:

use opaque_ke::ciphersuite::CipherSuite;
struct Default;
impl CipherSuite for Default {
    type Group = curve25519_dalek::ristretto::RistrettoPoint;
    type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
    type Hash = sha2::Sha512;
    type SlowHash = opaque_ke::slow_hash::NoOpHash;
}

See examples/simple_login.rs for a working example of a simple password-based login using OPAQUE.

Note that our choice of slow hashing function in this example, NoOpHash, is selected only to ensure that the tests execute quickly. A real application should use an actual slow hashing function, such as scrypt, which can be enabled through the slow-hash feature.

Setup

To set up the protocol, the server begins by generating a static keypair:

use rand::{rngs::OsRng, RngCore};
let mut rng = OsRng;
let server_kp = Default::generate_random_keypair(&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: RegistrationRequest, RegistrationResponse, and RegistrationUpload. A successful execution of the registration protocol results in the server producing a password file corresponding to the password provided by the client. This password file is typically stored server-side, and retrieved upon future login attempts made by the client.

Client Registration Start

In the first step of registration, the client chooses as input a registration password. The client runs ClientRegistration::start to produce a ClientRegistrationStartResult, which consists of a RegistrationRequest to be sent to the server and a ClientRegistration which must be persisted on the client for the final step of client registration.

use opaque_ke::ClientRegistration;
use rand::{rngs::OsRng, RngCore};
let mut client_rng = OsRng;
let client_registration_start_result = ClientRegistration::<Default>::start(
    &mut client_rng,
    b"password",
)?;

Server Registration Start

In the second step of registration, the server takes as input the instance of RegistrationRequest from the client, and the server’s public key server_kp.public(). The server runs ServerRegistration::start to produce an a ServerRegistrationStartResult, which consists of a RegistrationResponse to be returned to the client and a ServerRegistration which must be persisted on the server for the final step of server registration.

use opaque_ke::ServerRegistration;
let mut server_rng = OsRng;
let server_kp = Default::generate_random_keypair(&mut server_rng);
let server_registration_start_result = ServerRegistration::<Default>::start(
    &mut server_rng,
    client_registration_start_result.message,
    server_kp.public(),
)?;

Client Registration Finish

In the third step of registration, the client takes as input a RegistrationResponse from the server, and a ClientRegistration from the first step of registration. The client runs ClientRegistration::finish to produce a ClientRegistrationFinishResult, which consists of a RegistrationUpload to be sent to the server and an export_key field which can be used optionally as described in the Export Key section.

let client_registration_finish_result = client_registration_start_result.state.finish(
    &mut client_rng,
    server_registration_start_result.message,
    ClientRegistrationFinishParameters::default(),
)?;

Server Registration Finish

In the fourth step of registration, the server takes as input a RegistrationUpload from the client, and a ServerRegistration from the second step. The server runs ServerRegistration::finish to produce a finalized ServerRegistration. At this point, the client can be considered as successfully registered, and the server can invoke ServerRegistration::serialize to store the password file for use during the login protocol.

let password_file = server_registration_start_result.state.finish(
    client_registration_finish_result.message,
)?;

Login

The login protocol between a client and server also consists of four steps along with three messages: CredentialRequest, CredentialResponse, CredentialFinalization. The server is expected to have access to the password file corresponding to an output of the registration phase. The login protocol will execute successfully only if the same password was used in the registration phase that produced the password file that the server is testing against.

Client Login Start

In the first step of login, the client chooses as input a login password. The client runs ClientLogin::start to produce an output consisting of a CredentialRequest to be sent to the server, and a ClientLogin which must be persisted on the client for the final step of client login.

use opaque_ke::{ClientLogin, ClientLoginStartParameters};
let mut client_rng = OsRng;
let client_login_start_result = ClientLogin::<Default>::start(
  &mut client_rng,
  b"password",
  ClientLoginStartParameters::default(),
)?;

Server Login Start

In the second step of login, the server takes as input a CredentialRequest from the client, the server’s private key server_kp.private(), and the password file output from registration. The server runs ServerLogin::start to produce an output consisting of a CredentialResponse which is returned to the client, and a ServerLogin which must be persisted on the server for the final step of login.

use opaque_ke::{ServerLogin, ServerLoginStartParameters};
let password_file = ServerRegistration::<Default>::deserialize(&password_file_bytes[..])?;
let mut server_rng = OsRng;
let server_login_start_result = ServerLogin::start(
    &mut server_rng,
    password_file,
    &server_kp.private(),
    client_login_start_result.message,
    ServerLoginStartParameters::default(),
)?;

Client Login Finish

In the third step of login, the client takes as input a CredentialResponse from the server. The client runs ClientLogin::finish and produces an output consisting of a CredentialFinalization to be sent to the server to complete the protocol, the session_key sequence of bytes which will match the server’s session key upon a successful login.

let client_login_finish_result = client_login_start_result.state.finish(
    server_login_start_result.message,
    ClientLoginFinishParameters::default(),
)?;

Server Login Finish

In the fourth step of login, the server takes as input a CredentialFinalization from the client and runs ServerLogin::finish to produce an output consisting of the session_key sequence of bytes which will match the client’s session key upon a successful login.

let server_login_finish_result = server_login_start_result.state.finish(
    client_login_finish_result.message,
)?;

assert_eq!(
   client_login_finish_result.session_key,
   server_login_finish_result.session_key,
);

If the protocol completes successfully, then the server obtains a server_login_finish_result.session_key which is guaranteed to match client_login_finish_result.session_key (see the Session Key section). Otherwise, on failure, the ServerLogin::finish algorithm outputs the error InvalidLoginError.

Advanced Usage

This implementation offers support for several optional features of OPAQUE, described below. They are not critical to the execution of the main protocol, but can provide additional security benefits which can be suitable for various applications that rely on OPAQUE for authentication.

Session Key

Upon a successful completion of the OPAQUE protocol (the client runs login with the same password used during registration), the client and server have access to a session key, which is a pseudorandomly distributed 32-byte string which only the client and server know. Multiple login runs using the same password for the same client will produce different session keys, distributed as uniformly random strings. Thus, the session key can be used to establish a secure channel between the client and server.

The session key can be accessed from the session_key field of ClientLoginFinishResult and ServerLoginFinishResult. See the combination of Client Login Finish and Server Login Finish for example usage.

Checking Server Consistency

A ClientLoginFinishResult contains the server_s_pk field, which is represents the static public key of the server that is established during the setup phase. This can be used by the client to verify the authenticity of the server it engages with during the login phase. In particular, the client can check that the static public key of the server supplied during registration matches this field during login.

// During setup, server generates its static keypair
let server_kp = Default::generate_random_keypair(&mut server_rng);

// During setup or registration, the server transmits its static public key to the client
let server_s_pk = server_kp.public(); // obtained from the server

// And then later, during login...
let client_login_finish_result = client_login_start_result.state.finish(
    server_login_start_result.message,
    ClientLoginFinishParameters::default(),
)?;

// Check that the server's static public key matches what was obtained during
// setup or registration
assert_eq!(
    &client_login_finish_result.server_s_pk,
    server_s_pk,
);

Note that without this check over the consistency of the server’s static public key, a malicious actor could impersonate the registration server if it were able to copy the password file output during registration! Therefore, it is recommended to perform the following check in the application layer if the client can obtain a copy of the server’s static public key beforehand.

Export Key

The export key is a pseudorandomly distributed 32-byte string output by both the Client Registration Finish and Client Login Finish steps. The same export key string will be output by both functions only if the exact same password is passed to ClientRegistration::start and ClientLogin::start.

The export key retains as much secrecy as the password itself, and is similarly derived through an evaluation of the slow hashing function. Hence, only the parties which know the password the client uses during registration and login can recover this secret, as it is never exposed to the server. As a result, the export key can be used (separately from the OPAQUE protocol) to provide confidentiality and integrity to other data which only the client should be able to process. For instance, if the server is expected to maintain any client-side secrets which require a password to access, then this export key can be used to encrypt these secrets so that they remain hidden from the server (see examples/digital_locker.rs for a working example).

You can access the export key from the export_key field of ClientRegistrationFinishResult and ClientLoginFinishResult.

// During registration...
let client_registration_finish_result = client_registration_start_result.state.finish(
    &mut client_rng,
    server_registration_start_result.message,
    ClientRegistrationFinishParameters::default()
)?;

// And then later, during login...
let client_login_finish_result = client_login_start_result.state.finish(
    server_login_start_result.message,
    ClientLoginFinishParameters::default(),
)?;

assert_eq!(
    client_registration_finish_result.export_key,
    client_login_finish_result.export_key,
);

Custom Identifiers

Typically when applications use OPAQUE to authenticate a client to a server, the client has a registered “username” which is sent to the server to identify the corresponding password file established during registration. The server may also have an identifier corresponding to an entity (e.g. facebook.com). By default, neither of these public identifiers need to be supplied to the OPAQUE protocol.

But, for applications that wish to cryptographically bind these identities to the registered password file as well as the session key output by the login phase, these custom identifiers can be specified through ClientRegistrationFinishParameters::WithIdentifiers in Client Registration Finish:

let client_registration_finish_result = client_registration_start_result.state.finish(
    &mut client_rng,
    server_registration_start_result.message,
    ClientRegistrationFinishParameters::WithIdentifiers(
        b"username".to_vec(),
        b"facebook.com".to_vec(),
    ),
)?;

The same identifiers must also be supplied using ServerLoginStartParameters::WithIdentifiers in Server Login Start:

let server_login_start_result = ServerLogin::start(
    &mut server_rng,
    password_file,
    &server_kp.private(),
    client_login_start_result.message,
    ServerLoginStartParameters::WithIdentifiers(
        b"username".to_vec(),
        b"facebook.com".to_vec(),
    ),
)?;

as well as ClientLoginFinishParameters::WithIdentifiers in Client Login Finish:

let client_login_finish_result = client_login_start_result.state.finish(
    server_login_start_result.message,
    ClientLoginFinishParameters::WithIdentifiers(
        b"username".to_vec(),
        b"facebook.com".to_vec(),
    ),
)?;

Failing to supply the same pair of custom identifiers in any of the three steps above will result in an error in attempting to complete the protocol!

Key Exchange Additional Data

A key exchange protocol typically supports the passing of data between the two parties before the exchange is complete, so as to bind the integrity and/or confidentiality of application-specific data to the security of the key exchange. During the login phase, the client and server can pass additional data alongside the first two messages of the protocol, with confidential data being supported for the second message.

The following three messages support passing of additional data:

For the second login message, the WithInfoAndIdentifiers variant can be used to specify these fields in addition to custom identifiers, with the ordering of the fields as WithInfoAndIdentifiers(confidential_info, username, server_name).

Re-exports

pub use rand;

Modules

Defines the CipherSuite trait to specify the underlying primitives for OPAQUE

A list of error types which are produced during an execution of the protocol

Defines the Group trait to specify the underlying prime order group used in OPAQUE’s OPRF

A convenience trait for digest bounds used throughout the library

Includes instantiations of key exchange protocols used in the login step for OPAQUE

Contains the keypair types that must be supplied for the OPAQUE API

Defines the GroupWithMapToCurve trait to specify how to map a password to a curve point

Trait specifying a slow hashing function

Structs

The state elements the client holds to perform a login

Contains the fields that are returned by a client login finish

Contains the fields that are returned by a client login start

The state elements the client holds to register itself

Contains the fields that are returned by a client registration finish

Contains the fields that are returned by a client registration start

The answer sent by the client to the server, upon reception of the sealed envelope

The message sent by the user to the server, to initiate registration

The answer sent by the server to the user, upon reception of the login attempt

The message sent by the client to the server, to initiate registration

The answer sent by the server to the user, upon reception of the registration attempt

The final message from the client, containing sealed cryptographic identifiers

The state elements the server holds to record a login

Contains the fields that are returned by a server login finish

Contains the fields that are returned by a server login start

The state elements the server holds to record a registration

Contains the fields that are returned by a server registration start

Enums

Optional parameters for client login finish

Optional parameters for client login start

Optional parameters for client registration finish

Optional parameters for server login start