mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-01 18:50:34 +03:00
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:
@@ -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-----
|
||||
|
||||
Binary file not shown.
@@ -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)...')
|
||||
|
||||
Reference in New Issue
Block a user