From 62865caf1b0aec26ac60d20cbaf6f28b53b7557b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thi=C3=A9baud=20Weksteen?= Date: Wed, 18 Oct 2023 11:08:47 +1100 Subject: [PATCH] Revert "Add sign_sepolicy_path for a binary to sign sepolicy.apex." This reverts commit e0a977affd1a97764ac088e7de7386fa89f4b006. Test: atest --host releasetools_test Bug: 297794885 Change-Id: I951277e4aa3ae1f90474a1f7a036fc8693453c53 --- tools/releasetools/apex_utils.py | 72 +++++----------------- tools/releasetools/common.py | 42 +------------ tools/releasetools/sign_apex.py | 4 +- tools/releasetools/test_sign_apex.py | 15 ----- tools/releasetools/testdata/sepolicy.apex | Bin 311296 -> 0 bytes 5 files changed, 16 insertions(+), 117 deletions(-) delete mode 100644 tools/releasetools/testdata/sepolicy.apex diff --git a/tools/releasetools/apex_utils.py b/tools/releasetools/apex_utils.py index bfc87b8826..1ddffc16ba 100644 --- a/tools/releasetools/apex_utils.py +++ b/tools/releasetools/apex_utils.py @@ -68,7 +68,7 @@ class ApexApkSigner(object): self.avbtool = avbtool if avbtool else "avbtool" self.sign_tool = sign_tool - def ProcessApexFile(self, apk_keys, payload_key, signing_args=None, is_sepolicy=False): + def ProcessApexFile(self, apk_keys, payload_key, signing_args=None): """Scans and signs the payload files and repack the apex Args: @@ -86,13 +86,9 @@ class ApexApkSigner(object): 'list', self.apex_path] entries_names = common.RunAndCheckOutput(list_cmd).split() apk_entries = [name for name in entries_names if name.endswith('.apk')] - sepolicy_entries = [] - if is_sepolicy: - sepolicy_entries = [name for name in entries_names if - name.startswith('./etc/SEPolicy') and name.endswith('.zip')] # No need to sign and repack, return the original apex path. - if not apk_entries and not sepolicy_entries and self.sign_tool is None: + if not apk_entries and self.sign_tool is None: logger.info('No apk file to sign in %s', self.apex_path) return self.apex_path @@ -108,14 +104,14 @@ class ApexApkSigner(object): ' %s', entry) payload_dir, has_signed_content = self.ExtractApexPayloadAndSignContents( - apk_entries, sepolicy_entries, apk_keys, payload_key, signing_args) + apk_entries, apk_keys, payload_key, signing_args) if not has_signed_content: - logger.info('No contents have been signed in %s', self.apex_path) + logger.info('No contents has been signed in %s', self.apex_path) return self.apex_path return self.RepackApexPayload(payload_dir, payload_key, signing_args) - def ExtractApexPayloadAndSignContents(self, apk_entries, sepolicy_entries, apk_keys, payload_key, signing_args): + def ExtractApexPayloadAndSignContents(self, apk_entries, apk_keys, payload_key, signing_args): """Extracts the payload image and signs the containing apk files.""" if not os.path.exists(self.debugfs_path): raise ApexSigningError( @@ -133,11 +129,11 @@ class ApexApkSigner(object): 'extract', self.apex_path, payload_dir] common.RunAndCheckOutput(extract_cmd) - assert os.path.exists(self.apex_path) has_signed_content = False for entry in apk_entries: apk_path = os.path.join(payload_dir, entry) + assert os.path.exists(self.apex_path) key_name = apk_keys.get(os.path.basename(entry)) if key_name in common.SPECIAL_CERT_STRINGS: @@ -154,37 +150,6 @@ class ApexApkSigner(object): codename_to_api_level_map=self.codename_to_api_level_map) has_signed_content = True - for entry in sepolicy_entries: - sepolicy_path = os.path.join(payload_dir, entry) - - if not 'etc' in entry: - logger.warning('Sepolicy path does not contain the intended directory name etc:' - ' %s', entry) - - key_name = apk_keys.get(os.path.basename(entry)) - if key_name is None: - logger.warning('Failed to find signing keys for {} in' - ' apex {}, payload key will be used instead.' - ' Use "-e =" to specify a key' - .format(entry, self.apex_path)) - key_name = payload_key - - if key_name in common.SPECIAL_CERT_STRINGS: - logger.info('Not signing: %s due to special cert string', sepolicy_path) - continue - - if OPTIONS.sign_sepolicy_path is not None: - sig_path = os.path.join(payload_dir, sepolicy_path + '.sig') - fsv_sig_path = os.path.join(payload_dir, sepolicy_path + '.fsv_sig') - old_sig = common.MakeTempFile() - old_fsv_sig = common.MakeTempFile() - os.rename(sig_path, old_sig) - os.rename(fsv_sig_path, old_fsv_sig) - - logger.info('Signing sepolicy file %s in apex %s', sepolicy_path, self.apex_path) - if common.SignSePolicy(sepolicy_path, key_name, self.key_passwords.get(key_name)): - has_signed_content = True - if self.sign_tool: logger.info('Signing payload contents in apex %s with %s', self.apex_path, self.sign_tool) # Pass avbtool to the custom signing tool @@ -368,8 +333,7 @@ def ParseApexPayloadInfo(avbtool, payload_path): def SignUncompressedApex(avbtool, apex_file, payload_key, container_key, container_pw, apk_keys, codename_to_api_level_map, - no_hashtree, signing_args=None, sign_tool=None, - is_sepolicy=False): + no_hashtree, signing_args=None, sign_tool=None): """Signs the current uncompressed APEX with the given payload/container keys. Args: @@ -382,7 +346,6 @@ def SignUncompressedApex(avbtool, apex_file, payload_key, container_key, no_hashtree: Don't include hashtree in the signed APEX. signing_args: Additional args to be passed to the payload signer. sign_tool: A tool to sign the contents of the APEX. - is_sepolicy: Indicates if the apex is a sepolicy.apex Returns: The path to the signed APEX file. @@ -392,8 +355,7 @@ def SignUncompressedApex(avbtool, apex_file, payload_key, container_key, apk_signer = ApexApkSigner(apex_file, container_pw, codename_to_api_level_map, avbtool, sign_tool) - apex_file = apk_signer.ProcessApexFile( - apk_keys, payload_key, signing_args, is_sepolicy) + apex_file = apk_signer.ProcessApexFile(apk_keys, payload_key, signing_args) # 2a. Extract and sign the APEX_PAYLOAD_IMAGE entry with the given # payload_key. @@ -447,8 +409,7 @@ def SignUncompressedApex(avbtool, apex_file, payload_key, container_key, def SignCompressedApex(avbtool, apex_file, payload_key, container_key, container_pw, apk_keys, codename_to_api_level_map, - no_hashtree, signing_args=None, sign_tool=None, - is_sepolicy=False): + no_hashtree, signing_args=None, sign_tool=None): """Signs the current compressed APEX with the given payload/container keys. Args: @@ -460,7 +421,6 @@ def SignCompressedApex(avbtool, apex_file, payload_key, container_key, codename_to_api_level_map: A dict that maps from codename to API level. no_hashtree: Don't include hashtree in the signed APEX. signing_args: Additional args to be passed to the payload signer. - is_sepolicy: Indicates if the apex is a sepolicy.apex Returns: The path to the signed APEX file. @@ -487,8 +447,7 @@ def SignCompressedApex(avbtool, apex_file, payload_key, container_key, codename_to_api_level_map, no_hashtree, signing_args, - sign_tool, - is_sepolicy) + sign_tool) # 3. Compress signed original apex. compressed_apex_file = common.MakeTempFile(prefix='apex-container-', @@ -515,8 +474,8 @@ def SignCompressedApex(avbtool, apex_file, payload_key, container_key, def SignApex(avbtool, apex_data, payload_key, container_key, container_pw, - apk_keys, codename_to_api_level_map, no_hashtree, - signing_args=None, sign_tool=None, is_sepolicy=False): + apk_keys, codename_to_api_level_map, + no_hashtree, signing_args=None, sign_tool=None): """Signs the current APEX with the given payload/container keys. Args: @@ -528,7 +487,6 @@ def SignApex(avbtool, apex_data, payload_key, container_key, container_pw, codename_to_api_level_map: A dict that maps from codename to API level. no_hashtree: Don't include hashtree in the signed APEX. signing_args: Additional args to be passed to the payload signer. - is_sepolicy: Indicates if the apex is a sepolicy.apex Returns: The path to the signed APEX file. @@ -554,8 +512,7 @@ def SignApex(avbtool, apex_data, payload_key, container_key, container_pw, no_hashtree=no_hashtree, apk_keys=apk_keys, signing_args=signing_args, - sign_tool=sign_tool, - is_sepolicy=is_sepolicy) + sign_tool=sign_tool) elif apex_type == 'COMPRESSED': return SignCompressedApex( avbtool, @@ -567,8 +524,7 @@ def SignApex(avbtool, apex_data, payload_key, container_key, container_pw, no_hashtree=no_hashtree, apk_keys=apk_keys, signing_args=signing_args, - sign_tool=sign_tool, - is_sepolicy=is_sepolicy) + sign_tool=sign_tool) else: # TODO(b/172912232): support signing compressed apex raise ApexInfoError('Unsupported apex type {}'.format(apex_type)) diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py index 0f3c430ad1..8ee983f81a 100644 --- a/tools/releasetools/common.py +++ b/tools/releasetools/common.py @@ -75,9 +75,7 @@ class Options(object): if "ANDROID_HOST_OUT" in os.environ: self.search_path = os.environ["ANDROID_HOST_OUT"] self.signapk_shared_library_path = "lib64" # Relative to search_path - self.sign_sepolicy_path = None self.extra_signapk_args = [] - self.extra_sign_sepolicy_args = [] self.aapt2_path = "aapt2" self.java_path = "java" # Use the one on the path by default. self.java_args = ["-Xmx4096m"] # The default JVM args. @@ -97,7 +95,6 @@ class Options(object): self.cache_size = None self.stash_threshold = 0.8 self.logfile = None - self.sepolicy_name = 'sepolicy.apex' OPTIONS = Options() @@ -2629,38 +2626,6 @@ def SignFile(input_name, output_name, key, password, min_api_level=None, proc.returncode, stdoutdata)) -def SignSePolicy(sepolicy, key, password): - """Sign the sepolicy zip, producing an fsverity .fsv_sig and - an RSA .sig signature files. - """ - - if OPTIONS.sign_sepolicy_path is None: - logger.info("No sign_sepolicy_path specified, %s was not signed", sepolicy) - return False - - java_library_path = os.path.join( - OPTIONS.search_path, OPTIONS.signapk_shared_library_path) - - cmd = ([OPTIONS.java_path] + OPTIONS.java_args + - ["-Djava.library.path=" + java_library_path, - "-jar", os.path.join(OPTIONS.search_path, OPTIONS.sign_sepolicy_path)] + - OPTIONS.extra_sign_sepolicy_args) - - cmd.extend([key + OPTIONS.public_key_suffix, - key + OPTIONS.private_key_suffix, - sepolicy, os.path.dirname(sepolicy)]) - - proc = Run(cmd, stdin=subprocess.PIPE) - if password is not None: - password += "\n" - stdoutdata, _ = proc.communicate(password) - if proc.returncode != 0: - raise ExternalError( - "Failed to run sign sepolicy: return code {}:\n{}".format( - proc.returncode, stdoutdata)) - return True - - def CheckSize(data, target, info_dict): """Checks the data string passed against the max size limit. @@ -2836,8 +2801,7 @@ def ParseOptions(argv, opts, args = getopt.getopt( argv, "hvp:s:x:" + extra_opts, ["help", "verbose", "path=", "signapk_path=", - "signapk_shared_library_path=", "extra_signapk_args=", - "sign_sepolicy_path=", "extra_sign_sepolicy_args=", "aapt2_path=", + "signapk_shared_library_path=", "extra_signapk_args=", "aapt2_path=", "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=", "private_key_suffix=", "boot_signer_path=", "boot_signer_args=", "verity_signer_path=", "verity_signer_args=", "device_specific=", @@ -2861,10 +2825,6 @@ def ParseOptions(argv, OPTIONS.signapk_shared_library_path = a elif o in ("--extra_signapk_args",): OPTIONS.extra_signapk_args = shlex.split(a) - elif o in ("--sign_sepolicy_path",): - OPTIONS.sign_sepolicy_path = a - elif o in ("--extra_sign_sepolicy_args",): - OPTIONS.extra_sign_sepolicy_args = shlex.split(a) elif o in ("--aapt2_path",): OPTIONS.aapt2_path = a elif o in ("--java_path",): diff --git a/tools/releasetools/sign_apex.py b/tools/releasetools/sign_apex.py index d73998247a..a0a94f67aa 100755 --- a/tools/releasetools/sign_apex.py +++ b/tools/releasetools/sign_apex.py @@ -56,7 +56,6 @@ import apex_utils import common logger = logging.getLogger(__name__) -OPTIONS = common.OPTIONS def SignApexFile(avbtool, apex_file, payload_key, container_key, no_hashtree, @@ -75,8 +74,7 @@ def SignApexFile(avbtool, apex_file, payload_key, container_key, no_hashtree, no_hashtree=no_hashtree, apk_keys=apk_keys, signing_args=signing_args, - sign_tool=sign_tool, - is_sepolicy=apex_file.endswith(OPTIONS.sepolicy_name)) + sign_tool=sign_tool) def main(argv): diff --git a/tools/releasetools/test_sign_apex.py b/tools/releasetools/test_sign_apex.py index 7723de7a91..8470f202c5 100644 --- a/tools/releasetools/test_sign_apex.py +++ b/tools/releasetools/test_sign_apex.py @@ -58,21 +58,6 @@ class SignApexTest(test_utils.ReleaseToolsTestCase): apk_keys) self.assertTrue(os.path.exists(signed_test_apex)) - @test_utils.SkipIfExternalToolsUnavailable() - def test_SignSepolicyApex(self): - test_apex = os.path.join(self.testdata_dir, 'sepolicy.apex') - payload_key = os.path.join(self.testdata_dir, 'testkey_RSA4096.key') - container_key = os.path.join(self.testdata_dir, 'testkey') - apk_keys = {'SEPolicy-33.zip': os.path.join(self.testdata_dir, 'testkey')} - signed_test_apex = sign_apex.SignApexFile( - 'avbtool', - test_apex, - payload_key, - container_key, - False, - None) - self.assertTrue(os.path.exists(signed_test_apex)) - @test_utils.SkipIfExternalToolsUnavailable() def test_SignCompressedApexFile(self): apex = os.path.join(test_utils.get_current_dir(), 'com.android.apex.compressed.v1.capex') diff --git a/tools/releasetools/testdata/sepolicy.apex b/tools/releasetools/testdata/sepolicy.apex deleted file mode 100644 index 2c646cdfd878d42b6aa6ad3a0818bf685ca35916..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 311296 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 z00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck) z1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`; zKmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l z00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck) z1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`; zKmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l z00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck) z1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`; zKmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l z00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck) z1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`; zKmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l z00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck) z1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`; zKmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l z00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck) z1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`; zKmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l z00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck) z1V8`;KmY_l00ck)1pdVY#sUrY63MwjHt|SqIGq}^5#;AU;X~!InAV z(B6`xB{I3&H5!@Ic>SDbz8ZRkeidB`y4y`P=Uwew+ZD#H`OCL%BbwV^uIQ=YUy;ca z`Eb6W_WBti>M$9S78jZ(6x!UNyDvS~hECK>JXPu#&@00&!I;%IdcRkEfgCeUhbe&l zN>*bFf{pXE{?$`!QXC$&%vWQhqmB7C>{-3_ zMw2|mJ*p}7CoU_dS(ipc`adhV&boG*MWprBLhgx3mj(vTO@ort{-JAaQs*%Vhacu_ zNuKcdTBr8BRkTEwAqqp4gXHg>J~+S_R?d5P?(7BjsGck6udMk}69^X!QYP4oG^B>3 zbPPkdRU0kHR$nf2$*^OE_a)@NI7XZB-XQMY$00|SxieJ3N%y>kt(>~R7Y}8bWrm10y;ykubmTgb8j)l9$&bNx`{=Fk-zRW;Keoev3X{q z5VvSNEIuXBK-if{t%*()uf+EnTHvMh2M_GVk15^Gb79w1>be%V{%M)BlfK*|#58{_ zJ2?DObr!~#Comm+VEcgg2kamIHTRGIVy7$691s8j5C8!X0D=Dj0jYD+e8}0L{G1i) z>F>iB`+d~!!+-ft|6}M!|6nqT*!O&N?Co+eFfTM6sqfnJQBI#j3i~x#<<9d3|RP^Ysp^ZE zK!p8M?$%PKnzbXWCAF7n?vtBiJL`uJ)T^*OiNw8ls&XSk^{Lr9choqMrC+ppe6NY0 z^_#po4w`zy&HI>=8T8#+1GK)n5riREgyxPsG0s1w>iU9j?0mwzF#qV1Cf*{lF9^1K z?0!Yvl%DxwuO`)GBqFE>m^ez`hx3=!P!Kn+Gz6F4xQ{(jB`?=iJ-9WfgecnC&3hm? zk}g2OuGPG9*RPZ!TXm(Qgm~5vZ=)G;ozJNlX&M&5-vN& zB19hM^Rg}LX+3U1J&uLG+ws6C=`e=}!Dhmp+scyDo3H^m6@wrxfUBko~DtkPbASIV;*mQIhr{l z{In)6sr`xpE{9p7_H?J8ON&jn^QkY_b#*BgqgRJ@#ZNugv#%%`ysh~lkYYhePQ;Eb z=iI<)jB_?gQbpt+Qxw&4nV8^&KPUE7+78It9QT8`(j|cCcIppiV52LMo zFM7~N5F^JAy1$;Qn~kY}u*hHM|CU3K9N+K$t`tCcG-Ca-y}5jQJ5xp53r6GeJ>_`J z(<8)tC(EU>tZ*LnENu}~?jDtY6FqW|Sz_-*+Fi2qN%RE$%4^SMXZvmbWZ?&I-5>9N z1evL`r8keAr4Pc~$$`hz(Zbcq)`G{)(%H%0*4*dY42Ti@%fctF-wb4T=(wJLCf>ae zJ(=_ZpQ=`b|4lvl4(*{FCB_Ym_&2u|Haa2(5Bu~Af$I$k3q3CAQ*X(%f@inr~f_UwNBXTqF!?kLqg zLx;{-lUUo}agV8~jgNXvhS^GA4AmP+YW!VSIy1v(h$i{e)Xw(jZy7OzXiY-LErw63U8f$F8pqW8PP9Jj3ZPWG|HO6pJs;OmH?K(|&fNXo{qt zzJ07aU-L44{_BwNVWQ_uVa)#Mb|oY+#awj#Unsmk-+#PSMXEKSB`tSF;^F00>KEi& z)A6-e2HX~7Ogsgr+=PY2@K`O&MN$njLT0j+NKVq1>8euOm^K^?`_l!tvOtAy|RV?29NJDH{%;Y}V=Cq*bo&~k5?J6^oN@~9hAB8!W@iaEa= zJ^sqX&Cu+P(d73`O6{5z-IX;}vdY+=jK^&rk?((iNY^ug5 zyw7V;>^)Yu`ZuB`3`*}LEBVtL_hlF4zgK%U<6Utuk1-;gm9Y0# zgfpbN+?E9;y~Qbeg_m}EMVv5~`9Ds4?RLVj%4YK$2LCG;?{N}^dYZ$t;d zsn$_qCvNVGoSSiEh*-nhqk*=W&c-k61F;sUWzSrXOp&i;d*wG2K~w!F`x3|k0T2KI z5C8!X009sH0T2KI5C8!X009sH0T2KI5CDOHdjaI{_f|-RgWn$!h=bo7(GO^_0}qD& ztgo(&hK`FseEZ#wL7d!b!xx1VIXh%Wj_4*bb#t?HcjHx6(^iy`<*{*hu;;P%6&`6P z`u0oapUn^QKmY_l00ck)1V8`;KmY_l00ck)1pfI1jz)jKWEXYsmBF&(5z*^=r_?lx zT4|ZHh0D1*BOPP|hcrDsW);GiZ}MUXV-(c-hFOb@D3=SrCs-OM(qXP0$&Fu>iNSOJ zLVU@pCi-)OzmDw9?fLDk#qr){-V4)*J;YB;N~)eaRya5k`LSMl#V=a&gZJeXtlMrU z3MxYH`fd{FYv^e5nlyi0pCk}^!gJf7pm(A%pjRBDA?SL~vS6ei<0?&MznG76b|WwQ z@k8V1b-%uJjv&|(xPSYJUk+A7>qY@l#*vld1r?;c^-l%|gxqd%+F1yD4b!l6?!Ken z7CCV|;%)qq0>Qw97n7t;#e`orRHg2QxFM^dwbXYW!KM^k>^~g;q9SvUboO3f$Y;>z%JrN?x|ikvvsG%0#5 zdy(R1=^k2Y!0y*nc@w%<#&xqqU*+XL26U_w5x<>F$K$_0zVkZMa2MrC-RAm}ZT{dR z0axiuyy(&krqc#__vE`i-ee0HeU0U$H2j)qqkoonUCMYOH^ zs?4bl(`89sT7TP~a&F?`eIml4h0lg7{BGtcSn)Dme7Tg%JdXY~p%q1L-k1D5{C&K9 z{5c=xo)sXo!OZ=4Bdb(Oj6g+{xrAYN->2Os>^rDY*2xAJ5CT?)N0-@)$F}>-K4c?u+)&WvM&F}`wHw1|}ZVJ7!7bn?< zLPF(i%FYOIWG>gZl2dbCsFyz1oI|9Eou6rE!`A{)1$yLj@WDO3@ zk55_el|Ow-9o>(JRG-bHyitQLkRqFMtV2RKyG=h?^A7vxnBKd1+x9it*+%B!PQlho z(-ZVU3vr`>M(uL1d#mA5li(z?3=FctH}s>B-FK`>2Qc6_)rTqr+B%qz2K# z`q4U@RaVNEb8>04+VzuzbNP-3KEM55S2numiU?hzJ+sRsftc1;;p&@HGPi`tDP)QT zZYi*yRL{Gt(P?S@#f#p(G~Tm>axe7uYo6dfzs;VU1+u3a4k~B_$(*+=*){2XI4g76 zWL?E}u+5`acPfP=i?8mqWVr1z#vPH7eb3xOd`#-9MxC5C_FH2G77C8(Xs(;&iC5eC zl}U`w;`=aL$~x3>xhOr*aEgcLJh{R8 zPDDJmTb{q%xZO30g{U}BG!tR5mgS+gr<*9+W~TOA7WM*{=q%%@bd=riNEk>p z`dX4<;Zbp>XyR41uE%}!?y&pJEJ0R!_sJcJwIv%F)}uvNTjicHIF^woON4!TVkTsx z@i{whw3`=ku}}MqUTBJlw01omzG7B{CfREH4K(&p4wMUPD>LPvt)EAAdV3Qq)49tzKs! zDbrlLkxe8+o{4Mc8MQN)(dM;8z9iuZu_~^uKvZh^ZF_Oz5MqN*ndx{XS&m~xw1h&LAUOK^y2obPCp?D3`#=^n)wd>4vc%;fv7GJ%*_qjpq$8PCNxo^~jEQ$yXM!3Q z)=wYH@oumnfR=3XniETbBEzcGnIUW&rXCX!Xp85w=z<)F8lID zrF1q1>*8*uB^VyT{t$~{-}q)o>|rl16Ti|R$z$A`w=q$aijCMLA`6|Vqk>n^J~Evf zein68+1Zc@b&BV~5>uwDkm}7ArF9#s_APSE2$9b%=(jZm4>Pd{Gn>b>9!l*ycZZNH z$D;ln=MxtC^Ei@Xud|KjA2k$9b9nQIAMRpPcRN)>vXeR@QlW0EpT^hAeAEwfr%VxB z)r_S!AKU*7w)|ESHOHsQaYB}z$g}5SxJfxJ-0xxX>sE^>rFq=hVE3xeBG`Fpe67-r zF1dxbjB<-4`nElb=0FF-YM<;tKpZ-`;ZczjS49ow2GgQHbn#)+Fo=BCd%h9Q5YIwc zo*#c>o7p{$=R}s~t2YB@67+XiIML8D=i4nk%Vl}j~nFfyKu<$EpGQ2H5 zig#XmJv~@8>D;2m0L_P^hh+pj0^MJFr8x1Ts(;*kqqj`vzseQ-cErag(emyE<(88J zd}G{9N&4C-6tb=Lb90v;>C`7?ggj48XJ8fWA(o<|xV1^MV`cHc&bP>aWIhBd^od4o zk`vZjPV?svqHavTj8uvDKH?#9(dMnuQpL%-_JpmLk+sb%!jjWN=8ta;R3G7-G~a5Y zh^yi{G}D=HUS>U4R zJXDX@4$&imuA=B!;W8eYex@fv!J#+m)LiRgrMg5 zivw>?W;#8Ym@yyH(D2>v(Rdk!!lVWr&`eQS^{$sAbJ4=}Ic7`ZTKl0{{B>v-do!Q8|VV4+_5w4%FUt*=Ssu zxwiUpgxhcelX*R*Vyts4?_FGqS3vEqGuF{wN5`I$B_3YK$t~6f3g;cB45J94*k-Y95~+uZ>16DY?JSd*8pV zx^8QsRgLnz7sj@`%g1#m>+@dL0o9kid^QAY2aroVPx)|i>xo|8>h6b`rM1OyFKf}2 z-RADE3uDph9?e4S2uv(; zj_L~+o_2BHb8#GY%43p<7&tNU*eNdInk<`Ti>@wn0((2wQyi4^b3$gvGgk-4<~y3a zzcg0qcDT&3da!e%J~DlA`ZQ~o_ign^n~vwxO4p+Lf=f-Y)LvY^Gg6}EU$JWG;3-ow z|3p+%-rVYo@J;#;{+da!t3fD z1@hOLTb(9Cs>-bLOk558k;JxHWTuXMiefA*Uv#PW#@}XX054ZRwhmB<kGFV;^|6i`W;fH#l0~r$Bw>E?dKSa#UDOVqUe3+a6|qX@qCS$6q4RYI@E$&V#hmP z(f35$^}6oyte0$sVM0&zb9Oqe-E$ppb4%?i%S0ag=Cor$AEctq#k_`M$124XDSB?* zUWt@>@pkoyPrrO0KkoAcOu0lJj59VX@AvkVqY~ZZz{2?@`y1o3*|| zbo}WtkxDasi81ZeK^o7XsVd5ed{)&X_0OU%-h0<1GW{-aAY^Pfxh-0#g$I`{+{pjA z+2{+x+;kTNqkZ-D4fad1cu!5X9xn%knxqD3IXPMdOh3(?($tU~R+<|aHr17S`zy*qv8l(wj#;&fDTE{NWi+`x?d`Nlz zA?4TJ+1|~h`%XZfkZ_bHnnR_3OQrk0KU)`x_>?hP6Q*UtBB(3#Lr-Q(BJ$Enyk-ZMgqb$|A1 zFM^GS?(LlEgT1*1y>rD++RoOj8s{>Pd}mNea6EbI<_olsb-@7q&|a3H)l1@Gas?+%ur5}PVdc? zZ*F&cR3kCf&0DowHW#Mi;m-M@w{RHigmkFv&z^k=A4w}*J|@|jnem(_kQ)|S`2j!K{lT-n~#A&`X}ZwM{S}< zC#zVRW4xTv&8_e5Q4ue5&_68=E;{sq$yORq5kEFHj7_eBpAFps9s9a3VxapxVt0gW z<>`a^?YwS%)2gnit556&@HIQ~YV|(H&@Az?<|Lr)%BQknP2Rk@tA_j*Zf_6Qcb6tk z9D(@b1u%H(BBaNV)Au_<#2~(_rJIw7tGT5ckEyGhx$sCYK3w_o&-W!J)CdGX00ck) z1V8`;KmY_l;BOK@Mc{w?u(SVS6vPqa-!BPySHZ#f{>LOT1UB~m)dCF2k4--l@>T(a z)b{{iDyELMR+et=Jl+oWNeSIN=u%{%YTYquZP}3+3}$Tcn?{i==NLu&hs6Eay5`*D z8ZyI6KVehQHH{ABdZRq%5Y8`13wfX6{-Sy#(u5K$GaCb8%2HJIjo`~V4`zKch&FVrm;!0#l<^|)_Ys8{e6u1MQ4Xjs-%1g zKvh@9!j3(oOj?iJ0-TX|lKjVO{}6KR@5g#GG4rssw=l7Fv~uEcHoJO_U#*#sI20q8 z1r3d9a1w{3RaF`9*hT$Y1NEp3{Pa4k)k2P>WAD>6uWe#%%lqTyhv1S2t-P5WpPjwr zv^TA=(&lp1KP!Crl^Vatl+RhUccnD5g)ON$^uz;iT@4lQxiWh_UEDcZxoPgX@_;3q zTDE_dJkvw`@XTc;xd3_9u|<`ZfCijw<<`&67D`E{LfP21^+xviP8PJLK7VKOf%nLn zKK*dh`(xv~$rSeIdRD^g|9p9U~D#iWuvnpDG&*OIAU7rm; z{7EyRy3j8AAQ3{`>eC9!iXkn6hSF3e3byw)5_?V*)8Z-KbgbsWp~k+cd>LZ_<7)k3 zWsYX$k9V+K!|sj8z0WD4HLO`bvA;fq)wfO6k?SKG8FceMULX4r2EVNjhlBT`v)R=3 zvpu~pl)IGKJ9^LdbEuo<<@?4oq;U4h?bmYvius@CT;2lL4rA2oC@yziKX721^ zhK%ipf{niMbw8*f1oDGGeh|p--#d`s{$6Ajx2%@{dAPv-PY)UUyC@Y|Z7FU=RXJW6 zSxs#oEjfptF~=NrGR!xZNM8qartuw-l}#|FSC0#fpsy7r2q$pFSWmtZtoB-U(CUV` z7bmBk-`;X|CHDLLkQl5^+(|Pf9In^PcUkZ+QVCWpnM>_xMm=gdmY=bK6`B#Saj8ez zv}H1)UmdsT27fRr%W`vKVN!BNr0}Z1g_7t+mmw;Wg7%Fyh2<1^+O%}ClG{qo24C*J z{+OnAL*s$n3@_0XJIn4Z%pKzfYs}*rnl~v;NbJ+CB|jECM^tWI#}_}esAcS#OVwgX z6?KB0RLb{0w~A7rLP7KJ zR$LE77P;_lsimMBg`t=fZfVjbj0GzFM{hlb=X87HJGYT zp%eKjl4hB1DEJ2Tr-=wfEF0tQ6wfj=MB~=v?kqksa5&On+qC=6?uKmuTM_OMxwZb+ z+l3WR#e0W8?M9rqkf_T_WjJs#tEuHge>c@Vj^2FA64sb?#8_@s^O&&6<4KlcXDzu> zvn%CMbT67+4KYa#3ldWA)CO7OeRA=ydKDrgNcYf-g@hu!f}yD7)2+uS`&$cPXkyt4 z@_>ei_MaVdNd9VT(bSR>v@siNYrFemrhh#%B|g(uige8N3hRT`voSGY+n9Cs88{Af zF?YMT&c+nuW4^);jlsL2J47W^R?KH(mQ@;<78xHInPBQVal`M!O{(Q9pQOkI%27WH ztMb1Sd1GNtXlY7ng-akpCPIwvA{J7eLFy(jxE8%8KuXYxf97=XhbMK9zufj=iA?js zHBGxa9fpc|ncjuR)bnXwV$OCKSt_<@7`CAFnfB8tOPH#1{F7%Q9-$=;DfjdWUn89r zF6HABC>P=r5)t8#J~S&VBFrZ$6z+;uRFO_aehrO^ioZ)GrVjtK3t>L1oLrb)0*l@f z?bNxE&2{gAE9s)ziywMs>XQ9F-MnYr8ZE)uV`WnE%%G?09Lvo%KS>geO41)|IOH!vj~<61fgdx7QtZaYs)ek`BMfW(^q?D)VT4I^D^(;aBUvF zHpnq$-Zu-7S9B4LV?*CiG%?beH0ZwcJIsq230zO;&I)J|89{^8^RSwTzdiJSF3 zW=*WfA1}-M_4bTA1o;Jc9kINleehn~aRGfyHziS_Wb)(- zCYwI*L(KQP2E6;ST};r<3F95M=C;i^rJyBEsu??l;v<_H`XVqbepOO`*BtY9RN?1A zUt_EzE*1odkDTp3`?k%Mlpp&%ofA#yuzLOw$xgt+5kc$cyDT-YM~+9ik_p_T>#41c z4K=-)Uql$bajV7wZ(*owob6Wbn}__mE6Q^ONe-qDZ@$uSO}%3IvTDE{Fm}_$v50Oghv)I_0&}T(>dxlvmY1dJsUS)(^P!u9&jhXeD|SH zlRjIBmlMI%kqV=9tK;N!WDQ3x+`i7uwsG`gEvKx0xW`L!zi2I`(qlKL#W*o}c4}K6Y-^?j90|Y^ASz8)8XY7CwWs`WQ$8uE{Y3Qk2Uu_(X-^m zo8o2QFA8*ijBnD++UC~C<%+9YN-e}glgA%*E{rjy{?75N+mZ2;9a#<0?kq2KODGoW zw4SGbDS4si>vjEAT@N_=0=Sn(|tXL@ZeuoKl^T5!7WRk)Eg z{IPbWc(VHViHGCF+GF=BM3>K1t6e=IMrJ>Z}6%9fxD@ZtPWE zCLeV!PD$Y(erlk=C^|Ddt|GV|W&cQ@wW45eK{CITsFSnhyhOKzp3Ym*)$Pg;YOP)( zF9kziVSPJ!_9$Acib5XpYB2w^9U0lLJ2Dk1RYf^jEo~kZ@Vbh18j`OQQ` zApLDc-ZP=n7Ke^0D%zQbS{CHrn{ z{jr^;bEi6$j=Ct)d5ph7f8O;~y*2y?7BgSRGZIc9n6h?;trU@bJ%71h_er|M=md%9VV2z}(zBgdIQ-L>rJGA|3%-u8 zKYGC>qcIF!zF_n_e8Fhf zve8gcP*HIZ368~&vDQ#t^ATX;aD*v@Z4jWKU|=Blf)e@h_;4`MbkH$LQFXNVN%=_j zN3cn8WlZgDt(;sPZB6+p_{jD%u}BG2oID)eO>G_N&)Hgf@e}hA?&sr@V*R)P;UEX^ zuX50S<}mWn|D_H%C={eWo=$f9N1q4z#4QqJKc7J-i`&h1@<$9wM1#cdV3?Myza`(&Pga+JbJgAGdOc@^{qAX)|PURA(em$Ho$P6PQA| zbP+>PZzezS16RdbNOqppGvCDF6Z|;h)}lPT7Ka)g9i&%vsb24*1$E&+KQn4S-u#GJ zms$a(g2jSVtzjay{g{`+*k|jD+5|z`&uTlcs^7}%2$Hne-PmxN_|O$=JNqoBFV>rt zK2b8w)$b(wu4@`?+UxWZvFvinfLvOGi@WnjQBlxxgSPpCzVOi^Hym0*bP9Ab1wF($ z2{tad5|S{CJa_71oQ@b0zM%H~=}hQBO?*L*1ON0jw}Niu1*QCxZVAD^yyNiqn?j0? z!H0%|viIxefqefVHxCqq@BQSM1@#sw3=OKj(W3h(22q~+Yz5>wPb$?I$;kIcd(d$7 z8}hg}u&`b2>20T*9mh-15s%qrxv-U=_&>PiU!3`vii=*gOpD}`&M@ZgY$+nKRd;7u(E^UJP%P1KlHRU&?U79FgYn~iy{TSq{_+~jDzdpd7(%)B&T0-U% zBbKX`<5`s}llA&x*q1-5aerWVE8AhuSxPZ;#XL}bpoMR#;K{`!ii4iGxX){pM+BDJ*f*AN97j`$jB?p`&YTRoMdIIEu>FSZi}ZRQjhspRk^7~m4JS4O zJ~V!o$4hzSpI(aNsI#K2cj+xF-k|JuZA+Oue>_cFB)z;R?FH_)wMie(y{`>zZq?yC zf-@$7;_s5m*HiO#iLGe&{q^;X<)_(Wp8{V{UnYl0{4UoGL z*uDH0yO)FGXrqnR%E57T1&*U|9EIa397q4fkE2w`L*?GyUcmkV^kDe+;E&yS6&Gzp?6%jhVQ-j-ac8X@W+o@(UA+~knx%8fWG?gL93Hk(^^A%*BYDpskqA~wI~m%iHiFNH{0>3MQssk%j@)+&eg~KBn*>j z-ZI49Zf8h5?4KC%ZoDiy-^YaU;ydXeRf*=O=h^5aL~t^xwUXm-YNK5_ee&Qq`nR2? zE9{?lA(NM-tMvCF`2Y6^9DL4~Qdg$evbCm{ws$hOqZblTS4Kg_Mf>>#0Fij&^9Xw6 z2@A3#MhyJ;b^w9M3))}$w=0XCO?~X0Of7h99jtdd5D4Ls4tN~E-?M6LVV~5cs}4S9xw<3AOHd&00JNY0w4ea zAOHd&00RGK1%CfN;P(LOKO2Ukg^qx5=|4SS2n0X?1V8`;KmY_l00cnb|G2>Z$00Jr z&p_zE2FczJg#I%fLq=Bqld$N&&*0an&ik>ne zQT~@m(7%`dHDK$0aOaitaWPQMiYCB~k*G6pg>9|HNiHZEd@2RU#J9fAJi4uhYi{~d?^ EKdgG@v;Y7A