From 7573ff7efb99d93274305f69ea07b505f3921a57 Mon Sep 17 00:00:00 2001 From: Victor Hsieh Date: Tue, 2 Aug 2016 16:47:01 -0700 Subject: [PATCH] Add script to sign Android image sign_android_image.sh is the main script that signs the image. It makes similar changes to an image like the Android official signing tool (sign_target_files_apks.py) does, but more Chrome OS specific. TEST=./sign_official_build.sh recovery recovery_image.bin \ ../../tests/devkeys/ out_img TEST=Same above but with a recovery image without Android image. Android signing was skipping. TEST=Same above but with a M53 image. Android signing was skipped. TEST=Unpack the image and diff the before and after. Looks correct. BUG=b:29915721 Change-Id: I0ae5f0ad8d2b05e485d60262558517ea563bf527 Reviewed-on: https://chromium-review.googlesource.com/366794 Commit-Ready: Victor Hsieh Tested-by: Victor Hsieh Reviewed-by: Mike Frysinger --- scripts/image_signing/sign_android_image.sh | 219 ++++++++++++++++++ scripts/image_signing/sign_official_build.sh | 30 +++ .../keygeneration/create_new_android_keys.sh | 63 +++++ tests/devkeys/android/media.pk8 | Bin 0 -> 1218 bytes tests/devkeys/android/media.x509.pem | 24 ++ tests/devkeys/android/platform.pk8 | Bin 0 -> 1217 bytes tests/devkeys/android/platform.x509.pem | 24 ++ tests/devkeys/android/releasekey.pk8 | Bin 0 -> 1219 bytes tests/devkeys/android/releasekey.x509.pem | 24 ++ tests/devkeys/android/shared.pk8 | Bin 0 -> 1219 bytes tests/devkeys/android/shared.x509.pem | 24 ++ 11 files changed, 408 insertions(+) create mode 100755 scripts/image_signing/sign_android_image.sh create mode 100755 scripts/keygeneration/create_new_android_keys.sh create mode 100644 tests/devkeys/android/media.pk8 create mode 100644 tests/devkeys/android/media.x509.pem create mode 100644 tests/devkeys/android/platform.pk8 create mode 100644 tests/devkeys/android/platform.x509.pem create mode 100644 tests/devkeys/android/releasekey.pk8 create mode 100644 tests/devkeys/android/releasekey.x509.pem create mode 100644 tests/devkeys/android/shared.pk8 create mode 100644 tests/devkeys/android/shared.x509.pem diff --git a/scripts/image_signing/sign_android_image.sh b/scripts/image_signing/sign_android_image.sh new file mode 100755 index 0000000000..6f9997937c --- /dev/null +++ b/scripts/image_signing/sign_android_image.sh @@ -0,0 +1,219 @@ +#!/bin/bash + +# Copyright 2016 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +. "$(dirname "$0")/common.sh" + +set -e + +keytool_bin="/usr/local/buildtools/java/jdk/bin/keytool" + +# Print usage string +usage() { + cat < /dev/null + zipalign 4 "${signed_apk}" "${aligned_apk}" + + sudo mv -f "${aligned_apk}" "${apk}" + + : $(( counter_${keyname} += 1 )) + : $(( counter_total += 1 )) + done < <(find "${system_mnt}/system" -type f -name '*.apk' -print0) + + # Sanity check. + if [[ ${counter_platform} -lt 2 || ${counter_media} -lt 2 || + ${counter_shared} -lt 2 || ${counter_releasekey} -lt 2 || + ${counter_total} -lt 25 ]]; then + die "Number of re-signed package seems to be wrong" + fi +} + +# Platform key is part of the SELinux policy. Since we are re-signing framework +# apks, we need to replace the key in the policy as well. +update_sepolicy() { + local system_mnt=$1 + local key_dir=$2 + + # Only platform is used at this time. + local public_platform_key="${key_dir}/platform.x509.pem" + + info "Start updating sepolicy" + + local new_cert=$(sed -E '/(BEGIN|END) CERTIFICATE/d' \ + "${public_platform_key}" | tr -d '\n' \ + | base64 --decode | hexdump -v -e '/1 "%02x"') + + if [[ -z "${new_cert}" ]]; then + die "Unable to get the public platform key" + fi + + local output=$(make_temp_file) + local xml="${system_mnt}/system/etc/security/mac_permissions.xml" + local pattern='( "${output}" + + # Sanity check. + if cmp "${xml}" "${output}"; then + die "Failed to replace SELinux policy cert" + fi + + sudo mv -f "${output}" "${xml}" +} + +# Replace the debug key in OTA cert with release key. +replace_ota_cert() { + local system_mnt=$1 + local release_cert=$2 + local ota_zip="${system_mnt}/system/etc/security/otacerts.zip" + + info "Replacing OTA cert" + + local temp_dir=$(make_temp_dir) + pushd "${temp_dir}" > /dev/null + cp "${release_cert}" . + sudo rm "${ota_zip}" + sudo zip -q -r "${ota_zip}" . + popd > /dev/null +} + +# Restore SELinux context. This has to run after all file changes, before +# creating the new squashfs image. +reapply_file_security_context() { + local system_mnt=$1 + local root_fs_dir=$2 + + info "Reapplying file security context" + + sudo /sbin/setfiles -v -r "${system_mnt}" \ + "${root_fs_dir}/etc/selinux/arc/contexts/files/android_file_contexts" \ + "${system_mnt}" +} + +main() { + local root_fs_dir=$1 + local key_dir=$2 + local android_dir="${root_fs_dir}/opt/google/containers/android" + local system_img="${android_dir}/system.raw.img" + + if [[ $# -ne 2 ]]; then + usage "command takes exactly 2 args" + fi + + if [[ ! -f "${system_img}" ]]; then + die "System image does not exist: ${system_img}" + fi + + local working_dir=$(make_temp_dir) + local system_mnt="${working_dir}/mnt" + + info "Unpacking sqaushfs image to ${system_img}" + sudo unsquashfs -f -d "${system_mnt}" "${system_img}" + + sign_framework_apks "${system_mnt}" "${key_dir}" + update_sepolicy "${system_mnt}" "${key_dir}" + replace_ota_cert "${system_mnt}" "${key_dir}/releasekey.x509.pem" + reapply_file_security_context "${system_mnt}" "${root_fs_dir}" + + info "Repacking sqaushfs image" + + local new_system_img="${working_dir}/system.raw.img" + sudo mksquashfs "${system_mnt}" "${new_system_img}" -comp lzo + + local old_size=$(stat -c '%s' "${system_img}") + local new_size=$(stat -c '%s' "${new_system_img}") + info "Android system image size change: ${old_size} -> ${new_size}" + + sudo mv -f "${new_system_img}" "${system_img}" +} + +main "$@" diff --git a/scripts/image_signing/sign_official_build.sh b/scripts/image_signing/sign_official_build.sh index 4f3407ef0d..badfaa92fb 100755 --- a/scripts/image_signing/sign_official_build.sh +++ b/scripts/image_signing/sign_official_build.sh @@ -590,6 +590,35 @@ resign_firmware_payload() { echo "Re-signed firmware AU payload in $image" } +# Re-sign Android image if exists. +resign_android_image_if_exists() { + local image=$1 + + local rootfs_dir=$(make_temp_dir) + mount_image_partition "${image}" 3 "${rootfs_dir}" + + local system_img="${rootfs_dir}/opt/google/containers/android/system.raw.img" + + if [[ ! -e "${system_img}" ]]; then + info "Android image not found. Not signing Android APKs." + sudo umount "${rootfs_dir}" + return + fi + + # Sign only 54+ images to make sure it works on dev channel first. + local milestone=$(grep CHROMEOS_RELEASE_CHROME_MILESTONE= \ + "${rootfs_dir}/etc/lsb-release" | cut -d= -f2) + if [[ "${milestone}" -le 53 ]]; then + info "Not signing Android apks before 53 (incl.). Current: ${milestone}." + return + fi + + "${SCRIPT_DIR}/sign_android_image.sh" "${rootfs_dir}" "${KEY_DIR}/android" + + sudo umount "${rootfs_dir}" + echo "Re-signed Android image" +} + # Verify an image including rootfs hash using the specified keys. verify_image() { local rootfs_image=$(make_temp_file) @@ -772,6 +801,7 @@ sign_image_file() { echo "Preparing ${image_type} image..." cp --sparse=always "${input}" "${output}" resign_firmware_payload "${output}" + resign_android_image_if_exists "${output}" # We do NOT strip /boot for factory installer, since some devices need it to # boot EFI. crbug.com/260512 would obsolete this requirement. # diff --git a/scripts/keygeneration/create_new_android_keys.sh b/scripts/keygeneration/create_new_android_keys.sh new file mode 100755 index 0000000000..a233a97a92 --- /dev/null +++ b/scripts/keygeneration/create_new_android_keys.sh @@ -0,0 +1,63 @@ +#!/bin/bash + +# Copyright 2016 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +set -e + +usage() { + cat <&2 + exit 1 + else + exit 0 + fi +} + +# Use the same SUBJECT used in Nexus. +SUBJECT='/C=US/ST=California/L=Mountain View/O=Google Inc./OU=Android/CN=Android' + +# Generate .pk8 and .x509.pem at the given directory. +make_pair() { + local dir=$1 + local name=$2 + + # Generate RSA key. + openssl genrsa -3 -out "${dir}/temp.pem" 2048 + + # Create a certificate with the public part of the key. + openssl req -new -x509 -key "${dir}/temp.pem" -out "${dir}/${name}.x509.pem" \ + -days 10000 -subj "${SUBJECT}" + + # Create a PKCS#8-formatted version of the private key. + openssl pkcs8 -in "${dir}/temp.pem" -topk8 -outform DER \ + -out "${dir}/${name}.pk8" -nocrypt + + # Best attempt to securely delete the temp.pem file. + shred --remove "${dir}/temp.pem" +} + +main() { + if [[ $# -ne 1 ]]; then + usage "Invalid argument." + fi + + local dir=$1 + + make_pair "${dir}" platform + make_pair "${dir}" shared + make_pair "${dir}" media + make_pair "${dir}" releasekey +} + +main "$@" diff --git a/tests/devkeys/android/media.pk8 b/tests/devkeys/android/media.pk8 new file mode 100644 index 0000000000000000000000000000000000000000..53fb253154f94817cf26e1b9b66939e09c360ca3 GIT binary patch literal 1218 zcmV;z1U>sOf&{(-0RS)!1_>&LNQUrs4#*Aqyhl|0)hbn0Muq>eIAp6 z{~bG+e&o1V`^*FzQAKp7%Ek2~@dyEP)sV|DA!)C}{pSUF3WP~pEGV#n&d8c<10i8X3MQJ=Lv43^% zPomn7nUk4(!fScEg2O?-k4f-yDxKciPSSu40BcrnGJVEQcMet&kBYAEiswQwEyz6@ z?{`j9R1ux2_V7ET`cjB!g)^$E+{fF5DT;VEXX1j8)+wU>K}pp~Zvq1W009Dm0RaHh zLoPcnNQDaBX2YGMo&i)MZwo}>63{#Bev(U4EibGCBR4W+&fwt@0*_>60CKk_CQb~n zo(7V`bi|C*R^>rXDf?(uioj=`Q80wiM7ZMt#G?q$2pt*kO$HyQ9%bLuk!f~txN15dmA|i$(#!`|Y z)d&~=bN8ZP7&KH%6e~O}X&&CtEC7V3Wa`A-(gsnkdf3C(J>Fsw_D0efWSj87}>XMR=ltj2WRHgU)` zmSac23w^Yy5-AdcTYkJ!W34XTX@9E%fq?+%CBmEI={x?Y+qY}y2vQ~wSfZJArQpBq zK(u27z75P&>ZrbWZ^GtdLXzbJWM||EhEoLy+aLj_X2BA&*5dm|-L!Xas@=ob)pB+< z*=6N&7wIgH?XEI`n?IO6k2w(o5_&9DY%AdXVZm3oWiUWcyS_ay6Qbwfm%cEWcBxGQ zfq()ufv-PUIPGZpwMGo%NI&pzRY)Fsmk&v8+wcZeC;DkRsLra>vtGISMI!g-HFRK$ zt;NK#Pb1r9SM3x@w$}q8>7*pyb#hYWbR#I<-QA(Na(oXGm!*-E`g~v@5KnB_-jQ(^ zw;+UQqYR|sHqgPEyDCC-RS+K1b7}I4u)6|*fdII@kT_e6im)n%HaC~FeJSZ^~ zo5V`eIPSqe8V04WtRnGMROFb5v&TRLqDe^I)P;55CwJhmoELJa6`H3mA4D8YWy4cF zML7b2fHtBYW$^|6s=MG*?N*QECL(LKmU6a=FSVq9o#XGpmi>zeu&kh=$r{_@`4vV9 zXE19d@y&IW4=}&vlh$83`*|a`MS;7819@I476IHIn@IDJwoLM(N`X>7{*rJdd+icF gC+@x47C>)|!E&u=k literal 0 HcmV?d00001 diff --git a/tests/devkeys/android/media.x509.pem b/tests/devkeys/android/media.x509.pem new file mode 100644 index 0000000000..d3ec5bedf7 --- /dev/null +++ b/tests/devkeys/android/media.x509.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIID/TCCAuWgAwIBAgIJAMi4UJBVFupsMA0GCSqGSIb3DQEBBQUAMIGUMQswCQYD +VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4g +VmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UE +AwwHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe +Fw0xNjA2MjkyMTUxNDVaFw00MzExMTUyMTUxNDVaMIGUMQswCQYDVQQGEwJVUzET +MBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEQMA4G +A1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UEAwwHQW5kcm9p +ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBANRmZX0ek4H/HTuYfuS4WPvMBBtRRXSmysX1 +I/EIAXPVkMswIWmvw/3nBXnjmZDLrg/XG0pOQbUGSJ7RI4d27jHblX/3RQNwpmEx +r8RPLcnBosM/KYaE+9lmSGl/XfbR0b8nreWOeJVBkGMDrrC5O4f7FSrORJBs2JI8 +ZZjiRFcnstdcPHwKBnHjNCRkhC72MvHMUXmndxMBnT18RWk8KrF/de9PotqPmZOZ +fMJrebuCw0G/j0nwciqd3tpO0oANAGtWbzJ9xk53DlYRj4qu74rnQjAtyD0a73dO +U1QRnan28Duk+lKIaIUzqqrcx9uEKYp4N2figpDWKaL9QUnVSW8CAwEAAaNQME4w +HQYDVR0OBBYEFN99GqWFkNea+rCFvb4x45xBmMhIMB8GA1UdIwQYMBaAFN99GqWF +kNea+rCFvb4x45xBmMhIMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB +AFluihpLMwTAcv6l3jJZomf/a8fjEn3Ghx54B3g5n8xeDD0h2OxDOdrDc63Bg14Q +s+ANOXsqNRkZse3ze97lX6qGzXmTfuUwXdlCzgnNA970/WG8EqOgI4yQJ+7a5bu5 +jE7WHaGRuZg5r3XiX+/A8Amo8w1vPsSTCHIiMeCShC9I6L/yF+o4bg7X/mnrVvzt +QLkpYhbxZ68sLlnessfj9zigHpyUtY6HzAvYbRKN4y/2RvkMwRHBHaxZyu1g8LeY +A5c/EfktgF7ORsBrxZeKejOu+8nFzlf8TQAr0zVCHaaP1FLtA2qeLDQsGeYaKBTE +1Ol/AEBSc6LFhA3Inj6xHoI= +-----END CERTIFICATE----- diff --git a/tests/devkeys/android/platform.pk8 b/tests/devkeys/android/platform.pk8 new file mode 100644 index 0000000000000000000000000000000000000000..7b615ecba525f72f550fd6cfce02dbece2bf912f GIT binary patch literal 1217 zcmV;y1U~yPf&{$+0RS)!1_>&LNQUrr!ay9qXGc{0)hbn0J6Eje{e&v zaRuQ!l%r?g8Q!go*>D^(0>==Zzmi)%FqnlJG`4$pU~s21T5HY^+8BcDUEOSu?wUK` z23rjykP9J3Tn45jo&mg2m~*83H41C%xL~YROd2s{vCvp{f7&)ktB^ghoCU{Ig{>`; zY^gZaZn>RLcsWG2B!^FS|2#7)Y$2h#>B~{0!a0nApdacBsu9=bh@QOs>qR*sSMF^n z_=hkKuPY`KDU!m)^!!+y$M;O{n~VGMfa6xTqMw>LtZbB zVsE5*4Inu;CTwgIhw)C~YMj!+yYi|4EFTW9<*r3L*(%CbaCYoitb} zCU$*AA&_X{-rw@zZsOhvZ3W){q4$Iva6?^Xr9rERNN{``1)#u5AFZ+w)XX#nv68KW%Vn}zW zqu$S`KwR^F&@Ze&_cIBtAM=)L*j!7U`y1&pHq`*qX=c%5s6u;w$0t`RT^c%hJY%)= z3!H3uWEco(*l>+r0A8j}9;1nd*z!>KYd{+5?u4_dM5RS~xf^zE2XgZcClJ8`fq?+* zUV!44O7&?A2C6M#>$lP1T=4&q6s(mFLD&n3I!iZoVC6&}Wvh*hR7TJXj1^pyE-UCj z6Hlq|)1aaZS@R5Y^BJ9pvPf689E^m2 zdSY9FVfW3?bK-GzZNXx>qoNml!qav`Ow2 zcTxJpg0}H)o>WE+? z%AkD~(uSzgRXSidk?6Y6D37+>Uh5OG$Njf`N!&TTO4(BZ#QpYHFl?STT%JOSSK?M| zAp(JbV1LXk5vo04h((dlch^<*( fANH=z1k;SKG08Hcq)^d9A75Tt5Re0oezKP&R24Ti literal 0 HcmV?d00001 diff --git a/tests/devkeys/android/platform.x509.pem b/tests/devkeys/android/platform.x509.pem new file mode 100644 index 0000000000..8b422ac760 --- /dev/null +++ b/tests/devkeys/android/platform.x509.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIID/TCCAuWgAwIBAgIJAKsh6Cqx4cjwMA0GCSqGSIb3DQEBBQUAMIGUMQswCQYD +VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4g +VmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UE +AwwHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe +Fw0xNjA2MjkyMTUxNDBaFw00MzExMTUyMTUxNDBaMIGUMQswCQYDVQQGEwJVUzET +MBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEQMA4G +A1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UEAwwHQW5kcm9p +ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBALK5wH9wQ7BxBeE7lKNn3xnerYzZcBwyAscQ +nr+SWz4wmIUaNLZ7d2BwpzNaa84P2hiC7F3dbJDumjvgBlsNI5ALIUZcBqYkngG8 +UJhzpPw1CmvruGCsVUwaMWSx0Fh2f9o2SKuQPbGcBcdTha0tkmypONVuuZ1PeDlE +tiSHT3b/PDMqbCGhuunLUaPCOYyBoB/qC6oR1+aInrz860U5IVfubSj4hzAOrysm +EymSwsXkbMOG1gmZZiVjDn1pyIRC7Dy2u70JWaL92nRtRfeVaFwk0KRDXi+PYm+k +eQ0gOTcmbGwTh/FO4Wqc0sG78qoALB8Or+WuRTvZKsp6NRF1Kw8CAwEAAaNQME4w +HQYDVR0OBBYEFKYVE2bBhv9LZ136ipflr1O3RlVWMB8GA1UdIwQYMBaAFKYVE2bB +hv9LZ136ipflr1O3RlVWMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB +AAK7tIOyglsBLWHhl/Kc1ZjHUTvhrI3n+pz4aCtViynaHJs5O5qW3hYCLwetxBJf +92SoRNIUc1va7vLkc5fk7k5FpW+kjz1XtLasGE9KsqN6Y2I5Y88w6f6ZS5Yj/DZp +jvVAJkyCu4Un/6Qygw8DzbRGoKqlUR4x7lrqM8ZU4LkeTkxZnwX95ygth3YyXtG/ +ozbJSCx0iiJQX/uH/bXtngp5LbhFcmQj/rUxvLchRQxGPONvvpiB+6mqkN2hDLMN +KFBc2z9vu5s477bz9sz1+uPZVOnwc8u3q6VF0dowFReXtzKViciYst/zktcGzz04 +z3jF4S6UhxYQ93pmqvI0pKI= +-----END CERTIFICATE----- diff --git a/tests/devkeys/android/releasekey.pk8 b/tests/devkeys/android/releasekey.pk8 new file mode 100644 index 0000000000000000000000000000000000000000..502b5d98b0a2d4dbb46b5da4370818ab67456511 GIT binary patch literal 1219 zcmV;!1U&mNf&{+;0RS)!1_>&LNQUrsW5^Br2+u}0)hbn0Jcwvn#)1P z2_o3z@TyVszd;U~5)pj4KG>v9RaIQy3ZP0?TGUU$goqnmbd-iZQ}u3u;j`1uX5Cvt z=4ohb0`FutGw{B(Uh*ROmMmI22?onSg#fGn^Y*!gh$RsvS6m9Fl5ncTzknAtLTNft zkPip|Y~oRW@ohw>eQ!dW5wb5`?nJjdTBxofHxMQ&l@I&NY}cmgSon!pE!L;_TdcZq zu2e<%MDG_*tA8LXRpLkQB8fg(z!j{NxUVe&S_N?s9N>_7>A2ijva|kFLePTzq7JsS=0L=p^H?Rr>@P_4GKx{X4m;qFu+U8^P)N&49EEG0XEe9Pug}y zXVwxF3>EFk9F}&IF{R+*wt0p<#4hQtw#{3?jco0+E87xa-wFQu#5|qy>KRzO-EyHu z7LIxW()E^t`=t!CTp0-y^&UuJ+mhg$1)Q{sA?^IyQPanWwyZ<@fAUwO+jWx~Uo@<` zH}Ts^bB9*T@mql|Xn3T{Xyig?Kv25w&(zZ07xg!fxs3ced$h6R0e6EXvI}F{1-^V3 zj-8v>xoY?v#LP#lhrPGLVs_bj%`&B_?0lS-{M4gzh;|-#$!))^nlluh%r0 zh_c|t19s8s$kWf31&rBV+{_@@HCE}$q(gpfRNaMzTo3ie}g}3ZM)wY zHf|j##7w>0#@e4Z81#(vN)i$^r=39pfq?+V^T{;FqV&I?`ON{Y+y)X-nuTuN9EoIv zw8VV1)VjPvu>e^Aeq^QQ+~c22|TYS6${4<3*@`9<$)lUk}pdF zfq-k@yIxG{y_3X*7h{AOtn)Ezy4y~NtJc3w>?zTR%$5}zu<(>0nDeiX6HsXq8=}7^ z4)*Lk8%^UY5u?0Zw~{#lx?pDz56X7JD1v|v1AmIuK)l+so2zpeBVreyKV{Bg8^F0O z5B#daZRU8*v>=9sE*X;}rKx||LGhx8sA*L){0qV!ft0}Qn281 zFma47V&~N@N9)TLcT0+BBqrU&LNQUrsW5^Br2+u}0)hbn0J{1nT)=0s zS&RElP{LD^A5iR@ie9RWso-c)1Ox>RpaxjGUa#|07X!)v z_%T6Z?wBh%L=1f?LG*L9iYpAUkC=yg?@axt_UjE91kl=8$iPXXiZ9k*;$!hIpi}04 zds)UHM!)b3Y~Mk0Q57vcxopyqZ}@6st2RbxQ-iKF^4t&eN!06T{;d>Q zBwf2JH&FqOk@q{8^COuEpV$$6j<<>B-+fb&XTcTVIT^q+&tOg;vL+PAcf9AbNev#2 zdWD1xJmMNwvS06WEFcqjav&O}^512!VL{nA{823)u-k+4xW86sxLcC%6d$8>L_8@>uH;_&E%9ibf zq^|R*6@&i*6bcmL!4e4iSvT1}#=!!CfdIi=w_M^i$HWH(w4AHJZB=x{X>YO_q@Xew zix|r~LW8vj`gU!-p0S{>2rxX`Vssb&uKPbgPeLZZrJdH2XIVT(jeo}ra{ zs~_QI*4EBxKROk4(}BLvX*gT3(~NnnPhLhFCMoqYxrc9G-^sGJ|H>!!sm>Civ-JXj zfdHDB*axqm%FgQIYLLv=j8Ebt1whN~8)Klp%?-gPeTuBIGv5#?pvlnIet6~@Ebass zc=tH~$7d()czv~!6!Kc!xehsWSzL2jIIqxH4I%UH7n4hvpPK`e9${qMyq3GkJ|J(g z>%^-5W@Dvf+FKuvO1Y^eobw2bVc-#4wSfYGfdHhQebGx91W3fayTyWsq0N>`y@dsM z#cD|$5*PZlPx*I$B4PgyB9th7bHE$dLoJRGgjdY*+p%)ouok60?e|a}9