chore: rename quicproquo → quicprochat in Rust workspace
Rename all crate directories, package names, binary names, proto package/module paths, ALPN strings, env var prefixes, config filenames, mDNS service names, and plugin ABI symbols from quicproquo/qpq to quicprochat/qpc.
This commit is contained in:
142
crates/quicprochat-sdk/src/auth.rs
Normal file
142
crates/quicprochat-sdk/src/auth.rs
Normal file
@@ -0,0 +1,142 @@
|
||||
//! OPAQUE authentication — register and login via the v2 RPC protocol.
|
||||
//!
|
||||
//! Wraps the `opaque-ke` crate to perform the OPAQUE 3-message flow against
|
||||
//! the quicprochat server using prost-encoded protobuf messages over `RpcClient::call`.
|
||||
|
||||
use bytes::Bytes;
|
||||
use opaque_ke::{
|
||||
ClientLogin, ClientLoginFinishParameters, ClientRegistration,
|
||||
ClientRegistrationFinishParameters, CredentialResponse, RegistrationResponse,
|
||||
};
|
||||
use prost::Message;
|
||||
use quicprochat_core::{opaque_auth::OpaqueSuite, IdentityKeypair};
|
||||
use quicprochat_proto::{method_ids, qpc::v1};
|
||||
use quicprochat_rpc::client::RpcClient;
|
||||
|
||||
use crate::error::SdkError;
|
||||
|
||||
/// Register a new user account via the OPAQUE protocol.
|
||||
///
|
||||
/// If `identity` is `None`, a fresh Ed25519 keypair is generated.
|
||||
/// Returns the identity keypair bound to this account.
|
||||
pub async fn opaque_register(
|
||||
rpc: &RpcClient,
|
||||
username: &str,
|
||||
password: &str,
|
||||
identity: Option<&IdentityKeypair>,
|
||||
) -> Result<IdentityKeypair, SdkError> {
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
|
||||
// Generate or use provided identity.
|
||||
let keypair = match identity {
|
||||
Some(kp) => IdentityKeypair::from_seed(*kp.seed_bytes()),
|
||||
None => IdentityKeypair::generate(),
|
||||
};
|
||||
|
||||
// ── Step 1: Registration Start ─────────────────────────────────────────
|
||||
let reg_start = ClientRegistration::<OpaqueSuite>::start(&mut rng, password.as_bytes())
|
||||
.map_err(|e| SdkError::AuthFailed(format!("OPAQUE register start: {e}")))?;
|
||||
|
||||
let start_req = v1::OpaqueRegisterStartRequest {
|
||||
username: username.to_string(),
|
||||
request: reg_start.message.serialize().to_vec(),
|
||||
};
|
||||
let resp_bytes = rpc
|
||||
.call(method_ids::OPAQUE_REGISTER_START, Bytes::from(start_req.encode_to_vec()))
|
||||
.await?;
|
||||
|
||||
let start_resp = v1::OpaqueRegisterStartResponse::decode(resp_bytes)
|
||||
.map_err(|e| SdkError::AuthFailed(format!("decode register_start response: {e}")))?;
|
||||
|
||||
let reg_response = RegistrationResponse::<OpaqueSuite>::deserialize(&start_resp.response)
|
||||
.map_err(|e| SdkError::AuthFailed(format!("invalid registration response: {e}")))?;
|
||||
|
||||
// ── Step 2: Registration Finish ────────────────────────────────────────
|
||||
let reg_finish = reg_start
|
||||
.state
|
||||
.finish(
|
||||
&mut rng,
|
||||
password.as_bytes(),
|
||||
reg_response,
|
||||
ClientRegistrationFinishParameters::<OpaqueSuite>::default(),
|
||||
)
|
||||
.map_err(|e| SdkError::AuthFailed(format!("OPAQUE register finish: {e}")))?;
|
||||
|
||||
let finish_req = v1::OpaqueRegisterFinishRequest {
|
||||
username: username.to_string(),
|
||||
upload: reg_finish.message.serialize().to_vec(),
|
||||
identity_key: keypair.public_key_bytes().to_vec(),
|
||||
};
|
||||
let resp_bytes = rpc
|
||||
.call(method_ids::OPAQUE_REGISTER_FINISH, Bytes::from(finish_req.encode_to_vec()))
|
||||
.await?;
|
||||
|
||||
let finish_resp = v1::OpaqueRegisterFinishResponse::decode(resp_bytes)
|
||||
.map_err(|e| SdkError::AuthFailed(format!("decode register_finish response: {e}")))?;
|
||||
|
||||
if !finish_resp.success {
|
||||
return Err(SdkError::AuthFailed("server rejected registration".into()));
|
||||
}
|
||||
|
||||
Ok(keypair)
|
||||
}
|
||||
|
||||
/// Log in via the OPAQUE protocol and receive a session token.
|
||||
///
|
||||
/// `identity_key` is the 32-byte Ed25519 public key to bind to this session.
|
||||
/// Returns the session token bytes.
|
||||
pub async fn opaque_login(
|
||||
rpc: &RpcClient,
|
||||
username: &str,
|
||||
password: &str,
|
||||
identity_key: &[u8],
|
||||
) -> Result<Vec<u8>, SdkError> {
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
|
||||
// ── Step 1: Login Start ────────────────────────────────────────────────
|
||||
let login_start = ClientLogin::<OpaqueSuite>::start(&mut rng, password.as_bytes())
|
||||
.map_err(|e| SdkError::AuthFailed(format!("OPAQUE login start: {e}")))?;
|
||||
|
||||
let start_req = v1::OpaqueLoginStartRequest {
|
||||
username: username.to_string(),
|
||||
request: login_start.message.serialize().to_vec(),
|
||||
};
|
||||
let resp_bytes = rpc
|
||||
.call(method_ids::OPAQUE_LOGIN_START, Bytes::from(start_req.encode_to_vec()))
|
||||
.await?;
|
||||
|
||||
let start_resp = v1::OpaqueLoginStartResponse::decode(resp_bytes)
|
||||
.map_err(|e| SdkError::AuthFailed(format!("decode login_start response: {e}")))?;
|
||||
|
||||
let credential_response = CredentialResponse::<OpaqueSuite>::deserialize(&start_resp.response)
|
||||
.map_err(|e| SdkError::AuthFailed(format!("invalid credential response: {e}")))?;
|
||||
|
||||
// ── Step 2: Login Finish ───────────────────────────────────────────────
|
||||
let login_finish = login_start
|
||||
.state
|
||||
.finish(
|
||||
&mut rng,
|
||||
password.as_bytes(),
|
||||
credential_response,
|
||||
ClientLoginFinishParameters::<OpaqueSuite>::default(),
|
||||
)
|
||||
.map_err(|e| SdkError::AuthFailed(format!("OPAQUE login finish (bad password?): {e}")))?;
|
||||
|
||||
let finish_req = v1::OpaqueLoginFinishRequest {
|
||||
username: username.to_string(),
|
||||
finalization: login_finish.message.serialize().to_vec(),
|
||||
identity_key: identity_key.to_vec(),
|
||||
};
|
||||
let resp_bytes = rpc
|
||||
.call(method_ids::OPAQUE_LOGIN_FINISH, Bytes::from(finish_req.encode_to_vec()))
|
||||
.await?;
|
||||
|
||||
let finish_resp = v1::OpaqueLoginFinishResponse::decode(resp_bytes)
|
||||
.map_err(|e| SdkError::AuthFailed(format!("decode login_finish response: {e}")))?;
|
||||
|
||||
if finish_resp.session_token.is_empty() {
|
||||
return Err(SdkError::AuthFailed("server returned empty session token".into()));
|
||||
}
|
||||
|
||||
Ok(finish_resp.session_token)
|
||||
}
|
||||
Reference in New Issue
Block a user