//! Sync engine (milestone M1, kestrove-spec/docs/03). //! //! Responsibilities: //! * [`transport`]: HTTPS with rustls, TLS 1.3 only, SPKI pinning (primary + backup pin). //! * [`opaque`]: OPAQUE login via `opaque-ke` (ristretto255 / SHA-512, identity KSF because the input is already //! Argon2id-stretched). //! * [`signing`]: RFC 9421 request signing with the device key. The key lives in the platform keystore, so signing is //! a callback into platform code ([`DeviceSigner`]). //! * [`engine`]: pull, then push with `If-Match`, then three-way field merge on 412 ([`merge`]), then manifest verify //! and publish ([`manifest`]). pub mod client; pub mod engine; pub mod manifest; pub mod merge; pub mod opaque; pub mod signing; #[cfg(feature = "test-server")] pub mod test_server; pub mod transport; pub mod wire; pub use client::{ApiClient, LoginOutcome}; pub use engine::{SyncContext, SyncReport}; use uuid::Uuid; #[derive(Debug, thiserror::Error)] pub enum SyncError { #[error("network: {0}")] Network(String), #[error("server certificate does not match the pinned keys")] PinMismatch, #[error("configuration: {0}")] Config(String), #[error("server returned {status} {code}: {message}")] Server { status: u16, code: String, message: String }, #[error("unexpected server response: {0}")] Protocol(String), #[error("integrity alarm on vault {vault_id}: {reason}")] Integrity { vault_id: Uuid, reason: String }, #[error("not logged in")] NotAuthenticated, #[error("wrong master password or Secret Key")] BadCredentials, #[error("authentication failed: {0}")] Auth(String), #[error("device signer: {0}")] Signer(String), #[error("local storage: {0}")] Store(String), #[error("crypto: {0}")] Crypto(String), #[error("not found")] NotFound, /// HTTP 412: the item changed on the server since `If-Match`. #[error("revision conflict")] RevisionConflict, /// HTTP 409. #[error("version conflict")] VersionConflict, /// Internal: the manifest is newer than our cursor; pull again. #[error("manifest is ahead of the pulled changes")] ManifestAhead, } impl From for SyncError { fn from(e: kv_store::StoreError) -> Self { SyncError::Store(e.to_string()) } } /// Implemented by each platform: Secure Enclave, StrongBox, TPM or software fallback. pub trait DeviceSigner: Send + Sync { fn device_id(&self) -> Uuid; /// SEC1 uncompressed P-256 public key. fn public_key(&self) -> Vec; /// ECDSA P-256 / SHA-256 signature (IEEE P1363 r||s) over `message`. fn sign(&self, message: &[u8]) -> Result, SyncError>; } /// Server endpoint plus pinned SPKI SHA-256 hashes (base64). #[derive(Debug, Clone, Default)] pub struct ServerConfig { pub base_url: String, pub spki_pins: Vec, /// Extra trust anchors (DER), e.g. a private CA for a self-hosted server. Empty in normal use. pub extra_roots_der: Vec>, } impl ServerConfig { pub fn new(base_url: impl Into, spki_pins: Vec) -> Self { Self { base_url: base_url.into(), spki_pins, extra_roots_der: vec![] } } }