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")]
100pub enum CatalogKind {
101    /// A signing/asymmetric key.
102    Signing,
103    /// An opaque value key.
104    Value,
105    /// A symmetric AEAD key.
106    Encryption,
107}
108
109/// BYOK key material for import. Write-only; never returned to clients.
110#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
111#[serde(rename_all = "snake_case")]
112pub enum KeyMaterial {
113    /// 32-byte raw Ed25519 seed.
114    Ed25519Seed(#[serde(with = "serde_bytes")] Vec<u8>),
115    /// Generic PKCS#8 DER.
116    Pkcs8Der(#[serde(with = "serde_bytes")] Vec<u8>),
117}
118
119impl std::fmt::Debug for KeyMaterial {
120    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121        match self {
122            Self::Ed25519Seed(_) => f.write_str("Ed25519Seed(REDACTED)"),
123            Self::Pkcs8Der(_) => f.write_str("Pkcs8Der(REDACTED)"),
124        }
125    }
126}
127
128impl Drop for KeyMaterial {
129    fn drop(&mut self) {
130        match self {
131            Self::Ed25519Seed(bytes) | Self::Pkcs8Der(bytes) => bytes.zeroize(),
132        }
133    }
134}
135
136/// Self-describing AEAD ciphertext produced by `encrypt` and consumed by
137/// `decrypt`. The broker owns the nonce.
138#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
139pub struct CiphertextEnvelope {
140    /// AEAD suite.
141    pub alg: AeadAlgorithm,
142    /// Key version used.
143    pub key_version: u32,
144    /// Broker-generated nonce.
145    #[serde(with = "serde_bytes")]
146    pub nonce: Vec<u8>,
147    /// AEAD ciphertext, including tag.
148    #[serde(with = "serde_bytes")]
149    pub ciphertext: Vec<u8>,
150}
151
152/// Metadata for one visible catalog entry.
153#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
154pub struct CatalogEntry {
155    /// Dotted catalog name.
156    pub name: String,
157    /// Catalog entry class.
158    pub kind: CatalogKind,
159    /// Present for signing/encryption keys; omitted for opaque values.
160    #[serde(default, skip_serializing_if = "Option::is_none")]
161    pub key_type: Option<KeyType>,
162    /// Latest visible version.
163    pub latest_version: u32,
164}
165
166#[cfg(test)]
167mod tests {
168    use super::{AeadAlgorithm, KeyMaterial, KeyType};
169    use serde_json::json;
170
171    #[test]
172    fn key_type_and_algorithm_wire_spellings() {
173        assert_eq!(
174            serde_json::to_value(KeyType::Ed25519).unwrap(),
175            json!("ed25519")
176        );
177        assert_eq!(
178            serde_json::to_value(KeyType::Ed25519Nkey).unwrap(),
179            json!("ed25519-nkey")
180        );
181        assert_eq!(
182            serde_json::to_value(KeyType::Rsa2048).unwrap(),
183            json!("rsa-2048")
184        );
185        assert_eq!(
186            serde_json::to_value(KeyType::EcdsaP256).unwrap(),
187            json!("ecdsa-p256")
188        );
189        assert_eq!(
190            serde_json::to_value(KeyType::EcdsaP384).unwrap(),
191            json!("ecdsa-p384")
192        );
193        assert_eq!(
194            serde_json::to_value(KeyType::EcdsaP521).unwrap(),
195            json!("ecdsa-p521")
196        );
197        assert_eq!(
198            serde_json::to_value(KeyType::MlDsa65).unwrap(),
199            json!("ml-dsa-65")
200        );
201        assert_eq!(
202            serde_json::to_value(KeyType::MlKem768).unwrap(),
203            json!("ml-kem-768")
204        );
205        assert_eq!(
206            serde_json::from_value::<KeyType>(json!("ml-dsa-44")).unwrap(),
207            KeyType::MlDsa44
208        );
209        assert_eq!(
210            serde_json::from_value::<KeyType>(json!("ml-kem-1024")).unwrap(),
211            KeyType::MlKem1024
212        );
213        assert_eq!(
214            serde_json::to_value(AeadAlgorithm::Aes256Gcm).unwrap(),
215            json!("aes-256-gcm")
216        );
217        assert_eq!(
218            serde_json::to_value(AeadAlgorithm::Chacha20Poly1305).unwrap(),
219            json!("chacha20-poly1305")
220        );
221    }
222
223    #[test]
224    fn key_material_is_tagged_union() {
225        let v = serde_json::to_value(KeyMaterial::Ed25519Seed(vec![1, 2, 3])).unwrap();
226        assert_eq!(v, json!({"ed25519_seed":[1,2,3]}));
227        let back: KeyMaterial = serde_json::from_value(v).unwrap();
228        assert_eq!(back, KeyMaterial::Ed25519Seed(vec![1, 2, 3]));
229    }
230
231    #[test]
232    fn key_material_debug_redacts_private_bytes() {
233        let rendered = format!("{:?}", KeyMaterial::Ed25519Seed(vec![171; 32]));
234        assert_eq!(rendered, "Ed25519Seed(REDACTED)");
235        assert!(!rendered.contains("171"));
236    }
237}