Adding digital locker example (#117)
This commit is contained in:
@@ -91,6 +91,26 @@ jobs:
|
|||||||
- name: Run expect (which then runs cargo run)
|
- name: Run expect (which then runs cargo run)
|
||||||
run: expect -f scripts/simple_login.exp
|
run: expect -f scripts/simple_login.exp
|
||||||
|
|
||||||
|
digital-locker-test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
name: test digital_locker command-line example
|
||||||
|
steps:
|
||||||
|
- name: install expect
|
||||||
|
run: sudo apt-get install expect
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: install rust
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: nightly
|
||||||
|
override: true
|
||||||
|
components: rustfmt, clippy
|
||||||
|
- name: Run expect (which then runs cargo run)
|
||||||
|
run: expect -f scripts/digital_locker.exp
|
||||||
|
|
||||||
benches:
|
benches:
|
||||||
name: cargo bench compilation
|
name: cargo bench compilation
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
Generated
+60
-1
@@ -1,5 +1,14 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
|
[[package]]
|
||||||
|
name = "aead"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.35"
|
version = "1.0.35"
|
||||||
@@ -133,6 +142,29 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chacha20"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed8738f14471a99f0e316c327e68fc82a3611cc2895fcb604b89eedaf8f39d95"
|
||||||
|
dependencies = [
|
||||||
|
"cipher",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chacha20poly1305"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af1fc18e6d90c40164bf6c317476f2a98f04661e310e79830366b7e914c58a8e"
|
||||||
|
dependencies = [
|
||||||
|
"aead",
|
||||||
|
"chacha20",
|
||||||
|
"cipher",
|
||||||
|
"poly1305",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cipher"
|
name = "cipher"
|
||||||
version = "0.2.5"
|
version = "0.2.5"
|
||||||
@@ -165,6 +197,12 @@ version = "0.1.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
|
checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpuid-bool"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "criterion"
|
name = "criterion"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
@@ -570,6 +608,7 @@ version = "0.3.0-pre.1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
|
"chacha20poly1305",
|
||||||
"criterion",
|
"criterion",
|
||||||
"curve25519-dalek",
|
"curve25519-dalek",
|
||||||
"digest",
|
"digest",
|
||||||
@@ -616,6 +655,16 @@ dependencies = [
|
|||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "poly1305"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4b7456bc1ad2d4cf82b3a016be4c2ac48daf11bf990c1603ebd447fe6f30fca8"
|
||||||
|
dependencies = [
|
||||||
|
"cpuid-bool 0.2.0",
|
||||||
|
"universal-hash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.8"
|
version = "0.2.8"
|
||||||
@@ -952,7 +1001,7 @@ checksum = "6e7aab86fe2149bad8c507606bdb3f4ef5e7b2380eb92350f56122cca72a42a8"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"block-buffer",
|
"block-buffer",
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"cpuid-bool",
|
"cpuid-bool 0.1.2",
|
||||||
"digest",
|
"digest",
|
||||||
"opaque-debug",
|
"opaque-debug",
|
||||||
]
|
]
|
||||||
@@ -1063,6 +1112,16 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "universal-hash"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf8parse"
|
name = "utf8parse"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ zeroize = "1.1.1"
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
anyhow = "1.0.35"
|
anyhow = "1.0.35"
|
||||||
base64 = "0.13.0"
|
base64 = "0.13.0"
|
||||||
|
chacha20poly1305 = "0.7.1"
|
||||||
criterion = "0.3.3"
|
criterion = "0.3.3"
|
||||||
hex = "0.4.2"
|
hex = "0.4.2"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
|||||||
@@ -0,0 +1,308 @@
|
|||||||
|
// 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 an implementation of a server-side secured digital locker using
|
||||||
|
//! the client's OPAQUE export key, over a command-line interface
|
||||||
|
//!
|
||||||
|
//! A client can password-protect a secret message to be stored in a digital locker,
|
||||||
|
//! controlled by the server. The locker's contents are only revealed to the holder
|
||||||
|
//! of the password when attempting to open the locker.
|
||||||
|
//!
|
||||||
|
//! 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 chacha20poly1305::aead::{Aead, NewAead};
|
||||||
|
use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce};
|
||||||
|
use rand_core::{OsRng, RngCore};
|
||||||
|
use rustyline::error::ReadlineError;
|
||||||
|
use rustyline::Editor;
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::process::exit;
|
||||||
|
|
||||||
|
use opaque_ke::{
|
||||||
|
ciphersuite::CipherSuite, keypair::KeyPair, ClientLogin, ClientLoginFinishParameters,
|
||||||
|
ClientLoginStartParameters, ClientRegistration, ClientRegistrationFinishParameters,
|
||||||
|
CredentialRequest, CredentialResponse, RegistrationRequest, RegistrationResponse,
|
||||||
|
RegistrationUpload, ServerLogin, ServerLoginStartParameters, 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Locker {
|
||||||
|
contents: Vec<u8>,
|
||||||
|
password_file: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given a key and plaintext, produce an AEAD ciphertext along with a nonce
|
||||||
|
fn encrypt(key: &[u8], plaintext: &[u8]) -> Vec<u8> {
|
||||||
|
let cipher = ChaCha20Poly1305::new(Key::from_slice(&key));
|
||||||
|
|
||||||
|
let mut rng = OsRng;
|
||||||
|
let mut nonce_bytes = [0u8; 12];
|
||||||
|
rng.fill_bytes(&mut nonce_bytes);
|
||||||
|
let nonce = Nonce::from_slice(&nonce_bytes);
|
||||||
|
|
||||||
|
let ciphertext = cipher.encrypt(nonce, plaintext.as_ref()).unwrap();
|
||||||
|
[nonce_bytes.to_vec(), ciphertext].concat()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt using a key and a ciphertext (nonce included) to recover the original plaintext
|
||||||
|
fn decrypt(key: &[u8], ciphertext: &[u8]) -> Vec<u8> {
|
||||||
|
let cipher = ChaCha20Poly1305::new(Key::from_slice(&key));
|
||||||
|
cipher
|
||||||
|
.decrypt(
|
||||||
|
Nonce::from_slice(&ciphertext[..12]),
|
||||||
|
ciphertext[12..].as_ref(),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Password-based registration and encryption of client secret message between a client and server
|
||||||
|
fn register_locker(
|
||||||
|
server_kp: &opaque_ke::keypair::X25519KeyPair,
|
||||||
|
password: String,
|
||||||
|
secret_message: String,
|
||||||
|
) -> Locker {
|
||||||
|
let mut client_rng = OsRng;
|
||||||
|
let client_registration_start_result =
|
||||||
|
ClientRegistration::<Default>::start(&mut client_rng, password.as_bytes()).unwrap();
|
||||||
|
let registration_request_bytes = client_registration_start_result.message.serialize();
|
||||||
|
|
||||||
|
// Client sends registration_request_bytes to server
|
||||||
|
|
||||||
|
let mut server_rng = OsRng;
|
||||||
|
let server_registration_start_result = ServerRegistration::<Default>::start(
|
||||||
|
&mut server_rng,
|
||||||
|
RegistrationRequest::deserialize(®istration_request_bytes[..]).unwrap(),
|
||||||
|
server_kp.public(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let registration_response_bytes = server_registration_start_result.message.serialize();
|
||||||
|
|
||||||
|
// Server sends registration_response_bytes to client
|
||||||
|
|
||||||
|
let client_finish_registration_result = client_registration_start_result
|
||||||
|
.state
|
||||||
|
.finish(
|
||||||
|
&mut client_rng,
|
||||||
|
RegistrationResponse::deserialize(®istration_response_bytes[..]).unwrap(),
|
||||||
|
ClientRegistrationFinishParameters::default(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let message_bytes = client_finish_registration_result.message.serialize();
|
||||||
|
|
||||||
|
// Client encrypts secret message using export key
|
||||||
|
let ciphertext = encrypt(
|
||||||
|
&client_finish_registration_result.export_key,
|
||||||
|
secret_message.as_bytes(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Client sends message_bytes to server
|
||||||
|
|
||||||
|
let password_file = server_registration_start_result
|
||||||
|
.state
|
||||||
|
.finish(RegistrationUpload::deserialize(&message_bytes[..]).unwrap())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Locker {
|
||||||
|
contents: ciphertext,
|
||||||
|
password_file: password_file.to_bytes(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open the contents of a locker with a password between a client and server
|
||||||
|
fn open_locker(
|
||||||
|
server_kp: &opaque_ke::keypair::X25519KeyPair,
|
||||||
|
password: String,
|
||||||
|
locker: &Locker,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
let mut client_rng = OsRng;
|
||||||
|
let client_login_start_result = ClientLogin::<Default>::start(
|
||||||
|
&mut client_rng,
|
||||||
|
password.as_bytes(),
|
||||||
|
ClientLoginStartParameters::default(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let credential_request_bytes = client_login_start_result.message.serialize();
|
||||||
|
|
||||||
|
// Client sends credential_request_bytes to server
|
||||||
|
|
||||||
|
let password_file = ServerRegistration::<Default>::try_from(&locker.password_file[..]).unwrap();
|
||||||
|
let mut server_rng = OsRng;
|
||||||
|
let server_login_start_result = ServerLogin::start(
|
||||||
|
&mut server_rng,
|
||||||
|
password_file,
|
||||||
|
&server_kp.private(),
|
||||||
|
CredentialRequest::deserialize(&credential_request_bytes[..]).unwrap(),
|
||||||
|
ServerLoginStartParameters::default(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let credential_response_bytes = server_login_start_result.message.serialize();
|
||||||
|
|
||||||
|
// Server sends credential_response_bytes to client
|
||||||
|
|
||||||
|
let result = client_login_start_result.state.finish(
|
||||||
|
CredentialResponse::deserialize(&credential_response_bytes[..]).unwrap(),
|
||||||
|
ClientLoginFinishParameters::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if result.is_err() {
|
||||||
|
// Client-detected login failure
|
||||||
|
return Err(String::from("Incorrect password, please try again."));
|
||||||
|
}
|
||||||
|
let client_login_finish_result = result.unwrap();
|
||||||
|
|
||||||
|
// Decrypt contents of locker
|
||||||
|
let plaintext = decrypt(&client_login_finish_result.export_key, &locker.contents);
|
||||||
|
String::from_utf8(plaintext).map_err(|_| String::from("UTF8 error"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut rng = OsRng;
|
||||||
|
let server_kp = Default::generate_random_keypair(&mut rng).unwrap();
|
||||||
|
|
||||||
|
let mut rl = Editor::<()>::new();
|
||||||
|
let mut registered_lockers: Vec<Locker> = vec![];
|
||||||
|
loop {
|
||||||
|
display_lockers(®istered_lockers);
|
||||||
|
|
||||||
|
println!("Enter an option (1 or 2):");
|
||||||
|
println!("1) Register a locker");
|
||||||
|
println!("2) Open a locker\n");
|
||||||
|
let readline = rl.readline("> ");
|
||||||
|
match readline {
|
||||||
|
Ok(line) => {
|
||||||
|
if line != "1" && line != "2" {
|
||||||
|
println!("Error: Invalid option (either specify 1 or 2)");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
match line.as_ref() {
|
||||||
|
"1" => {
|
||||||
|
let (password, secret_message) = get_two_strings(
|
||||||
|
"Choose a password",
|
||||||
|
"Set a secret message",
|
||||||
|
&mut rl,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
registered_lockers.push(register_locker(
|
||||||
|
&server_kp,
|
||||||
|
password,
|
||||||
|
secret_message,
|
||||||
|
));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
"2" => {
|
||||||
|
let (locker, password) = get_two_strings(
|
||||||
|
"Choose a locker number",
|
||||||
|
"Enter the password",
|
||||||
|
&mut rl,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let locker_index: usize = match locker.parse() {
|
||||||
|
Ok(index) => index,
|
||||||
|
Err(_) => {
|
||||||
|
println!("Could not find locker number");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if locker_index >= registered_lockers.len() {
|
||||||
|
println!("Could not find locker number");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
match open_locker(&server_kp, password, ®istered_lockers[locker_index]) {
|
||||||
|
Ok(contents) => {
|
||||||
|
println!("\n\nSuccess! Contents: {}\n\n", contents);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
println!(
|
||||||
|
"\n\nError encountered, could not open locker: {}\n\n",
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => exit(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
handle_error(err);
|
||||||
|
exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
|
||||||
|
fn display_lockers(lockers: &Vec<Locker>) {
|
||||||
|
let mut locker_numbers = vec![];
|
||||||
|
for (i, _) in lockers.iter().enumerate() {
|
||||||
|
locker_numbers.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"\nCurrently registered locker numbers: {:?}\n",
|
||||||
|
locker_numbers
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle readline errors
|
||||||
|
fn handle_error(err: ReadlineError) {
|
||||||
|
match err {
|
||||||
|
ReadlineError::Interrupted => {
|
||||||
|
println!("CTRL-C");
|
||||||
|
}
|
||||||
|
ReadlineError::Eof => {
|
||||||
|
println!("CTRL-D");
|
||||||
|
}
|
||||||
|
err => {
|
||||||
|
println!("Error: {:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A function run on the client which extracts two strings from the CLI
|
||||||
|
fn get_two_strings(
|
||||||
|
s1: &str,
|
||||||
|
s2: &str,
|
||||||
|
rl: &mut Editor<()>,
|
||||||
|
string1: Option<String>,
|
||||||
|
) -> (String, String) {
|
||||||
|
let query = if string1.is_none() { s1 } else { s2 };
|
||||||
|
let readline = rl.readline(&format!("{}: ", query));
|
||||||
|
match readline {
|
||||||
|
Ok(line) => match string1 {
|
||||||
|
Some(x) => (x, line),
|
||||||
|
None => get_two_strings(s1, s2, rl, Some(line)),
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
handle_error(err);
|
||||||
|
exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+40
-38
@@ -142,34 +142,6 @@ fn account_login(
|
|||||||
client_login_finish_result.shared_secret == server_login_finish_result.shared_secret
|
client_login_finish_result.shared_secret == server_login_finish_result.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, 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() {
|
fn main() {
|
||||||
let mut rng = OsRng;
|
let mut rng = OsRng;
|
||||||
let server_kp = Default::generate_random_keypair(&mut rng).unwrap();
|
let server_kp = Default::generate_random_keypair(&mut rng).unwrap();
|
||||||
@@ -192,7 +164,7 @@ fn main() {
|
|||||||
println!("Error: Invalid option (either specify 1 or 2)");
|
println!("Error: Invalid option (either specify 1 or 2)");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let (username, password) = get_username_and_password(&mut rl, None);
|
let (username, password) = get_two_strings("Username", "Password", &mut rl, None);
|
||||||
match line.as_ref() {
|
match line.as_ref() {
|
||||||
"1" => {
|
"1" => {
|
||||||
registered_users
|
registered_users
|
||||||
@@ -217,18 +189,48 @@ fn main() {
|
|||||||
_ => exit(0),
|
_ => exit(0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(ReadlineError::Interrupted) => {
|
|
||||||
println!("CTRL-C");
|
|
||||||
exit(0)
|
|
||||||
}
|
|
||||||
Err(ReadlineError::Eof) => {
|
|
||||||
println!("CTRL-D");
|
|
||||||
exit(0)
|
|
||||||
}
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
println!("Error: {:?}", err);
|
handle_error(err);
|
||||||
exit(0)
|
exit(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
|
||||||
|
// Handle readline errors
|
||||||
|
fn handle_error(err: ReadlineError) {
|
||||||
|
match err {
|
||||||
|
ReadlineError::Interrupted => {
|
||||||
|
println!("CTRL-C");
|
||||||
|
}
|
||||||
|
ReadlineError::Eof => {
|
||||||
|
println!("CTRL-D");
|
||||||
|
}
|
||||||
|
err => {
|
||||||
|
println!("Error: {:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A function run on the client which extracts two strings from the CLI
|
||||||
|
fn get_two_strings(
|
||||||
|
s1: &str,
|
||||||
|
s2: &str,
|
||||||
|
rl: &mut Editor<()>,
|
||||||
|
string1: Option<String>,
|
||||||
|
) -> (String, String) {
|
||||||
|
let query = if string1.is_none() { s1 } else { s2 };
|
||||||
|
let readline = rl.readline(&format!("{}: ", query));
|
||||||
|
match readline {
|
||||||
|
Ok(line) => match string1 {
|
||||||
|
Some(x) => (x, line),
|
||||||
|
None => get_two_strings(s1, s2, rl, Some(line)),
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
handle_error(err);
|
||||||
|
exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
#!/bin/expect -f
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
set timeout 1
|
||||||
|
spawn cargo run --example digital_locker
|
||||||
|
match_max 100000
|
||||||
|
sleep 1
|
||||||
|
expect "*
|
||||||
|
\r
|
||||||
|
Currently registered locker numbers: \\\[\\\]\r
|
||||||
|
\r
|
||||||
|
Enter an option (1 or 2):\r
|
||||||
|
1) Register a locker\r
|
||||||
|
2) Open a locker\r
|
||||||
|
\r
|
||||||
|
"
|
||||||
|
sleep .1
|
||||||
|
send -- "1\r"
|
||||||
|
expect "Choose a password: \r"
|
||||||
|
send -- "foo\r"
|
||||||
|
expect "Set a secret message: \r"
|
||||||
|
send -- "bar\r"
|
||||||
|
expect "*
|
||||||
|
\r
|
||||||
|
Currently registered locker numbers: \\\[0\\\]\r
|
||||||
|
\r
|
||||||
|
Enter an option (1 or 2):\r
|
||||||
|
1) Register a locker\r
|
||||||
|
2) Open a locker\r
|
||||||
|
\r
|
||||||
|
"
|
||||||
|
sleep .1
|
||||||
|
send -- "2\r"
|
||||||
|
expect "Choose a locker number: \r"
|
||||||
|
send -- "1\r"
|
||||||
|
expect "Password: \r"
|
||||||
|
send -- "foo\r"
|
||||||
|
expect "*Error: Could not find locker number\r
|
||||||
|
\r
|
||||||
|
Currently registered locker numbers: \\\[0\\\]\r
|
||||||
|
\r
|
||||||
|
Enter an option (1 or 2):\r
|
||||||
|
1) Register a locker\r
|
||||||
|
2) Open a locker\r
|
||||||
|
\r
|
||||||
|
"
|
||||||
|
sleep .1
|
||||||
|
send -- "2\r"
|
||||||
|
expect "Choose a locker number: \r"
|
||||||
|
send -- "0\r"
|
||||||
|
expect "Password: \r"
|
||||||
|
send -- "baz\r"
|
||||||
|
expect "*Error encountered, could not open locker: Incorrect password, please try again.\r
|
||||||
|
\r
|
||||||
|
Currently registered locker numbers: \\\[0\\\]\r
|
||||||
|
\r
|
||||||
|
Enter an option (1 or 2):\r
|
||||||
|
1) Register a locker\r
|
||||||
|
2) Open a locker\r
|
||||||
|
\r
|
||||||
|
"
|
||||||
|
sleep .1
|
||||||
|
send -- "2\r"
|
||||||
|
expect "Choose a locker number: \r"
|
||||||
|
send -- "0\r"
|
||||||
|
expect "Password: \r"
|
||||||
|
send -- "foo\r"
|
||||||
|
expect "*
|
||||||
|
\r
|
||||||
|
*Success! Contents: bar\r
|
||||||
|
\r
|
||||||
|
Currently registered locker numbers: \\\[0\\\]\r
|
||||||
|
\r
|
||||||
|
Enter an option (1 or 2):\r
|
||||||
|
1) Register a locker\r
|
||||||
|
2) Open a locker\r
|
||||||
|
\r
|
||||||
|
"
|
||||||
|
sleep .1
|
||||||
|
send -- ""
|
||||||
|
expect eof
|
||||||
+4
-1
@@ -31,6 +31,8 @@
|
|||||||
//! type SlowHash = opaque_ke::slow_hash::NoOpHash;
|
//! type SlowHash = opaque_ke::slow_hash::NoOpHash;
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
|
//! See [examples/simple_login.rs](https://github.com/novifinancial/opaque-ke/blob/master/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
|
//! 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`,
|
//! that the tests execute quickly. A real application should use an actual slow hashing function, such as `scrypt`,
|
||||||
@@ -509,7 +511,8 @@
|
|||||||
//! 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
|
//! 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.
|
//! 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
|
//! 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.
|
//! these secrets so that they remain hidden from the server (see [examples/digital_locker.rs](https://github.com/novifinancial/opaque-ke/blob/master/examples/digital_locker.rs)
|
||||||
|
//! for a working example).
|
||||||
//!
|
//!
|
||||||
//! You can access the export key from the `export_key` field of [ClientRegistrationFinishResult] and [ClientLoginFinishResult].
|
//! You can access the export key from the `export_key` field of [ClientRegistrationFinishResult] and [ClientLoginFinishResult].
|
||||||
//!
|
//!
|
||||||
|
|||||||
Reference in New Issue
Block a user