Skip to main content

basil_proto/
types.rs

1// SPDX-FileCopyrightText: 2026 OpenBasil Contributors
2//
3// SPDX-License-Identifier: Apache-2.0
4
5//! Shared Basil domain types used by the client and agent internals.
6
7use serde::{Deserialize, Serialize};
8use zeroize::Zeroize;
9
10/// Asymmetric key type used by key creation, import, signing, and minting.
11///
12/// Mirrors the wire `basil.broker.v1.KeyType` enum one-for-one, including the
13/// post-quantum families. The classical types (`ed25519`..`ecdsa-p256`) are
14/// produced/imported in place by the backend; the post-quantum types
15/// (`ml-dsa-*` signing, `ml-kem-*` sealing) are provisioned through the
16/// local-software crypto provider against an operator-declared software-custody
17/// catalog entry: a client names the type but never the custody/storage.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
19pub enum KeyType {
20    /// Raw Ed25519 signing key.
21    #[serde(rename = "ed25519")]
22    Ed25519,
23    /// Ed25519 in the NATS `NKey` envelope.
24    #[serde(rename = "ed25519-nkey")]
25    Ed25519Nkey,
26    /// RSA-2048.
27    #[serde(rename = "rsa-2048")]
28    Rsa2048,
29    /// ECDSA P-256.
30    #[serde(rename = "ecdsa-p256")]
31    EcdsaP256,
32    /// ECDSA P-384.
33    #[serde(rename = "ecdsa-p384")]
34    EcdsaP384,
35    /// ECDSA P-521.
36    #[serde(rename = "ecdsa-p521")]
37    EcdsaP521,
38    /// ML-DSA (FIPS 204) post-quantum signatures, parameter set 44.
39    #[serde(rename = "ml-dsa-44")]
40    MlDsa44,
41    /// ML-DSA parameter set 65.
42    #[serde(rename = "ml-dsa-65")]
43    MlDsa65,
44    /// ML-DSA parameter set 87.
45    #[serde(rename = "ml-dsa-87")]
46    MlDsa87,
47    /// ML-KEM (FIPS 203) post-quantum key encapsulation, parameter set 512.
48    #[serde(rename = "ml-kem-512")]
49    MlKem512,
50    /// ML-KEM parameter set 768.
51    #[serde(rename = "ml-kem-768")]
52    MlKem768,
53    /// ML-KEM parameter set 1024.
54    #[serde(rename = "ml-kem-1024")]
55    MlKem1024,
56}
57
58impl std::fmt::Display for KeyType {
59    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60        f.write_str(match self {
61            Self::Ed25519 => "ed25519",
62            Self::Ed25519Nkey => "ed25519-nkey",
63            Self::Rsa2048 => "rsa-2048",
64            Self::EcdsaP256 => "ecdsa-p256",
65            Self::EcdsaP384 => "ecdsa-p384",
66            Self::EcdsaP521 => "ecdsa-p521",
67            Self::MlDsa44 => "ml-dsa-44",
68            Self::MlDsa65 => "ml-dsa-65",
69            Self::MlDsa87 => "ml-dsa-87",
70            Self::MlKem512 => "ml-kem-512",
71            Self::MlKem768 => "ml-kem-768",
72            Self::MlKem1024 => "ml-kem-1024",
73        })
74    }
75}
76
77/// AEAD suite used for Basil-owned nonce encryption.
78#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
79pub enum AeadAlgorithm {
80    /// `ChaCha20-Poly1305`: 12-byte nonce, 16-byte tag.
81    #[serde(rename = "chacha20-poly1305")]
82    Chacha20Poly1305,
83    /// AES-256-GCM: 12-byte nonce, 16-byte tag.
84    #[serde(rename = "aes-256-gcm")]
85    Aes256Gcm,
86}
87
88impl std::fmt::Display for AeadAlgorithm {
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        f.write_str(match self {
91            Self::Chacha20Poly1305 => "chacha20-poly1305",
92            Self::Aes256Gcm => "aes-256-gcm",
93        })
94    }
95}
96
97/// The kind of a catalog entry.
98#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
99#[serde(rename_all = "snake_case")]
100#[non_exhaustive]
101pub enum CatalogKind {
102    /// A signing/asymmetric key.
103    Signing,
104    /// An opaque value key.
105    Value,
106    /// A symmetric AEAD key.
107    Encryption,
108}
109
110/// BYOK key material for import. Write-only; never returned to clients.
111#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
112#[serde(rename_all = "snake_case")]
113pub enum KeyMaterial {
114    /// 32-byte raw Ed25519 seed.
115    Ed25519Seed(#[serde(with = "serde_bytes")] Vec<u8>),
116    /// Generic PKCS#8 DER.
117    Pkcs8Der(#[serde(with = "serde_bytes")] Vec<u8>),
118}
119
120impl std::fmt::Debug for KeyMaterial {
121    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122        match self {
123            Self::Ed25519Seed(_) => f.write_str("Ed25519Seed(REDACTED)"),
124            Self::Pkcs8Der(_) => f.write_str("Pkcs8Der(REDACTED)"),
125        }
126    }
127}
128
129impl Drop for KeyMaterial {
130    fn drop(&mut self) {
131        match self {
132            Self::Ed25519Seed(bytes) | Self::Pkcs8Der(bytes) => bytes.zeroize(),
133        }
134    }
135}
136
137/// Self-describing AEAD ciphertext produced by `encrypt` and consumed by
138/// `decrypt`. The broker owns the nonce.
139#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
140pub struct CiphertextEnvelope {
141    /// AEAD suite.
142    pub alg: AeadAlgorithm,
143    /// Key version used.
144    pub key_version: u32,
145    /// Broker-generated nonce.
146    #[serde(with = "serde_bytes")]
147    pub nonce: Vec<u8>,
148    /// AEAD ciphertext, including tag.
149    #[serde(with = "serde_bytes")]
150    pub ciphertext: Vec<u8>,
151}
152
153/// Metadata for one visible catalog entry.
154#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
155pub struct CatalogEntry {
156    /// Dotted catalog name.
157    pub name: String,
158    /// Catalog entry class.
159    pub kind: CatalogKind,
160    /// Present for signing/encryption keys; omitted for opaque values.
161    #[serde(default, skip_serializing_if = "Option::is_none")]
162    pub key_type: Option<KeyType>,
163    /// Latest visible version.
164    pub latest_version: u32,
165}
166
167#[cfg(test)]
168mod tests {
169    use super::{AeadAlgorithm, KeyMaterial, KeyType};
170    use serde_json::json;
171
172    #[test]
173    fn key_type_and_algorithm_wire_spellings() {
174        assert_eq!(
175            serde_json::to_value(KeyType::Ed25519).unwrap(),
176            json!("ed25519")
177        );
178        assert_eq!(
179            serde_json::to_value(KeyType::Ed25519Nkey).unwrap(),
180            json!("ed25519-nkey")
181        );
182        assert_eq!(
183            serde_json::to_value(KeyType::Rsa2048).unwrap(),
184            json!("rsa-2048")
185        );
186        assert_eq!(
187            serde_json::to_value(KeyType::EcdsaP256).unwrap(),
188            json!("ecdsa-p256")
189        );
190        assert_eq!(
191            serde_json::to_value(KeyType::EcdsaP384).unwrap(),
192            json!("ecdsa-p384")
193        );
194        assert_eq!(
195            serde_json::to_value(KeyType::EcdsaP521).unwrap(),
196            json!("ecdsa-p521")
197        );
198        assert_eq!(
199            serde_json::to_value(KeyType::MlDsa65).unwrap(),
200            json!("ml-dsa-65")
201        );
202        assert_eq!(
203            serde_json::to_value(KeyType::MlKem768).unwrap(),
204            json!("ml-kem-768")
205        );
206        assert_eq!(
207            serde_json::from_value::<KeyType>(json!("ml-dsa-44")).unwrap(),
208            KeyType::MlDsa44
209        );
210        assert_eq!(
211            serde_json::from_value::<KeyType>(json!("ml-kem-1024")).unwrap(),
212            KeyType::MlKem1024
213        );
214        assert_eq!(
215            serde_json::to_value(AeadAlgorithm::Aes256Gcm).unwrap(),
216            json!("aes-256-gcm")
217        );
218        assert_eq!(
219            serde_json::to_value(AeadAlgorithm::Chacha20Poly1305).unwrap(),
220            json!("chacha20-poly1305")
221        );
222    }
223
224    #[test]
225    fn key_material_is_tagged_union() {
226        let v = serde_json::to_value(KeyMaterial::Ed25519Seed(vec![1, 2, 3])).unwrap();
227        assert_eq!(v, json!({"ed25519_seed":[1,2,3]}));
228        let back: KeyMaterial = serde_json::from_value(v).unwrap();
229        assert_eq!(back, KeyMaterial::Ed25519Seed(vec![1, 2, 3]));
230    }
231
232    #[test]
233    fn key_material_debug_redacts_private_bytes() {
234        let rendered = format!("{:?}", KeyMaterial::Ed25519Seed(vec![171; 32]));
235        assert_eq!(rendered, "Ed25519Seed(REDACTED)");
236        assert!(!rendered.contains("171"));
237    }
238}