Skip to main content

basil_proto/
types.rs

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