Adding digital locker example (#117)
This commit is contained in:
@@ -91,6 +91,26 @@ jobs:
|
||||
- name: Run expect (which then runs cargo run)
|
||||
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:
|
||||
name: cargo bench compilation
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
Generated
+60
-1
@@ -1,5 +1,14 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# 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]]
|
||||
name = "anyhow"
|
||||
version = "1.0.35"
|
||||
@@ -133,6 +142,29 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "cipher"
|
||||
version = "0.2.5"
|
||||
@@ -165,6 +197,12 @@ version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
|
||||
|
||||
[[package]]
|
||||
name = "cpuid-bool"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba"
|
||||
|
||||
[[package]]
|
||||
name = "criterion"
|
||||
version = "0.3.3"
|
||||
@@ -570,6 +608,7 @@ version = "0.3.0-pre.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
"chacha20poly1305",
|
||||
"criterion",
|
||||
"curve25519-dalek",
|
||||
"digest",
|
||||
@@ -616,6 +655,16 @@ dependencies = [
|
||||
"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]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.8"
|
||||
@@ -952,7 +1001,7 @@ checksum = "6e7aab86fe2149bad8c507606bdb3f4ef5e7b2380eb92350f56122cca72a42a8"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"cfg-if 1.0.0",
|
||||
"cpuid-bool",
|
||||
"cpuid-bool 0.1.2",
|
||||
"digest",
|
||||
"opaque-debug",
|
||||
]
|
||||
@@ -1063,6 +1112,16 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.0"
|
||||
|
||||
@@ -37,6 +37,7 @@ zeroize = "1.1.1"
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.35"
|
||||
base64 = "0.13.0"
|
||||
chacha20poly1305 = "0.7.1"
|
||||
criterion = "0.3.3"
|
||||
hex = "0.4.2"
|
||||
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
|
||||
}
|
||||
|
||||
// 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() {
|
||||
let mut rng = OsRng;
|
||||
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)");
|
||||
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() {
|
||||
"1" => {
|
||||
registered_users
|
||||
@@ -217,18 +189,48 @@ fn main() {
|
||||
_ => exit(0),
|
||||
}
|
||||
}
|
||||
Err(ReadlineError::Interrupted) => {
|
||||
println!("CTRL-C");
|
||||
exit(0)
|
||||
}
|
||||
Err(ReadlineError::Eof) => {
|
||||
println!("CTRL-D");
|
||||
exit(0)
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Error: {:?}", err);
|
||||
handle_error(err);
|
||||
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;
|
||||
//! }
|
||||
//! ```
|
||||
//! 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
|
||||
//! 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
|
||||
//! 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.
|
||||
//! 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].
|
||||
//!
|
||||
|
||||
Reference in New Issue
Block a user