Adds the OpaqueSuite (Ristretto255 OPRF, TripleDH, SHA-512, identity KSF) shared by client and server, the httpsig signature-base builders, and the setup/credentials/account/device/SSE types for milestone M1. Cargo.lock changes only for kv-proto's new dependencies. Co-Authored-By: Claude Opus 5.5 <[email protected]>
79 lines
3.6 KiB
Rust
79 lines
3.6 KiB
Rust
//! The OPAQUE profile used for login, setup and password change (docs/02 "Authentication to the server").
|
|
//!
|
|
//! Client and server MUST both use [`OpaqueSuite`] with these conventions, or login fails:
|
|
//! * password input: the 32-byte `AUTH_INPUT`, which the client has already stretched with Argon2id,
|
|
//! so the suite's key-stretching function is the identity;
|
|
//! * credential identifier (server side only): the 16 raw bytes of the account UUID;
|
|
//! * default parameters everywhere (`ClientRegistrationFinishParameters::default()`,
|
|
//! `ClientLoginFinishParameters::default()`, `ServerLoginStartParameters::default()`):
|
|
//! no custom identifiers and no context string.
|
|
//!
|
|
//! Messages travel base64url-encoded (no padding) inside the JSON bodies in this crate, using
|
|
//! opaque-ke's own `serialize()` / `deserialize()`.
|
|
|
|
pub use opaque_ke;
|
|
|
|
/// Ristretto255 OPRF, Ristretto255 + TripleDH key exchange, SHA-512, identity KSF.
|
|
pub struct OpaqueSuite;
|
|
|
|
impl opaque_ke::CipherSuite for OpaqueSuite {
|
|
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(test)]
|
|
mod tests {
|
|
use opaque_ke::{
|
|
ClientLogin, ClientLoginFinishParameters, ClientRegistration, ClientRegistrationFinishParameters,
|
|
CredentialFinalization, CredentialRequest, CredentialResponse, RegistrationRequest, RegistrationResponse,
|
|
RegistrationUpload, ServerLogin, ServerLoginStartParameters, ServerRegistration, ServerSetup,
|
|
};
|
|
|
|
use rand_core::OsRng;
|
|
|
|
use super::OpaqueSuite;
|
|
|
|
/// Registration then login across serialized messages, as the client and server exchange them.
|
|
#[test]
|
|
fn registration_and_login_roundtrip() {
|
|
let mut rng = OsRng;
|
|
let setup = ServerSetup::<OpaqueSuite>::new(&mut rng);
|
|
let cred_id = uuid::Uuid::new_v4();
|
|
let auth_input = [7u8; 32];
|
|
|
|
let c = ClientRegistration::<OpaqueSuite>::start(&mut rng, &auth_input).unwrap();
|
|
let req = RegistrationRequest::deserialize(&c.message.serialize()).unwrap();
|
|
let s = ServerRegistration::<OpaqueSuite>::start(&setup, req, cred_id.as_bytes()).unwrap();
|
|
let resp = RegistrationResponse::deserialize(&s.message.serialize()).unwrap();
|
|
let fin = c.state.finish(&mut rng, &auth_input, resp, ClientRegistrationFinishParameters::default()).unwrap();
|
|
let upload = RegistrationUpload::<OpaqueSuite>::deserialize(&fin.message.serialize()).unwrap();
|
|
let record = ServerRegistration::finish(upload).serialize();
|
|
|
|
let login = |password: &[u8]| {
|
|
let c = ClientLogin::<OpaqueSuite>::start(&mut OsRng, password).unwrap();
|
|
let ke1 = CredentialRequest::deserialize(&c.message.serialize()).unwrap();
|
|
let record = ServerRegistration::deserialize(&record).unwrap();
|
|
let s = ServerLogin::start(
|
|
&mut OsRng,
|
|
&setup,
|
|
Some(record),
|
|
ke1,
|
|
cred_id.as_bytes(),
|
|
ServerLoginStartParameters::default(),
|
|
)
|
|
.unwrap();
|
|
let ke2 = CredentialResponse::deserialize(&s.message.serialize()).unwrap();
|
|
let Ok(done) = c.state.finish(password, ke2, ClientLoginFinishParameters::default()) else {
|
|
return false;
|
|
};
|
|
let ke3 = CredentialFinalization::deserialize(&done.message.serialize()).unwrap();
|
|
let server = s.state.finish(ke3).unwrap();
|
|
server.session_key == done.session_key
|
|
};
|
|
assert!(login(&auth_input));
|
|
assert!(!login(&[8u8; 32]));
|
|
}
|
|
}
|