From 2f275fb338f0a14add0a20534058cca914aa555f Mon Sep 17 00:00:00 2001 From: Remi Tricot-Le Breton Date: Tue, 20 Dec 2022 11:11:16 +0100 Subject: [PATCH] REGTESTS: ssl: Add tests for ocsp auto update mechanism Tests a subpart of the ocsp auto update feature. It will mainly focus on the 'auto' mode since the 'on' one relies strongly on timers way too long to be used in a regtest context. --- reg-tests/ssl/ocsp_auto_update.vtc | 245 ++++++++++++++++++ reg-tests/ssl/ocsp_update/index.txt | 2 + .../multicert/server_ocsp.pem.ecdsa | 33 +++ .../multicert/server_ocsp.pem.ecdsa.issuer | 30 +++ .../multicert/server_ocsp.pem.ecdsa.ocsp | Bin 0 -> 2281 bytes .../ocsp_update/multicert/server_ocsp.pem.rsa | 56 ++++ .../multicert/server_ocsp.pem.rsa.issuer | 30 +++ .../multicert/server_ocsp.pem.rsa.ocsp | Bin 0 -> 2298 bytes .../ssl/ocsp_update/multicert_ecdsa.crt-list | 1 + .../multicert_ecdsa_no_update.crt-list | 1 + .../multicert_no_ocsp/server_ocsp_ecdsa.pem | 63 +++++ .../multicert_no_ocsp/server_ocsp_rsa.pem | 86 ++++++ .../ssl/ocsp_update/multicert_rsa.crt-list | 1 + .../ssl/ocsp_update/ocsp.haproxy.com.pem | 84 ++++++ .../ssl/ocsp_update/ocsp_update_rootca.crt | 30 +++ 15 files changed, 662 insertions(+) create mode 100644 reg-tests/ssl/ocsp_auto_update.vtc create mode 100644 reg-tests/ssl/ocsp_update/index.txt create mode 100644 reg-tests/ssl/ocsp_update/multicert/server_ocsp.pem.ecdsa create mode 100644 reg-tests/ssl/ocsp_update/multicert/server_ocsp.pem.ecdsa.issuer create mode 100644 reg-tests/ssl/ocsp_update/multicert/server_ocsp.pem.ecdsa.ocsp create mode 100644 reg-tests/ssl/ocsp_update/multicert/server_ocsp.pem.rsa create mode 100644 reg-tests/ssl/ocsp_update/multicert/server_ocsp.pem.rsa.issuer create mode 100644 reg-tests/ssl/ocsp_update/multicert/server_ocsp.pem.rsa.ocsp create mode 100644 reg-tests/ssl/ocsp_update/multicert_ecdsa.crt-list create mode 100644 reg-tests/ssl/ocsp_update/multicert_ecdsa_no_update.crt-list create mode 100644 reg-tests/ssl/ocsp_update/multicert_no_ocsp/server_ocsp_ecdsa.pem create mode 100644 reg-tests/ssl/ocsp_update/multicert_no_ocsp/server_ocsp_rsa.pem create mode 100644 reg-tests/ssl/ocsp_update/multicert_rsa.crt-list create mode 100644 reg-tests/ssl/ocsp_update/ocsp.haproxy.com.pem create mode 100644 reg-tests/ssl/ocsp_update/ocsp_update_rootca.crt diff --git a/reg-tests/ssl/ocsp_auto_update.vtc b/reg-tests/ssl/ocsp_auto_update.vtc new file mode 100644 index 000000000..1dad5cfd8 --- /dev/null +++ b/reg-tests/ssl/ocsp_auto_update.vtc @@ -0,0 +1,245 @@ +#REGTEST_TYPE=slow + +# broken with BoringSSL. + +# This reg-test focuses on the OCSP response auto-update functionality. It does +# not test the full scope of the feature because most of it is based on +# expiration times and long delays between updates of valid OCSP responses. +# Automatic update of valid OCSP responses loaded during init will not be +# tested because by design, such a response would no be automatically updated +# until init+1H. +# +# This test will then focus on certificates that have a specified OCSP URI but +# no known OCSP response. For those certificates, OCSP requests are sent as +# soon as possible by the update task. +# +# The ocsp responder used in all the tests will be an openssl using the +# certificate database in ocsp_update/index.txt. It will listen on port 12346 +# which is not the same as the one specified in the certificates' OCSP URI +# which point to port 12345. The link from port 12345 to port 12346 will be +# ensured through HAProxy instances that will enable logs, later used as a +# synchronization mean. +# +# Unfortunately some arbitrary "sleep" calls are still needed to leave some +# time for the ocsp update task to actually process the ocsp responses and +# reinsert them into the tree. This explains why the test's mode is set to +# "slow". +# +# If this test does not work anymore: +# - Check that you have openssl + +varnishtest "Test the OCSP auto update feature" +feature cmd "$HAPROXY_PROGRAM -cc 'version_atleast(2.7-dev0)'" +feature cmd "$HAPROXY_PROGRAM -cc 'feature(OPENSSL) && !ssllib_name_startswith(BoringSSL) && openssl_version_atleast(1.1.1)'" +feature cmd "command -v openssl" +feature ignore_unknown_macro + + +################### +# # +# FIRST TEST CASE # +# # +################### + +# No automatic update should occur in this test case since we load two already +# valid OCSP responses during init which have a "Next Update" date really far +# in the future. So they should only be updated after one hour. +# This test will only be the most basic one where we check that ocsp response +# loading still works as expected. + +haproxy h1 -conf { + global + tune.ssl.default-dh-param 2048 + tune.ssl.capture-buffer-size 1 + stats socket "${tmpdir}/h1/stats" level admin + crt-base ${testdir}/ocsp_update + + defaults + mode http + option httplog + log stderr local0 debug err + option logasap + timeout connect "${HAPROXY_TEST_TIMEOUT-5s}" + timeout client "${HAPROXY_TEST_TIMEOUT-5s}" + timeout server "${HAPROXY_TEST_TIMEOUT-5s}" + + frontend ssl-fe + bind "${tmpdir}/ssl.sock" ssl crt multicert/server_ocsp.pem ca-file ${testdir}/set_cafile_rootCA.crt verify none crt-ignore-err all + http-request return status 200 +} -start + + +# We should have two distinct ocsp responses known that were loaded at build time +haproxy h1 -cli { + send "show ssl ocsp-response" + expect ~ "Certificate ID key : 303b300906052b0e03021a050004148a83e0060faff709ca7e9b95522a2e81635fda0a0414f652b0e435d5ea923851508f0adbe92d85de007a02021015" + send "show ssl ocsp-response" + expect ~ "Certificate ID key : 303b300906052b0e03021a050004148a83e0060faff709ca7e9b95522a2e81635fda0a0414f652b0e435d5ea923851508f0adbe92d85de007a02021016" + + send "show ssl ocsp-response 303b300906052b0e03021a050004148a83e0060faff709ca7e9b95522a2e81635fda0a0414f652b0e435d5ea923851508f0adbe92d85de007a02021015" + expect ~ "Cert Status: revoked" + + send "show ssl ocsp-response 303b300906052b0e03021a050004148a83e0060faff709ca7e9b95522a2e81635fda0a0414f652b0e435d5ea923851508f0adbe92d85de007a02021016" + expect ~ "Cert Status: good" +} + +haproxy h1 -wait + + + +#################### +# # +# SECOND TEST CASE # +# # +#################### + +# This test will focus on two separate certificates that have the same OCSP uri +# (http://ocsp.haproxy.com:12345) but no OCSP response loaded at build time. +# The update mode is set to 'on' in the two crt-lists used. The two ocsp +# responses should then be fetched automatically after init. We use an http +# listener as a rebound on which http log is enabled towards Syslog_http. This +# ensures that two requests are sent by the ocsp auto update task and it +# enables to use a barrier to synchronize the ocsp task and the subsequent cli +# calls. Thanks to the barrier we know that when calling "show ssl +# ocsp-response" on the cli, the two answers should already have been received +# and processed. + +process p1 "openssl ocsp -index ${testdir}/ocsp_update/index.txt -rsigner ${testdir}/ocsp_update/ocsp.haproxy.com.pem -CA ${testdir}/ocsp_update/ocsp_update_rootca.crt -nrequest 2 -ndays 1 -port 12346 -timeout 5" -start + +barrier b1 cond 2 -cyclic + +syslog Syslog_http -level info { + recv + expect ~ "GET /MEMwQTA%2FMD0wOzAJBgUrDgMCGgUABBSKg%2BAGD6%2F3Ccp%2Bm5VSKi6BY1%2FaCgQU9lKw5DXV6pI4UVCPCtvpLYXeAHoCAhAV HTTP/1.1" + + recv + expect ~ "GET /MEMwQTA%2FMD0wOzAJBgUrDgMCGgUABBSKg%2BAGD6%2F3Ccp%2Bm5VSKi6BY1%2FaCgQU9lKw5DXV6pI4UVCPCtvpLYXeAHoCAhAW HTTP/1.1" + + barrier b1 sync +} -start + +haproxy h2 -conf { + global + tune.ssl.default-dh-param 2048 + tune.ssl.capture-buffer-size 1 + stats socket "${tmpdir}/h2/stats" level admin + crt-base ${testdir}/ocsp_update + + defaults + mode http + option httplog + log stderr local0 debug err + timeout connect "${HAPROXY_TEST_TIMEOUT-5s}" + timeout client "${HAPROXY_TEST_TIMEOUT-5s}" + timeout server "${HAPROXY_TEST_TIMEOUT-5s}" + + frontend ssl-rsa-fe + bind "${tmpdir}/ssl2.sock" ssl crt-list ${testdir}/ocsp_update/multicert_rsa.crt-list ca-file ${testdir}/set_cafile_rootCA.crt verify none crt-ignore-err all + http-request return status 200 + + frontend ssl-ecdsa-fe + bind "${tmpdir}/ssl3.sock" ssl crt-list ${testdir}/ocsp_update/multicert_ecdsa.crt-list ca-file ${testdir}/set_cafile_rootCA.crt verify none crt-ignore-err all + http-request return status 200 + + listen http_rebound_lst + mode http + option httplog + log ${Syslog_http_addr}:${Syslog_http_port} local0 + bind "127.0.0.1:12345" + server s1 "127.0.0.1:12346" +} -start + +barrier b1 sync + +shell "sleep 1" + +# We should have two distinct ocsp IDs known that were loaded at build time and +# the responses' contents should have been filled automatically by the ocsp +# update task after init +haproxy h2 -cli { + send "show ssl ocsp-response" + expect ~ "Certificate ID key : 303b300906052b0e03021a050004148a83e0060faff709ca7e9b95522a2e81635fda0a0414f652b0e435d5ea923851508f0adbe92d85de007a02021015" + send "show ssl ocsp-response" + expect ~ "Certificate ID key : 303b300906052b0e03021a050004148a83e0060faff709ca7e9b95522a2e81635fda0a0414f652b0e435d5ea923851508f0adbe92d85de007a02021016" + + send "show ssl ocsp-response 303b300906052b0e03021a050004148a83e0060faff709ca7e9b95522a2e81635fda0a0414f652b0e435d5ea923851508f0adbe92d85de007a02021015" + expect ~ "Cert Status: revoked" + + send "show ssl ocsp-response 303b300906052b0e03021a050004148a83e0060faff709ca7e9b95522a2e81635fda0a0414f652b0e435d5ea923851508f0adbe92d85de007a02021016" + expect ~ "Cert Status: revoked" +} + +haproxy h2 -wait +process p1 -wait -expect-exit 0 + + +################### +# # +# THIRD TEST CASE # +# # +################### + +# This test will be roughly the same as the second one but one of the crt-lists +# will not enable ocsp-update on its certificate. Only one request should then +# be sent. + +process p2 "openssl ocsp -index ${testdir}/ocsp_update/index.txt -rsigner ${testdir}/ocsp_update/ocsp.haproxy.com.pem -CA ${testdir}/ocsp_update/ocsp_update_rootca.crt -nrequest 1 -ndays 1 -port 12346 -timeout 5" -start + +barrier b2 cond 2 -cyclic + +syslog Syslog_http2 -level info { + recv + expect ~ "GET /MEMwQTA%2FMD0wOzAJBgUrDgMCGgUABBSKg%2BAGD6%2F3Ccp%2Bm5VSKi6BY1%2FaCgQU9lKw5DXV6pI4UVCPCtvpLYXeAHoCAhAV HTTP/1.1" + + barrier b2 sync +} -start + +haproxy h3 -conf { + global + tune.ssl.default-dh-param 2048 + tune.ssl.capture-buffer-size 1 + stats socket "${tmpdir}/h3/stats" level admin + crt-base ${testdir}/ocsp_update + + defaults + mode http + option httplog + log stderr local0 debug err + timeout connect "${HAPROXY_TEST_TIMEOUT-5s}" + timeout client "${HAPROXY_TEST_TIMEOUT-5s}" + timeout server "${HAPROXY_TEST_TIMEOUT-5s}" + + frontend ssl-rsa-fe + bind "${tmpdir}/ssl4.sock" ssl crt-list ${testdir}/ocsp_update/multicert_rsa.crt-list ca-file ${testdir}/set_cafile_rootCA.crt verify none crt-ignore-err all + http-request return status 200 + + frontend ssl-ecdsa-fe + bind "${tmpdir}/ssl5.sock" ssl crt-list ${testdir}/ocsp_update/multicert_ecdsa_no_update.crt-list ca-file ${testdir}/set_cafile_rootCA.crt verify none crt-ignore-err all + http-request return status 200 + + listen http_rebound_lst + mode http + option httplog + log ${Syslog_http2_addr}:${Syslog_http2_port} local0 + bind "127.0.0.1:12345" + server s1 "127.0.0.1:12346" +} -start + +barrier b2 sync + +shell "sleep 1" + +# We should have a single ocsp ID known that was loaded at build time and the +# response should be filled + haproxy h3 -cli { + send "show ssl ocsp-response" + expect ~ "Certificate ID key : 303b300906052b0e03021a050004148a83e0060faff709ca7e9b95522a2e81635fda0a0414f652b0e435d5ea923851508f0adbe92d85de007a02021015" + send "show ssl ocsp-response" + expect !~ "Certificate ID key : 303b300906052b0e03021a050004148a83e0060faff709ca7e9b95522a2e81635fda0a0414f652b0e435d5ea923851508f0adbe92d85de007a02021016" + + send "show ssl ocsp-response 303b300906052b0e03021a050004148a83e0060faff709ca7e9b95522a2e81635fda0a0414f652b0e435d5ea923851508f0adbe92d85de007a02021015" + expect ~ "Cert Status: revoked" +} + +haproxy h3 -wait +process p2 -wait diff --git a/reg-tests/ssl/ocsp_update/index.txt b/reg-tests/ssl/ocsp_update/index.txt new file mode 100644 index 000000000..111ea4745 --- /dev/null +++ b/reg-tests/ssl/ocsp_update/index.txt @@ -0,0 +1,2 @@ +R 20500410103904Z 221123104541Z 1015 unknown /C=FR/O=HAProxy Technologies/CN=rsa.haproxy.com +R 20500410103956Z 221123104430Z 1016 unknown /C=FR/O=HAProxy Technologies/CN=ecdsa.haproxy.com diff --git a/reg-tests/ssl/ocsp_update/multicert/server_ocsp.pem.ecdsa b/reg-tests/ssl/ocsp_update/multicert/server_ocsp.pem.ecdsa new file mode 100644 index 000000000..a04fd2ec0 --- /dev/null +++ b/reg-tests/ssl/ocsp_update/multicert/server_ocsp.pem.ecdsa @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +MIIEODCCAiCgAwIBAgICEBYwDQYJKoZIhvcNAQELBQAwPjELMAkGA1UEBhMCRlIx +HTAbBgNVBAoMFEhBUHJveHkgVGVjaG5vbG9naWVzMRAwDgYDVQQDDAdSb290IENB +MCAXDTIyMTEyMzEwMzk1NloYDzIwNTAwNDEwMTAzOTU2WjBIMQswCQYDVQQGEwJG +UjEdMBsGA1UECgwUSEFQcm94eSBUZWNobm9sb2dpZXMxGjAYBgNVBAMMEWVjZHNh +LmhhcHJveHkuY29tMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQB5Id0dJy6Vubt +/ICfwLOOwgvyeOHOvC/yrqU/NCBNDVZLcOXbncm8Lxzl9Rn2t0VV9pla82/Qlexu +2jhx8LD3du8AmEn/4tkJMz85Jv4TN/eY7Tsfbqy2NtX17eBWkDA/S1v+9uw9m7UJ +mzwHIkQHi4S+flXt2ZtQKwgmYcuFYsP6jSGjgbswgbgwMgYIKwYBBQUHAQEEJjAk +MCIGCCsGAQUFBzABhhZodHRwOi8vMTI3LjAuMC4xOjEyMzQ1MB0GA1UdDgQWBBTS +Tdzvp9SeMDDfWVNdLPzVCaE/oDBjBgNVHSMEXDBaoUKkQDA+MQswCQYDVQQGEwJG +UjEdMBsGA1UECgwUSEFQcm94eSBUZWNobm9sb2dpZXMxEDAOBgNVBAMMB1Jvb3Qg +Q0GCFB4L4lCTIAmZTjzoVXNPaWeDYX8XMA0GCSqGSIb3DQEBCwUAA4ICAQBsoRvT +LPipFUSvGWWFphrqhri40e6GEKio2RNrHSwq6PBPd+FAjIan1yoZX3C/I/octhoq +/jHAlCB5GQzU3R3M/gaCyDk4x3wbR52zSNzgyh464B7HwlNyC9jCeh3yB8ylUZCu +Lc8NRTYavceUoDq2ebO8wpWX0LBd0oh7hMcQzWQrmU1B0NYVsTn65Ogcfokz2r0M +A3YjwT8vH9i9QFx1Fxy4OYJJQmskKrwAQ+MEtyBJvck2nthZA7KNX+OxuJjOh+lW ++WpTudaoMUd188zHFFjeM4C40uPsePlf1gpdjuTdir1sIH8GNa9XP1wEtvD6mNFU +6KCFSuZSkBqo2iD6yYzsd1H2DSMVQL67ATP8zSMjEccDYwkO72BR3InxWDFnFEQN +wosdBFKqqKNKkkdSW1QUsVd90Bi5pHFW0l4FaDk2SJRfzwa1Dc+LfQv9Wf+LcENW +6HOjqcRdU1PU1evVmq5xoHRDovQGNCStfwX3eW+jnHFYqovg51g5pEPEsmQccJXj +DMCGoQjM+4i+R0GhyJZ/Kr2Lnj5RyT6RVK8hNCx5NjJBK5z/pJK9pbPGoS9fkK8N +iQvPgw2+Y3rcVKHUw2epz/2mEzDb4rRiSIOIeuHB4PBL41jUNPwSxkjtjkPwVMuU +TlD6A5wDj3Sq0B4MoxWgIOyWENABvGl+VBtDNQ== +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIBkWJB8IW867HHc2iB +7J714zyea0hVD1Z/MEuEyKRZ7aekbjEQKmUfc5MLlQS0nedCqmiLuXObG/PyxxWs +mWTeH5qhgYkDgYYABAHkh3R0nLpW5u38gJ/As47CC/J44c68L/KupT80IE0NVktw +5dudybwvHOX1Gfa3RVX2mVrzb9CV7G7aOHHwsPd27wCYSf/i2QkzPzkm/hM395jt +Ox9urLY21fXt4FaQMD9LW/727D2btQmbPAciRAeLhL5+Ve3Zm1ArCCZhy4Viw/qN +IQ== +-----END PRIVATE KEY----- diff --git a/reg-tests/ssl/ocsp_update/multicert/server_ocsp.pem.ecdsa.issuer b/reg-tests/ssl/ocsp_update/multicert/server_ocsp.pem.ecdsa.issuer new file mode 100644 index 000000000..bed206164 --- /dev/null +++ b/reg-tests/ssl/ocsp_update/multicert/server_ocsp.pem.ecdsa.issuer @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFGjCCAwKgAwIBAgIUHgviUJMgCZlOPOhVc09pZ4NhfxcwDQYJKoZIhvcNAQEL +BQAwPjELMAkGA1UEBhMCRlIxHTAbBgNVBAoMFEhBUHJveHkgVGVjaG5vbG9naWVz +MRAwDgYDVQQDDAdSb290IENBMB4XDTIxMDQyMjE0MDEyMFoXDTQ4MDkwNzE0MDEy +MFowPjELMAkGA1UEBhMCRlIxHTAbBgNVBAoMFEhBUHJveHkgVGVjaG5vbG9naWVz +MRAwDgYDVQQDDAdSb290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC +AgEAti+5onUeFJNyF5s6xlnBxDnFhw7Q5VbBestHeQttjBWN31zq5yaf/+CYXdu+ +lY6gNZj6JBiFJ5P7VXX3DqUIJBX6byXWfIUWM+auBAMKlTz0+hWrF/UxI/3uG67N ++Z6NVffEPYbA4Emqozr0DIicWorRyHnrhEQQP87xBCUboUr3QEkNngfiJ0fPm3fj +7HfQemGL2OnTA8qdy0q1l4aUhVr9bgedP2Klvs0XhbszCGLI0Gq5lyNadlH1MEiw +SXa9rklE6NCNcyamO7Wt8LVrg6pxopa7oGnkLbnjzSuE+xsN0isOLaHH5LfYg6gT +aAHpnBHiWuDZQIyzKc+Z37gNksd46/y9B+oBZoCTcYMOsn7PK+gPzTbu3ic4L9hO +WCsTV0tn+qUGj6/J98gRgvuvZGA7NPDKNZU5p34oyApBPBUOgpn6pCuT5NlkPYAe +Rp/ypiy5NCHp0JW3JWkJ4+wEasZM34TZUYrOsicA0GV4ZVkoQ3WYyAjmLvRXmo/w +Z3sSlmHvCg9MrQ9pk24+OtvCbii0bb/Zmlx0Y4lU5TogcuJffJDVbj7oxTc2gRmI +SIZsnYLv2qVoeBoMY5otj+ef0Y8v98mKCbiWe2MzBkC2h5wmwyWedez8RysTaFHS +Z4yOYoCsEAtCxnib9d5fXf0+6aOuFtKMknkuWbYj6En647ECAwEAAaMQMA4wDAYD +VR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAjVzxHzq/87uj24It5hYj4mq4 +ero0zix4fA4tJNuTpZ/5r7GUYaf/uT4xfDilBX2fGMsxVTxJC25KzhdFeTzg1Tde +/N0LAeLWHfe6jR/P5XDATD0ZA73DQALOxRM5uRMeWJDVaUeco/aXsdQaCz2STDI3 +h7VVFoaOlmxQW3BBEvg2VUp9DS2UjqqdwsUDtzwKfrmj/FqyBvGrvNeIMv28HCu7 +r1WE1Z0UEJhpc1BPbu7F/vl60gRF3bQjh2tL8pWThxTJe6Qy+pLoSShyi85AM9XK +scCmUtQWjy7KQDL8XVFvuCWvMzknZQjJcncbKddPaaSIDkKUpz9FDv+wSJj/LKf7 +bGSFPM6sblioLbLNJByRYI8G7VHvKDbUnYHbHp75NTGA2eDeNqx5bC2G/EJUTwLM +bfcZr9hv+z1QpvSLEpar30kJjc1QMQcf60ToGYIC93rsVAKou2GPGry4h/nzwro0 +jjFWNgORTXllfcQDbDNOPkV1kFFibPbAU4faZMgC+xwIwDBsndvcvXjLaRUa4fmw +1xNkOO5Lj9AuvTXdCc9yUXRzmPZhU6Q4YB2daWvs3vbMTtvkAXGyQL4b2HD+NYZs +cMUtbteGgQzwM1gpMBn4GX53vhlCXq28r3cH1/1tLDweglSrxyvZbB7pZU7BAmLk +TEj2fXcvdcX+TtYhC10= +-----END CERTIFICATE----- diff --git a/reg-tests/ssl/ocsp_update/multicert/server_ocsp.pem.ecdsa.ocsp b/reg-tests/ssl/ocsp_update/multicert/server_ocsp.pem.ecdsa.ocsp new file mode 100644 index 0000000000000000000000000000000000000000..793aff1738ef1a3a0e949c156b55a55fea7722ff GIT binary patch literal 2281 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