From 915776dc4ab9308a5c62c42e72b5bd15b7012753 Mon Sep 17 00:00:00 2001 From: Ryan Goldberg Date: Mon, 14 Aug 2023 13:51:00 -0400 Subject: [PATCH] debuginfod: PR28204 - RPM IMA per-file signature verification Recent versions of Fedora/RHEL include per-file cryptographic signatures in RPMs, not just an overall RPM signature. This work extends debuginfod client & server to extract, transfer, and verify those signatures. These allow clients to assure users that the downloaded files have not been corrupted since their original packaging. Downloads that fail the test are rejected. Clients may select a desired level of enforcement for sets of URLs in the DEBUGINFOD_URLS by inserting special markers ahead of them: ima:ignore pay no attention to absence or presence of signatures ima:enforcing require every file to be correctly signed The default is ima:ignore mode. In ima:enforcing mode, section queries are forced to be entire-file downloads, as it is not possible to crypto-verify just sections. IMA signatures are verified against a set of signing certificates. These are normally published by distributions. The environment variable $DEBUGINFOD_IMA_CERT_PATH contains a colon-separated path for finding DER or PEM formatted certificates / public keys. These certificates are assumed trusted. The profile.d scripts transcribe /etc/debuginfod/*.certdir files into that variable. As for implementation: * configure.ac: Add --enable-debuginfod-ima-verification parameter. Add --enable-default-ima-cert-path=PATH parameter. Check for libimaevm (using headers only). * config/Makefile.am: Install defaults into /etc files. * config/profile.{csh,sh}.in: Process defaults into env variables. * config/elfutils.spec.in: Add more buildrequires. * debuginfod/debuginfod.cxx (handle_buildid_r_match): Added extraction of the per-file IMA signature for the queried file and store in http header. (find_globbed_koji_filepath): New function. (parse_opt): New flag --koji-sigcache. * debuginfod/debuginfod-client.c (debuginfod_query_server): Added policy for validating IMA signatures (debuginfod_validate_imasig): New function, with friends. * debuginfod/debuginfod.h.in: Added DEBUGINFOD_IMA_CERT_PATH_ENV_VAR. * debuginfod/Makefile.am: Add linker flags for rpm and crypto. * doc/debuginfod-client-config.7: Document DEBUGINFOD_IMA_CERT_PATH, update DEBUGINFOD_URLS. * doc/debuginfod.8: Document --koji-sigcache. * doc/debuginfod-find.1, doc/debuginfod_find_debuginfo.3: Update SECURITY. * tests/run-debuginfod-ima-verification.sh: New test. * tests/debuginfod-ima: Some new files for the tests. * tests/Makefile.am: run/distribute them. Signed-off-by: Ryan Goldberg Signed-off-by: Frank Ch. Eigler --- NEWS | 5 + config/Makefile.am | 4 + config/elfutils.spec.in | 6 + config/profile.csh.in | 10 +- config/profile.sh.in | 9 +- configure.ac | 48 +- debuginfod/Makefile.am | 5 +- debuginfod/debuginfod-client.c | 497 +++++++++++++++++- debuginfod/debuginfod.cxx | 166 +++++- debuginfod/debuginfod.h.in | 1 + doc/debuginfod-client-config.7 | 33 ++ doc/debuginfod-find.1 | 16 +- doc/debuginfod.8 | 19 +- doc/debuginfod_find_debuginfo.3 | 32 +- tests/Makefile.am | 9 + .../koji/arch/hello-2.10-9.fc38.x86_64.rpm | Bin 0 -> 71621 bytes .../arch/hello-2.10-9.fc38.x86_64.rpm.sig | Bin 0 -> 13000 bytes tests/debuginfod-ima/koji/fedora-38-ima.pem | 4 + .../rhel9/hello2-1.0-1.x86_64.rpm | Bin 0 -> 11702 bytes tests/debuginfod-ima/rhel9/imacert.der | Bin 0 -> 913 bytes tests/run-debuginfod-ima-verification.sh | 181 +++++++ 21 files changed, 1011 insertions(+), 34 deletions(-) create mode 100644 tests/debuginfod-ima/koji/arch/hello-2.10-9.fc38.x86_64.rpm create mode 100644 tests/debuginfod-ima/koji/data/sigcache/keyid/arch/hello-2.10-9.fc38.x86_64.rpm.sig create mode 100644 tests/debuginfod-ima/koji/fedora-38-ima.pem create mode 100644 tests/debuginfod-ima/rhel9/hello2-1.0-1.x86_64.rpm create mode 100644 tests/debuginfod-ima/rhel9/imacert.der create mode 100755 tests/run-debuginfod-ima-verification.sh diff --git a/NEWS b/NEWS index 4549bb6ef..6f931bb51 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,8 @@ +Version 0.192 (one after 0.191) + +debuginfod: Add per-file signature verification for integrity + checking, using RPM IMA scheme from Fedora/RHEL. + Version 0.191 "Bug fixes in C major" libdw: dwarf_addrdie now supports binaries lacking a .debug_aranges diff --git a/config/Makefile.am b/config/Makefile.am index fd41997f1..e42aacee0 100644 --- a/config/Makefile.am +++ b/config/Makefile.am @@ -47,12 +47,16 @@ install-data-local: if [ -n "@DEBUGINFOD_URLS@" ]; then \ echo "@DEBUGINFOD_URLS@" > $(DESTDIR)$(sysconfdir)/debuginfod/elfutils.urls; \ fi + if [ -n "@DEBUGINFOD_IMA_CERT_PATH@" ]; then \ + echo "@DEBUGINFOD_IMA_CERT_PATH@" > $(DESTDIR)$(sysconfdir)/debuginfod/elfutils.certpath; \ + fi uninstall-local: rm -f $(DESTDIR)$(sysconfdir)/profile.d/debuginfod.sh rm -f $(DESTDIR)$(sysconfdir)/profile.d/debuginfod.csh rm -f $(DESTDIR)$(datadir)/fish/vendor_conf.d/debuginfod.fish rm -f $(DESTDIR)$(sysconfdir)/debuginfod/elfutils.urls + rm -f $(DESTDIR)$(sysconfdir)/debuginfod/elfutils.certpath -rmdir $(DESTDIR)$(sysconfdir)/debuginfod endif diff --git a/config/elfutils.spec.in b/config/elfutils.spec.in index 4d802a25a..460729972 100644 --- a/config/elfutils.spec.in +++ b/config/elfutils.spec.in @@ -43,6 +43,12 @@ BuildRequires: curl # For run-debuginfod-response-headers.sh test case BuildRequires: socat +# For debuginfod rpm IMA verification +BuildRequires: rpm-devel +BuildRequires: ima-evm-utils-devel +BuildRequires: openssl-devel +BuildRequires: rpm-sign + %define _gnu %{nil} %define _programprefix eu- diff --git a/config/profile.csh.in b/config/profile.csh.in index d962d969c..1da9626c7 100644 --- a/config/profile.csh.in +++ b/config/profile.csh.in @@ -4,13 +4,19 @@ # See also [man debuginfod-client-config] for other environment variables # such as $DEBUGINFOD_MAXSIZE, $DEBUGINFOD_MAXTIME, $DEBUGINFOD_PROGRESS. +set prefix="@prefix@" if (! $?DEBUGINFOD_URLS) then - set prefix="@prefix@" set DEBUGINFOD_URLS=`sh -c 'cat /dev/null "$0"/*.urls 2>/dev/null; :' "@sysconfdir@/debuginfod" | tr '\n' ' '` if ( "$DEBUGINFOD_URLS" != "" ) then setenv DEBUGINFOD_URLS "$DEBUGINFOD_URLS" else unset DEBUGINFOD_URLS endif - unset prefix + set DEBUGINFOD_IMA_CERT_PATH=`sh -c 'cat /dev/null "$0"/*.certpath 2>/dev/null; :' "@sysconfdir@/debuginfod" | tr '\n' ':'` + if ( "$DEBUGINFOD_IMA_CERT_PATH" != "" ) then + setenv DEBUGINFOD_IMA_CERT_PATH "$DEBUGINFOD_IMA_CERT_PATH" + else + unset DEBUGINFOD_IMA_CERT_PATH + endif endif +unset prefix diff --git a/config/profile.sh.in b/config/profile.sh.in index 84d3260dd..911c7a434 100644 --- a/config/profile.sh.in +++ b/config/profile.sh.in @@ -4,9 +4,14 @@ # See also [man debuginfod-client-config] for other environment variables # such as $DEBUGINFOD_MAXSIZE, $DEBUGINFOD_MAXTIME, $DEBUGINFOD_PROGRESS. +prefix="@prefix@" if [ -z "$DEBUGINFOD_URLS" ]; then - prefix="@prefix@" DEBUGINFOD_URLS=$(cat /dev/null "@sysconfdir@/debuginfod"/*.urls 2>/dev/null | tr '\n' ' ' || :) [ -n "$DEBUGINFOD_URLS" ] && export DEBUGINFOD_URLS || unset DEBUGINFOD_URLS - unset prefix fi + +if [ -z "$DEBUGINFOD_IMA_CERT_PATH" ]; then + DEBUGINFOD_IMA_CERT_PATH=$(cat "@sysconfdir@/debuginfod"/*.certpath 2>/dev/null | tr '\n' ':' || :) + [ -n "$DEBUGINFOD_IMA_CERT_PATH" ] && export DEBUGINFOD_IMA_CERT_PATH || unset DEBUGINFOD_IMA_CERT_PATH +fi +unset prefix diff --git a/configure.ac b/configure.ac index 2aa728bd1..5adf76672 100644 --- a/configure.ac +++ b/configure.ac @@ -671,6 +671,41 @@ case "$ac_cv_search__obstack_free" in esac AC_SUBST([obstack_LIBS]) +enable_ima_verification="x" +AC_CHECK_LIB(rpm, headerGet, [ + AC_CHECK_DECL(RPMSIGTAG_FILESIGNATURES, + [ + enable_ima_verification=$enable_ima_verification"rpm" + AC_SUBST(rpm_LIBS, '-lrpm -lrpmio') + ], + [], [#include ]) +]) + +dnl we use only the header, not the code of this library +AC_CHECK_HEADER(imaevm.h, [ + enable_ima_verification=$enable_ima_verification"imaevm" +]) + +AC_CHECK_LIB(crypto, EVP_MD_CTX_new, [ + enable_ima_verification=$enable_ima_verification"crypto" + AC_SUBST(crypto_LIBS, '-lcrypto') +]) + +AC_ARG_ENABLE(debuginfod-ima-verification, + [AS_HELP_STRING([--enable-debuginfod-ima-verification],[enable per-file signature verification])], + [want_ima_verification=$enableval],[want_ima_verification=auto]) + +debuginfod_ima_verification_enabled="no" +if test "x$want_ima_verification" = "xno"; then + enable_ima_verification=nope # indicate failure of prerequisites for AM_CONDITIONAL below +elif test "$enable_ima_verification" = "xrpmimaevmcrypto"; then + debuginfod_ima_verification_enabled="yes" + AC_DEFINE([ENABLE_IMA_VERIFICATION], [1], [Define if the required ima verification libraries are available]) +elif test "x$want_ima_verification" = "xyes"; then + AC_MSG_ERROR("missing prerequisites for ima verification") +fi +AM_CONDITIONAL([ENABLE_IMA_VERIFICATION],[test "$enable_ima_verification" = "xrpmimaevmcrypto"]) + dnl The directories with content. dnl Documentation. @@ -884,7 +919,17 @@ AC_ARG_ENABLE(debuginfod-urls, default_debuginfod_urls="${enableval}"; fi], [default_debuginfod_urls=""]) -AC_SUBST(DEBUGINFOD_URLS, $default_debuginfod_urls) +AC_SUBST(DEBUGINFOD_URLS, $default_debuginfod_urls) + +AC_ARG_ENABLE(debuginfod-ima-cert-path, + [AS_HELP_STRING([--enable-debuginfod-ima-cert-path@<:@=PATH@:>@],[add PATH to profile.d DEBUGINFOD_IMA_CERT_PATH])], + [if test "x${enableval}" = "xyes"; + then AC_MSG_ERROR([PATH required]) + elif test "x${enableval}" != "xno"; then + default_debuginfod_ima_cert_path="${enableval}"; + fi], + [default_debuginfod_ima_cert_path=""]) +AC_SUBST(DEBUGINFOD_IMA_CERT_PATH, $default_debuginfod_ima_cert_path) AC_CONFIG_FILES([config/profile.sh config/profile.csh config/profile.fish]) AC_OUTPUT @@ -924,6 +969,7 @@ AC_MSG_NOTICE([ libdebuginfod client support : ${enable_libdebuginfod} Debuginfod server support : ${enable_debuginfod} Default DEBUGINFOD_URLS : ${default_debuginfod_urls} + Debuginfod RPM sig checking : ${debuginfod_ima_verification_enabled} ${default_debuginfod_ima_cert_path} EXTRA TEST FEATURES (used with make check) have bunzip2 installed (required) : ${HAVE_BUNZIP2} diff --git a/debuginfod/Makefile.am b/debuginfod/Makefile.am index 125be97bb..5e4f9669d 100644 --- a/debuginfod/Makefile.am +++ b/debuginfod/Makefile.am @@ -70,7 +70,7 @@ bin_PROGRAMS += debuginfod-find endif debuginfod_SOURCES = debuginfod.cxx -debuginfod_LDADD = $(libdw) $(libelf) $(libeu) $(libdebuginfod) $(argp_LDADD) $(fts_LIBS) $(libmicrohttpd_LIBS) $(sqlite3_LIBS) $(libarchive_LIBS) -lpthread -ldl +debuginfod_LDADD = $(libdw) $(libelf) $(libeu) $(libdebuginfod) $(argp_LDADD) $(fts_LIBS) $(libmicrohttpd_LIBS) $(sqlite3_LIBS) $(libarchive_LIBS) $(rpm_LIBS) -lpthread -ldl debuginfod_find_SOURCES = debuginfod-find.c debuginfod_find_LDADD = $(libdw) $(libelf) $(libeu) $(libdebuginfod) $(argp_LDADD) $(fts_LIBS) @@ -97,7 +97,7 @@ libdebuginfod_so_LIBS = libdebuginfod_pic.a if DUMMY_LIBDEBUGINFOD libdebuginfod_so_LDLIBS = else -libdebuginfod_so_LDLIBS = -lpthread $(libcurl_LIBS) $(fts_LIBS) $(libelf) +libdebuginfod_so_LDLIBS = -lpthread $(libcurl_LIBS) $(fts_LIBS) $(libelf) $(crypto_LIBS) endif $(LIBDEBUGINFOD_SONAME): $(srcdir)/libdebuginfod.map $(libdebuginfod_so_LIBS) $(AM_V_CCLD)$(LINK) $(dso_LDFLAGS) -o $@ \ @@ -117,7 +117,6 @@ install: install-am libdebuginfod.so $(DESTDIR)$(libdir)/libdebuginfod-$(PACKAGE_VERSION).so ln -fs libdebuginfod-$(PACKAGE_VERSION).so $(DESTDIR)$(libdir)/$(LIBDEBUGINFOD_SONAME) ln -fs libdebuginfod-$(PACKAGE_VERSION).so $(DESTDIR)$(libdir)/libdebuginfod.so - uninstall: uninstall-am rm -f $(DESTDIR)$(libdir)/libdebuginfod-$(PACKAGE_VERSION).so rm -f $(DESTDIR)$(libdir)/$(LIBDEBUGINFOD_SONAME) diff --git a/debuginfod/debuginfod-client.c b/debuginfod/debuginfod-client.c index 4e7a8a2ad..f01d1f0e5 100644 --- a/debuginfod/debuginfod-client.c +++ b/debuginfod/debuginfod-client.c @@ -1,5 +1,5 @@ /* Retrieve ELF / DWARF / source files from the debuginfod. - Copyright (C) 2019-2021 Red Hat, Inc. + Copyright (C) 2019-2024 Red Hat, Inc. Copyright (C) 2021, 2022 Mark J. Wielaard This file is part of elfutils. @@ -47,6 +47,17 @@ #include #include +#ifdef ENABLE_IMA_VERIFICATION +#include +#include +#include +#include +#include +#include +#endif +typedef enum {ignore, enforcing, undefined} ima_policy_t; + + /* We might be building a bootstrap dummy library, which is really simple. */ #ifdef DUMMY_LIBDEBUGINFOD @@ -92,6 +103,7 @@ void debuginfod_end (debuginfod_client *c) { } #include #include #include +#include /* If fts.h is included before config.h, its indirect inclusions may not give us the right LFS aliases of these functions, so map them manually. */ @@ -130,6 +142,17 @@ libcurl_init(void) } } + +#ifdef ENABLE_IMA_VERIFICATION +struct public_key_entry +{ + struct public_key_entry *next; /* singly-linked list */ + uint32_t keyid; /* last 4 bytes of sha1 of public key */ + EVP_PKEY *key; /* openssl */ +}; +#endif + + struct debuginfod_client { /* Progress/interrupt callback function. */ @@ -164,8 +187,14 @@ struct debuginfod_client handle data, etc. So those don't have to be reparsed and recreated on each request. */ char * winning_headers; + +#ifdef ENABLE_IMA_VERIFICATION + /* IMA public keys */ + struct public_key_entry *ima_public_keys; +#endif }; + /* The cache_clean_interval_s file within the debuginfod cache specifies how frequently the cache should be cleaned. The file's st_mtime represents the time of last cleaning. */ @@ -225,6 +254,182 @@ struct handle_data size_t response_data_size; }; + + +#ifdef ENABLE_IMA_VERIFICATION + static inline unsigned char hex2dec(char c) + { + if (c >= '0' && c <= '9') return (c - '0'); + if (c >= 'a' && c <= 'f') return (c - 'a') + 10; + if (c >= 'A' && c <= 'F') return (c - 'A') + 10; + return 0; + } + + static inline ima_policy_t ima_policy_str2enum(const char* ima_pol) + { + if (NULL == ima_pol) return undefined; + if (0 == strcmp(ima_pol, "ignore")) return ignore; + if (0 == strcmp(ima_pol, "enforcing")) return enforcing; + return undefined; + } + + static inline const char* ima_policy_enum2str(ima_policy_t ima_pol) + { + switch (ima_pol) + { + case ignore: + return "ignore"; + case enforcing: + return "enforcing"; + case undefined: + return "undefined"; + } + return ""; + } + + +static uint32_t extract_skid_pk(EVP_PKEY *pkey) // compute keyid by public key hashing +{ + if (!pkey) return 0; + uint32_t keyid = 0; + X509_PUBKEY *pk = NULL; + const unsigned char *public_key = NULL; + int len; + if (X509_PUBKEY_set(&pk, pkey) && + X509_PUBKEY_get0_param(NULL, &public_key, &len, NULL, pk)) + { + uint8_t sha1[SHA_DIGEST_LENGTH]; + SHA1(public_key, len, sha1); + memcpy(&keyid, sha1 + 16, 4); + } + X509_PUBKEY_free(pk); + return ntohl(keyid); +} + + +static uint32_t extract_skid(X509* x509) // compute keyid from cert or its public key + { + if (!x509) return 0; + uint32_t keyid = 0; + // Attempt to get the skid from the certificate + const ASN1_OCTET_STRING *skid_asn1_str = X509_get0_subject_key_id(x509); + if (skid_asn1_str) + { + int skid_len = ASN1_STRING_length(skid_asn1_str); + memcpy(&keyid, ASN1_STRING_get0_data(skid_asn1_str) + skid_len - sizeof(keyid), sizeof(keyid)); + } + else // compute keyid ourselves by hashing public key + { + EVP_PKEY *pkey = X509_get0_pubkey(x509); + keyid = htonl(extract_skid_pk(pkey)); + } + return ntohl(keyid); + } + + +static void load_ima_public_keys (debuginfod_client *c) +{ + /* Iterate over the directories in DEBUGINFOD_IMA_CERT_PATH. */ + char *cert_paths = getenv(DEBUGINFOD_IMA_CERT_PATH_ENV_VAR); + if (cert_paths == NULL || cert_paths[0] == '\0') + return; + cert_paths = strdup(cert_paths); // Modified during tokenization + if (cert_paths == NULL) + return; + + char* cert_dir_path; + DIR *dp; + struct dirent *entry; + int vfd = c->verbose_fd; + + char *strtok_context = NULL; + for(cert_dir_path = strtok_r(cert_paths, ":", &strtok_context); + cert_dir_path != NULL; + cert_dir_path = strtok_r(NULL, ":", &strtok_context)) + { + dp = opendir(cert_dir_path); + if(!dp) continue; + while((entry = readdir(dp))) + { + // Only consider regular files with common x509 cert extensions + if(entry->d_type != DT_REG || 0 != fnmatch("*.@(der|pem|crt|cer|cert)", entry->d_name, FNM_EXTMATCH)) continue; + char certfile[PATH_MAX]; + strncpy(certfile, cert_dir_path, PATH_MAX - 1); + if(certfile[strlen(certfile)-1] != '/') certfile[strlen(certfile)] = '/'; + strncat(certfile, entry->d_name, PATH_MAX - strlen(certfile) - 1); + certfile[strlen(certfile)] = '\0'; + + FILE *cert_fp = fopen(certfile, "r"); + if(!cert_fp) continue; + + X509 *x509 = NULL; + EVP_PKEY *pkey = NULL; + char *fmt = ""; + // Attempt to read the fp as DER + if(d2i_X509_fp(cert_fp, &x509)) + fmt = "der "; + // Attempt to read the fp as PEM and assuming the key matches that of the signature add this key to be used + // Note we fseek since this is the second time we read from the fp + else if(0 == fseek(cert_fp, 0, SEEK_SET) && PEM_read_X509(cert_fp, &x509, NULL, NULL)) + fmt = "pem "; // PEM with full certificate + else if(0 == fseek(cert_fp, 0, SEEK_SET) && PEM_read_PUBKEY(cert_fp, &pkey, NULL, NULL)) + fmt = "pem "; // some PEM files have just a PUBLIC KEY in them + fclose(cert_fp); + + if (x509) + { + struct public_key_entry *ne = calloc(1, sizeof(struct public_key_entry)); + if (ne) + { + ne->key = X509_extract_key(x509); + ne->keyid = extract_skid(x509); + ne->next = c->ima_public_keys; + c->ima_public_keys = ne; + if (vfd >= 0) + dprintf(vfd, "Loaded %scertificate %s, keyid = %04x\n", fmt, certfile, ne->keyid); + } + X509_free (x509); + } + else if (pkey) + { + struct public_key_entry *ne = calloc(1, sizeof(struct public_key_entry)); + if (ne) + { + ne->key = pkey; // preserve refcount + ne->keyid = extract_skid_pk(pkey); + ne->next = c->ima_public_keys; + c->ima_public_keys = ne; + if (vfd >= 0) + dprintf(vfd, "Loaded %spubkey %s, keyid %04x\n", fmt, certfile, ne->keyid); + } + } + else + { + if (vfd >= 0) + dprintf(vfd, "Cannot load certificate %s\n", certfile); + } + } /* for each file in directory */ + closedir(dp); + } /* for each directory */ + + free(cert_paths); +} + + +static void free_ima_public_keys (debuginfod_client *c) +{ + while (c->ima_public_keys) + { + EVP_PKEY_free (c->ima_public_keys->key); + struct public_key_entry *oen = c->ima_public_keys->next; + free (c->ima_public_keys); + c->ima_public_keys = oen; + } +} +#endif + + + static size_t debuginfod_write_callback (char *ptr, size_t size, size_t nmemb, void *data) { @@ -861,6 +1066,198 @@ cache_find_section (const char *scn_name, const char *target_cache_dir, return rc; } + +#ifdef ENABLE_IMA_VERIFICATION +/* Extract the hash algorithm name from the signature header, of which + there are several types. The name will be used for openssl hashing + of the file content. The header doesn't need to be super carefully + parsed, because if any part of it is wrong, be it the hash + algorithm number or hash value or whatever, it will fail + computation or verification. Return NULL in case of error. */ +static const char* +get_signature_params(debuginfod_client *c, unsigned char *bin_sig) +{ + int hashalgo = 0; + + switch (bin_sig[0]) + { + case EVM_IMA_XATTR_DIGSIG: +#ifdef IMA_VERITY_DIGSIG /* missing on debian-i386 trybot */ + case IMA_VERITY_DIGSIG: +#endif + break; + default: + if (c->verbose_fd >= 0) + dprintf (c->verbose_fd, "Unknown ima digsig %d\n", (int)bin_sig[0]); + return NULL; + } + + switch (bin_sig[1]) + { + case DIGSIG_VERSION_2: + struct signature_v2_hdr hdr_v2; + memcpy(& hdr_v2, & bin_sig[1], sizeof(struct signature_v2_hdr)); + hashalgo = hdr_v2.hash_algo; + break; + default: + if (c->verbose_fd >= 0) + dprintf (c->verbose_fd, "Unknown ima signature version %d\n", (int)bin_sig[1]); + return NULL; + } + + switch (hashalgo) + { + case PKEY_HASH_SHA1: return "sha1"; + case PKEY_HASH_SHA256: return "sha256"; + // (could add many others from enum pkey_hash_algo) + default: + if (c->verbose_fd >= 0) + dprintf (c->verbose_fd, "Unknown ima pkey hash %d\n", hashalgo); + return NULL; + } +} + + +/* Verify given hash against given signature blob. Return 0 on ok, -errno otherwise. */ +static int +debuginfod_verify_hash(debuginfod_client *c, const unsigned char *hash, int size, + const char *hash_algo, unsigned char *sig, int siglen) +{ + int ret = -EBADMSG; + struct public_key_entry *pkey; + struct signature_v2_hdr hdr; + EVP_PKEY_CTX *ctx; + const EVP_MD *md; + + memcpy(&hdr, sig, sizeof(struct signature_v2_hdr)); /* avoid just aliasing */ + + if (c->verbose_fd >= 0) + dprintf (c->verbose_fd, "Searching for ima keyid %04x\n", ntohl(hdr.keyid)); + + /* Find the matching public key. */ + for (pkey = c->ima_public_keys; pkey != NULL; pkey = pkey->next) + if (pkey->keyid == ntohl(hdr.keyid)) break; + if (!pkey) + return -ENOKEY; + + if (!(ctx = EVP_PKEY_CTX_new(pkey->key, NULL))) + goto err; + if (!EVP_PKEY_verify_init(ctx)) + goto err; + if (!(md = EVP_get_digestbyname(hash_algo))) + goto err; + if (!EVP_PKEY_CTX_set_signature_md(ctx, md)) + goto err; + ret = EVP_PKEY_verify(ctx, sig + sizeof(hdr), + siglen - sizeof(hdr), hash, size); + if (ret == 1) + ret = 0; // success! + else if (ret == 0) + ret = -EBADMSG; + err: + EVP_PKEY_CTX_free(ctx); + return ret; +} + + + +/* Validate an IMA file signature. + * Returns 0 on signature validity, -EINVAL on signature invalidity, -ENOSYS on undefined imaevm machinery, + * -ENOKEY on key issues, or other -errno. + */ + +static int +debuginfod_validate_imasig (debuginfod_client *c, int fd) +{ + int rc = ENOSYS; + + EVP_MD_CTX *ctx = NULL; + if (!c || !c->winning_headers) + { + rc = -ENODATA; + goto exit_validate; + } + // Extract the HEX IMA-signature from the header + char* sig_buf = NULL; + char* hdr_ima_sig = strcasestr(c->winning_headers, "x-debuginfod-imasignature"); + if (!hdr_ima_sig || 1 != sscanf(hdr_ima_sig + strlen("x-debuginfod-imasignature:"), "%ms", &sig_buf)) + { + rc = -ENODATA; + goto exit_validate; + } + if (strlen(sig_buf) > MAX_SIGNATURE_SIZE) // reject if too long + { + rc = -EBADMSG; + goto exit_validate; + } + // Convert the hex signature to bin + size_t bin_sig_len = strlen(sig_buf)/2; + unsigned char bin_sig[MAX_SIGNATURE_SIZE/2]; + for (size_t b = 0; b < bin_sig_len; b++) + bin_sig[b] = (hex2dec(sig_buf[2*b]) << 4) | hex2dec(sig_buf[2*b+1]); + + // Compute the binary digest of the cached file (with file descriptor fd) + ctx = EVP_MD_CTX_new(); + const char* sighash_name = get_signature_params(c, bin_sig) ?: ""; + const EVP_MD *md = EVP_get_digestbyname(sighash_name); + if (!ctx || !md || !EVP_DigestInit(ctx, md)) + { + rc = -EBADMSG; + goto exit_validate; + } + + long data_len; + char* hdr_data_len = strcasestr(c->winning_headers, "x-debuginfod-size"); + if (!hdr_data_len || 1 != sscanf(hdr_data_len + strlen("x-debuginfod-size:") , "%ld", &data_len)) + { + rc = -ENODATA; + goto exit_validate; + } + + char file_data[DATA_SIZE]; // imaevm.h data chunk hash size + ssize_t n; + for(off_t k = 0; k < data_len; k += n) + { + if (-1 == (n = pread(fd, file_data, DATA_SIZE, k))) + { + rc = -errno; + goto exit_validate; + } + + if (!EVP_DigestUpdate(ctx, file_data, n)) + { + rc = -EBADMSG; + goto exit_validate; + } + } + + uint8_t bin_dig[MAX_DIGEST_SIZE]; + unsigned int bin_dig_len; + if (!EVP_DigestFinal(ctx, bin_dig, &bin_dig_len)) + { + rc = -EBADMSG; + goto exit_validate; + } + + // XXX: in case of DIGSIG_VERSION_3, need to hash the file hash, yo dawg + + int res = debuginfod_verify_hash(c, + bin_dig, bin_dig_len, + sighash_name, + & bin_sig[1], bin_sig_len-1); // skip over first byte of signature + if (c->verbose_fd >= 0) + dprintf (c->verbose_fd, "Computed ima signature verification res=%d\n", res); + rc = res; + + exit_validate: + free (sig_buf); + EVP_MD_CTX_free(ctx); + return rc; +} +#endif /* ENABLE_IMA_VERIFICATION */ + + + /* Query each of the server URLs found in $DEBUGINFOD_URLS for the file with the specified build-id and type (debuginfo, executable, source or section). If type is source, then type_arg should be a filename. If @@ -1216,12 +1613,39 @@ debuginfod_query_server (debuginfod_client *c, /* Initialize the memory to zero */ char *strtok_saveptr; char **server_url_list = NULL; - char *server_url = strtok_r(server_urls, url_delim, &strtok_saveptr); + ima_policy_t* url_ima_policies = NULL; + char* server_url; /* Count number of URLs. */ int num_urls = 0; - while (server_url != NULL) + ima_policy_t verification_mode = ignore; // The default mode + for(server_url = strtok_r(server_urls, url_delim, &strtok_saveptr); + server_url != NULL; server_url = strtok_r(NULL, url_delim, &strtok_saveptr)) { + // When we encounted a (well-formed) token off the form ima:foo, we update the policy + // under which results from that server will be ima verified + if(startswith(server_url, "ima:")) + { +#ifdef ENABLE_IMA_VERIFICATION + ima_policy_t m = ima_policy_str2enum(server_url + strlen("ima:")); + if(m != undefined) + verification_mode = m; + else if (vfd >= 0) + dprintf(vfd, "IMA mode not recognized, skipping %s\n", server_url); +#else + if (vfd >= 0) + dprintf(vfd, "IMA signature verification is not enabled, skipping %s\n", server_url); +#endif + continue; // Not a url, just a mode change so keep going + } + + if (verification_mode==enforcing && 0==strcmp(type,"section")) + { + if (vfd >= 0) + dprintf(vfd, "skipping server %s section query in IMA enforcing mode\n", server_url); + continue; + } + /* PR 27983: If the url is already set to be used use, skip it */ char *slashbuildid; if (strlen(server_url) > 1 && server_url[strlen(server_url)-1] == '/') @@ -1253,21 +1677,28 @@ debuginfod_query_server (debuginfod_client *c, else { num_urls++; - char ** realloc_ptr; - realloc_ptr = reallocarray(server_url_list, num_urls, - sizeof(char*)); - if (realloc_ptr == NULL) + if (NULL == (server_url_list = reallocarray(server_url_list, num_urls, sizeof(char*))) +#ifdef ENABLE_IMA_VERIFICATION + || NULL == (url_ima_policies = reallocarray(url_ima_policies, num_urls, sizeof(ima_policy_t))) +#endif + ) { free (tmp_url); rc = -ENOMEM; goto out1; } - server_url_list = realloc_ptr; server_url_list[num_urls-1] = tmp_url; + if(NULL != url_ima_policies) url_ima_policies[num_urls-1] = verification_mode; } - server_url = strtok_r(NULL, url_delim, &strtok_saveptr); } + /* No URLs survived parsing / filtering? Abort abort abort. */ + if (num_urls == 0) + { + rc = -ENOSYS; + goto out1; + } + int retry_limit = default_retry_limit; const char* retry_limit_envvar = getenv(DEBUGINFOD_RETRY_LIMIT_ENV_VAR); if (retry_limit_envvar != NULL) @@ -1334,7 +1765,11 @@ debuginfod_query_server (debuginfod_client *c, if ((server_url = server_url_list[i]) == NULL) break; if (vfd >= 0) - dprintf (vfd, "init server %d %s\n", i, server_url); +#ifdef ENABLE_IMA_VERIFICATION + dprintf (vfd, "init server %d %s [IMA verification policy: %s]\n", i, server_url, ima_policy_enum2str(url_ima_policies[i])); +#else + dprintf (vfd, "init server %d %s\n", i, server_url); +#endif data[i].fd = fd; data[i].target_handle = &target_handle; @@ -1784,6 +2219,29 @@ debuginfod_query_server (debuginfod_client *c, /* PR31248: lseek back to beginning */ (void) lseek(fd, 0, SEEK_SET); + if(NULL != url_ima_policies && ignore != url_ima_policies[committed_to]) + { +#ifdef ENABLE_IMA_VERIFICATION + int result = debuginfod_validate_imasig(c, fd); +#else + int result = -ENOSYS; +#endif + if(0 == result) + { + if (vfd >= 0) dprintf (vfd, "valid signature\n"); + } + else if (enforcing == url_ima_policies[committed_to]) + { + // All invalid signatures are rejected. + // Additionally in enforcing mode any non-valid signature is rejected, so by reaching + // this case we do so since we know it is not valid. Note - this not just invalid signatures + // but also signatures that cannot be validated + if (vfd >= 0) dprintf (vfd, "error: invalid or missing signature (%d)\n", result); + rc = result; + goto out2; + } + } + /* rename tmp->real */ rc = rename (target_cache_tmppath, target_cache_path); if (rc < 0) @@ -1804,6 +2262,7 @@ debuginfod_query_server (debuginfod_client *c, for (int i = 0; i < num_urls; ++i) free(server_url_list[i]); free(server_url_list); + free(url_ima_policies); free (data); free (server_urls); @@ -1837,6 +2296,7 @@ debuginfod_query_server (debuginfod_client *c, for (int i = 0; i < num_urls; ++i) free(server_url_list[i]); free(server_url_list); + free(url_ima_policies); out0: free (server_urls); @@ -1869,7 +2329,11 @@ debuginfod_query_server (debuginfod_client *c, free (cache_miss_path); free (target_cache_dir); free (target_cache_path); + if (rc < 0 && target_cache_tmppath != NULL) + (void)unlink (target_cache_tmppath); free (target_cache_tmppath); + + return rc; } @@ -1901,6 +2365,10 @@ debuginfod_begin (void) goto out1; } +#ifdef ENABLE_IMA_VERIFICATION + load_ima_public_keys (client); +#endif + // extra future initialization goto out; @@ -1948,6 +2416,9 @@ debuginfod_end (debuginfod_client *client) curl_slist_free_all (client->headers); free (client->winning_headers); free (client->url); +#ifdef ENABLE_IMA_VERIFICATION + free_ima_public_keys (client); +#endif free (client); } @@ -1987,9 +2458,11 @@ debuginfod_find_section (debuginfod_client *client, { int rc = debuginfod_query_server(client, build_id, build_id_len, "section", section, path); - if (rc != -EINVAL) + if (rc != -EINVAL && rc != -ENOSYS) return rc; - + /* NB: we fall through in case of ima:enforcing-filtered DEBUGINFOD_URLS servers, + so we can download the entire file, verify it locally, then slice it. */ + /* The servers may have lacked support for section queries. Attempt to download the debuginfo or executable containing the section in order to extract it. */ diff --git a/debuginfod/debuginfod.cxx b/debuginfod/debuginfod.cxx index ece5031f0..d9259ad26 100644 --- a/debuginfod/debuginfod.cxx +++ b/debuginfod/debuginfod.cxx @@ -122,6 +122,13 @@ using namespace std; #define MHD_RESULT int #endif +#ifdef ENABLE_IMA_VERIFICATION + #include + #include + #include + #include +#endif + #include #include #include @@ -443,6 +450,10 @@ static const struct argp_option options[] = { "disable-source-scan", ARGP_KEY_DISABLE_SOURCE_SCAN, NULL, 0, "Do not scan dwarf source info.", 0 }, #define ARGP_SCAN_CHECKPOINT 0x100A { "scan-checkpoint", ARGP_SCAN_CHECKPOINT, "NUM", 0, "Number of files scanned before a WAL checkpoint.", 0 }, +#ifdef ENABLE_IMA_VERIFICATION +#define ARGP_KEY_KOJI_SIGCACHE 0x100B + { "koji-sigcache", ARGP_KEY_KOJI_SIGCACHE, NULL, 0, "Do a koji specific mapping of rpm paths to get IMA signatures.", 0 }, +#endif { NULL, 0, NULL, 0, NULL, 0 }, }; @@ -495,6 +506,9 @@ static bool scan_source_info = true; static string tmpdir; static bool passive_p = false; static long scan_checkpoint = 256; +#ifdef ENABLE_IMA_VERIFICATION +static bool requires_koji_sigcache_mapping = false; +#endif static void set_metric(const string& key, double value); static void inc_metric(const string& key); @@ -699,6 +713,11 @@ parse_opt (int key, char *arg, if (scan_checkpoint < 0) argp_failure(state, 1, EINVAL, "scan checkpoint"); break; +#ifdef ENABLE_IMA_VERIFICATION + case ARGP_KEY_KOJI_SIGCACHE: + requires_koji_sigcache_mapping = true; + break; +#endif // case 'h': argp_state_help (state, stderr, ARGP_HELP_LONG|ARGP_HELP_EXIT_OK); default: return ARGP_ERR_UNKNOWN; } @@ -1959,6 +1978,145 @@ handle_buildid_r_match (bool internal_req_p, return 0; } + // Extract the IMA per-file signature (if it exists) + string ima_sig = ""; + #ifdef ENABLE_IMA_VERIFICATION + do + { + FD_t rpm_fd; + if(!(rpm_fd = Fopen(b_source0.c_str(), "r.ufdio"))) // read, uncompressed, rpm/rpmio.h + { + if (verbose) obatched(clog) << "There was an error while opening " << b_source0 << endl; + break; // Exit IMA extraction + } + + Header rpm_hdr; + if(RPMRC_FAIL == rpmReadPackageFile(NULL, rpm_fd, b_source0.c_str(), &rpm_hdr)) + { + if (verbose) obatched(clog) << "There was an error while reading the header of " << b_source0 << endl; + Fclose(rpm_fd); + break; // Exit IMA extraction + } + + // Fill sig_tag_data with an alloc'd copy of the array of IMA signatures (if they exist) + struct rpmtd_s sig_tag_data; + rpmtdReset(&sig_tag_data); + do{ /* A do-while so we can break out of the koji sigcache checking on failure */ + if(requires_koji_sigcache_mapping) + { + /* NB: Koji builds result in a directory structure like the following + - PACKAGE/VERSION/RELEASE + - ARCH1 + - foo.rpm // The rpm known by debuginfod + - ... + - ARCHN + - data + - signed // Periodically purged (and not scanned by debuginfod) + - sigcache + - ARCH1 + - foo.rpm.sig // An empty rpm header + - ... + - ARCHN + - PACKAGE_KEYID1 + - ARCH1 + - foo.rpm.sig // The header of the signed rpm. This is the file we need to extract the IMA signatures + - ... + - ARCHN + - ... + - PACKAGE_KEYIDn + + We therefore need to do a mapping: + + P/V/R/A/N-V-R.A.rpm -> + P/V/R/data/sigcache/KEYID/A/N-V-R.A.rpm.sig + + There are 2 key insights here + + 1. We need to go 2 directories down from sigcache to get to the + rpm header. So to distinguish ARCH1/foo.rpm.sig and + PACKAGE_KEYID1/ARCH1/foo.rpm.sig we can look 2 directories down + + 2. It's safe to assume that the user will have all of the + required verification certs. So we can pick from any of the + PACKAGE_KEYID* directories. For simplicity we choose first we + match against + + See: https://pagure.io/koji/issue/3670 + */ + + // Do the mapping from b_source0 to the koji path for the signed rpm header + string signed_rpm_path = b_source0; + size_t insert_pos = string::npos; + for(int i = 0; i < 2; i++) insert_pos = signed_rpm_path.rfind("/", insert_pos) - 1; + string globbed_path = signed_rpm_path.insert(insert_pos + 1, "/data/sigcache/*").append(".sig"); // The globbed path we're seeking + glob_t pglob; + int grc; + if(0 != (grc = glob(globbed_path.c_str(), GLOB_NOSORT, NULL, &pglob))) + { + // Break out, but only report real errors + if (verbose && grc != GLOB_NOMATCH) obatched(clog) << "There was an error (" << strerror(errno) << ") globbing " << globbed_path << endl; + break; // Exit koji sigcache check + } + signed_rpm_path = pglob.gl_pathv[0]; // See insight 2 above + globfree(&pglob); + + if (verbose > 2) obatched(clog) << "attempting IMA signature extraction from koji header " << signed_rpm_path << endl; + + FD_t sig_rpm_fd; + if(NULL == (sig_rpm_fd = Fopen(signed_rpm_path.c_str(), "r"))) + { + if (verbose) obatched(clog) << "There was an error while opening " << signed_rpm_path << endl; + break; // Exit koji sigcache check + } + + Header sig_hdr = headerRead(sig_rpm_fd, HEADER_MAGIC_YES /* Validate magic too */ ); + if (!sig_hdr || 1 != headerGet(sig_hdr, RPMSIGTAG_FILESIGNATURES, &sig_tag_data, HEADERGET_ALLOC)) + { + if (verbose) obatched(clog) << "Unable to extract RPMSIGTAG_FILESIGNATURES from " << signed_rpm_path << endl; + } + headerFree(sig_hdr); // We can free here since sig_tag_data has an alloc'd copy of the data + Fclose(sig_rpm_fd); + } + }while(false); + + if(0 == sig_tag_data.count) + { + // In the general case (or a fallback from the koji sigcache mapping not finding signatures) + // we can just (try) extract the signatures from the rpm header + if (1 != headerGet(rpm_hdr, RPMTAG_FILESIGNATURES, &sig_tag_data, HEADERGET_ALLOC)) + { + if (verbose) obatched(clog) << "Unable to extract RPMTAG_FILESIGNATURES from " << b_source0 << endl; + } + } + // Search the array for the signature coresponding to b_source1 + int idx = -1; + char *sig = NULL; + rpmfi hdr_fi = rpmfiNew(NULL, rpm_hdr, RPMTAG_BASENAMES, RPMFI_FLAGS_QUERY); + do + { + sig = (char*)rpmtdNextString(&sig_tag_data); + idx = rpmfiNext(hdr_fi); + } + while (idx != -1 && 0 != strcmp(b_source1.c_str(), rpmfiFN(hdr_fi))); + rpmfiFree(hdr_fi); + + if(sig && 0 != strlen(sig) && idx != -1) + { + if (verbose > 2) obatched(clog) << "Found IMA signature for " << b_source1 << ":\n" << sig << endl; + ima_sig = sig; + inc_metric("http_responses_total","extra","ima-sigs-extracted"); + } + else + { + if (verbose > 2) obatched(clog) << "Could not find IMA signature for " << b_source1 << endl; + } + + rpmtdFreeData (&sig_tag_data); + headerFree(rpm_hdr); + Fclose(rpm_fd); + } while(false); + #endif + // check for a match in the fdcache first int fd = fdcache.lookup(b_source0, b_source1); while (fd >= 0) // got one!; NB: this is really an if() with a possible branch out to the end @@ -2016,11 +2174,13 @@ handle_buildid_r_match (bool internal_req_p, to_string(fs.st_size).c_str()); add_mhd_response_header (r, "X-DEBUGINFOD-ARCHIVE", b_source0.c_str()); add_mhd_response_header (r, "X-DEBUGINFOD-FILE", b_source1.c_str()); + if(!ima_sig.empty()) add_mhd_response_header(r, "X-DEBUGINFOD-IMASIGNATURE", ima_sig.c_str()); add_mhd_last_modified (r, fs.st_mtime); if (verbose > 1) obatched(clog) << "serving fdcache archive " << b_source0 << " file " << b_source1 - << " section=" << section << endl; + << " section=" << section + << " IMA signature=" << ima_sig << endl; /* libmicrohttpd will close it. */ if (result_fd) *result_fd = fd; @@ -2204,11 +2364,13 @@ handle_buildid_r_match (bool internal_req_p, to_string(archive_entry_size(e)).c_str()); add_mhd_response_header (r, "X-DEBUGINFOD-ARCHIVE", b_source0.c_str()); add_mhd_response_header (r, "X-DEBUGINFOD-FILE", b_source1.c_str()); + if(!ima_sig.empty()) add_mhd_response_header(r, "X-DEBUGINFOD-IMASIGNATURE", ima_sig.c_str()); add_mhd_last_modified (r, archive_entry_mtime(e)); if (verbose > 1) obatched(clog) << "serving archive " << b_source0 << " file " << b_source1 - << " section=" << section << endl; + << " section=" << section + << " IMA signature=" << ima_sig << endl; /* libmicrohttpd will close it. */ if (result_fd) *result_fd = fd; diff --git a/debuginfod/debuginfod.h.in b/debuginfod/debuginfod.h.in index 4a256ba9a..73f633f0b 100644 --- a/debuginfod/debuginfod.h.in +++ b/debuginfod/debuginfod.h.in @@ -39,6 +39,7 @@ #define DEBUGINFOD_MAXSIZE_ENV_VAR "DEBUGINFOD_MAXSIZE" #define DEBUGINFOD_MAXTIME_ENV_VAR "DEBUGINFOD_MAXTIME" #define DEBUGINFOD_HEADERS_FILE_ENV_VAR "DEBUGINFOD_HEADERS_FILE" +#define DEBUGINFOD_IMA_CERT_PATH_ENV_VAR "DEBUGINFOD_IMA_CERT_PATH" /* The libdebuginfod soname. */ #define DEBUGINFOD_SONAME "@LIBDEBUGINFOD_SONAME@" diff --git a/doc/debuginfod-client-config.7 b/doc/debuginfod-client-config.7 index 53d82806d..f16612084 100644 --- a/doc/debuginfod-client-config.7 +++ b/doc/debuginfod-client-config.7 @@ -27,6 +27,33 @@ debuginfod instances. Alternate URL prefixes are separated by space. This environment variable may be set by /etc/profile.d scripts reading /etc/debuginfod/*.urls files. +This environment variable can also contain policy defining tags which +dictate the response policy for verifying per-file IMA signatures in +RPMs. As the space seperated list is read left to right, upon +encountering a tag, subsequent URLs up to the next tag will be handled +using that specified policy. All URLs before the first tag will use +the default policy, \fIima:ignore\fP. For example: + +.in +4n +.EX +DEBUGINFOD_URLS="https://foo.com ima:enforcing https://bar.ca http://localhost:8002/ ima:ignore https://baz.org" +.EE +.in + +Where foo.com and baz.org use the default \fIignore\fP policy and +bar.ca and localhost use an \fIenforcing\fP policy. The policy tag +may be one of the following: +.IP +\fIima:enforcing\fP Every downloaded file requires a valid signature, +fully protecting integrity. +.IP +\fIima:ignore\fP Skips verification altogether, providing no +protection. +.IP + +Alerts of validation failure will be directed as specified +in $DEBUGINFOD_VERBOSE. + .TP .B $DEBUGINFOD_CACHE_PATH This environment variable governs the location of the cache where @@ -82,6 +109,12 @@ outbound HTTP requests, one per line. The header lines shouldn't end with CRLF, unless that's the system newline convention. Whitespace-only lines are skipped. +.TP +.B $DEBUGINFOD_IMA_CERT_PATH +This environment variable contains a list of absolute directory paths +holding X.509 certificates for RPM per-file IMA-verification. +Alternate paths are separated by colons. + .SH CACHE Before each query, the debuginfod client library checks for a need to diff --git a/doc/debuginfod-find.1 b/doc/debuginfod-find.1 index 7d577babe..d7db1bfdd 100644 --- a/doc/debuginfod-find.1 +++ b/doc/debuginfod-find.1 @@ -129,10 +129,18 @@ and printing the http response headers from the server. .SH "SECURITY" -debuginfod-find \fBdoes not\fP include any particular security -features. It trusts that the binaries returned by the debuginfod(s) -are accurate. Therefore, the list of servers should include only -trustworthy ones. If accessed across HTTP rather than HTTPS, the +If IMA signature(s) are available from the RPMs that contain +requested files, then +.BR debuginfod +will extract those signatures into response headers, and +.BR debuginfod-find +will perform verification upon the files. +Validation policy is controlled via tags inserted into +$DEBUGINFOD_URLS. By default, +.BR debuginfod-find +acts in ignore mode. + +If accessed across HTTP rather than HTTPS, the network should be trustworthy. Authentication information through the internal \fIlibcurl\fP library is not currently enabled, except for the basic plaintext \%\fIhttp[s]://userid:password@hostname/\fP style. diff --git a/doc/debuginfod.8 b/doc/debuginfod.8 index 42e0fc9fb..577f58b6e 100644 --- a/doc/debuginfod.8 +++ b/doc/debuginfod.8 @@ -285,6 +285,14 @@ completed archive or file scans. This may slow down parallel scanning phase somewhat, but generate much smaller "-wal" temporary files on busy servers. The default is 256. Disabled if 0. +.TP +.B "\-\-koji\-sigcache" +Enable an additional step of RPM path mapping when extracting signatures for use +in RPM per-file IMA verification on koji repositories. The signatures are retrieved +from the Fedora koji sigcache rpm.sig files as opposed to the original RPM header. +If a signature cannot be found in the sigcache rpm.sig file, the RPM will be +tried as a fallback. + .TP .B "\-v" Increase verbosity of logging to the standard error file descriptor. @@ -300,8 +308,15 @@ Unknown buildid / request combinations result in HTTP error codes. This file service resemblance is intentional, so that an installation can take advantage of standard HTTP management infrastructure. -For most queries, some custom http headers are added to the response, -providing additional metadata about the buildid-related response. For example: +Upon finding a file in an archive or simply in the database, some +custom http headers are added to the response. For files in the +database X-DEBUGINFOD-FILE and X-DEBUGINFOD-SIZE are added. +X-DEBUGINFOD-FILE is simply the unescaped filename and +X-DEBUGINFOD-SIZE is the size of the file. For files found in archives, +in addition to X-DEBUGINFOD-FILE and X-DEBUGINFOD-SIZE, +X-DEBUGINFOD-ARCHIVE is added. X-DEBUGINFOD-ARCHIVE is the name of the +archive the file was found in. X-DEBUGINFOD-IMA-SIGNATURE contains the +per-file IMA signature as a hexadecimal blob. .SAMPLE % debuginfod-find -v debuginfo /bin/ls |& grep -i x-debuginfo diff --git a/doc/debuginfod_find_debuginfo.3 b/doc/debuginfod_find_debuginfo.3 index 0d553665f..4e359c8c4 100644 --- a/doc/debuginfod_find_debuginfo.3 +++ b/doc/debuginfod_find_debuginfo.3 @@ -251,13 +251,21 @@ void *debuginfod_so = dlopen(DEBUGINFOD_SONAME, RTLD_LAZY); .in .SH "SECURITY" + +If IMA signature(s) are available from the RPMs that contain +requested files, then +.BR debuginfod +will extract those signatures into response headers, and +.BR debuginfod_find_* () +will perform verification upon the files. +Validation policy is controlled via tags inserted into +$DEBUGINFOD_URLS. By default, .BR debuginfod_find_* () -functions \fBdo not\fP include any particular security -features. They trust that the binaries returned by the debuginfod(s) -are accurate. Therefore, the list of servers should include only -trustworthy ones. If accessed across HTTP rather than HTTPS, the -network should be trustworthy. Passing user authentication information -through the internal \fIlibcurl\fP library is not currently enabled, except +acts in ignore mode. + +If accessed across HTTP rather than HTTPS, the +network should be trustworthy. Authentication information through +the internal \fIlibcurl\fP library is not currently enabled, except for the basic plaintext \%\fIhttp[s]://userid:password@hostname/\fP style. (The debuginfod server does not perform authentication, but a front-end proxy server could.) @@ -325,6 +333,18 @@ Query failed due to timeout. \fB$DEBUGINFOD_TIMEOUT\fP and Query aborted due to the file requested being too big. The \fB$DEBUGINFOD_MAXSIZE\fP controls this. +.TP +.BR EBADMSG +File content failed IMA verification against a known signer certificate. + +.TP +.BR ENOKEY +File content failed IMA verification due to missing signer certificate. + +.TP +.BR ENODATA +File content failed IMA verification because of a missing signature. + .nr zZ 1 .so man7/debuginfod-client-config.7 diff --git a/tests/Makefile.am b/tests/Makefile.am index 7aae3d8aa..db071186c 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -278,6 +278,9 @@ if !OLD_LIBMICROHTTPD # Too many open file descriptors confuses libmicrohttpd < 0.9.51 TESTS += run-debuginfod-federation-metrics.sh endif +if ENABLE_IMA_VERIFICATION +TESTS += run-debuginfod-ima-verification.sh +endif endif if HAVE_CXX11 @@ -600,6 +603,7 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh run-ar.sh \ run-debuginfod-webapi-concurrency.sh \ run-debuginfod-section.sh \ run-debuginfod-IXr.sh \ + run-debuginfod-ima-verification.sh \ debuginfod-rpms/fedora30/hello2-1.0-2.src.rpm \ debuginfod-rpms/fedora30/hello2-1.0-2.x86_64.rpm \ debuginfod-rpms/fedora30/hello2-debuginfo-1.0-2.x86_64.rpm \ @@ -623,6 +627,11 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh run-ar.sh \ debuginfod-rpms/rhel7/hello2-debuginfo-1.0-2.x86_64.rpm \ debuginfod-rpms/rhel7/hello2-two-1.0-2.x86_64.rpm \ debuginfod-rpms/rhel7/hello2-two-1.0-2.x86_64.rpm \ + debuginfod-ima/koji/arch/hello-2.10-9.fc38.x86_64.rpm \ + debuginfod-ima/koji/data/sigcache/keyid/arch/hello-2.10-9.fc38.x86_64.rpm.sig \ + debuginfod-ima/koji/fedora-38-ima.pem \ + debuginfod-ima/rhel9/hello2-1.0-1.x86_64.rpm \ + debuginfod-ima/rhel9/imacert.der \ debuginfod-debs/hithere-dbgsym_1.0-1_amd64.ddeb \ debuginfod-debs/hithere_1.0-1.debian.tar.xz \ debuginfod-debs/hithere_1.0-1.dsc \ diff --git a/tests/debuginfod-ima/koji/arch/hello-2.10-9.fc38.x86_64.rpm b/tests/debuginfod-ima/koji/arch/hello-2.10-9.fc38.x86_64.rpm new file mode 100644 index 0000000000000000000000000000000000000000..b04ad8c2af398d84b70323ea1420465ff38ed2d4 GIT binary patch literal 71621 zc-ri|2V4{1x+prKNRcK@Ma591hMDvTNben`D#|1?fe0j!gl0hmq=^j_QB<&F!2(uP zKomhl6bmY%fQn)TJJ@(DlD*H~=bU>y_n!OS{oV8ai>%CiYkjMIb+z#J(7T7S5D0`= zJU(7vO(r3*HH*aMP?*sFbRa@1WoNDl$|lJTzWjhAD;LOY@_p z@CRu=CWSvrVUZO6B89W0@K-5ZEQLqL+n3oXg?~u%tEKQyDcmWAe@Wqcq_Bn* z{w9UB$NQ)CN!mZ1@$%Xl()`Ke_}cfS{hA`pmy8>y5*@(NfrKynfw0 zY5mOc^12ckk_WFWDgGWQJbN6T+$)7W#_^|8rTy`g!at<2*Eqi3Whop!o{? zNMZ4K`=-Xy`je&b8!4PJ-u|o;(($K`=bL?!=BJIr=I5nw`Z#QnGhR-sL<;X2Z%=FE zc>5MrQux$3p2c-3d|C?MlEP=Cb;O2OUY~$MfRw-?b+2_`C!(o(R%KQAS=^inj zClV9I0%C#y<8jl8k~}sZi>B}dpb=4WVxm9@lKwVYFh)Kx0p+JF$D{n1WHd%Hc9f41 zMFOILPa;Yv3C5Fv3e$;+$-+c|2$zigUwQ#ZBo-)Rc!Gd05(`l=PK*_#O4?5q@&GVS zJSllZzv)B71d>sTBx9BIz!vmTS%eEyB%F~j2lO0|CvpYC-_3BqIQTI{kr?y}6=I^_ zy^~}lpn%LE;zB@ilq2Q=YLFn#ReC-DDlf#6vMMiHl?pZ{uSsE=$k^Z9BbY^|^9#vpZWq_{B zTm-nlC&?WLX+X#2)`2ImXR3!l-b3&-0P5Bbp1=>w0NbgNHJ+x*2k+o1=L&Rl3#iu` z_=Z{tQU~@;*%HJiP_ORFw@%=1=h(_UPH$K4`G4o{|BDF&ctX87J{G{ zmCL5mF_guIaWVsu(1MFFxKtcwNziaEl?=mF3WLT(V3^5f(zsj>n~hLeTow$&Y?K9a zSzMe(X407$lT5+bT;N-n9Gp#Mvv8b-VmJocW3VX*&SKCw2*!dr3<^+72Aj@C=rk0; zI2aS8acEQ;&Zg7YESLhZFqna`D0CK`g(GyFN@l@06J>xd&{-@x1?RvN4wHhiI22Gl znM$W2bP5aOk~vHSXP{&p;?i&y2Zp&c8U>@$VFm*sgCTI)bS9HQXEQkn%wls9I)_T8 zG1w@Iv0+R?B@|Ey<5Fl`2%s}K7=qAfbPCGExiFhcLuo9SM#XVD%%YLmTnfgeAygWI zaM)Co!ew%)2nVC$pl39g3?X!wg25Cr1z|ENG>`!bQmHVF!XR^T4jbdZWDb|hrQl4E z!{xvXF5m*0jYt^CA|nXIU~>RLQ3-Q!CYgo-OaujNMFCY1GLEqs0FTCE0}6nVq8vI4 zVbZB&Km-_2A4ebrW3vGR0Rjz-8|5-^IvH?)OJ%@RG8tyz6qG?>l36qs#=IMBzd(pc{)# zWpK$r?%5m`&O#BO&Y(j;&B#C?m^eTLGK#ZNAgwf77qsk1Q-qh7zS#N({W&YU_eor z3QQ4=&EWzi0lei<*eos>HG*=;K-gJ;Trf_jQQ1JpsX(nD4g&rdC?Fn#gK{w%%Alh_ zjd3=cMPYM+#51`VFh~@X3sPA?l-YnOOdJ?VpsQSj4PiKkOr-$h3Am2YFkp2NE?_61 z2@}SFy$1AR0$Jv=FoeP(gA59n#-ahGgHZ%1F%uXRm(8~3_37tbev41 zvndo7=r$lf0|z#WjN&v3;5C9#XgCIR6=fn!h{|RF%>x7l9l{w1u&Wq`un~zepfC*V z12DwE;8TG1z%&F0j)OsgfzmO+D7j<=hZsO1$iOXtS#%%=Fc}yc4ntz*fl=hLX)Feic@~|D zvRMcV7<2|;6~d*nIVg}FCSW5BtTBU*v8aH~D8>RF450%$QrWOX-hp>Qfn9~Uff55N1;Jd5P6uoP0!gKk=>R|&paLqB3iJaA3mGs7XMj!u!%POG2l_<^ zipBu02%`gCfmk%4&o~UsJcWa?fc!D2fIBb?ct$GlH9$DnR4O9zL%_hH2nUqI0ZW1T z0)_+lEC^vxsdN+&luDrh0wWSj2graj>A>1jQ51ML4hm`j%mdyH7!eo+RvM5WIAR*G zr4Z;P7$OA*BFJS^Xux%WmVxF1!%rtusVq9o1$<;PfiED#z_oLMbp+muCgC+`kqLp` zFqv#%M?kNEMFD+g1IcCsF=4Y%78e7|VS^3>Q%s=(>H~S^GJwa2*=!(`43q{TOpHqh zX5hEqrBZ!2QTLVlw6X*%h4vdNdJ)waq3Kuv^oWY^6 zfcvB|Q3}EWA_Ei+h%xXKIGrk)JOINC#245;$;1P2lnnd=45kw>1bhjM!9YBKIRQ?E zieOASM`9si8pZ|!2nGtQBTSRH4+I7?4l1y;K*$&{9ZU?kYzSu4$y_=RQYNsfbS8&M z2l4`D6$}i#BPY8g;2q#HFxNB+u!=xSnGnEWNIVsot$`xI5dm2RvlW&^{7?p% znW0 z@E{yAg$1k_Ln1F^P(K8mA28}T2jep6RA8vczzPCyjoXbo5 z*V$g8NRb!=sp5Fd%)>uqk*CvKPxHUo843v|QLPb(N}`kC-%rUmjYK6OOz;9po+MbZ zKXestWIu3dL|^(hMvq1{Mr+79N_PZ<@pv2+;Y2rn43CfFLdlk;oe(7QV;uj@*5e$a zHE~?Rq5o2G=6|g?cX{dAu7dQuS8_%sIp3T( zepdMV%vTLWb?Kx>a>rhBaH%Cd|CO9g>Vhaao0~Ge-wsL68>fLd9mE+R>Vr5FM9F!S z0f>em8i6P|k1_$#6vSB|O3o!E2E+nHOAsaJm)4S59Edg`O3Vc^e#Z7Yuwx)F(Zs+9 zhwyj^7eKfO!o?7t3~^D2%Y(Q=CFS09J)mI)aV+ zVG@te6_8?UrWk)PY#xlSH#bN@XI4*Dky?9UkZ zpD^(FKVk^}h#~qThWL*d$$!G&{&BQi-XAf9f5M3QV=FO#q*&}9sUG{se#HKf9zZt# zgz-nJFaG0b#-{v*a8`NHS_2_x~3btH=ajIqc$_)kc}KN3y& zM*$GU{So7j!x8;a5Ji7vo;dc87=LUf`Hy2s`J-|#iCyI4|DQmb7xq6yN2S5594sP$0AiQ}qN;nF!}33wh#nke+B|9kR$hAb6gad&Hu-0U^OI zZdTT=|0>0vGLGrG==V}MJbaPU+_`Q5Bfx(y(OPn=L}dSW{{X)P+sAv}MUPsMx=?FS)T~&E;_a8Ri3GC-B79 zTp?J~TPF&5d@(Krjr}bhEFro7oC$Ffc>-t&Shzz7OL}R>TH5d57ip|x_{smcxB1^8 z{NuVuddKzm99-uAk|^iq>rMoGV)MjA-?`4jL>^AW(|~gPmKZDI`1Y37->*Cg3aB3luiB@;X^ z*ZDy4ktll(?LRlwxBA;9GaFw?l|%}OOoFY5Kzn$Ji8uyfJmGJBN{1k)xh`H_L@}Nw z25Tz;mrE2#ZnfdkJ9q%#@e`B9R>Xh7Bbvo5;Uy9|f8Wp|VyGCkA|{||e@_Gykla8N zBoIURl3lC+UP1A{!ytlzT!SbJqCAKbK$HWK5ImkISpzHnZL5Zz9JA!#UNn@R;z-sS z(mIpHTx%vo;ZhMU8RIe$Adm>kq{A?UBRRrkP^dIELPb~@h03J?NoM0@6lS1o6lPK| z1_zN`bwQAE8dJzjI)-y-WCopz!E6kNSqut}F~|smj{iRWWpd~!0%I&Hla10SI0M1i z2*TjfFb-RCtKx5$?#ITyOa7^VhS^D*pHS=x6X=xXZm-5EAqr+we#|t*(%buw?P>Aa zn)uN5p2`u7oY+_ckB-eJT`v`m9e7}UdO;V_dg5YJD+``y^~a^XU$YIX#c~@e23($9 zP+VLyw;b9IEr%Fp8N0V^n;)P>4P7N4;Ha^4rs5>$e0^$Byh$v#hyYoVRSFE_MKbBe*W$0Xohx&2Pffr2^=+}QSAvaqsNP#X_4TJ7qtPj% z;>+oN@eAWJ0-ictKa_NpaiG@!VNL0`vyUEmEL~FEd)q$cQ~J62SC3E}YNi~?YosC^ts!% zC>q_L@2OCjrJYx<9j2%G4K;E+6mh})p;*qQgmS%gf?Qys!AkhN6{KCIe)`yt+0|CsGT zGq;X?OL?&?qIvB7r_j#B$uIb?yOxR8UcL8nrCXI#M2x$uv6pi2lq-mNPzQ1-!LU%R zX)3o$12LRutZ|rlz(Yh_`Dpr^i-j}VM^gi5Pf2`XwH6f}Pd#_=qB(80Qr_OhkpT+H z$1Tj2Bj=(|pPv0$)N)PXx{=TB`#ZKy;Ac-NUiiy$5JzRz5_TAWPca|$Zkg)6?U3fi z%ve3V^z4c6s+arhkGkzRc0453^}5Qh-7?k1$7`eAOdb3(>P4k9etem%^R%KVQ^PN9d&B!V zu~UV0_pH`K)G-|0zm>dGVWa1vqFuu(1S`UWB4}f||IN;peRf^JvreY|`D~@_7F5f6&%t}kHmS}LB{h1AGmPt2)bmZBH=7^bbbPXpZROFc5%08!UcqW5 z(16me@|78>S=XaEN%MS{xdp|Skw3Co_ixlhzer;-8e5`fb%*V`lw%*V%VW3Gxttv% zx(%>>%Tg^lw#Fmos>IP}M`^rH)th>rW?xfPW%QWd>;w0Z-l>{H{7 z7m_a*L|thr9FQf1hyyqW(Xx>Otp!TZMwP*t!=w>}YDR%svpwn2%vinxAyFnxHd8Kt z!g_@=#Y*L>iN{pVs5Yv#s^8SOKdDc1P-|FwLoYcua4T|?41HAB|K^$fM$vP%{DXj1jbfRx zo1WR16)!>nB2|%thJbay#WWOjxOqq=+g7DLYL>R18!n zsS(uQ|I+B6)Twz<>$vt#oejE~yQ+w%^fvXcH}Tph>pUe;&M9AR1*k-ovitJ|lP~f| zW8<<)r+g}mT{7R?YLwlr(dk#b)pzN$;EPJ@ceI|5h=0_sy?TM|V!^C?M=Y+o5?sHz zZODAH%k})%M&}Q4g9N)t&@=6olar>PQ-k!JrXkY}W=zs2%zXco*l*Bjc+u#%@lKNs zrYmP9nW5%E7EYFkm4WpnKOa{JaA+_OjC(2Mb%=&buLvz6GPc1s^;M9=jI2 z7!1fB8B@>soIN5|&$+k4D0?JEJ?DI^`l|hdu5wW+>Z{hsA2L^+Z2k&q+%7Ztu))mX zN48~A-D8C-NaJADds+Q4zk;8Gb>COj`K{s0g?U2I%Y%fMTA916g7$<_yeuNRh$ic| z1nJ3U4!Z8q{(RS`?@-VDhKqMPRbui6N?d>4%sM)-Q2vl+q|yCYG2W7q+)%oQ^yQq# z#EKOvg=I?@-AgF2e52Nt_5)cqJE*d9d+5`I-8V95A>?df9{Ry7Q8zSu|x-u6nTKN=#?YgQj4*%if)u zhvz|h(QkC!9QSk)zCJif=sbgT$dz_&q}00h*+nT1ISUTi#x`4d)ff5fvl)zfxoT19 z*FdMDr?)?E-9jHe7!6?bdPPLs)MDfHF&chB2Z z6^gIIOA;)64*A~sv1W%+xyk0xqom# z-FmgfcX3Ab76-Ck@0yCQ-;<-xJPm#I4&J)GrNO1%;K6#Fo~2o@u`O?Jeawb?J1-m+ z+gsLrmnX)0rpa?cv*?X`edTp4y)stIn=MS8biSYWm_GDqpr5t@KA0|GMlNR^ibx+0)mb z;qyy%O{k*Y9%If_!7I91ry zes9B1+?bsDuy5V>Z(LK`d(RY?`%m^fkiK`(#EW#M$(?VN`d5>ZTS)0e)X&G(p0`_Z z-QSbpbmc|A*1H+%x!$yanyUA2=Qy6eX>wQI?ZBm5{U?_z2(#uy6rP{W(+uDFbfoNJ zS2@;_zWCQPs}+UDmj>VQeV6G+ImT<)8kJjqI@gNIu zm~>8Vt7s>kYq^^cs~;AV-CZ0}s@!VMIlIocSllw#?}=`xLj1$p9AwGHh#bfLHmkLc zQ#3lxf9?*P_vQAw#TPQpHh)s8j2`uGxwOD{=0vv}Y0Ngar(3vlnMby6FZoHx@Jmd` z7yX#Un)%Zt-P3E|q23ego=2HET)!0heJH)!!em*ntLB-wN@X#16_G*|u}>uqv%9`_Rta&K1AS;5ra zSY1CT;r`I%_T76s)>kwxY%+iRdFO$bXFi{uTKw$ANkpGBlhZP@*KBF|liUp%J;V1m z>wS+@-&P~raQxb6*^SmuHRraP(@QcSaY#?1#vr|G*_D!8Ij5Ty1HPqgtXuZpzVN~O zZDef9ynBy^gg1MfQWoB};=W)IG>CT|MBFl3ObQXzRmj>b^RVIN`@4>)-+q51A&J%4 z)@^7l*tuTWm3WU7q;qW8X;Yy~y|UeDAvCEmzRi8~fwU7KlBEz}hD%Ox&|I@5Hq@_F@?@RNn@nbS7( z4u%~%D0lq=RDK8REV;kydZO+2{sVgguPe2!yF10Cq^in1FC$Ml?I)hE__p6>?$fi_ zxl8GC*Jn=d>KO=s=>II->`I+y(b1)?Vs=W^x)wXa>Gc;tHu?< zQIBXJ&t6$NN>uHS-8lU5XU+CYW6PGmDXOchVAjPtR$QBWdfomx^qspOm7_XpDm{kV zs19?N-7a)Hoqwiozo+O_*6OO@4^}BE*Ndp;o_exblX7HM63y6!j9m|RiH(87x<=se z?B%!5lC2FXJJsH;VkR4UMtd&Frr~ROo1A=E@I{^2oDdt2mGZNyi1wlCCmwtEemgR& zY-;6x!}wTA)+xAUcwJzG^C{oUoh3w-Lg!f*FK&0Z@$%QRjJ5kpa~1`7O;9T1ylQaL zwS9Ba*SlXanAM+UKYEWA7*9E!b~17A$u8R&9j7aI8KkVfbXh^BwZM?f+O$%m`}pC^ zc^^JUzyCnYhPpexMJ#~7YIN7_U;DJ_;eGFKJ$$CuWfhr`)+5zf>uOeR^@(&oYNs#1 zcdqwcVu`N*5o6l1BfSYN_f=Z1b~v?uj;aitpp&^HULETg=FQd=kuR^aTW9C-Y;Uoy z>xl1xL6=JH*gVKMYSTrp%3=NYbK#{UK9%nUD~bb$w&gBZ@^JDO3uC*?jVaBq;$ou@ ztkKyho00h9{n(A2vn!Sz(Yu#3b!X<&mBNrJxodm!q61FL<}Be`UG7vo&dWZLZ5+CI z*Y=)DgOj`G9hH^e{4nkFEpw$QBC|x--sjE}yn5>j>~mWh4VX0#gNzB=yZzoh+P>^; zD54R3FZqt(#?`gKCXS~|cPq0a%vLX+rjYR56u-VzM#Dj_uy#xaeUU&V*%pA+2PUCt~g|?xy#fHf4=IOX6o_~zB2!?x`R$% zT_Ef2!?wz)j3d;J#}h^2>!<-U8@%CY`{b*QYd3l9>6`AXSk-TFU@78DoOQwJwJ`AooWX}unVExeD_yQ>yi_}C{5bQcsqox#=aV%C=J!Tp zzQmkRei}D6KW2BZ4&m#C8W|Si^4NFAo-4*%^o`et?QUYI99XvI@XGu93clsESg7f? zZPqN$UjI#7bSfOHf4ez*U*pX~Jx7X8I`Yr-btW)_m)@)JL>m0hzq}K;aMf}#cXE%7 zQ|%6Yz0HGK?g2N3nn(A#E?IimW$pV%VTHy`y}@7GH)ovK&uTdOHDh{Qtwqe>tcI!j zALQQ^b+a?SZd&JAnv^#+dqRhVvH za-+JN{QcoI*KOAva@^;=K*iavNnY3w;R?kyqAwl}7SUsbF=#wmAIqHk^c z+{3#3QdgtEkf*nWm)oX9yK%6$bpNX-6E!+y(l_H|RYi*1>xQ-V6Xh4kO{J1OqrBr? zhzr{HS=gUQ?;uYYRxqk|zV{7(r0`n9GNm)7T{Jh*0{c3+wg2n%jh{P<0-+^A?OGmz z3%dxZ(?Cd1%tgmTQ8c}gIc35~bNTf98SiGjAO;Idd0$V3HD~Y5Hb3|{ zwX1GLpkVFvVKXg*te0G+-11H4WCBc>M7VFUuBwu1;|X{1Lg(5(9e7>7cT5sj(o7~C;V+5p4^xkO8B{%8p!B4hell?w>ZLylYf3m;wKQ)JvU75h>Lf+4 zlG80`!}33~b3*2uH%*E>+vX9xFVs{}v2{-U!84v`Pu;;koZWCkY;U|IXTecF1=F9! z=QHN)m}1WIbv{T+Sg=|xeczH9?)9Oqo)dSR5(fV`cdS(-g7xw><#wAR8GrmB^w$Ai z`H8ZKUUOEaX4sbW^VC6!w^&w&JC)oLi;{EMY@4xLytzDb9KO1?X!GWGk2{g^^j9q4Vsmsmfb@ZFO z&?^=jyh>u)-7e10s_FByn7$?XP`1&ezQZ@FtfMziws|_S{YXh-)wMmU`?nx2?@f+h zce#Bp6pMNHA>CB8`MyQ!S=}qkZ(S;0Fnf5xdAV5w##_5}+YhNJYdlAKOV>;;2t9N%oVx4zsMgWF4Oe)k ziY9ZjKi*5D?caO43(j+{7;U%}WTBRjL9876DEDowBGWtJ@R(=~eLw9}$G1;kgY>#T z-8p|muwUT+^4z}eiu#Y$-Eji}M*COzH!d|!Q@B2uFn?^FP1hNPw%zslTC00YhTRP~ zK^l1`V@XCq>solbD%jq^Gq#j^>?>aUifLqD6j^rZdz$`C6nI6Q^10Lh4x>U*B>XJ zH!8b*alf-iW2aUW;o$?>7jw=mUm+9LEbEk?6_jPX?!xgWt$wyL8^v-xs!qeRoA#_D zo+O%{n=~O%<8^#<#w*u1YsBCA3ZbWl1n+gNZdlqb{_@DXeA={;E4K3Po0jKgJ%9A| zam$wdP*%t14bcc51`9`b)BlKO0>1}v(`R>Z7JG5r` zowoA>SIMZn%uu^(5_PUehp72FNvD3H)5qA5Z!Zt5!7nTvV9kph%ZYZLv47v%nHk#i z{MvWkJ(N{gC7d1c;!b{%vDeN=yPR^LL{G^@%^#^Xwe1l5ZqeLO;F`6^*t}{c&$dFZ z^+L+IDkGO28y@f9PT3s%CHBRgy58^8#BrTNMXOlKl>tAGPapZp=-nX?f0&i}+B$R9yy<-nPM;Yq=`_u^#nF#7AK(4h+3&AyG-yZGER)1bVS86gV~zxB8MVwAg47 z0_1f1hqs+-wivvI%H?#E2WuIR)4n`!l{5Z zX)-*icTrhqbJvwoQOn`$4}z$pdoH_$53*+{-mpG$*r-!ubx^QPjNjX%E3Rsro_&12 zyl9I5jfQ03_xZSC-u$1pJS+?rKSfdeRci9}REuNUil0BfXjAxbsfxz>BNons%?joy!s56Z+LmH`QxTDgZ8GqX|bAqwD!=`jU+-%AT&vjP9WK`iikL)r>D7yFR~IG?A&RGxthj)}|AM zYf=M;s*xE!qm!mguDL;97#B}=At!0PD412-R35}+yFAQn{F-<)@4UIUF`Ig8ec-)8 z-IHAN37HGR2zOdR9w&N>2lIo`LG`Q;Dev}%J^kXmD6lK>X@wJ2{*~4CIF?FqY9#4xM?}Z_ z#l+GDLCyXOczemOTQgrynmzCPWHYCHtWQMLIvf&M`mRRCWyR_HId_viFKGBaoN#;D zf!w4xoz!c`0w+40p0{(WW_V4=g$?%nbi<Ba9mT~m=k>LGQkNIs`1VLfbKl8rvUxK%$NsRtNZQo6$++jN;+GE#-UXE$ z(%4ci&Oh|@ac))c_A|qJ-n%^yej%NjxM*7zrjwB*yWP<{VVle~jIO>kxAm}2kPR}4 zAl!JXz?~2&`^`SDaL(=+_nexPGqZO0ICT`v-o%|%zSK-4B0W>Nlg~7D-q3b6=$0w- z`{OvLMXTZ0$IT$$t6Tip+H(VU@7k^%dwhW)lhb#8Hf8gbXA!1eVH-Ci$*r@MunxjD zZ`m@stl#-QO`-)MCU&;iJ%un;Mun)F<1$UJZ2IS4V}fuwc{Ak+mC6Su>c|H2W#kkl z$Eu%EJ*K4peT?wSb8Pt5s6uo+Omqy4PRv|id93kf->`uO5&lQm@LvTZf`*5aF@b_2 zE)!!UKN&UoY+@lJD=)cbLyX~Yh=}C3CNPliI6av&qo*&G!^9J z4OFE!g`i)$mVdh!G}j{_3}VY`akz>G;zeBHBFP;Yb;+$8$P{%+G|y5cz!n5+5(&eR zujhU;mV`$_klgdG6YesQ0YPjtu)9yaS57$!a)`Ar*wHAZ%WaHwl|_f_+icf)txp${}bc%p}03zJT>Ed1IwW9|n3 zubaGzZHz_Od!rZ2-acD-n`9FEZeoeP3hTZ5^d8w7mu$6FTh7{syUsJ+XJ?!{w8H6S zL`I}f>E5V_=xTwP>^+0rI%J0FU74aKtSxHW)?T!WdUO1^dvZ_z{vWgY-IE^OU+|;m zdr!0Od}5U<;;m!=7o7gUf6I3tz14i!uFBdTs4;= z-^{p_n^Pm!Hy>YATm3n-(YODy!GsX&?r-PjT;9Av!*}SWRWcrQvj}fYiMLqVFik+a zV4Ya{R_UO+>OzBcpK1zv+m+ahClU|l&}2%gSjMnb)hPdWN-qPCpR2;gSEYV|R@eDsuC9 z!8*&YgpnNhRjx%J-1Xj2z8Ff;qT;*;3QeAF4ZTRMQmG@gO5!MzttY9zNuu`w7=d$tT8K=S;&V-=x zO~TVf6|1cxnj_BE2DTB|s~+W^%DH!M%RJRPo8Oi^$PD_1-^nO$Kdf-PY*d8qKe;5c z_0-uVE1%YExxKJ+Oz&=dmpHU1Pc0#;CikY=&Tp%JWE?3PFvOI08yun^i_dj>;kx{0 z?A13LWA-Iz<^(%Z%ursa!~8o{0kZFEZ+Smk za8-4*;l(JD^Hg!;y2m@43@k=Jtul*ZWtjbl(m5)4nqxL`Pp;b1EXDn;i>l=s&R8GP ziSefThJ|DASG~QgQ9_o#5!Q_Kml9u5OqF6hcI|RVZt~TR0HN&~Y&=yPSF9!J}^{NRMUP75vVfS?%qVqvh^%RJ*Uy zt2uVW;qvMNd;_W;DVy<8QFmhO55srGk5%Ltyw3hPjx*#9ztnu3TNX9(LRhBqQtchV z#Y>n_O>-sdyDcqgGcy08YT3X;?>CsCraboGq$MYez0Qi65E80 z{J826JoQj1MA)a;Hxq)kw&FjJUyk`MBNq_~kwmd#lns#*Are1PC{AZ5^LUt(vzN6P zjY))JNmx2xl%B9ih{vOe@nRlHgmWMgi7P-M5*{0i2^XP4A)1aR@L~jPkq9E8;ze;2 zWwcCI&7y|Hg% zEP+g=+E5ra92yms{A&yr1-JQ6-3BAs&?G;Wr%@&U83m(q5H5)nvt;IK8QJU0AErFK zQX1`$@^O~l?#bQ}+MKH?r1qD!?xk3Hfk~Y4fbouq+B&BiGs6i4HD!(KABz_aEw7#k zU0wT`ys*G}gX7&JQQtET)J5U+m1Ci%H?AdxT3rjOInd@tVXVeZ`EdE@o_QFcrMtf-UN`*Pm%W&BVjO)PXTd zVr8U!dJ?@cKJ39Vc@ftwLBYIe`xZ7~QE$kVI}u5n-|TNTJif1vLHFu!FhnYDeKuuK zn@zW`@EvL=KpTv&^3Fa8dGP3<|Ca#u=zTNSCDqTYxZT^kCb}c0<PiXQ>!#Xz;2Ne@yQbKU``V(Mc9L*k zx3}rmy#d4d?~3)-HyK&ArPtEkwl|LK%d+tezEnv!-~4GJDaVmjqM(%jEQ-!pMKjWW zHZ!Yv_xq1>re3R~iuc1s54ZYJu2IfCS#t?}dbg99QQ%+{xQ-(!QZ*YrsjJ=){0eTf&aX_ZvMmW*PJheh@BHVB1(ePp^eEYO@rY zhO7h18=Gop=h=MK_h@~b247hA^Ve|vlI3Zlj3u_qS138&kLN2ePS3h`I`7@XkJlc) zR?ilcY0SDhXVTk?%!09-r;WN-B}5FYe&6@w!(sc@zFvi+wLQLupI@&sM!Bw;k~TkDQ`8SuMS#^RZnA&6`?| zT;F#%R=)Om$CIjK@F?xTppQ?Zm0u)7MISh zPw&3Bi_0(0r^puDTnMPO+deSz462X3BtF`1*NoQcO@*Exa?(yCJzw?x%`eTPH>4U(IhRP7cN5(jp^Nclf(xWb5=oEBB;duqZ!w zH#(|2QVzPc2svERbc|1^CJPRmz~t%ew`)8@9?V#Mrib-v#>G@~4yAhW@-Ii^s-hVx zMt#Yl50+>vmg85;k9byWzAQR5;mzK=O{U*_wU048Yp#j;rrFEC-X*MmJCb>`NN{AX zhS#uz=9No=*K>c6E9D2L9XP~3esbdqD}$8#eQ zDfi|X-e*@m*7n4wo9m?j z-8ALl!s+VaPrv5beKTlVW3UE3ad)~^*|%d4GS{$kUDcrhwrgEbnnty$zSDh`vu$v{ z%F!DE9i$F*G%W9`rg1HQ2j!NG_A&8+i#gRZBOO*)DndJ)qwUx$bNxbYM&7pjKFg+? zKJ9XQczfZ_&$H9zo!ML!M>+Z)x2FmH6D)$%D&4|*XrA8uinO(M3O*$Vwx05vnyg4H zE&t~5vF1#+4nux^@cHTb%Vw^(b4YW!VKUTr_Sw(eI|G<=xw243{-L{7!;-tRb%82)A|lf;o=-q!NH22#lN~rre$`^ zdN+kmxgNSytN7&UH`_ueDuXKuLzUKj{PN|=BeS!!m)d)G{6gfzYyvhoWnS^PIy9nF zV{nbO!?m=JCf_}5;T1vOQ8TjkzB$I&=x~ORa?In4;=}t(+b#@h!EZmFUo7t1!Q_u9 zoyub_tE3!mJBhZ*_}VR&Q_@=TJ@u%5rjD9O`^VK`8CQ~WVN#vGq49Zd&C|+^_%E9D zd;E4gpN~%4Lt*3c*J{rk8uh+Un;zw{qR~iIz>g zrM2y}%&ep-^YouqX}&9MH9%ZD^@$-jGTL38t)8qb!A54T;N?FX)`%5sIIfu@dnAi6 z_A2e*(J{`Z(f2hTUn{c{9_QTOl3afCF>+e##!ttr$}e1I41AMNr?f|R{x6hkRAfAK z)GU&$|Iz)YsloL9KYxCo5)s|&bbT{=!e`f;&+_|I7t5$UDrldtY$h*z`SFgi4BMQ5 zcbdNnHq9AQ&XDmVcy6L-D&!Kj}7~D%n+ceD$sHJ6c2~r`CN% zspl-))?Cl0h0QsynYNmvKd{FU8lq%wvYV+4b*w+od^eKhw3EPY-uO+s8HqA|UA!!`a&~t+-cDX@HY8h}z3X!G zI`N&udX>oX@I7&kJ4aXerBpm0tSElz(RGFF(wleca&(%Gt4@5-j>tP-W?pQQzeW(7 z-n#yD_AaCR@Hxv(-yQyN(CE>&#wDG4={JoIdn-M>dPno<+(pIqLfT%l$UQsN_s?_B zR(;^sYMgmsx~G;RqkMNlh0%sF)!aG8eZTgbE$e*Kdd{g&Lv^8^C#zF?N?t*JR`p_v z;?LZzU5yBJz_d@dDB3Gcd5V6Dm7A5995>-sQ!~W3#d!9g-%@V8ZJY3BGDNgAgG|{` z+wc&7@6qW7gJsvhJKDd=$y}b@fE`O6y%13}b^U3_NAp!zZ<_Y0rFHbd%JWgPJPW5^ zUe9}T|I)hWu%N5>`E_OGkHwQBraUV;(VZM5Kd3$dsi152Q-~(yHcnuNYY#Ky!yao{ z*=?e}u5zNHa+z=A@>nO2OZgQ(Wfxqw$Th0c^w_ysEcQa~=@~wX8nUd1=bkKIY2cu6 zn@%ZIUtbX|Xu4#&KhWR}!#}HH`NqK)Mak-(6N65gc{DCtxbgbo2|MUiR^&XpV6OQW zg(^aPW-3$Yy7lg}Zq>KZxkr3;4i?v0mC%;-|33gHK-j;u3itx(`<_gTaOgF~Y6=N| zUZpk26H68yXb%)BKPA~R*_761fMUlb6s^KYEj#Sw$0Ubfwu}23xas&+5V=zDhNl#H zeOKNMF}s5pR7Zc`tkbzY;0F?TVlu%%;1y5J76k1($X-iC>VlHH{4?V2vvQ5aUlibX z(yk*bowPgIY%m$|3F!0j8G5X?2mXkS(f$+U`bNofSk=8DU3UEJ*$?tcoa%%xo>5}+ zC`fXCPk;Xq=K+&WmXn54r)g=Imd0_dmZFI;n^9Cyv8px z4Z;xn!rPh!^pR=|xKn7Jh?IiX7;ZTrrV8~<`bBZto27@F8RnPGv8km$#CCwtkZ{QA-K)*AvPYAWfoWOQLbL=D8 zCg91yuJJfC!;A{H7I?G<*J?NT8-~f2ni`k*Fb1wbM$ANo*|FQ-C?vDsb0;T4`)Lle6E-wYej?#Y z83-DZGh&$QdotxuxNbC-X2!+j$r3y_=q%yqdn7-hN0eVmeRHaY573_=z=7c4Ci#n+(h-5|TzJS72)D4`jJj6@djIPO{GQA!(%RUav~4Njv5#7H%v zL>eNkAs&NIqH+E#Ett?XX%H+!_2Et{oc4G#*!51;tRwiARPiL@K`N7?-E_+lt%DNK zd;)vdg6aXrC((!xR)(DjyUI55vczyy#ggO8y4QAxFQeawmhwz40ic3xzpU8lVpK5T zmd$X-)QB416Jm7`<09WNLeK7vKu&rP9+J6q8lcJj+f{>6t{BWP8$lHZsWZ56MF0-y zT0p}bqg6>~BjACZHy^+|q=Xd9FZU)m6=e2B2%W2!l{#6z)L5-WXAR3XV75Xga@(Sa z6AEi%ZshDL06zx>0w$z4ufR6~i>I_D{4>X2(Z!mx5ZIJ?Ce3AlqT}N{r;b3O_b@)Pf|5Pa6+%-Fk)X zs4;WdDKFRqSuVyB*1KpmAF6m#=F5!7N@pdRHA|2WDgyihDbAL8dI~%Nr0-d{MqHv> zlcXQLL{fPt)Svv*RDDH`V}dtYqyrhWPEmtk)ndn8&-)BN9 zfKfIn=fjBcF{R1*Ijz*hLI~N-P7x+jTf6E~cQn$bPuNiqoD%Vcns011o z%mCVJbf!v{|NA&xwkA?QR|6njI03ZDKA9Y(xxT!?sHDp|xYrj|is=@3(6V*D08J#s z>((d_$=n);?pYg0_7EiiswfJ=V=XFc$!e=4@f_ucQ4do#!PK;aQbnLD>!C@4@qP6$ zd55G8d~jIU%M%M%`9#~iPOFd2hD?ZbW~nxjG!cKgO0Ci(uy0yb7=KzY0c@#cj%+DO z-k`RyU^fu~2381|#Y%AiWo1=j1(1{3{Hyix(s6z>#`|o6%rmh zx^qr>#w|I^k}PyO^oNJqq^F7vg;6f#C`a7P`KM>mgK!-6JfF0AQE96sWm)hLdc*tgrA%S5lz2Xx}7P zfc!m{{#*wbd*vhcFzHdQG^?%2mqK8hypb|BpOfBjsirKSWA4EI=+}%_nHq$gG zHd!DiMy@_PQ{rxe1Cds$e?2H;N6Ub7?uILH!&esl0n?yV2pU*TX`<+Xre??wxCo$nPKix0_WQIS`u-n3nA^W!0n` z7{B6lQpq`(1Qy*k7!P4~E))LFJ`D_k)yY;hq0*5z+MFxuUh6oc2`0&b-r)Nn*KCEL z)iQDG#03Qjc8Nhqy`{+elljM@+yXA{I5@cnq;dvZDSk5kdR{Dm+uYYM*WO$5j&^(F z1KG$j*}IacFrkg8ZI87Bg=Z9|E!w*ndT{+sZ3jPy#=2IL1Gtk(eo2XTxpnWMj#xQdn#eAGS+#f#+sYi>`B{}uq>$hE+*24<0Q77Hq~l314uKw@o=EvdGHrszp(aWqg>5d}*)v}xw6f}#zuPnd!t zD9af~l~t5gWiGL()H51d@J*$0w_PCZ#oQw%$1GYDSHzNbaK`~yQLa1LYK~Nr^8ygN zLQ(Tc`fiq`1o*_~c4!G(CqYzJcdjBsYImrEz=MRj8!AEP>MKgozZP-xeB7c*dN(%| z8RM<;VPQ&JbYsI5v4`Cq@DT;5J^KMY)q1ji87 z=}Z|}=DtG}l|=(@q#CrtMhbQ6h>QKJ_Z0DKslAaPk!q=2mC_OBi6hf42r#zT!r3@c zm_c_!EL9M5rL;?Vz!@YpaH*hmo`Wrtvq*48mWyVx)C%UA^hG^bE>VP*rwEqPj)&;e zGx(ezmhKY;&Qp_s<~Ld3b73G_v((u6{3zqw8c*9Ue-MDk^GH~N;cu`knhX&N44~+s zaEPaaQ|qm1e}pzMX#?K^X|EJFUEC8L~LFoBN~*9l2LFss@i^Cnw* zPccz7I;j1FF|l*W51*kvc#%h$so|g=GX%k|T@5bTU@jk3BQ7 zbR$AtkV(cimCGyO7EoVVS%T8bv8)~ogaIdlKs?j zzXJU0hH%{=y8|V>bWWK_Pp2f1v!XEqGmY!Y2o`^&b815K-9H^+5vO^?(-f}jHF_VE z08X5$DeOv9+u^lt#Dz6GQ38>Ufj0tRc4TLg_~dMLkjQC2N@;TLBRwokO(~?j6my39 zBiLx8qDppBWnujAAqNd4!G=~$N;R6)L0N8#yi6BK_{^b35TaqI^R%3kODySr0KT*4 zj)28{q;q3{vp9=D?B_I{r<9uBke@G16aw~CL#c+LD>UnG#hNkSJRvPv$(wm zre=&ft4D+|hz@eVM%=nbWh&lY>Z}giHWZFEwFO}(7~_-bH#CROiqUOT1C!+VCB~A8 zRm3_#uQXlOqF=dc6%0{YM|5aHL=pQg9p(B@Ru$O<@E#0!rv;;x0Y@$OnlI(3szf2X zPUKWV-Y^qK*3s%lYf7cZ$QSaF7dV%+h0D)%btYF|&&;wUgV65_HJIzc*SfXiH z4c@XsaR^*M6zG*9I&;B3ZC7NvYdfs9n8(-BA5L(i81PCr;QB==#sej0ZFC++d#;JA z5+NMp#f~)7DW^!nx&&7c z+c4V(Tv~V6Lwsq2GKsO!LG!hP3d~ zY_+l)XFZ@|9b?6AQ~}^5g4)b$W_Jo~a~nPyipHZx29wPMR&+<`BJV8H!dbo#P$> z;wOGR#=2fK=U_|LO_P12VyLZ`K?P^~-$|Y?D^6v>aLIb)f{ZQAI>BiA(Hrk*>HDp-YKkp-@=8 z-yDn)Pwatw>-L0{Imn@!VujwWYz5fZWC@lQ9+c$5s4kj23UB~`5_60~BEsDKg50`{ zd*ra+15G(HRa8VcM{Y#s+XxKgZ6Bw{g~AW1$W$>_wk5t7o309@lH$)t7TMJtUR}+} zeI&W7tLiK!1V5C3?S**nHCTW{t4MpQ{s5FlNM^Kh^U6$N=);Q`(VYc3?WO&Io)~^^ z5kuQwQiVV2C(}^U(qS6J$=+Q`zrf##@s?*F4K-}_LGu8!_yXm4xz3OP>cl4O8I`4! zg*zHXicX{`wSzZLBSJob%}}n9E?eFIvH6wJ`%Ggu8x#r zYI3cyLq~hgeP*?^Mw0jMBFzOG-4Q*cEwD9nz3_kwUDZ;w>(KG8M_cz4ZFOkc$>MPT z3Tbkn6dyM<=_gckH+I^>xARpklJp&&F3%n$iNe#Fbo*p%x1@`4a%LzL(Z9)2cF&hn zceh3J24PxC_OLCS{Op%7dnpbw2luGePP?%j(3I^`phA|6z)QGH73(%Y?ojq841tCf zc3qY1fqEbAGQ8B6=V}E9JK|SRfQiCwZ@I5G)ul9et_eeo#$r4ezsW>9hqcxzP; zNQJXI^utr2OQY0m2y%qTmpf_;#F|_RF~-?>bO5L57z728B|DfID!YZY8;3=sVoc`b z%k))x zRd;Ej$3}@8!X`>q2YpO&!EEYkp!)G8Ri%8i5l6Z2jieXIKW?X0g4|Zm+gho~@3wL* zBc&ok?O9=~0#j9JJ3l0EfTuC@jVeObPUR^FIS9O-GE=HBT}aA(za2f4NunZ-O%BesyooBFd<;tP0yR z4HtD{rNk~21lm2`s{&tQ4)@~K^b)hUXYWscy*R(T?FLiVVCA>!8n}^@oOFMJVO(k5 zR|C>^k%{eDPpb@kAtm(AhF&Y=YCiYGTzwn4Mh6jQ5jq<2mmTT1m%PUDW~^J;NJGO4 z%SaB~kTy$tJ1p0ahDI^$Nfu-=pt#8`yUvpx|?!hCY-9msFsz-)CpZ2B*RYDe9)v_uF*I*OGw z!+5Kk8GIu&_X~2A=p=JV7plN)zDrW?;#SuL)rT79iQBNle~ZZpn#QD{&|__vc=>=u?NyuYYl5DXLZ_<9dw@Zm*D0E4 zIou9HZ9itY-dk3o=m+ZtkHq!Mac%TuAV_^^xB@34qazLiq=KOB+Xc6B)>x>sMSu6o zV($3l(?JMCJjpjsT`A()21W0}gC4y;Jo+rrA`J6?KTb~GytzJqLz&j(x9RQMWO{ja zesvCiUEU_Yy}$nD#+5K*T|t(oeHkt?Pih++VPFF@LY*Z!pOnK93xl31TTe=Q+HpRT zx(n3sT=T+5QETCGNu4cWF$r~IN+G82904C?FTJ}%+|+m!#SW=3lQau#&D(5 z@iGfT5#}J5L=_F3a)Nj_Uw7^mhWUkr_&ubMkz@y2IM{tyh(vB&pW-=&L{~FYZ;i;*5LH@F-<~sw1_fYJzD3RKDu7_r zKZvhL!!Sge<$;v+k-@Ah>L1C2jP!tys8J?tj2VR`rCb2v9Pk4dSLmi&{bNT5d|7=eI61%SqD2#qT7S=+&2*umQZG)OJz7b zD`r98X#;XBlX^!~Ln>_6bGp-(_(*d~*_`r2F zVm)>#N4!g+3mqv&m65%t=V;K43*tpH@hR~h4VLkchjM6tc0j^6#bR)ruiq+@X_mk= z6zfN>lR9?IJ316eqh_lH)krrNs6tNNeD^F_;7I~O83~^G80Kd#%F1y3GX(t0aI4ch z1P7~B)!uoHmZj|>q37ONU1wo(Fq#|*&1K%MJQe#Z+(}O+!5Vg;qYYz z(Gg2A^dr|$GH)c8?1y$}f6izZ#v$A@cOYOJmqUyTPHu&S1jq;2sdG|?qrA?kL!9d1 zbO;(g+=1edqxPnog|^()clFAaX$%()$hTuC6x5*R8{9*J@N_Z?G2y+%Zn{joA8M@B zq?}thg_%hc!|n-0?u0cylIz(xdHw6{uh-|v_4z-hHyXOQeLKBL-c3)>FK@yl3LiRi z(Kio)J}(R%ECu2th~Y+=l*iAR6x$1_7HuNwu1~qCBdc&T6IcY;LN8v*ac|Uot$DUr)%>bNG|w z90L|{iF0@~9H;(M$tvD8fJ1TFtut|5(6D{Di=L<~=6ocN_$75$9x}7``mWvE3!4Np zPUi+qT4dO`b8Lg=VP~UD)>!MQr6I!;LFz#d%D*ekUc`3*(&L(Nj8B^a-9a8LX0H|% zNRorzdjbPmbx3Tr`8uwVQ*$xO5TCi-(Yld0GhdZ_rQFWFM=g+F2C1AJ@d&t&?Riyx z+W8af7Vz~#eD`G8(1mib*&J+taNgi32no@UL#q{4E;~#cM4ge^f$z~2h_dhi&g`F} z%FwuCBoW;Dnr0fKsKL?E7$g!)Am4_&cY)Wm0RdiMbVj1_r$Bz`tmKpOlA8>pI@ocz zcX4sbzaIETerb5dWB8Ka5KF(|of~NLN)AxB%X-a5>Xic=>@=xc<1WtejciMBLUXQx z7PU!d4PdXzf?`Ctd>YH2MI<=hW*;-zQF#mZhF;5}yAH!9b_9IvPqzCUdOT(TDUus% zRi5DmB*DQ4)<`s>iwtTz@Z>Ee%rLeeNJZe@>FMFC4~I7=;V}=VSlx)=o>CjAO-^bT zxI4U|d+ts!`ky=>Ka1_!=u*(mFsEg;k@b$)H^WkgL)awO**5o&Xl&b;jC0r=7Cplz zE7S#eXc=t4Ydm%WPtg&J{}k(H%BslH=+XjVIaLHL-t&dLZAmuU>CslC<$WE6!{W41 z!2Nj{W!Okp8T};<9QUk{u#Ugui}VCRtuC<8BdR z#GUcyqVF`wKKQ$8e+ZMg ze1yGf!$jqnRz2tr^EZ!hG2>5JPZ^X?1IF)J)Uc@~wVx-=9R>}Lgu(%`AEVHYQDS(A z7GNDGXJ_Y^XTM%d61fePYa3dr9Ws-B{^mcv;t$=!&>5VLNOL(Ib$s*&q*2fnDrWT{ zGRTVfXl1VRs9QI{c(2D_#9;;+iTZ=$uFj+M$KUv#r}&`rjK^bM%pQPW7_E-*cOE&+Jy6`j$7I}s z8+SgvPHv~S@6K;VLC3N>VqGBp%OigsHa^-uiNFu!U-=TuxWm<=eJv~TZ7DSvR`clT z0jmm+7hao+TBFE_+~SpXa#`g)yDE6}8wo*>Z%v>()h%~y9ULNyCENFzzu=am0&+a~ zyu^m1tr_^ku{rTB?v6Flm4BGc%yW|quhE=f^Gb|`I;-8O-!1~C7fyce7&}ON`PctLpa09h{@?KaZGS`7Bzfddd>xNo zX1-4>aP-{)4TM*ZJUU2t&1QTbIuG>x2R~5qx1+y*HAKhHc%a~pr^#0h^!$hi5+VBd z>K+~FB@J}57f-%^pyQW15EcbJ zc_6uF5wBdFJZWj+1&}|d50d5Q2lDq+Jt_d=>uKU$ONb!p>QAbBTgKO4O}>1fA90}e z-ISBBU0Ux|b0fZ+4`7^p35z_Zfkc6PX8WTH7RN~sj{Gg0cq!-P>$hL-qkRgCJbyt0 z^<29?`D+6meXj%c-NHWk%LCE<+)vUX$3#b})jqRrj}64T=B{GJi&-a6U|PMQRc>tk zdZzs%J^MIi%hKeY>n8}d7sohICR0tG3eg|sM}y8q{q?(cL=gQ#m*Ro9K6#=R39jCy zyLi7^9>upl%Z1L9zckQG(jx9^U%u-1>*)xbQLxS@PuKl=;p&qKvUHcbJ&|cQeBIp0 zw|w?e=VR)KRko&>J_FI`73A>x{N(K7{BU9Jhv8+|lfNq1s%Wao*F$7Og*ADCUK`qK zj;jBV&kKU((c#RKzbvzz+870zY4R27s5Ow+Oq0JjkQ^=k6ixer2KpjTa*JlsWWfF>IFWz;aC9WU>7xETdj8_Ypy%H2KVQ5&em3a4jQYq=FVmC5j zVKF%|GC4G2GBYnjPf%G&PDda`PDN5dOdwEILQF|RAVp47MG7EREFdyDAWC&^Wgs#N z3Ls{4WLo1VQo-#VjxUm zENo?FbP98Cav*DIZe(+7b1oolZ*z1YaB^W|bZKKCWMy+{XKrO=3Upy>cVT&QARt0{ zZgX@j3UqE{XLVt6VPy&}Ep2mka%FRKa#spFVQ_G4b8lg8d2D52VtHY8bZBo`V_|M{ zEN@q6Wo{sMaAjd@EG!Cea!(*(bP8x;b|5HnZ6Il8Aa`kVXelgXWodI`WMv9>V`X;= zYi@6MWN&XEb7gj9Wo~(4b8B*PE^i8Bb8u;5X>4h9EDB_4W;%k4s1i$?sgV&0qM)Q8 z1!W{1Qc48EP#VS^A{rlpft14{MyP-wNF*4M83{y^Opt;Uq|zF9@?=u5C4=y3ct|7| zY|2olo??CxWV@LPLLNVsMf=;t{AVlp@AlJe;FC`OMXX){1%omA_=+*dzTQFdec;`M z@_MQRTlFy*3y&V3IC87>SvB49DDmLvV62uKEL=Nniz+oI&!uyvAyhmdLqp!5Vf-XP zh5&ZTGDm=?6YIzDbAyUrBt^i|uzo_eRvRoypN{;(#Df-H$Q4M=E+3$gf`o+|JE0yP zIWL*ND-Ha#nKFl=A60^5`eHq}lGA%qL1DSwFG{92@PF;nE|5k&F<3sB8 zOIwLgK~Q0Qsu@JI##oFTr6Y~$j7f=dk0aUpXx&#TCzS>`g-IHw$c*HPY3EBaflzxKl0C9Qsk55zSRD- zo;?Q%Wap*bw$LD6X@#ZN+{)vInlq}PgT~iV=;hg(Nci3C(Z;-4c_0~_25F3fZ5^h7 zYqNIX^3aPWfA^LMd{B`h9bxaBVIcV23@&ViYGGd5Ud}>xB?wqrBzqyp-|a6W{{dW+ zQmVr+hckrf!jG3HBZPzZe@KpMeC+|=&!n#s9YH&o{SJCUf@VB}gM{261er--21?HO z&~S)v__y|(e)<3)hRrqDh}ahO5%819uD5!|O+%`OnI)-08S@PXJP$JC?F>`r2ut%Q zdPENhoB1O}Dufd(djp_^0haHkim~Z|5X9RlMql&8W8qc;Aytwh3pn_m^(F=kGC+b2 zR_>?h2gAIzE$rqAzrNOfWngjOdvN40-i<@g;0m}ZL?3Ko9v%*V_pLZ+>y2|fyzy<5 zi*>Mw^%K`0*kbcgD?l^=APc6VJp-#=)h!=i6bh`CziE6HahDAqM6V9pArXHspoD%k zoLE)yEvAEO10pyDboh&sm1u6CuaS)-hi|iP`$gIjdwWY91 z*?G6YlqgJ@&>MUEtiD`%NvZeE#GZ=+$m|4z2CEt2ssE_0wx61*2g)l37*sqg^_0P; z?x5(p6ctk5h>KR#GQy+uodK5|vYF+5)4II~@}36hKh6R$;SR$RrtkD@1Nqx)s&wz+ zW4vf5+%s#(wn8@G;;F^V*-h9C3Do_4-3e6sx-eT-)$Um5cvFMY&h|u4NR_5S>6e;6 zTXLP!fVb@FL$hy*(WV`nB*ej)2?%wMi^p}eMcO&G>V9EUKo=N_Q@mvDJeSAp3 z&KO308!>%f6i^@=4k_3MfdhqvKWA*+a%J~h7`zg=42a11!C?~Y;DMGv+oCF&fzkk< z+91fo;Z$;XjAO`SO$KF_qP4TryCQLyF&J)6D#?&37qMf|jMQNM!m2nD3UwUFQyS93 zj60cW3$89z96ip_0an-u3^g_P2%c@~AjI9~T*o&s=!de_xO7*G@igPn9P8IC^&ITO z05*NBB>;1)12zG&QW;qgB;pHE=;J=SK|7COiQRe==6;FR|NqPVVFjZAoB*c)P1D;n zjdSebb+TZ%>uh1uWHdA~P;sEBpeP55iNct;c*tl0@pYPv?!HZ@97_zX(yUT|#XX84 zy^Y*wjAKkARFQk3j7qU)wz)dms*`Ni)+7|)&P;-`O)RSMF~^T}(gwM!-00wPZ{{%< z;+P!ucVuRqshN9vK!e)0{l85!~9rRVhQCf&y!BX-`NjC||i97HzFRjY_m4|GZg zn|O2&lb;ZVg!QpL$X;!|Cf63pT%Rk-v3Ecf6gCY7Lq#NWwuM{IB-ojis@b8B(MEzqaWfTSm~vj91%}n@Y8&x8t#UaF`|>ujHEIGi{HC?Xb!m1wCDw#+ zWD5fgAa7yKV?T+B3qp?*#LyM>xQIDU%stC;i^``pO#+0OTE;@J!Ivivq)*8JF_$o< z!@MzR*W(N;b%d4Q0F;f0!4*JUhV62;Vu)o{w?2Y`f&_vN%Fzmo3Wr2ud5>A-lkuV) zBq<~)0TYM|jYvU9Iv`=dnA|85Uk<;b$1V$X{of!9moxH7w(5^g%SV{46Yz35Y*#B0g5W91bCZS4T=U+FaQ7;ELdG(omYY}|tvW1PlEld)oaQsFa;KJ8) z*U}=0-%3I1o4c$;Soh}*Kn#SkZwt4?j=r%sDl69deDV^Sx*GlK)ov=ducdqBO3Db zl2_v`{wmYw{86@d@8qg{Y*8UxZm+*>*^YcXvMgezXKNU5i`?ER?@m%!PgWYpNW*XhOPpmXiX#n2br3sNOGE z?I?W59&S%|u9@-u&~ADue|C#?+`9V#8grIuL>J-%?VWrSK}LTg^vziqxpLz{C^rjm z!{L$b4UWk+ND;RJ(=XjnS12=zyt@)PV*CP(1_4%1lVaqy*wm9s2#*0{5kW#2GFSo_t;F*QpsX%_YY)aBzfS3)RD0p8 z?YYXl+t#Ajsm?wn@9$?JUaaE6edYE(6P>cuJM|c#Fw4w&S~n@3`9zKjq1*J0fHI~e zfjn}&*dm1Wrb8V1CW)YoNxRkh7F-=OKHuDAO0Mf)>!rFjpgFmA@%w=9UN1<|L3U-`<0CE zw+J94U5C3p=(!ta+8*iR*pWVJYAM1j_*T`_Xa%F{bI5n6IiZ2f3dmf+kLZ zUByh%eQ-BrF;?c@d7Vq$)d<^ooIO9)tXN2C@!JzAjU!xx#Cc@qpRMezsL*D7kSqbr z=;n9&x0$1^y?L^)Z%X9L@>n1}3kw|5TixgF~Ix7Eb%J>^zAwNno zPoCNoxirwNo->3)0?@nDCAy~6Cr#{X$;q`2!Gd)kS)8(=>PIr{Gz)cVCy&g|FG4I< z#&kNiXmNR}7%0leQqH6}H@CV?$SG+1SkZ+P5xzH1}2lflP z0Spq7qXCdZHEeTFKJ-{h!j+LZCqZQHadiM8U6$5lZgbQ0CB^Jv`n+3$F*~I zae@&7y2IXQJ&t(6lmhG8JHQB!S&i-ry!Uh-2yM;YHo9pBkr~yS>8mCnPly*?-A+i@ z3DzXwc_ZRKh*7!&2ce@`v6~x3V#GlQqcRP%zj&Vs(E|7~NPC(+oe3DdMPbs{!)feW z;ZT#Z@4XTeXKe}Jx2hoi_HaysoKq*k9qa5cT|M;XN(g|a52Pck7>W?|TElC8_S2+s z5OGqpdgapoGC|8u0!H-BE5cSCBg6^n>yWbg#*OEwEdJkr_!K$eB>gXK0@aSf zKPL$q@|Kcvr^;0~F=#I`MQ$Lb>0LjS?8Wq7`TkTB zYBo6;lU2NZ#go59?wel}I&_J0PQw_0va=u+Un>6oGf#V4F(JZMHa5H7ytK2@M1+LNRG=K zfsO!~jR<>35Qe}*FWti+>=Sk+KpI7AMZCcCKqty)xZTo*+Fy!k9@NaG({fNB-|@*0 z(oW6X-_OA*igV&bQ@V}^4}yINN|}R#@0o#7^(`_!IEV$wnNlC%2csUJ-)ah{zzoBu z5gU=v_Yd#+lt&$8rUY7sf?Fm*Ky)z%-M>ri!#Y49Ei6=W2lx3KIj|6`GS zCD2imkd=BdqJu3{Z+^{;6VT_t0j5xA)Qn~I0fKO?Zl;}rvz7maD^@Y{o;(cY_)+G} zlzLBy;V6KAhYU<}wqUi1|QiEW@vs^9x0KjOh**^;g0kVh#7N+L8K+Wr_ zU7z$8R%2V%PdPBNra|5Qalnv&-WC{!Ap>dLgeMB2EVI<2=NSs4YU<_oi>Xc)SOG*g z!Bfw6TZp3QznM9O14PgUZZ8q7>HTF@9Zoln9S?7Y##*75PU*EPH#sP@qgT?j*%vvn z7cpaHNAMoed9qnW!_D0;6{UdYId~&&EjFGJ61Uca+h+}dd=!iq$Z3eJeD^-(aV$Um z522Cl2Ilrcrls3sSD_{qY*qOlTmQhO#)RC*QShajQ~+5LXlKD%f;P7_L>md_xV7$l zhcDA~3~vK4z`@zZ&d?t*;(L=El%q5*;F0+9Bstd0PuveXIKWE@b76{?xqz4qlH2m; zq)_I+#*(Aob`)vUq_m5a!;c14xIvRA)TVVTP@E8R>;%d4E@omoTkoVkivZYzz7v9|m$a|&B6qvSBngHcMFAA z9I(YIP1=tR<{80?$c}~nj@E-SH4x}R6afJmE zk;YwR#ME^7V48VNbIoYONk<+G=-TiImjgv@1^8}Wc%nc$U3>ix`49+Ve8yno&JdG1 zF0e0VoXw_zz5K#3%%yNk8$brf3tpSn)3I8=NB<^z&vdw$6q|R<8SPOLOhWt@?cO9zS zFsZJe9Tt)uc|VaZbL9Z%)@6}wW>^&fmg&CXyfx%{z-)MU=rN1hYF_b0)6bs?98YPA zl1*>7`~=yBB9|(kO&7OAT}@I2@nHuhO^NcDisv~>Qg?Gu#q`JmFsLHHeQsF*56Q4L z?(|Y+r+PqW^?>0Rcu$N9!liC~r>h$ip0Ti1N#m8?V%-uxSHoSHOKR)Hb z&^nS1h2|L0vEtLsewPIEY5 z^OE7Lz+kTJD^_xm!fD@_#}?Z@LR^ALT6Y)bCBMbMlnLlXp#QR53{8m3L{iyggtzt$ zOMgg){gkR{ttx5yL_AvrD_>}V(qTJTv-aUB_MQDz^WpoTo@R~A%g^*JaqN-f!;rPo zrh*+`XoN-P$0m#PX6C2g9mO2SQVi#ZJOpsevNS$H>Mg$2Kv*A&r&`~0z>PYpMYj`2 zCicT<@AC!JBxPSX1S?e8&5CXAvKNR)pE=AADfn2(uDd*Z(QkF;5ujO60ObnKRq4oc z4-!+C$kmY>(8!26nnDM9)*;k6-lxD~XF4x7Y#{hp6=^t*{eYNlLXmMB#*t*w825^* zo~Jf*_#=`!27j4qGB9lsY&v`5W>M0sNn<hxpafH0f5FV5FC6Q0-1}wI-B6OWo*_JH*aD=!dgi*LO%~Q741ik zx91bh48vo%9D|xm`R(H~uocUX*2Pjei4Qpk82ySD;At#3h-%~u$Ic%8LIg$87Wi|n zgiBO1Xiq|7h?>V}C{C0N1vx8(!Q|Ny1)IDloH-lbOniZqJd`@A?(&Y)O$LXfJVm%J z#E&8eM@Exhi!7EbwFw&vL?QpVQwQlnlK!+rP>4yoPjeQo*iN&=VX;&Bokys!{VpY z1iB_23JZO*WS}09r(sY>OUkY;tCzFliwa`3Zzcfr|Kuz~q^FrgX5R&YLl-dJH>s_K@~zdfOM@tmAv3ZCIez*Kj`JKj*RuM15sDDk zjCZwjpTGmdg4eYk9U1%7G|#-(NVN$DiON~^HU1m`5_1*X48k|NQ96{i1YaBa!WQI1 zVM>+)D$`5%PppCp)F=+HJYdQPsz~f6dVs!nI!OpcXqV|IuDZYFpQ&oTC!bgBFi9eX z7C4*hV47+ioes(3od|yP7`{1DMzal&oB>AA1}6Y*N1wX_QfA`>NP-vrbu8q18dC{| z%n%{l(_^DMsq~A}@04hy#DK1F`fkcC%vHzs9TH`O5Im}g5=lyWC>x;d3doMQnfh0h zwLKK^RAwP5a<42q9XA-1`D+~72P zdGq8@zMzZ&_2eIvujXPJC;7k>Tt>@aADXR_x2;zuWIA#aJ*$rHyHD<&? zf;l9&G&`aSDHi6gl((b~;E?qb1!@qD3W??nYb_uv2r{XD7oSK982M~WxQEMe9}NdK zl9~`t<>)`3_?-7-!vc7EY#^W?KpOcJ!0zvaZ}Nhug~>)(*3xZ01vzFF$=3rD3h4o- zK$Q_RaZ-ixHII9%NLTsjg8~Oa-UbkhUJa6R;i4n<651dJ`d6b8^Vy>L<`V9SBj8 z?0J>(GFDDh7qHP*xg*e>0Av;3(*T92Ky)k(u~+#1z0lrPCbA90B`XOc7Cb3yp2M;p z2;BC!9XHJ;!MW>LHXGg-7OA>3fS%sWz}k6GLE~eV$fqD+SYD(Q0M9)yzh=}v=R}Bk z)Fw6#$I?JFC6U`r8c^qTdwz$*nw&8FKm7&YQ|bY9+Wfv!4Qt=)i$S*uTd`d1I>1QK zYCMAgg)wB{1w|WvHH>nswQg$W>z*Nqj$GAwL1LDQZmKL-F@ULyj+h+iE;f!>RP%bK zGH#^QJO-ro0qss$XNKdU+mT4e>{xXONxytgxQnwP1^0l?aHD|PDKuxvCMZSS5J=ZKFl+?cPJ1rkt{!bGX7DO` zpc;Qv5ZG{ATsVVtLR(z0F;tQ<_tVs?h483GANk0R@&0t4kR{UC*8C59wp0hn)2^>X zbP0<6nRP!h0ExIsa0N|P=FgO=D8`VIywIyFujq72R#wq_vp-R{j1m${AVx|tK2R`O zeVI$X8bgBe@p=Rn)QM}b$#RfJ76a!&vva8KaB0$L;%BJR*>c5ZvVjOJmS%V(YOP&K#AvW1q*wLjvMgYWV9|q86ogw$8DHi_FDhdd_jQ3h-+#f96B^8$Ob; zo7_sA8NuELvy_tTVcETAp?Ok%k;Tu0PtsG|(wjaXT$s0jeh&zM7$?l+nGmkKUbiRk z5o9_2-?Rg?u!-LvBBQAvG2-#96q9xJvz9?8MBs{riZj#-3PE;(rd>Z4q#M_QgTan_YeZoAX$|Ff2~7Ff9RA08;=9K=i)=PXGbm-gce2b?qdSbc)UIks^d8j(JN>$TGE`7^xjCg%c0Ze&mF1p+}@P(u$=BZRM`9nk+R`v7OrC zrh<}NHsCbDOB(#NwQ*uA^|bsVNCUBY(+6aVK}uAlWrHA1N@;{sQzu4fSC&xdw8T=v z+u9LTUq_;K+R+d9BdICjw8ebsUzB zDT4J)dzO6H5JwnAB<{(>l(|e!k5>e9_EU)gO5|OTml7{7P;|3*gqV=i!?NKU{i6i}^fC zKvI;d0LpO>rA}qz8!i+eTAVM?kS-55be~kkT_d~73^q8Rgl=7q{*`0FoLV3*!pT=W z+%Y`yF-gIwapMuLkv5Z}Ah~%5ECJ$ellGKc&bdr@>-Vk4Y$I{GO@w{uiWEGa;hN3( zow9z&Q7(*AgR11Y|N43j)fR!gveWY|0I>=bR->$6@&pwF?vnqqAU5W67Wz{MMorAS@kKF#T4-*0f2^_vzRu%zPwC>Ab8ZhQv72nD#P`k*%~vDj_q4 z^9df|Eu~j!C)*U9;Z~+`?2N-1gs}RDoNF7D*w>pjE1%aE`XkXjvQF8bATQzD5*DtO z-)XaKUkkEmaYh$nm>^Y9s88?s-3%G?B_qTkdw_xDs(>B8PZQ{6!3cM?M%=^HCoYlp zbQE8cfmgH*+oX_EDavO+>?$hiQ{>F17@Bd)(9J!qdjH*6PE8b=)SI6psOj(ytm3})WC+t_rAj(9yfqJO89T)^MP#`O^H_J;;S0mi+!0* zQ2@n%%xWaN0&=-|hnP2O%Wu~TbBhIHQg{}Cy>roRhrMDF(Am8#nU}3PofsG})H@ra z2Fb^UNc;Jl?K{%m%rwo$ojTYpjw#Kkd8(ZsG(ltsQ}H1!&8^=lg0xeOcs#%{5SKd5 z{FB~rt}rH6C5z@&HgBSo(QQF`TXPO}mg6A^An8fE95ESR25b()ck)q<=tG+y@CK_H z1_6@aT!$caG3Q%)1NFjMl7O!;Z6G?=4iK7Z))Dm&?~iY++XQ1=Y=TZUm9X!dx*{mp_vy z|8#7SXrO$P!*fQIRoR=R1C=5X2?+Nt4n-bO; z@Vt-;5(bJGm^>LF6)k5X5{$2rZ4W2ARi>kowV9Q=Rz5Kp~sz;6L?DjZ6b+)C=rk0193MHV%I~ zLa?M3FgSWgCZ>V)(gg#Yro zh2rN|H&SYmOygmY!9iMaQZjcQ*7KrmIrJx~DK-HeI(gV0yTeD^&c7~XzRwSfZFe;F zoO6Xl_`^OJaHfOGX zh0Qozd}ZKimiP^WXDNR3Px|#SdiKos82A@Z{Sz=u%_T!r`8Q4N&O1}=JbJPg7ctN( zf`Ifks1ajhj?V=8K@$gPx`4A~E*hTgY;dT~CaXF26lp3YR(M>&AbsOg8_Y@`x{y*^ z@s+1K6aN9V-faIX0n$EX33%3ZERy@8)1PS`qxS5 ze$`*_;UgE1d{SxbKSSJnLhZ$`1^DN4QMR}?!EB6SpVJY+uc~~00ZmsIezlQ}biEJZ zJTh0r7Oyb?{x>~+HcP_!3Gf9>l8M;t*x_Es`y@M)Zqy=YV4ZsT?NClg{Ivao59@}n zAQHJko53XlE1dyw*6{}(2K46!=7g)%&RK@#R-GDpN8N6R|_9LHnrJaEPpE3 zBJ3AL?J*1(40AmM-ey{C-*dl@5r!zKvUl<}8s@-ZVAp~r$H52ZpMqH{r*^l?P-UA#q^i9Zt@#SG&%L-8lZ9G+ZO zuOb&V9drcZt)6|5fGQ=IC=Y4aFh4m__#%fIo0)=sQ7wz*S7^;}|M4I~t8@#h{$ny7 zf|d%T2sS04*W}QE*onOq1hM~-bO*C*QK->Z%s8E>7x}Ecrt;sK4+TX(1dVA)3@&j; zm0~5)vqXp(JtJ^}G}+YX5W&;6LFtkgbB!iq#%6r9JnKu32q4EBXEcZoe%hiS#F(_wP$Yd< z$!>bXp`fO3i2_pG8Oh%ggI`!VwNEmln75SCN%g^YBwyj6g6+>Z7*Qz}A-n*g4b|wW zfV9Ukq5PfAguxNOa4$yjH7z6!<9Yj!jioJL(X5v6V16Kr%;ids3dGb_NsR!{{)Ptb z#yNjfp(d^ z`p$~%GVj75RbsbOm<`r&P(`Cw^%^=&S+t;hzHcn6tnipnrQgLVQoi6p0HGK<64>wp zV!=b>dxATn)S#&#HeAGmgg~+BKzzo3kQ^NRK*_YH(EVYx6_BAHi!gz1x7j&r4W8x> z)OP@?M@$h&V4m3qFmS>tQb=FI35(nL67q;nzV{w+j0GMDbKn4^RHhqzC$5Hw8coC6 zC5qu2UTDVv%|XD`X2D6jsM>~OJO$rZ`%pML`Tzg{2LW^Zr=-&58ym2r(|2|vfT7?; z3+p!IY#>L6xFv?krN_!o6(tL@f~2!403!gXNkhecfI|S#m#C3MuxNWSZA_$GlFt4g zOLeu~HPy5zHz#(HlK2d(s!<;1HSg zw4NL?!A4jZFTp)2HH>;9#=@h~AEjtMKUcLf2)Hv_Fh`9~6Aesg+!BZoj69Z4x z&~zMVxqT(z5C93F$jp%3+&SePn0H-lx3;qG4<3+cY9!Y8e6Ws#WtH$`N~;ry|OR1SKAod zDcSsL3zlPTJy$kYvGv*fNe(67%C+(+IakiqdEW5rK2f<;@;%!W8|=3lZfJ~+{rO~m z8FvDG-sUO)k4ewK6FzU^mewD5w(=K`ZWne*yxi^UZvS66O5$O_eH`xRsFP1q6`m}E?YnJLpR?$%T-t&4=n zRTvRBv~kd_GSO1KNg224TDGd`Xza?QPP9|BFYI=OJ!z?o&vFp05p7pkvj|Dp^nPu+ z4x&Z6Hq(i=Zq{C&tyS{-tjg9BvX`kme4g(aC^zk3?XG8FmCxum;Bq`2?)Hyxx>suT z9scL_p3hhM;ze(>==HoJ0|?WpY8lznq)>cby0WTorK?bXwVh;p+&1`6+u z5QsVLn+7sWMl@$SE#bdblYAMd3=1Pgb0GZ3-}llg3e-|EViL1*xIp7_O zc4t;MoK`ybn-Ho}a--7ZA{kzYdC06akG^{X)i3F<9c7{wn)E#ZNufW% zsL?C)FryaBHlH$qrmY6@D3m~JH%waL`|P5@p);H5kKyKfaUf$Y=SCDQvvUb>W{f|Q zpwdy=x^}C-6*o@HPSa2MTn|#*jBoW2yW};pbj`e+ssYWk?>uD2GEeheZ)#jlTQttu ziSb;FKuxz1qd;+~axkt zps&z3Vmm**SoQJilSZK%ZVq8MJZlYPSg%7~5x1pUm%GRt+qLB0r4KTd6 zE)bU4vO@f2@p*{MO_kt8+vE@%cQ-djW<{{0#OUjTFJ z!{Hb--DT1n zzKFaQF13t@^_f*{fa6LbYY5CKcgC9hs`|_FQX*KqSIN8wdNiF(KWa^B_i7kdI6jEU z4CPS=!(jpX!+TISn8teqiPVNKWj@u%O6n;j!-@dJAGvAGGeGgS>fF*#b#VcHKI7vc z=xV#gZse99jPwIU=z2i>vAMsKZ{L2#E#8g2#>|Az*;?zmjfQ*bRJJ2^?c0cR5L~@5 zn25{QUq=B)?=!*$G{(q0Xq7@i1nDe>V~9Kb=zkj56s}1ZuE>6h-6E)_Glpp3S0;}z zln%?3Awzc;08$DKTQa2=Y$|^MUzYh^R2@k+Xnl%oS9wA+5DWJ+9HBU=2Vahk`3oRt zqv>nsygi754u@r`SaeE8cC7O#7>*0Jsj5-gtj&BMiW3_?PEg&xL7X27q_8kR|NuZZ-|5uPmQvVWOI3j$_2@`_z? zc*QAMp0$w^-2+xs(T!&hp_sce28GXcxP6h=D<1cw%VI~G*7et{wZG|`Q`dg{>$>8Q z)Dihj+&%S+?T&qNueqk%ZThzntF34|3ZPc zleO>+9Ki|cDov-yjxN+++xE_z;+c&yk>8u>bsS4l-LQUk!kOy!VQU}Sfn7MF!(nB# z$ejALRM4pNmu5r(DeXco&yua%Ha2JDG)J))$))p=3jMcPWk?;F&3`ZGa(pS+8N)!L zgc8+k!0->AH1&z8Jhjh%DF$ijYcD*y!?QkQ@4s=^2~*fWW- z7&6k@G{*meeKavf_wsSih4WD)uvXhFF!|KdOqz(x)TnuO`uAx*BomM_$NW=oh2*^!OGwC5{*i0_{+jC1?j(scYL?1uJG|^k=Es*Rw0YwXY%^(=td^Dd* z>YXU`+}y56YxHmJPCdS^J(j;u3}y^fO}IDwH{VxzGx&7NpW=iIhfYp%30DOMZiNmB zYp~!yUc!~!@q{;Y)bfSBS!&fc^>(MVBS(Bj1Ei!U|1HG2iMBd+JT|c%LqCP+J$ zbW#kG=yXhmLWz% zgitmHXN#FUia`=n@Me!+A~kW~O+9Z9vqz)S@Oi>8jqOIheFXh&2IsOuy@mJRn)N`6 zqbC-$WfctUTI}PAj>!W#|5KbDX^n%1szOIsMQ9S64yTf$>&BlcCB_X6U zYw=ZsjO`*IZsM{ZYU zMugHhSm-Jg7JjyK&saZykk|m{A=6RjM7X95PfCu4&9vKc8%852yWIq^Y5bFUfB|k{#48(^DJou!`iw|4BZUFHYt;M zp|OC#x!r^zayrFatE~Xv3(_hxX;ywH#&2W1sU8|BgO)0e*rC~? z%ZXTa=(X1gRa5U=s*YWTNz1Bam}Oq5GmlR_yeV7s3#=A+V(@9c`;V8G z=_8o>9ZH>E(vdLngG*#g;PxCmvau=(O8#n&owwfz(Y9GZ;ET(BCge4=&x)LNu>ghyB%*vsHvE?l}`nMAprZe^UL5Y}Fg+p{UdC8%BLj)5{} z37T=WD$i%fuyHOpOCL0|Fe!b90kWG2!%uq>Eh7`Oi6_$!{f}+|dzjl&){`*Z;1xA! zD2XkFV5D2qO{%ljfUUoK<|t~X(x3@jDN|(=tB*7NE{s)RDH`bMmLKt}U(mK?#nFen zXPKELmRT`;EGw{Q167Jvs3R6*N$@TTRJ=4VcHz1g7RtTVwj~p)!!d=`(m!1nYWI2q zAS%r_t`Oo|DI;<}+Yg^OyEIzS&N44@{-a1W3lkBbwNb1xmoMfm%TkVx@RP;?1gAx; zFjq2QqNdeh%(>j(%7c{9XbcCJ<)G#Y=zK^~ARAa2?yWHHK;SwFW(brBjkYJ=C_f!I zb}1?Ig7iedkmx5c8DpzLFzLvcas-ci)U22RMqcc+L-RPa2UC&+2SoY+8nDnPPau*? zZbN9sRb^ZC8da3|s!B>XQ}SJr-%WC1K>=wG1aaPZnY#u^<3b`YrikrkYAtJbKx`#> zp8H{OsO9eNOS=ipKuXd|I)iG5OF?W0c>;0*Zvs4EKWf_u;0^|u6-8HFXZFX{ud{a6 z%a`wFv(NETK8pKtD(=3j{?13tm&Z!p$NtBlaEL@eP-g~DffF}-OqIK%=DfgxvH>#! zt?P#k0bsZw?8!?$0)jhoK~Y%97{X&9AhxfuC!6y{=Kh$}XE{x!emLs&MV~odd(Zo2 za!KX+Q_0WA=2S8+xBUHdU*8||nE%cD`^n~#N#6P*bKB?t{^g~A^t;iXiX#44dV*?t zK*y|Pmx6%9V9n2{vN&&hc8JKKra zHr4G-iwksZ4UP#Es1s-h(6YBFP!(%!D~+p2nij4!CBCbxtR9zD6iHMo465?D?cP?j z%U5*owJu%Wy7n3z7!t~mE(>%G+64*%^lf8B*PvR_ttcCG zzd^I2aL_H#G$>b84(bMlkG@t1%-L?VW6osqsUOf#7!2mvkN|MaNQ@sgeAN7&R9!Ae zVA+fq;mA^Y!jD79P-A3M^jL*=P=laJDO+UR;FuM`x$s~xS5(bQFqWhzy9 zR)4vy=Jb@zi7F5K_LA>Mir4QRy{Af|A~xNo4!bgydNZG9ciwZE7c?(@emh!?Ru|Rr z3@u*D%DgP;aG4k1-R}3y%3q0AD}DG37s!3WarFA7e8d_t(P|~3aQHh5%NhWKAUio$ z@W=?k=!2YM+`2p_BkM?bZspI; zbMkNHe%8-gmYdJ|_5SraywT@df8=Yu4`uN8^ZC&kL4ucabHE7!Oq8%O!$S)#+)Vg% zt@)+E1w~YQqay|oBQWtr2FSQ$V1*JC$aPpL$i+#?fG!f*I3aC72NTe&f&fXxpv4H& z#0UXwzFxSX!t-EP-PUHKJEPlMT6Jx7YFFJFUE7xoU3FVbUKKj8x+`f@=+@}ASKY2c z*Hu@A?#oKdmke8Vo9hZ4SKXdMw^heg*Ht%#j*V_})qTk@h3<<^18(F-9_tbNS0pzg zJ5unbC`ANfhHxX05I9$P`nU89%d9`ju-`4;oR;7D$lV!PT)N!X`OzG6m$&iUkNO*= z+5J(f^-#j$kMPFEh7D$jpd+)vvANi(~|Mw930WRQzhd_!lB=kI?Aw-7DTYyLd1rRJ)2`^t;*}baSzUkiN z8+P`l*7&nSmX8L1c5bv7Fb~P`?+Bhyn$o5 zf4hMjJijeN#s=QF@dNeo zfKVZZ8U28X7``+$z;pm?eVB&eFt9Ob6;jho$CTMq>2%-yFlJhYN9Bc4v&HBjgb!-= zof_W0LnCJI-~*Zc!wo{*?Hlg)7&Kw_4tHwCNPEyhjZa8t>b{L-36_9_KOo-^i%t-c z9~~J~3figks`EePU$wf!y=mq&TOIG1znrgKNYxbzYIPOt z{x|*{_PItM?oUHNAU9_L9DvOyT;%XE@KU~{oDoN902omNT+b08L;(Qne9kI3`t9&Y zgDfVPIdG3cMIhw*)oPekdyz^%7l}-9lUjw_YSzD;g<;N-KCbpGf4NsW^6q}#i}CNC zBUkUNJD2&9AK|7L=x6vtf;*riC8VN7iyVS1q)?&9kR?9`3H|&87q}GY+!UR7QN)Wd zJ8iuvIZ!=zjK~oKM)b+As^`fD13qv8v+#&#^%gUxtt?9O+k%l$Ra;3@C$_g1VD^it53yU-jiZqSMboZ7tnP$0Tz^;|v8Qt< zQN_xdIQQ5FS$1Skl8l9A*`YbJ?8LI{lw{d0$+9b319OsPmn6%sZToxl(yU)O{qq9~ z-cT8>2+{_JgheXO=u?Czn5UU2&K#Z|v^_&m&79B!7c*u!n5kd}_oP7!l**PU&Jmt( z&oj9#Cj9WsVkYO*Vpf~knL(dzPaZpblBkw=a))o5hD1YY1^_W5;7MR{?ac1dy*T@& z>{+GJZu&w(Lg3mk1|k=EyNOIZl%0p#ot`{L@;LVVsGK>H3%9RN`ljr8)07f%I}w$d z*md4K=4)SH*)Lt*d}kkXdFhjw*m*7@`Mmb{udPRNVFFm!5lL(I#FZez$-=~~Ru{LDFLZC^&cp)CE{&yT zvT}82vi;Z#iYvGj?zbheb0h%)2oMATWNt^~;wmf(NFatqD1c%Z3Izy2L;#Gy5MUHx zfW)Rm2HcFd^l}kDTs!nw$=+kD>)UP7Fm`HMKEgiBz_1dF#t$fLnAsgCe(`s73h$3& z%&@Qb%=T-h2$D*Sw{#0xIXH8fOZ2$=E3wvBBpiv4)rV^FFJ+1rteK2A@`-)MT5drH z&;-2bcGhw|3<1b~{VBO4v|_PR4P7S;&yA!ka76#rQ|bszy%uzaV@UN2=_Hm9jt4~~ zjGa9NpWS&mGgsYtz$PlV+5C+VuE~e_URt;w z4zgNSjy77GOkv~I#^lGj~S zpaxGo^|oR_c|L`n-yj02*N5N7m=3eT3XQp;zyA%?2n(K>&DB^bRTa~e*UfimY=`T3 zt9qn?D1Yx-eTKWv4t)A&RiW_xv;{IlQ8*+!1dmGg6?Z-$y21!LU?l5WS?6xa^Rf+w zJ3?4wjjqPyjG4`y& zYTV%1$M&YL_sfEL!EGxp3ihx}mDfq7Tq(ZR`K+r6IX}>uiMtkrm(Yw8roMn#r4g_o zS?xZaE(j z&7x{$fl+D&B0*34#n2-!*uwnT98-4Ln^PEr3~Ab)&z2grY6!7xU~#T#P@Rz7k#luP z#wy@~njqM*1kPDKQm*)uM62#Y0Bp-z^DoyTCjEd984)ACXUuJNI>R(-VwkZ+rKsaw zNW1^yaO2216*@$0d-R$nGL;#MPxV#If@bD1587g^8MY|nW(}^-bD+`{1G;9^Ivf`7 zp`o-32&?w`;1(bd6I7hh(bYC4TGqX?GbR{}e6^f@Fxk^CN6tjpjPOm6GpWIDB>zp4 zSPTM_yn5hQEud1gk|yvq_F~nWLZhgc@AA@Cvn73}pHgbyY_#^Ok}%Tq(EXsZ;0P_cv35&wt;G(x(g>6ORDw(vdJd zKD{;qS?`{Ca35W=MyWqBEMTH=0zg;A+|u|vyk)BLp@PTtO~)|W+d*ejLl|dQr8+71 zZ-MYTO;)7t^aD&IBda`L;Cm@+LNDo2R&_p-fcJ`REz!B8g)+1*5RGwW-31JR;v#gxSHprsX}e1#Xp{Qs_MxxoQ2lV zlFs$aHo}?B0F;@)?&#WK&xg`6Rnx`O+G;jKoVHoa>Mli2gfr>Z(v-EmWEPA2ynj1gX(X_wrMmt{>yNk7gpdXK*qzTY=`|mD`et-cW|@OEVw9#dbnLmr!y)? zPqxyzV&qi`jqkb-4{Hs@2lkl?*k5cy?E#H%Yy!IVa1<=H!BA)V-p_bC$7?_PKu@7njUuE%A3J?9*KD1JXHm<*$ii~H}8(dspt?Nu<2WFdm^2%CLRBR{bS#@LWTIDp-2zJpW6 zaSg|-`p+NYfL2ERy_Yjhm~tkwONJUl?&$tg3NkXmlWyHn_FenDgQ5fK(S%0ypn_7Y z5B;{1F&D=zlu(=QdEShZdVFXoU2AWF{O(Rf0xMKSK4fDEk!%rs4P}fmMT2!c;1wBL zaZ~JQ3v#26{1CTGX>NP3U+@laj|<7XKDLs`O+$V`HUin4V&w7jfV^$?O>O4dqX6Nd z*ZxR5UOvc1!Gni>!INfot9TTxl$W(Kx`$?>MJXjr7-+nBrZ6!bgk((Bo$D828=e0+ zf{YX@s!qn#rM^Rrn8LUz+)og#hla+Ekjuj6Zf@&s5JBC?#|8-VTX1GOt^FX&5(^qr z9^79$N5`v)KV{$w?|eicPMFx+Oz$CMx4@1-OvaPks>InZwSW+jphs|A`~L*yxyAs) zHPa;xx;FF_^Lqx7^rLgq_lH1a@tFFjUMoZyZAcI)skJP~qmhOU+RCi|N&9ZsEBXe9 zdT4@EpLv$MtnKrOd;Rfnaj|&zT2+j)wFlqcknInnqc66Hxnrj?T=QA*_93S|2a$Z< z`C$<;l-}5|zM{N6d2`uE%+4-`!|ND`d2mJ-LvZokgk6JETO#^JBzi##W?>h-wgGxO z%SVQQY$B>zBm!1?N0!=RhCCgurjCuprdv6DAAyw~r4P!#mmw=b;mL6KqxpnCI$ENXf zywsA*^faJXnUGi>=ZFkz%}P>qH+_!aLngY{HXFbra4^CJl_|zvTnVe{jM0y=FW;92SDt-_IUpd zTKo8J2eqx2WA$oWbHZ=RY@OyXNcXj&9KJNk7vgnE=Q zcvX+AyB`F4*};_2M2NWbpVQ<;{yg#kt;2h zI${baKMDG_OQrjD?`Jv=iW33*9(g_@Jz{Nb9|@#3;^&)E|4a19u(eZzv1IBC! za}ezoOa)URd1^)iE(ez-lGftk+W$4O)t(qvhVm2;U>DidZ6I8Ji@U^jOkJ-Q!yf%O z_$QlUTNtA!KV9^2oF zccEmgt;kYQdCTLkl_hhY%JnMGDsrf4P;R4Q6V0;W-u`*Oy?M>7l;w=hrbiWp4KG}* z>x=2Qb}t%IUe}3c6AXJ!qo2>+WK{s4P+iE;$H{ zv;-oj9T~OH@cukcZFtli?Nw?jt_?H0)yi?17fwH4;FrT3eO}#6xI?zr6{tpjLeG~Z z6UXfs+odZj@CnU*J*X^x=?{zVB37jNM z#`u#$sTgnI?X}^|E~cDPF=OM}@fN!>B^_7?!$qsEJ-M=K=7D7R!|1C?muxl_lzU0= zSyhtX4+aRrag^%2lS5ntYCJV|n};AWD>HcY-Q52J@q5YDmKG5Z6ceDsX$!D!(ns5zVKu8xY+Rfsz57Zi%A+rCw9>k>~ds z#eCP}Io?W0eQ>HE{1DTF_K|cVjhkaLBaO}9^F~2ZEZk3V01D0b=Xrnipg)K-66mDZ zVU&C&RY=+hXkTpoquaCDp7XPQCOAY6yv&Ix;f?ab?_Hom8&2@*PEPS-Wutv!Aazf2 z6>ws1C4h;VX9bD0!7h5ics|l;WS@Y?R^7$A_VVj@7CA-epWGWJ--P~km;QzS@6hk$ z0$ZZHgPoGIHwViK*_zeNw9m;$fcqK+gw{N2D6}oReegY-o#me1bl|T;tdCN4g5@XY6%uOs0;`}l(!%Scr};6? zNPk2>cy9|oD+|X3IS9d7a$OV385!j`mU#1cdu*!&MV~eAdk6cTf5Q(yjOy{VA)(yc zgLuSxz}pHee5}a*ncUF&>`D{7K;Cz`$Xiic{>sT8T4byz@@N@+ zMDZViqH#=-3%=%hmzFkr$?)zZkpSCKfr_F6#eKupRUT{U4)ragMiQrJOZ;NcnGx zG3=ninzI0$o);J~jsZNmt(yz>pBqGRS~?GqQ*DIlsQwAoR*DR(=D$3%DYAXM8Io-A z4|~{m2_q6ux2nGm_`3ie@CU-71FD(j4PumRS?h8F1XfreJYmtF4Qi}6Rf-(9l>Qlu zo+eTI=x`78)$~|>11E5n8yFjCkKG_JAIp}3G`Wm*g;p&jW6X_ zHj|rjjN}M5sc>2!CBsf-7a0Ef&M`ROUed}^r2Yx{UuloMrQmD`9)jY%K&L(86om7e z25$mpCgQQc1F$3io`J}l;%IQ4hBQDguXMSj7jMiGut3nrB$uqnR~^#kStkHGi=h@c zrdnEp{&JSYg`CzO$)czqVcaL#Pc$$l$M8GV(7PCMeU`_uCk+90fBD-T_md7Wzx?Q^ zd1Bwo`HOy>^nTVba8QvO>T3q|Y&TnX!-ovF^&#PEtJlsju=!G=h|!yx2z?i{qvrd* z!mrS$o_G^DpyM$l-#LadWvrA^7mTB^#^+iz)YxUbHw>2~+fUG*zd@-*FTcC7&N_`v zGx*(#@H`f7l(f4^^BLF$GUJt~y?Z(D0ZJ^I$7DRose2R>j5R%%bd3XYilVIRCjHTjhly&t1ps|C5Fy!W|t@%0>CP? zKz3d!_>o;Uh!zc9tQ^bg%?!7y z=7r+K3x9zi=45UY+0x}?-^9A3=?{@21A&wnYZ+d_L;!Pt8XS^d*dbVKytWRmI`zyV z`PF#=;#T0uhdevia9Hw*+BJ<3+i9(6_H_B%z8e`IczU+xuWnF#myMRji?hEl#4%i= z(c=~niobt#UaJ$B*Y;66;G}%E`lIo~ouuz_PGn^ILpZ#~ljpkV^m@!s2txAIO!-3; zLv2vCD7M1-;CNn>vUVO43nd1XCa*0vEgU}gnUM#_)PR7~g$*VlsGWWbFshY@Tt~cW z8UhTjZgn9Y)U<9D&`hxy2iR;94@dpkd7JVa8U&WqjZh^QlT|XlnoEXIDxm6gyOrm7 zI#)a1;oaU9j0a9;jdiH+IlpI%oZ)^(zDx+B(*#S!GihKSzl9};hd2%zJ%GTY0i6s7@t*@$+{(;dki#pp7 z2CZip%tMb?-!WZ!$ykGzn)9$vFIRSOCGb?H=gux=-u*Jq^CFJ+8=?g5SmOD1l9rp} z6#KA3P*AUJ&6-%QlZvEJvB*`<#5#i0*@H@Q12Yv$SVDLl0}3VKrS3WXZQtJ=&c;S{UvOIq zxt+x2+WLwOajXHd>Z%0ku2E$Lamd&scolaq$8t@#whD)mRr$F+EGtBHL@~f6mqbw* zqBbS_O;iLJPIDS9LE<%`YtPY;TeMNR0xt$iQwao?7!^Ec99L zn+u+L9rhx@KSi-op-{8u>JA-(p$bbWq$)p)(tMQ^ar62%m+1rjA?%|VnVwTPiwE~I zUKRslu(THl!`UBm>{92&4^>kJ77WWOAd7UVmg}7UlN9AWczVLzPgq<5Rbc`81>rtW z5Ez9iH}J5AE7gw=rwfr9 zCzbQ3m7bE8kd}Pgp44Oi`N!?Y+HNAf?K-gi&h1Ou>*#!)qYIrH$>i2i_WPCYZ+on) z(Sw|A9KLNN@9wNyBMVbg^v*c2r;397HanbOgcjmU>Lwj zaz@e$5=0SA>Is@~9C?vIelrl9i8DFKlR0kVOyTh}i*7P?4`0VL8XzB`A1oP;APK_c z+Ni)o0cReM#Gm4Sx=b6(YG#d>pXo{=Kh-ryUOC_RQ(f_zsm$|_G_z7&jb2KAex=e$ zBtH81=gz%req6p%-$~au@G>8Qw34kPN-Um=}OFRbUTKvcB=&^-I;uA4~b%rk_WvYc0nWK=AyA2!HGM%dwJWTu?+~qwpD!#c ziRkxzlODg{xA~dImx%i^U*vG6@n+GC=QAzD@ggG)63R;TW$BPyuEb!$;9Nwb`=K(b6B^Bc*7GowJFJ0Ih*@44-ir*;T^S;R> zo(tiLn{qS93LhO&P$P}RZ6^48A-w!H^B;5kJ(6?a^Jm`6d-;`~LK+DL zFO`4EBjH>rs5k^vqiQ)q4sya~h^E7Yt@rI-Mg{#u$+>@tfAWRddyxnn`;c?kXtGaY7D4EsNSlyYPotc@g2aTd@ z(R3}9p52?9RM(na(Y0>6R!-MW>1m;4>1mnh+BRKlr)%GIt(C5w)3sW9S}I+OrEA4> zZJe%!>S>Rn@EpcrJpQl~Q;5$(XoO%&ny@711(*qDH(uBd_?gleYF+TqY(iwklLe)^ zC!!K94lu33y_P6$*XZ!x}vGxH=C$!ki z|C{eE6y7~dRoT^Qc*bij##O_oOshpJ7FJF5Bf^J9!k-KaE*G)%wa z(amz!vRP`L2Mn7v1q>@enOXF1NNpO$E^At5byo+5<-58vgq9^TE9=v-+%qaXYPDJx zW5;N}_$wc4yQpr~c8o^Vb!-}zMvnyzcC06`*p$I|i$pO*n8LJ$f@ki7gD%Y2VwA5*p8vAnGBJAQLCjXu3Zeq}s&S^ay@_v=Fxl7EDtLf|Md6oN>R#sr#? zAtWCn0HR8eCMdopEPdp(fB*)bPRq#8&V@j4_fw*QB=<`LDdf6cKeNl~Y`Px@pA`s` z`;T7v?;QE(rtBkzQ}#7;r|E=`V!o>N&$ttRl{IXXH|O^1HAmL;amcc5f4;N7ebXEH zcxr;~P~M0K-_`#ccgnZ&%j&;>U;M3J@%AO(P9tk$J>UP0pYd-pJ(aY-d?J7K>TFhP zW7S$WY5dFb-OS;}T~@mO%;}DMx2v&!%6E@^fB6>TMXwVL;*AG37EI?B9}odEj4@Sx zP-1i3;nTbb!y>xF#D#GP1PD{1CpZc?2_WA=Z&)j5Ur(y^w6fK!Y+aVG!>UPjshcyE z)%Wh&uzS@eSsuO53 z<%UNCP}T>MfXJZY_E!w5@zQx50W$IQevjXidH#1ANBv@L*LK}x3zJ0k&_(cDCm|Ju zhS>&iC3RdzqG{EQ> zAl26vIz_Nc|H~Q!WOD+j*EXm%H7IQxr0RckP{6jnzba{fSz3WptnJj)V4mKV3X`oB zeJf=Sa4IFRS_a6qcjjj1wVizDWL+nV;Af0`=O|X@&ZiBZ7fI4D4CUhC>%%!@86?oM3w;2^%ebaVuMzJKTw!%5dYE^$%) zl8$^4v)xG#K60DDKU^k;8+-^dc2YaRv{wA2@93E4(Jk{9JU>Ra&{(q1<0H?t@9;@W zQ+tvJ0>y{zSQ6WleB21{;@QKnZ~7R&+mD`Z8^WDXb};MB5Z$%~_A#2zk&)?ioBG9d z%O%X@k5tB}eoi!$>gUWI!13p3+>GjX^m*Lw9Aew&Dl6@Yk~EuixBZW9xqaM(>es~T zYb>hY?3qzyPj@->cKq$=(ov>G0gOMJKVO{ahP|q?X+i3_c-aJQ2lJ&Z&Cf| zDym-$D;X_$CnO$6K`|1^dHgz##A4ixn=k&xL!@XZdmh9eyQqH2B~QOi{cNWiLBkFk zFs$(~2x4oq=eW(_@Ii%_8&&w}#z+D-VXD|c8F;cnWk*^Jl?mDtY8CGheob)VW({K0 zFsjNWbJ@n$&ZvETYhGVMF&^yGNEM-1B1DDps9TYKO-+<-M|GSv)LRR>#V@oX6jmpN@EuY~%QaBzX{lh98`y zV7?$?!s&zeIWvrUFw&&Ps3lt2x!?j#7NWhRw7v0E_Wao6s5P;*1_VO5C``zGj6~w= z#<^>hwH4% z=XZYRU()!W?U>%%<#^3%*IDN}D=qtHofV0E`{&#KCZn8xZ(r{1xBcZh>)Y<_B>v8^ z2eN;WnD@wbZaJU5^3j(P}C4WbW=4IMVD_;Lb|g%^(?4}U@!Fd+*YJ#@kc67#Nt z_{eaPfrXgi7N$Ik8?JT>$Y~E2C{d;~I6bWFLVzl>3PU=;HbdBk3wcS5YHq+tz=bSx+Y7jG zDG5N4Rk3!Xp@IeqxQG-Wf)v^zkfmXnyNYg21dAX9DBy!oC;=pCIy)RN$J~*!XqH*L zZoo*t0g_W&j3*8T91c8aQ53FsA&yI2LaeEXc(F;8;*c0uwP^kZkJYuxwRXkX$hUGHEJC08_)}!ct&4*MbAQE=58& zF1Vmjlc$5lMH?80(vW##W(^4u2-!Vp0ds{aE6h$$6grd}T!zq;#S0B1+ZrY6+i@Zx zniscKB!efX&;?ddJiuj39~8Rdfguc@d`f7*mKGr`x&~Hd_J}^1gOWESSY3z=RKe|C z@WBe&h+lqd7&<^PM*tvVw1`=N3mvY&kqHPibyYc-3BhMbSJfg$$exrbJ1b$5$k4GO zwl=}5=#H}f20=K;!XUN4kt8nykO5DX3IYfLXMzD4_>cewfl~x|F{+?&$pDFngC-l4 za?t4k7Q}Kr(k5V(0_ah}n_2_r;DpR5g1AiJF~OL@El@*fo&!%~fO85|0$0q{A1Bf7j%EA(4P=i7aQ$hh_zPP~v0ZU3rOwi6HC`{^tR5A?!l4KCn z@sVjk&P1VYx$7afM3u(`1Hds2ACPH~!3ay0>qLbscR7IWa8QT~U=&AJqG|v)GWN3lvsdFwS83KHV7zEfCIBAh?1=6pRROm!kkgosdQ8)gx?( zK~NqNSW`rQtA=I%h_*WgBs>TdakK(ShAGX#ZRLpzg&}c?9J>wZ!gx`#8zQ4P7A%-a zufS7NUcfNuAZ$nz@5TjcgK0=FKOcM{A_4>+f}(hhaat0IG=M&_=ytW0quaV|Xn}c4 z0L$P6-8$(MXs9H3~+33TL&iyz1nt7kvY2Hnj+(`*amfI zn^ho`ZIEmV6F5PvJHaioK`3a0T-9x!RiIZHU|gIaUEAQ0D1{5@A#v`Bfq&zh&&E&Z zKYHSqNYP6*Kmnm0& z8kloDn3|jaZ{wS3Y%GM2ih!KNWW(&nW=1$V#;s4{#`FkZS zD@%KwJa#nr==Y6;?2$d|PsVHX=6dZWu_5@87cv-M!>DVW8zvS3kKSB*8&Fxs5i$iI z%NWq5L`I(@wwSp1CJ=@^{Eoo$gD3(xkgWT;dT=pt95NjYVbGg{(&6D@#K0?dE~pl7 zn}*P|ULx>j%Hh34VRmiegxfLO^IRpTSp^REFdd27i7<%Z#aOmIs~mkcqxiL%fN_#* zLI!%I1(kI-G_96Z5G~Oi_i+d-Uzv`n?I>;RPgTQ~%`0n%2YP+uO=`IdSaF)uC(40i z0JzrafcyTJKmX(Ko8Nbbcbkn6sC&Tlbo=gG_o*qW@$n>6)&OpwX;&-Bq_7FZ6mEM( zE;ex!R@pY!1MyTq?=x2B@HQDmvt2FOzfL2a-w}X9@Gq+CqWE;#8)vcWqVu z*GE3lW5|54QEx9Z*XV&GH=e#Am3FK5*-4QGrpFUfxjBYEh|=}0gm-gZO|RmN_DW%#izTE3 zASVwoB?UQ&M>U%mTRn=A9Kk;JLyD3UUg!7Iv`*Gl?-l{rNCh{Z{;93(403a$4!*Ul z=Db`kD>%12K2jj)>z9DX?$5{i0%Aa(yd^C1_8Z%U9F`iem%cssOC0-`jD78b@ry4_ z%z*6xR!jO1typyUnAmCs$*x0Ct$5fEbmj}nv3%|9wu)49DCa-4G%+XiDp#Yt|Q6J>qNo0J-5vuIVDVp)jCtbkpFpFuCQS* zFIl}j++D!EP^z0k!p?RjjeLNVdDxa07XWAx;h^wV&5YQPOy0*3^;WPC=@jl%%($Cw z9AnQzEaM*-)GNY`BZro+?Iz69{Q%D;EAyvPgpO>9F7?phBVJPiYHodh@6X+{}nhYSE=w%?-v$%nG|- zL<+kok!da>E)T$p_1&F)G=~f7#&!ux`wCI;j#rA%Evf}jJ5jW^1QRHZEe^U3Mn=n) zERuTi3jdy>W-oc;KQo{p&aTA|UEi2EjfU188WmKEHTW|XMSHsx3%o}qJ+EWAEv+I_ z?Vgx}msUkn(&VOe-#1(XC9kmJu_9pAIuzoL;zqKYL=Q4hPW~}DS(NM7o`J1aXK~F_ zVE2snB$rFDoC33MOk{Siuj*)a*CzxD?8RD`0^3UD6maJjb6!r08Eo`&S~<4Zv;sJ* zJTVu2qkwOT@I1FdsH&usPZ$P&?SAT8fviFXiDUo!{>l_!Z!#E;${XCVeCE=zx=Ie7 zkllusOM$I*en_g4gHswsmpQ9Fs%ahUMfe5od;LSY;Z(z@$Y(gJjZ`{OVsxO!m`=S$ zw1rZaG=7mNB@^LK>YGYy%RJeU47I7|BXBjg-t73N3yU|3wqN>>U4a2X(OFncw8@Jx zz=6_6>$XM5%FgjT`a{QvUL}6@LvV+%3J@;zk0?Ke@09Oy1C6Cma?M@rYB0Li{siB9 zWdrxmC-L5um4B%=E2*~z0Sk_o) z_#kQiF9Urk)Fx{C(?;Ulq(Q>N2|q(mdYSRS{J0z5opAV-Hxild{8)$1*=DIMXrq{0 zKKGyBNwoI5Ptc4?PKc*GEQO_`5NBO1OZBoL2HlGMAH&Tw=qBeXC>z%diuqS*@XYLD z*l_a1r&bvAl2{8l`AfYPGvNGwAaYw{I%>rE2v#Q9EI_r8vye$gOL*!9DDMd4v`<_g zAvikwe;gN4PlXrxW(>{vMbR`lNu@Il1RPgHGkp-l8)$)5bH3)c%`^L2>2DL~8TuLo zcKHHN4>A&{EFx7L6qYpL@o`rD-0%Q)XB`UeN>MWF58}yiQurf|Gk$lnX7TalK#QPn z&QE%W+g+MeNQ(P2d{_>RZMxADi_mim$9aDVI091g0uj2?EMr!weHiw+X$~*HbF;$ zK>WUDd*6`eH#C^}hMthgBC22ubd;)dKy}7>)TsxA<%yDxr9MZ8Kg5P@Q`~w*NKAPo z-{V^kWH~{X%OLG7S4iq^n_PGxOa1Qy!$q8>aIwoq1ocb0h)qN`XPQWBAX2GN6UweK z0RF7t??)zUJ;YRO|BK+=(*c+OOwBBiOryBf8{vi&;8X8bllL@0I|57Yz`Nt&n&Qf% zp0$%k#~#lt4GHXC6XY-k!jopMOg81GLeXk}8%9A=aeIci@;DN@2}i@6%2c?&Bz$7T z?n&KM4DnTH#CwUUBUOpBKH``3hbA#v_=bSjZ^pEMaSmR>x~>AVylYn66h*nlR%{G? zvEp@(U>q(Fn z6eseyU&gSS=8_7-qS4T&V7REP4C@Ls&>h2azRAz=jx5HQj$^t%E>tA~#-eOZaO18N zoEdoG4g1%1y`v0@pPikinCzg``smbUFg872?HmyhsjYgE+*3-8~Sa)FmVBg*R=i5 z9!JXK1=?#!_UScUk`T?XD%24%^t)e8nBX3Er4l+SfpgmIDmufha`c(%RPG`ey}Tys zKkku?MNPqVxDhK<`A~tgvdeU{u1c+dQ8HW(OSd2g=s$q)i8@AX5=cuLm`%-z-;ztzz)!FZ2%u4ayGyaL&g#myLbZn-I z3EEk*ly?|s+%m_N{W+RL@*yfC?b=PjG^-h2)hg~rOjw)&<9xyr&{=#j*sEbG&5WZB zF$~V9(r30Qg-(&Hl>$<<^~MrEXSofye}!C;S$t=Z!C)Az-IU@~qv!E(E6ups>wrRZ z`e?P4Lo;;$HW&E2Pdt73%{(uLH(&zw;B_~+%>H;gz*P(v{P&|@>y4}@{F{S0tz$Gx3-V7EE7ra~Qulh5hMThR729<6bIjn| zL#Vb3VQb!@F zq+QYv)i%^xz)4Y8!R3~3`1~ZclCP1N8>sOELKS&br`K>lD-EhECugFE_V@Tr8BqK- zToTWlnuT9MWCfW?r}mgQ1E#k?j(~P5>m*~qkzOu3{NInK96RE7UX_i4NL))Hhb>YU zLZ~Hs5i6w~E9|LEAp=#<2*g-ofyl9f*rAq^KNVt?$+3oXbL8@woBg0JFO?vNaa5HjePPpm=AHnbcM1l2h56blBi}Uzxt~{kh6QDm3pBO zub9U)TvsA&3Q2-p))G(Wp2d`;(#b-?+(Q*nuB&BC7Dg*l-n}3R$+rE(m-||*o8|Z! zDdpmzjff%Uq(MOWPYk{@M^G|3fu6zjaRjFd4@+&ArSeGg4YheY?BXHUpx!k??a+&H ztPL4uMKAjlZ-;uUveY3eI)aYFrfM;d?iDt(&Q?SFZ(X? zLL$Jb?Hr*0u}MN#4W6d9LH9)@yr?vo(az=B&AB)X=VYUCb14!=7oJLp?vwsZn*F{) zfv+_Cl{$X%FfaF4x}6g)?3qhIM8_jP zh7V8&S=j&o)&Q2|M{y0-!PJvnKFeqBgqKJqKOX~IbT+Q7+U4`@LWk60Rmae}WN)`t zV)tF_8UBrC-*kjuw^kwvfshQPYQLTg^qI(g);CoWKF^oRfF74(TBCDvP&amA5A#uaJrjFxd7 z(>*2^1YZo~4%ix!B9{8D2Afb%J;+xCfn^^i1b&ub60vZ;zzyF68;|ef76%JIE^#kx zrD)=S3MJ0iR}M6U^8-;+MVbmPX?hB{9%h-vqia{O96Kh@X-gm|5)S3^6x;E{4-HEk zf)H*WGZ@lWi4-GI>t0O>8NQ@<$d{$z17#Xm>Yy z$@j1W@7BLSGOiav1}?|}?L*ETQXhMIi8K2FUv>6-*PExK!#=`S|Ai?vH&-V{0_4_# zVHt?vUyB*=Vgn)vqrCZR%d;IIpU_h@=WJ3-if^{P(xep|I$Q@1N?WM~d9J0N7lfVE zEBU@982S?rLTixnVFkyCvYYk{jT$5drJVBYe6e z)MEYym3CM(ij8!Qcr-$}^n|E79_s1C{D^#5QwYSvjp34*B6AS+FPvAtVt*Smz_%ep zGUY5{8vSf%k~`b5C=8G$&WrWfF80~WhZ#gn?(==AjaqL*)rQ>!;@{mfD9dW&29Ehx zJAvAWM;x`(#Z6LZE7c0Sa?*#Mg5r@L=Q)7(dH~jbTme8 zW}y_*5+21YL(kA6?i`3BoX;Rc-W<&ZM77loDrW0;7qlpDtOgd>?x!F@OokAXsBvy| zFtJS5{T0yYMwo00T^(e7ZdbrH#sM_8rn5?sc&!F-WB5Qeb_oVV9bra9^(7FPbC4W* zinHV=faCOgtz;E-xw1P5xzA%e47~OR>tuvIM4Rc~qS$mt&T*J`=N*$;9`82R5ZcsE za0@y|l85pu5N^j!>Wn|Q0V^r3v9MqPeEYp7ApP5tGalRdbxk!IG>IDf*E;~(I_hsH%N>c zfb!eR#}IF+7%x{Kdkh%h1UyTHtatD%K^GB2Q~rZ-T1;pnw}W;*enI}2M6(Pq&~Opq zHS^2@L9WmMimSbuy#USHX+gZ1;O%;ur?!EkXA&_pqd8fCCiC=YZ!x6|762>pJK^6O zK%Gh`C!5lw@Lai=aoM?^1*a3gtp1)6l4Uh^zaE8FWp5G8Gejc;t+}Hk?=i^s8VK7C zgoL`E#fk()1mEb6{Hp8Mp5{#To~`gh`m=T+gy&qrrp4ANH9NLs5C@|1ihJl2+F+8~ z5oQQI*5f$-HeA25aYV$6{3Y>?BOWhD?Zk?{2J0NzPF&K`J$3f_N6gQwo^jlOkMh?j z_ifhn#Bh8&fM18Oy=Kvqpf?!0Se6&)8<>yHd1%P&a_C#%CYc%*Fz&(}GLp?)rhml6 z=?S-TZC*SBrD0-|VWZ<8@7%W)rwe|yuSKRHdW-*@@X--0IRn>mtwF2|u-U5*yjQ$b zpp?XZA}0=OIr(cN)}o9jzPYKEqX>zl!TD5^y@n7I75+qFjQ(udB67hU;`sp{98_#@ zfdPT6$;K7}RSM{Eu!$mCvlQD8R*zSoNJfEJDpmjh5p)j$?A=Q44*w)}NA<3qbZc`k zIE-7XXF!D3Gba9PNqqzV;7r5$=u$D+q^9%`3hV)mXPV-guZyL;Ui5V4q%`gmXqw8; z3%s+kQ6rQzoVhK*5cU?{y_Lo?RKTq=)! z`~b5-ek^KkSvmheYF7;@GfEUEy({(6&X6cFLUzcrdmsGN{b%ZrVc6{GEM6{FqpY{- zJ~6v`OOUS~Ed;#w3C(P$(i0jdBJ#*V8{5Ez#)_YUSFuGG8Au=3mCbI#r!kb=^CQvB zEV5g&m5ylpFJ#5z5nVapxw0u_M5AjXdaBm(WCRtz46uwqgWH_Z|8VVYZEYSNCeDG7 zU1GdOC_wF%yz`&(JOblnIuE;u(+e!Nw1KZ%Olo;BC~y<_@V;B$9O^+&6$+_{o-KC1 zy-)i3TwX~DdZO4w_n*MFPzTJE_c?(MP#@?@jbxP~n!<9$kY;xs4%)VumjyfGnBd_A zJpn~kxS=}6c_~XhY>QHAhO|zEL{D-VzJYIMSVdb*wXAvUl)%V!8Lp+ipr=zpP?B zDNXPO$$veC1x&~%A*g4>#kmU`-FiqPZ4m(@7HtuHe`g0~OvQhgxF?l%BKgVNs}x&! zi)o7MF)iKx#G^G%Lyfh?eXUG%i^uLb`Ro|T-8WA_CunWhH{v`P@wTy;Yel z7bU)CdY!j)aAR>S*-HTwhd=3=Z!C_3nb>~A#t8^iU4`XfY9nNx;+?E{a`aajcwiRt zpdds`><{ioq2{47ls71puNscXpOwa-+){&~j*52%#*h;s6{|y+)^Xr6fV5MSRTrm3 zSo#d;SIqxZ&S42&gx_5l{OX46W8qyRZhEm=OxXU}YAjLvW#;-p6=qZPV@`d2bM`&5I~@CZ1?t;ipuPK;munEexvtD*bsfP!AY%m z7eWx1O50Ex7qhawi}c4=<+7>~WoywztFN?(86E3{aOJN2tQm>UGi$GGu}lE$0wCGW z%Wm{dPlwr2OBeeU!UCz|=nvG)w)~~`DJtQIg(oBTsCguWk*&y?U<$;)tsb(-;_c8M z%vdKr3o(wQ6M1?_8#Ej`uxPpXb{MlRr>y3gH9$LaXQmMr6uoD;Z89wMa^7Rl2xE z2q}{5oFjEch)4#Da!YPgapv{>&d;n_t7*@g|IgR5_TIm}_w)TcpUeC5Y)(4wq2;EQ zUaL$^sSS9X!@n=#`AR&lIW@K1YD-^_YnJ2n>O5I->GLVRyWCncuMe1cy*7`0ch*@v zs;Q~vAL4QL)YPV@FMWUc-!ENn)2}RDWBEsxzTRx{()%s{_|ngBcK^)hpO|@{b=G{v z3A6O>)$c&()HKg>=S34c=R9Eytu9~tK4+dAE#DXdCeF9 za_N0*EcwV^K6%?tTkcTbULHI8*$ZyF{tf5;;>X9HaqF&IZ?@VdtL}62KWuT(vvb1Y z4Gw(#x^3U|iIcW`;8idG&J$O>?SwlQtart4KYG#I_YK?Zcg&G%pL*Xdhri;iHE;Ol zhMQl0-#Mp!eQWcld#>3!yVri}fgc`o>!AS zYhQiOsoP(_@X?>9LqB`+)YY?|*zgT!UN7(a$bCB=xATX0yZx!v7jF0FUu^TnegEy- zSDgGGKYC%WL#}*-IOewf*8S@xPn`CRcO1Umy{qqW*1f;l>K`|mH)St>$NlfU?9#PY zo_pUH|7Eq8&E4&H>t1%&nIGF@=f$&*zxw#}*a?f~UHiI?)Q8i(FWhz1wxxe`$%;p> z{_uLg|NdU{*1h@s-M0J6?dthUkKFpixo7-lou6*7>wzoXI&07R$)k3i_mcDOzc~EU zr;gir)ptFA@I&){`kKc+{>8fvT>Sj3Q`fzH?)~#m@E=+EnH9D?_r34FboSd`FFrqe zj~_33WRIukUc2|+^Dp1&^t1O{{#EDfx7n5loc849VTB{!^vh2jvG>I{?6Od;Z$7)t zZTlQ1R(*bz^B;ZYXBRwo_iK-zd-(!;*5ZZv^k4ny(QmJDz#ZGp`|%;CeDD0%&3$Hr z&Fx$7(5L+QEl=&d&n0Kwbmt9oFFO6apUX{On78@`k3Y5Cb%%zlZrW>=4}SjGGxpl> zv`03%>B7&vYu|0mDYx9Q;QVd(`NY(F58HO9MHl}0&ma2JN7sJ)`Ny8}(XYQt8B8WcJn-{;}QlZoSmbhY1m~ zdUyPp!fEYuh=H1F?k`J9ww1^Vs>jx|v(YJMoJz53tz#}llK7a%K836yiSgwaMD4wi z=#)b2J@;N!mL^rc(o0XOia=Mep=5G5qR5`Z-v;Oi(2S*>64~k!Nq&yDhMenk;zTSq z$|7nq)QDsS-K&eS=0?l4>Xq`vx9K(Ls;uZ%9;9`)Bv~aZ%dC_cE99uif)J&$oy@*s z@S2<2P@RgpPt0GACF^FIp|xzFvY8xX8n;bO zSLK?>E=77%Y1?X2TJ+3^WY;aYlBi2>MTe!EtW8wPRaO2^p3F;Ge{I6C#xBiEEyG}Geu}jvXq(kOZV1VnW#z$y@XVy4)`>n$;EjmZGw!|oH`LG(WYKh;n{U1v#YV|w z5XDv>xH5CB=~17vEi4qAiLGj8fcGZkPgavqJVX!FES)Z&xsLNt1_MA?Xf<=~4NCw3wAP=*1fOL{O?IjUkLfo5xh85ka0 zZi*X*UW3n#zGKc!BfUn?bYYbi=uSbfeS~wFUH4 zQ$3XqDrLCN%M8-X2wY26O7KnQZoGhC&4A$1w!AShz#wvV8LFUTY=aV}!5LNwnK&9_ z4ynM!4N{~7fmuVlM^M@iGOZ&(79U(ffVG4uU3c14`ecApG%A8hZhVoiSGqi+K9C%w zPlDpG$pj`rQ%GPRfbmU8k$r?Ff@-uf4cH@`Kp$&vY9Je!RH;-vEGqe!rx6C4#x`oo zidNg)MWH=a4wZHg5Iv}%Q&FahR>&$`8MV_I!IKG5G(Qn$5`Y)^phx};n63y62Yv+7 z1pl|zVnH4az85Hf8cHNvnN}U>tzT@D2y!66a;06pNz}aYfedeegiKWK3QS>$Q%dkE zG(s0n(>m1(Vxr^%)hZgKc(19mM8p}-<`SC4XoHsPRt=U9^du9AssjI$8LX^yni(_@ z7#Jii#vpwX#0@xC5G&>C5(6t6C6J;Z=$zHE<{ZGO3gkk6Vqy*v0I(5rU?{N==qx(8 z#6CLAS(#f9)5sfv!;n;@cQgn{YYof@{zXoAwmPP`=vYy$(pjgYM+D`Dj_?u?QlocZ zcQQ+{fglY=!s2Me34(#aE2GrtNYX@%fHA@NDrs~lI^rm+9 znQaER7R)4z(48g(U!?9JC&=werLInuR+SyRr4W`GAvmyW=xb1T(wIP`r82(`c(33n z-xg9P)5^ecn}tW6puh-qC=qB8Xri$kijUk(>t0fC$x)~&6d%nfXBp~Lq5C@e9&|{E z_kdPPh&yDgGwAu&O?1BG$!y%oiR>{$0=D@Sp)=4L$MigC#tq}pz z$A$_mGh9+JD_T3YkfAU)K%Tov2h?kiE5XDtX`mOoMx)0J3#sJL?Vzu8;_qe!gAkX4jB7#TFBk1winD+Y&QVO-Wk9*_#fk+bHI z1#pRiEnCZ_QxSl|Hp~kYoqR&9O~OIGB~3vtAOv6osEaxmS!k1hRm;`e6s3sJZ)=?g zZeT(kyp6n1(>SP5%$BN=WFmH@6?&0ETQ!Jf}*2P`@E3csq?? z0ey!#Av;8a|1C2iLgTe(*=W}Yy5Ns-MMF!7F-Pn^7UWEb18M2w=oxCJgIFsHCyL3g zP9c%;A0xgrBvpdwYen7(hw7l&Fp&T=bJ=kz8QF|(z#s#i2rNB~1TiR8dlk7tOymqj z0f|EHXkhv^YE+D2_#qBI*6ER?ct$W0znu)!^zYyaE?^3v2ggj2h&09hA!ZuvAYY|_SmD1?V=N4LSiY%NQ#vOw-`|%s62g z1SIi2)M}D>4{4;P(YjT1c^m8NDdg$^n;s(go2%QbQ*(6|FJZsfk;XR z{b9J_nGl319vre0G|ZS3e0$a%Q`V-}P?5dR4+W{F0jF+1i%j zw5wJczto&o7$*1?k=ueUo_)~83O(LS8@&sK)9@DT;PR2yag(_?GZbdH8)^$ilSG6% zZHaae3Y(IUz@WLr9v5hEGSF)kL$i1|!D$}wzJM1Q=#&>l zHJ-broCJ~kg2)!gD`_O;;YcGuM>8|_Fs48ejuSY#j+V^OC5vl`XRh_{_uzn;>c|Ny zQjJ3`J^WE2dxDMkuCiQwo>p`z0`LQ!C_&TxY6(`()#U z5NFscCF4!*8X?Qxid;hO7vHZ1-xoZb7!d2w36tnN3<6QW{?Pi&(3Ekw zqhZJ(5kp!QGu@^c_(ad3h%pCZ7#2+Uq;ueqExsFvPAZYlfxZBHNC@eyctpiElPH44 zt#cK;2Dn8XGcTZ%8L$_!z*>Z<;xHc+7KZ}PAu}y@78%1HSTPS26_Zxr=-6vA(Gl-=xQdsj#NVH**NVE6G%)NAtr#CI`#1j1!|QfjNe4M9%6n5t_lnh zbYd-pe(nfW95szg{_U`zs?rEBVo&3IYMh@T&XY&0AoTiVKB1hb^D$$Y*S$5cd7R{y zDn=_?^!xaIBX%@WtGIIXeMF6=Sc{B)4M@gL8ucJi%B&7QwMN&YGcBInI3z(g=A*S7BtP%|%xX z%Zn$iQ52MTqoWl(UR3IKAWCD@a5;=)ho%LP)sbQb`5}87* zJK8FaBU_C!$DUa{@*uxRu57SF0k)y;@PGq0UQ{${09~Ww0ah1)7(Y%Hr5Zn5AgqTg z^Tv-_!AK>IUye+K4#zoF>}k^J0!U0+G~SpnBPt6eBs2U3@!|@(2WAJYFNXe)1=pD@ zlNFJ2*yT8LLIcs^>=ER=HROFW!@Z!6@C=%9uau<^Co+!x2M94T1<7RKmT`PFS+YuG zAB`Iu)8MrB<3|(j>Du6_ah%4ik)(v#Hj(ibdHlv=0s{uEPD@rPTN*H)a1O%o6Yi;q({SH0;@W+0B_g#h1P1nFID;>Ed@Du*GxV{o2u6z5o8R5Bl{h z9}wSJ@T+&9@{{+!;*vjJaQkQO{>DZBdfXmwT=?`S?*HRkKXu2PjW=I%#YRVe%TJy7 z@RyIcdG@EvweMLl=kSGB>g6}P`8{9LzukYw&wOve`gh&`)tfeZe(#+QS?AK-Z#?w- z-+A(JedN^_o_+p-FWcg-e_#1a`l$8)d8aRI^|_Bf^VOB&K~v{m^2qMH__s5@qy?*B42e&_U(IvB9SZ!+4!>|3pXYYE=4^E!7)}5yxu;9+CH`-{I zd2dpmKlmpnKYRQ`hrRS`J6?Cmw@*vgW=+yUZz0OJN?e@6X_`K)8{f4s-z2n)xzU43HUvmBLet6_Y z4?g&Q|MZdvuUcW&zSk^R?aTYGJGJmUyUq5y-SW^O*UZ`SbK^fV`NN{AW!JK6*|qFi mb}hS>UCXXz*RpHbwd`7UExVRo%dVOKPi4uHB?t1j=6?ZMo9GGv literal 0 Hc-jL100001 diff --git a/tests/debuginfod-ima/koji/fedora-38-ima.pem b/tests/debuginfod-ima/koji/fedora-38-ima.pem new file mode 100644 index 000000000..e323fa24a --- /dev/null +++ b/tests/debuginfod-ima/koji/fedora-38-ima.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEj5EVzjUa4PW3I3Y/RTkLgfjP3Elu +4AyKdXXxIldW6VVi3QMEpP5eZ7lZmlB2892QFpbWMLNJ4jXlPehMgqNgvg== +-----END PUBLIC KEY----- diff --git a/tests/debuginfod-ima/rhel9/hello2-1.0-1.x86_64.rpm b/tests/debuginfod-ima/rhel9/hello2-1.0-1.x86_64.rpm new file mode 100644 index 0000000000000000000000000000000000000000..0262ae2f0c4c2c34d5c007a6340ef3a7d0209caf GIT binary patch literal 11702 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. + +. $srcdir/debuginfod-subr.sh + +type rpmsign 2>/dev/null || { echo "need rpmsign"; exit 77; } +cat << EoF > include.c +#include +#include +#include +#include +#include +EoF +tempfiles include.c +gcc -H -fsyntax-only include.c 2> /dev/null || { echo "one or more devel packages are missing (rpm-devel, ima-evm-utils-devel, openssl-devel)"; exit 77; } + +set -x +export DEBUGINFOD_VERBOSE=1 + +DB=${PWD}/.debuginfod_tmp.sqlite +tempfiles $DB +export DEBUGINFOD_CACHE_PATH=${PWD}/.client_cache +IMA_POLICY="enforcing" + +# This variable is essential and ensures no time-race for claiming ports occurs +# set base to a unique multiple of 100 not used in any other 'run-debuginfod-*' test +base=14000 +get_ports +mkdir R +env LD_LIBRARY_PATH=$ldpath DEBUGINFOD_URLS= ${abs_builddir}/../debuginfod/debuginfod $VERBOSE -R \ + -d $DB -p $PORT1 -t0 -g0 R > vlog$PORT1 2>&1 & +PID1=$! +tempfiles vlog$PORT1 +errfiles vlog$PORT1 + +######################################################################## +cp -pv ${abs_srcdir}/debuginfod-ima/rhel9/hello2-1.0-1.x86_64.rpm signed.rpm +tempfiles signed.rpm +RPM_BUILDID=460912dbc989106ec7325d243384df20c5ccec0c # /usr/local/bin/hello + +MIN_IMAEVM_MAJ_VERSION=3 +MIN_RPM_MAJ_VERSION=4 +# If the correct programs (and versions) exist sign the rpm in the test +if false && \ + (command -v openssl &> /dev/null) && \ + (command -v rpmsign &> /dev/null) && \ + (command -v gpg &> /dev/null) && \ + [ $(ldd `which rpmsign` | grep libimaevm | awk -F'[^0-9]+' '{ print $2 }') -ge $MIN_IMAEVM_MAJ_VERSION ] && \ + [ $(rpm --version | awk -F'[^0-9]+' '{ print $2 }') -ge $MIN_RPM_MAJ_VERSION ] +then + # SIGN THE RPM + # First remove any old signatures + rpmsign --delsign signed.rpm &> /dev/null + rpmsign --delfilesign signed.rpm &> /dev/null + + # Make a gpg keypair (with $PWD as the homedir) + mkdir -m 700 openpgp-revocs.d private-keys-v1.d + gpg --quick-gen-key --yes --homedir ${PWD} --batch --passphrase '' --no-default-keyring --keyring "${PWD}/pubring.kbx" example@elfutils.org 2> /dev/null + + # Create a private DER signing key and a public X509 DER format verification key pair + openssl genrsa | openssl pkcs8 -topk8 -nocrypt -outform PEM -out signing.pem + openssl req -x509 -key signing.pem -out imacert.pem -days 365 -keyform PEM \ + -subj "/C=CA/ST=ON/L=TO/O=Elfutils/CN=www.sourceware.org\/elfutils" + + tempfiles openpgp-revocs.d/* private-keys-v1.d/* * openpgp-revocs.d private-keys-v1.d + + rpmsign --addsign --signfiles --fskpath=signing.pem -D "_gpg_name example@elfutils.org" -D "_gpg_path ${PWD}" signed.rpm + cp signed.rpm R/signed.rpm + VERIFICATION_CERT_DIR=${PWD} + + # Cleanup + rm -rf openpgp-revocs.d private-keys-v1.d +else + # USE A PRESIGNED RPM + cp signed.rpm R/signed.rpm + # Note we test with no trailing / + VERIFICATION_CERT_DIR=${abs_srcdir}/debuginfod-ima/rhel9 +fi + +######################################################################## +# Server must become ready with R fully scanned and indexed +wait_ready $PORT1 'ready' 1 +wait_ready $PORT1 'thread_work_total{role="traverse"}' 1 +wait_ready $PORT1 'thread_work_pending{role="scan"}' 0 +wait_ready $PORT1 'thread_busy{role="scan"}' 0 + +export DEBUGINFOD_URLS="ima:$IMA_POLICY http://127.0.0.1:$PORT1" + +echo Test 1: Without a certificate the verification should fail +export DEBUGINFOD_IMA_CERT_PATH= +RC=0 +testrun ${abs_top_builddir}/debuginfod/debuginfod-find -vv executable $RPM_BUILDID || RC=1 +test $RC -ne 0 + +echo Test 2: It should pass once the certificate is added to the path +export DEBUGINFOD_IMA_CERT_PATH=$VERIFICATION_CERT_DIR +rm -rf $DEBUGINFOD_CACHE_PATH # clean it from previous tests +kill -USR1 $PID1 +wait_ready $PORT1 'thread_work_total{role="traverse"}' 2 +wait_ready $PORT1 'thread_work_pending{role="scan"}' 0 +wait_ready $PORT1 'thread_busy{role="scan"}' 0 +testrun ${abs_top_builddir}/debuginfod/debuginfod-find -vv executable $RPM_BUILDID + +echo Test 3: Corrupt the data and it should fail +dd if=/dev/zero of=R/signed.rpm bs=1 count=128 seek=1024 conv=notrunc +rm -rf $DEBUGINFOD_CACHE_PATH # clean it from previous tests +kill -USR1 $PID1 +wait_ready $PORT1 'thread_work_total{role="traverse"}' 3 +wait_ready $PORT1 'thread_work_pending{role="scan"}' 0 +wait_ready $PORT1 'thread_busy{role="scan"}' 0 +RC=0 +testrun ${abs_top_builddir}/debuginfod/debuginfod-find executable $RPM_BUILDID || RC=1 +test $RC -ne 0 + +echo Test 4: A rpm without a signature will fail +cp signed.rpm R/signed.rpm +rpmsign --delfilesign R/signed.rpm +rm -rf $DEBUGINFOD_CACHE_PATH # clean it from previous tests +kill -USR1 $PID1 +wait_ready $PORT1 'thread_work_total{role="traverse"}' 4 +wait_ready $PORT1 'thread_work_pending{role="scan"}' 0 +wait_ready $PORT1 'thread_busy{role="scan"}' 0 +RC=0 +testrun ${abs_top_builddir}/debuginfod/debuginfod-find executable $RPM_BUILDID || RC=1 +test $RC -ne 0 + +echo Test 5: Only tests 1,2 will result in extracted signature +[[ $(curl -s http://127.0.0.1:$PORT1/metrics | grep 'http_responses_total{extra="ima-sigs-extracted"}' | awk '{print $NF}') -eq 2 ]] + +kill $PID1 +wait $PID1 +PID1=0 + +####################################################################### +# We also test the --koji-sigcache +cp -pR ${abs_srcdir}/debuginfod-ima/koji R/koji + +rm -rf $DEBUGINFOD_CACHE_PATH # clean it from previous tests +env LD_LIBRARY_PATH=$ldpath DEBUGINFOD_URLS= ${abs_builddir}/../debuginfod/debuginfod $VERBOSE -R \ + -d $DB -p $PORT2 -t0 -g0 -X /data/ --koji-sigcache R/koji > vlog$PORT1 2>&1 & +#reuse PID1 +PID1=$! +tempfiles vlog$PORT2 +errfiles vlog$PORT2 + +RPM_BUILDID=c592a95e45625d7891b90f6b86e63373d540461d #/usr/bin/hello +# Note we test with a trailing slash +VERIFICATION_CERT_DIR=/not/a/dir:${abs_srcdir}/debuginfod-ima/koji/ + +######################################################################## +# Server must become ready with koji fully scanned and indexed +wait_ready $PORT2 'ready' 1 +wait_ready $PORT2 'thread_work_total{role="traverse"}' 1 +wait_ready $PORT2 'thread_work_pending{role="scan"}' 0 +wait_ready $PORT2 'thread_busy{role="scan"}' 0 + +echo Test 6: The path should be properly mapped and verified using the actual fedora 38 cert +export DEBUGINFOD_URLS="ima:$IMA_POLICY http://127.0.0.1:$PORT2" +export DEBUGINFOD_IMA_CERT_PATH=$VERIFICATION_CERT_DIR +testrun ${abs_top_builddir}/debuginfod/debuginfod-find -vv executable $RPM_BUILDID + +kill $PID1 +wait $PID1 +PID1=0 + +exit 0 -- 2.47.3