use std::process::exit;
use chacha20poly1305::aead::{Aead, NewAead};
use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce};
use generic_array::GenericArray;
use opaque_ke::ciphersuite::CipherSuite;
use opaque_ke::rand::rngs::OsRng;
use opaque_ke::rand::RngCore;
use opaque_ke::{
ClientLogin, ClientLoginFinishParameters, ClientRegistration,
ClientRegistrationFinishParameters, CredentialFinalization, CredentialRequest,
CredentialResponse, RegistrationRequest, RegistrationResponse, RegistrationUpload, ServerLogin,
ServerLoginStartParameters, ServerRegistration, ServerRegistrationLen, ServerSetup,
};
use rustyline::error::ReadlineError;
use rustyline::Editor;
#[allow(dead_code)]
struct DefaultCipherSuite;
#[cfg(feature = "ristretto255")]
impl CipherSuite for DefaultCipherSuite {
type OprfCs = opaque_ke::Ristretto255;
type KeGroup = opaque_ke::Ristretto255;
type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
type Ksf = opaque_ke::ksf::Identity;
}
#[cfg(not(feature = "ristretto255"))]
impl CipherSuite for DefaultCipherSuite {
type OprfCs = p256::NistP256;
type KeGroup = p256::NistP256;
type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
type Ksf = opaque_ke::ksf::Identity;
}
struct Locker {
contents: Vec<u8>,
password_file: GenericArray<u8, ServerRegistrationLen<DefaultCipherSuite>>,
}
fn encrypt(key: &[u8], plaintext: &[u8]) -> Vec<u8> {
let cipher = ChaCha20Poly1305::new(Key::from_slice(&key[..32]));
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()
}
fn decrypt(key: &[u8], ciphertext: &[u8]) -> Vec<u8> {
let cipher = ChaCha20Poly1305::new(Key::from_slice(&key[..32]));
cipher
.decrypt(
Nonce::from_slice(&ciphertext[..12]),
ciphertext[12..].as_ref(),
)
.unwrap()
}
fn register_locker(
server_setup: &ServerSetup<DefaultCipherSuite>,
locker_id: usize,
password: String,
secret_message: String,
) -> Locker {
let mut client_rng = OsRng;
let client_registration_start_result =
ClientRegistration::<DefaultCipherSuite>::start(&mut client_rng, password.as_bytes())
.unwrap();
let registration_request_bytes = client_registration_start_result.message.serialize();
let server_registration_start_result = ServerRegistration::<DefaultCipherSuite>::start(
server_setup,
RegistrationRequest::deserialize(®istration_request_bytes).unwrap(),
&locker_id.to_be_bytes(),
)
.unwrap();
let registration_response_bytes = server_registration_start_result.message.serialize();
let client_finish_registration_result = client_registration_start_result
.state
.finish(
&mut client_rng,
password.as_bytes(),
RegistrationResponse::deserialize(®istration_response_bytes).unwrap(),
ClientRegistrationFinishParameters::default(),
)
.unwrap();
let message_bytes = client_finish_registration_result.message.serialize();
let ciphertext = encrypt(
&client_finish_registration_result.export_key,
secret_message.as_bytes(),
);
let password_file = ServerRegistration::finish(
RegistrationUpload::<DefaultCipherSuite>::deserialize(&message_bytes).unwrap(),
);
Locker {
contents: ciphertext,
password_file: password_file.serialize(),
}
}
fn open_locker(
server_setup: &ServerSetup<DefaultCipherSuite>,
locker_id: usize,
password: String,
locker: &Locker,
) -> Result<String, String> {
let mut client_rng = OsRng;
let client_login_start_result =
ClientLogin::<DefaultCipherSuite>::start(&mut client_rng, password.as_bytes()).unwrap();
let credential_request_bytes = client_login_start_result.message.serialize();
let password_file =
ServerRegistration::<DefaultCipherSuite>::deserialize(&locker.password_file).unwrap();
let mut server_rng = OsRng;
let server_login_start_result = ServerLogin::start(
&mut server_rng,
server_setup,
Some(password_file),
CredentialRequest::deserialize(&credential_request_bytes).unwrap(),
&locker_id.to_be_bytes(),
ServerLoginStartParameters::default(),
)
.unwrap();
let credential_response_bytes = server_login_start_result.message.serialize();
let result = client_login_start_result.state.finish(
password.as_bytes(),
CredentialResponse::deserialize(&credential_response_bytes).unwrap(),
ClientLoginFinishParameters::default(),
);
if result.is_err() {
return Err(String::from("Incorrect password, please try again."));
}
let client_login_finish_result = result.unwrap();
let credential_finalization_bytes = client_login_finish_result.message.serialize();
let server_login_finish_result = server_login_start_result
.state
.finish(CredentialFinalization::deserialize(&credential_finalization_bytes).unwrap())
.unwrap();
let encrypted_locker_contents =
encrypt(&server_login_finish_result.session_key, &locker.contents);
let plaintext = decrypt(
&client_login_finish_result.export_key,
&decrypt(
&client_login_finish_result.session_key,
&encrypted_locker_contents,
),
);
String::from_utf8(plaintext).map_err(|_| String::from("UTF8 error"))
}
fn main() {
let mut rng = OsRng;
let server_setup = ServerSetup::<DefaultCipherSuite>::new(&mut rng);
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,
);
let locker_id = registered_lockers.len();
registered_lockers.push(register_locker(
&server_setup,
locker_id,
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!("Error: Could not find locker number");
continue;
}
};
if locker_index >= registered_lockers.len() {
println!("Error: Could not find locker number");
continue;
}
match open_locker(
&server_setup,
locker_index,
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)
}
}
}
}
fn display_lockers(lockers: &[Locker]) {
let mut locker_numbers = vec![];
for (i, _) in lockers.iter().enumerate() {
locker_numbers.push(i);
}
println!(
"\nCurrently registered locker numbers: {:?}\n",
locker_numbers
);
}
fn handle_error(err: ReadlineError) {
match err {
ReadlineError::Interrupted => {
println!("CTRL-C");
}
ReadlineError::Eof => {
println!("CTRL-D");
}
err => {
println!("Error: {:?}", err);
}
}
}
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)
}
}
}