Skip to main content

pqc_binary_format/
metadata.rs

1//! Metadata structures for algorithm-specific parameters.
2
3use crate::{error::CryptoError, Result};
4use base64::{engine::general_purpose::STANDARD, Engine};
5use serde::{Deserialize, Serialize};
6use serde_json::{Map, Value};
7use std::collections::HashMap;
8
9/// Algorithm-specific metadata container
10///
11/// Contains all parameters needed to decrypt and verify the encrypted data.
12#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13pub struct PqcMetadata {
14    /// Key encapsulation parameters (optional)
15    pub kem_params: Option<KemParameters>,
16    /// Signature parameters (optional)
17    pub sig_params: Option<SigParameters>,
18    /// Encryption parameters (required)
19    pub enc_params: EncParameters,
20    /// Compression parameters (optional)
21    pub compression_params: Option<CompressionParameters>,
22    /// Additional custom parameters
23    pub custom: HashMap<String, Vec<u8>>,
24}
25
26/// Key Encapsulation Mechanism (KEM) parameters
27///
28/// Used by algorithms that employ key encapsulation (ML-KEM, etc.)
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
30pub struct KemParameters {
31    /// Public key for KEM
32    pub public_key: Vec<u8>,
33    /// Encapsulated ciphertext
34    pub ciphertext: Vec<u8>,
35    /// Algorithm-specific parameters
36    pub params: HashMap<String, Vec<u8>>,
37}
38
39/// Digital signature parameters
40///
41/// Used by algorithms that include signatures (ML-DSA, Ed25519, etc.)
42#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
43pub struct SigParameters {
44    /// Public key for signature verification
45    pub public_key: Vec<u8>,
46    /// Digital signature
47    pub signature: Vec<u8>,
48    /// Algorithm-specific parameters
49    pub params: HashMap<String, Vec<u8>>,
50}
51
52/// Symmetric encryption parameters
53///
54/// Required for all algorithms as they all use symmetric encryption
55/// for the actual data encryption.
56#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
57pub struct EncParameters {
58    /// Initialization vector / nonce
59    pub iv: Vec<u8>,
60    /// Authentication tag (for AEAD ciphers like AES-GCM)
61    pub tag: Vec<u8>,
62    /// Algorithm-specific parameters
63    pub params: HashMap<String, Vec<u8>>,
64}
65
66/// Compression algorithm parameters
67///
68/// Indicates if compression was applied before encryption.
69#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
70pub struct CompressionParameters {
71    /// Compression algorithm name (e.g., "zstd", "brotli", "lz4")
72    pub algorithm: String,
73    /// Compression level (0-9)
74    pub level: u8,
75    /// Original size before compression
76    pub original_size: u64,
77    /// Algorithm-specific parameters
78    pub params: HashMap<String, Vec<u8>>,
79}
80
81impl PqcMetadata {
82    /// Create new empty metadata with default encryption parameters
83    ///
84    /// # Example
85    ///
86    /// ```
87    /// use pqc_binary_format::PqcMetadata;
88    ///
89    /// let mut metadata = PqcMetadata::new();
90    /// metadata.enc_params.iv = vec![0; 12];
91    /// metadata.enc_params.tag = vec![0; 16];
92    /// ```
93    #[must_use]
94    pub fn new() -> Self {
95        Self {
96            kem_params: None,
97            sig_params: None,
98            enc_params: EncParameters {
99                iv: Vec::new(),
100                tag: Vec::new(),
101                params: HashMap::new(),
102            },
103            compression_params: None,
104            custom: HashMap::new(),
105        }
106    }
107
108    /// Validate metadata structure
109    ///
110    /// Ensures all required fields are present and valid.
111    pub fn validate(&self) -> Result<()> {
112        // Validate encryption parameters (required)
113        if self.enc_params.iv.is_empty() {
114            return Err(CryptoError::MetadataError(
115                "Encryption IV cannot be empty".to_string(),
116            ));
117        }
118
119        // Additional validation can be added here for specific parameters
120        Ok(())
121    }
122
123    /// Add custom parameter
124    ///
125    /// # Example
126    ///
127    /// ```
128    /// use pqc_binary_format::PqcMetadata;
129    ///
130    /// let mut metadata = PqcMetadata::new();
131    /// metadata.add_custom("my_param".to_string(), vec![1, 2, 3]);
132    /// ```
133    pub fn add_custom(&mut self, key: String, value: Vec<u8>) {
134        self.custom.insert(key, value);
135    }
136
137    /// Get custom parameter
138    ///
139    /// # Example
140    ///
141    /// ```
142    /// use pqc_binary_format::PqcMetadata;
143    ///
144    /// let mut metadata = PqcMetadata::new();
145    /// metadata.add_custom("my_param".to_string(), vec![1, 2, 3]);
146    /// assert_eq!(metadata.get_custom("my_param"), Some(&[1, 2, 3][..]));
147    /// ```
148    #[must_use]
149    pub fn get_custom(&self, key: &str) -> Option<&[u8]> {
150        self.custom.get(key).map(Vec::as_slice)
151    }
152
153    /// Serialize the metadata to the canonical UTF-8 JSON object used in the
154    /// metadata section of the binary format (Section 5 of the specification).
155    ///
156    /// Byte-valued fields (IV, tag, public keys, ciphertext, signature, custom
157    /// values) are base64-encoded so the section stays valid UTF-8. Object keys
158    /// serialize in sorted order (serde_json's map is a `BTreeMap`), giving a
159    /// deterministic encoding suitable for the integrity checksum.
160    #[must_use]
161    pub fn to_json_bytes(&self) -> Vec<u8> {
162        let mut root = Map::new();
163
164        let mut enc = Map::new();
165        enc.insert(
166            "iv".into(),
167            Value::String(STANDARD.encode(&self.enc_params.iv)),
168        );
169        enc.insert(
170            "tag".into(),
171            Value::String(STANDARD.encode(&self.enc_params.tag)),
172        );
173        enc.insert("params".into(), bytes_map_to_json(&self.enc_params.params));
174        root.insert("encryption_params".into(), Value::Object(enc));
175
176        if let Some(ref kem) = self.kem_params {
177            let mut m = Map::new();
178            m.insert(
179                "public_key".into(),
180                Value::String(STANDARD.encode(&kem.public_key)),
181            );
182            m.insert(
183                "ciphertext".into(),
184                Value::String(STANDARD.encode(&kem.ciphertext)),
185            );
186            m.insert("params".into(), bytes_map_to_json(&kem.params));
187            root.insert("kem_params".into(), Value::Object(m));
188        }
189
190        if let Some(ref sig) = self.sig_params {
191            let mut m = Map::new();
192            m.insert(
193                "public_key".into(),
194                Value::String(STANDARD.encode(&sig.public_key)),
195            );
196            m.insert(
197                "signature".into(),
198                Value::String(STANDARD.encode(&sig.signature)),
199            );
200            m.insert("params".into(), bytes_map_to_json(&sig.params));
201            root.insert("signature_params".into(), Value::Object(m));
202        }
203
204        if let Some(ref comp) = self.compression_params {
205            let mut m = Map::new();
206            m.insert("algorithm".into(), Value::String(comp.algorithm.clone()));
207            m.insert("level".into(), Value::Number(comp.level.into()));
208            m.insert(
209                "original_size".into(),
210                Value::Number(comp.original_size.into()),
211            );
212            m.insert("params".into(), bytes_map_to_json(&comp.params));
213            root.insert("compression_params".into(), Value::Object(m));
214        }
215
216        if !self.custom.is_empty() {
217            root.insert("custom".into(), bytes_map_to_json(&self.custom));
218        }
219
220        // serde_json never fails serializing an in-memory Value built from
221        // owned data, so the conversion is infallible here.
222        serde_json::to_vec(&Value::Object(root)).unwrap_or_default()
223    }
224
225    /// Reconstruct metadata from the canonical JSON object produced by
226    /// [`PqcMetadata::to_json_bytes`].
227    ///
228    /// # Errors
229    ///
230    /// Returns [`CryptoError::MetadataError`] if the bytes are not valid UTF-8
231    /// JSON, the required `encryption_params` object is missing, or any
232    /// base64-encoded field cannot be decoded.
233    pub fn from_json_bytes(bytes: &[u8]) -> Result<Self> {
234        let root: Value = serde_json::from_slice(bytes)
235            .map_err(|e| CryptoError::MetadataError(format!("Invalid metadata JSON: {e}")))?;
236        let obj = root
237            .as_object()
238            .ok_or_else(|| CryptoError::MetadataError("Metadata must be a JSON object".into()))?;
239
240        let enc_obj = obj
241            .get("encryption_params")
242            .and_then(Value::as_object)
243            .ok_or_else(|| CryptoError::MetadataError("Missing encryption_params".into()))?;
244        let enc_params = EncParameters {
245            iv: decode_field(enc_obj, "iv")?,
246            tag: decode_field(enc_obj, "tag")?,
247            params: json_to_bytes_map(enc_obj.get("params"))?,
248        };
249
250        let kem_params = match obj.get("kem_params").and_then(Value::as_object) {
251            Some(m) => Some(KemParameters {
252                public_key: decode_field(m, "public_key")?,
253                ciphertext: decode_field(m, "ciphertext")?,
254                params: json_to_bytes_map(m.get("params"))?,
255            }),
256            None => None,
257        };
258
259        let sig_params = match obj.get("signature_params").and_then(Value::as_object) {
260            Some(m) => Some(SigParameters {
261                public_key: decode_field(m, "public_key")?,
262                signature: decode_field(m, "signature")?,
263                params: json_to_bytes_map(m.get("params"))?,
264            }),
265            None => None,
266        };
267
268        let compression_params = match obj.get("compression_params").and_then(Value::as_object) {
269            Some(m) => Some(CompressionParameters {
270                algorithm: m
271                    .get("algorithm")
272                    .and_then(Value::as_str)
273                    .unwrap_or_default()
274                    .to_string(),
275                level: m
276                    .get("level")
277                    .and_then(Value::as_u64)
278                    .and_then(|n| u8::try_from(n).ok())
279                    .unwrap_or(0),
280                original_size: m.get("original_size").and_then(Value::as_u64).unwrap_or(0),
281                params: json_to_bytes_map(m.get("params"))?,
282            }),
283            None => None,
284        };
285
286        Ok(Self {
287            kem_params,
288            sig_params,
289            enc_params,
290            compression_params,
291            custom: json_to_bytes_map(obj.get("custom"))?,
292        })
293    }
294}
295
296/// Encode a `HashMap<String, Vec<u8>>` as a JSON object of base64 strings.
297fn bytes_map_to_json(map: &HashMap<String, Vec<u8>>) -> Value {
298    let mut out = Map::new();
299    for (k, v) in map {
300        out.insert(k.clone(), Value::String(STANDARD.encode(v)));
301    }
302    Value::Object(out)
303}
304
305/// Decode a JSON object of base64 strings back into a `HashMap<String, Vec<u8>>`.
306fn json_to_bytes_map(value: Option<&Value>) -> Result<HashMap<String, Vec<u8>>> {
307    let mut out = HashMap::new();
308    if let Some(Value::Object(m)) = value {
309        for (k, v) in m {
310            let s = v.as_str().ok_or_else(|| {
311                CryptoError::MetadataError(format!("Param '{k}' is not a string"))
312            })?;
313            let decoded = STANDARD
314                .decode(s)
315                .map_err(|e| CryptoError::MetadataError(format!("Param '{k}' base64: {e}")))?;
316            out.insert(k.clone(), decoded);
317        }
318    }
319    Ok(out)
320}
321
322/// Decode a required base64 string field from a JSON object.
323fn decode_field(obj: &Map<String, Value>, key: &str) -> Result<Vec<u8>> {
324    let s = obj
325        .get(key)
326        .and_then(Value::as_str)
327        .ok_or_else(|| CryptoError::MetadataError(format!("Missing metadata field '{key}'")))?;
328    STANDARD
329        .decode(s)
330        .map_err(|e| CryptoError::MetadataError(format!("Field '{key}' base64: {e}")))
331}
332
333impl Default for PqcMetadata {
334    fn default() -> Self {
335        Self::new()
336    }
337}
338
339#[cfg(test)]
340mod tests {
341    use super::*;
342
343    #[test]
344    fn test_metadata_validation() {
345        let mut metadata = PqcMetadata::new();
346
347        // Should fail with empty IV
348        assert!(metadata.validate().is_err());
349
350        // Should succeed with valid IV
351        metadata.enc_params.iv = vec![1; 12];
352        assert!(metadata.validate().is_ok());
353    }
354
355    #[test]
356    fn test_custom_parameters() {
357        let mut metadata = PqcMetadata::new();
358        metadata.add_custom("test".to_string(), vec![1, 2, 3]);
359
360        assert_eq!(metadata.get_custom("test"), Some(&[1, 2, 3][..]));
361        assert_eq!(metadata.get_custom("missing"), None);
362    }
363}