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")]
100pub enum CatalogKind {
101 Signing,
103 Value,
105 Encryption,
107}
108
109#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
111#[serde(rename_all = "snake_case")]
112pub enum KeyMaterial {
113 Ed25519Seed(#[serde(with = "serde_bytes")] Vec<u8>),
115 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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
139pub struct CiphertextEnvelope {
140 pub alg: AeadAlgorithm,
142 pub key_version: u32,
144 #[serde(with = "serde_bytes")]
146 pub nonce: Vec<u8>,
147 #[serde(with = "serde_bytes")]
149 pub ciphertext: Vec<u8>,
150}
151
152#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
154pub struct CatalogEntry {
155 pub name: String,
157 pub kind: CatalogKind,
159 #[serde(default, skip_serializing_if = "Option::is_none")]
161 pub key_type: Option<KeyType>,
162 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}