diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5cf115a2a35e..48ce7b4e382c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -69,3 +69,4 @@ /trace/**/* @ymotongpoo @GoogleCloudPlatform/python-samples-reviewers /translate/**/* @nicain @GoogleCloudPlatform/python-samples-reviewers /workflows/**/* @GoogleCloudPlatform/python-samples-reviewers +/kms/**/** @GoogleCloudPlatform/dee-infra @GoogleCloudPlatform/python-samples-reviewers diff --git a/.github/blunderbuss.yml b/.github/blunderbuss.yml index 97e0b4b04893..170d4be5454a 100644 --- a/.github/blunderbuss.yml +++ b/.github/blunderbuss.yml @@ -126,6 +126,11 @@ assign_issues_by: - 'api: monitoring' to: - GoogleCloudPlatform/dee-observability +- labels: + - 'api: kms' + - 'api: cloudkms' + to: + - GoogleCloudPlatform/dee-infra assign_prs_by: - labels: diff --git a/kms/README.rst b/kms/README.rst deleted file mode 100644 index 63f6e5c37482..000000000000 --- a/kms/README.rst +++ /dev/null @@ -1,3 +0,0 @@ -These samples have been moved. - -https://github.com/googleapis/python-kms/tree/main/samples diff --git a/kms/attestations/README.rst b/kms/attestations/README.rst new file mode 100644 index 000000000000..0dce20a99c4e --- /dev/null +++ b/kms/attestations/README.rst @@ -0,0 +1,120 @@ +Google Cloud Key Management Service Python Samples +=============================================================================== + +.. image:: https://gstatic.com/cloudssh/images/open-btn.png + :target: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=kms/attestations/README.rst + + +This directory contains samples for Google Cloud Key Management Service. The `Cloud Key Management Service`_ allows you to create, import, and manage cryptographic keys and perform cryptographic operations in a single centralized cloud service. + + + + +.. _Cloud Key Management Service: https://cloud.google.com/kms/docs/ + + + + + +Setup +------------------------------------------------------------------------------- + + +Install Dependencies +++++++++++++++++++++ + +#. Clone python-kms and change directory to the sample directory you want to use. + + .. code-block:: bash + + $ git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git + +#. Install `pip`_ and `virtualenv`_ if you do not already have them. You may want to refer to the `Python Development Environment Setup Guide`_ for Google Cloud Platform for instructions. + + .. _Python Development Environment Setup Guide: + https://cloud.google.com/python/setup + +#. Create a virtualenv. Samples are compatible with Python 2.7 and 3.4+. + + .. code-block:: bash + + $ virtualenv env + $ source env/bin/activate + +#. Install the dependencies needed to run the samples. + + .. code-block:: bash + + $ pip install -r requirements.txt + +.. _pip: https://pip.pypa.io/ +.. _virtualenv: https://virtualenv.pypa.io/ + +Samples +------------------------------------------------------------------------------- + +Verify attestations and certificate chains for keys generated by Cloud HSM ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +.. image:: https://gstatic.com/cloudssh/images/open-btn.png + :target: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=kms/attestations/verify_attestation_chains.py,kms/attestations/README.rst + + + + +To run this sample: + +.. code-block:: bash + + $ python verify_attestation_chains.py + + usage: verify_attestation_chains.py [-h] [--certificates CERTIFICATES] + [--attestation ATTESTATION] + + This application verifies HSM attestations using certificate chains + obtained from Cloud HSM and the HSM manufacturer. + + For more information, visit https://cloud.google.com/kms/docs/attest-key. + + optional arguments: + -h, --help show this help message and exit + --certificates CERTIFICATES + The certificate chains filename. + --attestation ATTESTATION + The attestation filename. + + + +Verify attestations for keys generated by Cloud HSM ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +.. image:: https://gstatic.com/cloudssh/images/open-btn.png + :target: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=kms/attestations/verify_attestation.py,kms/attestations/README.rst + + + + +To run this sample: + +.. code-block:: bash + + $ python verify_attestation.py + + usage: verify_attestation.py [-h] attestation_file bundle_file + + This application verifies HSM attestations using certificate bundles obtained + from Cloud HSM. For more information, visit https://cloud.google.com/kms/docs + /attest-key. + + positional arguments: + attestation_file Name of attestation file. + bundle_file Name of certificate bundle file. + + optional arguments: + -h, --help show this help message and exit + + + + + +.. _Google Cloud SDK: https://cloud.google.com/sdk/ diff --git a/kms/attestations/noxfile_config.py b/kms/attestations/noxfile_config.py new file mode 100644 index 000000000000..34d0d0b1bb8a --- /dev/null +++ b/kms/attestations/noxfile_config.py @@ -0,0 +1,42 @@ +# 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. + +# Default TEST_CONFIG_OVERRIDE for python repos. + +# You can copy this file into your directory, then it will be imported from +# the noxfile.py. + +# The source of truth: +# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/noxfile_config.py + +TEST_CONFIG_OVERRIDE = { + # You can opt out from the test for specific Python versions. + "ignored_versions": ["2.7"], + # Old samples are opted out of enforcing Python type hints + # All new samples should feature them + "enforce_type_hints": False, + # An envvar key for determining the project id to use. Change it + # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a + # build specific Cloud project. You can also use your own string + # to use your own Cloud project. + "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", + # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', + # If you need to use a specific version of pip, + # change pip_version_override to the string representation + # of the version number, for example, "20.2.4" + "pip_version_override": None, + # A dictionary you want to inject into your test. Don't put any + # secrets here. These values will override predefined values. + "envs": {}, +} diff --git a/kms/attestations/requirements-test.txt b/kms/attestations/requirements-test.txt new file mode 100644 index 000000000000..49780e035690 --- /dev/null +++ b/kms/attestations/requirements-test.txt @@ -0,0 +1 @@ +pytest==7.2.0 diff --git a/kms/attestations/requirements.txt b/kms/attestations/requirements.txt new file mode 100644 index 000000000000..cf5cffd011cf --- /dev/null +++ b/kms/attestations/requirements.txt @@ -0,0 +1,3 @@ +cryptography==38.0.2 +pem==21.2.0 +requests==2.28.1 \ No newline at end of file diff --git a/kms/attestations/verify_attestation.py b/kms/attestations/verify_attestation.py new file mode 100644 index 000000000000..d56242b0194b --- /dev/null +++ b/kms/attestations/verify_attestation.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python + +# Copyright 2020 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 attestations using certificate bundles +obtained from Cloud HSM. + +For more information, visit https://cloud.google.com/kms/docs/attest-key. +""" + +# [START kms_verify_attestations] +import argparse +import gzip + +from cryptography import exceptions +from cryptography import x509 +from cryptography.hazmat import backends +from cryptography.hazmat.primitives.asymmetric import padding +import pem + + +def verify(attestation_file, bundle_file): + """Verifies an attestation using a bundle of certificates. + + Args: + attestation_file: The name of the attestation file. + bundle_file: The name of the bundle file containing the certificates + used to verify the attestation. + + Returns: + True if at least one of the certificates in bundle_file can verify the + attestation data and its signature. + """ + with gzip.open(attestation_file, 'rb') as f: + # An attestation file consists of a data portion and a 256 byte + # signature portion concatenated together. + attestation = f.read() + # Separate the components. + data = attestation[:-256] + signature = attestation[-256:] + + # Verify the attestation with one of the certificates in the bundle + for cert in pem.parse_file(bundle_file): + cert_obj = x509.load_pem_x509_certificate( + str(cert).encode('utf-8'), backends.default_backend()) + try: + # Check if the data was signed by the private key associated + # with the public key in the certificate. The data should have + # been signed with PKCS1v15 padding. + cert_obj.public_key().verify( + signature, data, padding.PKCS1v15(), + cert_obj.signature_hash_algorithm) + return True + except exceptions.InvalidSignature: + # Certificate bundles contain certificates that will not be + # able to verify the attestation, so the InvalidSignature + # errors can be ignored. + continue + return False +# [END kms_verify_attestations] + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description=__doc__) + parser.add_argument('attestation_file', help="Name of attestation file.") + parser.add_argument('bundle_file', help="Name of certificate bundle file.") + + args = parser.parse_args() + + if verify(args.attestation_file, args.bundle_file): + print('Signature verified.') + else: + print('Signature verification failed.') diff --git a/kms/attestations/verify_attestation_chains.py b/kms/attestations/verify_attestation_chains.py new file mode 100644 index 000000000000..d50173dbe07d --- /dev/null +++ b/kms/attestations/verify_attestation_chains.py @@ -0,0 +1,297 @@ +#!/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 attestations using certificate chains +obtained from Cloud HSM and the HSM manufacturer. + +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/kms/attestations/verify_attestation_chains_test.py b/kms/attestations/verify_attestation_chains_test.py new file mode 100644 index 000000000000..7435737c3263 --- /dev/null +++ b/kms/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']) diff --git a/kms/attestations/verify_attestation_test.py b/kms/attestations/verify_attestation_test.py new file mode 100644 index 000000000000..2d373b54436e --- /dev/null +++ b/kms/attestations/verify_attestation_test.py @@ -0,0 +1,109 @@ +# Copyright 2020 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 + +import verify_attestation + +# Test certificate bundles can be generated with the following steps: +# 1. Generate test key pairs. +# - openssl genrsa -out test1.key 2048 +# - openssl genrsa -out test2.key 2048 +# 2. Generate test certificates using the key pairs. +# - openssl req -x509 -key test1.key -days 3650 -out test1.pem +# - openssl req -x509 -key test2.key -days 3650 -out test2.pem +# 3. Create a bundle using the test certificates. +# - cat test1.pem test2.pem > bundle.pem +# For instructions on downloading certificate bundles from Cloud HSM, refer to: +# https://cloud.google.com/kms/docs/attest-key#downloading_the_certificates +TEST_CERT_BUNDLE = b"""-----BEGIN CERTIFICATE----- +MIIDZDCCAkwCFE2PSNf++wMw+Jv86m41lbsa9aUMMA0GCSqGSIb3DQEBCwUAMFkx +CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl +cm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCVRlc3QgQ2FyZDAeFw0yMDAz +MzEyMTQ0MjNaFw0zMDAzMjkyMTQ0MjNaMIGDMQswCQYDVQQGEwJBVTETMBEGA1UE +CAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk +MSMwIQYDVQQLDBpjbG91ZC1rbXMtcHJvZC11cy1jZW50cmFsMTEXMBUGA1UEAwwO +VGVzdCBQYXJ0aXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDh +cOk8hil8G4vl66jb02eXu2rKO6PmNF1GziXtzawyhx/+PDmEuGu+g/8hpivY6vDr +buAMFs8OIBBBlfED/soVANflXktDRGqWJau+rIrw9EGcclcwzlIboIi6KLPcLih0 +N0TTxqRLgy2EsEQ6UKS7su4bOIxD3Z6FSeTsoq+C2sgSWXmLitO0iRYYcgRjoyCU +kdzzO/JCwPKhhQx5NUrrHseALaIltG4D0aWLuBZKyV38yA1XEMdyCGk7RedEYC+v +OzaJrNToQBCIaCdn3F0uqJd49irLNPyQ5CY3NNL8rupJSq3iVxhEIZ8ANaU8UDvo +5iaQNKV1/KiQsXfUW6fbAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAIIGB0aXhd6k +xyWgYzJ0DHpYP2ALwHRWXde5PtEkdHDguPlUTzZ5iTfqOJlEeUXlDO9RV82jc4mE +hguCaMl3Q+2JGLJLCnSDgcaY5WAVBF9FSakdbHBj4Ik9L8NDlUQB6Me4d4gKWpg1 +bUD4n2KtvCZGZzA3pfRBcYyAbwC+xEi1XrITyshb0pkjsWO4Urg36W/RpkCiYAw0 +Xua0jJMG/wcF+xktd7kgcsBh5Es2VCzyQwisXoOIi3EY7lMJK2+ctjQFy1GxumBU +jBlXj0VjAm3QOVLTh3mfb1XofoIjOOYkMBjXMiQhFy/Lv68u5q7qlEYe92OKxkCO +0UaAcqt8+QM= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDRjCCAi4CFBVm+eV+oRkaYq2NyuTfwxWapjFOMA0GCSqGSIb3DQEBCwUAMGYx +CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl +cm5ldCBXaWRnaXRzIFB0eSBMdGQxHzAdBgNVBAMMFlRlc3QgTWFudWZhY3R1cmVy +IFJvb3QwHhcNMjAwMzMxMjE0MjU1WhcNMzAwMzI5MjE0MjU1WjBZMQswCQYDVQQG +EwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lk +Z2l0cyBQdHkgTHRkMRIwEAYDVQQDDAlUZXN0IENhcmQwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQC28Wu0dusN6AYkYon8RIuHlJWWwZWlTxXSMK4v/IOY +pG9F2/gUEDMQOgpyCCpTc5eLHRPa/Z2QgB0c2VSlQC8FZ1l9/YL7uBTJ0UpDoBf8 +LUimIqotneXpL+7CW1kWFLZIgpm0iVuTPjV2b3frtvu0B+nYuyo4dtToebqoOKse +F3ymLsAjSqA9aoCD0XbspAkLJIvdQU28vXY4Y2y0OTGUnaQ7ZDwkNLxeAfeIdNJD +FRCcYsLRopsyptFMYLLDrI70gywAGYaGOxYG8747BIZELyT5Gnt0o7JwpuF8Mi53 +T5NGiu5/wLwXnxRRhb3M5+lStdTfvbEfgK1mC0ac8ym5AgMBAAEwDQYJKoZIhvcN +AQELBQADggEBAILH0Q8WlgaGBosPBjnpAsp4eZOfq9skKZERYzFA2wBAy4B0q9/S +3oL1MIZEU6/ckiFyAm3r4/ZxMLX0UrisuRQUFQ3w+gqFccqlmGETsZGJpPtfIm+n +JQo44XXnTHndqoYPNfvfQkp0WtJQS8hSgTASP+1GYjqOn1fZaa28m39/mx9Mrw7K +xtXOtrdKqJhWCWJPprfC5sYNCYTA2HXVmBU2Y4AP4A4w+A1gCAdzvH8EEyDjnvxJ +GEa4uczhA3n+NmhLipg1aGbxJO5ZHXdyFF2rTXVVXSiX8EEasnwcTDjeXDKhdpu6 +biaxW9fnsJIXirAE03FFkC/tWelkGFkmMfs= +-----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_GZ = ( + b'\x1f\x8b\x08\x08\xda\xde\x84^\x00\x03attestation\x00\x01\x06\x01\xf9\xfe' + b'\x15\xa7~W\xdazHq03\x95\xd1F\xcf\x1d\n\xe0\xbbv\x11\xed\xae\x186\xc0\xcc' + b'.\xcf)\xf1?\xf7!\xf3\xd6M\x85\xfe\xb9\x84\xb2\x08V2(\xa1\x87]\xab\x01=' + b'\xb5\x0f)~\x06\xee\xfa/\x94\xa6x\x96o\xb1\xcb$\x82\x90\xe03J\t\x03\xf0' + b'\xa4\xa5\xa9\xf9\xb2\xce\xdd2\xfam\x94W\x07\x00~\xa5\xc2\xcdq\xa1\x81' + b'\x18\x83\xe0\xd9\x11k]\xbd\xf8\x81@\x9c*\x80\x91R\xb0\xae\x9d\x88\xb8T' + b'\xd1>\xf6;\xe4\x83q%_\x8aw\x894\xb5:\xeab\xd2\x9a\x81\xdd\xa6\xf9\x94' + b'\xff8\xb1\xed\x7fs\x0e\xc0\xde\x89\x00]\x8fL\x82\x8a\x11\x8f\\\xe483\x9d' + b'&\x0b%\xfd\x0et\x8f\xa8\x1a\xb5K\xb4\xc7\x96\xd1}\x06\xdd\x93i\x1f\xc1@' + b'\x92\xef}(B\x0f\xd1\x03\xaaYo\x9b\xad\xa9zw#\xc8\x9a\xad\x94\xfc\x07q]x' + b'\xeb\xa2CA\xf8\xac\x96\xd9\xe5y4\xae]\x81\xb0$\x93\tx\xdb\xcc\x08:%\x1d' + b'\xe2q\xaa\xc8:\xc2cloud.google.com. + + +### Quickstart + +This quickstart shows you how to create and use encryption keys with Cloud Key Management Service. + + +Open in Cloud Shell + + +To run this sample: + +1. If this is your first time working with GCP products, you will need to set up [the Cloud SDK][cloud_sdk] or utilize [Google Cloud Shell][gcloud_shell]. This sample may [require authetication][authentication] and you will need to [enable billing][enable_billing]. + +1. Make a fork of this repo and clone the branch locally, then navigate to the sample directory you want to use. + +1. Install the dependencies needed to run the samples. + + pip install -r requirements.txt + +1. Run the sample using + + python quickstart.py + + + +More information about the Cloud KMS quickstart is available at https://cloud.google.com/kms/docs/quickstart + +## Additional Information + +These samples use the [Google Cloud Client Library for Python][client_library_python]. +You can read the documentation for more details on API usage and use GitHub +to browse the source and [report issues][issues]. + +### Contributing +View the [contributing guidelines][contrib_guide], the [Python style guide][py_style] for more information. + +[authentication]: https://cloud.google.com/docs/authentication/getting-started +[enable_billing]:https://cloud.google.com/apis/docs/getting-started#enabling_billing +[client_library_python]: https://googlecloudplatform.github.io/google-cloud-python/ +[issues]: https://github.com/GoogleCloudPlatform/google-cloud-python/issues +[contrib_guide]: https://github.com/googleapis/google-cloud-python/blob/main/CONTRIBUTING.rst +[py_style]: http://google.github.io/styleguide/pyguide.html +[cloud_sdk]: https://cloud.google.com/sdk/docs +[gcloud_shell]: https://cloud.google.com/shell/docs +[gcloud_shell]: https://cloud.google.com/shell/docs diff --git a/kms/snippets/check_state_import_job.py b/kms/snippets/check_state_import_job.py new file mode 100644 index 000000000000..5d8e05e2a855 --- /dev/null +++ b/kms/snippets/check_state_import_job.py @@ -0,0 +1,41 @@ +# Copyright 2020 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 + + +# [START kms_check_state_import_job] +def check_state_import_job(project_id, location_id, key_ring_id, import_job_id): + """ + Check the state of an import job in Cloud KMS. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + import_job_id (string): ID of the import job (e.g. 'my-import-job'). + """ + + # Import the client library. + from google.cloud import kms + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Retrieve the fully-qualified import_job string. + import_job_name = client.import_job_path( + project_id, location_id, key_ring_id, import_job_id) + + # Retrieve the state from an existing import job. + import_job = client.get_import_job(name=import_job_name) + + print('Current state of import job {}: {}'.format(import_job.name, import_job.state)) +# [END kms_check_state_import_job] diff --git a/kms/snippets/check_state_imported_key.py b/kms/snippets/check_state_imported_key.py new file mode 100644 index 000000000000..1b65eee1e0ba --- /dev/null +++ b/kms/snippets/check_state_imported_key.py @@ -0,0 +1,41 @@ +# Copyright 2020 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 + + +# [START kms_check_state_imported_key] +def check_state_imported_key(project_id, location_id, key_ring_id, import_job_id): + """ + Check the state of an import job in Cloud KMS. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + import_job_id (string): ID of the import job (e.g. 'my-import-job'). + """ + + # Import the client library. + from google.cloud import kms + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Retrieve the fully-qualified import_job string. + import_job_name = client.import_job_path( + project_id, location_id, key_ring_id, import_job_id) + + # Retrieve the state from an existing import job. + import_job = client.get_import_job(name=import_job_name) + + print('Current state of import job {}: {}'.format(import_job.name, import_job.state)) +# [END kms_check_state_imported_key] diff --git a/kms/snippets/create_import_job.py b/kms/snippets/create_import_job.py new file mode 100644 index 000000000000..dd79c4e488ea --- /dev/null +++ b/kms/snippets/create_import_job.py @@ -0,0 +1,47 @@ +# Copyright 2020 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 + + +# [START kms_create_import_job] +def create_import_job(project_id, location_id, key_ring_id, import_job_id): + """ + Create a new import job in Cloud KMS. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + import_job_id (string): ID of the import job (e.g. 'my-import-job'). + """ + + # Import the client library. + from google.cloud import kms + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Retrieve the fully-qualified key_ring string. + key_ring_name = client.key_ring_path(project_id, location_id, key_ring_id) + + # Set paramaters for the import job, allowed values for ImportMethod and ProtectionLevel found here: + # https://googleapis.dev/python/cloudkms/latest/_modules/google/cloud/kms_v1/types/resources.html + + import_method = kms.ImportJob.ImportMethod.RSA_OAEP_3072_SHA1_AES_256 + protection_level = kms.ProtectionLevel.HSM + import_job_params = {"import_method": import_method, "protection_level": protection_level} + + # Call the client to create a new import job. + import_job = client.create_import_job({"parent": key_ring_name, "import_job_id": import_job_id, "import_job": import_job_params}) + + print('Created import job: {}'.format(import_job.name)) +# [END kms_create_import_job] diff --git a/kms/snippets/create_key_asymmetric_decrypt.py b/kms/snippets/create_key_asymmetric_decrypt.py new file mode 100644 index 000000000000..5d0e64a0754e --- /dev/null +++ b/kms/snippets/create_key_asymmetric_decrypt.py @@ -0,0 +1,61 @@ +# Copyright 2020 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 + + +# [START kms_create_key_asymmetric_decrypt] +def create_key_asymmetric_decrypt(project_id, location_id, key_ring_id, key_id): + """ + Creates a new asymmetric decryption key in Cloud KMS. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (string): ID of the key to create (e.g. 'my-asymmetric-decrypt-key'). + + Returns: + CryptoKey: Cloud KMS key. + + """ + + # Import the client library. + from google.cloud import kms + from google.protobuf import duration_pb2 + import datetime + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the parent key ring name. + key_ring_name = client.key_ring_path(project_id, location_id, key_ring_id) + + # Build the key. + purpose = kms.CryptoKey.CryptoKeyPurpose.ASYMMETRIC_DECRYPT + algorithm = kms.CryptoKeyVersion.CryptoKeyVersionAlgorithm.RSA_DECRYPT_OAEP_2048_SHA256 + key = { + 'purpose': purpose, + 'version_template': { + 'algorithm': algorithm, + }, + + # Optional: customize how long key versions should be kept before + # destroying. + 'destroy_scheduled_duration': duration_pb2.Duration().FromTimedelta(datetime.timedelta(days=1)) + } + + # Call the API. + created_key = client.create_crypto_key( + request={'parent': key_ring_name, 'crypto_key_id': key_id, 'crypto_key': key}) + print('Created asymmetric decrypt key: {}'.format(created_key.name)) + return created_key +# [END kms_create_key_asymmetric_decrypt] diff --git a/kms/snippets/create_key_asymmetric_sign.py b/kms/snippets/create_key_asymmetric_sign.py new file mode 100644 index 000000000000..0422508ceb21 --- /dev/null +++ b/kms/snippets/create_key_asymmetric_sign.py @@ -0,0 +1,61 @@ +# Copyright 2020 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 + + +# [START kms_create_key_asymmetric_sign] +def create_key_asymmetric_sign(project_id, location_id, key_ring_id, key_id): + """ + Creates a new asymmetric signing key in Cloud KMS. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (string): ID of the key to create (e.g. 'my-asymmetric-signing-key'). + + Returns: + CryptoKey: Cloud KMS key. + + """ + + # Import the client library. + from google.cloud import kms + from google.protobuf import duration_pb2 + import datetime + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the parent key ring name. + key_ring_name = client.key_ring_path(project_id, location_id, key_ring_id) + + # Build the key. + purpose = kms.CryptoKey.CryptoKeyPurpose.ASYMMETRIC_SIGN + algorithm = kms.CryptoKeyVersion.CryptoKeyVersionAlgorithm.RSA_SIGN_PKCS1_2048_SHA256 + key = { + 'purpose': purpose, + 'version_template': { + 'algorithm': algorithm, + }, + + # Optional: customize how long key versions should be kept before + # destroying. + 'destroy_scheduled_duration': duration_pb2.Duration().FromTimedelta(datetime.timedelta(days=1)) + } + + # Call the API. + created_key = client.create_crypto_key( + request={'parent': key_ring_name, 'crypto_key_id': key_id, 'crypto_key': key}) + print('Created asymmetric signing key: {}'.format(created_key.name)) + return created_key +# [END kms_create_key_asymmetric_sign] diff --git a/kms/snippets/create_key_for_import.py b/kms/snippets/create_key_for_import.py new file mode 100644 index 000000000000..3c4e895039cb --- /dev/null +++ b/kms/snippets/create_key_for_import.py @@ -0,0 +1,54 @@ +# Copyright 2020 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 + + +# [START kms_create_key_for_import] +def create_key_for_import(project_id, location_id, key_ring_id, crypto_key_id): + """ + + Sets up an empty CryptoKey within a KeyRing for import. + + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + crypto_key_id (string): ID of the key to import (e.g. 'my-asymmetric-signing-key'). + """ + + # Import the client library. + from google.cloud import kms + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the key. For more information regarding allowed values of these fields, see: + # https://googleapis.dev/python/cloudkms/latest/_modules/google/cloud/kms_v1/types/resources.html + purpose = kms.CryptoKey.CryptoKeyPurpose.ASYMMETRIC_SIGN + algorithm = kms.CryptoKeyVersion.CryptoKeyVersionAlgorithm.EC_SIGN_P256_SHA256 + protection_level = kms.ProtectionLevel.HSM + key = { + 'purpose': purpose, + 'version_template': { + 'algorithm': algorithm, + 'protection_level': protection_level + } + } + + # Build the parent key ring name. + key_ring_name = client.key_ring_path(project_id, location_id, key_ring_id) + + # Call the API. + created_key = client.create_crypto_key(request={'parent': key_ring_name, 'crypto_key_id': crypto_key_id, 'crypto_key': key}) + print('Created hsm key: {}'.format(created_key.name)) +# [END kms_create_key_for_import] diff --git a/kms/snippets/create_key_hsm.py b/kms/snippets/create_key_hsm.py new file mode 100644 index 000000000000..6e95e68c9275 --- /dev/null +++ b/kms/snippets/create_key_hsm.py @@ -0,0 +1,63 @@ +# Copyright 2020 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 + + +# [START kms_create_key_hsm] +def create_key_hsm(project_id, location_id, key_ring_id, key_id): + """ + Creates a new key in Cloud KMS backed by Cloud HSM. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (string): ID of the key to create (e.g. 'my-hsm-key'). + + Returns: + CryptoKey: Cloud KMS key. + + """ + + # Import the client library. + from google.cloud import kms + from google.protobuf import duration_pb2 + import datetime + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the parent key ring name. + key_ring_name = client.key_ring_path(project_id, location_id, key_ring_id) + + # Build the key. + purpose = kms.CryptoKey.CryptoKeyPurpose.ENCRYPT_DECRYPT + algorithm = kms.CryptoKeyVersion.CryptoKeyVersionAlgorithm.GOOGLE_SYMMETRIC_ENCRYPTION + protection_level = kms.ProtectionLevel.HSM + key = { + 'purpose': purpose, + 'version_template': { + 'algorithm': algorithm, + 'protection_level': protection_level + }, + + # Optional: customize how long key versions should be kept before + # destroying. + 'destroy_scheduled_duration': duration_pb2.Duration().FromTimedelta(datetime.timedelta(days=1)) + } + + # Call the API. + created_key = client.create_crypto_key( + request={'parent': key_ring_name, 'crypto_key_id': key_id, 'crypto_key': key}) + print('Created hsm key: {}'.format(created_key.name)) + return created_key +# [END kms_create_key_hsm] diff --git a/kms/snippets/create_key_labels.py b/kms/snippets/create_key_labels.py new file mode 100644 index 000000000000..cf84265a704e --- /dev/null +++ b/kms/snippets/create_key_labels.py @@ -0,0 +1,59 @@ +# Copyright 2020 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 + + +# [START kms_create_key_labels] +def create_key_labels(project_id, location_id, key_ring_id, key_id): + """ + Creates a new key in Cloud KMS with labels. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (string): ID of the key to create (e.g. 'my-labeled-key'). + + Returns: + CryptoKey: Cloud KMS key. + + """ + + # Import the client library. + from google.cloud import kms + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the parent key ring name. + key_ring_name = client.key_ring_path(project_id, location_id, key_ring_id) + + # Build the key. + purpose = kms.CryptoKey.CryptoKeyPurpose.ENCRYPT_DECRYPT + algorithm = kms.CryptoKeyVersion.CryptoKeyVersionAlgorithm.GOOGLE_SYMMETRIC_ENCRYPTION + key = { + 'purpose': purpose, + 'version_template': { + 'algorithm': algorithm, + }, + 'labels': { + 'team': 'alpha', + 'cost_center': 'cc1234' + } + } + + # Call the API. + created_key = client.create_crypto_key( + request={'parent': key_ring_name, 'crypto_key_id': key_id, 'crypto_key': key}) + print('Created labeled key: {}'.format(created_key.name)) + return created_key +# [END kms_create_key_labels] diff --git a/kms/snippets/create_key_mac.py b/kms/snippets/create_key_mac.py new file mode 100644 index 000000000000..7c4c86755843 --- /dev/null +++ b/kms/snippets/create_key_mac.py @@ -0,0 +1,61 @@ +# 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 + + +# [START kms_create_key_mac] +def create_key_mac(project_id, location_id, key_ring_id, key_id): + """ + Creates a new key in Cloud KMS for HMAC operations. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (string): ID of the key to create (e.g. 'my-mac-key'). + + Returns: + CryptoKey: Cloud KMS key. + + """ + + # Import the client library. + from google.cloud import kms + from google.protobuf import duration_pb2 + import datetime + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the parent key ring name. + key_ring_name = client.key_ring_path(project_id, location_id, key_ring_id) + + # Build the key. + purpose = kms.CryptoKey.CryptoKeyPurpose.MAC + algorithm = kms.CryptoKeyVersion.CryptoKeyVersionAlgorithm.HMAC_SHA256 + key = { + 'purpose': purpose, + 'version_template': { + 'algorithm': algorithm, + }, + + # Optional: customize how long key versions should be kept before + # destroying. + 'destroy_scheduled_duration': duration_pb2.Duration().FromTimedelta(datetime.timedelta(days=1)) + } + + # Call the API. + created_key = client.create_crypto_key( + request={'parent': key_ring_name, 'crypto_key_id': key_id, 'crypto_key': key}) + print('Created mac key: {}'.format(created_key.name)) + return created_key +# [END kms_create_key_mac] diff --git a/kms/snippets/create_key_ring.py b/kms/snippets/create_key_ring.py new file mode 100644 index 000000000000..89e914e54268 --- /dev/null +++ b/kms/snippets/create_key_ring.py @@ -0,0 +1,47 @@ +# Copyright 2020 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 + + +# [START kms_create_key_ring] +def create_key_ring(project_id, location_id, key_ring_id): + """ + Creates a new key ring in Cloud KMS + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the key ring to create (e.g. 'my-key-ring'). + + Returns: + KeyRing: Cloud KMS key ring. + + """ + + # Import the client library. + from google.cloud import kms + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the parent location name. + location_name = f'projects/{project_id}/locations/{location_id}' + + # Build the key ring. + key_ring = {} + + # Call the API. + created_key_ring = client.create_key_ring( + request={'parent': location_name, 'key_ring_id': key_ring_id, 'key_ring': key_ring}) + print('Created key ring: {}'.format(created_key_ring.name)) + return created_key_ring +# [END kms_create_key_ring] diff --git a/kms/snippets/create_key_rotation_schedule.py b/kms/snippets/create_key_rotation_schedule.py new file mode 100644 index 000000000000..ce450dea8334 --- /dev/null +++ b/kms/snippets/create_key_rotation_schedule.py @@ -0,0 +1,68 @@ +# Copyright 2020 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 + + +# [START kms_create_key_rotation_schedule] +def create_key_rotation_schedule(project_id, location_id, key_ring_id, key_id): + """ + Creates a new key in Cloud KMS that automatically rotates. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (string): ID of the key to create (e.g. 'my-rotating-key'). + + Returns: + CryptoKey: Cloud KMS key. + + """ + + # Import the client library. + from google.cloud import kms + + # Import time for getting the current time. + import time + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the parent key ring name. + key_ring_name = client.key_ring_path(project_id, location_id, key_ring_id) + + # Build the key. + purpose = kms.CryptoKey.CryptoKeyPurpose.ENCRYPT_DECRYPT + algorithm = kms.CryptoKeyVersion.CryptoKeyVersionAlgorithm.GOOGLE_SYMMETRIC_ENCRYPTION + key = { + 'purpose': purpose, + 'version_template': { + 'algorithm': algorithm, + }, + + # Rotate the key every 30 days. + 'rotation_period': { + 'seconds': 60 * 60 * 24 * 30 + }, + + # Start the first rotation in 24 hours. + 'next_rotation_time': { + 'seconds': int(time.time()) + 60 * 60 * 24 + } + } + + # Call the API. + created_key = client.create_crypto_key( + request={'parent': key_ring_name, 'crypto_key_id': key_id, 'crypto_key': key}) + print('Created labeled key: {}'.format(created_key.name)) + return created_key +# [END kms_create_key_rotation_schedule] diff --git a/kms/snippets/create_key_symmetric_encrypt_decrypt.py b/kms/snippets/create_key_symmetric_encrypt_decrypt.py new file mode 100644 index 000000000000..abc4eae2b787 --- /dev/null +++ b/kms/snippets/create_key_symmetric_encrypt_decrypt.py @@ -0,0 +1,55 @@ +# Copyright 2020 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 + + +# [START kms_create_key_symmetric_encrypt_decrypt] +def create_key_symmetric_encrypt_decrypt(project_id, location_id, key_ring_id, key_id): + """ + Creates a new symmetric encryption/decryption key in Cloud KMS. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (string): ID of the key to create (e.g. 'my-symmetric-key'). + + Returns: + CryptoKey: Cloud KMS key. + + """ + + # Import the client library. + from google.cloud import kms + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the parent key ring name. + key_ring_name = client.key_ring_path(project_id, location_id, key_ring_id) + + # Build the key. + purpose = kms.CryptoKey.CryptoKeyPurpose.ENCRYPT_DECRYPT + algorithm = kms.CryptoKeyVersion.CryptoKeyVersionAlgorithm.GOOGLE_SYMMETRIC_ENCRYPTION + key = { + 'purpose': purpose, + 'version_template': { + 'algorithm': algorithm, + } + } + + # Call the API. + created_key = client.create_crypto_key( + request={'parent': key_ring_name, 'crypto_key_id': key_id, 'crypto_key': key}) + print('Created symmetric key: {}'.format(created_key.name)) + return created_key +# [END kms_create_key_symmetric_encrypt_decrypt] diff --git a/kms/snippets/create_key_version.py b/kms/snippets/create_key_version.py new file mode 100644 index 000000000000..aced5abfc9db --- /dev/null +++ b/kms/snippets/create_key_version.py @@ -0,0 +1,47 @@ +# Copyright 2020 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 + + +# [START kms_create_key_version] +def create_key_version(project_id, location_id, key_ring_id, key_id): + """ + Creates a new version of the given key. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (string): ID of the key for which to create a new version (e.g. 'my-key'). + + Returns: + CryptoKeyVersion: Cloud KMS key version. + + """ + + # Import the client library. + from google.cloud import kms + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the parent key name. + key_name = client.crypto_key_path(project_id, location_id, key_ring_id, key_id) + + # Build the key version. + version = {} + + # Call the API. + created_version = client.create_crypto_key_version(request={'parent': key_name, 'crypto_key_version': version}) + print('Created key version: {}'.format(created_version.name)) + return created_version +# [END kms_create_key_version] diff --git a/kms/snippets/decrypt_asymmetric.py b/kms/snippets/decrypt_asymmetric.py new file mode 100644 index 000000000000..006d5afb9637 --- /dev/null +++ b/kms/snippets/decrypt_asymmetric.py @@ -0,0 +1,75 @@ +# Copyright 2020 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 + + +# [START kms_decrypt_asymmetric] +def decrypt_asymmetric(project_id, location_id, key_ring_id, key_id, version_id, ciphertext): + """ + Decrypt the ciphertext using an asymmetric key. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (string): ID of the key to use (e.g. 'my-key'). + version_id (string): ID of the key version to use (e.g. '1'). + ciphertext (bytes): Encrypted bytes to decrypt. + + Returns: + DecryptResponse: Response including plaintext. + + """ + + # Import the client library. + from google.cloud import kms + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the key version name. + key_version_name = client.crypto_key_version_path(project_id, location_id, key_ring_id, key_id, version_id) + + # Optional, but recommended: compute ciphertext's CRC32C. + # See crc32c() function defined below. + ciphertext_crc32c = crc32c(ciphertext) + + # Call the API. + decrypt_response = client.asymmetric_decrypt( + request={'name': key_version_name, 'ciphertext': ciphertext, 'ciphertext_crc32c': ciphertext_crc32c}) + + # Optional, but recommended: perform integrity verification on decrypt_response. + # For more details on ensuring E2E in-transit integrity to and from Cloud KMS visit: + # https://cloud.google.com/kms/docs/data-integrity-guidelines + if not decrypt_response.verified_ciphertext_crc32c: + raise Exception('The request sent to the server was corrupted in-transit.') + if not decrypt_response.plaintext_crc32c == crc32c(decrypt_response.plaintext): + raise Exception('The response received from the server was corrupted in-transit.') + # End integrity verification + + print('Plaintext: {}'.format(decrypt_response.plaintext)) + return decrypt_response + + +def crc32c(data): + """ + Calculates the CRC32C checksum of the provided data. + Args: + data: the bytes over which the checksum should be calculated. + Returns: + An int representing the CRC32C checksum of the provided bytes. + """ + import crcmod + import six + crc32c_fun = crcmod.predefined.mkPredefinedCrcFun('crc-32c') + return crc32c_fun(six.ensure_binary(data)) +# [END kms_decrypt_asymmetric] diff --git a/kms/snippets/decrypt_symmetric.py b/kms/snippets/decrypt_symmetric.py new file mode 100644 index 000000000000..8c6ec724f2c3 --- /dev/null +++ b/kms/snippets/decrypt_symmetric.py @@ -0,0 +1,72 @@ +# Copyright 2020 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 + + +# [START kms_decrypt_symmetric] +def decrypt_symmetric(project_id, location_id, key_ring_id, key_id, ciphertext): + """ + Decrypt the ciphertext using the symmetric key + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (string): ID of the key to use (e.g. 'my-key'). + ciphertext (bytes): Encrypted bytes to decrypt. + + Returns: + DecryptResponse: Response including plaintext. + + """ + + # Import the client library. + from google.cloud import kms + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the key name. + key_name = client.crypto_key_path(project_id, location_id, key_ring_id, key_id) + + # Optional, but recommended: compute ciphertext's CRC32C. + # See crc32c() function defined below. + ciphertext_crc32c = crc32c(ciphertext) + + # Call the API. + decrypt_response = client.decrypt( + request={'name': key_name, 'ciphertext': ciphertext, 'ciphertext_crc32c': ciphertext_crc32c}) + + # Optional, but recommended: perform integrity verification on decrypt_response. + # For more details on ensuring E2E in-transit integrity to and from Cloud KMS visit: + # https://cloud.google.com/kms/docs/data-integrity-guidelines + if not decrypt_response.plaintext_crc32c == crc32c(decrypt_response.plaintext): + raise Exception('The response received from the server was corrupted in-transit.') + # End integrity verification + + print('Plaintext: {}'.format(decrypt_response.plaintext)) + return decrypt_response + + +def crc32c(data): + """ + Calculates the CRC32C checksum of the provided data. + Args: + data: the bytes over which the checksum should be calculated. + Returns: + An int representing the CRC32C checksum of the provided bytes. + """ + import crcmod + import six + crc32c_fun = crcmod.predefined.mkPredefinedCrcFun('crc-32c') + return crc32c_fun(six.ensure_binary(data)) +# [END kms_decrypt_symmetric] diff --git a/kms/snippets/destroy_key_version.py b/kms/snippets/destroy_key_version.py new file mode 100644 index 000000000000..1425c890e4b0 --- /dev/null +++ b/kms/snippets/destroy_key_version.py @@ -0,0 +1,45 @@ +# Copyright 2020 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 + + +# [START kms_destroy_key_version] +def destroy_key_version(project_id, location_id, key_ring_id, key_id, version_id): + """ + Schedule destruction of the given key version. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (string): ID of the key to use (e.g. 'my-key'). + version_id (string): ID of the key version to destroy (e.g. '1'). + + Returns: + CryptoKeyVersion: The version. + + """ + + # Import the client library. + from google.cloud import kms + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the key version name. + key_version_name = client.crypto_key_version_path(project_id, location_id, key_ring_id, key_id, version_id) + + # Call the API. + destroyed_version = client.destroy_crypto_key_version(request={'name': key_version_name}) + print('Destroyed key version: {}'.format(destroyed_version.name)) + return destroyed_version +# [END kms_destroy_key_version] diff --git a/kms/snippets/disable_key_version.py b/kms/snippets/disable_key_version.py new file mode 100644 index 000000000000..a4625d704393 --- /dev/null +++ b/kms/snippets/disable_key_version.py @@ -0,0 +1,53 @@ +# Copyright 2020 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 + + +# [START kms_disable_key_version] +def disable_key_version(project_id, location_id, key_ring_id, key_id, version_id): + """ + Disable a key. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (string): ID of the key to use (e.g. 'my-key'). + version_id (string): ID of the key version to disable (e.g. '1'). + + Returns: + CryptoKeyVersion: The version. + + """ + + # Import the client library. + from google.cloud import kms + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the key version name. + key_version_name = client.crypto_key_version_path(project_id, location_id, key_ring_id, key_id, version_id) + + key_version = { + 'name': key_version_name, + 'state': kms.CryptoKeyVersion.CryptoKeyVersionState.DISABLED + } + + # Build the update mask. + update_mask = {'paths': ['state']} + + # Call the API. + disabled_version = client.update_crypto_key_version(request={'crypto_key_version': key_version, 'update_mask': update_mask}) + print('Disabled key version: {}'.format(disabled_version.name)) + return disabled_version +# [END kms_disable_key_version] diff --git a/kms/snippets/enable_key_version.py b/kms/snippets/enable_key_version.py new file mode 100644 index 000000000000..edad8eabe1c9 --- /dev/null +++ b/kms/snippets/enable_key_version.py @@ -0,0 +1,53 @@ +# Copyright 2020 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 + + +# [START kms_enable_key_version] +def enable_key_version(project_id, location_id, key_ring_id, key_id, version_id): + """ + Enable a key. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (string): ID of the key to use (e.g. 'my-key'). + version_id (string): ID of the key version to enable (e.g. '1'). + + Returns: + CryptoKeyVersion: The version. + + """ + + # Import the client library. + from google.cloud import kms + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the key version name. + key_version_name = client.crypto_key_version_path(project_id, location_id, key_ring_id, key_id, version_id) + + key_version = { + 'name': key_version_name, + 'state': kms.CryptoKeyVersion.CryptoKeyVersionState.ENABLED + } + + # Build the update mask. + update_mask = {'paths': ['state']} + + # Call the API. + enabled_version = client.update_crypto_key_version(request={'crypto_key_version': key_version, 'update_mask': update_mask}) + print('Enabled key version: {}'.format(enabled_version.name)) + return enabled_version +# [END kms_enable_key_version] diff --git a/kms/snippets/encrypt_asymmetric.py b/kms/snippets/encrypt_asymmetric.py new file mode 100644 index 000000000000..065c7e9bf70d --- /dev/null +++ b/kms/snippets/encrypt_asymmetric.py @@ -0,0 +1,69 @@ +# Copyright 2020 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 + + +# [START kms_encrypt_asymmetric] +def encrypt_asymmetric(project_id, location_id, key_ring_id, key_id, version_id, plaintext): + """ + Encrypt plaintext using the public key portion of an asymmetric key. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (string): ID of the key to use (e.g. 'my-key'). + version_id (string): ID of the key version to use (e.g. '1'). + plaintext (string): message to encrypt + + Returns: + bytes: Encrypted ciphertext. + + """ + + # Import the client library. + from google.cloud import kms + + # Import base64 for printing the ciphertext. + import base64 + + # Import cryptographic helpers from the cryptography package. + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import hashes, serialization + from cryptography.hazmat.primitives.asymmetric import padding + + # Convert the plaintext to bytes. + plaintext_bytes = plaintext.encode('utf-8') + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the key version name. + key_version_name = client.crypto_key_version_path(project_id, location_id, key_ring_id, key_id, version_id) + + # Get the public key. + public_key = client.get_public_key(request={'name': key_version_name}) + + # Extract and parse the public key as a PEM-encoded RSA key. + pem = public_key.pem.encode('utf-8') + rsa_key = serialization.load_pem_public_key(pem, default_backend()) + + # Construct the padding. Note that the padding differs based on key choice. + sha256 = hashes.SHA256() + mgf = padding.MGF1(algorithm=sha256) + pad = padding.OAEP(mgf=mgf, algorithm=sha256, label=None) + + # Encrypt the data using the public key. + ciphertext = rsa_key.encrypt(plaintext_bytes, pad) + print('Ciphertext: {}'.format(base64.b64encode(ciphertext))) + return ciphertext +# [END kms_encrypt_asymmetric] diff --git a/kms/snippets/encrypt_symmetric.py b/kms/snippets/encrypt_symmetric.py new file mode 100644 index 000000000000..9340133b8b3d --- /dev/null +++ b/kms/snippets/encrypt_symmetric.py @@ -0,0 +1,82 @@ +# Copyright 2020 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 + + +# [START kms_encrypt_symmetric] +def encrypt_symmetric(project_id, location_id, key_ring_id, key_id, plaintext): + """ + Encrypt plaintext using a symmetric key. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (string): ID of the key to use (e.g. 'my-key'). + plaintext (string): message to encrypt + + Returns: + bytes: Encrypted ciphertext. + + """ + + # Import the client library. + from google.cloud import kms + + # Import base64 for printing the ciphertext. + import base64 + + # Convert the plaintext to bytes. + plaintext_bytes = plaintext.encode('utf-8') + + # Optional, but recommended: compute plaintext's CRC32C. + # See crc32c() function defined below. + plaintext_crc32c = crc32c(plaintext_bytes) + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the key name. + key_name = client.crypto_key_path(project_id, location_id, key_ring_id, key_id) + + # Call the API. + encrypt_response = client.encrypt( + request={'name': key_name, 'plaintext': plaintext_bytes, 'plaintext_crc32c': plaintext_crc32c}) + + # Optional, but recommended: perform integrity verification on encrypt_response. + # For more details on ensuring E2E in-transit integrity to and from Cloud KMS visit: + # https://cloud.google.com/kms/docs/data-integrity-guidelines + if not encrypt_response.verified_plaintext_crc32c: + raise Exception('The request sent to the server was corrupted in-transit.') + if not encrypt_response.ciphertext_crc32c == crc32c(encrypt_response.ciphertext): + raise Exception('The response received from the server was corrupted in-transit.') + # End integrity verification + + print('Ciphertext: {}'.format(base64.b64encode(encrypt_response.ciphertext))) + return encrypt_response + + +def crc32c(data): + """ + Calculates the CRC32C checksum of the provided data. + + Args: + data: the bytes over which the checksum should be calculated. + + Returns: + An int representing the CRC32C checksum of the provided bytes. + """ + import crcmod + import six + crc32c_fun = crcmod.predefined.mkPredefinedCrcFun('crc-32c') + return crc32c_fun(six.ensure_binary(data)) +# [END kms_encrypt_symmetric] diff --git a/kms/snippets/generate_random_bytes.py b/kms/snippets/generate_random_bytes.py new file mode 100644 index 000000000000..9b5438315cd6 --- /dev/null +++ b/kms/snippets/generate_random_bytes.py @@ -0,0 +1,49 @@ +# 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 + + +# [START kms_generate_random_bytes] +def generate_random_bytes(project_id, location_id, num_bytes): + """ + Generate random bytes with entropy sourced from the given location. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + num_bytes (integer): number of bytes of random data. + + Returns: + bytes: Encrypted ciphertext. + + """ + + # Import the client library. + from google.cloud import kms + + # Import base64 for encoding the bytes for printing. + import base64 + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the location name. + location_name = client.common_location_path(project_id, location_id) + + # Call the API. + protection_level = kms.ProtectionLevel.HSM + random_bytes_response = client.generate_random_bytes( + request={'location': location_name, 'length_bytes': num_bytes, 'protection_level': protection_level}) + + print('Random bytes: {}'.format(base64.b64encode(random_bytes_response.data))) + return random_bytes_response +# [END kms_generate_random_bytes] diff --git a/kms/snippets/get_key_labels.py b/kms/snippets/get_key_labels.py new file mode 100644 index 000000000000..504dbcaa0c03 --- /dev/null +++ b/kms/snippets/get_key_labels.py @@ -0,0 +1,48 @@ +# Copyright 2020 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 + + +# [START kms_get_key_labels] +def get_key_labels(project_id, location_id, key_ring_id, key_id): + """ + Get a key and its labels. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (string): ID of the key to use (e.g. 'my-key'). + + Returns: + CryptoKey: Cloud KMS key. + + """ + + # Import the client library. + from google.cloud import kms + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the key name. + key_name = client.crypto_key_path(project_id, location_id, key_ring_id, key_id) + + # Call the API. + key = client.get_crypto_key(request={'name': key_name}) + + # Example of iterating over labels. + for k, v in key.labels.items(): + print('{} = {}'.format(k, v)) + + return key +# [END kms_get_key_labels] diff --git a/kms/snippets/get_key_version_attestation.py b/kms/snippets/get_key_version_attestation.py new file mode 100644 index 000000000000..569cf2045bba --- /dev/null +++ b/kms/snippets/get_key_version_attestation.py @@ -0,0 +1,56 @@ +# Copyright 2020 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 + + +# [START kms_get_key_version_attestation] +def get_key_version_attestation(project_id, location_id, key_ring_id, key_id, version_id): + """ + Get an HSM-backend key's attestation. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (string): ID of the key to use (e.g. 'my-key'). + version_id (string): ID of the version to use (e.g. '1'). + + Returns: + Attestation: Cloud KMS key attestation. + + """ + + # Import the client library. + from google.cloud import kms + + # Import base64 for printing the attestation. + import base64 + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the key version name. + key_version_name = client.crypto_key_version_path(project_id, location_id, key_ring_id, key_id, version_id) + + # Call the API. + version = client.get_crypto_key_version(request={'name': key_version_name}) + + # Only HSM keys have an attestation. For other key types, the attestion + # will be None. + attestation = version.attestation + if not attestation: + raise 'no attestation - attestations only exist on HSM keys' + + encoded_attestation = base64.b64encode(attestation.content) + print('Got key attestation: {}'.format(encoded_attestation)) + return attestation +# [END kms_get_key_version_attestation] diff --git a/kms/snippets/get_public_key.py b/kms/snippets/get_public_key.py new file mode 100644 index 000000000000..e265f7fd67b3 --- /dev/null +++ b/kms/snippets/get_public_key.py @@ -0,0 +1,70 @@ +# Copyright 2020 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 + + +# [START kms_get_public_key] +def get_public_key(project_id, location_id, key_ring_id, key_id, version_id): + """ + Get the public key for an asymmetric key. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (string): ID of the key to use (e.g. 'my-key'). + version_id (string): ID of the key to use (e.g. '1'). + + Returns: + PublicKey: Cloud KMS public key response. + + """ + + # Import the client library. + from google.cloud import kms + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the key version name. + key_version_name = client.crypto_key_version_path(project_id, location_id, key_ring_id, key_id, version_id) + + # Call the API. + public_key = client.get_public_key(request={'name': key_version_name}) + + # Optional, but recommended: perform integrity verification on public_key. + # For more details on ensuring E2E in-transit integrity to and from Cloud KMS visit: + # https://cloud.google.com/kms/docs/data-integrity-guidelines + if not public_key.name == key_version_name: + raise Exception('The request sent to the server was corrupted in-transit.') + # See crc32c() function defined below. + if not public_key.pem_crc32c == crc32c(public_key.pem): + raise Exception('The response received from the server was corrupted in-transit.') + # End integrity verification + + print('Public key: {}'.format(public_key.pem)) + return public_key + + +def crc32c(data): + """ + Calculates the CRC32C checksum of the provided data. + Args: + data: the bytes over which the checksum should be calculated. + Returns: + An int representing the CRC32C checksum of the provided bytes. + """ + import crcmod + import six + crc32c_fun = crcmod.predefined.mkPredefinedCrcFun('crc-32c') + return crc32c_fun(six.ensure_binary(data)) +# [END kms_get_public_key] diff --git a/kms/snippets/iam_add_member.py b/kms/snippets/iam_add_member.py new file mode 100644 index 000000000000..7847aa4a7079 --- /dev/null +++ b/kms/snippets/iam_add_member.py @@ -0,0 +1,61 @@ +# Copyright 2020 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 + + +# [START kms_iam_add_member] +def iam_add_member(project_id, location_id, key_ring_id, key_id, member): + """ + Add an IAM member to a resource. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (string): ID of the key to use (e.g. 'my-key'). + member (string): Member to add (e.g. 'user:foo@example.com') + + Returns: + Policy: Updated Cloud IAM policy. + + """ + + # Import the client library. + from google.cloud import kms + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the resource name. + resource_name = client.crypto_key_path(project_id, location_id, key_ring_id, key_id) + + # The resource name could also be a key ring. + # resource_name = client.key_ring_path(project_id, location_id, key_ring_id); + + # Get the current policy. + policy = client.get_iam_policy(request={'resource': resource_name}) + + # Add the member to the policy. + policy.bindings.add( + role='roles/cloudkms.cryptoKeyEncrypterDecrypter', + members=[member]) + + # Save the updated IAM policy. + request = { + 'resource': resource_name, + 'policy': policy + } + + updated_policy = client.set_iam_policy(request=request) + print('Added {} to {}'.format(member, resource_name)) + return updated_policy +# [END kms_iam_add_member] diff --git a/kms/snippets/iam_get_policy.py b/kms/snippets/iam_get_policy.py new file mode 100644 index 000000000000..96ae743ebb5a --- /dev/null +++ b/kms/snippets/iam_get_policy.py @@ -0,0 +1,54 @@ +# Copyright 2020 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 + + +# [START kms_iam_get_policy] +def iam_get_policy(project_id, location_id, key_ring_id, key_id): + """ + Get the IAM policy for a resource. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (string): ID of the key to use (e.g. 'my-key'). + + Returns: + Policy: Cloud IAM policy. + + """ + + # Import the client library. + from google.cloud import kms + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the resource name. + resource_name = client.crypto_key_path(project_id, location_id, key_ring_id, key_id) + + # The resource name could also be a key ring. + # resource_name = client.key_ring_path(project_id, location_id, key_ring_id); + + # Get the current policy. + policy = client.get_iam_policy(request={'resource': resource_name}) + + # Print the policy + print('IAM policy for {}'.format(resource_name)) + for binding in policy.bindings: + print(binding.role) + for member in binding.members: + print('- {}'.format(member)) + + return policy +# [END kms_iam_get_policy] diff --git a/kms/snippets/iam_remove_member.py b/kms/snippets/iam_remove_member.py new file mode 100644 index 000000000000..890a027d9e54 --- /dev/null +++ b/kms/snippets/iam_remove_member.py @@ -0,0 +1,61 @@ +# Copyright 2020 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 + + +# [START kms_iam_remove_member] +def iam_remove_member(project_id, location_id, key_ring_id, key_id, member): + """ + Remove an IAM member from a resource. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (string): ID of the key to use (e.g. 'my-key'). + member (string): Member to remove (e.g. 'user:foo@example.com') + + Returns: + Policy: Updated Cloud IAM policy. + + """ + + # Import the client library. + from google.cloud import kms + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the resource name. + resource_name = client.crypto_key_path(project_id, location_id, key_ring_id, key_id) + + # The resource name could also be a key ring. + # resource_name = client.key_ring_path(project_id, location_id, key_ring_id); + + # Get the current policy. + policy = client.get_iam_policy(request={'resource': resource_name}) + + # Remove the member from the policy. + for binding in policy.bindings: + if binding.role == 'roles/cloudkms.cryptoKeyEncrypterDecrypter': + if member in binding.members: + binding.members.remove(member) + + # Save the updated IAM policy. + request = { + 'resource': resource_name, + 'policy': policy + } + updated_policy = client.set_iam_policy(request=request) + print('Removed {} from {}'.format(member, resource_name)) + return updated_policy +# [END kms_iam_remove_member] diff --git a/kms/snippets/import_manually_wrapped_key.py b/kms/snippets/import_manually_wrapped_key.py new file mode 100644 index 000000000000..fec269e00db0 --- /dev/null +++ b/kms/snippets/import_manually_wrapped_key.py @@ -0,0 +1,81 @@ +# Copyright 2020 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 + + +# [START kms_import_manually_wrapped_key] +def import_manually_wrapped_key(project_id, location_id, key_ring_id, crypto_key_id, import_job_id): + """ + Generates and imports local key material to Cloud KMS. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + crypto_key_id (string): ID of the key to import (e.g. 'my-asymmetric-signing-key'). + import_job_id (string): ID of the import job (e.g. 'my-import-job'). + """ + + # Import the client library and Python standard cryptographic libraries. + import os + from cryptography.hazmat import backends + from cryptography.hazmat.primitives import hashes, keywrap, serialization + from cryptography.hazmat.primitives.asymmetric import ec, padding + from google.cloud import kms + + # Generate some key material in Python and format it in PKCS #8 DER as + # required by Google Cloud KMS. + key = ec.generate_private_key(ec.SECP256R1, backends.default_backend()) + formatted_key = key.private_bytes( + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption()) + + print('Generated key bytes: {}'.format(formatted_key)) + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Retrieve the fully-qualified crypto_key and import_job string. + crypto_key_name = client.crypto_key_path( + project_id, location_id, key_ring_id, crypto_key_id) + import_job_name = client.import_job_path( + project_id, location_id, key_ring_id, import_job_id) + + # Generate a temporary 32-byte key for AES-KWP and wrap the key material. + kwp_key = os.urandom(32) + wrapped_target_key = keywrap.aes_key_wrap_with_padding( + kwp_key, formatted_key, backends.default_backend()) + + # Retrieve the public key from the import job. + import_job = client.get_import_job(name=import_job_name) + import_job_pub = serialization.load_pem_public_key( + bytes(import_job.public_key.pem, 'UTF-8'), backends.default_backend()) + + # Wrap the KWP key using the import job key. + wrapped_kwp_key = import_job_pub.encrypt( + kwp_key, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA1(), + label=None)) + + # Import the wrapped key material. + client.import_crypto_key_version({ + "parent": crypto_key_name, + "import_job": import_job_name, + "algorithm": kms.CryptoKeyVersion.CryptoKeyVersionAlgorithm.EC_SIGN_P256_SHA256, + "rsa_aes_wrapped_key": wrapped_kwp_key + wrapped_target_key, + }) + + print('Imported: {}'.format(import_job.name)) +# [END kms_import_manually_wrapped_key] diff --git a/kms/snippets/noxfile_config.py b/kms/snippets/noxfile_config.py new file mode 100644 index 000000000000..34d0d0b1bb8a --- /dev/null +++ b/kms/snippets/noxfile_config.py @@ -0,0 +1,42 @@ +# 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. + +# Default TEST_CONFIG_OVERRIDE for python repos. + +# You can copy this file into your directory, then it will be imported from +# the noxfile.py. + +# The source of truth: +# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/noxfile_config.py + +TEST_CONFIG_OVERRIDE = { + # You can opt out from the test for specific Python versions. + "ignored_versions": ["2.7"], + # Old samples are opted out of enforcing Python type hints + # All new samples should feature them + "enforce_type_hints": False, + # An envvar key for determining the project id to use. Change it + # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a + # build specific Cloud project. You can also use your own string + # to use your own Cloud project. + "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", + # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', + # If you need to use a specific version of pip, + # change pip_version_override to the string representation + # of the version number, for example, "20.2.4" + "pip_version_override": None, + # A dictionary you want to inject into your test. Don't put any + # secrets here. These values will override predefined values. + "envs": {}, +} diff --git a/kms/snippets/quickstart.py b/kms/snippets/quickstart.py new file mode 100644 index 000000000000..6b24d643f5e0 --- /dev/null +++ b/kms/snippets/quickstart.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python + +# Copyright 2017 Google, Inc +# +# 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 + +import argparse + + +# [START kms_quickstart] +def quickstart(project_id, location_id): + # Import the client library. + from google.cloud import kms + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the parent location name. + location_name = f'projects/{project_id}/locations/{location_id}' + + # Call the API. + key_rings = client.list_key_rings(request={'parent': location_name}) + + # Example of iterating over key rings. + for key_ring in key_rings: + print(key_ring.name) + + return key_rings +# [END kms_quickstart] + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument('project_id', help='id of the GCP project') + parser.add_argument('location_id', help='id of the KMS location') + args = parser.parse_args() + + quickstart(args.project_id, args.location_id) diff --git a/kms/snippets/requirements-test.txt b/kms/snippets/requirements-test.txt new file mode 100644 index 000000000000..49780e035690 --- /dev/null +++ b/kms/snippets/requirements-test.txt @@ -0,0 +1 @@ +pytest==7.2.0 diff --git a/kms/snippets/requirements.txt b/kms/snippets/requirements.txt new file mode 100644 index 000000000000..2dbb88fd37d0 --- /dev/null +++ b/kms/snippets/requirements.txt @@ -0,0 +1,3 @@ +google-cloud-kms==2.12.3 +cryptography==38.0.2 +crcmod==1.7 diff --git a/kms/snippets/restore_key_version.py b/kms/snippets/restore_key_version.py new file mode 100644 index 000000000000..c65456b2dff2 --- /dev/null +++ b/kms/snippets/restore_key_version.py @@ -0,0 +1,45 @@ +# Copyright 2020 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 + + +# [START kms_restore_key_version] +def restore_key_version(project_id, location_id, key_ring_id, key_id, version_id): + """ + Restore a key version scheduled for destruction. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (string): ID of the key to use (e.g. 'my-key'). + version_id (string): ID of the version to use (e.g. '1'). + + Returns: + CryptoKeyVersion: Restored Cloud KMS key version. + + """ + + # Import the client library. + from google.cloud import kms + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the key version name. + key_version_name = client.crypto_key_version_path(project_id, location_id, key_ring_id, key_id, version_id) + + # Call the API. + restored_version = client.restore_crypto_key_version(request={'name': key_version_name}) + print('Restored key version: {}'.format(restored_version.name)) + return restored_version +# [END kms_restore_key_version] diff --git a/kms/snippets/sign_asymmetric.py b/kms/snippets/sign_asymmetric.py new file mode 100644 index 000000000000..6b8baa7a7747 --- /dev/null +++ b/kms/snippets/sign_asymmetric.py @@ -0,0 +1,94 @@ +# Copyright 2020 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 + + +# [START kms_sign_asymmetric] +def sign_asymmetric(project_id, location_id, key_ring_id, key_id, version_id, message): + """ + Sign a message using the public key part of an asymmetric key. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (string): ID of the key to use (e.g. 'my-key'). + version_id (string): Version to use (e.g. '1'). + message (string): Message to sign. + + Returns: + AsymmetricSignResponse: Signature. + """ + + # Import the client library. + from google.cloud import kms + + # Import base64 for printing the ciphertext. + import base64 + + # Import hashlib for calculating hashes. + import hashlib + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the key version name. + key_version_name = client.crypto_key_version_path(project_id, location_id, key_ring_id, key_id, version_id) + + # Convert the message to bytes. + message_bytes = message.encode('utf-8') + + # Calculate the hash. + hash_ = hashlib.sha256(message_bytes).digest() + + # Build the digest. + # + # Note: Key algorithms will require a varying hash function. For + # example, EC_SIGN_P384_SHA384 requires SHA-384. + digest = {'sha256': hash_} + + # Optional, but recommended: compute digest's CRC32C. + # See crc32c() function defined below. + digest_crc32c = crc32c(hash_) + + # Call the API + sign_response = client.asymmetric_sign( + request={'name': key_version_name, 'digest': digest, 'digest_crc32c': digest_crc32c}) + + # Optional, but recommended: perform integrity verification on sign_response. + # For more details on ensuring E2E in-transit integrity to and from Cloud KMS visit: + # https://cloud.google.com/kms/docs/data-integrity-guidelines + if not sign_response.verified_digest_crc32c: + raise Exception('The request sent to the server was corrupted in-transit.') + if not sign_response.name == key_version_name: + raise Exception('The request sent to the server was corrupted in-transit.') + if not sign_response.signature_crc32c == crc32c(sign_response.signature): + raise Exception('The response received from the server was corrupted in-transit.') + # End integrity verification + + print('Signature: {}'.format(base64.b64encode(sign_response.signature))) + return sign_response + + +def crc32c(data): + """ + Calculates the CRC32C checksum of the provided data. + Args: + data: the bytes over which the checksum should be calculated. + Returns: + An int representing the CRC32C checksum of the provided bytes. + """ + import crcmod + import six + crc32c_fun = crcmod.predefined.mkPredefinedCrcFun('crc-32c') + return crc32c_fun(six.ensure_binary(data)) +# [END kms_sign_asymmetric] diff --git a/kms/snippets/sign_mac.py b/kms/snippets/sign_mac.py new file mode 100644 index 000000000000..fa054b42abe5 --- /dev/null +++ b/kms/snippets/sign_mac.py @@ -0,0 +1,53 @@ +# 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 + + +# [START kms_sign_mac] +def sign_mac(project_id, location_id, key_ring_id, key_id, version_id, data): + """ + Sign a message using the public key part of an asymmetric key. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (string): ID of the key to use (e.g. 'my-key'). + version_id (string): Version to use (e.g. '1'). + data (string): Data to sign. + + Returns: + MacSignResponse: Signature. + """ + + # Import the client library. + from google.cloud import kms + + # Import base64 for printing the ciphertext. + import base64 + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the key version name. + key_version_name = client.crypto_key_version_path(project_id, location_id, key_ring_id, key_id, version_id) + + # Convert the message to bytes. + data_bytes = data.encode('utf-8') + + # Call the API + sign_response = client.mac_sign( + request={'name': key_version_name, 'data': data_bytes}) + + print('Signature: {}'.format(base64.b64encode(sign_response.mac))) + return sign_response +# [END kms_sign_mac] diff --git a/kms/snippets/snippets_test.py b/kms/snippets/snippets_test.py new file mode 100644 index 000000000000..048f6f8f9cec --- /dev/null +++ b/kms/snippets/snippets_test.py @@ -0,0 +1,520 @@ +# Copyright 2017 Google, Inc +# +# 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 + +import datetime +import hashlib +import os +import time +import uuid + +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import padding, utils +from google.cloud import kms +import pytest + +from check_state_import_job import check_state_import_job +from check_state_imported_key import check_state_imported_key +from create_import_job import create_import_job +from create_key_asymmetric_decrypt import create_key_asymmetric_decrypt +from create_key_asymmetric_sign import create_key_asymmetric_sign +from create_key_for_import import create_key_for_import +from create_key_hsm import create_key_hsm +from create_key_labels import create_key_labels +from create_key_mac import create_key_mac +from create_key_ring import create_key_ring +from create_key_rotation_schedule import create_key_rotation_schedule +from create_key_symmetric_encrypt_decrypt import create_key_symmetric_encrypt_decrypt +from create_key_version import create_key_version +from decrypt_asymmetric import decrypt_asymmetric +from decrypt_symmetric import decrypt_symmetric +from destroy_key_version import destroy_key_version +from disable_key_version import disable_key_version +from enable_key_version import enable_key_version +from encrypt_asymmetric import encrypt_asymmetric +from encrypt_symmetric import encrypt_symmetric +from generate_random_bytes import generate_random_bytes +from get_key_labels import get_key_labels +from get_key_version_attestation import get_key_version_attestation +from get_public_key import get_public_key +from iam_add_member import iam_add_member +from iam_get_policy import iam_get_policy +from iam_remove_member import iam_remove_member +from import_manually_wrapped_key import import_manually_wrapped_key +from quickstart import quickstart +from restore_key_version import restore_key_version +from sign_asymmetric import sign_asymmetric +from sign_mac import sign_mac +from update_key_add_rotation import update_key_add_rotation +from update_key_remove_labels import update_key_remove_labels +from update_key_remove_rotation import update_key_remove_rotation +from update_key_set_primary import update_key_set_primary +from update_key_update_labels import update_key_update_labels +from verify_asymmetric_ec import verify_asymmetric_ec +from verify_asymmetric_rsa import verify_asymmetric_rsa +from verify_mac import verify_mac + + +@pytest.fixture(scope="module") +def client(): + return kms.KeyManagementServiceClient() + + +@pytest.fixture(scope="module") +def project_id(): + return os.environ['GOOGLE_CLOUD_PROJECT'] + + +@pytest.fixture(scope="module") +def location_id(): + return "us-east1" + + +@pytest.fixture(scope="module") +def import_job_id(): + return "my-import-job" + + +@pytest.fixture(scope="module") +def import_tests_key_id(): + return "my-import-job-ec-key" + + +@pytest.fixture(scope="module") +def key_ring_id(client, project_id, location_id): + location_name = f"projects/{project_id}/locations/{location_id}" + key_ring_id = '{}'.format(uuid.uuid4()) + key_ring = client.create_key_ring(request={'parent': location_name, 'key_ring_id': key_ring_id, 'key_ring': {}}) + + yield key_ring_id + + for key in client.list_crypto_keys(request={'parent': key_ring.name}): + if key.rotation_period or key.next_rotation_time: + updated_key = {'name': key.name} + update_mask = {'paths': ['rotation_period', 'next_rotation_time']} + client.update_crypto_key(request={'crypto_key': updated_key, 'update_mask': update_mask}) + + f = 'state != DESTROYED AND state != DESTROY_SCHEDULED' + for version in client.list_crypto_key_versions(request={'parent': key.name, 'filter': f}): + client.destroy_crypto_key_version(request={'name': version.name}) + + +@pytest.fixture(scope="module") +def asymmetric_decrypt_key_id(client, project_id, location_id, key_ring_id): + key_ring_name = client.key_ring_path(project_id, location_id, key_ring_id) + key_id = '{}'.format(uuid.uuid4()) + key = client.create_crypto_key(request={'parent': key_ring_name, 'crypto_key_id': key_id, 'crypto_key': { + 'purpose': kms.CryptoKey.CryptoKeyPurpose.ASYMMETRIC_DECRYPT, + 'version_template': { + 'algorithm': kms.CryptoKeyVersion.CryptoKeyVersionAlgorithm.RSA_DECRYPT_OAEP_2048_SHA256 + }, + 'labels': {'foo': 'bar', 'zip': 'zap'} + }}) + wait_for_ready(client, '{}/cryptoKeyVersions/1'.format(key.name)) + return key_id + + +@pytest.fixture(scope="module") +def asymmetric_sign_ec_key_id(client, project_id, location_id, key_ring_id): + key_ring_name = client.key_ring_path(project_id, location_id, key_ring_id) + key_id = '{}'.format(uuid.uuid4()) + key = client.create_crypto_key(request={'parent': key_ring_name, 'crypto_key_id': key_id, 'crypto_key': { + 'purpose': kms.CryptoKey.CryptoKeyPurpose.ASYMMETRIC_SIGN, + 'version_template': { + 'algorithm': kms.CryptoKeyVersion.CryptoKeyVersionAlgorithm.EC_SIGN_P256_SHA256 + }, + 'labels': {'foo': 'bar', 'zip': 'zap'} + }}) + wait_for_ready(client, '{}/cryptoKeyVersions/1'.format(key.name)) + return key_id + + +@pytest.fixture(scope="module") +def asymmetric_sign_rsa_key_id(client, project_id, location_id, key_ring_id): + key_ring_name = client.key_ring_path(project_id, location_id, key_ring_id) + key_id = '{}'.format(uuid.uuid4()) + key = client.create_crypto_key(request={'parent': key_ring_name, 'crypto_key_id': key_id, 'crypto_key': { + 'purpose': kms.CryptoKey.CryptoKeyPurpose.ASYMMETRIC_SIGN, + 'version_template': { + 'algorithm': kms.CryptoKeyVersion.CryptoKeyVersionAlgorithm.RSA_SIGN_PKCS1_2048_SHA256 + }, + 'labels': {'foo': 'bar', 'zip': 'zap'} + }}) + wait_for_ready(client, '{}/cryptoKeyVersions/1'.format(key.name)) + return key_id + + +@pytest.fixture(scope="module") +def hsm_key_id(client, project_id, location_id, key_ring_id): + key_ring_name = client.key_ring_path(project_id, location_id, key_ring_id) + key_id = '{}'.format(uuid.uuid4()) + key = client.create_crypto_key(request={'parent': key_ring_name, 'crypto_key_id': key_id, 'crypto_key': { + 'purpose': kms.CryptoKey.CryptoKeyPurpose.ENCRYPT_DECRYPT, + 'version_template': { + 'algorithm': kms.CryptoKeyVersion.CryptoKeyVersionAlgorithm.GOOGLE_SYMMETRIC_ENCRYPTION, + 'protection_level': kms.ProtectionLevel.HSM + }, + 'labels': {'foo': 'bar', 'zip': 'zap'} + }}) + wait_for_ready(client, '{}/cryptoKeyVersions/1'.format(key.name)) + return key_id + + +@pytest.fixture(scope="module") +def hmac_key_id(client, project_id, location_id, key_ring_id): + key_ring_name = client.key_ring_path(project_id, location_id, key_ring_id) + key_id = '{}'.format(uuid.uuid4()) + key = client.create_crypto_key(request={'parent': key_ring_name, 'crypto_key_id': key_id, 'crypto_key': { + 'purpose': kms.CryptoKey.CryptoKeyPurpose.MAC, + 'version_template': { + 'algorithm': kms.CryptoKeyVersion.CryptoKeyVersionAlgorithm.HMAC_SHA256, + 'protection_level': kms.ProtectionLevel.HSM + }, + 'labels': {'foo': 'bar', 'zip': 'zap'} + }}) + wait_for_ready(client, '{}/cryptoKeyVersions/1'.format(key.name)) + return key_id + + +@pytest.fixture(scope="module") +def symmetric_key_id(client, project_id, location_id, key_ring_id): + key_ring_name = client.key_ring_path(project_id, location_id, key_ring_id) + key_id = '{}'.format(uuid.uuid4()) + key = client.create_crypto_key(request={'parent': key_ring_name, 'crypto_key_id': key_id, 'crypto_key': { + 'purpose': kms.CryptoKey.CryptoKeyPurpose.ENCRYPT_DECRYPT, + 'version_template': { + 'algorithm': kms.CryptoKeyVersion.CryptoKeyVersionAlgorithm.GOOGLE_SYMMETRIC_ENCRYPTION + }, + 'labels': {'foo': 'bar', 'zip': 'zap'} + }}) + wait_for_ready(client, '{}/cryptoKeyVersions/1'.format(key.name)) + return key_id + + +def wait_for_ready(client, key_version_name): + for i in range(5): + key_version = client.get_crypto_key_version(request={'name': key_version_name}) + if key_version.state == kms.CryptoKeyVersion.CryptoKeyVersionState.ENABLED: + return + time.sleep(0.1*(i**2)) + pytest.fail('{} not ready'.format(key_version_name)) + + +def test_create_import_job(project_id, location_id, key_ring_id, import_job_id, capsys): + create_import_job(project_id, location_id, key_ring_id, import_job_id) + out, _ = capsys.readouterr() + assert "Created import job" in out + + +def test_check_state_import_job(project_id, location_id, key_ring_id, import_job_id, capsys): + check_state_import_job(project_id, location_id, key_ring_id, import_job_id) + out, _ = capsys.readouterr() + assert "Current state" in out + + +def test_check_state_imported_key(project_id, location_id, key_ring_id, import_job_id, capsys): + check_state_imported_key(project_id, location_id, key_ring_id, import_job_id) + out, _ = capsys.readouterr() + assert "Current state" in out + + +def test_create_key_asymmetric_decrypt(project_id, location_id, key_ring_id): + key_id = '{}'.format(uuid.uuid4()) + key = create_key_asymmetric_decrypt(project_id, location_id, key_ring_id, key_id) + assert key.purpose == kms.CryptoKey.CryptoKeyPurpose.ASYMMETRIC_DECRYPT + assert key.version_template.algorithm == kms.CryptoKeyVersion.CryptoKeyVersionAlgorithm.RSA_DECRYPT_OAEP_2048_SHA256 + + +def test_create_key_asymmetric_sign(project_id, location_id, key_ring_id): + key_id = '{}'.format(uuid.uuid4()) + key = create_key_asymmetric_sign(project_id, location_id, key_ring_id, key_id) + assert key.purpose == kms.CryptoKey.CryptoKeyPurpose.ASYMMETRIC_SIGN + assert key.version_template.algorithm == kms.CryptoKeyVersion.CryptoKeyVersionAlgorithm.RSA_SIGN_PKCS1_2048_SHA256 + + +def test_create_key_for_import(project_id, location_id, key_ring_id, import_tests_key_id, capsys): + create_key_for_import(project_id, location_id, key_ring_id, import_tests_key_id) + out, _ = capsys.readouterr() + assert "Created hsm key" in out + + +def test_create_key_hsm(project_id, location_id, key_ring_id): + key_id = '{}'.format(uuid.uuid4()) + key = create_key_hsm(project_id, location_id, key_ring_id, key_id) + assert key.purpose == kms.CryptoKey.CryptoKeyPurpose.ENCRYPT_DECRYPT + assert key.version_template.algorithm == kms.CryptoKeyVersion.CryptoKeyVersionAlgorithm.GOOGLE_SYMMETRIC_ENCRYPTION + assert key.version_template.protection_level == kms.ProtectionLevel.HSM + + +def test_create_key_labels(project_id, location_id, key_ring_id): + key_id = '{}'.format(uuid.uuid4()) + key = create_key_labels(project_id, location_id, key_ring_id, key_id) + assert key.purpose == kms.CryptoKey.CryptoKeyPurpose.ENCRYPT_DECRYPT + assert key.version_template.algorithm == kms.CryptoKeyVersion.CryptoKeyVersionAlgorithm.GOOGLE_SYMMETRIC_ENCRYPTION + assert key.labels == {'team': 'alpha', 'cost_center': 'cc1234'} + + +def test_create_key_mac(project_id, location_id, key_ring_id): + key_id = '{}'.format(uuid.uuid4()) + key = create_key_mac(project_id, location_id, key_ring_id, key_id) + assert key.purpose == kms.CryptoKey.CryptoKeyPurpose.MAC + assert key.version_template.algorithm == kms.CryptoKeyVersion.CryptoKeyVersionAlgorithm.HMAC_SHA256 + + +def test_create_key_ring(project_id, location_id): + key_ring_id = '{}'.format(uuid.uuid4()) + key_ring = create_key_ring(project_id, location_id, key_ring_id) + assert key_ring + + +def test_create_key_rotation_schedule(project_id, location_id, key_ring_id): + key_id = '{}'.format(uuid.uuid4()) + key = create_key_rotation_schedule(project_id, location_id, key_ring_id, key_id) + assert key.rotation_period == datetime.timedelta(seconds=60*60*24*30) + assert key.next_rotation_time + + +def test_create_key_symmetric_encrypt_decrypt(project_id, location_id, key_ring_id): + key_id = '{}'.format(uuid.uuid4()) + key = create_key_symmetric_encrypt_decrypt(project_id, location_id, key_ring_id, key_id) + assert key.purpose == kms.CryptoKey.CryptoKeyPurpose.ENCRYPT_DECRYPT + assert key.version_template.algorithm == kms.CryptoKeyVersion.CryptoKeyVersionAlgorithm.GOOGLE_SYMMETRIC_ENCRYPTION + + +def test_create_key_version(project_id, location_id, key_ring_id, symmetric_key_id): + version = create_key_version(project_id, location_id, key_ring_id, symmetric_key_id) + assert version + + +def test_decrypt_asymmetric(client, project_id, location_id, key_ring_id, asymmetric_decrypt_key_id): + message = 'my message'.encode('utf-8') + + key_version_name = client.crypto_key_version_path(project_id, location_id, key_ring_id, asymmetric_decrypt_key_id, '1') + public_key = client.get_public_key(request={'name': key_version_name}) + + pem = public_key.pem.encode('utf-8') + rsa_key = serialization.load_pem_public_key(pem, default_backend()) + + pad = padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=None) + ciphertext = rsa_key.encrypt(message, pad) + + response = decrypt_asymmetric(project_id, location_id, key_ring_id, asymmetric_decrypt_key_id, '1', ciphertext) + assert response.plaintext == message + + +def test_decrypt_symmetric(client, project_id, location_id, key_ring_id, symmetric_key_id): + plaintext = 'my message'.encode('utf-8') + + key_version_name = client.crypto_key_path(project_id, location_id, key_ring_id, symmetric_key_id) + encrypt_response = client.encrypt(request={'name': key_version_name, 'plaintext': plaintext}) + ciphertext = encrypt_response.ciphertext + + decrypt_response = decrypt_symmetric(project_id, location_id, key_ring_id, symmetric_key_id, ciphertext) + assert decrypt_response.plaintext == plaintext + + +def test_destroy_restore_key_version(client, project_id, location_id, key_ring_id, asymmetric_decrypt_key_id): + key_name = client.crypto_key_path(project_id, location_id, key_ring_id, asymmetric_decrypt_key_id) + version = client.create_crypto_key_version(request={'parent': key_name, 'crypto_key_version': {}}) + version_id = version.name.split('/')[-1] + + wait_for_ready(client, version.name) + + destroyed_version = destroy_key_version(project_id, location_id, key_ring_id, asymmetric_decrypt_key_id, version_id) + assert destroyed_version.state == kms.CryptoKeyVersion.CryptoKeyVersionState.DESTROY_SCHEDULED + + restored_version = restore_key_version(project_id, location_id, key_ring_id, asymmetric_decrypt_key_id, version_id) + assert restored_version.state == kms.CryptoKeyVersion.CryptoKeyVersionState.DISABLED + + +def test_disable_enable_key_version(client, project_id, location_id, key_ring_id, asymmetric_decrypt_key_id): + key_name = client.crypto_key_path(project_id, location_id, key_ring_id, asymmetric_decrypt_key_id) + version = client.create_crypto_key_version(request={'parent': key_name, 'crypto_key_version': {}}) + version_id = version.name.split('/')[-1] + + wait_for_ready(client, version.name) + + disabled_version = disable_key_version(project_id, location_id, key_ring_id, asymmetric_decrypt_key_id, version_id) + assert disabled_version.state == kms.CryptoKeyVersion.CryptoKeyVersionState.DISABLED + + enabled_version = enable_key_version(project_id, location_id, key_ring_id, asymmetric_decrypt_key_id, version_id) + assert enabled_version.state == kms.CryptoKeyVersion.CryptoKeyVersionState.ENABLED + + +def test_encrypt_asymmetric(client, project_id, location_id, key_ring_id, asymmetric_decrypt_key_id): + plaintext = 'my message' + ciphertext = encrypt_asymmetric(project_id, location_id, key_ring_id, asymmetric_decrypt_key_id, '1', plaintext) + + key_version_name = client.crypto_key_version_path(project_id, location_id, key_ring_id, asymmetric_decrypt_key_id, '1') + response = client.asymmetric_decrypt(request={'name': key_version_name, 'ciphertext': ciphertext}) + assert response.plaintext == plaintext.encode('utf-8') + + +def test_encrypt_symmetric(client, project_id, location_id, key_ring_id, symmetric_key_id): + plaintext = 'my message' + encrypt_response = encrypt_symmetric(project_id, location_id, key_ring_id, symmetric_key_id, plaintext) + + key_name = client.crypto_key_path(project_id, location_id, key_ring_id, symmetric_key_id) + decrypt_response = client.decrypt(request={'name': key_name, 'ciphertext': encrypt_response.ciphertext}) + assert decrypt_response.plaintext == plaintext.encode('utf-8') + + +def test_generate_random_bytes(client, project_id, location_id): + generate_random_bytes_response = generate_random_bytes(project_id, location_id, 256) + assert len(generate_random_bytes_response.data) == 256 + + +def test_get_key_labels(project_id, location_id, key_ring_id, symmetric_key_id): + key = get_key_labels(project_id, location_id, key_ring_id, symmetric_key_id) + assert key.labels == {'foo': 'bar', 'zip': 'zap'} + + +def test_get_key_version_attestation(project_id, location_id, key_ring_id, hsm_key_id): + attestation = get_key_version_attestation(project_id, location_id, key_ring_id, hsm_key_id, '1') + assert attestation.format + assert attestation.content + + +def test_get_public_key(project_id, location_id, key_ring_id, asymmetric_decrypt_key_id): + public_key = get_public_key(project_id, location_id, key_ring_id, asymmetric_decrypt_key_id, '1') + assert public_key.pem + + +def test_iam_add_member(project_id, location_id, key_ring_id, symmetric_key_id): + member = 'group:test@google.com' + policy = iam_add_member(project_id, location_id, key_ring_id, symmetric_key_id, member) + assert any(member in b.members for b in policy.bindings) + + +def test_iam_get_policy(project_id, location_id, key_ring_id, symmetric_key_id): + policy = iam_get_policy(project_id, location_id, key_ring_id, symmetric_key_id) + assert policy + + +def test_iam_remove_member(client, project_id, location_id, key_ring_id, asymmetric_sign_rsa_key_id): + resource_name = client.crypto_key_path(project_id, location_id, key_ring_id, asymmetric_sign_rsa_key_id) + + policy = client.get_iam_policy(request={"resource": resource_name}) + policy.bindings.add( + role='roles/cloudkms.cryptoKeyEncrypterDecrypter', + members=['group:test@google.com', 'group:tester@google.com']) + client.set_iam_policy(request={"resource": resource_name, "policy": policy}) + + policy = iam_remove_member(project_id, location_id, key_ring_id, asymmetric_sign_rsa_key_id, 'group:test@google.com') + assert not any('group:test@google.com' in b.members for b in policy.bindings) + assert any('group:tester@google.com' in b.members for b in policy.bindings) + + +def test_import_manually_wrapped_key(project_id, location_id, key_ring_id, import_job_id, import_tests_key_id, capsys): + import_manually_wrapped_key(project_id, location_id, key_ring_id, import_tests_key_id, import_job_id) + out, _ = capsys.readouterr() + assert "Imported" in out + + +def test_sign_asymmetric(client, project_id, location_id, key_ring_id, asymmetric_sign_rsa_key_id): + message = 'my message' + + sign_response = sign_asymmetric(project_id, location_id, key_ring_id, asymmetric_sign_rsa_key_id, '1', message) + assert sign_response.signature + + key_version_name = client.crypto_key_version_path(project_id, location_id, key_ring_id, asymmetric_sign_rsa_key_id, '1') + public_key = client.get_public_key(request={'name': key_version_name}) + pem = public_key.pem.encode('utf-8') + rsa_key = serialization.load_pem_public_key(pem, default_backend()) + hash_ = hashlib.sha256(message.encode('utf-8')).digest() + + try: + sha256 = hashes.SHA256() + pad = padding.PKCS1v15() + rsa_key.verify(sign_response.signature, hash_, pad, utils.Prehashed(sha256)) + except InvalidSignature: + pytest.fail('invalid signature') + + +def test_sign_mac(client, project_id, location_id, key_ring_id, hmac_key_id): + data = 'my data' + + sign_response = sign_mac(project_id, location_id, key_ring_id, hmac_key_id, '1', data) + assert sign_response.mac + + key_version_name = client.crypto_key_version_path(project_id, location_id, key_ring_id, hmac_key_id, '1') + verify_response = client.mac_verify(request={'name': key_version_name, 'data': data.encode('utf-8'), 'mac': sign_response.mac}) + + assert verify_response.success + + +def test_update_key_add_rotation(project_id, location_id, key_ring_id, symmetric_key_id): + key = update_key_add_rotation(project_id, location_id, key_ring_id, symmetric_key_id) + assert key.rotation_period == datetime.timedelta(seconds=60*60*24*30) + assert key.next_rotation_time + + +def test_update_key_remove_labels(project_id, location_id, key_ring_id, symmetric_key_id): + key = update_key_remove_labels(project_id, location_id, key_ring_id, symmetric_key_id) + assert key.labels == {} + + +def test_update_key_remove_rotation(project_id, location_id, key_ring_id, symmetric_key_id): + key = update_key_remove_rotation(project_id, location_id, key_ring_id, symmetric_key_id) + assert not key.rotation_period + assert not key.next_rotation_time + + +def test_update_key_set_primary(project_id, location_id, key_ring_id, symmetric_key_id): + key = update_key_set_primary(project_id, location_id, key_ring_id, symmetric_key_id, '1') + assert '1' in key.primary.name + + +def test_update_key_update_labels(project_id, location_id, key_ring_id, symmetric_key_id): + key = update_key_update_labels(project_id, location_id, key_ring_id, symmetric_key_id) + assert key.labels == {'new_label': 'new_value'} + + +def test_verify_asymmetric_ec(client, project_id, location_id, key_ring_id, asymmetric_sign_ec_key_id): + message = 'my message' + + key_version_name = client.crypto_key_version_path(project_id, location_id, key_ring_id, asymmetric_sign_ec_key_id, '1') + hash_ = hashlib.sha256(message.encode('utf-8')).digest() + sign_response = client.asymmetric_sign(request={'name': key_version_name, 'digest': {'sha256': hash_}}) + + verified = verify_asymmetric_ec(project_id, location_id, key_ring_id, asymmetric_sign_ec_key_id, '1', message, sign_response.signature) + assert verified + + +def test_verify_asymmetric_rsa(client, project_id, location_id, key_ring_id, asymmetric_sign_rsa_key_id): + message = 'my message' + + key_version_name = client.crypto_key_version_path(project_id, location_id, key_ring_id, asymmetric_sign_rsa_key_id, '1') + hash_ = hashlib.sha256(message.encode('utf-8')).digest() + sign_response = client.asymmetric_sign(request={'name': key_version_name, 'digest': {'sha256': hash_}}) + + verified = verify_asymmetric_rsa(project_id, location_id, key_ring_id, asymmetric_sign_rsa_key_id, '1', message, sign_response.signature) + assert verified + + +def test_verify_mac(client, project_id, location_id, key_ring_id, hmac_key_id): + data = 'my data' + + key_version_name = client.crypto_key_version_path(project_id, location_id, key_ring_id, hmac_key_id, '1') + sign_response = client.mac_sign(request={'name': key_version_name, 'data': data.encode('utf-8')}) + + verify_response = verify_mac(project_id, location_id, key_ring_id, hmac_key_id, '1', data, sign_response.mac) + assert verify_response.success + + +def test_quickstart(project_id, location_id): + key_rings = quickstart(project_id, location_id) + assert key_rings diff --git a/kms/snippets/update_key_add_rotation.py b/kms/snippets/update_key_add_rotation.py new file mode 100644 index 000000000000..acc4d95612a9 --- /dev/null +++ b/kms/snippets/update_key_add_rotation.py @@ -0,0 +1,60 @@ +# Copyright 2020 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 + + +# [START kms_update_key_add_rotation_schedule] +def update_key_add_rotation(project_id, location_id, key_ring_id, key_id): + """ + Add a rotation schedule to an existing key. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (string): ID of the key to use (e.g. 'my-key'). + + Returns: + CryptoKey: Updated Cloud KMS key. + + """ + + # Import the client library. + from google.cloud import kms + + # Import time for getting the current time. + import time + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the key name. + key_name = client.crypto_key_path(project_id, location_id, key_ring_id, key_id) + + key = { + 'name': key_name, + 'rotation_period': { + 'seconds': 60*60*24*30 # Rotate the key every 30 days. + }, + 'next_rotation_time': { + 'seconds': int(time.time()) + 60*60*24 # Start the first rotation in 24 hours. + } + } + + # Build the update mask. + update_mask = {'paths': ['rotation_period', 'next_rotation_time']} + + # Call the API. + updated_key = client.update_crypto_key(request={'crypto_key': key, 'update_mask': update_mask}) + print('Updated key: {}'.format(updated_key.name)) + return updated_key +# [END kms_update_key_add_rotation_schedule] diff --git a/kms/snippets/update_key_remove_labels.py b/kms/snippets/update_key_remove_labels.py new file mode 100644 index 000000000000..648db99c5973 --- /dev/null +++ b/kms/snippets/update_key_remove_labels.py @@ -0,0 +1,52 @@ +# Copyright 2020 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 + + +# [START kms_update_key_remove_labels] +def update_key_remove_labels(project_id, location_id, key_ring_id, key_id): + """ + Remove labels from an existing key. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (string): ID of the key to use (e.g. 'my-key'). + + Returns: + CryptoKey: Updated Cloud KMS key. + + """ + + # Import the client library. + from google.cloud import kms + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the key name. + key_name = client.crypto_key_path(project_id, location_id, key_ring_id, key_id) + + key = { + 'name': key_name, + 'labels': [], + } + + # Build the update mask. + update_mask = {'paths': ['labels']} + + # Call the API. + updated_key = client.update_crypto_key(request={'crypto_key': key, 'update_mask': update_mask}) + print('Updated key: {}'.format(updated_key.name)) + return updated_key +# [END kms_update_key_remove_labels] diff --git a/kms/snippets/update_key_remove_rotation.py b/kms/snippets/update_key_remove_rotation.py new file mode 100644 index 000000000000..5dd596a7822b --- /dev/null +++ b/kms/snippets/update_key_remove_rotation.py @@ -0,0 +1,51 @@ +# Copyright 2020 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 + + +# [START kms_update_key_remove_rotation_schedule] +def update_key_remove_rotation(project_id, location_id, key_ring_id, key_id): + """ + Remove a rotation schedule from an existing key. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (string): ID of the key to use (e.g. 'my-key'). + + Returns: + CryptoKey: Updated Cloud KMS key. + + """ + + # Import the client library. + from google.cloud import kms + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the key name. + key_name = client.crypto_key_path(project_id, location_id, key_ring_id, key_id) + + key = { + 'name': key_name + } + + # Build the update mask. + update_mask = {'paths': ['rotation_period', 'next_rotation_time']} + + # Call the API. + updated_key = client.update_crypto_key(request={'crypto_key': key, 'update_mask': update_mask}) + print('Updated key: {}'.format(updated_key.name)) + return updated_key +# [END kms_update_key_remove_rotation_schedule] diff --git a/kms/snippets/update_key_set_primary.py b/kms/snippets/update_key_set_primary.py new file mode 100644 index 000000000000..74ba5cdf845a --- /dev/null +++ b/kms/snippets/update_key_set_primary.py @@ -0,0 +1,45 @@ +# Copyright 2020 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 + + +# [START kms_update_key_set_primary] +def update_key_set_primary(project_id, location_id, key_ring_id, key_id, version_id): + """ + Update the primary version of a key. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (string): ID of the key to use (e.g. 'my-key'). + version_id (string): ID of the key to make primary (e.g. '2'). + + Returns: + CryptoKey: Updated Cloud KMS key. + + """ + + # Import the client library. + from google.cloud import kms + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the key name. + key_name = client.crypto_key_path(project_id, location_id, key_ring_id, key_id) + + # Call the API. + updated_key = client.update_crypto_key_primary_version(request={'name': key_name, 'crypto_key_version_id': version_id}) + print('Updated {} primary to {}'.format(updated_key.name, version_id)) + return updated_key +# [END kms_update_key_set_primary] diff --git a/kms/snippets/update_key_update_labels.py b/kms/snippets/update_key_update_labels.py new file mode 100644 index 000000000000..3f58f15f142c --- /dev/null +++ b/kms/snippets/update_key_update_labels.py @@ -0,0 +1,54 @@ +# Copyright 2020 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 + + +# [START kms_update_key_update_labels] +def update_key_update_labels(project_id, location_id, key_ring_id, key_id): + """ + Update labels on an existing key. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (string): ID of the key to use (e.g. 'my-key'). + + Returns: + CryptoKey: Updated Cloud KMS key. + + """ + + # Import the client library. + from google.cloud import kms + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the key name. + key_name = client.crypto_key_path(project_id, location_id, key_ring_id, key_id) + + key = { + 'name': key_name, + 'labels': { + 'new_label': 'new_value' + } + } + + # Build the update mask. + update_mask = {'paths': ['labels']} + + # Call the API. + updated_key = client.update_crypto_key(request={'crypto_key': key, 'update_mask': update_mask}) + print('Updated key: {}'.format(updated_key.name)) + return updated_key +# [END kms_update_key_update_labels] diff --git a/kms/snippets/verify_asymmetric_ec.py b/kms/snippets/verify_asymmetric_ec.py new file mode 100644 index 000000000000..376f7507890a --- /dev/null +++ b/kms/snippets/verify_asymmetric_ec.py @@ -0,0 +1,72 @@ +# Copyright 2020 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 + + +# [START kms_verify_asymmetric_signature_ec] +def verify_asymmetric_ec(project_id, location_id, key_ring_id, key_id, version_id, message, signature): + """ + Verify the signature of an message signed with an asymmetric EC key. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (string): ID of the key to use (e.g. 'my-key'). + version_id (string): ID of the version to use (e.g. '1'). + message (string): Original message (e.g. 'my message') + signature (bytes): Signature from a sign request. + + Returns: + bool: True if verified, False otherwise + + """ + + # Import the client library. + from google.cloud import kms + + # Import cryptographic helpers from the cryptography package. + from cryptography.exceptions import InvalidSignature + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import hashes, serialization + from cryptography.hazmat.primitives.asymmetric import ec, utils + + # Import hashlib. + import hashlib + + # Convert the message to bytes. + message_bytes = message.encode('utf-8') + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the key version name. + key_version_name = client.crypto_key_version_path(project_id, location_id, key_ring_id, key_id, version_id) + + # Get the public key. + public_key = client.get_public_key(request={'name': key_version_name}) + + # Extract and parse the public key as a PEM-encoded EC key. + pem = public_key.pem.encode('utf-8') + ec_key = serialization.load_pem_public_key(pem, default_backend()) + hash_ = hashlib.sha256(message_bytes).digest() + + # Attempt to verify. + try: + sha256 = hashes.SHA256() + ec_key.verify(signature, hash_, ec.ECDSA(utils.Prehashed(sha256))) + print('Signature verified') + return True + except InvalidSignature: + print('Signature failed to verify') + return False +# [END kms_verify_asymmetric_signature_ec] diff --git a/kms/snippets/verify_asymmetric_rsa.py b/kms/snippets/verify_asymmetric_rsa.py new file mode 100644 index 000000000000..ee7330367d75 --- /dev/null +++ b/kms/snippets/verify_asymmetric_rsa.py @@ -0,0 +1,73 @@ +# Copyright 2020 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 + + +# [START kms_verify_asymmetric_signature_rsa] +def verify_asymmetric_rsa(project_id, location_id, key_ring_id, key_id, version_id, message, signature): + """ + Verify the signature of an message signed with an asymmetric RSA key. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (string): ID of the key to use (e.g. 'my-key'). + version_id (string): ID of the version to use (e.g. '1'). + message (string): Original message (e.g. 'my message') + signature (bytes): Signature from a sign request. + + Returns: + bool: True if verified, False otherwise + + """ + + # Import the client library. + from google.cloud import kms + + # Import cryptographic helpers from the cryptography package. + from cryptography.exceptions import InvalidSignature + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import hashes, serialization + from cryptography.hazmat.primitives.asymmetric import padding, utils + + # Import hashlib. + import hashlib + + # Convert the message to bytes. + message_bytes = message.encode('utf-8') + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the key version name. + key_version_name = client.crypto_key_version_path(project_id, location_id, key_ring_id, key_id, version_id) + + # Get the public key. + public_key = client.get_public_key(request={'name': key_version_name}) + + # Extract and parse the public key as a PEM-encoded RSA key. + pem = public_key.pem.encode('utf-8') + rsa_key = serialization.load_pem_public_key(pem, default_backend()) + hash_ = hashlib.sha256(message_bytes).digest() + + # Attempt to verify. + try: + sha256 = hashes.SHA256() + pad = padding.PKCS1v15() + rsa_key.verify(signature, hash_, pad, utils.Prehashed(sha256)) + print('Signature verified') + return True + except InvalidSignature: + print('Signature failed to verify') + return False +# [END kms_verify_asymmetric_signature_rsa] diff --git a/kms/snippets/verify_mac.py b/kms/snippets/verify_mac.py new file mode 100644 index 000000000000..6fbcc73bdc3b --- /dev/null +++ b/kms/snippets/verify_mac.py @@ -0,0 +1,51 @@ +# 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 + + +# [START kms_verify_mac] +def verify_mac(project_id, location_id, key_ring_id, key_id, version_id, data, signature): + """ + Verify the signature of data from an HMAC key. + + Args: + project_id (string): Google Cloud project ID (e.g. 'my-project'). + location_id (string): Cloud KMS location (e.g. 'us-east1'). + key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring'). + key_id (string): ID of the key to use (e.g. 'my-key'). + version_id (string): Version to use (e.g. '1'). + data (string): Data that was signed. + signature (bytes): Signature bytes. + + Returns: + MacVerifyResponse: Success. + """ + + # Import the client library. + from google.cloud import kms + + # Create the client. + client = kms.KeyManagementServiceClient() + + # Build the key version name. + key_version_name = client.crypto_key_version_path(project_id, location_id, key_ring_id, key_id, version_id) + + # Convert the message to bytes. + data_bytes = data.encode('utf-8') + + # Call the API + verify_response = client.mac_verify( + request={'name': key_version_name, 'data': data_bytes, 'mac': signature}) + + print('Verified: {}'.format(verify_response.success)) + return verify_response +# [END kms_verify_mac]