use serde::{Deserialize, Serialize};
use zeroize::Zeroize;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum KeyType {
#[serde(rename = "ed25519")]
Ed25519,
#[serde(rename = "ed25519-nkey")]
Ed25519Nkey,
#[serde(rename = "rsa-2048")]
Rsa2048,
#[serde(rename = "ecdsa-p256")]
EcdsaP256,
#[serde(rename = "ecdsa-p384")]
EcdsaP384,
#[serde(rename = "ecdsa-p521")]
EcdsaP521,
#[serde(rename = "ml-dsa-44")]
MlDsa44,
#[serde(rename = "ml-dsa-65")]
MlDsa65,
#[serde(rename = "ml-dsa-87")]
MlDsa87,
#[serde(rename = "ml-kem-512")]
MlKem512,
#[serde(rename = "ml-kem-768")]
MlKem768,
#[serde(rename = "ml-kem-1024")]
MlKem1024,
}
impl std::fmt::Display for KeyType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::Ed25519 => "ed25519",
Self::Ed25519Nkey => "ed25519-nkey",
Self::Rsa2048 => "rsa-2048",
Self::EcdsaP256 => "ecdsa-p256",
Self::EcdsaP384 => "ecdsa-p384",
Self::EcdsaP521 => "ecdsa-p521",
Self::MlDsa44 => "ml-dsa-44",
Self::MlDsa65 => "ml-dsa-65",
Self::MlDsa87 => "ml-dsa-87",
Self::MlKem512 => "ml-kem-512",
Self::MlKem768 => "ml-kem-768",
Self::MlKem1024 => "ml-kem-1024",
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AeadAlgorithm {
#[serde(rename = "chacha20-poly1305")]
Chacha20Poly1305,
#[serde(rename = "aes-256-gcm")]
Aes256Gcm,
}
impl std::fmt::Display for AeadAlgorithm {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::Chacha20Poly1305 => "chacha20-poly1305",
Self::Aes256Gcm => "aes-256-gcm",
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum CatalogKind {
Signing,
Value,
Encryption,
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum KeyMaterial {
Ed25519Seed(#[serde(with = "serde_bytes")] Vec<u8>),
Pkcs8Der(#[serde(with = "serde_bytes")] Vec<u8>),
}
impl std::fmt::Debug for KeyMaterial {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Ed25519Seed(_) => f.write_str("Ed25519Seed(REDACTED)"),
Self::Pkcs8Der(_) => f.write_str("Pkcs8Der(REDACTED)"),
}
}
}
impl Drop for KeyMaterial {
fn drop(&mut self) {
match self {
Self::Ed25519Seed(bytes) | Self::Pkcs8Der(bytes) => bytes.zeroize(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CiphertextEnvelope {
pub alg: AeadAlgorithm,
pub key_version: u32,
#[serde(with = "serde_bytes")]
pub nonce: Vec<u8>,
#[serde(with = "serde_bytes")]
pub ciphertext: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CatalogEntry {
pub name: String,
pub kind: CatalogKind,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub key_type: Option<KeyType>,
pub latest_version: u32,
}
#[cfg(test)]
mod tests {
use super::{AeadAlgorithm, KeyMaterial, KeyType};
use serde_json::json;
#[test]
fn key_type_and_algorithm_wire_spellings() {
assert_eq!(
serde_json::to_value(KeyType::Ed25519).unwrap(),
json!("ed25519")
);
assert_eq!(
serde_json::to_value(KeyType::Ed25519Nkey).unwrap(),
json!("ed25519-nkey")
);
assert_eq!(
serde_json::to_value(KeyType::Rsa2048).unwrap(),
json!("rsa-2048")
);
assert_eq!(
serde_json::to_value(KeyType::EcdsaP256).unwrap(),
json!("ecdsa-p256")
);
assert_eq!(
serde_json::to_value(KeyType::EcdsaP384).unwrap(),
json!("ecdsa-p384")
);
assert_eq!(
serde_json::to_value(KeyType::EcdsaP521).unwrap(),
json!("ecdsa-p521")
);
assert_eq!(
serde_json::to_value(KeyType::MlDsa65).unwrap(),
json!("ml-dsa-65")
);
assert_eq!(
serde_json::to_value(KeyType::MlKem768).unwrap(),
json!("ml-kem-768")
);
assert_eq!(
serde_json::from_value::<KeyType>(json!("ml-dsa-44")).unwrap(),
KeyType::MlDsa44
);
assert_eq!(
serde_json::from_value::<KeyType>(json!("ml-kem-1024")).unwrap(),
KeyType::MlKem1024
);
assert_eq!(
serde_json::to_value(AeadAlgorithm::Aes256Gcm).unwrap(),
json!("aes-256-gcm")
);
assert_eq!(
serde_json::to_value(AeadAlgorithm::Chacha20Poly1305).unwrap(),
json!("chacha20-poly1305")
);
}
#[test]
fn key_material_is_tagged_union() {
let v = serde_json::to_value(KeyMaterial::Ed25519Seed(vec![1, 2, 3])).unwrap();
assert_eq!(v, json!({"ed25519_seed":[1,2,3]}));
let back: KeyMaterial = serde_json::from_value(v).unwrap();
assert_eq!(back, KeyMaterial::Ed25519Seed(vec![1, 2, 3]));
}
#[test]
fn key_material_debug_redacts_private_bytes() {
let rendered = format!("{:?}", KeyMaterial::Ed25519Seed(vec![171; 32]));
assert_eq!(rendered, "Ed25519Seed(REDACTED)");
assert!(!rendered.contains("171"));
}
}