1use serde::{Deserialize, Serialize};
8use zeroize::Zeroize;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
19pub enum KeyType {
20 #[serde(rename = "ed25519")]
22 Ed25519,
23 #[serde(rename = "ed25519-nkey")]
25 Ed25519Nkey,
26 #[serde(rename = "rsa-2048")]
28 Rsa2048,
29 #[serde(rename = "ecdsa-p256")]
31 EcdsaP256,
32 #[serde(rename = "ecdsa-p384")]
34 EcdsaP384,
35 #[serde(rename = "ecdsa-p521")]
37 EcdsaP521,
38 #[serde(rename = "ml-dsa-44")]
40 MlDsa44,
41 #[serde(rename = "ml-dsa-65")]
43 MlDsa65,
44 #[serde(rename = "ml-dsa-87")]
46 MlDsa87,
47 #[serde(rename = "ml-kem-512")]
49 MlKem512,
50 #[serde(rename = "ml-kem-768")]
52 MlKem768,
53 #[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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
79pub enum AeadAlgorithm {
80 #[serde(rename = "chacha20-poly1305")]
82 Chacha20Poly1305,
83 #[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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
99#[serde(rename_all = "snake_case")]
100#[non_exhaustive]
101pub enum CatalogKind {
102 Signing,
104 Value,
106 Encryption,
108}
109
110#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
112#[serde(rename_all = "snake_case")]
113pub enum KeyMaterial {
114 Ed25519Seed(#[serde(with = "serde_bytes")] Vec<u8>),
116 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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
140pub struct CiphertextEnvelope {
141 pub alg: AeadAlgorithm,
143 pub key_version: u32,
145 #[serde(with = "serde_bytes")]
147 pub nonce: Vec<u8>,
148 #[serde(with = "serde_bytes")]
150 pub ciphertext: Vec<u8>,
151}
152
153#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
155pub struct CatalogEntry {
156 pub name: String,
158 pub kind: CatalogKind,
160 #[serde(default, skip_serializing_if = "Option::is_none")]
162 pub key_type: Option<KeyType>,
163 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}