From fabe08352c7bbe8bc778443e67aa35aa26301fcc Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Wed, 17 Jan 2018 15:52:28 -0800 Subject: [PATCH] releasetools: Add PayloadSigner class. Create a wrapper class that handles the payload signing, which unifies the paths with and without external signer. Also add tests for the newly added class. The test keys under testdata/ are created with the script in development/tools/make_key. testdata/testkey_with_passwd.pk8 uses password "foo". Test: python -m unittest test_ota_from_target_files Test: Get identical A/B OTA packages w/ and w/o the CL. Change-Id: Ic770aec726498a3babb88ec509985e7f1210fb18 --- tools/releasetools/ota_from_target_files.py | 96 ++++++++++-------- .../test_ota_from_target_files.py | 90 +++++++++++++++- tools/releasetools/testdata/payload_signer.sh | 4 + tools/releasetools/testdata/sigfile.bin | 1 + .../releasetools/testdata/signed-sigfile.bin | 2 + tools/releasetools/testdata/testkey.pk8 | Bin 0 -> 1216 bytes tools/releasetools/testdata/testkey.x509.pem | 24 +++++ .../testdata/testkey_with_passwd.pk8 | Bin 0 -> 1329 bytes .../testdata/testkey_with_passwd.x509.pem | 24 +++++ 9 files changed, 198 insertions(+), 43 deletions(-) create mode 100755 tools/releasetools/testdata/payload_signer.sh create mode 100644 tools/releasetools/testdata/sigfile.bin create mode 100644 tools/releasetools/testdata/signed-sigfile.bin create mode 100644 tools/releasetools/testdata/testkey.pk8 create mode 100644 tools/releasetools/testdata/testkey.x509.pem create mode 100644 tools/releasetools/testdata/testkey_with_passwd.pk8 create mode 100644 tools/releasetools/testdata/testkey_with_passwd.x509.pem diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py index 88cb74110..95b73033c 100755 --- a/tools/releasetools/ota_from_target_files.py +++ b/tools/releasetools/ota_from_target_files.py @@ -310,6 +310,56 @@ class BuildInfo(object): script.AssertOemProperty(prop, values, oem_no_mount) +class PayloadSigner(object): + """A class that wraps the payload signing works. + + When generating a Payload, hashes of the payload and metadata files will be + signed with the device key, either by calling an external payload signer or + by calling openssl with the package key. This class provides a unified + interface, so that callers can just call PayloadSigner.Sign(). + + If an external payload signer has been specified (OPTIONS.payload_signer), it + calls the signer with the provided args (OPTIONS.payload_signer_args). Note + that the signing key should be provided as part of the payload_signer_args. + Otherwise without an external signer, it uses the package key + (OPTIONS.package_key) and calls openssl for the signing works. + """ + + def __init__(self): + if OPTIONS.payload_signer is None: + # Prepare the payload signing key. + private_key = OPTIONS.package_key + OPTIONS.private_key_suffix + pw = OPTIONS.key_passwords[OPTIONS.package_key] + + cmd = ["openssl", "pkcs8", "-in", private_key, "-inform", "DER"] + cmd.extend(["-passin", "pass:" + pw] if pw else ["-nocrypt"]) + signing_key = common.MakeTempFile(prefix="key-", suffix=".key") + cmd.extend(["-out", signing_key]) + + get_signing_key = common.Run(cmd, verbose=False, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + stdoutdata, _ = get_signing_key.communicate() + assert get_signing_key.returncode == 0, \ + "Failed to get signing key: {}".format(stdoutdata) + + self.signer = "openssl" + self.signer_args = ["pkeyutl", "-sign", "-inkey", signing_key, + "-pkeyopt", "digest:sha256"] + else: + self.signer = OPTIONS.payload_signer + self.signer_args = OPTIONS.payload_signer_args + + def Sign(self, in_file): + """Signs the given input file. Returns the output filename.""" + out_file = common.MakeTempFile(prefix="signed-", suffix=".bin") + cmd = [self.signer] + self.signer_args + ['-in', in_file, '-out', out_file] + signing = common.Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + stdoutdata, _ = signing.communicate() + assert signing.returncode == 0, \ + "Failed to sign the input file: {}".format(stdoutdata) + return out_file + + def SignOutput(temp_zip_name, output_zip_name): pw = OPTIONS.key_passwords[OPTIONS.package_key] @@ -1076,20 +1126,8 @@ def WriteABOTAPackageWithBrilloScript(target_file, output_file, # The place where the output from the subprocess should go. log_file = sys.stdout if OPTIONS.verbose else subprocess.PIPE - # A/B updater expects a signing key in RSA format. Gets the key ready for - # later use in step 3, unless a payload_signer has been specified. - if OPTIONS.payload_signer is None: - cmd = ["openssl", "pkcs8", - "-in", OPTIONS.package_key + OPTIONS.private_key_suffix, - "-inform", "DER"] - pw = OPTIONS.key_passwords[OPTIONS.package_key] - cmd.extend(["-passin", "pass:" + pw] if pw else ["-nocrypt"]) - rsa_key = common.MakeTempFile(prefix="key-", suffix=".key") - cmd.extend(["-out", rsa_key]) - p1 = common.Run(cmd, verbose=False, stdout=log_file, - stderr=subprocess.STDOUT) - p1.communicate() - assert p1.returncode == 0, "openssl pkcs8 failed" + # Get the PayloadSigner to be used in step 3. + payload_signer = PayloadSigner() # Stage the output zip package for package signing. temp_zip_file = tempfile.NamedTemporaryFile() @@ -1130,37 +1168,11 @@ def WriteABOTAPackageWithBrilloScript(target_file, output_file, assert p1.returncode == 0, "brillo_update_payload hash failed" # 3. Sign the hashes and insert them back into the payload file. - signed_payload_sig_file = common.MakeTempFile(prefix="signed-sig-", - suffix=".bin") - signed_metadata_sig_file = common.MakeTempFile(prefix="signed-sig-", - suffix=".bin") # 3a. Sign the payload hash. - if OPTIONS.payload_signer is not None: - cmd = [OPTIONS.payload_signer] - cmd.extend(OPTIONS.payload_signer_args) - else: - cmd = ["openssl", "pkeyutl", "-sign", - "-inkey", rsa_key, - "-pkeyopt", "digest:sha256"] - cmd.extend(["-in", payload_sig_file, - "-out", signed_payload_sig_file]) - p1 = common.Run(cmd, stdout=log_file, stderr=subprocess.STDOUT) - p1.communicate() - assert p1.returncode == 0, "openssl sign payload failed" + signed_payload_sig_file = payload_signer.Sign(payload_sig_file) # 3b. Sign the metadata hash. - if OPTIONS.payload_signer is not None: - cmd = [OPTIONS.payload_signer] - cmd.extend(OPTIONS.payload_signer_args) - else: - cmd = ["openssl", "pkeyutl", "-sign", - "-inkey", rsa_key, - "-pkeyopt", "digest:sha256"] - cmd.extend(["-in", metadata_sig_file, - "-out", signed_metadata_sig_file]) - p1 = common.Run(cmd, stdout=log_file, stderr=subprocess.STDOUT) - p1.communicate() - assert p1.returncode == 0, "openssl sign metadata failed" + signed_metadata_sig_file = payload_signer.Sign(metadata_sig_file) # 3c. Insert the signatures back into the payload file. signed_payload_file = common.MakeTempFile(prefix="signed-payload-", diff --git a/tools/releasetools/test_ota_from_target_files.py b/tools/releasetools/test_ota_from_target_files.py index 5f6c5d065..fa6655b4f 100644 --- a/tools/releasetools/test_ota_from_target_files.py +++ b/tools/releasetools/test_ota_from_target_files.py @@ -15,11 +15,20 @@ # import copy +import os.path import unittest import common from ota_from_target_files import ( - _LoadOemDicts, BuildInfo, GetPackageMetadata, WriteFingerprintAssertion) + _LoadOemDicts, BuildInfo, GetPackageMetadata, PayloadSigner, + WriteFingerprintAssertion) + + +def get_testdata_dir(): + """Returns the testdata dir, in relative to the script dir.""" + # The script dir is the one we want, which could be different from pwd. + current_dir = os.path.dirname(os.path.realpath(__file__)) + return os.path.join(current_dir, 'testdata') class MockScriptWriter(object): @@ -476,3 +485,82 @@ class OtaFromTargetFilesTest(unittest.TestCase): 'pre-build-incremental' : 'build-version-incremental-source', }, metadata) + + +class PayloadSignerTest(unittest.TestCase): + + SIGFILE = 'sigfile.bin' + SIGNED_SIGFILE = 'signed-sigfile.bin' + + def setUp(self): + self.testdata_dir = get_testdata_dir() + self.assertTrue(os.path.exists(self.testdata_dir)) + + common.OPTIONS.payload_signer = None + common.OPTIONS.payload_signer_args = [] + common.OPTIONS.package_key = os.path.join(self.testdata_dir, 'testkey') + common.OPTIONS.key_passwords = { + common.OPTIONS.package_key : None, + } + + def tearDown(self): + common.Cleanup() + + def _assertFilesEqual(self, file1, file2): + with open(file1, 'rb') as fp1, open(file2, 'rb') as fp2: + self.assertEqual(fp1.read(), fp2.read()) + + def test_init(self): + payload_signer = PayloadSigner() + self.assertEqual('openssl', payload_signer.signer) + + def test_init_withPassword(self): + common.OPTIONS.package_key = os.path.join( + self.testdata_dir, 'testkey_with_passwd') + common.OPTIONS.key_passwords = { + common.OPTIONS.package_key : 'foo', + } + payload_signer = PayloadSigner() + self.assertEqual('openssl', payload_signer.signer) + + def test_init_withExternalSigner(self): + common.OPTIONS.payload_signer = 'abc' + common.OPTIONS.payload_signer_args = ['arg1', 'arg2'] + payload_signer = PayloadSigner() + self.assertEqual('abc', payload_signer.signer) + self.assertEqual(['arg1', 'arg2'], payload_signer.signer_args) + + def test_Sign(self): + payload_signer = PayloadSigner() + input_file = os.path.join(self.testdata_dir, self.SIGFILE) + signed_file = payload_signer.Sign(input_file) + + verify_file = os.path.join(self.testdata_dir, self.SIGNED_SIGFILE) + self._assertFilesEqual(verify_file, signed_file) + + def test_Sign_withExternalSigner_openssl(self): + """Uses openssl as the external payload signer.""" + common.OPTIONS.payload_signer = 'openssl' + common.OPTIONS.payload_signer_args = [ + 'pkeyutl', '-sign', '-keyform', 'DER', '-inkey', + os.path.join(self.testdata_dir, 'testkey.pk8'), + '-pkeyopt', 'digest:sha256'] + payload_signer = PayloadSigner() + input_file = os.path.join(self.testdata_dir, self.SIGFILE) + signed_file = payload_signer.Sign(input_file) + + verify_file = os.path.join(self.testdata_dir, self.SIGNED_SIGFILE) + self._assertFilesEqual(verify_file, signed_file) + + def test_Sign_withExternalSigner_script(self): + """Uses testdata/payload_signer.sh as the external payload signer.""" + common.OPTIONS.payload_signer = os.path.join( + self.testdata_dir, 'payload_signer.sh') + common.OPTIONS.payload_signer_args = [ + os.path.join(self.testdata_dir, 'testkey.pk8')] + payload_signer = PayloadSigner() + input_file = os.path.join(self.testdata_dir, self.SIGFILE) + signed_file = payload_signer.Sign(input_file) + + verify_file = os.path.join(self.testdata_dir, self.SIGNED_SIGFILE) + self._assertFilesEqual(verify_file, signed_file) diff --git a/tools/releasetools/testdata/payload_signer.sh b/tools/releasetools/testdata/payload_signer.sh new file mode 100755 index 000000000..a44ef340e --- /dev/null +++ b/tools/releasetools/testdata/payload_signer.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +# The script will be called with 'payload_signer.sh -in -out '. +openssl pkeyutl -sign -keyform DER -inkey $1 -pkeyopt digest:sha256 -in $3 -out $5 diff --git a/tools/releasetools/testdata/sigfile.bin b/tools/releasetools/testdata/sigfile.bin new file mode 100644 index 000000000..8682216de --- /dev/null +++ b/tools/releasetools/testdata/sigfile.bin @@ -0,0 +1 @@ +QܢGp'[4KLc \ No newline at end of file diff --git a/tools/releasetools/testdata/signed-sigfile.bin b/tools/releasetools/testdata/signed-sigfile.bin new file mode 100644 index 000000000..86d2f9ec2 --- /dev/null +++ b/tools/releasetools/testdata/signed-sigfile.bin @@ -0,0 +1,2 @@ +R&Es%?|&̀zbSA[tqWKґls~Fc `# +T{۽Fx16̋=QV^T߰xX/#I'tcLpovzђR:W9(26̬bBP16n߱QCgh;rO}%Ľo6d2Y` ۼ_ROrCa,I"n(`nbaiŨS)kO[`6ce \ No newline at end of file diff --git a/tools/releasetools/testdata/testkey.pk8 b/tools/releasetools/testdata/testkey.pk8 new file mode 100644 index 0000000000000000000000000000000000000000..99be2910b9053a70d07df3820c1f4e398e079d94 GIT binary patch literal 1216 zcmV;x1V8&Qf&{z*0RS)!1_>&LNQUrrZ9p8q5=T`0)hbn0KPl&J7Vx! z36M7`yU*-O(}$qa-M}x5wl*3-0PNMAT!I3zToy9Z834n+M%h23b=dB{cLHcUx$T~B zK>1nEAW=~s4F^$Vn;g%Olfq!XZ}tIM08&0l*1v7xf3{eS9!D}y*Op_SuPH#GrgJAE zuPq>M`IxZ~P^A5vYzY z41%79Zc-L4qqj$t@A&Tj$M-m7BrbS34PB5u{1EsK~cj_6wcUOEoGP&uVWd6RCWyUs-Wh&qPt_Ktn)ig^-V6? z8KyaswS#R09^c{Oo^G(;yA(M~x=$i+IiRn!0!tR0==(T6q`qJbmtem~v_K2>O)E1h zlT?>bt>zzwqwCidbqB8dP?v|w2MU0B{9Jc*c+Yx&+$nZtf)UTsJ5hBRWsC_Ew!<_$ z_h?pijYlxx+|FV_LdD8; %j-;opG)r1=z@)-39c{!caa zB!M=<;2pApAHboqPvySWpOWjacQ+Z1g3MiF@jfwK1aY0BmJ`mBI%ZImNwNxhatdd~ zR_2it%6tg*zkvebrxc|@(V5Y<3yT7QfdI*@ltXtWi2RE#vWP!B0*6#!ROl&@Ynx1i zABG}aVS1=;4ynPCy&={@_R*NU(3ghp@|LgXaDJ%-YPa1@@1wg3NVyHn66MPQy9zeu zl-72RsN&}KxY>alQSO6FRDM8iLX2pf^~}zgh{<{2Q3GGLRb$5bt5UOJboa1my@vvU zfEx{KOQUKEPcA#Ngehe;7${8@w9n}){ZxrUbK!5-lh#zXAE3{fYg71k`@>acDZ#Bx zNH%pwC#$I4PPS&*gUuP5)zmRtBP@VYdb`9qX9u@K>w&;G^*VvFYI&pVBK9~2p^1k2 zE380qVb?P5WsddMWNKcMv#M(&gz`8_Cp1zd^MJvVqVv5 zlZs-539%#R8R&)|j=F!F14FaC!lTa7!od~!d0NZ3uiDy<30E_Eu*HwsbuASMN`jho z_-4G-AF?_Xz41x;uU;c}+SOB|6Vv`4M6it=-RDHJYvoImf~1Y_8F0&9j+q0k(YF{` z0)c>69;YJ0QD^3J4;2@@zm2--fMQbI!ayc*4$~famI5ZpMSeQ!J#$oXPv4gnuxcqI z7Q)FA*?Va{$EDc0BeeX;CL~d6fC3)vz(x}};kv*3L~HZruo&4~@b=C?sqdi>n*HEz e`v>4(=L4Q#Pw|o3Df(S6_^fbmj(%VlqXo)iLQTv7 literal 0 HcmV?d00001 diff --git a/tools/releasetools/testdata/testkey.x509.pem b/tools/releasetools/testdata/testkey.x509.pem new file mode 100644 index 000000000..65c8085b8 --- /dev/null +++ b/tools/releasetools/testdata/testkey.x509.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIJAN/FvjYzGNOKMA0GCSqGSIb3DQEBCwUAMIGUMQswCQYD +VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4g +VmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UE +AwwHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe +Fw0xODAxMTgwMDM0NTFaFw00NTA2MDUwMDM0NTFaMIGUMQswCQYDVQQGEwJVUzET +MBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEQMA4G +A1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UEAwwHQW5kcm9p +ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAL478jti8FoJkDcqu8/sStOHoNLdwC+MtjYa +QADs1ZxcggKxXBYy0xkAw75G2T+jddjuvncCaDy57Z5vQPlZzyBRUR4NB1FkmxzP +kJPCYL9v9gFZAFI+Sda/beF/tliNHkcyT9eWY5+vKUChpnMnIq8tIG75mL1y9mVJ +k5ueg5hHwlAkSGNiBifwnDJxXiLVVNC8SrFeTJbeQTtFb/wleBGoji8Mgp6GblIW +LaO3R5Tv+O7/x/c4ZCQueDgNXZA9/BD4DuRp34RhUjV0EZiQ016xYHejvkDuMlDV +/JWD9dDM4plKSLWWtObevDQA6sGJd0+51s77gva+CKmQ8j39tU0CAwEAAaNTMFEw +HQYDVR0OBBYEFNJPJZDpq6tc/19Z2kxPA2bj9D6UMB8GA1UdIwQYMBaAFNJPJZDp +q6tc/19Z2kxPA2bj9D6UMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQAD +ggEBABSUG9qrwV3WcClDJwqkNLN4yeVVYzkRMGA8/XqOiYrW4zh0mKDLfr6OeU1C +AKwZBLhhql59Po25r4gcwPiTN2DkoCfb3T59XG8J54PAgTQjIAZ3J+mGZplnmuD3 +wj+UGUpPe0qTr33ZPoJfwxVo4RVnOt/UCsIGXch0HS/BIdpechqP0w4rOHUbq6EA +8UEi5irKSDOU9b/5rD/tX2f4nGwJlKQEHWrsj9LLKlaL7fX36ghoSxN/pBJOhedg +/VjT6xbaEwfyhC6Zj9av5Xl7UdpYt+rBMroAGenz0OSxKhIphdcx4ZMhvfkBoYG9 +Crupdqe+kUsfg2RlPb5grQ3klMo= +-----END CERTIFICATE----- diff --git a/tools/releasetools/testdata/testkey_with_passwd.pk8 b/tools/releasetools/testdata/testkey_with_passwd.pk8 new file mode 100644 index 0000000000000000000000000000000000000000..3d567def31f8a10967e80a1d9311fceaa236219e GIT binary patch literal 1329 zcmV-11&LNQU&90UlXoSkx0YQeI$mN$hG$f}Xyz{2_V)_B$idM(m`s^&1gH>sd zJ6Vt1g`e{jrs)_2tqC<8WX}fz$aO+}1EUuJ{)~@YN)9@1ag?9aQWh<4p@vY-ph=wA zFSQyELIrpaVeTxgw;2MkyjV^n44$bh&!n0UGc_H>diLzBPtr?7PB2^y?3rEC_9FE^ z4a`7+L#PU;*X);}OiGeq5gB=A@i+<(@@MVzchsJpDNR6Mus8Y7Tq`e`PSjmvXEm^+ zSFE{a7O7&dAR*7zKg6&fH-g>Zui0gZB4mLexc-;)mKScOX8SA%8danv@Bq<-dokD| z`|wkqvQx89a7I~NW)wS(DW@w|95>%TH69RKPWIx61phFHRSmk2qO?b&drrA?MA#P= zftiZ@9{xpxH3tG!eR+3&w5~!9{tL{+oIOo$?~usk_(LalIQ|c2V64HgYbi6hbaSQ~1nk~N zewN$ZL+|7l3Zq9&DMjfdsv1998!NTi7i>#DUMn;rN_6)FYUu`ud0An$hxSHU4&6T` zjwm!Sf=u@-rMRM;Ix_m{1-2K2a{NFy?>R(?pR>`xSN2bPA8Jt+Kpa7;uXzx}afq}F zb}CB7H=wFXCa+~sd6+z}c2ClDD+MH_?xDmT@~)rI?hJ9gkFBWtF8wl{B?nHv!`hl*A$?u9k#zO;6Wd$FeiK+Y#O)r9Az|--y#ZSM6 zACIfe>l)+WQfRf?vbUiEc`Rbe{lO-fLjFeBM2ZcK-g!-U!y#n4r!&6zv72My!Z54! zed&KVFXK7fPbn!h{qZTL3@5c{ zt+6${q~}Io1|8!yFblm0&nsB}X8g}|h0@ql*#Fi7{)mE(((cq_J~rt6j7M9WJAhJg zf`T~E0H5|)0#Z&NW_)|hDT9)|QDaHW0W)wKQ>=|e5(}x=aMN}vbeM3IA(N1apaGR& zewz}eHiNb@`KQ#zQm7rxB@&iFqwYSVI|n4&h1k#*;h|vmK9gBXtLbh?R(ggk5jl6K z@cqTM4p1}bH9a7^W{A^!&M*avnyutrd(A}_G>FS$_e?J^Pzv~j nOz^!y@30kK{e!>~=VS1ya;q(VxM}!;f6aeM#CF3!F??4_cGPa> literal 0 HcmV?d00001 diff --git a/tools/releasetools/testdata/testkey_with_passwd.x509.pem b/tools/releasetools/testdata/testkey_with_passwd.x509.pem new file mode 100644 index 000000000..449396e88 --- /dev/null +++ b/tools/releasetools/testdata/testkey_with_passwd.x509.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIJANefUd3Piu0yMA0GCSqGSIb3DQEBCwUAMIGUMQswCQYD +VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4g +VmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UE +AwwHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe +Fw0xODAxMTgwMDI3NDRaFw00NTA2MDUwMDI3NDRaMIGUMQswCQYDVQQGEwJVUzET +MBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEQMA4G +A1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UEAwwHQW5kcm9p +ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBALBoA4c+qCQKapQAVGclbousC5J/L0TNZJEd +KSW2nzXUHIwgTQ3r82227xkIvjnqXMCsc0q3/N2gGKR4sHqA30JO9Dyfgsx1ISaR +GXe5cG048m5U5snplQgvPovtah9ZyvwNPzWPYC3uceJaDxKQKwVdsV+mOWM6WmpQ +bdLO37jxfytyAbzaz3sG5HA3FSB8rX/xDM6If18NsxSHpcjaOjZXC4Fg6wlp0klY +5/qhFEdmieu2zQVelXjoJfKSku8tPa7kZeDU/F3uLUq/U/xvFk7NVsRV+QvYOdQK +1QECc/3yv1TKNAN3huWTgzCX6bMHmi09Npw3MQaGY0oS34cH9x0CAwEAAaNTMFEw +HQYDVR0OBBYEFNsJZ0n9Opeea0rVAzL+1jwkDKzPMB8GA1UdIwQYMBaAFNsJZ0n9 +Opeea0rVAzL+1jwkDKzPMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQAD +ggEBAJ/bzIzA+NrYwPEv56XKf6Vuj81+M1rTHAsH9PqbOvJT7iM7aU7wAl6vmXAo +DQtvKoOBMdIXprapwe0quHCQm7PGxg+RRegr+dcTSVJFv1plnODOBOEAVlEfFwuW +Cz0USF2jrNq+4ciH5zPL1a31ONb1rMkxJXQ/tAi0x8m6tZz+jsbE0wO6qB80UmkA +4WY2Tu/gnAvFpD8plkiU0EKwedBHAcaFFZkQp23MKsVZ3UBqsqzzfXDYV1Oa6rIy +XIZpI2Gx75pvAb57T2ap/yl0DBEAu7Nmpll0GCsgeJVdy7tS4LNj96Quya3CHWQw +WNTVuan0KZqwDIm4Xn1oHUFQ9vc= +-----END CERTIFICATE-----