diff --git a/components/mbedtls/esp_crt_bundle/esp_crt_bundle.c b/components/mbedtls/esp_crt_bundle/esp_crt_bundle.c index 05eff00b9cb..0b271b73dc0 100644 --- a/components/mbedtls/esp_crt_bundle/esp_crt_bundle.c +++ b/components/mbedtls/esp_crt_bundle/esp_crt_bundle.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -316,8 +316,33 @@ int esp_crt_verify_callback(void *buf, mbedtls_x509_crt* const crt, const int de { const mbedtls_x509_crt* const child = crt; - /* It's OK for a trusted cert to have a weak signature hash alg. - as we already trust this certificate */ +#if defined(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY) + /* When cross-signed verification is enabled, the CA callback provides a + * synthetic bundle root containing only the subject name and public key. + * It has no meaningful validity window, so mbedtls may set EXPIRED/FUTURE + * on this generated cert. Clear those flags only for this synthetic bundle + * root so that cross-signed verification can continue. + * + * Real certificates must keep their time-based verification result and + * should not proceed to additional bundle signature checks once they are + * marked expired or not-yet-valid. */ + const uint32_t time_flags = *flags & + (MBEDTLS_X509_BADCERT_EXPIRED | MBEDTLS_X509_BADCERT_FUTURE); + if (time_flags && s_crt_bundle != NULL && child->raw.p == NULL && + child->valid_from.year == 0 && child->valid_to.year == 0) { + cert_t cert = esp_crt_find_cert(child->subject_raw.p, + child->subject_raw.len); + if (cert != NULL) { + *flags &= ~(MBEDTLS_X509_BADCERT_EXPIRED | + MBEDTLS_X509_BADCERT_FUTURE); + } + } +#endif /* CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY */ + + /* It's OK for a trusted bundle cert to have a weak signature hash alg, + * as we already trust this certificate. Do not ignore EXPIRED/FUTURE here: + * real certificates must fail on validity checks, and only the synthetic + * cross-signed bundle root has those flags cleared above. */ uint32_t flags_filtered = *flags & ~(MBEDTLS_X509_BADCERT_BAD_MD); if (flags_filtered != MBEDTLS_X509_BADCERT_NOT_TRUSTED) { @@ -339,6 +364,10 @@ int esp_crt_verify_callback(void *buf, mbedtls_x509_crt* const crt, const int de if (likely(ret == 0)) { ESP_LOGI(TAG, "Certificate validated"); + /* Bundle trust and signature verification succeeded. Real + * certificates with EXPIRED/FUTURE return earlier, and the + * synthetic cross-signed bundle root has those flags cleared + * above, so clear the remaining verification flags here. */ *flags = 0; return 0; } else { @@ -429,7 +458,10 @@ static esp_err_t esp_crt_bundle_init(const uint8_t* const x509_bundle, const siz } #if defined(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY) -static int esp_crt_copy_asn1(const mbedtls_asn1_named_data *src, mbedtls_asn1_named_data *dst) +/* Reference ASN1 named data by pointing into src's buffers. + * The src data must outlive dst. */ +static int esp_crt_ref_asn1(const mbedtls_asn1_named_data *src, + mbedtls_asn1_named_data *dst) { if (src == NULL || dst == NULL) { return -1; @@ -437,21 +469,11 @@ static int esp_crt_copy_asn1(const mbedtls_asn1_named_data *src, mbedtls_asn1_na dst->oid.tag = src->oid.tag; dst->oid.len = src->oid.len; - dst->oid.p = calloc(1, src->oid.len); - if (dst->oid.p == NULL) { - ESP_LOGE(TAG, "Failed to allocate memory for OID"); - return -1; - } - memcpy(dst->oid.p, src->oid.p, src->oid.len); + dst->oid.p = src->oid.p; dst->val.tag = src->val.tag; dst->val.len = src->val.len; - dst->val.p = calloc(1, src->val.len); - if (dst->val.p == NULL) { - ESP_LOGE(TAG, "Failed to allocate memory for value"); - free(dst->oid.p); - return -1; - } - memcpy(dst->val.p, src->val.p, src->val.len); + dst->val.p = src->val.p; + dst->next_merged = src->next_merged; return 0; } @@ -484,16 +506,10 @@ static int esp_crt_ca_cb_callback(void *ctx, mbedtls_x509_crt const *child, mbed const uint8_t *cert_name = esp_crt_get_name(cert); uint16_t cert_name_len = esp_crt_get_name_len(cert); + /* Point into persistent bundle data */ new_cert->subject_raw.tag = MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE; new_cert->subject_raw.len = cert_name_len; - new_cert->subject_raw.p = calloc(1, cert_name_len); - if (new_cert->subject_raw.p == NULL) { - ESP_LOGE(TAG, "Failed to allocate memory for subject"); - mbedtls_x509_crt_free(new_cert); - free(new_cert); - return MBEDTLS_ERR_X509_ALLOC_FAILED; - } - memcpy(new_cert->subject_raw.p, cert_name, cert_name_len); + new_cert->subject_raw.p = (unsigned char *)cert_name; const uint8_t *cert_key = esp_crt_get_key(cert); uint16_t cert_key_len = esp_crt_get_key_len(cert); @@ -508,31 +524,33 @@ static int esp_crt_ca_cb_callback(void *ctx, mbedtls_x509_crt const *child, mbed return ret; } - // Loop through the child->issuer and copy the values to the new certificate + /* Populate parent->subject by referencing child->issuer data */ const mbedtls_asn1_named_data *child_issuer = &child->issuer; mbedtls_asn1_named_data *parent_subject = &new_cert->subject; + if (esp_crt_ref_asn1(child_issuer, parent_subject) != 0) { + ESP_LOGE(TAG, "Failed to reference ASN.1 data"); + mbedtls_x509_crt_free(new_cert); + free(new_cert); + return MBEDTLS_ERR_X509_ALLOC_FAILED; + } + + child_issuer = child_issuer->next; while (child_issuer != NULL) { - if (esp_crt_copy_asn1(child_issuer, parent_subject) != 0) { - ESP_LOGE(TAG, "Failed to copy ASN.1 data"); + parent_subject->next = calloc(1, sizeof(mbedtls_asn1_named_data)); + if (parent_subject->next == NULL) { + ESP_LOGE(TAG, "Failed to allocate memory for subject node"); + mbedtls_x509_crt_free(new_cert); + free(new_cert); + return MBEDTLS_ERR_X509_ALLOC_FAILED; + } + parent_subject = parent_subject->next; + if (esp_crt_ref_asn1(child_issuer, parent_subject) != 0) { + ESP_LOGE(TAG, "Failed to reference ASN.1 data"); mbedtls_x509_crt_free(new_cert); free(new_cert); return MBEDTLS_ERR_X509_ALLOC_FAILED; } child_issuer = child_issuer->next; - if (child_issuer == NULL) { - break; - } - - if (parent_subject->next == NULL) { - parent_subject->next = calloc(1, sizeof(mbedtls_asn1_named_data)); - if (parent_subject->next == NULL) { - ESP_LOGE(TAG, "Failed to allocate memory for next issuer"); - mbedtls_x509_crt_free(new_cert); - free(new_cert); - return MBEDTLS_ERR_X509_ALLOC_FAILED; - } - parent_subject = parent_subject->next; - } } // Set the parsed certificate as the candidate CA @@ -576,6 +594,11 @@ void esp_crt_bundle_detach(mbedtls_ssl_config *conf) s_crt_bundle = NULL; if (conf) { mbedtls_ssl_conf_verify(conf, NULL, NULL); +#if defined(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY) + mbedtls_ssl_conf_ca_cb(conf, NULL, NULL); +#else + mbedtls_ssl_conf_ca_chain(conf, NULL, NULL); +#endif /* CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY */ } } diff --git a/components/mbedtls/test_apps/mbedtls_ut/main/crts/prvtkey.pem b/components/mbedtls/test_apps/mbedtls_ut/main/crts/prvtkey.pem index bb0a510a7cb..06aebb289be 100644 --- a/components/mbedtls/test_apps/mbedtls_ut/main/crts/prvtkey.pem +++ b/components/mbedtls/test_apps/mbedtls_ut/main/crts/prvtkey.pem @@ -1,27 +1,28 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEAySb2QFrQZjQ1EhfN7I+raKrWSWWeYGppqPk3E1sV2y6LSE3M -7cVZyXxcpnP4mcKos3D9k8sbkt05oKcHR2THpWdz5mJn8A7TfJYWnYRHcRuR85th -XC8Pjf3f+JfjXgr/2a2JqHb4fttxoDRwWP1+kbTHa4iERqTFOIhYB9wD8uzbBHJq -IlIGbBHO9J+JxZKVkgDWZfc7zik3YzvkuWju/PmF73BagGxxRDzodzfxhHq1f96J -w10YS9pkhVFLxzOz3O+buwL8plCQplVnpj6J+2MaY4JlUvCcosDkT0EORAtrYGwZ -KJBKn4dRftk8Wbtns7zoJ2SNUL5TZ2oyAb2dwQIDAQABAoIBAQCRmE3tTs5A69Du -A6TdcTAUVnM8NP1ptBw+XgRrUiaDuzC9aPLHt2zB1e4J3S83vBn3p/UjIIQYzV+E -1OED4AJRyoutWdT5gQG6z7gW00QSrm358aGK49VSZUvT17yOuU9u85kMAvDigVvB -JbOb9f/C3yLoxqtXprPJs4ZkSe/hyB0JtRzauDYZnK4JmtgDfGds1cykohbUaeCW -ExUSbi/EPuroowmjEPFmN4tH/C3GtQonwGfjP76GXm8u5Fg8VXYUmb5pnxYcFdvv -shoasbK5lksgK50VP2vA9Y3mIrThRvkWgcv0TZaQWAF/JtdSXIID5WzfsgLbtDQF -hZLk1dDxAoGBAPfAjPAqapNVl4GqSkUWofUHMzZG85fHoPN3INSA0aMr4X9wHFfQ -pQ0ACxuimQj66Vk4rWww+HrsjPfiNMZzoi1exS1tjbQVyTBffrHj8sSdWt8Gw6MB -Pp5ubnCy9pl4lWNHlJZJp2SwAd10LzrizzAQALeEtRmg8meYGZElVUy7AoGBAM/Z -REXLJgaad5V3A2xehrSnknKUwab4LFIrgirZ6h0RXYo+wEHGJpDvM5Vw3sZT+UaJ -Jdlb3cXbqOxWrKlqjKe/S2vNScP7V2Na8l/ySO93PYE0V1Q+EeuNGi41xWC4Dh7o -D7BX2nDm9YBZzNVxM/30/dTzFM+CKrCARsLIXvWzAoGBAOQ4GRv61qXVyHSHO1cd -HB+sfD5ZaXa9S8Q6TqGx8GrQty4/RbyW1BN/oLvaMgKVr3KixQ3OpnYFhW2qkFbm -mdQVYqkQK+Jh1yyaKwkPI8h98wFTJ8/2C4rByzZBhOumqmYDwBoYyvvzLiSjLAag -e56YfzCOLIzpN6K594M+0q6VAoGBALWR5D1gKRjNqbetHxV1QhHg7WMhJkaZOAaU -MYMDmKvJ9sAE72jGE/y6qYJb9pCk3PdMaf8GbKciq9/CG9Vn2fXUe6txy4XkNEP8 -OA2vFx3yOY18Tumty3PNcNh7arCCOPuw17vCE3ZbnI2CZRj0amnosjFsJHreCDLl -7GrOJX5XAoGASZXbGykpYJTTr5PGPL/eX0koU1RZ9f6fvVdkfeWNGZfJ4oGkxDcO -fJnzq9wC9YREy6f3eoMrix95RPv4Qo1Wwi2PmtyMFvUdsYckFEhxSN3p4Iqn/nQg -6I7VB0yNqw8ZdP1vBkRcg3kk+QO2tci+OTdpDSKmO5nGjuqpsdBM5/o= ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCTJ5b+P/Qyla3v +ri/JphhHto/S39vE9GlnR3iP+7pKfmev89PmkQGvu1csGL4wHOhxtWh1KUqbgVGA +XXO0hVKGherY7XaugVg5td2kxrDeVVtx/mHf0cE8SqSdeTRNrIlBp43ogKb4QZdp +i019khCt6blB6Ky0vuFiud8ipDdZ+Iw1vokhpl2odjJ6wyMF6szDaWs3N/cVLAD5 +D8O8WgjWRvoQqjU/6UAaCXMAS7rQwolN2k4vjWuXAaq3TU6Kx8XQGS+3dqtWD6Vh +PetnnqPGrVZb8qC+ugN+Q9sNJlBlhaLDw3q6icS3wZT/x9B1sszZUzVl8L7dkvxd +A6SOuo8HAgMBAAECggEAM+P0BLf8npHVgf1EcLEXQsKHuO6jirI9MOQtCWjU6kvk +uFEc2eMsWxGOzcfz2Pd4qBA2Q4pJ+sgBO2i40mFpFnLGu7QQ87w8pi0ReXdvCxfC +ZVI0pT5MC7yui7Ef1nwO8o3pJqGnP0ex4s/yhWMqaolOgIVR0XK1+6BbpNWPhzPr +FqgvgLEnJWS0u2T2hOKFE37WFnM0pLuQl0oGwKOSpOaY9dFSUDMoPrmMCWEdAcuZ +qIz5GgmAwkHaA1ztnIec+Svm2sXSXTy0nrnyD/mcDOS0OCeCmxM5jZRJ1AkZIqm9 +PhHPVPEhHy+uR+jzvVE5lxUYPZman0t0dIaDuKqn0QKBgQDIwxpjJFjxkMVewZtD +wvFFVfkY7CVobZiTB1Tr7cL7/Bs6ge6ltnwG1sceuiTg23BzOgyt2cQpl+F7dDoU +QCXDWoO+Od3Qx7Sr0IQ3rwyycrez3DfcahmYieBiGRwIZx17ZcfpV8rsK8zg6enZ +4C/eV41RDG1dKJyDbXaYTzzY3QKBgQC7pJYfYKjyKwLuh5ORbt/OvnXSC99SrpMV +HaJRKgq6m6LPcCIz9CcZNLSj3coVojBOWWKC254GVghddrUxA77ntasQ9EYBAVmD +pBrMW62dJ+g9qZVLZw+3lF71nYmBAEIXulW5Do09hnmHLpQJQfRgfZQBsn0yryhH +h8+ZNOWXMwKBgCVxdfdtrQUIyjbdBxdBQXx8B2RljqiGYEFzyZvjEU2r/GwSFa9k +dIdWAXcyonQpmTR0bC70gYh8YjqDN93VBPYFaLLO2hb5WDH/RtmX1Vdm2+o4tVZv +l2yCso25Pyg2CyKbnghgLmGT3bdJCStwi5z9WUb3eWI5k89TWB4aETAlAoGBALsJ +7hJM2Vq7AOse/TtkV3bPZsX+y5axKS9NILTiwVsNNBat4YoD/s0jOkR1GbDCwH/4 +nTdvDm+mZiQz5Zx77VkuPtxhgT4TSFTtyUCWydHzK437cjN9Aa+uF5GgfKW3yim4 +tSYHmUYQuybCiRFJSvy7cELY1e8lpXLXr1k53vj9AoGBALNK7W6+f+y98/2e45Jd +SSEe1GF984hHAi4zRn64Zhg9gxKBM9CEyrsbL2BlP3UY+2xdnJvzKnmPtelhIvgH +FqG2XC6236PGzD9ETGyayNu/d612C82h/BeePzce/3o4WaMldbgMRdjniKl+nFJR +8EDuH1YACHhGCCE5BT24OtFs +-----END PRIVATE KEY----- diff --git a/components/mbedtls/test_apps/mbedtls_ut/main/crts/server_cert_bundle b/components/mbedtls/test_apps/mbedtls_ut/main/crts/server_cert_bundle index 5a0a2f139f5..16b7c3a6c69 100644 Binary files a/components/mbedtls/test_apps/mbedtls_ut/main/crts/server_cert_bundle and b/components/mbedtls/test_apps/mbedtls_ut/main/crts/server_cert_bundle differ diff --git a/components/mbedtls/test_apps/mbedtls_ut/main/crts/server_cert_chain.pem b/components/mbedtls/test_apps/mbedtls_ut/main/crts/server_cert_chain.pem index afc99a9e302..b16beb8d836 100644 --- a/components/mbedtls/test_apps/mbedtls_ut/main/crts/server_cert_chain.pem +++ b/components/mbedtls/test_apps/mbedtls_ut/main/crts/server_cert_chain.pem @@ -1,20 +1,44 @@ -----BEGIN CERTIFICATE----- -MIIDVTCCAj0CFG5WO5Ukqd/0PnrSPIlQnXNjrCUUMA0GCSqGSIb3DQEBCwUAMGIx -CzAJBgNVBAYTAkNOMRMwEQYDVQQIDApTb21lLVN0YXRlMREwDwYDVQQHDAhTaGFu -Z2hhaTESMBAGA1UECgwJRXNwcmVzc2lmMRcwFQYDVQQDDA5Fc3ByZXNzaWYgUm9v -dDAeFw0yMDAzMjYwNjQxMTlaFw0yMTAzMjEwNjQxMTlaMGwxCzAJBgNVBAYTAkNO -MRMwEQYDVQQIDApTb21lLVN0YXRlMREwDwYDVQQHDAhTaGFuZ2hhaTEhMB8GA1UE -CgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRIwEAYDVQQDDAlsb2NhbGhvc3Qw -ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJJvZAWtBmNDUSF83sj6to -qtZJZZ5gammo+TcTWxXbLotITcztxVnJfFymc/iZwqizcP2TyxuS3TmgpwdHZMel -Z3PmYmfwDtN8lhadhEdxG5Hzm2FcLw+N/d/4l+NeCv/ZrYmodvh+23GgNHBY/X6R -tMdriIRGpMU4iFgH3APy7NsEcmoiUgZsEc70n4nFkpWSANZl9zvOKTdjO+S5aO78 -+YXvcFqAbHFEPOh3N/GEerV/3onDXRhL2mSFUUvHM7Pc75u7AvymUJCmVWemPon7 -YxpjgmVS8JyiwORPQQ5EC2tgbBkokEqfh1F+2TxZu2ezvOgnZI1QvlNnajIBvZ3B -AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAI2RzAwx1IiyWYPbSQOMjATKG1hiqNJF -fkkqJrSfu93iQyye3Umb/pdUf7v5xgN2NrW5VnRow19VR7uCU4VCCBfx77f0Zp2e -UA13qhT5zljoqgtkU9bHbRfTW/Hq30joKqQz8+Z0Yom6qZA7XjAhXXiHt7I4Noq6 -y+HwH08Xr1nII1c6Zc0cDqK9UV02w2v1RJrnGlq3v/CBpanA/nz4LdP5Jqbh79WW -bCe8+Y7WEYR7K4dKSkDugf8ROAaGuCYAbhRMU3tFjNlMRR/5HcBpy7MfUvX6GcI0 -QCfe4ugnHXQXNxS0rb2uM6yCHOTiQ5MJjBPh9tRYV9bSko5u/NmwsFU= +MIIDqTCCApGgAwIBAgIUeARDBjDWIDu3UOVT1V4TAsrQDs0wDQYJKoZIhvcNAQEL +BQAwYjELMAkGA1UEBhMCQ04xEzARBgNVBAgMClNvbWUtU3RhdGUxETAPBgNVBAcM +CFNoYW5naGFpMRIwEAYDVQQKDAlFc3ByZXNzaWYxFzAVBgNVBAMMDkVzcHJlc3Np +ZiBSb290MB4XDTI2MDQyODAzMjE1NVoXDTQxMDQyNDAzMjE1NVowbDELMAkGA1UE +BhMCQ04xEzARBgNVBAgMClNvbWUtU3RhdGUxETAPBgNVBAcMCFNoYW5naGFpMSEw +HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCWxvY2Fs +aG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJMnlv4/9DKVre+u +L8mmGEe2j9Lf28T0aWdHeI/7ukp+Z6/z0+aRAa+7VywYvjAc6HG1aHUpSpuBUYBd +c7SFUoaF6tjtdq6BWDm13aTGsN5VW3H+Yd/RwTxKpJ15NE2siUGnjeiApvhBl2mL +TX2SEK3puUHorLS+4WK53yKkN1n4jDW+iSGmXah2MnrDIwXqzMNpazc39xUsAPkP +w7xaCNZG+hCqNT/pQBoJcwBLutDCiU3aTi+Na5cBqrdNTorHxdAZL7d2q1YPpWE9 +62eeo8atVlvyoL66A35D2w0mUGWFosPDerqJxLfBlP/H0HWyzNlTNWXwvt2S/F0D +pI66jwcCAwEAAaNNMEswCQYDVR0TBAIwADAdBgNVHQ4EFgQUyTHWaahNKgm67bDV +g/Z298Lgz44wHwYDVR0jBBgwFoAUaMEt9ST7r9G/w6zSZbZg0Y5XQOIwDQYJKoZI +hvcNAQELBQADggEBAB4Z4AG7cOyMrtql6CLgdadbjf8s49h8Z82/POcAb5tx73B3 +rX7QoI542hFoeeTNTIYRwyr7bANe/9IRp9/t0dOIUwltl3OWuKDgOjyopGc5uJnf +RVHntmMOO6DmnGWYEqjzVdp9ApFzef6SkjgJSGMiRHdH1fue21py6sEXvPNz9MZw +Fit/Oos3Pr49vJAcaJU5/cjJPeYZ84lWNV7ceJZGaMYdKXE0nbHnPa07v0/3mJBg +MCiSu5QyIDe0LMsDkiY3EgyqRAKyTgxitq1P/BbHWNwKMLV3AhmmP3CJoqes+CCL +HndeEtZ+M7arpUx29xtHBXhcN7WyjEbMYPI4wug= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIUeCJ5NrL+dsyhqe2JktSfNwY+3/QwDQYJKoZIhvcNAQEL +BQAwYjELMAkGA1UEBhMCQ04xEzARBgNVBAgMClNvbWUtU3RhdGUxETAPBgNVBAcM +CFNoYW5naGFpMRIwEAYDVQQKDAlFc3ByZXNzaWYxFzAVBgNVBAMMDkVzcHJlc3Np +ZiBSb290MB4XDTI2MDQyODAzMjE1NVoXDTQxMDQyNDAzMjE1NVowYjELMAkGA1UE +BhMCQ04xEzARBgNVBAgMClNvbWUtU3RhdGUxETAPBgNVBAcMCFNoYW5naGFpMRIw +EAYDVQQKDAlFc3ByZXNzaWYxFzAVBgNVBAMMDkVzcHJlc3NpZiBSb290MIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/DgvPVCOSy15DOmTQMq5AsT2FRsN +80bCpZSgeRFoPnvL1Gz6+xBB5/RUY/a1+F0lOjXVq8UTmh0tQT+Sr14boaN0Tzks +M7TY0AjaaQPkr55gLwHV1jHYDhlJPXLrHFiG5pDpIN+ml6K6bbyPP1r1jWQYHCfS +kGxgYBU5yXLvko34oBf/yKymAvrRjlCsB1nNFlT/FzXOd0S4vgotMpV6D8p/m+sR +9vAI2eIRfvykf21pwa2UWUzTpa5MLcKPU/IeELDw8zNYIM/16xZS/NBnEu8uSuPt +U/GkmOm2v2qGmLAl2d9HYZ8jn4KeoIpgEBrF/OqCGgMrrO3Vz5OZun4e2QIDAQAB +o2MwYTAdBgNVHQ4EFgQUaMEt9ST7r9G/w6zSZbZg0Y5XQOIwHwYDVR0jBBgwFoAU +aMEt9ST7r9G/w6zSZbZg0Y5XQOIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E +BAMCAQYwDQYJKoZIhvcNAQELBQADggEBAPdtnZgFIESYXscEpqBmtaO/J25buNaJ +vszF/aKzn0iiwXEC74xEiPs90Xc3UbIa22fqvOTKljm7l5BheEZ76gbyD1mxJ6T6 +6IbtfXcv7Nblqrl84AKC13+lrzgU/T8atv29qaiyqEaIkfFQbtGETD213NC1ubNn +5qOdRzMFUrHcgh9fnGyNicc/JFXrrXN6Gqvwzu36IaNUXjzXrE52zr9mkyFtePTI +2HstreAMLn54v7gqc0mHRB7bMInKp/+E312OnXmYk1tYHI00/TOAGNY4z+okyFJ9 +uW9nKnUIiZwByECmPTGhIwvTWJEgJeuW6n07g6VnmwTBKwKpX8fIuwQ= -----END CERTIFICATE----- diff --git a/components/mbedtls/test_apps/mbedtls_ut/main/crts/server_root.pem b/components/mbedtls/test_apps/mbedtls_ut/main/crts/server_root.pem index 5854747f9dc..2333a894b0a 100644 --- a/components/mbedtls/test_apps/mbedtls_ut/main/crts/server_root.pem +++ b/components/mbedtls/test_apps/mbedtls_ut/main/crts/server_root.pem @@ -1,22 +1,22 @@ -----BEGIN CERTIFICATE----- -MIIDpTCCAo2gAwIBAgIUduK+lv/MILT278PPIYz8HkFzhFUwDQYJKoZIhvcNAQEL +MIIDtTCCAp2gAwIBAgIUeCJ5NrL+dsyhqe2JktSfNwY+3/QwDQYJKoZIhvcNAQEL BQAwYjELMAkGA1UEBhMCQ04xEzARBgNVBAgMClNvbWUtU3RhdGUxETAPBgNVBAcM CFNoYW5naGFpMRIwEAYDVQQKDAlFc3ByZXNzaWYxFzAVBgNVBAMMDkVzcHJlc3Np -ZiBSb290MB4XDTIwMDMyNjA2NDAxMFoXDTI1MDMyNjA2NDAxMFowYjELMAkGA1UE +ZiBSb290MB4XDTI2MDQyODAzMjE1NVoXDTQxMDQyNDAzMjE1NVowYjELMAkGA1UE BhMCQ04xEzARBgNVBAgMClNvbWUtU3RhdGUxETAPBgNVBAcMCFNoYW5naGFpMRIw EAYDVQQKDAlFc3ByZXNzaWYxFzAVBgNVBAMMDkVzcHJlc3NpZiBSb290MIIBIjAN -BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp9ILzOjsz7dZbABUIoDCBat3MPsv -qd20Jsk3GzkLjY/HjTCMBweq2zt0sRsa+YwCPtQyAsYPtgt/VzRY4TF8jqmSj7Ko -DKOWkbim0O0XDAT8DfkQ32pZC7DnAw/374Vmm/ZmN/yE4zNUjNbjO2weswczcSdL -B3ITsa+OquKYK8J2Pe5gZh/tC0f0I9ks3UplcLyEex8TQZivAK3RL4QWj4j4NJWn -wH5qdizuKStwWEo3FvTP4g95SQItw31HTA8mJcBzCZC0NOZyMckRSmK51XljQ0iU -G7KwK8GNbDC+VUZEt5aGB5QZhCFC2wo5An7u20UHRUWbv4MEgddPDoQ4EwIDAQAB -o1MwUTAdBgNVHQ4EFgQU3inIjbdXp/DgSnVAiJmTlAtKH08wHwYDVR0jBBgwFoAU -3inIjbdXp/DgSnVAiJmTlAtKH08wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B -AQsFAAOCAQEAOpLjyXj2e0IaUgJK3lGuJ9u6piehYK1WqPoCR7K7pUnFGyNLu0mq -yfTqNoXV8a7NmM8Vn8ZJ1Gep20JqgvR27o3OE87bT7E/JPUsvbu7MNdfiVxpFWi1 -HxdBrzHr+mcakbhRxI38s3GVNT9Y89Y7FZbE+dqT8SxILk2pVUExfZR/ItazDTxl -95ARCOj/bQPCEN+oLYzS31ORmkJfY2AuJAcJUTCyO4UfpKVFmQeAKlNmTq9Q0a6C -0RlbzZ/PJoB3d265A9fTjlANQ7XzE8GgIJVR7cz5OJzZVxfEr9ME9VfgNrjKyXS3 -FcFQvif6JqX6IbmTenEKi7IfgX2zu1nxtQ== +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/DgvPVCOSy15DOmTQMq5AsT2FRsN +80bCpZSgeRFoPnvL1Gz6+xBB5/RUY/a1+F0lOjXVq8UTmh0tQT+Sr14boaN0Tzks +M7TY0AjaaQPkr55gLwHV1jHYDhlJPXLrHFiG5pDpIN+ml6K6bbyPP1r1jWQYHCfS +kGxgYBU5yXLvko34oBf/yKymAvrRjlCsB1nNFlT/FzXOd0S4vgotMpV6D8p/m+sR +9vAI2eIRfvykf21pwa2UWUzTpa5MLcKPU/IeELDw8zNYIM/16xZS/NBnEu8uSuPt +U/GkmOm2v2qGmLAl2d9HYZ8jn4KeoIpgEBrF/OqCGgMrrO3Vz5OZun4e2QIDAQAB +o2MwYTAdBgNVHQ4EFgQUaMEt9ST7r9G/w6zSZbZg0Y5XQOIwHwYDVR0jBBgwFoAU +aMEt9ST7r9G/w6zSZbZg0Y5XQOIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E +BAMCAQYwDQYJKoZIhvcNAQELBQADggEBAPdtnZgFIESYXscEpqBmtaO/J25buNaJ +vszF/aKzn0iiwXEC74xEiPs90Xc3UbIa22fqvOTKljm7l5BheEZ76gbyD1mxJ6T6 +6IbtfXcv7Nblqrl84AKC13+lrzgU/T8atv29qaiyqEaIkfFQbtGETD213NC1ubNn +5qOdRzMFUrHcgh9fnGyNicc/JFXrrXN6Gqvwzu36IaNUXjzXrE52zr9mkyFtePTI +2HstreAMLn54v7gqc0mHRB7bMInKp/+E312OnXmYk1tYHI00/TOAGNY4z+okyFJ9 +uW9nKnUIiZwByECmPTGhIwvTWJEgJeuW6n07g6VnmwTBKwKpX8fIuwQ= -----END CERTIFICATE----- diff --git a/components/mbedtls/test_apps/mbedtls_ut/main/test_esp_crt_bundle.c b/components/mbedtls/test_apps/mbedtls_ut/main/test_esp_crt_bundle.c index ca8c918de15..8fbc02d4aac 100644 --- a/components/mbedtls/test_apps/mbedtls_ut/main/test_esp_crt_bundle.c +++ b/components/mbedtls/test_apps/mbedtls_ut/main/test_esp_crt_bundle.c @@ -9,6 +9,8 @@ * SPDX-FileContributor: 2019-2025 Espressif Systems (Shanghai) CO LTD */ #include +#include +#include #include "esp_err.h" #include "esp_log.h" @@ -89,6 +91,38 @@ static volatile bool exit_flag; esp_err_t endpoint_teardown(mbedtls_endpoint_t *endpoint); +#if defined(CONFIG_MBEDTLS_HAVE_TIME_DATE) +/* Set system time to compile time so that MBEDTLS_HAVE_TIME_DATE checks + * pass without network/NTP. __DATE__ gives "Mon DD YYYY", __TIME__ gives "HH:MM:SS". */ +static void set_system_time_to_compile_time(void) +{ + const char *months[] = {"Jan","Feb","Mar","Apr","May","Jun", + "Jul","Aug","Sep","Oct","Nov","Dec"}; + char mon_str[4]; + int day, year, hour, min, sec; + + sscanf(__DATE__, "%3s %d %d", mon_str, &day, &year); + sscanf(__TIME__, "%d:%d:%d", &hour, &min, &sec); + + int mon = 0; + for (int i = 0; i < 12; i++) { + if (strcmp(mon_str, months[i]) == 0) { + mon = i; + break; + } + } + + struct tm t = { + .tm_sec = sec, .tm_min = min, .tm_hour = hour, + .tm_mday = day, .tm_mon = mon, .tm_year = year - 1900, + }; + + struct timeval tv = { .tv_sec = mktime(&t), .tv_usec = 0 }; + settimeofday(&tv, NULL); + ESP_LOGI(TAG, "System time set to compile time: %s %s", __DATE__, __TIME__); +} +#endif // CONFIG_MBEDTLS_HAVE_TIME_DATE + esp_err_t server_setup(mbedtls_endpoint_t *server) { int ret; @@ -302,7 +336,7 @@ void client_task(void *pvParameters) size_t available_before_handshake = uxTaskGetStackHighWaterMark(NULL); ESP_LOGI(TAG, "Available stack before handshake: %d", available_before_handshake); - ESP_LOGI(TAG, "Performing the SSL/TLS handshake with bundle that is missing the server root certificate"); + ESP_LOGI(TAG, "Performing the SSL/TLS handshake with bundle that contains the server root certificate"); while ( ( ret = mbedtls_ssl_handshake( &client->ssl ) ) != 0 ) { if ( ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE ) { printf( "mbedtls_ssl_handshake failed with -0x%x\n", -ret ); @@ -343,6 +377,10 @@ TEST_CASE("custom certificate bundle", "[mbedtls]") { test_case_uses_tcpip(); +#if defined(CONFIG_MBEDTLS_HAVE_TIME_DATE) + set_system_time_to_compile_time(); +#endif + SemaphoreHandle_t signal_sem = xSemaphoreCreateBinary(); TEST_ASSERT_NOT_NULL(signal_sem); @@ -567,3 +605,146 @@ TEST_CASE("custom certificate bundle init API - bound checking - Incorrect certi esp_ret = esp_crt_bundle_set(test_bundle, sizeof(test_bundle)); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_ret); } + +#if defined(CONFIG_MBEDTLS_HAVE_TIME_DATE) +TEST_CASE("certificate bundle - expired cert rejected with time-date check", "[mbedtls]") +{ + /* With HAVE_TIME_DATE enabled, a genuinely expired certificate must still + * be rejected even though its issuer is in the bundle and signature is valid. + * correct_sig_crt_esp32_com.pem expired Feb 2025 — with system time set to + * compile time (2026+), the EXPIRED flag must persist after bundle verification. */ + set_system_time_to_compile_time(); + + mbedtls_x509_crt crt; + uint32_t flags = 0; + + esp_crt_bundle_attach(NULL); + + mbedtls_x509_crt_init(&crt); + mbedtls_x509_crt_parse(&crt, correct_sig_crt_pem_start, + correct_sig_crt_pem_end - correct_sig_crt_pem_start); + + int ret = mbedtls_x509_crt_verify(&crt, NULL, NULL, NULL, &flags, + esp_crt_verify_callback, NULL); + + /* Verification must fail — the cert is genuinely expired */ + TEST_ASSERT_NOT_EQUAL(0, ret); + /* The EXPIRED flag specifically must be set */ + TEST_ASSERT_BITS_HIGH(MBEDTLS_X509_BADCERT_EXPIRED, flags); + + mbedtls_x509_crt_free(&crt); + esp_crt_bundle_detach(NULL); +} +#endif /* CONFIG_MBEDTLS_HAVE_TIME_DATE */ + +#if defined(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY) && defined(CONFIG_MBEDTLS_HAVE_TIME_DATE) +/* Client task for cross-signed verification with time-date checking. + * Exercises the esp_crt_ca_cb_callback path where synthetic root certs + * from the bundle have no validity dates. */ +void client_task_cross_signed(void *pvParameters) +{ + SemaphoreHandle_t *client_signal_sem = (SemaphoreHandle_t *) pvParameters; + int ret = ESP_FAIL; + + mbedtls_endpoint_t *client = calloc(1, sizeof(mbedtls_endpoint_t)); + if (client == NULL) { + ESP_LOGE(TAG, "Failed to allocate memory for client"); + vTaskSuspend(NULL); + } + esp_crt_validate_res_t res = ESP_CRT_VALIDATE_UNKNOWN; + + if (client_setup(client) != ESP_OK) { + ESP_LOGE(TAG, "SSL client setup failed"); + goto exit; + } + + /* Attach bundle — this registers both esp_crt_verify_callback and + * esp_crt_ca_cb_callback when CROSS_SIGNED_VERIFY is enabled */ + ret = esp_crt_bundle_attach(&client->conf); + TEST_ASSERT_EQUAL(ESP_OK, ret); + + ret = esp_crt_bundle_set(server_cert_bundle_start, + server_cert_bundle_end - server_cert_bundle_start); + TEST_ASSERT_EQUAL(ESP_OK, ret); + + ESP_LOGI(TAG, "Connecting to %s:%s...", SERVER_ADDRESS, SERVER_PORT); + if ((ret = mbedtls_net_connect(&client->client_fd, SERVER_ADDRESS, + SERVER_PORT, MBEDTLS_NET_PROTO_TCP)) != 0) { + ESP_LOGE(TAG, "mbedtls_net_connect returned -%x", -ret); + goto exit; + } + + mbedtls_ssl_set_bio(&client->ssl, &client->client_fd, + mbedtls_net_send, mbedtls_net_recv, NULL); + + ESP_LOGI(TAG, "Performing SSL/TLS handshake (cross-signed verify + time-date)"); + while ((ret = mbedtls_ssl_handshake(&client->ssl)) != 0) { + if (ret != MBEDTLS_ERR_SSL_WANT_READ && + ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + ESP_LOGE(TAG, "mbedtls_ssl_handshake failed with -0x%x", -ret); + break; + } + } + + ESP_LOGI(TAG, "Verifying peer X.509 certificate..."); + ret = mbedtls_ssl_get_verify_result(&client->ssl); + res = (ret == 0) ? ESP_CRT_VALIDATE_OK : ESP_CRT_VALIDATE_FAIL; + + if (res == ESP_CRT_VALIDATE_OK) { + ESP_LOGI(TAG, "Certificate verification passed!"); + } else { + ESP_LOGE(TAG, "Certificate verification failed! flags=0x%x", ret); + } + TEST_ASSERT_EQUAL(ESP_CRT_VALIDATE_OK, res); + +exit: + mbedtls_ssl_close_notify(&client->ssl); + mbedtls_ssl_session_reset(&client->ssl); + esp_crt_bundle_detach(&client->conf); + endpoint_teardown(client); + xSemaphoreGive(*client_signal_sem); + free(client); + vTaskSuspend(NULL); +} + +TEST_CASE("cross-signed certificate bundle with time-date check", "[mbedtls]") +{ + test_case_uses_tcpip(); + + /* Set system time so that certificate validity checks pass */ + set_system_time_to_compile_time(); + + SemaphoreHandle_t signal_sem = xSemaphoreCreateBinary(); + TEST_ASSERT_NOT_NULL(signal_sem); + + exit_flag = false; + TaskHandle_t server_task_handle; + xTaskCreate(server_task, "server task", 8192, &signal_sem, 10, + &server_task_handle); + + if (!xSemaphoreTake(signal_sem, SEM_TIMEOUT / portTICK_PERIOD_MS)) { + TEST_FAIL_MESSAGE("signal_sem not released, server start failed"); + } + + SemaphoreHandle_t client_signal_sem = xSemaphoreCreateBinary(); + TEST_ASSERT_NOT_NULL(client_signal_sem); + + TaskHandle_t client_task_handle; + xTaskCreate(client_task_cross_signed, "client task", 8192, + &client_signal_sem, 10, &client_task_handle); + + if (!xSemaphoreTake(client_signal_sem, SEM_TIMEOUT / portTICK_PERIOD_MS)) { + TEST_FAIL_MESSAGE("client_signal_sem not released, client exit failed"); + } + unity_utils_task_delete(client_task_handle); + + exit_flag = true; + + if (!xSemaphoreTake(signal_sem, SEM_TIMEOUT / portTICK_PERIOD_MS)) { + TEST_FAIL_MESSAGE("signal_sem not released, server exit failed"); + } + unity_utils_task_delete(server_task_handle); + vSemaphoreDelete(client_signal_sem); + vSemaphoreDelete(signal_sem); +} +#endif /* CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY && CONFIG_MBEDTLS_HAVE_TIME_DATE */ diff --git a/components/mbedtls/test_apps/mbedtls_ut/pytest_mbedtls_ut.py b/components/mbedtls/test_apps/mbedtls_ut/pytest_mbedtls_ut.py index 78392bb6014..3e5820959a1 100644 --- a/components/mbedtls/test_apps/mbedtls_ut/pytest_mbedtls_ut.py +++ b/components/mbedtls/test_apps/mbedtls_ut/pytest_mbedtls_ut.py @@ -155,3 +155,22 @@ def test_mbedtls_ds_rsa(dut: Dut) -> None: @idf_parametrize('target', ['esp32s3'], indirect=['target']) def test_mbedtls_aria(dut: Dut) -> None: dut.run_all_single_board_cases(group='aria') + + +@pytest.mark.generic +@pytest.mark.parametrize( + 'config', + [ + 'cross_signed', + ], + indirect=True, +) +@idf_parametrize('target', ['supported_targets'], indirect=['target']) +def test_mbedtls_cross_signed(dut: Dut) -> None: + dut.run_all_single_board_cases( + name=[ + 'cross-signed certificate bundle with time-date check', + 'custom certificate bundle', + 'certificate bundle - expired cert rejected with time-date check', + ] + ) diff --git a/components/mbedtls/test_apps/mbedtls_ut/sdkconfig.ci.cross_signed b/components/mbedtls/test_apps/mbedtls_ut/sdkconfig.ci.cross_signed new file mode 100644 index 00000000000..35866d2f491 --- /dev/null +++ b/components/mbedtls/test_apps/mbedtls_ut/sdkconfig.ci.cross_signed @@ -0,0 +1,2 @@ +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY=y +CONFIG_MBEDTLS_HAVE_TIME_DATE=y diff --git a/examples/protocols/http_request/pytest_http_request.py b/examples/protocols/http_request/pytest_http_request.py index b2a871e9830..7d6c7aa0c1a 100644 --- a/examples/protocols/http_request/pytest_http_request.py +++ b/examples/protocols/http_request/pytest_http_request.py @@ -22,7 +22,7 @@ def test_examples_protocol_http_request(dut: Dut) -> None: # check and log bin size binary_file = os.path.join(dut.app.binary_path, 'http_request.bin') bin_size = os.path.getsize(binary_file) - logging.info('http_request_bin_size : {}KB'.format(bin_size // 1024)) + logging.info(f'http_request_bin_size : {bin_size // 1024}KB') # start test dut.expect(r'DNS lookup succeeded.', timeout=30) # check if connected or not @@ -30,7 +30,7 @@ def test_examples_protocol_http_request(dut: Dut) -> None: dut.expect(' ... socket send success') dut.expect(' ... set socket receiving timeout success') # check server response - dut.expect(r'HTTP/1.0 200 OK') + dut.expect(r'HTTP/1.1 200 OK') # read from the socket completed dut.expect('... done reading from socket. Last read return=0 errno=128') dut.expect(r'(\d)...')