From 89cc0b2cb4bb4f4f23388eca5e5bdd9c1f2fe0a4 Mon Sep 17 00:00:00 2001 From: Sridhar Markonda Date: Tue, 1 Sep 2026 13:30:17 -0500 Subject: [PATCH] pkcs7: Add certificate chain verification and extended APIs Extend PKCS#7 SignedData verification to support building and verifying the certificate chain of the signer. Add extended signature verification APIs that use caller-supplied trusted certificates and optionally embedded leaf certificates from the PKCS#7 structure. When embedded leaf certificates are used, their certificate chain is validated against the provided trusted CAs. Signed-off-by: Sridhar Markonda --- include/mbedtls/pkcs7.h | 76 ++++++- library/pkcs7.c | 425 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 469 insertions(+), 32 deletions(-) diff --git a/include/mbedtls/pkcs7.h b/include/mbedtls/pkcs7.h index 96ca302b20..9d89158bc2 100644 --- a/include/mbedtls/pkcs7.h +++ b/include/mbedtls/pkcs7.h @@ -63,6 +63,8 @@ #define MBEDTLS_ERR_PKCS7_ALLOC_FAILED PSA_ERROR_INSUFFICIENT_MEMORY /**< Allocation of memory failed. */ #define MBEDTLS_ERR_PKCS7_VERIFY_FAIL PSA_ERROR_INVALID_SIGNATURE /**< Verification Failed */ #define MBEDTLS_ERR_PKCS7_CERT_DATE_INVALID -0x5880 /**< The PKCS #7 date issued/expired dates are invalid */ +#define MBEDTLS_ERR_PKCS7_CERT_VERIFY_FAILED -0x5900 /**< The PKCS #7 signer certificate verification failed. */ +#define MBEDTLS_ERR_PKCS7_SIGNER_CERT_NOT_FOUND -0x5980 /**< No certificate matching the PKCS#7 signer information was found. */ /* \} name */ /** @@ -208,7 +210,7 @@ int mbedtls_pkcs7_parse_der(mbedtls_pkcs7 *pkcs7, const unsigned char *buf, * * \return 0 if the signature verifies, or a negative error code on failure. */ -int mbedtls_pkcs7_signed_data_verify(mbedtls_pkcs7 *pkcs7, +int mbedtls_pkcs7_signed_data_verify(const mbedtls_pkcs7 *pkcs7, const mbedtls_x509_crt *cert, const unsigned char *data, size_t datalen); @@ -240,10 +242,80 @@ int mbedtls_pkcs7_signed_data_verify(mbedtls_pkcs7 *pkcs7, * * \return 0 if the signature verifies, or a negative error code on failure. */ -int mbedtls_pkcs7_signed_hash_verify(mbedtls_pkcs7 *pkcs7, +int mbedtls_pkcs7_signed_hash_verify(const mbedtls_pkcs7 *pkcs7, const mbedtls_x509_crt *cert, const unsigned char *hash, size_t hashlen); +/** + * \brief Verify a PKCS #7 SignedData signature over plain data. + * + * For each signer in the PKCS#7 structure, this function verifies + * the signature over the supplied data using the digest algorithm + * specified by the signer and a matching leaf certificate. + * Verification succeeds if at least one signer signature is valid. + * + * The verification uses certificates provided by the caller and, + * optionally, certificates embedded within the PKCS#7 SignedData + * structure. When certificates from the PKCS#7 structure are used, + * the signer certificate chain is validated against the supplied + * trusted certificates. + * + * \param pkcs7 PKCS#7 SignedData structure containing signatures. + * \param trust_certs Certificate list containing trusted leaf and CA certificates. + * CA certificates are used for certificate chain verification and + * leaf certificates are used for signature verification. + * \param data Plain data on which signature has to be verified. + * \param datalen Length of the \p data. + * \param use_pkcs7_leaf Flag indicating whether to use the leaf certificate embedded + * in the PKCS#7 structure: set to \c 1 to enforce use of the PKCS#7 + * leaf certificate, or \c 0 to use only caller-supplied certificates. + * + * \note This function internally calculates the hash on the supplied + * plain data for signature verification. + * + * \return 0 on success, or a negative error code on failure. + */ +int mbedtls_pkcs7_signed_data_verify_ext(const mbedtls_pkcs7 *pkcs7, + const mbedtls_x509_crt *trust_certs, + const unsigned char *data, + const size_t datalen, + const int use_pkcs7_leaf); + +/** + * \brief Verify PKCS #7 SignedData signature over a supplied hash. + * + * For each signer in the PKCS#7 structure, this function verifies + * the signature over the supplied message hash using the digest + * algorithm specified by the signer and a matching leaf certificate. + * Verification succeeds if at least one signer signature is valid. + * + * The verification uses certificates provided by the caller and, + * optionally, certificates embedded within the PKCS#7 SignedData + * structure. When certificates from the PKCS#7 structure are used, + * the signer certificate chain is validated against the supplied + * trusted certificates. + * + * \param pkcs7 PKCS #7 SignedData structure containing signatures. + * \param trust_certs Certificate list containing trusted leaf and CA certificates. + * CA certificates are used for certificate chain verification and + * leaf certificates are used for signature verification. + * \param hash Hash of the plain data on which signature has to be verified. + * \param hashlen Length of the \p hash. + * \param use_pkcs7_leaf Flag indicating whether to use the leaf certificate embedded in + * the PKCS#7 structure: set to \c 1 to enforce use of the PKCS#7 + * leaf certificate, or \c 0 to use only caller-supplied certificates. + * + * \note This function differs from mbedtls_pkcs7_signed_data_verify_ext() + * in that the hash of the input data is provided directly by caller. + * + * \return 0 on success, or a negative error code on failure. + */ +int mbedtls_pkcs7_signed_hash_verify_ext(const mbedtls_pkcs7 *pkcs7, + const mbedtls_x509_crt *trust_certs, + const unsigned char *hash, + const size_t hashlen, + const int use_pkcs7_leaf); + /** * \brief Unallocate all PKCS #7 data and zeroize the memory. * It doesn't free \p pkcs7 itself. This should be done by the caller. diff --git a/library/pkcs7.c b/library/pkcs7.c index bbd71842ca..7f58569e4d 100644 --- a/library/pkcs7.c +++ b/library/pkcs7.c @@ -697,20 +697,258 @@ out: return ret; } -static int mbedtls_pkcs7_data_or_hash_verify(mbedtls_pkcs7 *pkcs7, - const mbedtls_x509_crt *cert, - const unsigned char *data, - size_t datalen, - const int is_data_hash) +/** + * \brief Decide whether a certificate should be classified as a CA. + * Policy: + * - If BasicConstraints present and CA=TRUE => CA + * - If KeyUsage is present, require keyCertSign for CA + * - If BasicConstraints absent => NOT a CA (classify as leaf) + * - If BasicConstraints is present and CA=FALSE => not a CA (leaf) + * + * \param crt Certificate to classify. + * + * \return 1 if the certificate is classified as CA, 0 otherwise. + */ +static int pkcs7_is_ca_cert(const mbedtls_x509_crt *crt) { int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; - unsigned char *hash; - mbedtls_pk_context pk_cxt = cert->pk; - const mbedtls_md_info_t *md_info; - mbedtls_md_type_t md_alg; - mbedtls_pkcs7_signer_info *signer; + /* BasicConstraints: is CA? */ + ret = mbedtls_x509_crt_get_ca_istrue(crt); + if (ret == 1) { + /* CA=TRUE; enforce keyCertSign only if KU exists */ + ret = mbedtls_x509_crt_check_key_usage(crt, MBEDTLS_X509_KU_KEY_CERT_SIGN); + return (ret == 0) ? 1 : 0; + } else { + /* ret == 0: BC present, CA=FALSE. ret < 0: BasicConstraints absent) */ + return 0; + } +} - if (pkcs7->signed_data.no_of_signers == 0) { +/** + * \brief Check if the given CA certificate is a possible issuer of + * the provided certificate. + * \details This function attempts to match the issuer of \p cert with + * the subject of \p ca using the following order: + * 1. If both AKI keyIdentifier and SKI are present, compare them. + * 2. If AKI authorityCertIssuer and authorityCertSerialNumber are + * present, compare against the CA's subject DN and serial. + * 3. Fallback to comparing issuer and subject Distinguished Name (DN). + * + * \param cert The certificate whose issuer is to be found. + * \param ca Candidate CA certificate. + * + * \return 0 if \p ca is a potential issuer of \p cert, -1 otherwise. + */ +static int pkcs7_is_issuer_match(const mbedtls_x509_crt *cert, + const mbedtls_x509_crt *ca) +{ + if (cert->authority_key_id.keyIdentifier.len != 0 && + ca->subject_key_id.len != 0) { + + if (cert->authority_key_id.keyIdentifier.len == ca->subject_key_id.len && + memcmp(cert->authority_key_id.keyIdentifier.p, ca->subject_key_id.p, + ca->subject_key_id.len) == 0) { + return 0; + } + + return -1; + } + + if (cert->authority_key_id.authorityCertIssuer.buf.len != 0 && + cert->authority_key_id.authorityCertSerialNumber.len != 0) { + + if (cert->authority_key_id.authorityCertIssuer.buf.len == ca->subject_raw.len && + memcmp(cert->authority_key_id.authorityCertIssuer.buf.p, + ca->subject_raw.p, ca->subject_raw.len) == 0 && + cert->authority_key_id.authorityCertSerialNumber.len == ca->serial.len && + memcmp(cert->authority_key_id.authorityCertSerialNumber.p, + ca->serial.p, ca->serial.len) == 0) { + return 0; + } + + return -1; + } + + if (cert->issuer_raw.len == ca->subject_raw.len && + memcmp(cert->issuer_raw.p, ca->subject_raw.p, + cert->issuer_raw.len) == 0) { + return 0; + } + + return -1; +} + +/** + * \brief Check if a certificate already exist in the chain + * by comparing TBSCertificate content. + * + * \param chain The certificate chain to search + * \param cert The certificate to look for + * + * \return 1 if certificate exists in chain + * 0 if certificate does not exist + */ +static int pkcs7_is_duplicate_cert(const mbedtls_x509_crt *chain, + const mbedtls_x509_crt *cert) +{ + const mbedtls_x509_crt *cur; + + for (cur = chain; cur != NULL; cur = cur->next) { + if (cur->tbs.len != cert->tbs.len) { + continue; + } + + if (memcmp(cur->tbs.p, cert->tbs.p, cert->tbs.len) == 0) { + return 1; + } + } + + return 0; +} + +/** + * \brief Build CA certificate chain from embedded PKCS#7 certs. + * + * \param pkcs7_certs Embedded certificate list (mbedtls_pkcs7_cert). + * \param leaf_cert Leaf certificate to build chain for. + * \param ca_chain On success, pointer to CA chain (caller must free). + * + * \return 0 on success, or a negative error code on failure. + */ +static int pkcs7_build_ca_chain(const mbedtls_pkcs7_cert *pkcs7_certs, + const mbedtls_x509_crt *leaf_cert, + mbedtls_x509_crt **ca_chain) +{ + const mbedtls_x509_crt *current = leaf_cert; + const mbedtls_pkcs7_cert *embedded_cert; + mbedtls_x509_crt *chain = NULL; + int ret; + size_t depth = 0; + + *ca_chain = NULL; + + while (depth < MBEDTLS_X509_MAX_INTERMEDIATE_CA && current != NULL) { + int found = 0; + + for (embedded_cert = pkcs7_certs; embedded_cert != NULL; + embedded_cert = embedded_cert->next) { + const mbedtls_x509_crt *issuer = &embedded_cert->cert; + + if (issuer->raw.p == NULL || issuer->raw.len == 0) { + continue; + } + + if (issuer == current) { + continue; + } + + if (pkcs7_is_ca_cert(issuer) != 1) { + continue; + } + + if (pkcs7_is_issuer_match(current, issuer) == 0) { + if (pkcs7_is_duplicate_cert(chain, issuer)) { + continue; + } + + if (chain == NULL) { + chain = mbedtls_calloc(1, sizeof(mbedtls_x509_crt)); + if (chain == NULL) { + ret = MBEDTLS_ERR_PKCS7_ALLOC_FAILED; + goto cleanup; + } + mbedtls_x509_crt_init(chain); + } + + ret = mbedtls_x509_crt_parse_der(chain, issuer->raw.p, issuer->raw.len); + if (ret != 0) { + goto cleanup; + } + + current = issuer; + found = 1; + depth++; + break; + } + } + + if (!found) { + break; + } + } + + *ca_chain = chain; + return 0; + +cleanup: + if (chain != NULL) { + mbedtls_x509_crt_free(chain); + mbedtls_free(chain); + } + return ret; +} + +/** + * \brief Verify certificate chain against trusted anchors. + * + * \details Builds CA chain from embedded PKCS#7 certs and verifies + * the complete chain against trust anchors. + * + * \note mbedtls_x509_crt_verify() accepts mixed list of trusted + * CAs and trusted leaf certs. + * + * \param pkcs7_certs Embedded certificate list + * \param leaf_cert Leaf certificate to verify. + * \param trust_anchors Trust anchors: root CAs and/or trusted leaf certs. + * + * \return 0 on success, or a negative error code on failure. + */ +static int pkcs7_verify_chain(const mbedtls_pkcs7_cert *pkcs7_certs, + mbedtls_x509_crt *leaf_cert, + const mbedtls_x509_crt *trust_anchors) +{ + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + mbedtls_x509_crt *ca_chain = NULL; + uint32_t flags = 0; + + ret = pkcs7_build_ca_chain(pkcs7_certs, leaf_cert, &ca_chain); + if (ret != 0) { + return ret; + } + + leaf_cert->next = ca_chain; + ret = mbedtls_x509_crt_verify(leaf_cert, (mbedtls_x509_crt *) trust_anchors, + NULL, NULL, &flags, NULL, NULL); + + leaf_cert->next = NULL; + + if (ca_chain != NULL) { + mbedtls_x509_crt_free(ca_chain); + mbedtls_free(ca_chain); + } + + if (ret != 0 || flags != 0) { + return MBEDTLS_ERR_PKCS7_CERT_VERIFY_FAILED; + } + + return 0; +} + +/** + * \brief Check if certificate matches signer info. + * + * \details Validates certificate date, matches serial/issuer, + * and checks key usage for digital signature. + * + * \param cert Certificate to check. + * \param signer Signer info to match against. + * + * \return 0 if match, or a negative error code otherwise. + */ +static int pkcs7_check_cert_with_signer(const mbedtls_x509_crt *cert, + const mbedtls_pkcs7_signer_info *signer) +{ + if (cert->raw.p == NULL || cert->raw.len == 0) { return MBEDTLS_ERR_PKCS7_INVALID_CERT; } @@ -719,6 +957,96 @@ static int mbedtls_pkcs7_data_or_hash_verify(mbedtls_pkcs7 *pkcs7, return MBEDTLS_ERR_PKCS7_CERT_DATE_INVALID; } + if (cert->serial.len != signer->serial.len || + memcmp(cert->serial.p, signer->serial.p, signer->serial.len) != 0) { + return MBEDTLS_ERR_PKCS7_SIGNER_CERT_NOT_FOUND; + } + + if (cert->issuer_raw.len != signer->issuer_raw.len || + memcmp(cert->issuer_raw.p, signer->issuer_raw.p, + signer->issuer_raw.len) != 0) { + return MBEDTLS_ERR_PKCS7_SIGNER_CERT_NOT_FOUND; + } + + if (mbedtls_x509_crt_check_key_usage(cert, + MBEDTLS_X509_KU_DIGITAL_SIGNATURE) != 0) { + return MBEDTLS_ERR_PKCS7_SIGNER_CERT_NOT_FOUND; + } + + return 0; +} + +/** + * \brief Find matching leaf certificate. + * + * \details Searches for certificate matching signer info. + * Can search in embedded PKCS#7 certs or external chain. + * + * \param pkcs7 PKCS#7 structure (for embedded certs). + * \param certs External certificate chain. + * \param signer Signer info to match against. + * \param use_pkcs7_leaf Flag: 1=search embedded, 0=search external. + * \param leaf_cert On success, pointer to matching certificate. + * + * \return 0 on success, or a negative error code on failure. + */ +static int pkcs7_get_leaf_cert(const mbedtls_pkcs7 *pkcs7, + const mbedtls_x509_crt *certs, + const mbedtls_pkcs7_signer_info *signer, + const int use_pkcs7_leaf, + mbedtls_x509_crt **leaf_cert) +{ + int ret = MBEDTLS_ERR_PKCS7_SIGNER_CERT_NOT_FOUND; + + if (use_pkcs7_leaf == 1) { + const mbedtls_pkcs7_cert *embedded_cert = pkcs7->signed_data.certs; + + while (embedded_cert != NULL) { + const mbedtls_x509_crt *cert = &embedded_cert->cert; + + ret = pkcs7_check_cert_with_signer(cert, signer); + if (ret == 0) { + *leaf_cert = (mbedtls_x509_crt *) cert; + return 0; + } + + embedded_cert = embedded_cert->next; + } + } else { + const mbedtls_x509_crt *cert = certs; + + while (cert != NULL) { + ret = pkcs7_check_cert_with_signer(cert, signer); + if (ret == 0) { + *leaf_cert = (mbedtls_x509_crt *) cert; + return 0; + } + + cert = cert->next; + } + } + + return ret; +} + +static int mbedtls_pkcs7_data_or_hash_verify(const mbedtls_pkcs7 *pkcs7, + const mbedtls_x509_crt *cert, + const unsigned char *data, + size_t datalen, + const int is_data_hash, + const int use_pkcs7_leaf) +{ + int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; + unsigned char *hash = NULL; + const mbedtls_md_info_t *md_info; + mbedtls_md_type_t md_alg; + const mbedtls_pkcs7_signer_info *signer; + mbedtls_x509_crt *leaf_cert = NULL; + + if (pkcs7->signed_data.no_of_signers == 0) { + return MBEDTLS_ERR_PKCS7_INVALID_CERT; + } + ret = mbedtls_x509_oid_get_md_alg(&pkcs7->signed_data.digest_alg_identifiers, &md_alg); if (ret != 0) { return ret; @@ -764,21 +1092,26 @@ static int mbedtls_pkcs7_data_or_hash_verify(mbedtls_pkcs7 *pkcs7, /* assume failure */ ret = MBEDTLS_ERR_PKCS7_VERIFY_FAIL; - /* - * Potential TODOs - * Currently we iterate over all signers and return success if any of them - * verify. - * - * However, we could make this better by checking against the certificate's - * identification and SignerIdentifier fields first. That would also allow - * us to distinguish between 'no signature for key' and 'signature for key - * failed to validate'. - */ for (signer = &pkcs7->signed_data.signers; signer; signer = signer->next) { - ret = mbedtls_pk_verify_ext(cert->sig_pk, &pk_cxt, md_alg, hash, - mbedtls_md_get_size(md_info), - signer->sig.p, signer->sig.len); + if (signer->sig.p == NULL || signer->sig.len == 0) { + continue; + } + ret = pkcs7_get_leaf_cert(pkcs7, cert, signer, use_pkcs7_leaf, &leaf_cert); + if (ret != 0) { + continue; + } + + if (use_pkcs7_leaf == 1) { + ret = pkcs7_verify_chain(pkcs7->signed_data.certs, leaf_cert, cert); + if (ret != 0) { + continue; + } + } + + ret = mbedtls_pk_verify_ext(leaf_cert->sig_pk, &leaf_cert->pk, md_alg, + hash, mbedtls_md_get_size(md_info), + signer->sig.p, signer->sig.len); if (ret == 0) { break; } @@ -789,26 +1122,58 @@ static int mbedtls_pkcs7_data_or_hash_verify(mbedtls_pkcs7 *pkcs7, return ret; } -int mbedtls_pkcs7_signed_data_verify(mbedtls_pkcs7 *pkcs7, +int mbedtls_pkcs7_signed_data_verify(const mbedtls_pkcs7 *pkcs7, const mbedtls_x509_crt *cert, const unsigned char *data, size_t datalen) { - if (data == NULL) { + if (data == NULL || pkcs7 == NULL || cert == NULL) { return MBEDTLS_ERR_PKCS7_BAD_INPUT_DATA; } - return mbedtls_pkcs7_data_or_hash_verify(pkcs7, cert, data, datalen, 0); + + return mbedtls_pkcs7_data_or_hash_verify(pkcs7, cert, data, datalen, 0, 0); } -int mbedtls_pkcs7_signed_hash_verify(mbedtls_pkcs7 *pkcs7, +int mbedtls_pkcs7_signed_hash_verify(const mbedtls_pkcs7 *pkcs7, const mbedtls_x509_crt *cert, const unsigned char *hash, size_t hashlen) { - if (hash == NULL) { + if (hash == NULL || pkcs7 == NULL || cert == NULL) { return MBEDTLS_ERR_PKCS7_BAD_INPUT_DATA; } - return mbedtls_pkcs7_data_or_hash_verify(pkcs7, cert, hash, hashlen, 1); + + return mbedtls_pkcs7_data_or_hash_verify(pkcs7, cert, hash, hashlen, 1, 0); +} + +int mbedtls_pkcs7_signed_data_verify_ext(const mbedtls_pkcs7 *pkcs7, + const mbedtls_x509_crt *trust_certs, + const unsigned char *data, + const size_t datalen, + const int use_pkcs7_leaf) +{ + if (data == NULL || pkcs7 == NULL || trust_certs == NULL || + (use_pkcs7_leaf != 0 && use_pkcs7_leaf != 1)) { + return MBEDTLS_ERR_PKCS7_BAD_INPUT_DATA; + } + + return mbedtls_pkcs7_data_or_hash_verify(pkcs7, trust_certs, data, datalen, + 0, use_pkcs7_leaf); +} + +int mbedtls_pkcs7_signed_hash_verify_ext(const mbedtls_pkcs7 *pkcs7, + const mbedtls_x509_crt *trust_certs, + const unsigned char *hash, + const size_t hashlen, + const int use_pkcs7_leaf) +{ + if (hash == NULL || pkcs7 == NULL || trust_certs == NULL || + (use_pkcs7_leaf != 0 && use_pkcs7_leaf != 1)) { + return MBEDTLS_ERR_PKCS7_BAD_INPUT_DATA; + } + + return mbedtls_pkcs7_data_or_hash_verify(pkcs7, trust_certs, hash, hashlen, + 1, use_pkcs7_leaf); } /*