From 7b0799f4e1b52b359862e97ea2b89befafe92713 Mon Sep 17 00:00:00 2001 From: Benson Kuang <3453547+bkuang@users.noreply.github.com> Date: Fri, 23 Apr 2021 13:24:43 -0400 Subject: [PATCH] feat: add script to verify attestations with certificate chains (#99) Co-authored-by: Benson Kuang --- .../attestations/verify_attestation_chains.py | 296 ++++++++++++++++ .../verify_attestation_chains_test.py | 321 ++++++++++++++++++ 2 files changed, 617 insertions(+) create mode 100644 samples/attestations/verify_attestation_chains.py create mode 100644 samples/attestations/verify_attestation_chains_test.py diff --git a/samples/attestations/verify_attestation_chains.py b/samples/attestations/verify_attestation_chains.py new file mode 100644 index 00000000..30a5b922 --- /dev/null +++ b/samples/attestations/verify_attestation_chains.py @@ -0,0 +1,296 @@ +#!/usr/bin/env python + +# Copyright 2021 Google, LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""This application verifies HSM certificate chains. + +For more information, visit https://cloud.google.com/kms/docs/attest-key. +""" + +# [START kms_verify_chains] +import argparse +import gzip +import io +import zipfile + +from cryptography import exceptions +from cryptography import x509 +from cryptography.hazmat import backends +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import padding +import pem +import requests + +ATTESTATION_SIGNATURE_LEN = 256 + +MANUFACTURER_CERT_URL = 'https://www.marvell.com/content/dam/marvell/en/public-collateral/security-solutions/liquid_security_certificate.zip' + +# +MANUFACTURER_CERT_SUBJECT_BYTES = ( + b'0\x81\x911\x0b0\t\x06\x03U\x04\x06\x13\x02US1\x130\x11\x06\x03U\x04\x08' + b'\x0c\nCalifornia1\x110\x0f\x06\x03U\x04\x07\x0c\x08San Jose1\x150\x13\x06' + b'\x03U\x04\n\x0c\x0cCavium, Inc.1\x170\x15\x06\x03U\x04\x0b\x0c\x0e' + b'LiquidSecurity1*0(\x06\x03U\x04\x03\x0c!localca.liquidsecurity.cavium.com' +) + +# The owner root cert can be obtained from +# 'https://www.gstatic.com/cloudhsm/roots/global_1498867200.pem' +OWNER_ROOT_CERT_PEM = """Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3 (0x3) + Signature Algorithm: sha256WithRSAEncryption + Issuer: C = US, ST = CA, L = Mountain View, O = Google Inc, CN = Hawksbill Root v1 prod + Validity + Not Before: Jul 1 00:00:00 2017 GMT + Not After : Jan 1 00:00:00 2030 GMT + Subject: C = US, ST = CA, L = Mountain View, O = Google Inc, CN = Hawksbill Root v1 prod + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (2048 bit) + Modulus: + 00:ac:2e:a8:62:89:21:a0:70:92:df:b0:8e:c3:93: + 4d:26:38:db:a5:a2:5f:6b:1e:6d:a8:6c:2c:83:d6: + 5b:9a:f8:02:a0:f8:b0:16:fb:5c:da:b9:9b:b9:8b: + 4d:bc:15:26:e0:0e:4f:2f:b5:20:43:1c:31:7e:5e: + c1:67:a9:36:c8:19:5e:c2:b5:a8:b6:96:76:90:7b: + 55:15:4d:53:16:10:f0:62:d5:d8:98:19:c7:9e:0e: + b2:69:26:a3:f3:d9:a5:d3:70:88:21:ac:62:12:7b: + 2a:be:20:2e:33:db:9b:90:a7:b1:bf:0f:c0:11:7a: + c2:98:a9:8c:4d:36:a7:1f:66:53:08:93:4b:3a:12: + 1e:1a:3f:2b:c2:5d:8b:4b:97:d4:17:0f:41:83:27: + a9:f3:e0:d9:82:f8:5c:37:d4:1e:5d:e4:a8:3d:59: + 7c:43:64:e6:02:d7:35:39:f4:95:db:77:1c:73:78: + 2f:c4:26:8d:64:d4:01:e0:86:da:3f:27:c7:9d:bd: + 32:25:e4:d4:34:6a:13:87:2a:85:19:ce:18:43:46: + c5:41:8a:81:66:ca:65:6e:c1:a1:ce:71:74:d4:b0: + 77:b7:35:39:0d:c9:e2:c8:7e:81:69:b1:04:38:5d: + c1:fd:92:33:ba:ed:85:d3:91:d0:96:78:d6:30:fc: + 56:19 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Key Usage: critical + Digital Signature, Certificate Sign, CRL Sign + X509v3 Subject Key Identifier: + 31:E8:52:DF:E1:49:F8:12:7B:7C:6E:E7:4E:91:7A:97:75:BC:A8:AE + Signature Algorithm: sha256WithRSAEncryption + 8f:12:8e:8e:7a:fb:59:82:a8:0f:e6:be:b8:09:5d:17:c8:8e: + c1:3a:c7:a4:52:d4:0d:2e:ac:a8:5c:b1:f4:52:ee:b7:c4:25: + 9a:2a:32:fc:91:3d:ba:29:9a:ed:c8:de:1f:75:39:54:16:d1: + 72:74:e0:95:a0:e2:41:36:9c:f8:95:c2:21:10:29:12:5f:4d: + d1:b0:e1:a1:5b:c5:79:3c:d1:23:c9:c9:74:c2:42:58:fa:1b: + 35:75:77:30:7a:58:b2:07:e0:cd:ec:21:e2:51:54:59:08:21: + be:c7:05:df:6e:55:81:21:0d:d1:ad:61:81:77:27:3e:bd:39: + 81:df:bd:91:32:3d:cc:5d:eb:de:fc:a7:73:26:2f:cd:88:a7: + 70:65:f4:35:06:b3:d6:02:56:e1:ba:e6:d5:6f:b0:4d:b5:95: + cb:c6:34:a3:a7:35:79:99:bb:bf:cb:07:a0:d4:a0:de:f2:2c: + e8:9b:27:43:c6:c0:5c:ae:62:da:a3:bf:01:76:50:bb:6e:70: + 1f:56:8f:41:cb:7c:41:d1:b0:c7:62:41:b2:31:23:99:6a:47: + b8:10:c0:5c:f0:9e:b0:3e:5c:bb:d5:33:cc:38:1c:a5:dc:26: + 8b:b5:e2:76:5e:f8:92:3d:df:fc:78:2b:39:e8:a6:45:d3:9b: + f2:51:b9:fc +-----BEGIN CERTIFICATE----- +MIIDjTCCAnWgAwIBAgIBAzANBgkqhkiG9w0BAQsFADBoMQswCQYDVQQGEwJVUzEL +MAkGA1UECAwCQ0ExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxEzARBgNVBAoMCkdv +b2dsZSBJbmMxHzAdBgNVBAMMFkhhd2tzYmlsbCBSb290IHYxIHByb2QwHhcNMTcw +NzAxMDAwMDAwWhcNMzAwMTAxMDAwMDAwWjBoMQswCQYDVQQGEwJVUzELMAkGA1UE +CAwCQ0ExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxEzARBgNVBAoMCkdvb2dsZSBJ +bmMxHzAdBgNVBAMMFkhhd2tzYmlsbCBSb290IHYxIHByb2QwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCsLqhiiSGgcJLfsI7Dk00mONulol9rHm2obCyD +1lua+AKg+LAW+1zauZu5i028FSbgDk8vtSBDHDF+XsFnqTbIGV7Ctai2lnaQe1UV +TVMWEPBi1diYGceeDrJpJqPz2aXTcIghrGISeyq+IC4z25uQp7G/D8AResKYqYxN +NqcfZlMIk0s6Eh4aPyvCXYtLl9QXD0GDJ6nz4NmC+Fw31B5d5Kg9WXxDZOYC1zU5 +9JXbdxxzeC/EJo1k1AHghto/J8edvTIl5NQ0ahOHKoUZzhhDRsVBioFmymVuwaHO +cXTUsHe3NTkNyeLIfoFpsQQ4XcH9kjO67YXTkdCWeNYw/FYZAgMBAAGjQjBAMA8G +A1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBQx6FLf4Un4 +Ent8budOkXqXdbyorjANBgkqhkiG9w0BAQsFAAOCAQEAjxKOjnr7WYKoD+a+uAld +F8iOwTrHpFLUDS6sqFyx9FLut8Qlmioy/JE9uima7cjeH3U5VBbRcnTglaDiQTac ++JXCIRApEl9N0bDhoVvFeTzRI8nJdMJCWPobNXV3MHpYsgfgzewh4lFUWQghvscF +325VgSEN0a1hgXcnPr05gd+9kTI9zF3r3vyncyYvzYincGX0NQaz1gJW4brm1W+w +TbWVy8Y0o6c1eZm7v8sHoNSg3vIs6JsnQ8bAXK5i2qO/AXZQu25wH1aPQct8QdGw +x2JBsjEjmWpHuBDAXPCesD5cu9UzzDgcpdwmi7Xidl74kj3f/HgrOeimRdOb8lG5 +/A== +-----END CERTIFICATE----- +""" + + +def get_manufacturer_root_certificate(): + """Gets the manufacturer root certificate.""" + resp = requests.get(MANUFACTURER_CERT_URL) + tmp_file = io.BytesIO(resp.content) + zip_file = zipfile.ZipFile(tmp_file) + with zip_file.open('liquid_security_certificate.crt') as f: + return x509.load_pem_x509_certificate(f.read(), backends.default_backend()) + + +def get_owner_root_certificate(): + """Gets the owner root certificate.""" + return x509.load_pem_x509_certificate( + OWNER_ROOT_CERT_PEM.encode('utf-8'), backends.default_backend()) + + +def verify_certificate(signing_cert, issued_cert): + """Verifies the signing_cert issued the issued_cert. + + Args: + signing_cert: The certificate used to verify the issued certificate's + signature. + issued_cert: The issued certificate. + + Returns: + True if the signing_cert issued the issued_cert. + """ + if signing_cert.subject != issued_cert.issuer: + return False + try: + signing_cert.public_key().verify(issued_cert.signature, + issued_cert.tbs_certificate_bytes, + padding.PKCS1v15(), + issued_cert.signature_hash_algorithm) + return True + except exceptions.InvalidSignature: + return False + return False + + +def get_issued_certificate(issuer_cert, + untrusted_certs, + predicate=lambda _: True): + """Finds an issued certificates issued by an issuer certificate. + + The issued certificate is removed from the set of untrusted certificates. + + Args: + issuer_cert: The issuer certificate. + untrusted_certs: A set of untrusted certificates. + predicate: An additional condition for the issued certificate. + + Returns: + A certificate within the set of untrusted certificates that was issued + by the issuer certificate and matches the predicate. + """ + for cert in untrusted_certs: + if verify_certificate(issuer_cert, cert) and predicate(cert): + untrusted_certs.remove(cert) + return cert + return None + + +def verify_attestation(cert, attestation): + """Verifies that the certificate signed the attestation. + + Args: + cert: The certificate used to verify the attestation. + attestation: The attestation to verify. + + Returns: + True if the certificate verified the attestation. + """ + data = attestation[:-ATTESTATION_SIGNATURE_LEN] + signature = attestation[-ATTESTATION_SIGNATURE_LEN:] + try: + cert.public_key().verify(signature, data, padding.PKCS1v15(), + hashes.SHA256()) + return True + except exceptions.InvalidSignature: + return False + return False + + +def verify(certs_file, attestation_file): + """Verifies that the certificate chains are valid. + + Args: + certs_file: The certificate chains filename. + attestation_file: The attestation filename. + + Returns: + True if the certificate chains are valid. + """ + mfr_root_cert = get_manufacturer_root_certificate() + if (mfr_root_cert.subject.public_bytes(backends.default_backend()) != + MANUFACTURER_CERT_SUBJECT_BYTES): + return False + + untrusted_certs_pem = pem.parse_file(certs_file) + untrusted_certs = { + x509.load_pem_x509_certificate( + str(cert_pem).encode('utf-8'), backends.default_backend()) + for cert_pem in untrusted_certs_pem + } + + # Build the manufacturer certificate chain. + mfr_card_cert = get_issued_certificate(mfr_root_cert, untrusted_certs) + mfr_partition_cert = get_issued_certificate(mfr_card_cert, untrusted_certs) + if not mfr_card_cert or not mfr_partition_cert: + print('Invalid HSM manufacturer certificate chain.') + return False + print('Successfully built HSM manufacturer certificate chain.') + + owner_root_cert = get_owner_root_certificate() + + # Build the owner card certificate chain. + def _check_card_pub_key(cert): + cert_pub_key_bytes = cert.public_key().public_bytes( + serialization.Encoding.DER, + serialization.PublicFormat.SubjectPublicKeyInfo) + mfr_card_pub_key_bytes = mfr_card_cert.public_key().public_bytes( + serialization.Encoding.DER, + serialization.PublicFormat.SubjectPublicKeyInfo) + return cert_pub_key_bytes == mfr_card_pub_key_bytes + + owner_card_cert = get_issued_certificate( + owner_root_cert, untrusted_certs, predicate=_check_card_pub_key) + + # Build the owner partition certificate chain. + def _check_partition_pub_key(cert): + cert_pub_key_bytes = cert.public_key().public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.SubjectPublicKeyInfo) + mfr_partition_pub_key_bytes = mfr_partition_cert.public_key().public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.SubjectPublicKeyInfo) + return cert_pub_key_bytes == mfr_partition_pub_key_bytes + + owner_partition_cert = get_issued_certificate( + owner_root_cert, untrusted_certs, predicate=_check_partition_pub_key) + + if not owner_card_cert or not owner_partition_cert or untrusted_certs: + print('Invalid HSM owner certificate chain.') + return False + print('Successfully built HSM owner certificate chain.') + + with gzip.open(attestation_file, 'rb') as f: + attestation = f.read() + return (verify_attestation(mfr_partition_cert, attestation) and + verify_attestation(owner_partition_cert, attestation)) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument( + '--certificates', help='The certificate chains filename.') + parser.add_argument('--attestation', help='The attestation filename.') + + args = parser.parse_args() + + if verify(args.certificates, args.attestation): + print('The attestation has been verified.') + else: + print('The attestation could not be verified.') +# [END kms_verify_chains] diff --git a/samples/attestations/verify_attestation_chains_test.py b/samples/attestations/verify_attestation_chains_test.py new file mode 100644 index 00000000..480fe318 --- /dev/null +++ b/samples/attestations/verify_attestation_chains_test.py @@ -0,0 +1,321 @@ +# Copyright 2021 Google, LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import tempfile + +from cryptography import x509 +from cryptography.hazmat import backends +import pytest + +import verify_attestation_chains + +# The following keys and CSRs are needed to generate the test root certificates +# and certificate chains: +# 1. Test manufacturer root key pair. +# - openssl genrsa -out mfr.key +# 2. Test owner root key pair. +# - openssl genrsa -out owner.key +# 3. Test card key pair. +# - openssl genrsa -out card.key +# 4. Test partition key pair. +# - openssl genrsa -out partition.key +# 5. Test card CSR. +# - openssl req -new -key card.key -out card.csr +# 6. Test partition CSR. +# - openssl req -new -key partition.key -out partition.csr + +# The manufacturer root certificate can be generated with the manufacturer root +# key: +# - openssl req -x509 -key mfr.key -days 3650 -out mfr.pem +TEST_MANUFACTURER_ROOT = b"""-----BEGIN CERTIFICATE----- +MIIDujCCAqKgAwIBAgIJAMs+bXbmbsuPMA0GCSqGSIb3DQEBCwUAMHIxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQxKzApBgNVBAMMIlRlc3QgTWFudWZhY3R1cmVyIFJvb3Qg +Q2VydGlmaWNhdGUwHhcNMjAwNDA4MTMzNDI1WhcNMzAwNDA2MTMzNDI1WjByMQsw +CQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJu +ZXQgV2lkZ2l0cyBQdHkgTHRkMSswKQYDVQQDDCJUZXN0IE1hbnVmYWN0dXJlciBS +b290IENlcnRpZmljYXRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +242hKtNxBY2TLzyjIzFJtCf44WKha2A3KcfZ2Ul7Q/f6gAlLOK5/GvIsdB8MK/Y0 +JgJBYUfPIZ8h0gLJVLhopopc4oOTRexCNCca97klSQCTZT4+wGqf/uIEF3PoL2Bb +uLhCQgxu+pPhfweBtEuqVcA33DYNN77J0f5KLKTHpOFEh1S1Q6ee/oRapj6J0hw6 +a/7FW1R7329V5Nr9qRIzhnlpy2lBFfCV95yukAf9pUp1jCVkesugrkFY3U8np4Kn +KhO4x1jHTsTmfq/oCUzj/hiUtD8vlg//0ZL0SMFDCANM+shi/AbyWU1BCESLrKTa +kvI+atplTmLk8o6lOb+hMwIDAQABo1MwUTAdBgNVHQ4EFgQUlDYZuNfW4l4XDGiA +yvJjmAiTtR8wHwYDVR0jBBgwFoAUlDYZuNfW4l4XDGiAyvJjmAiTtR8wDwYDVR0T +AQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEADyDx4HpwOpDNnG+Mf4DiSFAk +ujYaWsQGB8hUXKuJLsLLtcPNTvwSa/umu/IhBVYop1GpTadAG5yjYULwPnM4GIt3 +POffWAptw/a0XtFRxCXtvGlb+KvwQfYN8kq3weHcSvxTkrzgeWxs0LIQrZoaBPoo +MTJ88/s/p0d9Cf//5ukrW3LjF8OD8hd6HLpEie2qKc0wb6NXAuNgZ9m62kHSjXRS +Bd7Bwm5ZX3cOSz9SSseJKxEvD3lYUIF9w7gOeuifEpq2cfdT/VoiSL4GdN9wQ84R +0lM4loNrim85zL8YJdGAMlAQ5gbo9/Y8khSmoUOHHoV6P4UybOj+HEhDObhpQw== +-----END CERTIFICATE-----""" + +TEST_MANUFACTURER_SUBJECT_BYTES = ( + b'0r1\x0b0\t\x06\x03U\x04\x06\x13\x02AU1\x130\x11\x06\x03U\x04\x08\x0c\n' + b'Some-State1!0\x1f\x06\x03U\x04\n\x0c\x18Internet Widgits Pty Ltd1+0)\x06' + b'\x03U\x04\x03\x0c"Test Manufacturer Root Certificate') + +# The manufacturer certificate chain can be generated using the manufacturer +# root and card certificates, the manufacturer and card keys, and the card and +# partition CSRs: +# 1. Sign the card CSR with the manufacturer key and root certificate to create +# the manufacturer card certificate: +# - openssl x509 -req -in card.csr -CA mfr.pem -CAkey mfr.key -CAcreateserial -out mfr_card.pem -days 365 +# 2. Sign the partition CSR with the card key and manufacturer card certificate +# to create the manufacturer partition certificate: +# - openssl x509 -req -in partition.csr -CA mfr_card.pem -CAkey card.key -CAcreateserial -out mfr_partition.pem -days 365 +# 3. Create the manufacturer certificate chain using the manufacturer card and +# partition certificates: +# - cat mfr_partition.pem mfr_card.pem > mfr_chain.pem +TEST_MANUFACTURER_CHAIN = b"""-----BEGIN CERTIFICATE----- +MIIDSzCCAjMCCQDectxvPIHUkzANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQGEwJB +VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 +cyBQdHkgTHRkMR4wHAYDVQQDDBVUZXN0IENhcmQgQ2VydGlmaWNhdGUwHhcNMjAw +NDA4MTQ1NDAzWhcNMjEwNDA4MTQ1NDAzWjBqMQswCQYDVQQGEwJBVTETMBEGA1UE +CAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk +MSMwIQYDVQQDDBpUZXN0IFBhcnRpdGlvbiBDZXJ0aWZpY2F0ZTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAOKNNkMQXNHO9DnYcD+U8Ll/v9Z4v7oVJ2xV +YlDMMj2IJzhfZI77miii/ll3UZj3EDGC4y4pdS0L81988WWaQ4yVEZtgV5+WHLGr +Bb08Ex5qKRJ5ag2dI/Sz6+M9+5pI1wQ2TscqpFTIjYmBOB91CK96JJOKKtuPcDC1 +31guMzTBpm3WiYyJBhR4Xj1McOwFLGBoPuZl9N8CzbqofVG1aTZi/C+AedU4YMjM +lKnB/Qxv94w6tsNPgbkjUGl3ZqmyUEKOG7zlIatnuXs70QEuv+KIouuFOTGiQppE +QaTNZ7UO0nhEG3e+cXyUzHxzlf8RTJpmhwqEHGnjF6YTsjVipGkCAwEAATANBgkq +hkiG9w0BAQsFAAOCAQEAD0rrWYZ+++ib6vvYi37j7hbwTuCRi1CSxXsrgiBFM1YK +cGzUnmqDXvJiBHX/qDIlkPxJy4AcfJ29r2dQ6kq1nwyaCAjqbGiFNR+VjNo4oAih +/xi1O1tIIZ4j979NAZmozsOXScYV1jVM/chM5QibDvdMp71YN8HwdMfGhssQe4sf +Kmu1IXq8XY/b0f/k3q5URgM+ur0qBJbqumY6fYgAYrfhSv7v4YlH2yqPVMWUOpHt +xFUKVbiXLux9xCsZ948b+lZLlETudUES5MpjHarlrr/CWxibjk6RJomN3eHHauZs +2ccag9KEQnjsBP2N2wqJeHU902attwvOoWQokUp9/Q== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDUzCCAjsCCQDNU9BSQM85jTANBgkqhkiG9w0BAQsFADByMQswCQYDVQQGEwJB +VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 +cyBQdHkgTHRkMSswKQYDVQQDDCJUZXN0IE1hbnVmYWN0dXJlciBSb290IENlcnRp +ZmljYXRlMB4XDTIwMDQwODE0NTQwNloXDTIxMDQwODE0NTQwNlowZTELMAkGA1UE +BhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdp +ZGdpdHMgUHR5IEx0ZDEeMBwGA1UEAwwVVGVzdCBDYXJkIENlcnRpZmljYXRlMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmVDLbIEpoVx9IzEalubKQuer +1iOI3c59gQDa9V6+iFBjmeOeE2vI97pojTAvWMWkRRKARklY3BruVKR5698yTLzb +cPwg3vqPvGNQFztJzaYAwRoerdT289upoEADdjQmA2Y7PF3zH88nLh74B9M5O8/t +jCfOWehl/ctUhTnIRCwEs7ZLMc3HHhG+puymhruD+hLNcgWfX2aizDEW8wpNZ6dd +JhGyA/OPXgFOhpqOZd/BFM/9w8rSBHfijitdNRK5UQj6TtRBykBj9ZvBzapRbLS/ +rrqnSoXFo6dlZaxpJKXT/bFvx1ImZSizJln2klYcKv30g4lg+U4PaHvxNOFdrQID +AQABMA0GCSqGSIb3DQEBCwUAA4IBAQCtCrCZFLDYjEcKoMwbHa7XyELcQ79qM+YV +i2UBYZqvoq9ty0cLa1O1pvF35qbG1Z8B7dZ3rKwiXsBnFk87rMaweWvQWZkZEs3T +u4hBRdSrDCb2SEIEOusI49fZVhbZbgqnNX1AkFe9lqNVu82pYKpNSkP13cATVghB +mrDi6mxjdeiTHiek+ND05YEdGi38ja5T3/0PJjyEj35WXT+7CP95MArfNc3rhy6J +wkovm2tDYaojIJJtgFcBP3yhzsJOyHCkEzMqcOSjoT9pDv5qvrHKDMTZRKjcOgKP +YlIjHK237cZfoEh1PO2OI14sM2iD3ZpD5idGZI/GHF6GUWZ2AJqM +-----END CERTIFICATE-----""" + +# The owner root certificate can be generated with the owner root key: +# - openssl req -x509 -key owner.key -days 3650 -out owner.pem +TEST_OWNER_ROOT = b"""-----BEGIN CERTIFICATE----- +MIIDrDCCApSgAwIBAgIJAJdbRMhyDhf+MA0GCSqGSIb3DQEBCwUAMGsxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQxJDAiBgNVBAMMG1Rlc3QgT3duZXIgUm9vdCBDZXJ0aWZp +Y2F0ZTAeFw0yMDA0MDgxMzM3MDVaFw0zMDA0MDYxMzM3MDVaMGsxCzAJBgNVBAYT +AkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRn +aXRzIFB0eSBMdGQxJDAiBgNVBAMMG1Rlc3QgT3duZXIgUm9vdCBDZXJ0aWZpY2F0 +ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANwhPaA1nzlVrl1sFThl +GmwNSVczMmIVXOUyklTI2HIW7iHXWqqyusksQZJBvWpYLom0CzEv42wYWLtx5U9S +KE8P++DLCzspkV+KW11Gyyq5xsVxlrrcQZ6/SXDS8092IIZc/qht9Sgwv/u03tQA +JdwOgisnKRX2wtQfSi8lT+kNiT8IG6nbc1oRGcRa0cNY9uKaElF/EHxj33quZnQ0 +tvxH7NhjxZ+GSiMbLChp3DZGnq9lcTurBFGPUe31riP5uThTiKA5DZDJadFSs8Q1 +O0W0XPyWysm5y/7pAuz4yHZPdVzj6kssklHOE9+Yk3D8Q39n0utQJ84m+IrMrj8r +qSsCAwEAAaNTMFEwHQYDVR0OBBYEFCK8CyqtlBC06Hd9x680rN3nRJeZMB8GA1Ud +IwQYMBaAFCK8CyqtlBC06Hd9x680rN3nRJeZMA8GA1UdEwEB/wQFMAMBAf8wDQYJ +KoZIhvcNAQELBQADggEBAHXopvAk9qAqsW6tK9YmRy+EXCqxoOuKdXlU75G1GVnX +Obk/1DCbT2/ioXmSeA15p7fFdf5JG4J7ejCfJG8uXrKb0JW0RWRGXmGnO967FejD +STV8tp/uAYduPVcR9EqVDfOKU2jf0MoZnP95/dlBxZ+yTMnuusBu8z7ERPsQWve8 +yitpRkFz9nrFytZRJVJxl+bm0Llz2eqINYR3Oia7v0xtS1XaJUGX5mG2gpMlIfpu +1ByJP8g11f9HW84eeZ9ceKU848uJtj+DTDnx4Ck1x6huMvZkxOAVTNWmT1+osDv7 +vsVkjBnGwXfcAv6jFQiErcdpZ1MVdLxsAFHrAvYH67E= +-----END CERTIFICATE-----""" + +# The owner card certificate chain can be generated using the owner +# root certificate, the owner key, and the card CSR. +# 1. Sign the card CSR with the owner root certificate to create the +# owner card certificate: +# - openssl x509 -req -in card.csr -CA owner.pem -CAkey owner.key -CAcreateserial -out owner_card_chain.pem -days 365 +TEST_OWNER_CARD_CHAIN = b"""-----BEGIN CERTIFICATE----- +MIIDTDCCAjQCCQDp+9ouYgrR0DANBgkqhkiG9w0BAQsFADBrMQswCQYDVQQGEwJB +VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 +cyBQdHkgTHRkMSQwIgYDVQQDDBtUZXN0IE93bmVyIFJvb3QgQ2VydGlmaWNhdGUw +HhcNMjAwNDA4MTYyMTEyWhcNMjEwNDA4MTYyMTEyWjBlMQswCQYDVQQGEwJBVTET +MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ +dHkgTHRkMR4wHAYDVQQDDBVUZXN0IENhcmQgQ2VydGlmaWNhdGUwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCZUMtsgSmhXH0jMRqW5spC56vWI4jdzn2B +ANr1Xr6IUGOZ454Ta8j3umiNMC9YxaRFEoBGSVjcGu5UpHnr3zJMvNtw/CDe+o+8 +Y1AXO0nNpgDBGh6t1Pbz26mgQAN2NCYDZjs8XfMfzycuHvgH0zk7z+2MJ85Z6GX9 +y1SFOchELASztksxzcceEb6m7KaGu4P6Es1yBZ9fZqLMMRbzCk1np10mEbID849e +AU6Gmo5l38EUz/3DytIEd+KOK101ErlRCPpO1EHKQGP1m8HNqlFstL+uuqdKhcWj +p2VlrGkkpdP9sW/HUiZlKLMmWfaSVhwq/fSDiWD5Tg9oe/E04V2tAgMBAAEwDQYJ +KoZIhvcNAQELBQADggEBAM3hz+TfQNaeuBgPyqBedN6QkhSiTdzpNG7Eyfw3Sx8n +OSuZxcsZgNRo+WNJt4zi9cMaOwgPcuoGCW7Iw2StEtBqgujlExrfUHzu17yoBHxQ +DTvi7QRHb6W2amsSKcuoFkI1txVmVWQA2HkSQVqIzZZoI3qVu2cQMyVHG7MKPHFU +5Mzw0H37gfttXYnUDZM84ETpGuf7EXA7ROdgwDvDD8CqOMDBKpKqau9QVh4aBZW4 +koGMoga+RwjNt4FVCW4F4qn43fteDSmxdUuxqCn6V7CpRlHIc8J2q9nzsne/NCA0 +2W+pXJD8hjvb9YQuXyV1QOaV6dcDLDcKG6NCdtxisxM= +-----END CERTIFICATE-----""" + +# The owner partition certificate chain can be generated using the owner +# root certificate, the owner key, and the partition CSR. +# 1. Sign the partition CSR with the owner key and root certificate to create +# the owner partition certificate: +# - openssl x509 -req -in partition.csr -CA owner.pem -CAkey owner.key -CAcreateserial -out owner_partition_chain.pem -days 365 +TEST_OWNER_PARTITION_CHAIN = b"""-----BEGIN CERTIFICATE----- +MIIDUTCCAjkCCQDp+9ouYgrR0TANBgkqhkiG9w0BAQsFADBrMQswCQYDVQQGEwJB +VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 +cyBQdHkgTHRkMSQwIgYDVQQDDBtUZXN0IE93bmVyIFJvb3QgQ2VydGlmaWNhdGUw +HhcNMjAwNDA4MTYyNDQ0WhcNMjEwNDA4MTYyNDQ0WjBqMQswCQYDVQQGEwJBVTET +MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ +dHkgTHRkMSMwIQYDVQQDDBpUZXN0IFBhcnRpdGlvbiBDZXJ0aWZpY2F0ZTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOKNNkMQXNHO9DnYcD+U8Ll/v9Z4 +v7oVJ2xVYlDMMj2IJzhfZI77miii/ll3UZj3EDGC4y4pdS0L81988WWaQ4yVEZtg +V5+WHLGrBb08Ex5qKRJ5ag2dI/Sz6+M9+5pI1wQ2TscqpFTIjYmBOB91CK96JJOK +KtuPcDC131guMzTBpm3WiYyJBhR4Xj1McOwFLGBoPuZl9N8CzbqofVG1aTZi/C+A +edU4YMjMlKnB/Qxv94w6tsNPgbkjUGl3ZqmyUEKOG7zlIatnuXs70QEuv+KIouuF +OTGiQppEQaTNZ7UO0nhEG3e+cXyUzHxzlf8RTJpmhwqEHGnjF6YTsjVipGkCAwEA +ATANBgkqhkiG9w0BAQsFAAOCAQEARAcOL0Y1XBYqIT/ySRFmN6f+cLUO3sPGllt8 +BLYhHsnyJjzV4By00GLcLp5cd14sIfkyVPPFA7kCwOGbL52avRo6+bPB2Bdg6MRt +wbXG3pa6DQXQ2vsZ0I1bXUdCS05YbfvfTrF7LLNCPYbul4Wph5zT360JmBVLKPaX +smw8fAPhdDwHix1ee2bopldrPrS0L55t3HLOBEF4XT9TCXFS23yBpujGsLEwHgaJ +x1un6v2v+bEPr8tpPv33WlSC9Fwlit0Xwf6sp/YuX11t223D7QmGN8rRyqv9Fm5l +RZh2a6rJjlnErdcLx/+5ojsCjbElfluJvsToc+iCcwut6FKcPg== +-----END CERTIFICATE-----""" + +# Test attestations can be generated with the following steps: +# 1. Create a file containing the test attestation statement. +# - echo "content" > attestation.dat +# 2. Sign the file with one the key pairs used to create the test certificates. +# - openssl dgst -sha256 -sign test1.key -out signature.dat attestation.dat +# 3. Concatenate the signature to the statement to create an attestation. +# - cat signature.dat >> attestation.dat +# 4. Compress the test attestation. +# - gzip attestation.dat +# For instructions on downloading attestations from Cloud HSM, refer to: +# https://cloud.google.com/kms/docs/attest-key#downloading_the_attestation_statement +TEST_ATTESTATION = ( + b'\x1f\x8b\x08\x08\xb9\xe37`\x00\x03attestation.dat\x00\x01\x08\x01\xf7' + b'\xfecontent\n>\xede\xeb\xd3\x9d\x9e\x8e*\xa2\xf4\x04i\xec\x10lI\xa1\xc5' + b'\xd6\x0c\xfd\x1a^T\x1f&>f\xb2\xae}UD\xf1\xbaW\xcf\xec\xc5\x10\x86s\x92A' + b'\xa1E\xc3\xf9=8/\xe5\xf4y\xf1\xa4H\xb1"\x08\xe7\x1a\xd1[\xc2\xb1CCO\x82' + b'\xe7-\xbd-=u\x15\x9a=\x1b\x98\xec\xb6\x1d\xc0\xd2\xf7\xcb\x99g\xdd\xed' + b'\xba\xcb\x9bK\xc7\xd8[\xd9\xf8?K\x0f\xd5\xaaO\xd4R0\xf6>\x18\xb2F\x13 ' + b'\xedi\xedV\xdc\x1bR-j\x85\xe3\xd5\x92\x9a\x9dU4\xc8\x13\xa10\xbbg\xee' + b'\xa3R\x8a\xcf\x88\x91p\xde\x9c\xe1\x82\xcd\x8a>\xa0\x1c\xf2\xb5\xb2\xb2' + b'\x91.Z\t\xc8a{\x896\x03+|\x8b\xa0\xb7\x16\xe8E\xca\x0c+\x17)\xd4\xd2s' + b'\x96\xfen\xa9\xf7\xa2\x1eW\xd3\xbd\n\x16\x12\xea8\xaa\x85xJ\x13d\xe5\x85' + b'\xdd\xe6\xca\x82;qw\xbe\x8fa\xb7\xeb\x06L\xd2\\pb\x0b\xbf\x9bj\xcc\xb0' + b'\x92\xf2\x81v\x1c\xa0\xa3?{~\x8e\xc1O\x1a\xc9\x7f\x9cCH\x1d\xef\x85\xe1' + b'\xeb\xa5\x08\x01\x00\x00') + + +def test_verify_certificate(): + signing_cert = x509.load_pem_x509_certificate(TEST_OWNER_ROOT, + backends.default_backend()) + issued_cert = x509.load_pem_x509_certificate(TEST_OWNER_PARTITION_CHAIN, + backends.default_backend()) + assert verify_attestation_chains.verify_certificate(signing_cert, + issued_cert) + + +def test_verify_certificate_fail(): + signing_cert = x509.load_pem_x509_certificate(TEST_OWNER_ROOT, + backends.default_backend()) + issued_cert = x509.load_pem_x509_certificate(TEST_MANUFACTURER_ROOT, + backends.default_backend()) + assert not verify_attestation_chains.verify_certificate(signing_cert, + issued_cert) + + +def get_test_manufacturer_root(): + return x509.load_pem_x509_certificate(TEST_MANUFACTURER_ROOT, + backends.default_backend()) + + +def get_test_owner_root(): + return x509.load_pem_x509_certificate(TEST_OWNER_ROOT, + backends.default_backend()) + + +def make_temporary_file(contents): + """Creates a NamedTemporaryFile with contents and returns its file name. + + Args: + contents: The contents to write to the temporary file. + + Returns: + The name of the temporary file. + """ + temp_file = tempfile.NamedTemporaryFile(delete=False) + temp_file.write(contents) + temp_file.close() + return temp_file.name + + +@pytest.fixture(scope='function') +def test_data(): + mfr_root = make_temporary_file(TEST_MANUFACTURER_ROOT) + mfr_chain = make_temporary_file(TEST_MANUFACTURER_CHAIN) + owner_root = make_temporary_file(TEST_OWNER_ROOT) + owner_card_chain = make_temporary_file(TEST_OWNER_CARD_CHAIN) + owner_partition_chain = make_temporary_file(TEST_OWNER_PARTITION_CHAIN) + cert_chains = make_temporary_file(b'\n'.join([ + TEST_MANUFACTURER_CHAIN, + TEST_OWNER_CARD_CHAIN, + TEST_OWNER_PARTITION_CHAIN + ])) + attestation = make_temporary_file(TEST_ATTESTATION) + + param = { + 'mfr_root': mfr_root, + 'mfr_chain': mfr_chain, + 'owner_root': owner_root, + 'owner_card_chain': owner_card_chain, + 'owner_partition_chain': owner_partition_chain, + 'cert_chains': cert_chains, + 'attestation': attestation + } + yield param + + +def test_verify(monkeypatch, test_data): + monkeypatch.setattr(verify_attestation_chains, + 'MANUFACTURER_CERT_SUBJECT_BYTES', + TEST_MANUFACTURER_SUBJECT_BYTES) + monkeypatch.setattr(verify_attestation_chains, + 'get_manufacturer_root_certificate', + get_test_manufacturer_root) + monkeypatch.setattr(verify_attestation_chains, + 'get_owner_root_certificate', + get_test_owner_root) + assert verify_attestation_chains.verify(test_data['cert_chains'], + test_data['attestation']) + + +def test_verify_invalid_mfr_root_fails(monkeypatch, test_data): + monkeypatch.setattr(verify_attestation_chains, + 'MANUFACTURER_CERT_SUBJECT_BYTES', + b'invalid') + monkeypatch.setattr(verify_attestation_chains, + 'get_manufacturer_root_certificate', + get_test_owner_root) + monkeypatch.setattr(verify_attestation_chains, + 'get_owner_root_certificate', + get_test_owner_root) + + assert not verify_attestation_chains.verify(test_data['cert_chains'], + test_data['attestation'])