From 643c66a1e5dcc6f5a5f2d55cdf19e374c8f44c6d Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Thu, 26 Oct 2023 22:34:58 +0300 Subject: [PATCH] samples: net: sockets: http_server: Add HTTPS support The example certs are copied from echo-server sample application. Signed-off-by: Jukka Rissanen --- .../net/sockets/http_server/CMakeLists.txt | 34 +++++- samples/net/sockets/http_server/Kconfig | 33 +++++- samples/net/sockets/http_server/prj.conf | 23 ++-- .../net/sockets/http_server/sections-rom.ld | 1 + samples/net/sockets/http_server/src/ca.der | Bin 0 -> 783 bytes .../net/sockets/http_server/src/certificate.h | 50 +++++++++ .../net/sockets/http_server/src/dummy_psk.h | 14 +++ .../http_server/src/https-server-cert.der | Bin 0 -> 767 bytes .../http_server/src/https-server-key.der | Bin 0 -> 1218 bytes samples/net/sockets/http_server/src/main.c | 100 +++++++++++++++++- .../net/sockets/http_server/src/server.der | Bin 0 -> 693 bytes .../http_server/src/server_privkey.der | Bin 0 -> 1219 bytes 12 files changed, 238 insertions(+), 17 deletions(-) create mode 100644 samples/net/sockets/http_server/src/ca.der create mode 100644 samples/net/sockets/http_server/src/certificate.h create mode 100644 samples/net/sockets/http_server/src/dummy_psk.h create mode 100644 samples/net/sockets/http_server/src/https-server-cert.der create mode 100644 samples/net/sockets/http_server/src/https-server-key.der create mode 100644 samples/net/sockets/http_server/src/server.der create mode 100644 samples/net/sockets/http_server/src/server_privkey.der diff --git a/samples/net/sockets/http_server/CMakeLists.txt b/samples/net/sockets/http_server/CMakeLists.txt index f39e7edfa5071ca..4b538193ab76f18 100644 --- a/samples/net/sockets/http_server/CMakeLists.txt +++ b/samples/net/sockets/http_server/CMakeLists.txt @@ -7,6 +7,19 @@ find_package(Python REQUIRED COMPONENTS Interpreter) project(http_server) +if(CONFIG_NET_SOCKETS_SOCKOPT_TLS AND + CONFIG_MBEDTLS_KEY_EXCHANGE_PSK_ENABLED AND + (CONFIG_NET_SAMPLE_PSK_HEADER_FILE STREQUAL "dummy_psk.h")) + add_custom_target(development_psk + COMMAND ${CMAKE_COMMAND} -E echo "----------------------------------------------------------" + COMMAND ${CMAKE_COMMAND} -E echo "--- WARNING: Using dummy PSK! Only suitable for ---" + COMMAND ${CMAKE_COMMAND} -E echo "--- development. Set NET_SAMPLE_PSK_HEADER_FILE to use ---" + COMMAND ${CMAKE_COMMAND} -E echo "--- own pre-shared key. ---" + COMMAND ${CMAKE_COMMAND} -E echo "----------------------------------------------------------" + ) + add_dependencies(app development_psk) +endif() + option(INCLUDE_HTML_CONTENT "Include the HTML content" ON) target_sources(app PRIVATE src/main.c) @@ -22,4 +35,23 @@ generate_inc_file_for_target(app ${source_file_not_found} ${gen_dir}/not_found_p target_link_libraries(app PRIVATE zephyr_interface zephyr) zephyr_linker_sources(SECTIONS sections-rom.ld) -zephyr_iterable_section(NAME http_resource_desc_test_http_service KVMA RAM_REGION GROUP RODATA_REGION SUBALIGN 4) +zephyr_linker_section_ifdef(CONFIG_NET_SAMPLE_HTTPS_SERVICE NAME + http_resource_desc_test_https_service + KVMA RAM_REGION GROUP RODATA_REGION SUBALIGN 4) +zephyr_linker_section_ifdef(CONFIG_NET_SAMPLE_HTTP_SERVICE NAME + http_resource_desc_test_http_service + KVMA RAM_REGION GROUP RODATA_REGION SUBALIGN 4) + +foreach(inc_file + ca.der + server.der + server_privkey.der + https-server-cert.der + https-server-key.der + ) + generate_inc_file_for_target( + app + src/${inc_file} + ${gen_dir}/${inc_file}.inc + ) +endforeach() diff --git a/samples/net/sockets/http_server/Kconfig b/samples/net/sockets/http_server/Kconfig index 5e949bfc1531a1a..90104be01fa19ab 100644 --- a/samples/net/sockets/http_server/Kconfig +++ b/samples/net/sockets/http_server/Kconfig @@ -5,8 +5,37 @@ mainmenu "HTTP2 server sample application" -config NET_HTTP_SERVER_SERVICE_PORT +config NET_SAMPLE_HTTP_SERVICE + bool "Enable http service" + default y + +config NET_SAMPLE_HTTP_SERVER_SERVICE_PORT int "Port number for http service" - default 8080 + default 80 + depends on NET_SAMPLE_HTTP_SERVICE + +config NET_SAMPLE_HTTPS_SERVICE + bool "Enable https service" + depends on NET_SOCKETS_SOCKOPT_TLS || TLS_CREDENTIALS + +config NET_SAMPLE_HTTPS_SERVER_SERVICE_PORT + int "Port number for https service" + default 443 + depends on NET_SAMPLE_HTTPS_SERVICE + +config NET_SAMPLE_PSK_HEADER_FILE + string "Header file containing PSK" + default "dummy_psk.h" + depends on MBEDTLS_KEY_EXCHANGE_PSK_ENABLED + help + Name of a header file containing a + pre-shared key. + +config NET_SAMPLE_CERTS_WITH_SC + bool "Signed certificates" + depends on NET_SOCKETS_SOCKOPT_TLS + help + Enable this flag, if you are interested to run this + application with signed certificates and keys. source "Kconfig.zephyr" diff --git a/samples/net/sockets/http_server/prj.conf b/samples/net/sockets/http_server/prj.conf index f5ccce4ab4ed9a4..0c0553bd90d66c3 100644 --- a/samples/net/sockets/http_server/prj.conf +++ b/samples/net/sockets/http_server/prj.conf @@ -8,6 +8,7 @@ CONFIG_INIT_STACKS=y CONFIG_POSIX_MAX_FDS=32 CONFIG_POSIX_API=y CONFIG_FDTABLE=y +CONFIG_NET_SOCKETS_POLL_MAX=32 # Eventfd CONFIG_EVENTFD=y @@ -34,14 +35,15 @@ CONFIG_HTTP_SERVER=y # Network buffers CONFIG_NET_PKT_RX_COUNT=16 CONFIG_NET_PKT_TX_COUNT=16 -CONFIG_NET_BUF_RX_COUNT=64 -CONFIG_NET_BUF_TX_COUNT=64 +CONFIG_NET_BUF_RX_COUNT=128 +CONFIG_NET_BUF_TX_COUNT=128 CONFIG_NET_CONTEXT_NET_PKT_POOL=y # IP address options CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=3 CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=4 CONFIG_NET_MAX_CONTEXTS=32 +CONFIG_NET_MAX_CONN=32 # Network address config CONFIG_NET_CONFIG_SETTINGS=y @@ -53,13 +55,16 @@ CONFIG_NET_CONFIG_MY_IPV4_GW="192.0.2.2" CONFIG_NET_CONFIG_MY_IPV6_ADDR="2001:db8::1" CONFIG_NET_CONFIG_PEER_IPV6_ADDR="2001:db8::2" -# TLS Options (commented-out for regular TCP) -#CONFIG_TLS_CREDENTIALS=y -#CONFIG_NET_SOCKETS_SOCKOPT_TLS=y -#CONFIG_NET_SOCKETS_TLS_MAX_CONTEXTS=3 -#CONFIG_TLS_MAX_CREDENTIALS_NUMBER=5 -#CONFIG_MBEDTLS_ENABLE_HEAP=y -#CONFIG_MBEDTLS_HEAP_SIZE=44000 +# TLS configuration +CONFIG_MBEDTLS=y +CONFIG_MBEDTLS_BUILTIN=y +CONFIG_MBEDTLS_ENABLE_HEAP=y +CONFIG_MBEDTLS_HEAP_SIZE=60000 +CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=2048 +CONFIG_NET_SOCKETS_SOCKOPT_TLS=y +CONFIG_NET_SOCKETS_TLS_MAX_CONTEXTS=6 +CONFIG_TLS_CREDENTIALS=y +CONFIG_TLS_MAX_CREDENTIALS_NUMBER=5 # Networking tweaks # Required to handle large number of consecutive connections, diff --git a/samples/net/sockets/http_server/sections-rom.ld b/samples/net/sockets/http_server/sections-rom.ld index 512251641b2ad2b..09a6b9398f6706a 100644 --- a/samples/net/sockets/http_server/sections-rom.ld +++ b/samples/net/sockets/http_server/sections-rom.ld @@ -1,3 +1,4 @@ #include ITERABLE_SECTION_ROM(http_resource_desc_test_http_service, 4) +ITERABLE_SECTION_ROM(http_resource_desc_test_https_service, 4) diff --git a/samples/net/sockets/http_server/src/ca.der b/samples/net/sockets/http_server/src/ca.der new file mode 100644 index 0000000000000000000000000000000000000000..b1d3e097cadcea344d9b172b4a540ddd57dae71e GIT binary patch literal 783 zcmXqLV&*nzV*I>-nTe5!NkrQ5sj}%5hTSG>R`_Lq-*o88&x{EMylk9WZ60mkc^MhG zSs4sO4228?*qB3En0Yu;D-v@Ha#Ecg4HU$AjSLJ74b2TKfs|#G1iz7?p{1dbkqJ}) zhrT98C1eX2Ss9p{82K51ZsKBUVq|34wV`KTh|5~Fr}HK2;*u6Ee|T=)1e1!xi;caz z-Z<-YoAX;FSjv^Y>1nk1mK&woeDe9}%lcDyWX(QRwR~w^i;nQdPj^{11~o3t{h`P~MygMx|HOPX%tXsnEOA>fFBedeU33Yx~_59X84`-hUap=ymBqc8_=e zXJ0AHhzvS^yv$SG(`m0>x{E^8F`u&eJ(a)Lt@-kAO77R3LtU3@X0!0{EwG4T-8z42 z@`rzOzWi!tInektcgxn$j9`t&6D!)di-I|~?Tzus+tW+_OU zBOPJG^i2Jk=Y-?xkG9WkdAoLt*%`^MJMCs?Hr>7-wUvpPk%4h>utA`KEHKPv`B=nQ zL;`E>*2qT+TP*1l-lo3#WLEad32P1HLDI@B5(Z)o*cI@D6bLgi{%2t|UYw?JA%`W?xT7A$?XFl} zX+CG(vMCB{-fr{pd|lDHbz#Hxjy)3f&IdyKXSc+pI<5$Nwf$h;y-8pD0*`A~XmRRD S-t=3uW{SGEL-b8SsSE(&xI?V~ literal 0 HcmV?d00001 diff --git a/samples/net/sockets/http_server/src/certificate.h b/samples/net/sockets/http_server/src/certificate.h new file mode 100644 index 000000000000000..52a3fa9c8ea18f8 --- /dev/null +++ b/samples/net/sockets/http_server/src/certificate.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __CERTIFICATE_H__ +#define __CERTIFICATE_H__ + +enum tls_tag { + /** The Certificate Authority public key */ + HTTP_SERVER_CA_CERTIFICATE_TAG, + /** Used for both the public and private server keys */ + HTTP_SERVER_CERTIFICATE_TAG, + /** Used for both the public and private client keys */ + HTTP_SERVER_CLIENT_CERTIFICATE_TAG, + PSK_TAG, +}; + +#if !defined(CONFIG_NET_SAMPLE_CERTS_WITH_SC) +static const unsigned char server_certificate[] = { +#include "https-server-cert.der.inc" +}; + +/* This is the private key in pkcs#8 format. */ +static const unsigned char private_key[] = { +#include "https-server-key.der.inc" +}; + +#else + +static const unsigned char ca_certificate[] = { +#include "ca.der.inc" +}; + +static const unsigned char server_certificate[] = { +#include "server.der.inc" +}; + +/* This is the private key in pkcs#8 format. */ +static const unsigned char private_key[] = { +#include "server_privkey.der.inc" +}; +#endif + +#if defined(CONFIG_MBEDTLS_KEY_EXCHANGE_PSK_ENABLED) +#include CONFIG_NET_SAMPLE_PSK_HEADER_FILE +#endif + +#endif /* __CERTIFICATE_H__ */ diff --git a/samples/net/sockets/http_server/src/dummy_psk.h b/samples/net/sockets/http_server/src/dummy_psk.h new file mode 100644 index 000000000000000..e67107266fda45c --- /dev/null +++ b/samples/net/sockets/http_server/src/dummy_psk.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2019 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __DUMMY_PSK_H__ +#define __DUMMY_PSK_H__ + +static const unsigned char psk[] = {0x01, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, +0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}; +static const char psk_id[] = "PSK_identity"; + +#endif /* __DUMMY_PSK_H__ */ diff --git a/samples/net/sockets/http_server/src/https-server-cert.der b/samples/net/sockets/http_server/src/https-server-cert.der new file mode 100644 index 0000000000000000000000000000000000000000..bfcb335e31c8c37fd5c964276c42a3554abc3f4e GIT binary patch literal 767 zcmXqLV)|{+#Q1mtGZP~d6DPwv0r`WU3|LlA&)ap-DdR6;hMk(GhDiIJZH z=nO8VCPqevV+_^27sZ{kS1zxd!{1vz@zQs9)6L?K?!L`s{JC+`NsopH^5?fNiT_~s zYX3vyA1jYOyCT`$q&%G?~a`7n%!BhJR8NxJ8LeAXMj8@Z}=^MXr+0N72oA?D7SXK(^cx;@x^i)my z(tRQdLffsY{O=sUs>_nL`zz1ck4Bc)1848L{c-s}-C57(WQW}Pc-Q0S^$(^seJXV> z`k(h(@=aU)(3QD6qd7+wwtn5CeIu6c$fmF-&;}Dq9gkyFl=eiRu;X}cf0k(j@>)) zviDuwwgnHgTeUi?cV#qa|IX8EcwRM~bF02W-`hPi@~2*nwpEi<6R~VsvBJr1b>w9C z=I@pJi?_zcR{Tp^^LD$O*V@A~EolM$ literal 0 HcmV?d00001 diff --git a/samples/net/sockets/http_server/src/https-server-key.der b/samples/net/sockets/http_server/src/https-server-key.der new file mode 100644 index 0000000000000000000000000000000000000000..5a4d67372ea41873b1c69e5e9371f6f9d2c5a4bd GIT binary patch literal 1218 zcmV;z1U>sOf&{(-0RS)!1_>&LNQUrs4#*Aqyhl|0)hbn0LB1&4bc}v zYpJJsoDYq6k<#}^HM1Au-R*4w`LUA8NPyrU&$pys@HXnd;WPND#pcu*i-IND8FX-Y z?8a!x@6H;j@V5aqk^j?mZUVXnnkuZ%BEKsi!E!hvHR{@L-DjdJ88{gZMA30Lv~4DZ z*2ccUZ#?d=lspAiPOVdci_{}AX>uo%v^uOK=n$^;p9`jL({sug5z4-C09Gk9RLt5b zTP7))O<$p=xyviE4-fzZsSzwlv6-dHd}pP;6d)3}J9YgF3t-AMV@@HKpnBz{CM^S?O`maE}K1B+DL;kFThAp!#d009Dm0RaH7 z0UN?WjiWr2jsEeC*@o6{wYkmT#(VLVd8DRNY9H7lcyumSAs^->(9OQQ2?gklP=v-L`Gx}l}Epd9hrmzrWPB^HgY2; zPe4Raxg5}uhi0bmA2T-mx#s85P@au1X1#k-Aozb#I!LTKGTvnxtemE5>_qMcl?C!j z#SDE>AF8y#xd(^;D<~3x>O7vYf$#m);|U+hoAc_SeGMv2ZJY+*hf(xP2JocW!N5Gh>(fq?+ts}+mI(T~AV*HlNM#ec4c%-zD89*-5W zoj3kXL$XrmvJT)MNZtpI|8%|ly(cPqz-9@rTLkUAoS)`Hqn^ieJ#j$+4frJ&sg!>B1V-0 zfq+K~=0jzRp|HDkq&((1 z4^sTRXxZnW?SQnSc0LySo5cU}$nFBvF(&`13#hnd=8Im5cx&cpoOA&>{Ra-O67MCI z&{4EyJ}s*>fq2TSrW{4Sogwp8<_}h%jHv>FfdJOeMWN~48AK`JI_MJJDU z8=_kU!4}(t3vFMBFF}{=yak@NV(~@!ROfCqUOUOBjX|tSjTWBzA{$rPt$=l?X&Xg< zGCkVbG5nX724gmcghG9WqLN(tyh?m2u) #include +#include #include #include #include +#include -static uint16_t test_http_service_port = htons(CONFIG_NET_HTTP_SERVER_SERVICE_PORT); -HTTP_SERVICE_DEFINE(test_http_service, CONFIG_NET_CONFIG_MY_IPV4_ADDR, &test_http_service_port, 1, - 10, NULL); +#include +LOG_MODULE_REGISTER(net_http_server_sample, LOG_LEVEL_DBG); static uint8_t index_html_gz[] = { #include "index.html.gz.inc" }; +#if defined(CONFIG_NET_SAMPLE_HTTP_SERVICE) +static uint16_t test_http_service_port = CONFIG_NET_SAMPLE_HTTP_SERVER_SERVICE_PORT; +HTTP_SERVICE_DEFINE(test_http_service, CONFIG_NET_CONFIG_MY_IPV4_ADDR, &test_http_service_port, 1, + 10, NULL); + struct http_resource_detail_static index_html_gz_resource_detail = { .common = { .type = HTTP_RESOURCE_TYPE_STATIC, @@ -39,16 +45,100 @@ struct http_resource_detail_rest add_two_numbers_detail = { }; HTTP_RESOURCE_DEFINE(add_two_numbers, test_http_service, "/add", &add_two_numbers_detail); +#endif /* CONFIG_NET_SAMPLE_HTTP_SERVICE */ + +#if defined(CONFIG_NET_SAMPLE_HTTPS_SERVICE) +#include "certificate.h" + +static const sec_tag_t sec_tag_list_verify_none[] = { + HTTP_SERVER_CERTIFICATE_TAG, +#if defined(CONFIG_MBEDTLS_KEY_EXCHANGE_PSK_ENABLED) + PSK_TAG, +#endif + }; + +static uint16_t test_https_service_port = CONFIG_NET_SAMPLE_HTTPS_SERVER_SERVICE_PORT; +HTTPS_SERVICE_DEFINE(test_https_service, CONFIG_NET_CONFIG_MY_IPV4_ADDR, \ + &test_https_service_port, 1, 10, NULL, \ + sec_tag_list_verify_none, sizeof(sec_tag_list_verify_none)); + +static struct http_resource_detail_static index_html_gz_resource_detail_https = { + .common = { + .type = HTTP_RESOURCE_TYPE_STATIC, + .bitmask_of_supported_http_methods = BIT(HTTP_GET), + }, + .static_data = index_html_gz, + .static_data_len = sizeof(index_html_gz), +}; + +HTTP_RESOURCE_DEFINE(index_html_gz_resource_https, test_https_service, "/", + &index_html_gz_resource_detail_https); + +#endif /* CONFIG_NET_SAMPLE_HTTPS_SERVICE */ + +static void setup_tls(void) +{ +#if defined(CONFIG_NET_SAMPLE_HTTPS_SERVICE) +#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) + int err; + +#if defined(CONFIG_NET_SAMPLE_CERTS_WITH_SC) + err = tls_credential_add(HTTP_SERVER_CERTIFICATE_TAG, + TLS_CREDENTIAL_CA_CERTIFICATE, + ca_certificate, + sizeof(ca_certificate)); + if (err < 0) { + LOG_ERR("Failed to register CA certificate: %d", err); + } +#endif /* defined(CONFIG_NET_SAMPLE_CERTS_WITH_SC) */ + + err = tls_credential_add(HTTP_SERVER_CERTIFICATE_TAG, + TLS_CREDENTIAL_SERVER_CERTIFICATE, + server_certificate, + sizeof(server_certificate)); + if (err < 0) { + LOG_ERR("Failed to register public certificate: %d", err); + } + + err = tls_credential_add(HTTP_SERVER_CERTIFICATE_TAG, + TLS_CREDENTIAL_PRIVATE_KEY, + private_key, sizeof(private_key)); + if (err < 0) { + LOG_ERR("Failed to register private key: %d", err); + } + +#if defined(CONFIG_MBEDTLS_KEY_EXCHANGE_PSK_ENABLED) + err = tls_credential_add(PSK_TAG, + TLS_CREDENTIAL_PSK, + psk, + sizeof(psk)); + if (err < 0) { + LOG_ERR("Failed to register PSK: %d", err); + } + + err = tls_credential_add(PSK_TAG, + TLS_CREDENTIAL_PSK_ID, + psk_id, + sizeof(psk_id) - 1); + if (err < 0) { + LOG_ERR("Failed to register PSK ID: %d", err); + } +#endif /* defined(CONFIG_MBEDTLS_KEY_EXCHANGE_PSK_ENABLED) */ +#endif /* defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) */ +#endif /* defined(CONFIG_NET_SAMPLE_HTTPS_SERVICE) */ +} int main(void) { struct http_server_ctx ctx; + int server_fd; - int server_fd = http_server_init(&ctx); + setup_tls(); + server_fd = http_server_init(&ctx); if (server_fd < 0) { printf("Failed to initialize HTTP2 server\n"); - return -1; + return server_fd; } http_server_start(&ctx); diff --git a/samples/net/sockets/http_server/src/server.der b/samples/net/sockets/http_server/src/server.der new file mode 100644 index 0000000000000000000000000000000000000000..2b664a4bdb2ce64d9e2f92d88aa163d09fd0a073 GIT binary patch literal 693 zcmXqLV%liX#5j{lBd#AN85K^Mn-N{1_Kd8 zAp-$6=1>-99?sN?#N2|MRA)y61#w;@0|P@ta|25QLnDJI34S9(LrX&=BNM0qioTru zik^@kDXYAcf~pQKuP)zo+30&- zdsL`W?2aE_c8Ub||GIFX(J^B};2(|hvNbb#i*={IPiDy3J$tJ&ci@?4VMWV=4&VCe zed*i}zE-9BtHZF9qVy(`sT+oy{ksJnS;amZ=bj_*v&j0}v(Aq0#9UNL&?l7Jj%zSgmElFXmI`$*a$X#J<0sy{~v`PEt_XN{9Kv4~>gL4f1N!%QHKI zrQ!?c2J27zSdg}K;<*B6sk_I_f9h;pVn08MKhh_xqvX6o-GpVLVd2xY%A(A-@$z5W zA6>qdW5xTj>8pMAgl1+(2pQNNu1#6e`Z4|)(;s_#+qPfTUHR2lm>=v4@vgY1wJ2i3 MBGv5t+z!hv0H2m3zyJUM literal 0 HcmV?d00001 diff --git a/samples/net/sockets/http_server/src/server_privkey.der b/samples/net/sockets/http_server/src/server_privkey.der new file mode 100644 index 0000000000000000000000000000000000000000..2269293fe790f2276d24bb62e5347e2d6e5b9cdf GIT binary patch literal 1219 zcmV;!1U&mNf&{+;0RS)!1_>&LNQUrsW5^Br2+u}0)hbn0Nqb)D1e?a zp8&aImt7ZC<#fYJP1_)U~2r>KV;Sh&|uxsbgd+Wqim>*RQP17 zN+MEv!KZt7vePp6ZiiY`B3-!n^tlvMkNVKSfk9}HQT!-(cC48Vb1jwcV*qTso3%p= zQOxF6a;8$l+WAY$!e4q1-E5KRYMu^m^R8*?f@AY^R3J|xb1OnZyf)d%@7jF-n#bq{w z_V0-_an4m}EhQL0a0eyJLfgYUma~AbF4TKUx0e^de%Z>SRLcd3_W}a}009Dm0RaG! zDnpyyq31e$(;f_OH=9&LUdAGWmcg)_U4%E0mb$XCnj(|v8LAHpDTtP?vr%m06^$EJ zZpnTE;H3hyxV~h-V=#o!93n{(=c4FC>CFRKQ-|zrZ1|YaIP9Ik-N}{ zy2<3cWREtdn{P+&|LN!)p7{mIkU|!Zt`2R(;Pg#_N=yr%0Vbp2xs8}fq?+{ zc_*4}atI@kiq43z4w}weDdyNNVAbB4NkS;27(x^wl z9J%b|Zc?z3;9TOiJK1`TB(hH8#oFnIv_f(!BjM<1Tj6}HlvN;ezG8sA3fPIW5j-m0 zrtNkO#a&BWxBBTNvSLcW2Sl27{uX5dfq?+z1E4J%6tye))MkM!X;Uf?8bH~Szs0u6 z*)cirwi2tZjOy@!RH&>XpF<1p??`R2z_I*@ponczcJ58;AN9n=lmO7)}?cPxq}S7K$e| z%_M>YTZQ&9fy?JR7<=cE*V;m7sn;&-g+MhbC^OY4hZM-K;ApehPU?Alz@8rI#vgD* ziCnquP~ol5;^064q^Ud%WP58WWvLp2efKx^7FEPDLiY hlYUM(QL<8v)b323|FJ#?NUg;s4_k!y09su`yV-?yRI&g7 literal 0 HcmV?d00001