Merge branch 'fix/memory_leak_cross_signed_cert_verify_v6.0' into 'release/v6.0'

fix(esp_crt_bundle): fixes verification failures with cross signed certificates (v6.0)

See merge request espressif/esp-idf!48624
This commit is contained in:
Mahavir Jain
2026-06-29 11:17:37 +05:30
9 changed files with 356 additions and 106 deletions
@@ -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 */
}
}
@@ -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-----
@@ -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-----
@@ -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-----
@@ -9,6 +9,8 @@
* SPDX-FileContributor: 2019-2025 Espressif Systems (Shanghai) CO LTD
*/
#include <string.h>
#include <sys/time.h>
#include <time.h>
#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 */
@@ -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',
]
)
@@ -0,0 +1,2 @@
CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY=y
CONFIG_MBEDTLS_HAVE_TIME_DATE=y
@@ -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)...')