Closed DorianCoding closed 11 months ago
It would be helpful to provide a complete small reproducible example that only uses opaque-ke
.
cat Yes very sorry @daxpedda, I take the privilege to use the example (the example is complete but not small). I got this whereas I was expected three True. However I'm not an expert and maybe there is a parameter that I'm missing :
Registration finished
Result is true
Result is false
Result is false
//
// This source code is dual-licensed under either the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree or the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree. You may select, at your option, one of the above-listed
// licenses.
//! 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 std::collections::HashMap;
use std::process::exit;
use argon2::Argon2;
use generic_array::GenericArray;
use opaque_ke::ciphersuite::CipherSuite;
use opaque_ke::rand::rngs::OsRng;
use opaque_ke::{
ClientLogin, ClientLoginFinishParameters, ClientRegistration,
ClientRegistrationFinishParameters, CredentialFinalization, CredentialRequest,
CredentialResponse, RegistrationRequest, RegistrationResponse, RegistrationUpload, ServerLogin,
ServerLoginStartParameters, ServerRegistration, ServerRegistrationLen, ServerSetup,
};
// The ciphersuite trait allows to specify the underlying primitives that will
// be used in the OPAQUE protocol
#[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 = Argon2<'static>;
}
#[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 = Argon2<'static>;
}
// Password-based registration between a client and server
fn account_registration(
server_setup: &ServerSetup<DefaultCipherSuite>,
username: String,
password: String,
) -> GenericArray<u8, ServerRegistrationLen<DefaultCipherSuite>> {
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();
// Client sends registration_request_bytes to server
let server_registration_start_result = ServerRegistration::<DefaultCipherSuite>::start(
server_setup,
RegistrationRequest::deserialize(®istration_request_bytes).unwrap(),
username.as_bytes(),
)
.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,
password.as_bytes(),
RegistrationResponse::deserialize(®istration_response_bytes).unwrap(),
ClientRegistrationFinishParameters::default(),
)
.unwrap();
let message_bytes = client_finish_registration_result.message.serialize();
// Client sends message_bytes to server
let password_file = ServerRegistration::finish(
RegistrationUpload::<DefaultCipherSuite>::deserialize(&message_bytes).unwrap(),
);
password_file.serialize()
}
// Password-based login between a client and server
fn account_login(
server_setup: &ServerSetup<DefaultCipherSuite>,
username: String,
password: String,
password_file_bytes: &[u8],
) -> bool {
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();
// Client sends credential_request_bytes to server
let password_file =
ServerRegistration::<DefaultCipherSuite>::deserialize(password_file_bytes).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(),
username.as_bytes(),
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(
password.as_bytes(),
CredentialResponse::deserialize(&credential_response_bytes).unwrap(),
ClientLoginFinishParameters::default(),
);
if result.is_err() {
// Client-detected login failure
return false;
}
let client_login_finish_result = result.unwrap();
let credential_finalization_bytes = client_login_finish_result.message.serialize();
// Client sends credential_finalization_bytes to server
let server_login_finish_result = server_login_start_result
.state
.finish(CredentialFinalization::deserialize(&credential_finalization_bytes).unwrap())
.unwrap();
client_login_finish_result.session_key == server_login_finish_result.session_key
}
fn main() {
let mut rng = OsRng;
let server_setup = ServerSetup::<DefaultCipherSuite>::new(&mut rng);
let result = account_registration(
&server_setup,
String::from("dorian"),
String::from("coding"),
);
println!("Registration finished");
println!(
"Result is {}",
account_login(
&server_setup,
String::from("dorian"),
String::from("coding"),
result.as_slice()
)
);
let server_setup2 =
ServerSetup::<DefaultCipherSuite>::new_with_key(&mut rng, server_setup.keypair().clone());
println!(
"Result is {}",
account_login(
&server_setup2,
String::from("dorian"),
String::from("coding"),
result.as_slice()
)
);
rng = OsRng;
let server_setup2 =
ServerSetup::<DefaultCipherSuite>::new_with_key(&mut rng, server_setup.keypair().clone());
println!(
"Result is {}",
account_login(
&server_setup2,
String::from("dorian"),
String::from("coding"),
result.as_slice()
)
);
}
I believe the documentation on this is actually poor now that I look at it. The ServerSetup
also contains an OPRF seed, which has to stay the same between registration and logins.
Instead of ServerSetup::keypair()
+ ServerSetup::new_with_key()
you actually have to use ServerSetup::serialize()
+ ServerSetup::deserialize()
.
ServerSetup::new_with_key()
is only good if you want to use a certain key, and not one that is generated by opaque-ke
, or you want to use the same key but different OPRF seeds between clients.
The
ServerSetup
also contains an OPRF seed, which has to stay the same between registration and logins.
Okay this is what I was afraid of. It did work with the use of your commands. However I'm wondering now : how (this means get OPRF seed only) and what are the benefits of changing OPRF between clients? Is there any risk when sharing the same parameters between clients? Because in the draft it is written :
The oprf_seed value SHOULD be used for all clients; see Section 10.9.
This being said, I think an information in the documentation would be welcomed.
However I'm wondering now : how (this means get OPRF seed only) and what are the benefits of changing OPRF between clients? Is there any risk when sharing the same parameters between clients?
I was just about to link to the relevant part of the spec, but I found actually inconsistent recommendations, so I will open an issue to let them clarify it.
In any case, it's probably best if @kevinlewi chimes in, the actual expert here.
Because in the draft it is written :
The oprf_seed value SHOULD be used for all clients; see Section 10.9.
https://cfrg.github.io/draft-irtf-cfrg-opaque/draft-irtf-cfrg-opaque.html#section-3.1-1:
The server can use server_private_key and server_public_key with multiple clients and can opt to use multiple seeds (so long as they are kept consistent for each client).
This is the inconsistency I was talking about!
Yes I'm waiting for the answer but what I have understand is oprf_seed
must be the same if you use dummy login as you cannot know what oprf_seed
to send in case the user does not exist but if the server is compromised, it must be changed. And if the server key follows this faith and is trasmitted before to clients to avoid MITM attacks at login, this will break. Moreover if this is true, I doubt the new_from_key
function is useful. Moreover for the compromisation of the recent password change, it seems way easier to detect this as pointed out in another issue if the seed changes each time rather than being the same. For this reason the change if both server key and OPRF in case of compromise and keep both when everything is fine should be prioritized.
In the event of a server compromise that results in a re-registration of credentials for all compromised clients, the oprf_seed value MUST be resampled, resulting in a change in the oprf_key value for each client. Although this change can be detected by an adversary, it is only leaked upon password rotation after the exposure of the credential files, and equally affects all registered clients.
But if this is true, I doubt the
new_from_key
function is useful.
It's still useful for users who want to use a specific key, but it's not useful to rebuild the ServerSetup
for login.
Right, so ultimately we want to use the serialize and deserialize functions to persist the instance of ServerSetup. The new_with_key() and new() functions won't work for this. I put up a PR to clarify this in the docs. And thanks @daxpedda for chiming in :)
Hello and sorry if the question is evident.
I have implemented opaque and it works well when the server is up. However if I restart the server, I cannot connect anymore.
I have the same private key from ServerSetup using new_from_key and the public key is printed and is the same but the OsRng is different. Is it the problem ? if yes, how to save the OsRng parameters?
Of course, the password file is read succesfully each time.
Client :
Server (your public key represents the key from ServerSetup) :
The so-called code :
Thank you.