quantum_sign/tsp/
mod.rs

1//! RFC 3161 timestamping (client/validator).
2#![forbid(unsafe_code)]
3#![deny(missing_docs)]
4
5use core::fmt;
6use der::{Decode, Encode};
7use der::referenced::OwnedToRef;
8use sha2::Digest as _;
9
10/// Errors returned by timestamping operations.
11#[derive(Debug)]
12pub enum Error {
13    /// HTTP failure or non-success status code.
14    Http(String),
15    /// ASN.1/DER or CMS parsing error.
16    Parse(String),
17    /// Verification error (status, imprint, nonce, or chain).
18    Verify(String),
19    /// Unsupported or unknown digest algorithm.
20    Digest(String),
21}
22
23impl fmt::Display for Error {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        match self {
26            Error::Http(e) => write!(f, "http: {e}"),
27            Error::Parse(e) => write!(f, "parse: {e}"),
28            Error::Verify(e) => write!(f, "verify: {e}"),
29            Error::Digest(e) => write!(f, "digest: {e}"),
30        }
31    }
32}
33
34impl std::error::Error for Error {}
35
36/// Timestamp request parameters.
37pub struct TsaRequest {
38    /// Hash algorithm name ("sha512" | "shake256-64").
39    pub imprint_alg: &'static str,
40    /// Digest of the message to be timestamped (e.g., qsig.digest)
41    pub imprint: Vec<u8>,
42    /// Optional TSA policy OID string.
43    pub policy_oid: Option<String>,
44    /// 128-bit nonce suggested by the client.
45    pub nonce: [u8; 16],
46    /// Whether the TSA should include the certificate chain.
47    pub cert_req: bool,
48}
49
50/// Minimal verified metadata extracted from a TSA response.
51#[derive(Clone, Debug)]
52pub struct TsaResponse {
53    /// Raw DER `TimeStampResp`.
54    pub der: Vec<u8>,
55    /// `genTime` as UNIX seconds.
56    pub gen_time_unix: i64,
57    /// Token serial number as lowercase hex.
58    pub serial_hex: String,
59    /// TSA signer key identifier derived from SPKI DER.
60    pub tsa_kid: String,
61}
62
63/// Construct a DER-encoded `TimeStampReq` and POST to the TSA.
64pub fn request_timestamp(url: &str, req: &TsaRequest) -> Result<TsaResponse, Error> {
65    let der_req = build_timestamp_req_der(req)?;
66    let mut res = ureq::post(url)
67        .content_type("application/timestamp-query")
68        .send(der_req.as_slice())
69        .map_err(|e| Error::Http(e.to_string()))?;
70    let der = res
71        .body_mut()
72        .read_to_vec()
73        .map_err(|e| Error::Http(e.to_string()))?;
74    parse_basic_response(&der)
75}
76
77/// Verify a DER `TimeStampToken` inside a `TimeStampResp` against expectations.
78pub fn verify_token(
79    der: &[u8],
80    expected_alg: &str,
81    expected_imprint: &[u8],
82    expected_nonce: Option<&[u8]>,
83    trust_anchors: &[Vec<u8>],
84) -> Result<TsaResponse, Error> {
85    // Parse outer response
86    let tsr = x509_tsp::TimeStampResp::from_der(der).map_err(|e| Error::Parse(e.to_string()))?;
87    let status_val = tsr.status.status as u8;
88    if status_val > 1 {
89        return Err(Error::Verify(format!("status {} not granted", status_val)));
90    }
91    let tst = tsr
92        .time_stamp_token
93        .ok_or_else(|| Error::Parse("missing token".into()))?;
94    let tst_der = tst.to_der().map_err(|e| Error::Parse(e.to_string()))?;
95
96    // Parse CMS ContentInfo -> SignedData, get eContent (TSTInfo) and signer cert SPKI
97    let sd = cms::content_info::ContentInfo::from_der(&tst_der)
98        .map_err(|e| Error::Parse(format!("cms: {e}")))?;
99    let (tst_info_der, signer_spki, serial_hex, signer_cert, chain) =
100        extract_tstinfo_and_signer_spki(&sd).map_err(|e| Error::Parse(e))?;
101
102    let tsti = x509_tsp::TstInfo::from_der(&tst_info_der).map_err(|e| Error::Parse(e.to_string()))?;
103    verify_message_imprint(&tsti, expected_alg, expected_imprint)?;
104    if let (Some(nonce), Some(exp)) = (tsti.nonce.as_ref(), expected_nonce) {
105        if nonce.as_bytes() != exp {
106            return Err(Error::Verify("nonce mismatch".into()));
107        }
108    }
109
110    // Verify EKU + bind to provided anchors by SPKI KID, then verify CMS signature
111    verify_eku_and_trust(&signer_cert, &chain, trust_anchors)?;
112    let signer_der = signer_cert.to_der().map_err(|e| Error::Parse(format!("signer der: {e}")))?;
113    verify_cms_signed_attrs(&sd, &signer_der, &signer_spki)?;
114    let tsa_kid = crate::crypto::kid_from_spki_der(&signer_spki);
115    let gen_time_unix = tsti.gen_time.to_unix_duration().as_secs() as i64;
116    Ok(TsaResponse {
117        der: der.to_vec(),
118        gen_time_unix,
119        serial_hex,
120        tsa_kid,
121    })
122}
123
124/* ------------------- internals ------------------- */
125
126fn build_timestamp_req_der(req: &TsaRequest) -> Result<Vec<u8>, Error> {
127    // Use the x509-tsp structures to encode a minimal TimeStampReq.
128    use cms::cert::x509::spki::AlgorithmIdentifier;
129    use der::asn1::{Int, OctetString};
130    use der::oid::ObjectIdentifier;
131    use x509_tsp::{MessageImprint, TimeStampReq, TspVersion};
132
133    let alg_oid: ObjectIdentifier = match req.imprint_alg {
134        "sha512" => const_oid::db::rfc5912::ID_SHA_512,
135        // SHAKE256 OID (2.16.840.1.101.3.4.2.12); truncation length is not part of OID
136        "shake256-64" => ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.12"),
137        other => return Err(Error::Digest(other.into())),
138    };
139
140    let alg = AlgorithmIdentifier { oid: alg_oid, parameters: None };
141    let hashed_message = OctetString::new(req.imprint.clone())
142        .map_err(|e| Error::Parse(e.to_string()))?;
143    let imprint = MessageImprint { hash_algorithm: alg, hashed_message };
144    let tsreq = TimeStampReq {
145        version: TspVersion::V1,
146        message_imprint: imprint,
147        req_policy: req.policy_oid.as_deref().and_then(|s| s.parse().ok()),
148        nonce: Some(Int::new(&req.nonce).map_err(|e| Error::Parse(e.to_string()))?),
149        cert_req: req.cert_req,
150        extensions: None,
151    };
152    tsreq
153        .to_der()
154        .map_err(|e| Error::Parse(format!("encode req: {e}")))
155}
156
157fn parse_basic_response(der: &[u8]) -> Result<TsaResponse, Error> {
158    let tsr = x509_tsp::TimeStampResp::from_der(der).map_err(|e| Error::Parse(e.to_string()))?;
159    let status_val = tsr.status.status as u8;
160    if status_val > 1 {
161        return Err(Error::Verify(format!("status {} not granted", status_val)));
162    }
163    let tst = tsr
164        .time_stamp_token
165        .ok_or_else(|| Error::Parse("missing token".into()))?;
166    let tst_der = tst.to_der().map_err(|e| Error::Parse(e.to_string()))?;
167    let sd = cms::content_info::ContentInfo::from_der(&tst_der)
168        .map_err(|e| Error::Parse(format!("cms: {e}")))?;
169    let (tst_info_der, signer_spki, serial_hex, _signer_cert, _chain) =
170        extract_tstinfo_and_signer_spki(&sd).map_err(|e| Error::Parse(e))?;
171    let tsti = x509_tsp::TstInfo::from_der(&tst_info_der).map_err(|e| Error::Parse(e.to_string()))?;
172    let gen_time_unix = tsti.gen_time.to_unix_duration().as_secs() as i64;
173    let tsa_kid = crate::crypto::kid_from_spki_der(&signer_spki);
174    Ok(TsaResponse {
175        der: der.to_vec(),
176        gen_time_unix,
177        serial_hex,
178        tsa_kid,
179    })
180}
181
182fn extract_tstinfo_and_signer_spki(
183    ci: &cms::content_info::ContentInfo,
184) -> Result<(Vec<u8>, Vec<u8>, String, x509_cert::Certificate, Vec<x509_cert::Certificate>), String> {
185    use cms::cert::CertificateChoices;
186    use cms::signed_data::{EncapsulatedContentInfo, SignedData};
187
188    // Parse SignedData from ContentInfo content
189    let signed: SignedData = SignedData::from_der(&ci.content.to_der().map_err(|e| e.to_string())?)
190        .map_err(|e| format!("signed_data: {e}"))?;
191    let EncapsulatedContentInfo { econtent, .. } = signed.encap_content_info;
192    let tsti_der_any = econtent.ok_or_else(|| "missing econtent".to_string())?;
193
194    // Derive SPKI DER from the first embedded certificate (best-effort)
195    let mut serial_hex = String::new();
196    let mut all = Vec::<x509_cert::Certificate>::new();
197    if let Some(certs) = &signed.certificates {
198        for i in 0..certs.0.len() {
199            let ch = certs.0.get(i).ok_or_else(|| "bad certset".to_string())?;
200            if let CertificateChoices::Certificate(cert) = ch {
201                serial_hex = hex::encode(cert.tbs_certificate.serial_number.as_bytes());
202                all.push(cert.clone());
203            }
204        }
205    }
206    if all.is_empty() {
207        return Err("no certificates present".into());
208    }
209    let signer_cert = all[0].clone();
210    let signer_spki = signer_cert
211        .tbs_certificate
212        .subject_public_key_info
213        .to_der()
214        .map_err(|e| e.to_string())?;
215    Ok((tsti_der_any.value().to_vec(), signer_spki, serial_hex, signer_cert, all))
216}
217
218fn verify_message_imprint(
219    tsti: &x509_tsp::TstInfo,
220    expected_alg: &str,
221    expected_imprint: &[u8],
222) -> Result<(), Error> {
223    let oid = tsti.message_imprint.hash_algorithm.oid;
224    let ok = match expected_alg {
225        "sha512" => oid == const_oid::db::rfc5912::ID_SHA_512,
226        "shake256-64" => oid == const_oid::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.12"),
227        other => return Err(Error::Digest(other.into())),
228    };
229    if !ok {
230        return Err(Error::Verify("hash alg mismatch".into()));
231    }
232    let got = tsti.message_imprint.hashed_message.as_bytes();
233    if got != expected_imprint {
234        return Err(Error::Verify("messageImprint mismatch".into()));
235    }
236    Ok(())
237}
238
239// (Removed unused verify_chain_and_eku to minimize dead code.)
240
241fn verify_cms_signed_attrs(
242    ci: &cms::content_info::ContentInfo,
243    _signer_cert_der: &[u8],
244    spki_der: &[u8],
245) -> Result<(), Error> {
246    // Parse SignedData from ContentInfo
247    let signed_data: cms::signed_data::SignedData = cms::signed_data::SignedData::from_der(
248        &ci.content
249            .to_der()
250            .map_err(|e| Error::Parse(format!("content der: {e}")))?,
251    )
252    .map_err(|e| Error::Parse(format!("signed_data: {e}")))?;
253    let si = signed_data
254        .signer_infos
255        .0
256        .get(0)
257        .ok_or_else(|| Error::Parse("no SignerInfo".into()))?;
258    let attrs = si
259        .signed_attrs
260        .as_ref()
261        .ok_or_else(|| Error::Parse("missing signedAttrs".into()))?;
262    let attrs_der = attrs
263        .to_der()
264        .map_err(|e| Error::Parse(format!("attrs der: {e}")))?;
265    let sig = si.signature.as_bytes();
266
267    // Map algorithms
268    let sig_oid = si.signature_algorithm.oid;
269    let dig_oid = si.digest_alg.oid;
270    use pkcs8::spki::SubjectPublicKeyInfoRef;
271
272
273    use ecdsa::signature::Verifier as _;
274    let spki = SubjectPublicKeyInfoRef::try_from(spki_der).map_err(|e| Error::Parse(e.to_string()))?;
275    let spki_alg = spki.algorithm.oid;
276    let spki_key_bytes = spki.subject_public_key.raw_bytes();
277
278    match (spki_alg, sig_oid, dig_oid) {
279        // ECDSA P-256 / SHA-256
280        (o, s, d)
281            if o == const_oid::db::rfc5912::ID_EC_PUBLIC_KEY
282                && s == const_oid::db::rfc5912::ECDSA_WITH_SHA_256
283                && d == const_oid::db::rfc5912::ID_SHA_256 =>
284        {
285            let vk = p256::ecdsa::VerifyingKey::from_sec1_bytes(spki_key_bytes)
286                .map_err(|e| Error::Verify(format!("p256 sec1: {e}")))?;
287            let sig = p256::ecdsa::Signature::from_der(sig).map_err(|e| Error::Verify(format!("p256 sig: {e}")))?;
288            vk.verify(&attrs_der, &sig).map_err(|_| Error::Verify("ecdsa p256 sha256".into()))
289        }
290        // ECDSA P-384 / SHA-384
291        (o, s, d)
292            if o == const_oid::db::rfc5912::ID_EC_PUBLIC_KEY
293                && s == const_oid::db::rfc5912::ECDSA_WITH_SHA_384
294                && d == const_oid::db::rfc5912::ID_SHA_384 =>
295        {
296            let vk = p384::ecdsa::VerifyingKey::from_sec1_bytes(spki_key_bytes)
297                .map_err(|e| Error::Verify(format!("p384 sec1: {e}")))?;
298            let sig = p384::ecdsa::Signature::from_der(sig).map_err(|e| Error::Verify(format!("p384 sig: {e}")))?;
299            vk.verify(&attrs_der, &sig).map_err(|_| Error::Verify("ecdsa p384 sha384".into()))
300        }
301        // RSA PKCS#1 v1.5 / SHA-512
302        // RSA-PSS with indicated digest
303        (o, s, d)
304            if o == const_oid::db::rfc5912::RSA_ENCRYPTION
305                && s == const_oid::db::rfc5912::ID_RSASSA_PSS =>
306        {
307            use rsa::pss::{Signature as RsaPssSignature, VerifyingKey};
308            use pkcs8::DecodePublicKey;
309            let pk = rsa::RsaPublicKey::from_public_key_der(spki_der)
310                .map_err(|e| Error::Verify(format!("rsa spki: {e}")))?;
311            let rsig = RsaPssSignature::try_from(sig).map_err(|_| Error::Verify("rsa-pss sig parse".into()))?;
312            let ok = if d == const_oid::db::rfc5912::ID_SHA_512 {
313                VerifyingKey::<sha2::Sha512>::new_with_salt_len(pk.clone(), sha2::Sha512::output_size())
314                    .verify(&attrs_der, &rsig)
315                    .is_ok()
316            } else if d == const_oid::db::rfc5912::ID_SHA_384 {
317                VerifyingKey::<sha2::Sha384>::new_with_salt_len(pk.clone(), sha2::Sha384::output_size())
318                    .verify(&attrs_der, &rsig)
319                    .is_ok()
320            } else if d == const_oid::db::rfc5912::ID_SHA_256 {
321                VerifyingKey::<sha2::Sha256>::new_with_salt_len(pk.clone(), sha2::Sha256::output_size())
322                    .verify(&attrs_der, &rsig)
323                    .is_ok()
324            } else {
325                // Fallback to common set
326                VerifyingKey::<sha2::Sha512>::new_with_salt_len(pk.clone(), sha2::Sha512::output_size())
327                    .verify(&attrs_der, &rsig)
328                    .is_ok()
329                    || VerifyingKey::<sha2::Sha384>::new_with_salt_len(pk.clone(), sha2::Sha384::output_size())
330                        .verify(&attrs_der, &rsig)
331                        .is_ok()
332                    || VerifyingKey::<sha2::Sha256>::new_with_salt_len(pk.clone(), sha2::Sha256::output_size())
333                        .verify(&attrs_der, &rsig)
334                        .is_ok()
335            };
336            if ok { Ok(()) } else { Err(Error::Verify("rsa-pss verify failed".into())) }
337        }
338        (o, s, d) if o == const_oid::db::rfc5912::RSA_ENCRYPTION && s == const_oid::db::rfc5912::RSA_ENCRYPTION => {
339            // Some TSAs publish RSA signatures with digest OIDs that can vary by profile; try indicated digest first,
340            // then fall back to the common set {SHA-512, SHA-384, SHA-256} to accommodate chain differences.
341            use rsa::pkcs1v15::{Signature as RsaSignature, VerifyingKey};
342            use pkcs8::DecodePublicKey;
343            let pk = rsa::RsaPublicKey::from_public_key_der(spki_der)
344                .map_err(|e| Error::Verify(format!("rsa spki: {e}")))?;
345            let rsig = RsaSignature::try_from(sig).map_err(|_| Error::Verify("rsa sig parse".into()))?;
346
347            // inner helper to try a digest
348            fn try_rsa<D>(pk: &rsa::RsaPublicKey, msg: &[u8], sig: &RsaSignature) -> bool
349            where
350                D: sha2::digest::Digest + const_oid::AssociatedOid,
351            {
352                let vk = VerifyingKey::<D>::new_unprefixed(pk.clone());
353                vk.verify(msg, sig).is_ok()
354            }
355
356            let ok = if d == const_oid::db::rfc5912::ID_SHA_512 {
357                try_rsa::<sha2::Sha512>(&pk, &attrs_der, &rsig)
358            } else if d == const_oid::db::rfc5912::ID_SHA_384 {
359                try_rsa::<sha2::Sha384>(&pk, &attrs_der, &rsig)
360            } else if d == const_oid::db::rfc5912::ID_SHA_256 {
361                try_rsa::<sha2::Sha256>(&pk, &attrs_der, &rsig)
362            } else {
363                // Unknown digest OID: attempt the common set in order
364                try_rsa::<sha2::Sha512>(&pk, &attrs_der, &rsig)
365                    || try_rsa::<sha2::Sha384>(&pk, &attrs_der, &rsig)
366                    || try_rsa::<sha2::Sha256>(&pk, &attrs_der, &rsig)
367            };
368            if ok { Ok(()) } else { Err(Error::Verify("rsa pkcs1 verify failed".into())) }
369        }
370        // RSA PKCS#1 v1.5 / SHA-384
371        // These RSA cases are covered by the generic RSA handler above
372        _ => Err(Error::Verify("unsupported CMS algorithm".into())),
373    }
374}
375
376fn verify_eku_and_trust(
377    signer: &x509_cert::Certificate,
378    chain: &[x509_cert::Certificate],
379    trust: &[Vec<u8>],
380) -> Result<(), Error> {
381    use const_oid::db::rfc5280::ID_KP_TIME_STAMPING;
382    use x509_cert::ext::pkix::ExtendedKeyUsage;
383    use const_oid::AssociatedOid as _;
384
385    // Check EKU for id-kp-timeStamping
386    let mut has_eku = false;
387    if let Some(exts) = signer.tbs_certificate.extensions.as_ref() {
388        for ext in exts {
389            if ext.extn_id == ExtendedKeyUsage::OID {
390                let eku = ExtendedKeyUsage::from_der(ext.extn_value.as_bytes())
391                    .map_err(|e| Error::Parse(format!("eku: {e}")))?;
392                if eku.0.iter().any(|oid| *oid == ID_KP_TIME_STAMPING) {
393                    has_eku = true;
394                    break;
395                }
396            }
397        }
398    }
399    // Some TSA chains put EKU on the issuing CA. Accept if any embedded chain cert has the EKU.
400    if !has_eku {
401        for c in chain {
402            if let Some(exts) = c.tbs_certificate.extensions.as_ref() {
403                for ext in exts {
404                    if ext.extn_id == ExtendedKeyUsage::OID {
405                        let eku = ExtendedKeyUsage::from_der(ext.extn_value.as_bytes())
406                            .map_err(|e| Error::Parse(format!("eku: {e}")))?;
407                        if eku.0.iter().any(|oid| *oid == ID_KP_TIME_STAMPING) {
408                            has_eku = true;
409                            break;
410                        }
411                    }
412                }
413            }
414            if has_eku { break; }
415        }
416    }
417    if !has_eku {
418        return Err(Error::Verify("timeStamping EKU not found on signer or issuing CA".into()));
419    }
420
421    // Bind to trust: accept if signer SPKI KID or any cert in chain matches any trust anchor SPKI KID
422    let mut trusted_kids = std::collections::BTreeSet::new();
423    let mut trust_certs: Vec<x509_cert::Certificate> = Vec::new();
424    for blob in trust {
425        // try parse as X.509 cert, else assume SPKI DER
426        let kid = if let Ok(cert) = x509_cert::Certificate::from_der(blob) {
427            let spki = cert
428                .tbs_certificate
429                .subject_public_key_info
430                .to_der()
431                .map_err(|e| Error::Parse(format!("spki: {e}")))?;
432            crate::crypto::kid_from_spki_der(&spki)
433        } else {
434            crate::crypto::kid_from_spki_der(blob)
435        };
436        trusted_kids.insert(kid);
437        if let Ok(cert) = x509_cert::Certificate::from_der(blob) {
438            trust_certs.push(cert);
439        }
440    }
441    let signer_spki = signer
442        .tbs_certificate
443        .subject_public_key_info
444        .to_der()
445        .map_err(|e| Error::Parse(format!("spki: {e}")))?;
446    let signer_kid = crate::crypto::kid_from_spki_der(&signer_spki);
447    if trusted_kids.contains(&signer_kid) {
448        return Ok(());
449    }
450    for c in chain {
451        let spki = c
452            .tbs_certificate
453            .subject_public_key_info
454            .to_der()
455            .map_err(|e| Error::Parse(format!("spki: {e}")))?;
456        let kid = crate::crypto::kid_from_spki_der(&spki);
457        if trusted_kids.contains(&kid) {
458            return Ok(());
459        }
460    }
461    // Try verifying the TSA signer certificate signature against any trusted certificate (ECDSA only)
462    if let Some(()) = verify_signer_cert_with_trust(&signer, &trust_certs).ok() {
463        return Ok(());
464    }
465    Err(Error::Verify("TSA chain not anchored in provided trust".into()))
466}
467
468fn verify_signer_cert_with_trust(
469    signer: &x509_cert::Certificate,
470    trust_certs: &[x509_cert::Certificate],
471) -> Result<(), Error> {
472    use const_oid::db::rfc5912 as oids;
473    // Determine signature alg on subject cert
474    let sig_oid = signer.signature_algorithm.oid;
475    let tbs_der = signer
476        .tbs_certificate
477        .to_der()
478        .map_err(|e| Error::Parse(format!("tbs der: {e}")))?;
479
480    for issuer in trust_certs {
481        let spki = &issuer.tbs_certificate.subject_public_key_info;
482        // Only ECDSA issuers supported here
483        if spki.algorithm.oid != oids::ID_EC_PUBLIC_KEY {
484            continue;
485        }
486        let curve = spki
487            .algorithm
488            .owned_to_ref()
489            .parameters_oid()
490            .ok();
491        let pk_bytes = spki
492            .subject_public_key
493            .raw_bytes()
494            .to_vec();
495
496        // ECDSA with SHA-256 (P-256)
497        if sig_oid == oids::ECDSA_WITH_SHA_256 && curve == Some(oids::SECP_256_R_1) {
498            use ecdsa::signature::DigestVerifier;
499            let vk = p256::ecdsa::VerifyingKey::from_sec1_bytes(&pk_bytes)
500                .map_err(|_| Error::Verify("invalid issuer P-256 key".into()))?;
501            let sig_der = signer
502                .signature
503                .as_bytes()
504                .ok_or_else(|| Error::Verify("missing signature bytes".into()))?;
505            let sig = p256::ecdsa::Signature::from_der(sig_der)
506                .map_err(|_| Error::Verify("invalid ECDSA P-256 signature".into()))?;
507            let ok = vk
508                .verify_digest(sha2::Sha256::new().chain_update(&tbs_der), &sig)
509                .is_ok();
510            if ok {
511                return Ok(());
512            }
513        }
514        // ECDSA with SHA-384 (P-384)
515        if sig_oid == oids::ECDSA_WITH_SHA_384 && curve == Some(const_oid::db::rfc5912::SECP_384_R_1) {
516            use ecdsa::signature::DigestVerifier;
517            let vk = p384::ecdsa::VerifyingKey::from_sec1_bytes(&pk_bytes)
518                .map_err(|_| Error::Verify("invalid issuer P-384 key".into()))?;
519            let sig_der = signer
520                .signature
521                .as_bytes()
522                .ok_or_else(|| Error::Verify("missing signature bytes".into()))?;
523            let sig = p384::ecdsa::Signature::from_der(sig_der)
524                .map_err(|_| Error::Verify("invalid ECDSA P-384 signature".into()))?;
525            let ok = vk
526                .verify_digest(sha2::Sha384::new().chain_update(&tbs_der), &sig)
527                .is_ok();
528            if ok {
529                return Ok(());
530            }
531        }
532    }
533    Err(Error::Verify("could not validate signer cert against trust (ECDSA)".into()))
534}