kv-proto: OPAQUE suite, RFC 9421 signing profile and M1 auth wire types
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]>
This commit is contained in:
@@ -6,6 +6,9 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub mod httpsig;
|
||||
pub mod opaque;
|
||||
|
||||
pub const FORMAT_VERSION: u8 = 1;
|
||||
pub const API_VERSION: u32 = 1;
|
||||
|
||||
@@ -93,11 +96,34 @@ pub struct DeviceEnroll {
|
||||
pub p256_pk: Vec<u8>,
|
||||
}
|
||||
|
||||
/// `POST /v1/setup/start`: OPAQUE registration, first message. Needs the setup token.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SetupStart {
|
||||
pub setup_token: String,
|
||||
pub account_id: Uuid,
|
||||
/// Serialized opaque-ke `RegistrationRequest`.
|
||||
#[serde(with = "b64")]
|
||||
pub registration_request: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Reply to `POST /v1/setup/start` and `POST /v1/account/credentials/start`.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RegistrationStartResponse {
|
||||
/// Serialized opaque-ke `RegistrationResponse`.
|
||||
#[serde(with = "b64")]
|
||||
pub registration_response: Vec<u8>,
|
||||
}
|
||||
|
||||
/// `POST /v1/setup/finish`. `account_id` must match the one sent to `/v1/setup/start`.
|
||||
pub type SetupFinish = SetupRequest;
|
||||
|
||||
/// Body of `POST /v1/setup/finish` (alias [`SetupFinish`]).
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SetupRequest {
|
||||
pub setup_token: String,
|
||||
pub account_id: Uuid,
|
||||
pub kdf: KdfParams,
|
||||
/// Serialized opaque-ke `RegistrationUpload`.
|
||||
#[serde(with = "b64")]
|
||||
pub opaque_registration_upload: Vec<u8>,
|
||||
#[serde(with = "b64")]
|
||||
@@ -108,27 +134,35 @@ pub struct SetupRequest {
|
||||
pub device: DeviceEnroll,
|
||||
}
|
||||
|
||||
/// `POST /v1/auth/login/start`.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LoginStart {
|
||||
pub account_id: Uuid,
|
||||
/// Serialized opaque-ke `CredentialRequest`.
|
||||
#[serde(with = "b64")]
|
||||
pub ke1: Vec<u8>,
|
||||
/// The logging-in device. An unknown `device_id` is enrolled as pending after a successful login.
|
||||
pub device: DeviceEnroll,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LoginStartResponse {
|
||||
/// Handle for `/v1/auth/login/finish`; expires after 60 s.
|
||||
pub login_id: String,
|
||||
/// Serialized opaque-ke `CredentialResponse`.
|
||||
#[serde(with = "b64")]
|
||||
pub ke2: Vec<u8>,
|
||||
pub kdf: KdfParams,
|
||||
}
|
||||
|
||||
/// `POST /v1/auth/login/finish`.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LoginFinish {
|
||||
pub login_id: String,
|
||||
/// Serialized opaque-ke `CredentialFinalization`.
|
||||
#[serde(with = "b64")]
|
||||
pub ke3: Vec<u8>,
|
||||
/// Reserved for the TOTP enrollment path; not implemented by the server yet.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub totp: Option<String>,
|
||||
}
|
||||
@@ -140,13 +174,80 @@ pub enum LoginStatus {
|
||||
PendingApproval,
|
||||
}
|
||||
|
||||
/// `status: ok` carries a session (put it in `KV-Session`); `pending_approval` carries none:
|
||||
/// approve the device from an enrolled one, then log in again.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LoginFinishResponse {
|
||||
pub status: LoginStatus,
|
||||
pub session: Option<String>,
|
||||
/// RFC 3339. Rolling: every authenticated request extends it by 12 h.
|
||||
pub expires_at: Option<String>,
|
||||
}
|
||||
|
||||
/// `GET /v1/account`.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AccountInfo {
|
||||
pub account_id: Uuid,
|
||||
pub kdf: KdfParams,
|
||||
#[serde(with = "b64")]
|
||||
pub enc_pk: Vec<u8>,
|
||||
#[serde(with = "b64")]
|
||||
pub sig_pk: Vec<u8>,
|
||||
pub wrapped_keyset: Envelope,
|
||||
}
|
||||
|
||||
/// `POST /v1/account/credentials/start`: OPAQUE re-registration for a password change.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CredentialsStart {
|
||||
/// Serialized opaque-ke `RegistrationRequest` for the new `AUTH_INPUT`.
|
||||
#[serde(with = "b64")]
|
||||
pub registration_request: Vec<u8>,
|
||||
}
|
||||
|
||||
/// `PUT /v1/account/credentials`: replaces the OPAQUE record, KDF params and wrapped keyset atomically.
|
||||
/// Sessions of all other devices are ended.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CredentialsChange {
|
||||
pub kdf: KdfParams,
|
||||
/// Serialized opaque-ke `RegistrationUpload`.
|
||||
#[serde(with = "b64")]
|
||||
pub registration_upload: Vec<u8>,
|
||||
pub wrapped_keyset: Envelope,
|
||||
}
|
||||
|
||||
/// An entry of `GET /v1/devices`.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DeviceInfo {
|
||||
pub device_id: Uuid,
|
||||
pub name: String,
|
||||
pub platform: Platform,
|
||||
/// SEC1 uncompressed P-256 public key; show a fingerprint before approving.
|
||||
#[serde(with = "b64")]
|
||||
pub p256_pk: Vec<u8>,
|
||||
pub approved: bool,
|
||||
pub revoked_at: Option<String>,
|
||||
pub created_at: String,
|
||||
/// True for the device that made the request.
|
||||
pub current: bool,
|
||||
}
|
||||
|
||||
/// Server-Sent Events on `GET /v1/events`. The SSE event name equals `type`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum ServerEvent {
|
||||
VaultChanged { vault_id: Uuid, head: u64 },
|
||||
DevicePending { device_id: Uuid },
|
||||
}
|
||||
|
||||
impl ServerEvent {
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
ServerEvent::VaultChanged { .. } => "vault_changed",
|
||||
ServerEvent::DevicePending { .. } => "device_pending",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Vault {
|
||||
pub vault_id: Uuid,
|
||||
|
||||
Reference in New Issue
Block a user