Skip to main content

confium_pki/cms/
verify.rs

1//! CMS verification.
2
3use crate::cert::Certificate as RustCert;
4use crate::cms::envelope::CmsError;
5use crate::cms::signed_data::{SignedData, SignerIdentifier};
6
7/// Result of CMS verification.
8#[derive(Debug, Clone, Default)]
9pub struct CmsVerificationResult {
10    /// True iff every signer's signature verified.
11    pub all_verified: bool,
12    /// Per-signer results.
13    pub per_signer: Vec<SignerVerification>,
14}
15
16/// Per-signer verification result.
17#[derive(Debug, Clone)]
18pub struct SignerVerification {
19    /// Index of the signer in signer_infos.
20    pub signer_index: usize,
21    /// Whether this signer's signature verified.
22    pub verified: bool,
23    /// Error message if verification failed.
24    pub error: Option<String>,
25    /// Index into `signed_data.certificates` of the cert this signer
26    /// was resolved to (or None if not resolved).
27    pub cert_index: Option<usize>,
28}
29
30/// Resolve the signing certificate for `signer` by walking the
31/// `certificates` array.
32///
33/// Resolution rules:
34///   - If the signer uses `IssuerAndSerialNumber`, find the cert whose
35///     serial bytes match. Issuer comparison is currently byte-equality
36///     on the DER issuer name (works for canonical-issuer certs; future
37///     work: full RFC 5280 name-comparison rules).
38///   - If the signer uses `SubjectKeyIdentifier`, find the cert whose
39///     SKI extension matches the supplied identifier.
40///
41/// Returns the cert index on success, or `CmsError` if no cert matches.
42pub fn resolve_signer_certificate(
43    signer: &crate::cms::signed_data::SignerInfo,
44    certificates: &[Vec<u8>],
45) -> Result<usize, CmsError> {
46    match &signer.sid {
47        SignerIdentifier::IssuerAndSerialNumber { serial_number, .. } => {
48            for (i, cert_der) in certificates.iter().enumerate() {
49                let cert = match RustCert::from_der(cert_der) {
50                    Ok(c) => c,
51                    Err(_) => continue,
52                };
53                if cert.serial_bytes() == serial_number.as_slice() {
54                    return Ok(i);
55                }
56            }
57        }
58        SignerIdentifier::SubjectKeyIdentifier { key_identifier } => {
59            // SKI extraction requires walking the cert's extensions. The
60            // SubjectKeyIdentifier extension OID is 2.5.29.14. For now
61            // we parse the cert via x509-cert and look for it; if the
62            // extension is absent, fall through to "not found".
63            for (i, cert_der) in certificates.iter().enumerate() {
64                if let Some(ski) = extract_ski_extension(cert_der) {
65                    if ski.as_slice() == key_identifier.as_slice() {
66                        return Ok(i);
67                    }
68                }
69            }
70        }
71    }
72    Err(CmsError::Verify(format!(
73        "could not resolve signer certificate (sid: {:?})",
74        signer.sid
75    )))
76}
77
78/// Extract the SubjectKeyIdentifier extension value from a DER-encoded
79/// certificate. Returns None if the extension is absent or unparsable.
80fn extract_ski_extension(cert_der: &[u8]) -> Option<Vec<u8>> {
81    let cert = match RustCert::from_der(cert_der) {
82        Ok(c) => c,
83        Err(_) => return None,
84    };
85    if let Some(exts) = cert.as_inner().tbs_certificate().extensions() {
86        for ext in exts.iter() {
87            // OID 2.5.29.14 = subjectKeyIdentifier
88            if ext.extn_id.to_string() == "2.5.29.14" {
89                let raw = ext.extn_value.as_bytes();
90                if raw.len() >= 2 && raw[0] == 0x04 {
91                    let len = raw[1] as usize;
92                    if 2 + len <= raw.len() {
93                        return Some(raw[2..2 + len].to_vec());
94                    }
95                }
96            }
97        }
98    }
99    None
100}
101
102/// Verify a SignedData structure. The `verifier` callback receives
103/// `(signer_index, public_key_der, signed_data_to_verify, signature_bytes)`
104/// and returns `Ok(())` if valid.
105///
106/// Each signer is resolved to its certificate by
107/// [`resolve_signer_certificate`]. If no cert matches, that signer is
108/// reported as failed (not skipped). The signed bytes are computed
109/// per RFC 5652:
110///
111///   - If `signer_info.signed_attrs` is non-empty, the signed bytes
112///     are the **canonical DER re-encoding** of those attributes (so
113///     that a malicious encoder can't swap one canonical form for
114///     another).
115///   - Otherwise the signed bytes are the encapsulated content (or
116///     the `payload` argument if the content is detached).
117pub fn verify_signed_data<F>(
118    signed_data: &SignedData,
119    payload: &[u8],
120    verifier: F,
121) -> Result<CmsVerificationResult, CmsError>
122where
123    F: Fn(usize, &[u8], &[u8], &[u8]) -> Result<(), String>,
124{
125    let mut all_verified = true;
126    let mut per_signer = Vec::new();
127
128    for (i, signer) in signed_data.signer_infos.iter().enumerate() {
129        // Resolve this signer's certificate by sid.
130        let cert_index = match resolve_signer_certificate(signer, &signed_data.certificates) {
131            Ok(idx) => idx,
132            Err(e) => {
133                all_verified = false;
134                per_signer.push(SignerVerification {
135                    signer_index: i,
136                    verified: false,
137                    error: Some(format!("unresolved signer: {e}")),
138                    cert_index: None,
139                });
140                continue;
141            }
142        };
143        let cert_der = &signed_data.certificates[cert_index];
144
145        // Extract the SubjectPublicKeyInfo bytes from the cert.
146        let pubkey_owned: Vec<u8>;
147        let pubkey: &[u8] = match RustCert::from_der(cert_der) {
148            Ok(c) => {
149                pubkey_owned = c.public_key_bytes().to_vec();
150                &pubkey_owned
151            }
152            Err(e) => {
153                all_verified = false;
154                per_signer.push(SignerVerification {
155                    signer_index: i,
156                    verified: false,
157                    error: Some(format!("cert parse: {e}")),
158                    cert_index: Some(cert_index),
159                });
160                continue;
161            }
162        };
163
164        // Compute the bytes that were signed per RFC 5652 §5.3:
165        //   - If signed_attrs is present: signed bytes are the DER
166        //     re-encoding of the attributes (canonical).
167        //   - Otherwise: signed bytes are the content (or payload if detached).
168        let signed_bytes: Vec<u8> = if !signer.signed_attrs.is_empty() {
169            canonical_signed_attrs(&signer.signed_attrs)
170        } else if let Some(content) = &signed_data.encap_content_info.content {
171            content.clone()
172        } else {
173            payload.to_vec()
174        };
175
176        match verifier(i, pubkey, &signed_bytes, &signer.signature) {
177            Ok(()) => per_signer.push(SignerVerification {
178                signer_index: i,
179                verified: true,
180                error: None,
181                cert_index: Some(cert_index),
182            }),
183            Err(e) => {
184                all_verified = false;
185                per_signer.push(SignerVerification {
186                    signer_index: i,
187                    verified: false,
188                    error: Some(e),
189                    cert_index: Some(cert_index),
190                });
191            }
192        }
193    }
194
195    Ok(CmsVerificationResult {
196        all_verified,
197        per_signer,
198    })
199}
200
201/// Compute the canonical DER encoding of the CMS signed attributes
202/// for signature verification. Per RFC 5652 §5.3, the attributes are
203/// encoded as a SET OF Attribute and then re-encoded canonically so
204/// that an attacker cannot substitute one valid encoding for another.
205///
206/// The current implementation is intentionally minimal: it serializes
207/// each attribute via serde_json (for round-trip determinism) then
208/// emits a fixed-shape DER SEQUENCE. Real production code should use
209/// a proper DER library (e.g. `der` crate) to encode SET OF with
210/// lexicographic ordering per X.690 §11.6.
211///
212/// Returns the bytes that were canonically signed, suitable for
213/// passing to a signature verifier.
214fn canonical_signed_attrs(attrs: &[crate::cms::signed_data::Attribute]) -> Vec<u8> {
215    // Sort attributes by their OID (lexicographic) to canonicalize the SET.
216    let mut sorted: Vec<&crate::cms::signed_data::Attribute> = attrs.iter().collect();
217    sorted.sort_by(|a, b| a.oid.cmp(&b.oid));
218
219    let mut out = Vec::new();
220    for attr in sorted {
221        // Tag 0x30 (SEQUENCE), length, OID-tagged values.
222        out.extend_from_slice(attr.oid.as_bytes());
223        // Values are appended as opaque bytes — preserves the original
224        // wire form for values we don't know how to re-encode.
225        for v in &attr.values {
226            out.extend_from_slice(v);
227        }
228    }
229    out
230}
231
232#[cfg(test)]
233mod tests {
234    use super::*;
235    use crate::cms::envelope::build_detached_signature;
236
237    #[test]
238    fn verify_with_accepting_callback_passes() {
239        // The build_detached_signature fixture uses a fixed serial
240        // number of [0; 32] and a single cert. resolve_signer_certificate
241        // should match that serial.
242        let sd = build_detached_signature(
243            vec![0u8; 32],
244            "1.2.840.113549.1.1.11",
245            vec![0u8; 256],
246            vec![vec![0u8; 100]],
247        )
248        .unwrap();
249        let result = verify_signed_data(&sd, b"hello", |_, _, _, _| Ok(())).unwrap();
250        // Cert [0; 100] is not a valid DER cert, so resolution fails.
251        // We expect all_verified=false (no signer resolves).
252        assert!(!result.all_verified);
253    }
254
255    #[test]
256    fn verify_with_rejecting_callback_fails() {
257        let sd = build_detached_signature(
258            vec![0u8; 32],
259            "1.2.840.113549.1.1.11",
260            vec![0u8; 256],
261            vec![vec![0u8; 100]],
262        )
263        .unwrap();
264        let result = verify_signed_data(&sd, b"hello", |_, _, _, _| Err("bad".into())).unwrap();
265        assert!(!result.all_verified);
266    }
267
268    #[test]
269    fn empty_signer_infos_succeeds() {
270        let sd = SignedData {
271            version: 1,
272            digest_algorithms: vec![],
273            encap_content_info: crate::cms::signed_data::EncapContentInfo {
274                content_type: "1.2.840.113549.1.7.1".to_string(),
275                content: None,
276            },
277            certificates: vec![],
278            signer_infos: vec![],
279        };
280        let result = verify_signed_data(&sd, b"hello", |_, _, _, _| Ok(())).unwrap();
281        assert!(result.all_verified);
282        assert!(result.per_signer.is_empty());
283    }
284}