From c7f6abb59152337d065716eef50fa3c0498c0e47 Mon Sep 17 00:00:00 2001 From: Kevin Lewi Date: Mon, 26 Oct 2020 11:52:15 -0700 Subject: [PATCH] Adding simple login example and fixing some docs (#67) --- Cargo.lock | 105 ++++++++++++++++-- Cargo.toml | 1 + examples/simple_login.rs | 230 +++++++++++++++++++++++++++++++++++++++ src/lib.rs | 22 ++-- 4 files changed, 340 insertions(+), 18 deletions(-) create mode 100644 examples/simple_login.rs diff --git a/Cargo.lock b/Cargo.lock index 163fd07..a220eba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -92,12 +92,24 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "cc" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed67cbde08356238e75fc4656be4749481eeffb09e19f320a25237d5221c985d" + [[package]] name = "cfg-if" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "clap" version = "2.33.1" @@ -169,7 +181,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" dependencies = [ "autocfg", - "cfg-if", + "cfg-if 0.1.10", "crossbeam-utils", "lazy_static", "maybe-uninit", @@ -183,7 +195,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "crossbeam-utils", "maybe-uninit", ] @@ -195,7 +207,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" dependencies = [ "autocfg", - "cfg-if", + "cfg-if 0.1.10", "lazy_static", ] @@ -263,6 +275,27 @@ dependencies = [ "generic-array", ] +[[package]] +name = "dirs-next" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf36e65a80337bea855cd4ef9b8401ffce06a7baedf2e85ec467b1ac3f6e82b6" +dependencies = [ + "cfg-if 1.0.0", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99de365f605554ae33f115102a02057d4fc18b01f3284d6870be0938743cfe7d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "displaydoc" version = "0.1.7" @@ -308,7 +341,7 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "libc", "wasi", ] @@ -406,7 +439,7 @@ version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", ] [[package]] @@ -430,6 +463,18 @@ dependencies = [ "autocfg", ] +[[package]] +name = "nix" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83450fe6a6142ddd95fb064b746083fc4ef1705fe81f64a64e1d4b39f54a1055" +dependencies = [ + "bitflags", + "cc", + "cfg-if 0.1.10", + "libc", +] + [[package]] name = "num-traits" version = "0.2.12" @@ -463,7 +508,7 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "opaque-ke" -version = "0.2.0" +version = "0.2.1" dependencies = [ "anyhow", "base64", @@ -480,6 +525,7 @@ dependencies = [ "proptest", "rand", "rand_core", + "rustyline", "scrypt", "serde_json", "sha2", @@ -641,6 +687,16 @@ version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +[[package]] +name = "redox_users" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" +dependencies = [ + "getrandom", + "redox_syscall", +] + [[package]] name = "regex" version = "1.3.9" @@ -695,6 +751,25 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "rustyline" +version = "6.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0d5e7b0219a3eadd5439498525d4765c59b7c993ef0c12244865cd2d988413" +dependencies = [ + "cfg-if 0.1.10", + "dirs-next", + "libc", + "log", + "memchr", + "nix", + "scopeguard", + "unicode-segmentation", + "unicode-width", + "utf8parse", + "winapi", +] + [[package]] name = "ryu" version = "1.0.5" @@ -791,7 +866,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2933378ddfeda7ea26f48c555bdad8bb446bf8a3d17832dc83e380d444cfb8c1" dependencies = [ "block-buffer", - "cfg-if", + "cfg-if 0.1.10", "cpuid-bool", "digest", "opaque-debug", @@ -832,7 +907,7 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "libc", "rand", "redox_syscall", @@ -885,6 +960,12 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" +[[package]] +name = "unicode-segmentation" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" + [[package]] name = "unicode-width" version = "0.1.8" @@ -897,6 +978,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +[[package]] +name = "utf8parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" + [[package]] name = "version_check" version = "0.9.2" @@ -935,7 +1022,7 @@ version = "0.2.65" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3edbcc9536ab7eababcc6d2374a0b7bfe13a2b6d562c5e07f370456b1a8f33d" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "wasm-bindgen-macro", ] diff --git a/Cargo.toml b/Cargo.toml index 9ef29ed..c9f0e4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ lazy_static = "1.4.0" serde_json = "1.0.57" proptest = "0.10.1" rand = "0.7" +rustyline = "6.3.0" [[bench]] name = "oprf" diff --git a/examples/simple_login.rs b/examples/simple_login.rs new file mode 100644 index 0000000..9dd7b84 --- /dev/null +++ b/examples/simple_login.rs @@ -0,0 +1,230 @@ +// Copyright (c) Facebook, Inc. and its affiliates. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +//! Demonstrates a simple client-server password-based login protocol +//! using OPAQUE, over a command-line interface +//! +//! The client-server interactions are executed in a three-step protocol +//! within the account_registration (for password registration) and +//! account_login (for password login) functions. These steps +//! must be performed in the specific sequence outlined in each of these +//! functions. +//! +//! The CipherSuite trait allows the application to configure the +//! primitives used by OPAQUE, but must be kept consistent across the steps +//! of the protocol. +//! +//! In a more realistic client-server interaction, the client must send +//! messages over "the wire" to the server. These bytes are serialized +//! and explicitly annotated in the below functions. + +use rand_core::OsRng; +use rustyline::error::ReadlineError; +use rustyline::Editor; +use std::collections::HashMap; +use std::convert::TryFrom; +use std::process::exit; + +use opaque_ke::{ + ciphersuite::CipherSuite, + keypair::KeyPair, + opaque::{ + ClientLogin, ClientRegistration, LoginFirstMessage, LoginSecondMessage, LoginThirdMessage, + RegisterFirstMessage, RegisterSecondMessage, RegisterThirdMessage, ServerLogin, + ServerRegistration, + }, +}; + +// The ciphersuite trait allows to specify the underlying primitives +// that will be used in the OPAQUE protocol +#[allow(dead_code)] +struct Default; +impl CipherSuite for Default { + type Group = curve25519_dalek::ristretto::RistrettoPoint; + type KeyFormat = opaque_ke::keypair::X25519KeyPair; + type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH; + type Hash = sha2::Sha256; + type SlowHash = opaque_ke::slow_hash::NoOpHash; +} + +// Password-based registration between a client and server +fn account_registration( + server_kp: &opaque_ke::keypair::X25519KeyPair, + password: String, +) -> Vec { + let mut client_rng = OsRng; + let (r1, client_state) = + ClientRegistration::::start(password.as_bytes(), Some(b"pepper"), &mut client_rng) + .unwrap(); + let r1_bytes = r1.to_bytes(); + + // Client sends r1_bytes to server + + let mut server_rng = OsRng; + let (r2, server_state) = ServerRegistration::::start( + RegisterFirstMessage::try_from(&r1_bytes[..]).unwrap(), + &mut server_rng, + ) + .unwrap(); + let r2_bytes = r2.to_bytes(); + + // Server sends r2_bytes to client + + let (r3, _) = client_state + .finish( + RegisterSecondMessage::try_from(&r2_bytes[..]).unwrap(), + server_kp.public(), + &mut client_rng, + ) + .unwrap(); + let r3_bytes = r3.to_bytes(); + + // Client sends r3_bytes to server + + let password_file = server_state + .finish(RegisterThirdMessage::try_from(&r3_bytes[..]).unwrap()) + .unwrap(); + password_file.to_bytes() +} + +// Password-based login between a client and server +fn account_login( + server_kp: &opaque_ke::keypair::X25519KeyPair, + password: String, + password_file_bytes: &[u8], +) -> bool { + let mut client_rng = OsRng; + let (l1, client_state) = + ClientLogin::::start(password.as_bytes(), Some(b"pepper"), &mut client_rng) + .unwrap(); + let l1_bytes = l1.to_bytes(); + + // Client sends l1_bytes to server + + let password_file = ServerRegistration::::try_from(password_file_bytes).unwrap(); + let mut server_rng = OsRng; + let (l2, server_state) = ServerLogin::start( + password_file, + &server_kp.private(), + LoginFirstMessage::try_from(&l1_bytes[..]).unwrap(), + &mut server_rng, + ) + .unwrap(); + let l2_bytes = l2.to_bytes(); + + // Server sends l2_bytes to client + + let result = client_state.finish( + LoginSecondMessage::try_from(&l2_bytes[..]).unwrap(), + &server_kp.public(), + &mut client_rng, + ); + + if result.is_err() { + // Client-detected login failure + return false; + } + let (l3, client_shared_secret, _) = result.unwrap(); + let l3_bytes = l3.to_bytes(); + + // Client sends l3_bytes to server + + let server_shared_secret = server_state + .finish(LoginThirdMessage::try_from(&l3_bytes[..]).unwrap()) + .unwrap(); + + client_shared_secret == server_shared_secret +} + +// A function run on the client which extracts a username and password from the CLI +fn get_username_and_password(rl: &mut Editor<()>, username: Option) -> (String, String) { + let query = if username.is_none() { + "Username: " + } else { + "Password: " + }; + let readline = rl.readline(query); + match readline { + Ok(line) => match username { + Some(x) => (x, line), + None => get_username_and_password(rl, Some(line)), + }, + Err(ReadlineError::Interrupted) => { + println!("CTRL-C"); + exit(0) + } + Err(ReadlineError::Eof) => { + println!("CTRL-D"); + exit(0) + } + Err(err) => { + println!("Error: {:?}", err); + exit(0) + } + } +} + +fn main() { + let mut rng = OsRng; + let server_kp = Default::generate_random_keypair(&mut rng).unwrap(); + + let mut rl = Editor::<()>::new(); + let mut registered_users = HashMap::>::new(); + loop { + println!( + "\nCurrently registered usernames: {:?}\n", + registered_users.keys() + ); + + println!("Enter an option (1 or 2):"); + println!("1) Register a user"); + println!("2) Login as a user\n"); + let readline = rl.readline("> "); + match readline { + Ok(line) => { + if line != "1" && line != "2" { + println!("Error: Invalid option (either specify 1 or 2)"); + continue; + } + let (username, password) = get_username_and_password(&mut rl, None); + match line.as_ref() { + "1" => { + registered_users + .insert(username, account_registration(&server_kp, password)); + continue; + } + "2" => match registered_users.get(&username) { + Some(password_file_bytes) => { + if account_login(&server_kp, password, password_file_bytes) { + println!("\nLogin success!"); + } else { + // Note that at this point, the client knows whether or not the login + // succeeded. In this example, we simply rely on client-reported result + // of login, but in a real client-server implementation, the server may not + // know the outcome of login yet, and extra care must be taken to ensure + // that the server can learn the outcome as well. + println!("\nIncorrect password, please try again."); + } + } + None => println!("Error: Could not find username registered"), + }, + _ => exit(0), + } + } + Err(ReadlineError::Interrupted) => { + println!("CTRL-C"); + exit(0) + } + Err(ReadlineError::Eof) => { + println!("CTRL-D"); + exit(0) + } + Err(err) => { + println!("Error: {:?}", err); + exit(0) + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index ce39d7b..096304c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,7 +71,7 @@ //! ``` //! # use opaque_ke::{ //! # errors::ProtocolError, -//! # opaque::{ClientRegistration, ServerRegistration}, +//! # opaque::ServerRegistration, //! # keypair::{KeyPair, X25519KeyPair, SizedBytes}, //! # slow_hash::NoOpHash, //! # }; @@ -84,6 +84,7 @@ //! # type Hash = sha2::Sha256; //! # type SlowHash = opaque_ke::slow_hash::NoOpHash; //! # } +//! use opaque_ke::opaque::ClientRegistration; //! use rand_core::{OsRng, RngCore}; //! let mut client_rng = OsRng; //! let (r1, client_state) = ClientRegistration::::start( @@ -101,7 +102,7 @@ //! ``` //! # use opaque_ke::{ //! # errors::ProtocolError, -//! # opaque::{ClientRegistration, ServerRegistration}, +//! # opaque::ClientRegistration, //! # keypair::{KeyPair, X25519KeyPair, SizedBytes}, //! # slow_hash::NoOpHash, //! # }; @@ -121,6 +122,7 @@ //! # Some(b"pepper"), //! # &mut client_rng, //! # )?; +//! use opaque_ke::opaque::ServerRegistration; //! let mut server_rng = OsRng; //! let (r2, server_state) = ServerRegistration::::start(r1, &mut server_rng)?; //! # Ok::<(), ProtocolError>(()) @@ -155,7 +157,7 @@ //! # &mut client_rng, //! # )?; //! # let mut server_rng = OsRng; -//! let (r2, server_state) = ServerRegistration::::start(r1, &mut server_rng)?; +//! # let (r2, server_state) = ServerRegistration::::start(r1, &mut server_rng)?; //! # let server_kp = Default::generate_random_keypair(&mut server_rng)?; //! let (r3, export_key_registration) = //! client_state.finish(r2, server_kp.public(), &mut client_rng)?; @@ -190,7 +192,7 @@ //! # &mut client_rng, //! # )?; //! # let mut server_rng = OsRng; -//! let (r2, server_state) = ServerRegistration::::start(r1, &mut server_rng)?; +//! # let (r2, server_state) = ServerRegistration::::start(r1, &mut server_rng)?; //! # let server_kp = Default::generate_random_keypair(&mut server_rng)?; //! # let (r3, export_key_registration) = client_state.finish(r2, server_kp.public(), &mut client_rng)?; //! let password_file = server_state.finish(r3)?; @@ -212,7 +214,7 @@ //! ``` //! # use opaque_ke::{ //! # errors::ProtocolError, -//! # opaque::{ClientRegistration, ServerRegistration, ClientLogin, ServerLogin, LoginThirdMessage}, +//! # opaque::{ClientRegistration, ServerRegistration, ServerLogin, LoginThirdMessage}, //! # keypair::{KeyPair, X25519KeyPair, SizedBytes}, //! # slow_hash::NoOpHash, //! # }; @@ -226,6 +228,7 @@ //! # type SlowHash = opaque_ke::slow_hash::NoOpHash; //! # } //! # use rand_core::{OsRng, RngCore}; +//! use opaque_ke::opaque::ClientLogin; //! let mut client_rng = OsRng; //! let (l1, client_state) = ClientLogin::::start( //! b"password", @@ -242,7 +245,7 @@ //! ``` //! # use opaque_ke::{ //! # errors::ProtocolError, -//! # opaque::{ClientRegistration, ServerRegistration, ClientLogin, ServerLogin, LoginThirdMessage}, +//! # opaque::{ClientRegistration, ServerRegistration, ClientLogin, LoginThirdMessage}, //! # keypair::{KeyPair, X25519KeyPair, SizedBytes}, //! # slow_hash::NoOpHash, //! # }; @@ -263,7 +266,7 @@ //! # &mut client_rng, //! # )?; //! # let mut server_rng = OsRng; -//! let (r2, server_state) = ServerRegistration::::start(r1, &mut server_rng)?; +//! # let (r2, server_state) = ServerRegistration::::start(r1, &mut server_rng)?; //! # let server_kp = Default::generate_random_keypair(&mut server_rng)?; //! # let (r3, export_key_registration) = client_state.finish(r2, server_kp.public(), &mut client_rng)?; //! # let password_file_bytes = server_state.finish(r3)?.to_bytes(); @@ -272,6 +275,7 @@ //! # Some(b"pepper"), //! # &mut client_rng, //! # )?; +//! use opaque_ke::opaque::ServerLogin; //! use std::convert::TryFrom; //! let password_file = ServerRegistration::::try_from(&password_file_bytes[..])?; //! let mut server_rng = OsRng; @@ -308,7 +312,7 @@ //! # &mut client_rng, //! # )?; //! # let mut server_rng = OsRng; -//! let (r2, server_state) = ServerRegistration::::start(r1, &mut server_rng)?; +//! # let (r2, server_state) = ServerRegistration::::start(r1, &mut server_rng)?; //! # let server_kp = Default::generate_random_keypair(&mut server_rng)?; //! # let (r3, export_key_registration) = client_state.finish(r2, server_kp.public(), &mut client_rng)?; //! # let password_file_bytes = server_state.finish(r3)?.to_bytes(); @@ -365,7 +369,7 @@ //! # &mut client_rng, //! # )?; //! # let mut server_rng = OsRng; -//! let (r2, server_state) = ServerRegistration::::start(r1, &mut server_rng)?; +//! # let (r2, server_state) = ServerRegistration::::start(r1, &mut server_rng)?; //! # let server_kp = Default::generate_random_keypair(&mut server_rng)?; //! # let (r3, export_key) = client_state.finish(r2, server_kp.public(), &mut client_rng)?; //! # let password_file_bytes = server_state.finish(r3)?.to_bytes();