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