From c5c8b44f0f66f8edf5d6035705f15f3e55265798 Mon Sep 17 00:00:00 2001 From: martin Date: Sun, 19 Oct 2025 18:37:06 +0200 Subject: [PATCH] Fixed non-compliant handling of missing stapled OCSP responses If the OCSP response was not present for a certificate the server created a non-conforming empty CertificateStatus extension instead of not sending the extension at all. Fixes #28902 Fixes b1b4b154 Reviewed-by: Matt Caswell Reviewed-by: Tomas Mraz (Merged from https://github.com/openssl/openssl/pull/28955) --- apps/s_server.c | 34 +++-- ssl/ssl_local.h | 2 + ssl/statem/extensions_srvr.c | 11 +- ssl/statem/statem_local.h | 2 +- ssl/statem/statem_srvr.c | 272 ++++++++++++++++++--------------- test/recipes/ocsp-response.der | Bin 1517 -> 1237 bytes 6 files changed, 189 insertions(+), 132 deletions(-) diff --git a/apps/s_server.c b/apps/s_server.c index 82590f9adbb..52b4a60794f 100644 --- a/apps/s_server.c +++ b/apps/s_server.c @@ -601,7 +601,7 @@ static int bring_ocsp_resp_in_correct_order(SSL *s, tlsextstatusctx *srctx, STACK_OF(OCSP_RESPONSE) *sk_resp_unordered, STACK_OF(OCSP_RESPONSE) **sk_resp) { - STACK_OF(X509) *server_certs = NULL; + STACK_OF(X509) *server_chain = NULL; X509 *ssl_cert = NULL; X509 *issuer = NULL; OCSP_RESPONSE *resp = NULL; @@ -613,14 +613,14 @@ static int bring_ocsp_resp_in_correct_order(SSL *s, tlsextstatusctx *srctx, if (*sk_resp != NULL) sk_OCSP_RESPONSE_pop_free(*sk_resp, OCSP_RESPONSE_free); - SSL_get0_chain_certs(s, &server_certs); + SSL_get0_chain_certs(s, &server_chain); /* * TODO(DTLS-1.3): in future DTLS should also be considered */ - if (server_certs != NULL && srctx->status_all && + if (server_chain != NULL && srctx->status_all && !SSL_is_dtls(s) && SSL_version(s) >= TLS1_3_VERSION) { /* certificate chain is available */ - num = sk_X509_num(server_certs) + 1; + num = sk_X509_num(server_chain) + 1; } /* get OCSP response for server certificate first */ @@ -640,10 +640,21 @@ static int bring_ocsp_resp_in_correct_order(SSL *s, tlsextstatusctx *srctx, for (i = 0; i < num; i++) { if (i != 0) /* for each certificate in chain (except root) get the OCSP response */ - ssl_cert = sk_X509_value(server_certs, i - 1); + ssl_cert = sk_X509_value(server_chain, i - 1); /* issuer certificate is next in chain */ - issuer = sk_X509_value(server_certs, i); + issuer = sk_X509_value(server_chain, i); + + /* + * in the case the root CA certificate is not included in the chain + * we assume that the last remaining response is issued by it + */ + if (issuer == NULL && i == (num - 1) && sk_OCSP_RESPONSE_num(sk_resp_unordered) == 1) { + resp = sk_OCSP_RESPONSE_value(sk_resp_unordered, 0); + (void)sk_OCSP_RESPONSE_push(*sk_resp, resp); + sk_OCSP_RESPONSE_delete(sk_resp_unordered, 0); + continue; + } if (issuer == NULL || (cert_id = OCSP_cert_to_id(NULL, ssl_cert, issuer)) == NULL) { @@ -752,7 +763,7 @@ static int get_ocsp_resp_from_responder(SSL *s, tlsextstatusctx *srctx, { X509 *ssl_cert = NULL; int i, num = 0; - STACK_OF(X509) *server_certs = NULL; + STACK_OF(X509) *server_chain = NULL; OCSP_RESPONSE *resp = NULL; if (*sk_resp != NULL) { @@ -760,14 +771,15 @@ static int get_ocsp_resp_from_responder(SSL *s, tlsextstatusctx *srctx, *sk_resp = NULL; } - SSL_get0_chain_certs(s, &server_certs); + SSL_get0_chain_certs(s, &server_chain); + /* * TODO(DTLS-1.3): in future DTLS should also be considered */ - if (server_certs != NULL && srctx->status_all && + if (server_chain != NULL && srctx->status_all && !SSL_is_dtls(s) && SSL_version(s) >= TLS1_3_VERSION) { /* certificate chain is available */ - num = sk_X509_num(server_certs) + 1; + num = sk_X509_num(server_chain) + 1; } else { /* * certificate chain is not available, @@ -792,7 +804,7 @@ static int get_ocsp_resp_from_responder(SSL *s, tlsextstatusctx *srctx, /* for each certificate in chain (except root) get the OCSP response */ for (i = 0; i < num; i++) { if (i != 0) /* get OCSP response for server certificate first */ - ssl_cert = sk_X509_value(server_certs, i - 1); + ssl_cert = sk_X509_value(server_chain, i - 1); resp = NULL; if (get_ocsp_resp_from_responder_single(s, ssl_cert, srctx, &resp) != SSL_TLSEXT_ERR_OK) diff --git a/ssl/ssl_local.h b/ssl/ssl_local.h index 5cf7368a82c..3af36274c9c 100644 --- a/ssl/ssl_local.h +++ b/ssl/ssl_local.h @@ -2980,6 +2980,8 @@ __owur int srp_verify_server_param(SSL_CONNECTION *s); __owur int send_certificate_request(SSL_CONNECTION *s); +OCSP_RESPONSE *ossl_get_ocsp_response(SSL_CONNECTION *s, int chainidx); + /* statem/extensions_cust.c */ custom_ext_method *custom_ext_find(const custom_ext_methods *exts, diff --git a/ssl/statem/extensions_srvr.c b/ssl/statem/extensions_srvr.c index ac2bddde3b0..52508a4ce0a 100644 --- a/ssl/statem/extensions_srvr.c +++ b/ssl/statem/extensions_srvr.c @@ -1753,6 +1753,8 @@ EXT_RETURN tls_construct_stoc_status_request(SSL_CONNECTION *s, WPACKET *pkt, unsigned int context, X509 *x, size_t chainidx) { + OCSP_RESPONSE *resp; + /* We don't currently support this extension inside a CertificateRequest */ if (context == SSL_EXT_TLS1_3_CERTIFICATE_REQUEST) return EXT_RETURN_NOT_SENT; @@ -1760,6 +1762,13 @@ EXT_RETURN tls_construct_stoc_status_request(SSL_CONNECTION *s, WPACKET *pkt, if (!s->ext.status_expected) return EXT_RETURN_NOT_SENT; + /* Try to retrieve OCSP response for the actual certificate */ + resp = ossl_get_ocsp_response(s, (int)chainidx); + + /* If no OCSP response was found the extension is not sent */ + if (resp == NULL) + return EXT_RETURN_NOT_SENT; + if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_status_request) || !WPACKET_start_sub_packet_u16(pkt)) { SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); @@ -1772,7 +1781,7 @@ EXT_RETURN tls_construct_stoc_status_request(SSL_CONNECTION *s, WPACKET *pkt, * separate message */ if (SSL_CONNECTION_IS_TLS13(s) - && !tls_construct_cert_status_body(s, chainidx, pkt)) { + && !tls_construct_cert_status_body(s, resp, pkt)) { /* SSLfatal() already called */ return EXT_RETURN_FAIL; } diff --git a/ssl/statem/statem_local.h b/ssl/statem/statem_local.h index 48870683c34..1b7bab4521b 100644 --- a/ssl/statem/statem_local.h +++ b/ssl/statem/statem_local.h @@ -168,7 +168,7 @@ __owur int ssl_do_client_cert_cb(SSL_CONNECTION *s, X509 **px509, __owur CON_FUNC_RETURN tls_construct_client_key_exchange(SSL_CONNECTION *s, WPACKET *pkt); __owur int tls_client_key_exchange_post_work(SSL_CONNECTION *s); -__owur int tls_construct_cert_status_body(SSL_CONNECTION *s, size_t chainidx, WPACKET *pkt); +__owur int tls_construct_cert_status_body(SSL_CONNECTION *s, OCSP_RESPONSE *resp, WPACKET *pkt); __owur CON_FUNC_RETURN tls_construct_cert_status(SSL_CONNECTION *s, WPACKET *pkt); __owur MSG_PROCESS_RETURN tls_process_key_exchange(SSL_CONNECTION *s, diff --git a/ssl/statem/statem_srvr.c b/ssl/statem/statem_srvr.c index 5b6465bc239..8658e78bba2 100644 --- a/ssl/statem/statem_srvr.c +++ b/ssl/statem/statem_srvr.c @@ -17,6 +17,7 @@ #include "internal/constant_time.h" #include "internal/cryptlib.h" #include "internal/ssl_unwrap.h" +#include "internal/sizes.h" #include #include #include @@ -455,6 +456,148 @@ int send_certificate_request(SSL_CONNECTION *s) return 0; } +/* + * Get the OCSP response for the certificate from the chain identified + * chainidx. + * If no OCSP response could be found NULL is returned. + */ +OCSP_RESPONSE *ossl_get_ocsp_response(SSL_CONNECTION *s, int chainidx) +{ + OCSP_RESPONSE *resp = NULL; +#ifndef OPENSSL_NO_OCSP + int i = 0, num = 0; + unsigned int len; + X509 *x = NULL; + STACK_OF(X509) *chain_certs = NULL; + SSL *ssl = SSL_CONNECTION_GET_SSL(s); + OCSP_BASICRESP *bs = NULL; + OCSP_SINGLERESP *sr = NULL; + OCSP_CERTID *cid = NULL; + OCSP_CERTID *sr_cert_id = NULL; + ASN1_OBJECT *cert_id_md_oid; + char cert_id_md_txt[OSSL_MAX_NAME_SIZE]; + EVP_MD *cert_id_md; + ASN1_INTEGER *respSerial; + ASN1_OCTET_STRING *respIssuerNameHash = NULL; + ASN1_OCTET_STRING *certIssuerNameHash = NULL; + const X509_NAME *certIssuerName; + unsigned char md[EVP_MAX_MD_SIZE]; + const ASN1_INTEGER *certSerial; + SSL_CTX *sctx = SSL_CONNECTION_GET_CTX(s); + + /* + * In TLSv1.3 the caller gives the index of the certificate for which the + * status message should be created. + * Prior to TLSv1.3 the chain index is 0 and the body should contain only + * the status of the server certificate itself. + */ + SSL_get0_chain_certs(ssl, &chain_certs); + + /* + * If the certificate chain was built, get the status message for the + * requested certificate specified by chainidx. + * SSL_get0_chain_certs provides certificate chain except the server cert. + * + * if chainidx = 0 the server certificate is requested + * if chainidx > 0 an intermediate certificate is requested + */ + if (chainidx == 0) + x = SSL_get_certificate(ssl); + else + x = sk_X509_value(chain_certs, chainidx - 1); + if (x == NULL) + return NULL; + + /* for a selfsigned certificate there will be no OCSP response */ + if (X509_self_signed(x, 0)) + return NULL; + + if ((resp = sk_OCSP_RESPONSE_value(s->ext.ocsp.resp_ex, chainidx)) != NULL) { + /* + * Find the corresponding single OCSP response by comparing the current + * certificate's serial number, and the hash of the current certificate's + * issuer name, to the serial number and issuer name hash in each OCSP + * response received. + */ + if (OCSP_response_status(resp) == OCSP_RESPONSE_STATUS_SUCCESSFUL) { + /* + * Set a mark for the error queue here to be able to ignore errors + * happening because of test cases. + */ + ERR_set_mark(); + if (((bs = OCSP_response_get1_basic(resp)) != NULL) + && ((sr = OCSP_resp_get0(bs, 0)) != NULL)) { + /* use the first single response to get the algorithm used */ + cid = (OCSP_CERTID *)OCSP_SINGLERESP_get0_id(sr); + + /* determine the md algorithm which was used to create cert id */ + OCSP_id_get0_info(&respIssuerNameHash, &cert_id_md_oid, NULL, &respSerial, cid); + if (cert_id_md_oid != NULL) { + OBJ_obj2txt(cert_id_md_txt, sizeof(cert_id_md_txt), cert_id_md_oid, 0); + cert_id_md = EVP_MD_fetch(sctx->libctx, cert_id_md_txt, sctx->propq); + } else { + cert_id_md = EVP_MD_fetch(sctx->libctx, SN_sha1, sctx->propq); + } + + if (cert_id_md == NULL) { + OCSP_BASICRESP_free(bs); + ERR_clear_last_mark(); + return NULL; + } + + /* get serial number and issuer name hash of the certificate from the chain */ + certSerial = X509_get0_serialNumber(x); + certIssuerName = X509_get_issuer_name(x); + certIssuerNameHash = ASN1_OCTET_STRING_new(); + if (!X509_NAME_digest(certIssuerName, cert_id_md, md, &len) || + !(ASN1_OCTET_STRING_set(certIssuerNameHash, md, len))) { + ASN1_OCTET_STRING_free(certIssuerNameHash); + OCSP_BASICRESP_free(bs); + EVP_MD_free(cert_id_md); + ERR_clear_last_mark(); + return NULL; + } + + num = OCSP_resp_count(bs); + for (i = 0; i < num; i++) { + sr = OCSP_resp_get0(bs, i); + + /* + * get the CertID from the OCSP response to compare it with the information + * from the certificate + */ + sr_cert_id = (OCSP_CERTID *)OCSP_SINGLERESP_get0_id(sr); + + OCSP_id_get0_info(&respIssuerNameHash, NULL, NULL, &respSerial, sr_cert_id); + + if (!ASN1_INTEGER_cmp(certSerial, respSerial) && + !ASN1_OCTET_STRING_cmp(certIssuerNameHash, respIssuerNameHash)) + break; + } + + ASN1_OCTET_STRING_free(certIssuerNameHash); + OCSP_BASICRESP_free(bs); + EVP_MD_free(cert_id_md); + + /* + * if we did not find the right single response we return NULL here + */ + if (i == num) + resp = NULL; + } + + /* + * in a test case a response without a basic response is used the error set + * could be ignored here + */ + ERR_pop_to_mark(); + } + } +#endif + + return resp; +} + static int do_compressed_cert(SSL_CONNECTION *sc) { /* If we negotiated RPK, we won't attempt to compress it */ @@ -4333,30 +4476,10 @@ CON_FUNC_RETURN tls_construct_new_session_ticket(SSL_CONNECTION *s, WPACKET *pkt * In TLSv1.3 this is called from the extensions code, otherwise it is used to * create a separate message. Returns 1 on success or 0 on failure. */ -int tls_construct_cert_status_body(SSL_CONNECTION *s, size_t chainidx, WPACKET *pkt) +int tls_construct_cert_status_body(SSL_CONNECTION *s, OCSP_RESPONSE *resp, WPACKET *pkt) { unsigned char *respder = NULL; int resplen = 0; -#ifndef OPENSSL_NO_OCSP - int i = 0, num = 0; - unsigned int len; - X509 *x = NULL; - STACK_OF(X509) *chain_certs = NULL; - SSL *ssl = SSL_CONNECTION_GET_SSL(s); - OCSP_RESPONSE *resp = NULL; - OCSP_BASICRESP *bs = NULL; - OCSP_SINGLERESP *sr = NULL; - OCSP_CERTID *cid = NULL; - OCSP_CERTID *sr_cert_id = NULL; - ASN1_OBJECT *cert_id_md_oid; - const EVP_MD *cert_id_md; - ASN1_INTEGER *respSerial; - ASN1_OCTET_STRING *respIssuerNameHash; - ASN1_OCTET_STRING *certIssuerNameHash; - const X509_NAME *certIssuerName; - unsigned char md[EVP_MAX_MD_SIZE]; - const ASN1_INTEGER *certSerial; -#endif if (!WPACKET_put_bytes_u8(pkt, s->ext.status_type)) { SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); @@ -4364,103 +4487,7 @@ int tls_construct_cert_status_body(SSL_CONNECTION *s, size_t chainidx, WPACKET * } #ifndef OPENSSL_NO_OCSP - /* - * In TLSv1.3 the caller gives the index of the certificate for which the - * status message should be created. - * Prior to TLSv1.3 the chain index is 0 and the body should contain only - * the status of the server certificate itself. - */ - SSL_get0_chain_certs(ssl, &chain_certs); - - /* - * if the certificate chain was built, get the status message for the - * requested certificate specified by chainidx SSL_get0_chain_certs - * contains certificate chain except the server cert - * - * if chainidx = 0 the server certificate is requested - * if chainidx > 0 an intermediate certificate is requested - */ - if (chain_certs != NULL && (int)chainidx <= sk_X509_num(chain_certs) && chainidx > 0) - x = sk_X509_value(chain_certs, (int)chainidx - 1); - else - x = SSL_get_certificate(ssl); - if (x == NULL) - return 0; - - /* for a selfsigned certificate there will be no OCSP response */ - if (X509_self_signed(x, 0)) - return 1; - - if ((resp = sk_OCSP_RESPONSE_value(s->ext.ocsp.resp_ex, (int)chainidx)) != NULL) { - /* - * check if its the right response in the case it is a successful response - * as not every time the issuer certificate is available the check just - * uses the issuer name and the serial number from the current certificate - */ - if (OCSP_response_status(resp) == OCSP_RESPONSE_STATUS_SUCCESSFUL) { - /* - * set a mark for the error queue her to be able to ignore errors - * happening because of test cases - */ - ERR_set_mark(); - if (((bs = OCSP_response_get1_basic(resp)) != NULL) - && ((sr = OCSP_resp_get0(bs, 0)) != NULL)) { - /* use the first single response to get the algorithm used */ - cid = (OCSP_CERTID *)OCSP_SINGLERESP_get0_id(sr); - - OCSP_id_get0_info(&respIssuerNameHash, &cert_id_md_oid, NULL, &respSerial, cid); - if (cert_id_md_oid != NULL) - cert_id_md = EVP_get_digestbyobj(cert_id_md_oid); - else - cert_id_md = EVP_sha1(); - - /* get serial number and issuer name hash of the certificate from the chain */ - certSerial = X509_get0_serialNumber(x); - certIssuerName = X509_get_issuer_name(x); - certIssuerNameHash = ASN1_OCTET_STRING_new(); - if (!X509_NAME_digest(certIssuerName, cert_id_md, md, &len) || - !(ASN1_OCTET_STRING_set(certIssuerNameHash, md, len))) { - ASN1_OCTET_STRING_free(certIssuerNameHash); - OCSP_BASICRESP_free(bs); - ERR_clear_last_mark(); - return 0; - } - - num = OCSP_resp_count(bs); - for (i = 0; i < num; i++) { - sr = OCSP_resp_get0(bs, i); - - /* determine the md algorithm which was used to create cert id */ - sr_cert_id = (OCSP_CERTID *)OCSP_SINGLERESP_get0_id(sr); - - OCSP_id_get0_info(&respIssuerNameHash, NULL, NULL, &respSerial, sr_cert_id); - - if (!ASN1_INTEGER_cmp(certSerial, respSerial) && - !ASN1_OCTET_STRING_cmp(certIssuerNameHash, respIssuerNameHash)) - break; - } - - ASN1_OCTET_STRING_free(certIssuerNameHash); - OCSP_BASICRESP_free(bs); - - /* - * if we did not find the right single response in the OCSP response we - * construct an empty message - */ - if (i == num) - resp = NULL; - } - - /* - * in a test case a response without a basic response is used the error set - * could be ignored here - */ - ERR_pop_to_mark(); - } - } - - if (resp != NULL) - resplen = i2d_OCSP_RESPONSE(resp, &respder); + resplen = i2d_OCSP_RESPONSE(resp, &respder); #endif if (!WPACKET_sub_memcpy_u24(pkt, respder, resplen)) { @@ -4475,7 +4502,14 @@ int tls_construct_cert_status_body(SSL_CONNECTION *s, size_t chainidx, WPACKET * CON_FUNC_RETURN tls_construct_cert_status(SSL_CONNECTION *s, WPACKET *pkt) { - if (!tls_construct_cert_status_body(s, 0, pkt)) { + OCSP_RESPONSE *resp; + + resp = ossl_get_ocsp_response(s, 0); + + if (resp == NULL) + return CON_FUNC_DONT_SEND; + + if (!tls_construct_cert_status_body(s, resp, pkt)) { /* SSLfatal() already called */ return CON_FUNC_ERROR; } diff --git a/test/recipes/ocsp-response.der b/test/recipes/ocsp-response.der index 31351a0e3c89c0611fcf1b81a3c0e7ba4e58986f..c3e195575606d9d415732293365fc49a4c222999 100644 GIT binary patch literal 1237 zc-k{)V!6o0$grS^<&;4a%P}@iZ8k<$R(1nMMwTX)?LeW;295I8ar5x<@e2qF31b&!Vq|J!z%X*5vVo$3Ji=8>EJ7>-=(-Jf**LY@JlekVGBR?r zGB7tWGBU6f_d5k2DwaR^=3zrmXns>mv+|+&8sZw?Hp)HjX#2(-bns}l()`B6>0dUi zajDv4;Q3%rf?rh4pPfOr?B}v3&5<^ru&V7^+TZPcw|t6Tbp)MW7&NV>oB#f*&OQ5V z?LYkdT=-D(&7P-0Yo<0NyhuB`;iCcXA$Q-F^=7X&N_mEQ8?}mmw-TNm=$re{&hW96 zl8fA`Gcs@DFVET})O5zd=yJkY`-he)wtbcz8B<@)u@FdD-P|zo={~+~*ZX35iSgzFW?%p_F*2fs6}EUaP!Q)eG&3+X zG%+$VvNSM_0>+9VSilr2fK^u$qY|>=jNpjiX8?+GF@a)a|18mKXV30BcS+%f^`h*Z z%+m9OnD)NQsI>Kc*f=Y-Q!z?Vv-{SfV-^3ld~1F4f4w+wY1pIJAv(&7^<>qTzY9NI zv0YN&dV^T?O@@27OINMrT9qLg5E{8PT5w-hN^Z(Tu~H?Sj)2_B?`2s$-P= z|Le5An$!mPXtSSGN}XXo`TnEXZw?-C<@TMuW&J50A+O|I%W%Ey8i4~@$C;QJ85kD_ z82A~;0z*!gk420{q@Z4JU2{g{3>Iarv#;&M&#K&I^aLh%ptLfJgn?KCb_F~j1;Q+> z2F#3%|FI?hT}rQRMZ{Q4e0|XQ%XYEjle0g)+S%swCu`<`6`U@t=|!vey72tk9k^tH;8X_DcWFZY=5HTK^Azv-LLv!0obfoIKQi!Yz`_w@f5`_h!_<8miu zEv?_*LsTT+PC4F}WU&0dXH}eUg3$7wdqxVs{%k#Z-mHuDbI_Bn6OjV<_V=cWu4X>> z-0S&Iwa~@-JP(Va=Xy<8sg=J@)Vr18bC8Fy!P5Qq+r#EQxx)Hx`TXf~tsky>yWCCc z^pYu+hYwG8ny&t8#i^;`W~|lG_hn?d;~(WzMxHL1Ie)L*jcZD+enkn(Wn3Tst225K Y60O&E;LU%JK!d$`UpKw{Rw|tU0Ql0`6#xJL literal 1517 zc-k{)VtvWQ$grS^^^rjn>peD3Z8k<$R(1nMMwTYl^FX1~22G4o3me-E8k-Hd4LI4D zLs{5_ncSTW{w! zC%WcMOW&9|fCz&`IzB6ntoL)Z3vw;D_=!MD#AZNlugo#CnML_s@ zMPs!7RD+mj8SC?u9z8bTW#iOp^Jx3d%gD&h%D~*j$jD&$*L=nGC7w$^>vu{$UufR- zs_sZ&Xg1rTGsh1sw_;a$t5x*1pm*~umh#Wq{~VS`eF#uczQ3pP;h!m+9p#>7U+A;m zye+-uxth1*(v1f+3Qm1fJGkxnnN*%NYZR|IIqWeL_mETEFwZSso)I$oCUfuqFB@mQ%%x0L8O9xYe+u{tJ~{T?GmO>U zptUTyCiQ1~iN}>?ZW}BZc=xG%ONx6qufg-0SKI5%S7vOjpRU{#d)BJ><=nAX#;GqC z`k1n~>6s;k2ww1WPWY9mIyWn;$aEW*s)6{s(^<2YWUg4eV?)D3mao>67Bn$WHE3d< z2*kYxO-!W=n3))v5Mk|(9A+%63t@$k03r z$Tb6UO`zPtk)(n7O9?qd7{RHCp8+V&#RN)DduJTD{lM`QUsH(9#2Z?LSL%K<2=9JA z(a3zpX_e{~t=DB&9+bUt_0FR%^VJEAz1Aztu9O{1oKVKlzOUVIyJi(z15@_1^QPg- z#cQJ;|Nbeaa&}ex+lx{Y*pl;38#MFXjwmwoP&oR1$FuU#{hFs9$tyg>5DZw=`zITlgu}(_PHd(?dx|xuZEFZS$->E6b)&IP4R2xx;HxX1x>lme-tj zcWT|4r>7GA=;_t9Keis&GwWaJ%^%^(Zl-b@j-6lem3dC(o4p@jh0JIAx6o)^`^mH| z`?ps{)$q*}53se`v~!k#lLiF0Fpn{Mf-oOEu;*F*7nSF4i~D1xJ;v zFbk6bg8?^)$@F6RYn$jJR_BT^?`zO%VX9rZqnR -- 2.47.3