From 1d2391d827602fa644327267aa30c57940cb0add Mon Sep 17 00:00:00 2001 From: James Sadler Date: Wed, 23 Sep 2026 23:02:20 +1000 Subject: [PATCH 1/7] feat(stack-kms): ZeroKMS as a vitaminc key provider (CIP-3965) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `ZeroKmsKeyset` resolves one keyset over a shared `Arc` and implements vitaminc-kms's `KeyProvider` and `IndexKeyProvider`, so a client library can be generic over those traits and run on ZeroKMS or on a vendor data key source without knowing which. The keyset is resolved once, at construction, and every call carries it. That is not a restriction the traits impose: ZeroKMS itself carries the keyset id once per *request*, and the server resolves it to a single authority key for the whole batch. Binding at construction is what the wire protocol already does. At the trait level ZeroKMS is `ClientAndServer` reconstruction, `PerValue` isolation, and `Bound` — the binding is the descriptor, which ZeroKMS binds into the key server-side and logs on every retrieval. The `KeyId` is the IV followed by the tag. Nothing is removed. `DataKeySource` and `IndexKeySource` stay exactly as they are, so this adds a second shape beside the first rather than switching anything over; consumers move in a later change. The test fixtures move out of `client.rs`'s test module into `test_connection.rs` so both it and `provider.rs` can drive the same stub. `vitaminc-kms` is pinned to a git rev because these traits are not published yet, with `default-features = false` to keep every vendor SDK out of this crate's graph. --- packages/stack-kms/Cargo.toml | 17 + packages/stack-kms/src/client.rs | 41 +- .../stack-kms/src/client/test_connection.rs | 55 +- packages/stack-kms/src/errors.rs | 12 + packages/stack-kms/src/key_provider.rs | 9 + packages/stack-kms/src/lib.rs | 22 +- packages/stack-kms/src/provider.rs | 571 ++++++++++++++++++ 7 files changed, 684 insertions(+), 43 deletions(-) create mode 100644 packages/stack-kms/src/provider.rs diff --git a/packages/stack-kms/Cargo.toml b/packages/stack-kms/Cargo.toml index 105a38230..a0d920f1a 100644 --- a/packages/stack-kms/Cargo.toml +++ b/packages/stack-kms/Cargo.toml @@ -47,9 +47,26 @@ stack-auth = { workspace = true } zerokms-protocol = { workspace = true } vitaminc = { workspace = true, features = ["protected", "random"] } +# The key-provider traits `ZeroKmsKeyset` implements (`KeyProvider`, +# `IndexKeyProvider`, `Binding`) and their value types. `default-features = +# false` keeps every vendor SDK — and the native HTTP/TLS stack the WASI gate +# forbids — out of this crate's graph: nothing here talks to AWS, Azure, +# Google Cloud or Vault, only to ZeroKMS. +# +# Pinned to a git rev because these traits are not published yet. Swap to a +# version once vitaminc releases them. +vitaminc-kms = { git = "https://github.com/cipherstash/vitaminc", rev = "c6009a2", default-features = false } # Direct dep required because the `TimingSafeEq` derive macro expands to a # `::vitaminc_protected` path. vitaminc-protected = { workspace = true } +# `vitaminc-kms` is built against its own, newer `vitaminc-protected` — a +# different line from the workspace's `0.2.0-pre` pin above, which +# `stack-encrypt` already carries the same split for. A version-matched but +# differently-sourced dependency is a *different* type at compile time, so +# this must resolve to the identical git rev as `vitaminc-kms`'s own copy or +# every call into its types is an E0308. Renamed so it cannot collide with +# the entry above; used only by `provider.rs`. +vitaminc-protected-kms = { package = "vitaminc-protected", git = "https://github.com/cipherstash/vitaminc", rev = "c6009a2" } blake3 = { workspace = true } lazy_static = { workspace = true, optional = true } diff --git a/packages/stack-kms/src/client.rs b/packages/stack-kms/src/client.rs index 6b8d0bc6d..edddcf212 100644 --- a/packages/stack-kms/src/client.rs +++ b/packages/stack-kms/src/client.rs @@ -565,57 +565,18 @@ where } #[cfg(test)] -mod test_connection; +pub(crate) mod test_connection; #[cfg(test)] mod tests { use super::test_connection::*; use super::*; - use crate::key::V1KeySet; - use recipher::keyset::{EncryptionKeySet, ProxyKeySet}; use std::borrow::Cow; use uuid::uuid; use zerokms_protocol::{ GenerateKeyResponse, GeneratedKey, RetrieveKeyResponse, RetrievedKey, ViturKeyMaterial, }; - fn random_client_key() -> ClientKey { - let domain_key = EncryptionKeySet::generate().unwrap(); - let authority_key = EncryptionKeySet::generate().unwrap(); - let keyset = ProxyKeySet::generate(&authority_key, &domain_key); - - ClientKey { - key_id: uuid!("00000000-0000-0000-0000-000000000000"), - keyset: V1KeySet(keyset), - } - } - - fn build_client( - callback: impl FnOnce(TestConnectionBuilder) -> TestConnectionBuilder, - ) -> Client { - let builder = callback(TestConnectionBuilder::new()); - let client_opts = ClientOpts::new(builder) - .with_max_keys_per_req(10) - .unwrap() - .with_max_concurrent_reqs(5) - .unwrap(); - Client::init_opts(client_opts).expect("Failed to initialize test client") - } - - // 528 bytes is the size of the key material returned by ZeroKMS for the - // recipher proxy re-encryption scheme. - fn key_material() -> ViturKeyMaterial { - ViturKeyMaterial::from(vec![7u8; 528]) - } - - fn generated_key(tag: Vec) -> GeneratedKey { - GeneratedKey { - key_material: key_material(), - tag, - decryption_policy: None, - } - } - fn policy(claim: &str, value: &str) -> zerokms_protocol::DecryptionPolicy { zerokms_protocol::DecryptionPolicy { conditions: vec![zerokms_protocol::PolicyCondition { diff --git a/packages/stack-kms/src/client/test_connection.rs b/packages/stack-kms/src/client/test_connection.rs index 17560b471..84d634409 100644 --- a/packages/stack-kms/src/client/test_connection.rs +++ b/packages/stack-kms/src/client/test_connection.rs @@ -1,10 +1,61 @@ //! In-memory [`ZeroKMSConnection`] used by unit tests to stub ZeroKMS -//! responses without touching the network. +//! responses without touching the network, plus the fixtures every unit +//! test built on it needs (a client key, a client over the stub, and the +//! key material ZeroKMS would return). +//! +//! Shared by `client.rs`'s own tests and by `provider.rs`, which drives the +//! key-provider implementation over the same stub. use async_mutex::Mutex; -use zerokms_protocol::{ViturRequest, ViturRequestError}; +use uuid::uuid; +use zerokms_protocol::{GeneratedKey, ViturKeyMaterial, ViturRequest, ViturRequestError}; +use recipher::keyset::{EncryptionKeySet, ProxyKeySet}; + +use crate::client::{Client, ClientOpts}; use crate::connection::{ZeroKMSConnection, ZeroKMSConnectionInit}; +use crate::key::{ClientKey, V1KeySet}; + +/// A fresh, random [`ClientKey`]. Data and index keys are derived from it, +/// so a test that compares derived material must reuse one value (reach it +/// through `StackKms::client_key`) rather than call this twice. +pub(crate) fn random_client_key() -> ClientKey { + let domain_key = EncryptionKeySet::generate().unwrap(); + let authority_key = EncryptionKeySet::generate().unwrap(); + let keyset = ProxyKeySet::generate(&authority_key, &domain_key); + + ClientKey { + key_id: uuid!("00000000-0000-0000-0000-000000000000"), + keyset: V1KeySet(keyset), + } +} + +/// A low-level [`Client`] over a stub connection the callback configures. +pub(crate) fn build_client( + callback: impl FnOnce(TestConnectionBuilder) -> TestConnectionBuilder, +) -> Client { + let builder = callback(TestConnectionBuilder::new()); + let client_opts = ClientOpts::new(builder) + .with_max_keys_per_req(10) + .unwrap() + .with_max_concurrent_reqs(5) + .unwrap(); + Client::init_opts(client_opts).expect("Failed to initialize test client") +} + +/// 528 bytes is the size of the key material returned by ZeroKMS for the +/// recipher proxy re-encryption scheme. +pub(crate) fn key_material() -> ViturKeyMaterial { + ViturKeyMaterial::from(vec![7u8; 528]) +} + +pub(crate) fn generated_key(tag: Vec) -> GeneratedKey { + GeneratedKey { + key_material: key_material(), + tag, + decryption_policy: None, + } +} type EffectHandlers = Vec<(String, Box)>; type RequestHandlers = Vec<(String, Result)>; diff --git a/packages/stack-kms/src/errors.rs b/packages/stack-kms/src/errors.rs index 9287e6f18..b39a41c95 100644 --- a/packages/stack-kms/src/errors.rs +++ b/packages/stack-kms/src/errors.rs @@ -748,6 +748,18 @@ pub enum Error { crate::endpoint::InvalidEndpoint, ), + /// A [`Binding`](vitaminc_kms::provider::Binding) handed to + /// [`ZeroKmsKeyset`](crate::ZeroKmsKeyset) was not UTF-8. ZeroKMS + /// descriptors are strings. + #[error("a ZeroKMS binding must be UTF-8 text (it is the descriptor)")] + BindingNotUtf8, + + /// A [`KeyId`](vitaminc_kms::KeyId) handed to + /// [`ZeroKmsKeyset`](crate::ZeroKmsKeyset) is too short to hold the IV + /// that leads every id it mints. + #[error("ZeroKMS key id is {len} bytes; the IV alone is {min}")] + MalformedKeyId { len: usize, min: usize }, + #[error("Unexpected error: {0}")] #[diagnostic(code(stack_kms::unexpected))] Unexpected(String), diff --git a/packages/stack-kms/src/key_provider.rs b/packages/stack-kms/src/key_provider.rs index 2e87ba760..c87b96b1a 100644 --- a/packages/stack-kms/src/key_provider.rs +++ b/packages/stack-kms/src/key_provider.rs @@ -4,6 +4,15 @@ //! to generate or retrieve data keys. Each provider yields a complete [`ClientKey`] //! (client ID + key material) from a self-contained source. //! +//!
+//! +//! This is the *client key* provider — the ZeroKMS credential half. It is not +//! the **key provider** a client library such as `stack-encrypt` is generic +//! over: that is [`vitaminc_kms::provider::KeyProvider`], which this crate +//! implements as [`ZeroKmsKeyset`](crate::ZeroKmsKeyset). +//! +//!
+//! //! # Built-in providers //! //! | Provider | Source | diff --git a/packages/stack-kms/src/lib.rs b/packages/stack-kms/src/lib.rs index ee484f39e..8ffd7863a 100644 --- a/packages/stack-kms/src/lib.rs +++ b/packages/stack-kms/src/lib.rs @@ -15,6 +15,18 @@ //! Encryption/decryption, keyset and client management, and config save/load //! all remain in `cipherstash-client` for now. //! +//! # ZeroKMS as a key provider +//! +//! A client library does not have to take a `StackKms` directly. It can be +//! generic over the `vitaminc-kms` **key provider** traits +//! ([`vitaminc_kms::provider::KeyProvider`] and +//! [`vitaminc_kms::provider::IndexKeyProvider`]), so the same code runs on +//! ZeroKMS or on any vitaminc **data key source** (AWS KMS, Azure Key Vault, +//! Google Cloud KMS, Vault Transit). [`ZeroKmsKeyset`] is this crate's +//! implementation of those traits: one resolved keyset over a shared +//! `Arc`, binding each value's descriptor into the data key +//! server-side. +//! //! # Quick start //! // The quick start goes through `StackKmsBuilder`, which configures the default @@ -88,6 +100,7 @@ mod key_provider; mod key_source; mod maybe_send; mod payload; +pub mod provider; mod secret_key; #[cfg(feature = "http")] mod user_agent; @@ -138,7 +151,14 @@ pub use key_source::DeterministicSource; pub use key_source::FakeDataKeySource; pub use key_source::{DataKeySource, IndexKeySource}; -// Key providers +// ZeroKMS as a `vitaminc-kms` key provider: the trait shape a client library +// can be generic over. `StackKms` itself stays a plain ZeroKMS client. +pub use provider::ZeroKmsKeyset; + +// Credential providers: the ZeroKMS *client key*. Note the name collision — +// `key_provider::KeyProvider` sources that credential, and is not the key +// provider of the glossary (`vitaminc_kms::provider::KeyProvider`, which +// `ZeroKmsKeyset` above implements). pub use key_provider::{ EnvKeyProvider, FallbackKeyProvider, KeyProvider, KeyProviderError, StaticKeyProvider, }; diff --git a/packages/stack-kms/src/provider.rs b/packages/stack-kms/src/provider.rs new file mode 100644 index 000000000..66da3e6a8 --- /dev/null +++ b/packages/stack-kms/src/provider.rs @@ -0,0 +1,571 @@ +//! ZeroKMS as a key provider: [`ZeroKmsKeyset`] implements +//! [`vitaminc_kms::KeyProvider`] and [`vitaminc_kms::IndexKeyProvider`] +//! over one resolved keyset, so a `StackCipher` generic over those traits +//! runs on ZeroKMS exactly as it runs on a vendor data key source. +//! +//! What ZeroKMS is, at the trait level: +//! +//! - `RECONSTRUCTION = ClientAndServer`: the server returns partial key +//! material and the data key only exists after the client's own keyset +//! re-enciphers it (`recipher`). A server-side compromise plus a stored +//! key id does not recover a key. +//! - `ISOLATION = PerValue`: every value gets its own key, in one round +//! trip per batch. +//! - `BINDING = Bound`: the binding is the descriptor. ZeroKMS binds it into +//! the key server-side, refuses retrieval under a different one, and logs +//! it per retrieval (ADR 0002 in `packages/stack-encrypt/docs/adr`). +//! +//! The [`KeyId`] is the ZeroKMS IV followed by the tag: the two halves the +//! service needs back to re-derive a key. + +use std::borrow::Cow; +use std::sync::Arc; + +use uuid::Uuid; +use vitaminc_kms::provider::{Binding, BindingSupport, IndexKeyProvider, KeyProvider}; +use vitaminc_kms::{GeneratedDataKey, IndexKeyMaterial, KeyId, KeyIsolation, KeyReconstruction}; +use vitaminc_protected_kms::Protected; +use zerokms_protocol::IdentifiedBy; + +use crate::client::StackKms; +use crate::connection::ZeroKMSConnection; +use crate::errors::Error; +use crate::key::IndexKey; +use crate::payload::{GenerateKeyPayload, RetrieveKeyPayload}; +use recipher::key::Iv; +use stack_auth::{AuthStrategy, AuthStrategyBounds}; + +/// Length of the ZeroKMS IV that leads every [`KeyId`] this provider mints. +const IV_LEN: usize = 16; + +/// One ZeroKMS keyset, resolved once, as a key provider. +/// +/// Holds an `Arc` so several keysets can share one client, its +/// credentials and its connection. Build it with [`ZeroKmsKeyset::new`]. +pub struct ZeroKmsKeyset { + kms: Arc>, + keyset_id: Uuid, + index_key: IndexKey, +} + +impl ZeroKmsKeyset +where + C: AuthStrategyBounds, + for<'a> &'a C: AuthStrategy, + Conn: ZeroKMSConnection + Send + Sync, +{ + /// Resolve `keyset` (by id or name; `None` is the client's default + /// keyset) with one `load_keyset` call, which also yields the index key. + pub async fn new( + kms: Arc>, + keyset: Option, + ) -> Result { + let (keyset, index_key) = kms.load_keyset(keyset).await?; + Ok(Self { + kms, + keyset_id: keyset.id, + index_key, + }) + } + + /// The resolved keyset id. + pub fn keyset_id(&self) -> Uuid { + self.keyset_id + } + + /// The shared client. Several keysets can hold the same one — the + /// sharing is this type's own business, so the borrow hands out the + /// client, not the `Arc` around it. + pub fn kms(&self) -> &StackKms { + &self.kms + } + + /// ZeroKMS descriptors are strings; a binding that is not UTF-8 cannot + /// be one. + fn descriptor(binding: Binding<'_>) -> Result<&str, Error> { + std::str::from_utf8(binding.as_bytes()).map_err(|_| Error::BindingNotUtf8) + } + + /// Split a [`KeyId`] minted by this provider back into its IV and tag. + fn split_key_id(key_id: &KeyId) -> Result<(Iv, &[u8]), Error> { + let bytes = key_id.as_bytes(); + if bytes.len() < IV_LEN { + return Err(Error::MalformedKeyId { + len: bytes.len(), + min: IV_LEN, + }); + } + let (iv_bytes, tag) = bytes.split_at(IV_LEN); + let mut iv = Iv::default(); + iv.copy_from_slice(iv_bytes); + Ok((iv, tag)) + } +} + +impl KeyProvider<32> for ZeroKmsKeyset +where + C: AuthStrategyBounds, + for<'a> &'a C: AuthStrategy, + Conn: ZeroKMSConnection + Send + Sync, +{ + type Error = Error; + + const RECONSTRUCTION: KeyReconstruction = KeyReconstruction::ClientAndServer; + const ISOLATION: KeyIsolation = KeyIsolation::PerValue; + const BINDING: BindingSupport = BindingSupport::Bound; + + async fn generate_keys( + &self, + bindings: &[Binding<'_>], + ) -> Result>, Error> { + let payloads = bindings + .iter() + .map(|binding| { + Ok(GenerateKeyPayload::new( + Self::descriptor(*binding)?, + Cow::Owned(Vec::new()), + )) + }) + .collect::, Error>>()?; + + let keys = self + .kms + .generate_keys(payloads, Some(self.keyset_id), None) + .await?; + + Ok(keys + .into_iter() + .map(|key| { + let mut key_id = Vec::with_capacity(IV_LEN + key.tag.len()); + key_id.extend_from_slice(&key.key.iv); + key_id.extend_from_slice(&key.tag); + GeneratedDataKey { + plaintext: Protected::new(key.key.key), + key_id: KeyId::new(key_id), + } + }) + .collect()) + } + + async fn retrieve_keys( + &self, + keys: &[(KeyId, Binding<'_>)], + ) -> Result>, Error> { + let payloads = keys + .iter() + .map(|(key_id, binding)| { + let (iv, tag) = Self::split_key_id(key_id)?; + Ok(RetrieveKeyPayload::new( + iv, + Self::descriptor(*binding)?, + tag, + )) + }) + .collect::, Error>>()?; + + let keys = self + .kms + .retrieve_keys(payloads, Some(self.keyset_id), None) + .await?; + + Ok(keys + .into_iter() + .map(|key| Protected::new(key.key)) + .collect()) + } +} + +impl IndexKeyProvider<32> for ZeroKmsKeyset +where + C: AuthStrategyBounds, + for<'a> &'a C: AuthStrategy, + Conn: ZeroKMSConnection + Send + Sync, +{ + type Error = Error; + + async fn load_index_key(&self) -> Result, Error> { + Ok(IndexKeyMaterial(Protected::new(*self.index_key.key()))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::client::test_connection::{ + generated_key, key_material, random_client_key, TestConnection, TestConnectionBuilder, + }; + use crate::client::ClientOpts; + use crate::key::DataKey; + use stack_auth::StaticTokenStrategy; + use std::sync::Mutex; + use uuid::uuid; + use vitaminc_protected_kms::Controlled; + use zerokms_protocol::{ + GenerateKeyRequest, GenerateKeyResponse, Keyset, LoadKeysetRequest, LoadKeysetResponse, + RetrieveKeyRequest, RetrieveKeyResponse, RetrievedKey, + }; + + const KEYSET_ID: Uuid = uuid!("11111111-1111-1111-1111-111111111111"); + + type TestKeyset = ZeroKmsKeyset; + + /// The `load_keyset` response [`ZeroKmsKeyset::new`] consumes: the keyset + /// this provider resolves to, plus the partial key material its index key + /// is derived from. + fn load_keyset_response() -> LoadKeysetResponse { + LoadKeysetResponse { + partial_index_key: RetrievedKey { + key_material: key_material(), + }, + keyset: Keyset { + id: KEYSET_ID, + name: "default".to_string(), + description: String::new(), + is_disabled: false, + is_default: true, + }, + } + } + + /// A `StackKms` over a stub connection the callback configures. Every + /// request a test expects must be stubbed up front — the stub panics on + /// an unstubbed endpoint, which is what the "before any request" + /// assertions below rely on. + fn build_kms( + callback: impl FnOnce(TestConnectionBuilder) -> TestConnectionBuilder, + ) -> Arc> { + let opts = ClientOpts::new(callback(TestConnectionBuilder::new())); + Arc::new( + StackKms::<_, TestConnection>::connect( + opts, + StaticTokenStrategy::new("static-token"), + random_client_key(), + ) + .expect("connect over a test connection"), + ) + } + + /// A provider whose one `load_keyset` call is already stubbed, over a + /// connection the callback may stub further requests on. + async fn build_keyset( + callback: impl FnOnce(TestConnectionBuilder) -> TestConnectionBuilder, + ) -> TestKeyset { + let kms = build_kms(|builder| { + callback(builder.add_success_response::(load_keyset_response())) + }); + ZeroKmsKeyset::new(kms, None) + .await + .expect("the keyset resolves") + } + + /// Captures the one request sent to an endpoint, for assertions after the + /// call returns. + struct Captured(Arc>>); + + impl Captured { + fn new() -> Self { + Self(Arc::new(Mutex::new(None))) + } + + fn recorder(&self) -> impl FnOnce(R) + Send + 'static { + let slot = Arc::clone(&self.0); + move |request| *slot.lock().unwrap() = Some(request) + } + + fn take(&self) -> R { + self.0.lock().unwrap().take().expect("a request was sent") + } + } + + mod new { + use super::*; + + #[tokio::test] + async fn resolves_the_keyset_with_one_load_keyset_request() { + // One `load-keyset` handler is stubbed and the stub removes it + // once used, so a second request would panic. + let keyset = build_keyset(|builder| builder).await; + + assert_eq!(keyset.keyset_id(), KEYSET_ID); + } + + #[tokio::test] + async fn load_index_key_returns_the_keysets_derived_index_key() { + let keyset = build_keyset(|builder| builder).await; + + // The same derivation `load_keyset` performs — see + // `load_keyset_derives_a_deterministic_index_key` in `client.rs`. + let expected = + IndexKey::from_key_material(keyset.kms().client_key(), &key_material()).unwrap(); + + let material = keyset.load_index_key().await.unwrap(); + + assert_eq!(&material.0.risky_unwrap(), expected.key()); + } + + #[tokio::test] + async fn load_index_key_makes_no_further_request() { + let keyset = build_keyset(|builder| builder).await; + + // The stub has no handler left, so a request here would panic. + let first = keyset.load_index_key().await.unwrap(); + let second = keyset.load_index_key().await.unwrap(); + + assert_eq!(first.0.risky_unwrap(), second.0.risky_unwrap()); + } + + #[tokio::test] + async fn two_keysets_share_one_client() { + let kms = build_kms(|builder| { + builder + .add_success_response::(load_keyset_response()) + .add_success_response::(load_keyset_response()) + }); + + let by_default = ZeroKmsKeyset::new(Arc::clone(&kms), None).await.unwrap(); + let by_id = ZeroKmsKeyset::new(Arc::clone(&kms), Some(KEYSET_ID.into())) + .await + .unwrap(); + + assert_eq!(by_default.keyset_id(), KEYSET_ID); + assert_eq!(by_id.keyset_id(), KEYSET_ID); + assert!(std::ptr::eq(by_default.kms(), by_id.kms())); + // The two providers, plus the handle this test still holds. + assert_eq!(Arc::strong_count(&kms), 3); + } + } + + mod generate_keys { + use super::*; + + #[tokio::test] + async fn sends_one_spec_per_binding_carrying_the_descriptor_and_keyset_id() { + let captured = Captured::>::new(); + let keyset = build_keyset(|builder| { + builder + .add_effect::(captured.recorder()) + .add_success_response::(GenerateKeyResponse { + keys: vec![generated_key(vec![1]), generated_key(vec![2])], + }) + }) + .await; + + let keys = keyset + .generate_keys(&[Binding::from("users/email"), Binding::from("users/name")]) + .await + .unwrap(); + + let request = captured.take(); + assert_eq!(request.keys.len(), 2); + assert_eq!(request.keys[0].descriptor, "users/email"); + assert_eq!(request.keys[1].descriptor, "users/name"); + assert_eq!(request.keyset_id, Some(IdentifiedBy::Uuid(KEYSET_ID))); + assert_eq!(keys.len(), 2); + } + + #[tokio::test] + async fn key_ids_are_the_iv_followed_by_the_servers_tag() { + let captured = Captured::>::new(); + let keyset = build_keyset(|builder| { + builder + .add_effect::(captured.recorder()) + .add_success_response::(GenerateKeyResponse { + keys: vec![generated_key(vec![1, 2, 3]), generated_key(vec![4, 5])], + }) + }) + .await; + + let keys = keyset + .generate_keys(&[Binding::from("a"), Binding::from("b")]) + .await + .unwrap(); + + let request = captured.take(); + let tags: [&[u8]; 2] = [&[1, 2, 3], &[4, 5]]; + for ((key, spec), tag) in keys.iter().zip(request.keys.iter()).zip(tags) { + let bytes = key.key_id.as_bytes(); + assert_eq!(bytes.len(), IV_LEN + tag.len()); + assert_eq!(&bytes[..IV_LEN], spec.iv.as_ref()); + assert_eq!(&bytes[IV_LEN..], tag); + } + } + + #[tokio::test] + async fn plaintext_is_what_data_key_derives_from_the_same_material() { + let captured = Captured::>::new(); + let keyset = build_keyset(|builder| { + builder + .add_effect::(captured.recorder()) + .add_success_response::(GenerateKeyResponse { + keys: vec![generated_key(vec![7])], + }) + }) + .await; + + let keys = keyset.generate_keys(&[Binding::from("a")]).await.unwrap(); + + let iv = captured.take().keys[0].iv.into_inner(); + let expected = + DataKey::from_key_material(keyset.kms().client_key(), iv, &key_material()).unwrap(); + + assert_eq!(&keys[0].plaintext.clone().risky_unwrap(), expected.key()); + } + + #[tokio::test] + async fn a_non_utf8_binding_fails_before_any_request() { + // No `generate-data-key` handler is stubbed, so reaching the + // connection would panic rather than return this error. + let keyset = build_keyset(|builder| builder).await; + + // `let ... else` rather than `expect_err`: the success type + // deliberately has no `Debug` (it holds key material). + let Err(err) = keyset + .generate_keys(&[Binding::from("ok"), Binding::new(&[0xff, 0xfe])]) + .await + else { + panic!("a non-UTF-8 binding cannot be a descriptor"); + }; + + assert!(matches!(err, Error::BindingNotUtf8), "got: {err:?}"); + } + } + + mod retrieve_keys { + use super::*; + + fn key_id(iv: [u8; IV_LEN], tag: &[u8]) -> KeyId { + let mut bytes = iv.to_vec(); + bytes.extend_from_slice(tag); + KeyId::new(bytes) + } + + #[tokio::test] + async fn sends_the_split_iv_tag_and_binding_in_order() { + let captured = Captured::>::new(); + let keyset = build_keyset(|builder| { + builder + .add_effect::(captured.recorder()) + .add_success_response::(RetrieveKeyResponse { + keys: vec![ + RetrievedKey { + key_material: key_material(), + }, + RetrievedKey { + key_material: key_material(), + }, + ], + }) + }) + .await; + + let keys = keyset + .retrieve_keys(&[ + (key_id([1u8; IV_LEN], &[9, 9]), Binding::from("users/email")), + (key_id([2u8; IV_LEN], &[8]), Binding::from("users/name")), + ]) + .await + .unwrap(); + + let request = captured.take(); + assert_eq!(request.keys.len(), 2); + assert_eq!(request.keys[0].iv.as_ref(), &[1u8; IV_LEN]); + assert_eq!(request.keys[0].tag.as_ref(), &[9, 9]); + assert_eq!(request.keys[0].descriptor, "users/email"); + assert_eq!(request.keys[1].iv.as_ref(), &[2u8; IV_LEN]); + assert_eq!(request.keys[1].tag.as_ref(), &[8]); + assert_eq!(request.keys[1].descriptor, "users/name"); + assert_eq!(request.keyset_id, Some(IdentifiedBy::Uuid(KEYSET_ID))); + assert_eq!(keys.len(), 2); + } + + #[tokio::test] + async fn generate_then_retrieve_derives_the_same_key() { + // ZeroKMS returns the same underlying material for generate and + // retrieve, so the derived data key must round-trip. + let keyset = build_keyset(|builder| { + builder + .add_success_response::(GenerateKeyResponse { + keys: vec![generated_key(vec![9, 9, 9])], + }) + .add_success_response::(RetrieveKeyResponse { + keys: vec![RetrievedKey { + key_material: key_material(), + }], + }) + }) + .await; + + let generated = keyset + .generate_keys(&[Binding::from("desc")]) + .await + .unwrap(); + + let retrieved = keyset + .retrieve_keys(&[(generated[0].key_id.clone(), Binding::from("desc"))]) + .await + .unwrap(); + + assert_eq!( + generated[0].plaintext.clone().risky_unwrap(), + retrieved[0].clone().risky_unwrap() + ); + } + + #[tokio::test] + async fn a_short_key_id_fails_before_any_request() { + // No `retrieve-data-key` handler is stubbed, so reaching the + // connection would panic rather than return this error. + let keyset = build_keyset(|builder| builder).await; + + let Err(err) = keyset + .retrieve_keys(&[(KeyId::new(vec![0u8; IV_LEN - 1]), Binding::EMPTY)]) + .await + else { + panic!("a key id shorter than the IV cannot be split"); + }; + + assert!( + matches!( + err, + Error::MalformedKeyId { + len: 15, + min: IV_LEN + } + ), + "got: {err:?}" + ); + } + + #[tokio::test] + async fn a_non_utf8_binding_fails_before_any_request() { + let keyset = build_keyset(|builder| builder).await; + + let Err(err) = keyset + .retrieve_keys(&[(key_id([0u8; IV_LEN], &[1]), Binding::new(&[0xff, 0xfe]))]) + .await + else { + panic!("a non-UTF-8 binding cannot be a descriptor"); + }; + + assert!(matches!(err, Error::BindingNotUtf8), "got: {err:?}"); + } + } + + /// What this provider promises a caller that reads the traits rather than + /// the backend — see the module docs. + #[test] + fn declares_what_zerokms_is() { + assert_eq!( + >::RECONSTRUCTION, + KeyReconstruction::ClientAndServer + ); + assert_eq!( + >::ISOLATION, + KeyIsolation::PerValue + ); + assert_eq!( + >::BINDING, + BindingSupport::Bound + ); + } +} From 96d33085f0842e0beaf75676d2a074724acf0d1f Mon Sep 17 00:00:00 2001 From: James Sadler Date: Thu, 24 Sep 2026 23:51:24 +1000 Subject: [PATCH 2/7] fix(guest): classify the provider's own input refusals (CIP-3965) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `ZeroKmsKeyset` added two error variants — a binding that is not UTF-8 and a key id too short to hold an IV — and `stack_kms::Error` is not `#[non_exhaustive]`, so the guest's exhaustive match stopped compiling. Both are the provider refusing its own input before any request is sent, never a ZeroKMS verdict, so both are `STATUS_ENCODING`: something the host can fix, rather than something it can only log. The test pins the classification rather than the compile. A variant added upstream already fails the build; one reclassified here would not, and the difference between "fix your input" and "log this" is what the status word is for. --- languages/golang/encrypt/guest/src/status.rs | 26 ++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/languages/golang/encrypt/guest/src/status.rs b/languages/golang/encrypt/guest/src/status.rs index 8425272e4..a42f6de9c 100644 --- a/languages/golang/encrypt/guest/src/status.rs +++ b/languages/golang/encrypt/guest/src/status.rs @@ -216,6 +216,14 @@ fn status_for_kms(error: &stack_kms::Error) -> u32 { stack_kms::Error::ConnectionInit(_) | stack_kms::Error::InvalidEndpoint(_) => { STATUS_KMS_TRANSPORT } + // Neither reaches ZeroKMS: the provider refuses its own input first. + // A binding that is not UTF-8 cannot be a descriptor, and a key id + // too short to hold an IV cannot name a key — so both are malformed + // input at the boundary, which is what a host can act on, rather + // than a ZeroKMS verdict it can only log. + stack_kms::Error::BindingNotUtf8 | stack_kms::Error::MalformedKeyId { .. } => { + STATUS_ENCODING + } stack_kms::Error::Unexpected(_) => STATUS_KMS_OTHER, } } @@ -367,6 +375,24 @@ mod tests { ); } + /// The provider's own input refusals never reach ZeroKMS, so they are + /// the caller's to fix rather than a verdict to log. Pinned because the + /// enclosing match is exhaustive over `stack_kms::Error`: a variant + /// added upstream fails the build, but one *reclassified* here would + /// not, and the difference between "fix your input" and "log this" is + /// the whole point of the status word. + #[test] + fn the_providers_own_input_refusals_are_encoding() { + assert_eq!( + status_for_kms(&stack_kms::Error::BindingNotUtf8), + STATUS_ENCODING + ); + assert_eq!( + status_for_kms(&stack_kms::Error::MalformedKeyId { len: 4, min: 16 }), + STATUS_ENCODING + ); + } + #[test] fn a_plan_refusal_is_the_callers_input() { assert_eq!( From 7c011b1fba0abc714cd2338116f44293c16b4da8 Mon Sep 17 00:00:00 2001 From: James Sadler Date: Mon, 28 Sep 2026 10:11:32 +1000 Subject: [PATCH 3/7] test(stack-kms): pin the key-id length guard at the length it allows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `split_key_id` refuses a key id shorter than the IV, and a test covers `IV_LEN - 1`. Nothing covered `IV_LEN` itself, so tightening the guard to `<=` — rejecting a key id the format permits — passed the whole suite. A key id of exactly the IV length splits into that IV and an empty tag, which is a shape the split accepts. The test asserts the request ZeroKMS receives carries the whole id as the IV and an empty tag. Found by running the mutation gate locally over `stack-kms`, which the CI gate does not cover — its crate list is stack-auth and stack-encrypt. --- packages/stack-kms/src/provider.rs | 38 ++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/packages/stack-kms/src/provider.rs b/packages/stack-kms/src/provider.rs index 66da3e6a8..66e33cb24 100644 --- a/packages/stack-kms/src/provider.rs +++ b/packages/stack-kms/src/provider.rs @@ -511,6 +511,44 @@ mod tests { ); } + /// The other side of the length check. A key id of exactly the IV + /// length splits into an IV and an *empty* tag, which is a shape the + /// split accepts — so the guard has to be `<`, not `<=`. Without + /// this, tightening it by one byte would reject a key id the format + /// permits and no test would notice. + #[tokio::test] + async fn a_key_id_that_is_exactly_the_iv_splits_with_an_empty_tag() { + let captured = Captured::>::new(); + let keyset = build_keyset(|builder| { + builder + .add_effect::(captured.recorder()) + .add_success_response::(RetrieveKeyResponse { + keys: vec![RetrievedKey { + key_material: key_material(), + }], + }) + }) + .await; + + let keys = keyset + .retrieve_keys(&[(KeyId::new(vec![7u8; IV_LEN]), Binding::from("users/email"))]) + .await + .expect("a key id of exactly the IV length is splittable"); + assert_eq!(keys.len(), 1); + + let request = captured.take(); + assert_eq!(request.keys.len(), 1); + assert_eq!( + request.keys[0].iv.as_ref(), + &[7u8; IV_LEN], + "the whole key id is the IV" + ); + assert!( + request.keys[0].tag.is_empty(), + "and the tag is what is left, which is nothing" + ); + } + #[tokio::test] async fn a_short_key_id_fails_before_any_request() { // No `retrieve-data-key` handler is stubbed, so reaching the From a11d3ff36cef6977069109d771218e97a025ced0 Mon Sep 17 00:00:00 2001 From: James Sadler Date: Tue, 6 Oct 2026 10:08:29 +1100 Subject: [PATCH 4/7] chore(stack-kms): pin vitaminc-kms to the key-provider branch rebased onto vitaminc 0.5.1 cipherstash/vitaminc#352 is now on vitaminc main, which carries the 0.5.1 release that the workspace takes from crates.io. Pin b3bc6eb, the tip of claude/cip-3965-key-provider. The suite PR pinned c6009a2 and then 9cb1965; neither is on the branch now. Every lockfile that builds stack-kms by path moves with it: the root, the stackencrypt Go guest, the stack-kms and stack-encrypt fuzz crates and packages/eql. Only the seven git-sourced vitaminc packages are added. --- Cargo.lock | 131 ++++++++++++++++++---- languages/golang/encrypt/guest/Cargo.lock | 131 ++++++++++++++++++---- packages/stack-encrypt/fuzz/Cargo.lock | 127 +++++++++++++++++---- packages/stack-kms/Cargo.toml | 11 +- packages/stack-kms/fuzz/Cargo.lock | 119 +++++++++++++++++--- 5 files changed, 437 insertions(+), 82 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6d6dce0f8..3bf9cc6d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3220,7 +3220,7 @@ dependencies = [ "url", "uuid", "vitaminc", - "vitaminc-protected", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "web-time", "zeroize", "zerokms-protocol", @@ -3242,7 +3242,7 @@ dependencies = [ "tempfile", "tokio", "url", - "vitaminc-protected", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "zeroize", ] @@ -3286,7 +3286,7 @@ dependencies = [ "vitaminc-encrypt", "vitaminc-hmac", "vitaminc-prf", - "vitaminc-protected", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "zeroize", "zerokms-protocol", ] @@ -3312,7 +3312,7 @@ dependencies = [ "serde_json", "thiserror 1.0.69", "vitaminc-aead-value", - "vitaminc-protected", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "zeroize", ] @@ -3345,7 +3345,9 @@ dependencies = [ "url", "uuid", "vitaminc", - "vitaminc-protected", + "vitaminc-kms", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", "zeroize", "zerokms-protocol", ] @@ -4076,9 +4078,9 @@ dependencies = [ "vitaminc-aead", "vitaminc-context", "vitaminc-encrypt", - "vitaminc-protected", - "vitaminc-random", - "vitaminc-traits", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-random 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-traits 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -4091,8 +4093,8 @@ dependencies = [ "serde", "vitaminc-aead-derive", "vitaminc-context", - "vitaminc-protected", - "vitaminc-random", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-random 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "zeroize", ] @@ -4114,10 +4116,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d78cdadbc6374c3bd7c19fb4d99abca693b96953b18fef8c608efbe092cf3ea" dependencies = [ "vitaminc-aead", - "vitaminc-protected", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "zeroize", ] +[[package]] +name = "vitaminc-async-traits" +version = "0.5.1" +source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +dependencies = [ + "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-traits 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", +] + [[package]] name = "vitaminc-context" version = "0.5.1" @@ -4125,7 +4136,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7df52501fe33cdb3ee9e155b5242fac184c037d147e58c96e5143ed011252a8" dependencies = [ "mutants", - "vitaminc-protected", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -4137,8 +4148,8 @@ dependencies = [ "aes-gcm", "aws-lc-rs", "vitaminc-aead", - "vitaminc-protected", - "vitaminc-random", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-random 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "zeroize", ] @@ -4151,7 +4162,20 @@ dependencies = [ "hmac", "sha2 0.11.0", "vitaminc-prf", - "vitaminc-protected", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "zeroize", +] + +[[package]] +name = "vitaminc-kms" +version = "0.5.1" +source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +dependencies = [ + "futures", + "thiserror 2.0.18", + "vitaminc-async-traits", + "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-traits 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", "zeroize", ] @@ -4164,7 +4188,7 @@ dependencies = [ "mutants", "thiserror 2.0.18", "vitaminc-context", - "vitaminc-protected", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -4180,7 +4204,23 @@ dependencies = [ "serde_bytes", "subtle", "thiserror 2.0.18", - "vitaminc-protected-derive", + "vitaminc-protected-derive 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "zeroize", +] + +[[package]] +name = "vitaminc-protected" +version = "0.5.1" +source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +dependencies = [ + "bitvec", + "digest 0.11.3", + "libc", + "serde", + "serde_bytes", + "subtle", + "thiserror 2.0.18", + "vitaminc-protected-derive 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", "zeroize", ] @@ -4195,6 +4235,16 @@ dependencies = [ "syn 3.0.6", ] +[[package]] +name = "vitaminc-protected-derive" +version = "0.5.1" +source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + [[package]] name = "vitaminc-random" version = "0.5.1" @@ -4205,8 +4255,22 @@ dependencies = [ "getrandom 0.4.2", "rand 0.10.1", "thiserror 2.0.18", - "vitaminc-protected", - "vitaminc-random-derives", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-random-derives 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "zeroize", +] + +[[package]] +name = "vitaminc-random" +version = "0.5.1" +source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand 0.10.1", + "thiserror 2.0.18", + "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-random-derives 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", "zeroize", ] @@ -4221,6 +4285,16 @@ dependencies = [ "syn 3.0.6", ] +[[package]] +name = "vitaminc-random-derives" +version = "0.5.1" +source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + [[package]] name = "vitaminc-traits" version = "0.5.1" @@ -4232,8 +4306,23 @@ dependencies = [ "rmp-serde", "serde", "thiserror 2.0.18", - "vitaminc-protected", - "vitaminc-random", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-random 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "zeroize", +] + +[[package]] +name = "vitaminc-traits" +version = "0.5.1" +source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +dependencies = [ + "anyhow", + "bytes", + "rmp-serde", + "serde", + "thiserror 2.0.18", + "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-random 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", "zeroize", ] diff --git a/languages/golang/encrypt/guest/Cargo.lock b/languages/golang/encrypt/guest/Cargo.lock index 88e3723f7..ac077cccc 100644 --- a/languages/golang/encrypt/guest/Cargo.lock +++ b/languages/golang/encrypt/guest/Cargo.lock @@ -2056,7 +2056,7 @@ dependencies = [ "url", "uuid", "vitaminc", - "vitaminc-protected", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "web-time", "zeroize", "zerokms-protocol", @@ -2080,7 +2080,7 @@ dependencies = [ "vitaminc-encrypt", "vitaminc-hmac", "vitaminc-prf", - "vitaminc-protected", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "zeroize", ] @@ -2110,7 +2110,7 @@ dependencies = [ "stack-kms", "uuid", "vitaminc-aead-value", - "vitaminc-protected", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "zeroize", "zerokms-protocol", ] @@ -2123,7 +2123,7 @@ dependencies = [ "serde_json", "thiserror 1.0.69", "vitaminc-aead-value", - "vitaminc-protected", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "zeroize", ] @@ -2150,7 +2150,9 @@ dependencies = [ "url", "uuid", "vitaminc", - "vitaminc-protected", + "vitaminc-kms", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", "zeroize", "zerokms-protocol", ] @@ -2576,9 +2578,9 @@ dependencies = [ "vitaminc-aead", "vitaminc-context", "vitaminc-encrypt", - "vitaminc-protected", - "vitaminc-random", - "vitaminc-traits", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-random 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-traits 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2591,8 +2593,8 @@ dependencies = [ "serde", "vitaminc-aead-derive", "vitaminc-context", - "vitaminc-protected", - "vitaminc-random", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-random 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "zeroize", ] @@ -2614,10 +2616,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d78cdadbc6374c3bd7c19fb4d99abca693b96953b18fef8c608efbe092cf3ea" dependencies = [ "vitaminc-aead", - "vitaminc-protected", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "zeroize", ] +[[package]] +name = "vitaminc-async-traits" +version = "0.5.1" +source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +dependencies = [ + "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-traits 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", +] + [[package]] name = "vitaminc-context" version = "0.5.1" @@ -2625,7 +2636,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7df52501fe33cdb3ee9e155b5242fac184c037d147e58c96e5143ed011252a8" dependencies = [ "mutants", - "vitaminc-protected", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2637,8 +2648,8 @@ dependencies = [ "aes-gcm", "aws-lc-rs", "vitaminc-aead", - "vitaminc-protected", - "vitaminc-random", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-random 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "zeroize", ] @@ -2651,7 +2662,20 @@ dependencies = [ "hmac", "sha2 0.11.0", "vitaminc-prf", - "vitaminc-protected", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "zeroize", +] + +[[package]] +name = "vitaminc-kms" +version = "0.5.1" +source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +dependencies = [ + "futures", + "thiserror 2.0.20", + "vitaminc-async-traits", + "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-traits 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", "zeroize", ] @@ -2664,7 +2688,7 @@ dependencies = [ "mutants", "thiserror 2.0.20", "vitaminc-context", - "vitaminc-protected", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2680,7 +2704,23 @@ dependencies = [ "serde_bytes", "subtle", "thiserror 2.0.20", - "vitaminc-protected-derive", + "vitaminc-protected-derive 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "zeroize", +] + +[[package]] +name = "vitaminc-protected" +version = "0.5.1" +source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +dependencies = [ + "bitvec", + "digest 0.11.3", + "libc", + "serde", + "serde_bytes", + "subtle", + "thiserror 2.0.20", + "vitaminc-protected-derive 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", "zeroize", ] @@ -2695,6 +2735,16 @@ dependencies = [ "syn 3.0.4", ] +[[package]] +name = "vitaminc-protected-derive" +version = "0.5.1" +source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + [[package]] name = "vitaminc-random" version = "0.5.1" @@ -2705,8 +2755,22 @@ dependencies = [ "getrandom 0.4.3", "rand 0.10.2", "thiserror 2.0.20", - "vitaminc-protected", - "vitaminc-random-derives", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-random-derives 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "zeroize", +] + +[[package]] +name = "vitaminc-random" +version = "0.5.1" +source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand 0.10.2", + "thiserror 2.0.20", + "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-random-derives 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", "zeroize", ] @@ -2721,6 +2785,16 @@ dependencies = [ "syn 3.0.4", ] +[[package]] +name = "vitaminc-random-derives" +version = "0.5.1" +source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + [[package]] name = "vitaminc-traits" version = "0.5.1" @@ -2732,8 +2806,23 @@ dependencies = [ "rmp-serde", "serde", "thiserror 2.0.20", - "vitaminc-protected", - "vitaminc-random", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-random 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "zeroize", +] + +[[package]] +name = "vitaminc-traits" +version = "0.5.1" +source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +dependencies = [ + "anyhow", + "bytes", + "rmp-serde", + "serde", + "thiserror 2.0.20", + "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-random 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", "zeroize", ] diff --git a/packages/stack-encrypt/fuzz/Cargo.lock b/packages/stack-encrypt/fuzz/Cargo.lock index 3eaca509f..324792f17 100644 --- a/packages/stack-encrypt/fuzz/Cargo.lock +++ b/packages/stack-encrypt/fuzz/Cargo.lock @@ -1971,7 +1971,7 @@ dependencies = [ "url", "uuid", "vitaminc", - "vitaminc-protected", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "web-time", "zeroize", "zerokms-protocol", @@ -1995,7 +1995,7 @@ dependencies = [ "vitaminc-encrypt", "vitaminc-hmac", "vitaminc-prf", - "vitaminc-protected", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "zeroize", ] @@ -2042,7 +2042,9 @@ dependencies = [ "url", "uuid", "vitaminc", - "vitaminc-protected", + "vitaminc-kms", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", "zeroize", "zerokms-protocol", ] @@ -2438,9 +2440,9 @@ dependencies = [ "vitaminc-aead", "vitaminc-context", "vitaminc-encrypt", - "vitaminc-protected", - "vitaminc-random", - "vitaminc-traits", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-random 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-traits 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2453,8 +2455,8 @@ dependencies = [ "serde", "vitaminc-aead-derive", "vitaminc-context", - "vitaminc-protected", - "vitaminc-random", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-random 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "zeroize", ] @@ -2476,10 +2478,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d78cdadbc6374c3bd7c19fb4d99abca693b96953b18fef8c608efbe092cf3ea" dependencies = [ "vitaminc-aead", - "vitaminc-protected", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "zeroize", ] +[[package]] +name = "vitaminc-async-traits" +version = "0.5.1" +source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +dependencies = [ + "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-traits 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", +] + [[package]] name = "vitaminc-context" version = "0.5.1" @@ -2487,7 +2498,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7df52501fe33cdb3ee9e155b5242fac184c037d147e58c96e5143ed011252a8" dependencies = [ "mutants", - "vitaminc-protected", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2499,8 +2510,8 @@ dependencies = [ "aes-gcm", "aws-lc-rs", "vitaminc-aead", - "vitaminc-protected", - "vitaminc-random", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-random 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "zeroize", ] @@ -2513,7 +2524,20 @@ dependencies = [ "hmac", "sha2 0.11.0", "vitaminc-prf", - "vitaminc-protected", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "zeroize", +] + +[[package]] +name = "vitaminc-kms" +version = "0.5.1" +source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +dependencies = [ + "futures", + "thiserror 2.0.18", + "vitaminc-async-traits", + "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-traits 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", "zeroize", ] @@ -2526,7 +2550,7 @@ dependencies = [ "mutants", "thiserror 2.0.18", "vitaminc-context", - "vitaminc-protected", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2542,7 +2566,23 @@ dependencies = [ "serde_bytes", "subtle", "thiserror 2.0.18", - "vitaminc-protected-derive", + "vitaminc-protected-derive 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "zeroize", +] + +[[package]] +name = "vitaminc-protected" +version = "0.5.1" +source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +dependencies = [ + "bitvec", + "digest 0.11.3", + "libc", + "serde", + "serde_bytes", + "subtle", + "thiserror 2.0.18", + "vitaminc-protected-derive 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", "zeroize", ] @@ -2557,6 +2597,16 @@ dependencies = [ "syn 3.0.3", ] +[[package]] +name = "vitaminc-protected-derive" +version = "0.5.1" +source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + [[package]] name = "vitaminc-random" version = "0.5.1" @@ -2567,8 +2617,22 @@ dependencies = [ "getrandom 0.4.2", "rand 0.10.1", "thiserror 2.0.18", - "vitaminc-protected", - "vitaminc-random-derives", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-random-derives 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "zeroize", +] + +[[package]] +name = "vitaminc-random" +version = "0.5.1" +source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand 0.10.1", + "thiserror 2.0.18", + "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-random-derives 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", "zeroize", ] @@ -2583,6 +2647,16 @@ dependencies = [ "syn 3.0.3", ] +[[package]] +name = "vitaminc-random-derives" +version = "0.5.1" +source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + [[package]] name = "vitaminc-traits" version = "0.5.1" @@ -2594,8 +2668,23 @@ dependencies = [ "rmp-serde", "serde", "thiserror 2.0.18", - "vitaminc-protected", - "vitaminc-random", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-random 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "zeroize", +] + +[[package]] +name = "vitaminc-traits" +version = "0.5.1" +source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +dependencies = [ + "anyhow", + "bytes", + "rmp-serde", + "serde", + "thiserror 2.0.18", + "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-random 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", "zeroize", ] diff --git a/packages/stack-kms/Cargo.toml b/packages/stack-kms/Cargo.toml index a0d920f1a..e0b1eb5f6 100644 --- a/packages/stack-kms/Cargo.toml +++ b/packages/stack-kms/Cargo.toml @@ -55,18 +55,17 @@ vitaminc = { workspace = true, features = ["protected", "random"] } # # Pinned to a git rev because these traits are not published yet. Swap to a # version once vitaminc releases them. -vitaminc-kms = { git = "https://github.com/cipherstash/vitaminc", rev = "c6009a2", default-features = false } +vitaminc-kms = { git = "https://github.com/cipherstash/vitaminc", rev = "b3bc6eb", default-features = false } # Direct dep required because the `TimingSafeEq` derive macro expands to a # `::vitaminc_protected` path. vitaminc-protected = { workspace = true } -# `vitaminc-kms` is built against its own, newer `vitaminc-protected` — a -# different line from the workspace's `0.2.0-pre` pin above, which -# `stack-encrypt` already carries the same split for. A version-matched but -# differently-sourced dependency is a *different* type at compile time, so +# `vitaminc-kms` is built against its own git-sourced `vitaminc-protected`. +# It has the same version (0.5.1) as the crates.io one the workspace pins +# above, but a version-matched and differently-sourced dependency is a *different* type at compile time, so # this must resolve to the identical git rev as `vitaminc-kms`'s own copy or # every call into its types is an E0308. Renamed so it cannot collide with # the entry above; used only by `provider.rs`. -vitaminc-protected-kms = { package = "vitaminc-protected", git = "https://github.com/cipherstash/vitaminc", rev = "c6009a2" } +vitaminc-protected-kms = { package = "vitaminc-protected", git = "https://github.com/cipherstash/vitaminc", rev = "b3bc6eb" } blake3 = { workspace = true } lazy_static = { workspace = true, optional = true } diff --git a/packages/stack-kms/fuzz/Cargo.lock b/packages/stack-kms/fuzz/Cargo.lock index a18c2eba9..8207dc5ea 100644 --- a/packages/stack-kms/fuzz/Cargo.lock +++ b/packages/stack-kms/fuzz/Cargo.lock @@ -1956,7 +1956,7 @@ dependencies = [ "url", "uuid", "vitaminc", - "vitaminc-protected", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "web-time", "zeroize", "zerokms-protocol", @@ -1984,7 +1984,9 @@ dependencies = [ "url", "uuid", "vitaminc", - "vitaminc-protected", + "vitaminc-kms", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", "zeroize", "zerokms-protocol", ] @@ -2365,9 +2367,9 @@ dependencies = [ "vitaminc-aead", "vitaminc-context", "vitaminc-encrypt", - "vitaminc-protected", - "vitaminc-random", - "vitaminc-traits", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-random 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-traits 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2380,8 +2382,8 @@ dependencies = [ "serde", "vitaminc-aead-derive", "vitaminc-context", - "vitaminc-protected", - "vitaminc-random", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-random 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "zeroize", ] @@ -2396,6 +2398,15 @@ dependencies = [ "syn 3.0.3", ] +[[package]] +name = "vitaminc-async-traits" +version = "0.5.1" +source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +dependencies = [ + "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-traits 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", +] + [[package]] name = "vitaminc-context" version = "0.5.1" @@ -2403,7 +2414,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7df52501fe33cdb3ee9e155b5242fac184c037d147e58c96e5143ed011252a8" dependencies = [ "mutants", - "vitaminc-protected", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2415,8 +2426,21 @@ dependencies = [ "aes-gcm", "aws-lc-rs", "vitaminc-aead", - "vitaminc-protected", - "vitaminc-random", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-random 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "zeroize", +] + +[[package]] +name = "vitaminc-kms" +version = "0.5.1" +source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +dependencies = [ + "futures", + "thiserror 2.0.18", + "vitaminc-async-traits", + "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-traits 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", "zeroize", ] @@ -2433,7 +2457,23 @@ dependencies = [ "serde_bytes", "subtle", "thiserror 2.0.18", - "vitaminc-protected-derive", + "vitaminc-protected-derive 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "zeroize", +] + +[[package]] +name = "vitaminc-protected" +version = "0.5.1" +source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +dependencies = [ + "bitvec", + "digest 0.11.3", + "libc", + "serde", + "serde_bytes", + "subtle", + "thiserror 2.0.18", + "vitaminc-protected-derive 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", "zeroize", ] @@ -2448,6 +2488,16 @@ dependencies = [ "syn 3.0.3", ] +[[package]] +name = "vitaminc-protected-derive" +version = "0.5.1" +source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + [[package]] name = "vitaminc-random" version = "0.5.1" @@ -2458,8 +2508,22 @@ dependencies = [ "getrandom 0.4.2", "rand 0.10.1", "thiserror 2.0.18", - "vitaminc-protected", - "vitaminc-random-derives", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-random-derives 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "zeroize", +] + +[[package]] +name = "vitaminc-random" +version = "0.5.1" +source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand 0.10.1", + "thiserror 2.0.18", + "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-random-derives 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", "zeroize", ] @@ -2474,6 +2538,16 @@ dependencies = [ "syn 3.0.3", ] +[[package]] +name = "vitaminc-random-derives" +version = "0.5.1" +source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + [[package]] name = "vitaminc-traits" version = "0.5.1" @@ -2485,8 +2559,23 @@ dependencies = [ "rmp-serde", "serde", "thiserror 2.0.18", - "vitaminc-protected", - "vitaminc-random", + "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-random 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "zeroize", +] + +[[package]] +name = "vitaminc-traits" +version = "0.5.1" +source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +dependencies = [ + "anyhow", + "bytes", + "rmp-serde", + "serde", + "thiserror 2.0.18", + "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-random 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", "zeroize", ] From cf14683aa00a3642a203adf843aa8ac2f93f7a0b Mon Sep 17 00:00:00 2001 From: James Sadler Date: Tue, 6 Oct 2026 10:15:30 +1100 Subject: [PATCH 5/7] ci(crates): skip a git-pinned crate in the publish dry run, with a warning stack-kms now pins vitaminc-kms to a git rev, because vitaminc has not released the key-provider traits yet (cipherstash/vitaminc#352). cargo refuses to package a crate with a git dependency, so the dry run failed on stack-kms although nothing in this PR publishes it. A `version` beside the `git` would not help: the packaged crate would then build against vitaminc-kms 0.5.1 from crates.io, which does not have the traits. The step now finds the crates in its list that have a non-dev git dependency, skips each with a warning, and verifies the rest. This is the same shape test-eql.yml uses for a stack-encrypt not yet on crates.io. When vitaminc releases and the pin becomes a version, the skip stops applying by itself. --- .github/workflows/tests-crates.yml | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests-crates.yml b/.github/workflows/tests-crates.yml index 40cc572cc..b0f9a60f5 100644 --- a/.github/workflows/tests-crates.yml +++ b/.github/workflows/tests-crates.yml @@ -153,8 +153,36 @@ jobs: # `--allow-dirty` tolerates files earlier steps leave in the tree. Once a # version is on crates.io, cargo warns rather than fails on a dry run of # it. + # + # A crate with a git dependency outside its dev-dependencies cannot be + # published: cargo refuses to package it, and with a `version` beside the + # `git` it would build against crates.io's crate of that name, not the + # pinned rev. stack-kms pins vitaminc-kms to a git rev until vitaminc + # releases its key-provider traits (cipherstash/vitaminc#352). Such a + # crate is skipped here with a WARNING, as `test-eql.yml` does for a + # stack-encrypt not yet on crates.io: the PR cannot ship it, and + # release-crates fails loudly if a version bump tries. Every other crate + # in the list is still verified. - name: Verify the stack-* crates package cleanly for crates.io - run: cargo publish --locked --dry-run --allow-dirty -p stack-profile -p stack-auth -p stack-kms -p stack-encrypt-derive -p stack-encrypt + run: | + crates=(stack-profile stack-auth stack-kms stack-encrypt-derive stack-encrypt) + git_pinned=$(cargo metadata --locked --no-deps --format-version 1 | jq -r ' + .packages[] + | select(any(.dependencies[]; .kind != "dev" and ((.source // "") | startswith("git+")))) + | .name') + args=() + for crate in "${crates[@]}"; do + if grep -qx "$crate" <<<"$git_pinned"; then + echo "::warning title=${crate} cannot be published::${crate} has a git dependency, so cargo cannot package it. The dry run skips it until that dependency is on crates.io." + else + args+=(-p "$crate") + fi + done + if [ "${#args[@]}" -eq 0 ]; then + echo "Every crate in the list has a git dependency; nothing to verify." + exit 0 + fi + cargo publish --locked --dry-run --allow-dirty "${args[@]}" node-bindings: name: node bindings, napi typings, stack-auth-wasm From 3776cd4cc9ef69a4f453338e790c63ffa18337a9 Mon Sep 17 00:00:00 2001 From: James Sadler Date: Tue, 6 Oct 2026 10:47:40 +1100 Subject: [PATCH 6/7] docs(stack-kms): changelog ZeroKmsKeyset stack-kms had no CHANGELOG.md. It is published from 0.1.0 in its own version group, so it gets one in the format stack-encrypt's uses, with the entries for this change under Unreleased. --- packages/stack-kms/CHANGELOG.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 packages/stack-kms/CHANGELOG.md diff --git a/packages/stack-kms/CHANGELOG.md b/packages/stack-kms/CHANGELOG.md new file mode 100644 index 000000000..3bb7c1aba --- /dev/null +++ b/packages/stack-kms/CHANGELOG.md @@ -0,0 +1,29 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- `ZeroKmsKeyset`: one ZeroKMS keyset, resolved once over a shared + `Arc`, as a `vitaminc-kms` `KeyProvider<32>` and + `IndexKeyProvider<32>`. A client library can be generic over those traits + and run on ZeroKMS or on another data key source. It reconstructs keys + client-and-server, isolates them per value, and binds each key to its + descriptor. Its `KeyId` is the IV followed by the tag. +- `Error::BindingNotUtf8` and `Error::MalformedKeyId`, for a binding or a + key id that `ZeroKmsKeyset` cannot use. + +### Changed + +- `vitaminc-kms` is a git dependency until vitaminc releases the key + provider traits (cipherstash/vitaminc#352). Until then this crate cannot + be published. + +## [0.1.0] - 2026-10-04 + +The first crates.io release. From e4df2d632126f453db9a918126a793d81b422c10 Mon Sep 17 00:00:00 2001 From: James Sadler Date: Wed, 21 Oct 2026 21:52:00 +1100 Subject: [PATCH 7/7] chore(stack-kms): build against vitaminc-kms 5b8a9c7 (CIP-3965) Move the vitaminc-kms and vitaminc-protected git pins from b3bc6eb to 5b8a9c7, the head of the vitaminc PR stack ending at claude/cip-5029-aws-request-shape-tests. That stack removes KeyProvider::ISOLATION and ::BINDING, because every source now mints one key per value and binds it. It adds KeyProvider::CUSTODY. ZeroKmsKeyset declares BackendHeld: its key id names a key that ZeroKMS rebuilds, not a wrapped copy of the key. Main added a diagnostic code to every stack-kms error variant and an ErrorPayload impl. Give BindingNotUtf8 and MalformedKeyId their codes, payload arms and rows in the every-variant test. --- Cargo.lock | 117 +++++++++++------- languages/golang/encrypt/guest/Cargo.lock | 139 +++++++++++++++------- packages/stack-encrypt/fuzz/Cargo.lock | 137 ++++++++++++++------- packages/stack-kms/CHANGELOG.md | 11 +- packages/stack-kms/Cargo.toml | 10 +- packages/stack-kms/fuzz/Cargo.lock | 127 ++++++++++++++------ packages/stack-kms/src/errors.rs | 12 +- packages/stack-kms/src/provider.rs | 28 ++--- 8 files changed, 380 insertions(+), 201 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3bf9cc6d6..2d8bc0804 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2328,6 +2328,15 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.17+spec-1.1.0", +] + [[package]] name = "proc-macro-error-attr2" version = "2.0.0" @@ -3220,7 +3229,7 @@ dependencies = [ "url", "uuid", "vitaminc", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", "web-time", "zeroize", "zerokms-protocol", @@ -3242,7 +3251,7 @@ dependencies = [ "tempfile", "tokio", "url", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", "zeroize", ] @@ -3286,7 +3295,7 @@ dependencies = [ "vitaminc-encrypt", "vitaminc-hmac", "vitaminc-prf", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", "zeroize", "zerokms-protocol", ] @@ -3312,7 +3321,7 @@ dependencies = [ "serde_json", "thiserror 1.0.69", "vitaminc-aead-value", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", "zeroize", ] @@ -3346,8 +3355,8 @@ dependencies = [ "uuid", "vitaminc", "vitaminc-kms", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-protected 0.5.1", + "vitaminc-protected 0.8.0", "zeroize", "zerokms-protocol", ] @@ -3682,7 +3691,7 @@ dependencies = [ "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.11", - "toml_edit", + "toml_edit 0.22.27", ] [[package]] @@ -3732,6 +3741,18 @@ dependencies = [ "winnow 0.7.14", ] +[[package]] +name = "toml_edit" +version = "0.25.17+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3641d5bbb5349a79e1020a242d251efbc546ad8048d133958323ce9c40a9c9c" +dependencies = [ + "indexmap", + "toml_datetime 1.1.2+spec-1.1.0", + "toml_parser", + "winnow 1.0.4", +] + [[package]] name = "toml_parser" version = "1.1.5+spec-1.1.0" @@ -4078,9 +4099,9 @@ dependencies = [ "vitaminc-aead", "vitaminc-context", "vitaminc-encrypt", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "vitaminc-random 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "vitaminc-traits 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", + "vitaminc-random 0.5.1", + "vitaminc-traits 0.5.1", ] [[package]] @@ -4093,8 +4114,8 @@ dependencies = [ "serde", "vitaminc-aead-derive", "vitaminc-context", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "vitaminc-random 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", + "vitaminc-random 0.5.1", "zeroize", ] @@ -4116,17 +4137,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d78cdadbc6374c3bd7c19fb4d99abca693b96953b18fef8c608efbe092cf3ea" dependencies = [ "vitaminc-aead", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", "zeroize", ] [[package]] name = "vitaminc-async-traits" -version = "0.5.1" -source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +version = "0.8.0" +source = "git+https://github.com/cipherstash/vitaminc?rev=5b8a9c7#5b8a9c7721975d7128dda576b6f4d63e10dd6ffb" dependencies = [ - "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", - "vitaminc-traits 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-protected 0.8.0", + "vitaminc-traits 0.8.0", ] [[package]] @@ -4136,7 +4157,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7df52501fe33cdb3ee9e155b5242fac184c037d147e58c96e5143ed011252a8" dependencies = [ "mutants", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", ] [[package]] @@ -4148,8 +4169,8 @@ dependencies = [ "aes-gcm", "aws-lc-rs", "vitaminc-aead", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "vitaminc-random 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", + "vitaminc-random 0.5.1", "zeroize", ] @@ -4162,20 +4183,20 @@ dependencies = [ "hmac", "sha2 0.11.0", "vitaminc-prf", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", "zeroize", ] [[package]] name = "vitaminc-kms" -version = "0.5.1" -source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +version = "0.8.0" +source = "git+https://github.com/cipherstash/vitaminc?rev=5b8a9c7#5b8a9c7721975d7128dda576b6f4d63e10dd6ffb" dependencies = [ "futures", "thiserror 2.0.18", "vitaminc-async-traits", - "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", - "vitaminc-traits 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-protected 0.8.0", + "vitaminc-traits 0.8.0", "zeroize", ] @@ -4188,7 +4209,7 @@ dependencies = [ "mutants", "thiserror 2.0.18", "vitaminc-context", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", ] [[package]] @@ -4204,14 +4225,14 @@ dependencies = [ "serde_bytes", "subtle", "thiserror 2.0.18", - "vitaminc-protected-derive 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected-derive 0.5.1", "zeroize", ] [[package]] name = "vitaminc-protected" -version = "0.5.1" -source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +version = "0.8.0" +source = "git+https://github.com/cipherstash/vitaminc?rev=5b8a9c7#5b8a9c7721975d7128dda576b6f4d63e10dd6ffb" dependencies = [ "bitvec", "digest 0.11.3", @@ -4220,7 +4241,7 @@ dependencies = [ "serde_bytes", "subtle", "thiserror 2.0.18", - "vitaminc-protected-derive 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-protected-derive 0.8.0", "zeroize", ] @@ -4237,9 +4258,10 @@ dependencies = [ [[package]] name = "vitaminc-protected-derive" -version = "0.5.1" -source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +version = "0.8.0" +source = "git+https://github.com/cipherstash/vitaminc?rev=5b8a9c7#5b8a9c7721975d7128dda576b6f4d63e10dd6ffb" dependencies = [ + "proc-macro-crate", "proc-macro2", "quote", "syn 3.0.6", @@ -4255,22 +4277,22 @@ dependencies = [ "getrandom 0.4.2", "rand 0.10.1", "thiserror 2.0.18", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "vitaminc-random-derives 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", + "vitaminc-random-derives 0.5.1", "zeroize", ] [[package]] name = "vitaminc-random" -version = "0.5.1" -source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +version = "0.8.0" +source = "git+https://github.com/cipherstash/vitaminc?rev=5b8a9c7#5b8a9c7721975d7128dda576b6f4d63e10dd6ffb" dependencies = [ "chacha20", "getrandom 0.4.2", "rand 0.10.1", "thiserror 2.0.18", - "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", - "vitaminc-random-derives 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-protected 0.8.0", + "vitaminc-random-derives 0.8.0", "zeroize", ] @@ -4287,8 +4309,8 @@ dependencies = [ [[package]] name = "vitaminc-random-derives" -version = "0.5.1" -source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +version = "0.8.0" +source = "git+https://github.com/cipherstash/vitaminc?rev=5b8a9c7#5b8a9c7721975d7128dda576b6f4d63e10dd6ffb" dependencies = [ "proc-macro2", "quote", @@ -4306,23 +4328,23 @@ dependencies = [ "rmp-serde", "serde", "thiserror 2.0.18", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "vitaminc-random 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", + "vitaminc-random 0.5.1", "zeroize", ] [[package]] name = "vitaminc-traits" -version = "0.5.1" -source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +version = "0.8.0" +source = "git+https://github.com/cipherstash/vitaminc?rev=5b8a9c7#5b8a9c7721975d7128dda576b6f4d63e10dd6ffb" dependencies = [ "anyhow", "bytes", "rmp-serde", "serde", "thiserror 2.0.18", - "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", - "vitaminc-random 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-protected 0.8.0", + "vitaminc-random 0.8.0", "zeroize", ] @@ -4959,6 +4981,9 @@ name = "winnow" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] [[package]] name = "winreg" diff --git a/languages/golang/encrypt/guest/Cargo.lock b/languages/golang/encrypt/guest/Cargo.lock index ac077cccc..bace4e6fe 100644 --- a/languages/golang/encrypt/guest/Cargo.lock +++ b/languages/golang/encrypt/guest/Cargo.lock @@ -1486,6 +1486,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro-error-attr2" version = "2.0.0" @@ -2056,7 +2065,7 @@ dependencies = [ "url", "uuid", "vitaminc", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", "web-time", "zeroize", "zerokms-protocol", @@ -2080,7 +2089,7 @@ dependencies = [ "vitaminc-encrypt", "vitaminc-hmac", "vitaminc-prf", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", "zeroize", ] @@ -2110,7 +2119,7 @@ dependencies = [ "stack-kms", "uuid", "vitaminc-aead-value", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", "zeroize", "zerokms-protocol", ] @@ -2123,7 +2132,7 @@ dependencies = [ "serde_json", "thiserror 1.0.69", "vitaminc-aead-value", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", "zeroize", ] @@ -2151,8 +2160,8 @@ dependencies = [ "uuid", "vitaminc", "vitaminc-kms", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-protected 0.5.1", + "vitaminc-protected 0.8.0", "zeroize", "zerokms-protocol", ] @@ -2359,6 +2368,36 @@ dependencies = [ "syn 3.0.4", ] +[[package]] +name = "toml_datetime" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.17+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3641d5bbb5349a79e1020a242d251efbc546ad8048d133958323ce9c40a9c9c" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c" +dependencies = [ + "winnow", +] + [[package]] name = "tracing" version = "0.1.44" @@ -2578,9 +2617,9 @@ dependencies = [ "vitaminc-aead", "vitaminc-context", "vitaminc-encrypt", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "vitaminc-random 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "vitaminc-traits 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", + "vitaminc-random 0.5.1", + "vitaminc-traits 0.5.1", ] [[package]] @@ -2593,8 +2632,8 @@ dependencies = [ "serde", "vitaminc-aead-derive", "vitaminc-context", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "vitaminc-random 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", + "vitaminc-random 0.5.1", "zeroize", ] @@ -2616,17 +2655,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d78cdadbc6374c3bd7c19fb4d99abca693b96953b18fef8c608efbe092cf3ea" dependencies = [ "vitaminc-aead", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", "zeroize", ] [[package]] name = "vitaminc-async-traits" -version = "0.5.1" -source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +version = "0.8.0" +source = "git+https://github.com/cipherstash/vitaminc?rev=5b8a9c7#5b8a9c7721975d7128dda576b6f4d63e10dd6ffb" dependencies = [ - "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", - "vitaminc-traits 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-protected 0.8.0", + "vitaminc-traits 0.8.0", ] [[package]] @@ -2636,7 +2675,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7df52501fe33cdb3ee9e155b5242fac184c037d147e58c96e5143ed011252a8" dependencies = [ "mutants", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", ] [[package]] @@ -2648,8 +2687,8 @@ dependencies = [ "aes-gcm", "aws-lc-rs", "vitaminc-aead", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "vitaminc-random 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", + "vitaminc-random 0.5.1", "zeroize", ] @@ -2662,20 +2701,20 @@ dependencies = [ "hmac", "sha2 0.11.0", "vitaminc-prf", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", "zeroize", ] [[package]] name = "vitaminc-kms" -version = "0.5.1" -source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +version = "0.8.0" +source = "git+https://github.com/cipherstash/vitaminc?rev=5b8a9c7#5b8a9c7721975d7128dda576b6f4d63e10dd6ffb" dependencies = [ "futures", "thiserror 2.0.20", "vitaminc-async-traits", - "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", - "vitaminc-traits 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-protected 0.8.0", + "vitaminc-traits 0.8.0", "zeroize", ] @@ -2688,7 +2727,7 @@ dependencies = [ "mutants", "thiserror 2.0.20", "vitaminc-context", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", ] [[package]] @@ -2704,14 +2743,14 @@ dependencies = [ "serde_bytes", "subtle", "thiserror 2.0.20", - "vitaminc-protected-derive 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected-derive 0.5.1", "zeroize", ] [[package]] name = "vitaminc-protected" -version = "0.5.1" -source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +version = "0.8.0" +source = "git+https://github.com/cipherstash/vitaminc?rev=5b8a9c7#5b8a9c7721975d7128dda576b6f4d63e10dd6ffb" dependencies = [ "bitvec", "digest 0.11.3", @@ -2720,7 +2759,7 @@ dependencies = [ "serde_bytes", "subtle", "thiserror 2.0.20", - "vitaminc-protected-derive 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-protected-derive 0.8.0", "zeroize", ] @@ -2737,9 +2776,10 @@ dependencies = [ [[package]] name = "vitaminc-protected-derive" -version = "0.5.1" -source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +version = "0.8.0" +source = "git+https://github.com/cipherstash/vitaminc?rev=5b8a9c7#5b8a9c7721975d7128dda576b6f4d63e10dd6ffb" dependencies = [ + "proc-macro-crate", "proc-macro2", "quote", "syn 3.0.4", @@ -2755,22 +2795,22 @@ dependencies = [ "getrandom 0.4.3", "rand 0.10.2", "thiserror 2.0.20", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "vitaminc-random-derives 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", + "vitaminc-random-derives 0.5.1", "zeroize", ] [[package]] name = "vitaminc-random" -version = "0.5.1" -source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +version = "0.8.0" +source = "git+https://github.com/cipherstash/vitaminc?rev=5b8a9c7#5b8a9c7721975d7128dda576b6f4d63e10dd6ffb" dependencies = [ "chacha20", "getrandom 0.4.3", "rand 0.10.2", "thiserror 2.0.20", - "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", - "vitaminc-random-derives 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-protected 0.8.0", + "vitaminc-random-derives 0.8.0", "zeroize", ] @@ -2787,8 +2827,8 @@ dependencies = [ [[package]] name = "vitaminc-random-derives" -version = "0.5.1" -source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +version = "0.8.0" +source = "git+https://github.com/cipherstash/vitaminc?rev=5b8a9c7#5b8a9c7721975d7128dda576b6f4d63e10dd6ffb" dependencies = [ "proc-macro2", "quote", @@ -2806,23 +2846,23 @@ dependencies = [ "rmp-serde", "serde", "thiserror 2.0.20", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "vitaminc-random 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", + "vitaminc-random 0.5.1", "zeroize", ] [[package]] name = "vitaminc-traits" -version = "0.5.1" -source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +version = "0.8.0" +source = "git+https://github.com/cipherstash/vitaminc?rev=5b8a9c7#5b8a9c7721975d7128dda576b6f4d63e10dd6ffb" dependencies = [ "anyhow", "bytes", "rmp-serde", "serde", "thiserror 2.0.20", - "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", - "vitaminc-random 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-protected 0.8.0", + "vitaminc-random 0.8.0", "zeroize", ] @@ -3068,6 +3108,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen" version = "0.57.1" diff --git a/packages/stack-encrypt/fuzz/Cargo.lock b/packages/stack-encrypt/fuzz/Cargo.lock index 324792f17..b93d5bd37 100644 --- a/packages/stack-encrypt/fuzz/Cargo.lock +++ b/packages/stack-encrypt/fuzz/Cargo.lock @@ -1506,6 +1506,15 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro-error-attr2" version = "2.0.0" @@ -1971,7 +1980,7 @@ dependencies = [ "url", "uuid", "vitaminc", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", "web-time", "zeroize", "zerokms-protocol", @@ -1995,7 +2004,7 @@ dependencies = [ "vitaminc-encrypt", "vitaminc-hmac", "vitaminc-prf", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", "zeroize", ] @@ -2016,7 +2025,7 @@ dependencies = [ "libfuzzer-sys", "stack-encrypt", "uuid", - "vitaminc-protected", + "vitaminc-protected 0.5.1", ] [[package]] @@ -2043,8 +2052,8 @@ dependencies = [ "uuid", "vitaminc", "vitaminc-kms", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-protected 0.5.1", + "vitaminc-protected 0.8.0", "zeroize", "zerokms-protocol", ] @@ -2243,6 +2252,36 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "toml_datetime" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.17+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3641d5bbb5349a79e1020a242d251efbc546ad8048d133958323ce9c40a9c9c" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c" +dependencies = [ + "winnow", +] + [[package]] name = "tracing" version = "0.1.44" @@ -2440,9 +2479,9 @@ dependencies = [ "vitaminc-aead", "vitaminc-context", "vitaminc-encrypt", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "vitaminc-random 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "vitaminc-traits 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", + "vitaminc-random 0.5.1", + "vitaminc-traits 0.5.1", ] [[package]] @@ -2455,8 +2494,8 @@ dependencies = [ "serde", "vitaminc-aead-derive", "vitaminc-context", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "vitaminc-random 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", + "vitaminc-random 0.5.1", "zeroize", ] @@ -2478,17 +2517,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d78cdadbc6374c3bd7c19fb4d99abca693b96953b18fef8c608efbe092cf3ea" dependencies = [ "vitaminc-aead", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", "zeroize", ] [[package]] name = "vitaminc-async-traits" -version = "0.5.1" -source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +version = "0.8.0" +source = "git+https://github.com/cipherstash/vitaminc?rev=5b8a9c7#5b8a9c7721975d7128dda576b6f4d63e10dd6ffb" dependencies = [ - "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", - "vitaminc-traits 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-protected 0.8.0", + "vitaminc-traits 0.8.0", ] [[package]] @@ -2498,7 +2537,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7df52501fe33cdb3ee9e155b5242fac184c037d147e58c96e5143ed011252a8" dependencies = [ "mutants", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", ] [[package]] @@ -2510,8 +2549,8 @@ dependencies = [ "aes-gcm", "aws-lc-rs", "vitaminc-aead", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "vitaminc-random 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", + "vitaminc-random 0.5.1", "zeroize", ] @@ -2524,20 +2563,20 @@ dependencies = [ "hmac", "sha2 0.11.0", "vitaminc-prf", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", "zeroize", ] [[package]] name = "vitaminc-kms" -version = "0.5.1" -source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +version = "0.8.0" +source = "git+https://github.com/cipherstash/vitaminc?rev=5b8a9c7#5b8a9c7721975d7128dda576b6f4d63e10dd6ffb" dependencies = [ "futures", "thiserror 2.0.18", "vitaminc-async-traits", - "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", - "vitaminc-traits 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-protected 0.8.0", + "vitaminc-traits 0.8.0", "zeroize", ] @@ -2550,7 +2589,7 @@ dependencies = [ "mutants", "thiserror 2.0.18", "vitaminc-context", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", ] [[package]] @@ -2566,14 +2605,14 @@ dependencies = [ "serde_bytes", "subtle", "thiserror 2.0.18", - "vitaminc-protected-derive 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected-derive 0.5.1", "zeroize", ] [[package]] name = "vitaminc-protected" -version = "0.5.1" -source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +version = "0.8.0" +source = "git+https://github.com/cipherstash/vitaminc?rev=5b8a9c7#5b8a9c7721975d7128dda576b6f4d63e10dd6ffb" dependencies = [ "bitvec", "digest 0.11.3", @@ -2582,7 +2621,7 @@ dependencies = [ "serde_bytes", "subtle", "thiserror 2.0.18", - "vitaminc-protected-derive 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-protected-derive 0.8.0", "zeroize", ] @@ -2599,9 +2638,10 @@ dependencies = [ [[package]] name = "vitaminc-protected-derive" -version = "0.5.1" -source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +version = "0.8.0" +source = "git+https://github.com/cipherstash/vitaminc?rev=5b8a9c7#5b8a9c7721975d7128dda576b6f4d63e10dd6ffb" dependencies = [ + "proc-macro-crate", "proc-macro2", "quote", "syn 3.0.3", @@ -2617,22 +2657,22 @@ dependencies = [ "getrandom 0.4.2", "rand 0.10.1", "thiserror 2.0.18", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "vitaminc-random-derives 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", + "vitaminc-random-derives 0.5.1", "zeroize", ] [[package]] name = "vitaminc-random" -version = "0.5.1" -source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +version = "0.8.0" +source = "git+https://github.com/cipherstash/vitaminc?rev=5b8a9c7#5b8a9c7721975d7128dda576b6f4d63e10dd6ffb" dependencies = [ "chacha20", "getrandom 0.4.2", "rand 0.10.1", "thiserror 2.0.18", - "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", - "vitaminc-random-derives 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-protected 0.8.0", + "vitaminc-random-derives 0.8.0", "zeroize", ] @@ -2649,8 +2689,8 @@ dependencies = [ [[package]] name = "vitaminc-random-derives" -version = "0.5.1" -source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +version = "0.8.0" +source = "git+https://github.com/cipherstash/vitaminc?rev=5b8a9c7#5b8a9c7721975d7128dda576b6f4d63e10dd6ffb" dependencies = [ "proc-macro2", "quote", @@ -2668,23 +2708,23 @@ dependencies = [ "rmp-serde", "serde", "thiserror 2.0.18", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "vitaminc-random 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", + "vitaminc-random 0.5.1", "zeroize", ] [[package]] name = "vitaminc-traits" -version = "0.5.1" -source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +version = "0.8.0" +source = "git+https://github.com/cipherstash/vitaminc?rev=5b8a9c7#5b8a9c7721975d7128dda576b6f4d63e10dd6ffb" dependencies = [ "anyhow", "bytes", "rmp-serde", "serde", "thiserror 2.0.18", - "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", - "vitaminc-random 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-protected 0.8.0", + "vitaminc-random 0.8.0", "zeroize", ] @@ -3038,6 +3078,15 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen" version = "0.51.0" diff --git a/packages/stack-kms/CHANGELOG.md b/packages/stack-kms/CHANGELOG.md index 3bb7c1aba..0c967fddc 100644 --- a/packages/stack-kms/CHANGELOG.md +++ b/packages/stack-kms/CHANGELOG.md @@ -13,10 +13,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 `Arc`, as a `vitaminc-kms` `KeyProvider<32>` and `IndexKeyProvider<32>`. A client library can be generic over those traits and run on ZeroKMS or on another data key source. It reconstructs keys - client-and-server, isolates them per value, and binds each key to its - descriptor. Its `KeyId` is the IV followed by the tag. -- `Error::BindingNotUtf8` and `Error::MalformedKeyId`, for a binding or a - key id that `ZeroKmsKeyset` cannot use. + client-and-server, mints one key per value, and binds each key to its + descriptor. Its `KeyId` is the IV followed by the tag, and its custody is + `BackendHeld`: the id names a key ZeroKMS rebuilds, not a wrapped copy of + the key. +- `Error::BindingNotUtf8` (code `stack_kms::binding_not_utf8`) and + `Error::MalformedKeyId` (code `stack_kms::malformed_key_id`), for a binding + or a key id that `ZeroKmsKeyset` cannot use. ### Changed diff --git a/packages/stack-kms/Cargo.toml b/packages/stack-kms/Cargo.toml index e0b1eb5f6..1f9368236 100644 --- a/packages/stack-kms/Cargo.toml +++ b/packages/stack-kms/Cargo.toml @@ -55,17 +55,17 @@ vitaminc = { workspace = true, features = ["protected", "random"] } # # Pinned to a git rev because these traits are not published yet. Swap to a # version once vitaminc releases them. -vitaminc-kms = { git = "https://github.com/cipherstash/vitaminc", rev = "b3bc6eb", default-features = false } +vitaminc-kms = { git = "https://github.com/cipherstash/vitaminc", rev = "5b8a9c7", default-features = false } # Direct dep required because the `TimingSafeEq` derive macro expands to a # `::vitaminc_protected` path. vitaminc-protected = { workspace = true } -# `vitaminc-kms` is built against its own git-sourced `vitaminc-protected`. -# It has the same version (0.5.1) as the crates.io one the workspace pins -# above, but a version-matched and differently-sourced dependency is a *different* type at compile time, so +# `vitaminc-kms` is built against its own git-sourced `vitaminc-protected` +# (0.8.0 at this rev), not the crates.io 0.5.1 the workspace pins above. A +# differently-sourced dependency is a *different* type at compile time, so # this must resolve to the identical git rev as `vitaminc-kms`'s own copy or # every call into its types is an E0308. Renamed so it cannot collide with # the entry above; used only by `provider.rs`. -vitaminc-protected-kms = { package = "vitaminc-protected", git = "https://github.com/cipherstash/vitaminc", rev = "b3bc6eb" } +vitaminc-protected-kms = { package = "vitaminc-protected", git = "https://github.com/cipherstash/vitaminc", rev = "5b8a9c7" } blake3 = { workspace = true } lazy_static = { workspace = true, optional = true } diff --git a/packages/stack-kms/fuzz/Cargo.lock b/packages/stack-kms/fuzz/Cargo.lock index 8207dc5ea..a2434a217 100644 --- a/packages/stack-kms/fuzz/Cargo.lock +++ b/packages/stack-kms/fuzz/Cargo.lock @@ -1474,6 +1474,15 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro-error-attr2" version = "2.0.0" @@ -1956,7 +1965,7 @@ dependencies = [ "url", "uuid", "vitaminc", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", "web-time", "zeroize", "zerokms-protocol", @@ -1985,8 +1994,8 @@ dependencies = [ "uuid", "vitaminc", "vitaminc-kms", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-protected 0.5.1", + "vitaminc-protected 0.8.0", "zeroize", "zerokms-protocol", ] @@ -2179,6 +2188,36 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "toml_datetime" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.17+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3641d5bbb5349a79e1020a242d251efbc546ad8048d133958323ce9c40a9c9c" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c" +dependencies = [ + "winnow", +] + [[package]] name = "tracing" version = "0.1.44" @@ -2367,9 +2406,9 @@ dependencies = [ "vitaminc-aead", "vitaminc-context", "vitaminc-encrypt", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "vitaminc-random 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "vitaminc-traits 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", + "vitaminc-random 0.5.1", + "vitaminc-traits 0.5.1", ] [[package]] @@ -2382,8 +2421,8 @@ dependencies = [ "serde", "vitaminc-aead-derive", "vitaminc-context", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "vitaminc-random 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", + "vitaminc-random 0.5.1", "zeroize", ] @@ -2400,11 +2439,11 @@ dependencies = [ [[package]] name = "vitaminc-async-traits" -version = "0.5.1" -source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +version = "0.8.0" +source = "git+https://github.com/cipherstash/vitaminc?rev=5b8a9c7#5b8a9c7721975d7128dda576b6f4d63e10dd6ffb" dependencies = [ - "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", - "vitaminc-traits 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-protected 0.8.0", + "vitaminc-traits 0.8.0", ] [[package]] @@ -2414,7 +2453,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7df52501fe33cdb3ee9e155b5242fac184c037d147e58c96e5143ed011252a8" dependencies = [ "mutants", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", ] [[package]] @@ -2426,21 +2465,21 @@ dependencies = [ "aes-gcm", "aws-lc-rs", "vitaminc-aead", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "vitaminc-random 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", + "vitaminc-random 0.5.1", "zeroize", ] [[package]] name = "vitaminc-kms" -version = "0.5.1" -source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +version = "0.8.0" +source = "git+https://github.com/cipherstash/vitaminc?rev=5b8a9c7#5b8a9c7721975d7128dda576b6f4d63e10dd6ffb" dependencies = [ "futures", "thiserror 2.0.18", "vitaminc-async-traits", - "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", - "vitaminc-traits 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-protected 0.8.0", + "vitaminc-traits 0.8.0", "zeroize", ] @@ -2457,14 +2496,14 @@ dependencies = [ "serde_bytes", "subtle", "thiserror 2.0.18", - "vitaminc-protected-derive 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected-derive 0.5.1", "zeroize", ] [[package]] name = "vitaminc-protected" -version = "0.5.1" -source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +version = "0.8.0" +source = "git+https://github.com/cipherstash/vitaminc?rev=5b8a9c7#5b8a9c7721975d7128dda576b6f4d63e10dd6ffb" dependencies = [ "bitvec", "digest 0.11.3", @@ -2473,7 +2512,7 @@ dependencies = [ "serde_bytes", "subtle", "thiserror 2.0.18", - "vitaminc-protected-derive 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-protected-derive 0.8.0", "zeroize", ] @@ -2490,9 +2529,10 @@ dependencies = [ [[package]] name = "vitaminc-protected-derive" -version = "0.5.1" -source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +version = "0.8.0" +source = "git+https://github.com/cipherstash/vitaminc?rev=5b8a9c7#5b8a9c7721975d7128dda576b6f4d63e10dd6ffb" dependencies = [ + "proc-macro-crate", "proc-macro2", "quote", "syn 3.0.3", @@ -2508,22 +2548,22 @@ dependencies = [ "getrandom 0.4.2", "rand 0.10.1", "thiserror 2.0.18", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "vitaminc-random-derives 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", + "vitaminc-random-derives 0.5.1", "zeroize", ] [[package]] name = "vitaminc-random" -version = "0.5.1" -source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +version = "0.8.0" +source = "git+https://github.com/cipherstash/vitaminc?rev=5b8a9c7#5b8a9c7721975d7128dda576b6f4d63e10dd6ffb" dependencies = [ "chacha20", "getrandom 0.4.2", "rand 0.10.1", "thiserror 2.0.18", - "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", - "vitaminc-random-derives 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-protected 0.8.0", + "vitaminc-random-derives 0.8.0", "zeroize", ] @@ -2540,8 +2580,8 @@ dependencies = [ [[package]] name = "vitaminc-random-derives" -version = "0.5.1" -source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +version = "0.8.0" +source = "git+https://github.com/cipherstash/vitaminc?rev=5b8a9c7#5b8a9c7721975d7128dda576b6f4d63e10dd6ffb" dependencies = [ "proc-macro2", "quote", @@ -2559,23 +2599,23 @@ dependencies = [ "rmp-serde", "serde", "thiserror 2.0.18", - "vitaminc-protected 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "vitaminc-random 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vitaminc-protected 0.5.1", + "vitaminc-random 0.5.1", "zeroize", ] [[package]] name = "vitaminc-traits" -version = "0.5.1" -source = "git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb#b3bc6eb484f3ed1b933b5706f18b5e2fedc0d823" +version = "0.8.0" +source = "git+https://github.com/cipherstash/vitaminc?rev=5b8a9c7#5b8a9c7721975d7128dda576b6f4d63e10dd6ffb" dependencies = [ "anyhow", "bytes", "rmp-serde", "serde", "thiserror 2.0.18", - "vitaminc-protected 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", - "vitaminc-random 0.5.1 (git+https://github.com/cipherstash/vitaminc?rev=b3bc6eb)", + "vitaminc-protected 0.8.0", + "vitaminc-random 0.8.0", "zeroize", ] @@ -2929,6 +2969,15 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen" version = "0.51.0" diff --git a/packages/stack-kms/src/errors.rs b/packages/stack-kms/src/errors.rs index b39a41c95..4126827af 100644 --- a/packages/stack-kms/src/errors.rs +++ b/packages/stack-kms/src/errors.rs @@ -339,6 +339,8 @@ mod codes { Error::ConnectionInit(Box::new(std::io::Error::other("no"))) }, Error::InvalidEndpoint(_) => Error::InvalidEndpoint(InvalidEndpoint::Userinfo), + Error::BindingNotUtf8 => Error::BindingNotUtf8, + Error::MalformedKeyId { .. } => Error::MalformedKeyId { len: 3, min: 16 }, Error::Unexpected(_) => Error::Unexpected("unexpected".into()), ])); errors.extend(boxed(variants![ @@ -752,12 +754,14 @@ pub enum Error { /// [`ZeroKmsKeyset`](crate::ZeroKmsKeyset) was not UTF-8. ZeroKMS /// descriptors are strings. #[error("a ZeroKMS binding must be UTF-8 text (it is the descriptor)")] + #[diagnostic(code(stack_kms::binding_not_utf8))] BindingNotUtf8, /// A [`KeyId`](vitaminc_kms::KeyId) handed to /// [`ZeroKmsKeyset`](crate::ZeroKmsKeyset) is too short to hold the IV /// that leads every id it mints. #[error("ZeroKMS key id is {len} bytes; the IV alone is {min}")] + #[diagnostic(code(stack_kms::malformed_key_id))] MalformedKeyId { len: usize, min: usize }, #[error("Unexpected error: {0}")] @@ -772,9 +776,11 @@ impl ErrorPayload for Error { Self::RetrieveKey(error) => error.payload(), Self::LoadKeyset(error) => error.payload(), Self::Auth(error) => error.payload(), - Self::ConnectionInit(_) | Self::InvalidEndpoint(_) | Self::Unexpected(_) => { - serde_json::Map::new() - } + Self::ConnectionInit(_) + | Self::InvalidEndpoint(_) + | Self::BindingNotUtf8 + | Self::MalformedKeyId { .. } + | Self::Unexpected(_) => serde_json::Map::new(), } } } diff --git a/packages/stack-kms/src/provider.rs b/packages/stack-kms/src/provider.rs index 66e33cb24..74b8f987d 100644 --- a/packages/stack-kms/src/provider.rs +++ b/packages/stack-kms/src/provider.rs @@ -9,11 +9,14 @@ //! material and the data key only exists after the client's own keyset //! re-enciphers it (`recipher`). A server-side compromise plus a stored //! key id does not recover a key. -//! - `ISOLATION = PerValue`: every value gets its own key, in one round -//! trip per batch. -//! - `BINDING = Bound`: the binding is the descriptor. ZeroKMS binds it into -//! the key server-side, refuses retrieval under a different one, and logs -//! it per retrieval (ADR 0002 in `packages/stack-encrypt/docs/adr`). +//! - `CUSTODY = BackendHeld`: the [`KeyId`] is not the data key in wrapped +//! form. It names a key the service rebuilds from material it holds, so +//! losing a stored id does not lose the only copy of the key. +//! +//! Every value gets its own key, in one round trip per batch. The binding is +//! the descriptor: ZeroKMS binds it into the key server-side, refuses +//! retrieval under a different one, and logs it per retrieval (ADR 0002 in +//! `packages/stack-encrypt/docs/adr`). //! //! The [`KeyId`] is the ZeroKMS IV followed by the tag: the two halves the //! service needs back to re-derive a key. @@ -22,8 +25,8 @@ use std::borrow::Cow; use std::sync::Arc; use uuid::Uuid; -use vitaminc_kms::provider::{Binding, BindingSupport, IndexKeyProvider, KeyProvider}; -use vitaminc_kms::{GeneratedDataKey, IndexKeyMaterial, KeyId, KeyIsolation, KeyReconstruction}; +use vitaminc_kms::provider::{Binding, IndexKeyProvider, KeyProvider}; +use vitaminc_kms::{GeneratedDataKey, IndexKeyMaterial, KeyCustody, KeyId, KeyReconstruction}; use vitaminc_protected_kms::Protected; use zerokms_protocol::IdentifiedBy; @@ -111,8 +114,7 @@ where type Error = Error; const RECONSTRUCTION: KeyReconstruction = KeyReconstruction::ClientAndServer; - const ISOLATION: KeyIsolation = KeyIsolation::PerValue; - const BINDING: BindingSupport = BindingSupport::Bound; + const CUSTODY: KeyCustody = KeyCustody::BackendHeld; async fn generate_keys( &self, @@ -598,12 +600,8 @@ mod tests { KeyReconstruction::ClientAndServer ); assert_eq!( - >::ISOLATION, - KeyIsolation::PerValue - ); - assert_eq!( - >::BINDING, - BindingSupport::Bound + >::CUSTODY, + KeyCustody::BackendHeld ); } }