diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml index 9ee60f7e..a9fcd07c 100644 --- a/.github/.OwlBot.lock.yaml +++ b/.github/.OwlBot.lock.yaml @@ -1,3 +1,3 @@ docker: image: gcr.io/repo-automation-bots/owlbot-python:latest - digest: sha256:aea14a583128771ae8aefa364e1652f3c56070168ef31beb203534222d842b8b + digest: sha256:9743664022bd63a8084be67f144898314c7ca12f0a03e422ac17c733c129d803 diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ab9b422..c282d3a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ [1]: https://pypi.org/project/google-cloud-kms/#history +## [2.6.0](https://www.github.com/googleapis/python-kms/compare/v2.5.0...v2.6.0) (2021-08-30) + + +### Features + +* add support for Key Reimport ([#167](https://www.github.com/googleapis/python-kms/issues/167)) ([1aaaea9](https://www.github.com/googleapis/python-kms/commit/1aaaea9405109a2f226f3d6a9631eb5f110349ab)) + + +### Documentation + +* **kms:** add samples for new hmac and rng apis ([#161](https://www.github.com/googleapis/python-kms/issues/161)) ([558b740](https://www.github.com/googleapis/python-kms/commit/558b740f0491311ebcaf3c62d7117ec15883150a)) + ## [2.5.0](https://www.github.com/googleapis/python-kms/compare/v2.4.3...v2.5.0) (2021-08-07) diff --git a/docs/conf.py b/docs/conf.py index 59392272..ca96662c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -110,6 +110,7 @@ # directories to ignore when looking for source files. exclude_patterns = [ "_build", + "**/.nox/**/*", "samples/AUTHORING_GUIDE.md", "samples/CONTRIBUTING.md", "samples/snippets/README.rst", diff --git a/google/cloud/kms_v1/services/key_management_service/async_client.py b/google/cloud/kms_v1/services/key_management_service/async_client.py index e5593790..d539c874 100644 --- a/google/cloud/kms_v1/services/key_management_service/async_client.py +++ b/google/cloud/kms_v1/services/key_management_service/async_client.py @@ -1390,13 +1390,16 @@ async def import_crypto_key_version( timeout: float = None, metadata: Sequence[Tuple[str, str]] = (), ) -> resources.CryptoKeyVersion: - r"""Imports a new - [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion] into an - existing [CryptoKey][google.cloud.kms.v1.CryptoKey] using the - wrapped key material provided in the request. + r"""Import wrapped key material into a + [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion]. - The version ID will be assigned the next sequential id within - the [CryptoKey][google.cloud.kms.v1.CryptoKey]. + All requests must specify a + [CryptoKey][google.cloud.kms.v1.CryptoKey]. If a + [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion] is + additionally specified in the request, key material will be + reimported into that version. Otherwise, a new version will be + created, and will be assigned the next sequential id within the + [CryptoKey][google.cloud.kms.v1.CryptoKey]. Args: @@ -1938,12 +1941,14 @@ async def destroy_crypto_key_version( Upon calling this method, [CryptoKeyVersion.state][google.cloud.kms.v1.CryptoKeyVersion.state] will be set to - [DESTROY_SCHEDULED][google.cloud.kms.v1.CryptoKeyVersion.CryptoKeyVersionState.DESTROY_SCHEDULED] + [DESTROY_SCHEDULED][google.cloud.kms.v1.CryptoKeyVersion.CryptoKeyVersionState.DESTROY_SCHEDULED], and [destroy_time][google.cloud.kms.v1.CryptoKeyVersion.destroy_time] - will be set to a time 24 hours in the future, at which point the - [state][google.cloud.kms.v1.CryptoKeyVersion.state] will be - changed to + will be set to the time + [destroy_scheduled_duration][google.cloud.kms.v1.CryptoKey.destroy_scheduled_duration] + in the future. At that time, the + [state][google.cloud.kms.v1.CryptoKeyVersion.state] will + automatically change to [DESTROYED][google.cloud.kms.v1.CryptoKeyVersion.CryptoKeyVersionState.DESTROYED], and the key material will be irrevocably destroyed. diff --git a/google/cloud/kms_v1/services/key_management_service/client.py b/google/cloud/kms_v1/services/key_management_service/client.py index c7317018..ca78a45c 100644 --- a/google/cloud/kms_v1/services/key_management_service/client.py +++ b/google/cloud/kms_v1/services/key_management_service/client.py @@ -1550,13 +1550,16 @@ def import_crypto_key_version( timeout: float = None, metadata: Sequence[Tuple[str, str]] = (), ) -> resources.CryptoKeyVersion: - r"""Imports a new - [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion] into an - existing [CryptoKey][google.cloud.kms.v1.CryptoKey] using the - wrapped key material provided in the request. + r"""Import wrapped key material into a + [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion]. - The version ID will be assigned the next sequential id within - the [CryptoKey][google.cloud.kms.v1.CryptoKey]. + All requests must specify a + [CryptoKey][google.cloud.kms.v1.CryptoKey]. If a + [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion] is + additionally specified in the request, key material will be + reimported into that version. Otherwise, a new version will be + created, and will be assigned the next sequential id within the + [CryptoKey][google.cloud.kms.v1.CryptoKey]. Args: @@ -2065,12 +2068,14 @@ def destroy_crypto_key_version( Upon calling this method, [CryptoKeyVersion.state][google.cloud.kms.v1.CryptoKeyVersion.state] will be set to - [DESTROY_SCHEDULED][google.cloud.kms.v1.CryptoKeyVersion.CryptoKeyVersionState.DESTROY_SCHEDULED] + [DESTROY_SCHEDULED][google.cloud.kms.v1.CryptoKeyVersion.CryptoKeyVersionState.DESTROY_SCHEDULED], and [destroy_time][google.cloud.kms.v1.CryptoKeyVersion.destroy_time] - will be set to a time 24 hours in the future, at which point the - [state][google.cloud.kms.v1.CryptoKeyVersion.state] will be - changed to + will be set to the time + [destroy_scheduled_duration][google.cloud.kms.v1.CryptoKey.destroy_scheduled_duration] + in the future. At that time, the + [state][google.cloud.kms.v1.CryptoKeyVersion.state] will + automatically change to [DESTROYED][google.cloud.kms.v1.CryptoKeyVersion.CryptoKeyVersionState.DESTROYED], and the key material will be irrevocably destroyed. diff --git a/google/cloud/kms_v1/services/key_management_service/transports/grpc.py b/google/cloud/kms_v1/services/key_management_service/transports/grpc.py index 04159013..5b72a38a 100644 --- a/google/cloud/kms_v1/services/key_management_service/transports/grpc.py +++ b/google/cloud/kms_v1/services/key_management_service/transports/grpc.py @@ -582,13 +582,16 @@ def import_crypto_key_version( ) -> Callable[[service.ImportCryptoKeyVersionRequest], resources.CryptoKeyVersion]: r"""Return a callable for the import crypto key version method over gRPC. - Imports a new - [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion] into an - existing [CryptoKey][google.cloud.kms.v1.CryptoKey] using the - wrapped key material provided in the request. + Import wrapped key material into a + [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion]. - The version ID will be assigned the next sequential id within - the [CryptoKey][google.cloud.kms.v1.CryptoKey]. + All requests must specify a + [CryptoKey][google.cloud.kms.v1.CryptoKey]. If a + [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion] is + additionally specified in the request, key material will be + reimported into that version. Otherwise, a new version will be + created, and will be assigned the next sequential id within the + [CryptoKey][google.cloud.kms.v1.CryptoKey]. Returns: Callable[[~.ImportCryptoKeyVersionRequest], @@ -750,12 +753,14 @@ def destroy_crypto_key_version( Upon calling this method, [CryptoKeyVersion.state][google.cloud.kms.v1.CryptoKeyVersion.state] will be set to - [DESTROY_SCHEDULED][google.cloud.kms.v1.CryptoKeyVersion.CryptoKeyVersionState.DESTROY_SCHEDULED] + [DESTROY_SCHEDULED][google.cloud.kms.v1.CryptoKeyVersion.CryptoKeyVersionState.DESTROY_SCHEDULED], and [destroy_time][google.cloud.kms.v1.CryptoKeyVersion.destroy_time] - will be set to a time 24 hours in the future, at which point the - [state][google.cloud.kms.v1.CryptoKeyVersion.state] will be - changed to + will be set to the time + [destroy_scheduled_duration][google.cloud.kms.v1.CryptoKey.destroy_scheduled_duration] + in the future. At that time, the + [state][google.cloud.kms.v1.CryptoKeyVersion.state] will + automatically change to [DESTROYED][google.cloud.kms.v1.CryptoKeyVersion.CryptoKeyVersionState.DESTROYED], and the key material will be irrevocably destroyed. diff --git a/google/cloud/kms_v1/services/key_management_service/transports/grpc_asyncio.py b/google/cloud/kms_v1/services/key_management_service/transports/grpc_asyncio.py index 09319509..654f7529 100644 --- a/google/cloud/kms_v1/services/key_management_service/transports/grpc_asyncio.py +++ b/google/cloud/kms_v1/services/key_management_service/transports/grpc_asyncio.py @@ -600,13 +600,16 @@ def import_crypto_key_version( ]: r"""Return a callable for the import crypto key version method over gRPC. - Imports a new - [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion] into an - existing [CryptoKey][google.cloud.kms.v1.CryptoKey] using the - wrapped key material provided in the request. + Import wrapped key material into a + [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion]. - The version ID will be assigned the next sequential id within - the [CryptoKey][google.cloud.kms.v1.CryptoKey]. + All requests must specify a + [CryptoKey][google.cloud.kms.v1.CryptoKey]. If a + [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion] is + additionally specified in the request, key material will be + reimported into that version. Otherwise, a new version will be + created, and will be assigned the next sequential id within the + [CryptoKey][google.cloud.kms.v1.CryptoKey]. Returns: Callable[[~.ImportCryptoKeyVersionRequest], @@ -774,12 +777,14 @@ def destroy_crypto_key_version( Upon calling this method, [CryptoKeyVersion.state][google.cloud.kms.v1.CryptoKeyVersion.state] will be set to - [DESTROY_SCHEDULED][google.cloud.kms.v1.CryptoKeyVersion.CryptoKeyVersionState.DESTROY_SCHEDULED] + [DESTROY_SCHEDULED][google.cloud.kms.v1.CryptoKeyVersion.CryptoKeyVersionState.DESTROY_SCHEDULED], and [destroy_time][google.cloud.kms.v1.CryptoKeyVersion.destroy_time] - will be set to a time 24 hours in the future, at which point the - [state][google.cloud.kms.v1.CryptoKeyVersion.state] will be - changed to + will be set to the time + [destroy_scheduled_duration][google.cloud.kms.v1.CryptoKey.destroy_scheduled_duration] + in the future. At that time, the + [state][google.cloud.kms.v1.CryptoKeyVersion.state] will + automatically change to [DESTROYED][google.cloud.kms.v1.CryptoKeyVersion.CryptoKeyVersionState.DESTROYED], and the key material will be irrevocably destroyed. diff --git a/google/cloud/kms_v1/types/resources.py b/google/cloud/kms_v1/types/resources.py index 294a43fe..6686a3ca 100644 --- a/google/cloud/kms_v1/types/resources.py +++ b/google/cloud/kms_v1/types/resources.py @@ -323,17 +323,17 @@ class CryptoKeyVersion(proto.Message): [DESTROYED][google.cloud.kms.v1.CryptoKeyVersion.CryptoKeyVersionState.DESTROYED]. import_job (str): Output only. The name of the - [ImportJob][google.cloud.kms.v1.ImportJob] used to import - this + [ImportJob][google.cloud.kms.v1.ImportJob] used in the most + recent import of this [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion]. Only present if the underlying key material was imported. import_time (google.protobuf.timestamp_pb2.Timestamp): Output only. The time at which this [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion]'s - key material was imported. + key material was most recently imported. import_failure_reason (str): - Output only. The root cause of an import failure. Only - present if + Output only. The root cause of the most recent import + failure. Only present if [state][google.cloud.kms.v1.CryptoKeyVersion.state] is [IMPORT_FAILED][google.cloud.kms.v1.CryptoKeyVersion.CryptoKeyVersionState.IMPORT_FAILED]. external_protection_level_options (google.cloud.kms_v1.types.ExternalProtectionLevelOptions): @@ -343,6 +343,10 @@ class CryptoKeyVersion(proto.Message): that are specific to the [EXTERNAL][google.cloud.kms.v1.ProtectionLevel.EXTERNAL] protection level. + reimport_eligible (bool): + Output only. Whether or not this key version is eligible for + reimport, by being specified as a target in + [ImportCryptoKeyVersionRequest.crypto_key_version][google.cloud.kms.v1.ImportCryptoKeyVersionRequest.crypto_key_version]. """ class CryptoKeyVersionAlgorithm(proto.Enum): @@ -465,6 +469,7 @@ class CryptoKeyVersionView(proto.Enum): external_protection_level_options = proto.Field( proto.MESSAGE, number=17, message="ExternalProtectionLevelOptions", ) + reimport_eligible = proto.Field(proto.BOOL, number=18,) class PublicKey(proto.Message): diff --git a/google/cloud/kms_v1/types/service.py b/google/cloud/kms_v1/types/service.py index 96ab8e87..9f789293 100644 --- a/google/cloud/kms_v1/types/service.py +++ b/google/cloud/kms_v1/types/service.py @@ -501,6 +501,39 @@ class ImportCryptoKeyVersionRequest(proto.Message): Required. The [name][google.cloud.kms.v1.CryptoKey.name] of the [CryptoKey][google.cloud.kms.v1.CryptoKey] to be imported into. + + The create permission is only required on this key when + creating a new + [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion]. + crypto_key_version (str): + Optional. The optional + [name][google.cloud.kms.v1.CryptoKeyVersion.name] of an + existing + [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion] to + target for an import operation. If this field is not + present, a new + [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion] + containing the supplied key material is created. + + If this field is present, the supplied key material is + imported into the existing + [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion]. To + import into an existing + [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion], + the [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion] + must be a child of + [ImportCryptoKeyVersionRequest.parent][google.cloud.kms.v1.ImportCryptoKeyVersionRequest.parent], + have been previously created via [ImportCryptoKeyVersion][], + and be in + [DESTROYED][google.cloud.kms.v1.CryptoKeyVersion.CryptoKeyVersionState.DESTROYED] + or + [IMPORT_FAILED][google.cloud.kms.v1.CryptoKeyVersion.CryptoKeyVersionState.IMPORT_FAILED] + state. The key material and algorithm must match the + previous + [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion] + exactly if the + [CryptoKeyVersion][google.cloud.kms.v1.CryptoKeyVersion] has + ever contained key material. algorithm (google.cloud.kms_v1.types.CryptoKeyVersion.CryptoKeyVersionAlgorithm): Required. The [algorithm][google.cloud.kms.v1.CryptoKeyVersion.CryptoKeyVersionAlgorithm] @@ -543,6 +576,7 @@ class ImportCryptoKeyVersionRequest(proto.Message): """ parent = proto.Field(proto.STRING, number=1,) + crypto_key_version = proto.Field(proto.STRING, number=6,) algorithm = proto.Field( proto.ENUM, number=2, enum=resources.CryptoKeyVersion.CryptoKeyVersionAlgorithm, ) diff --git a/noxfile.py b/noxfile.py index 03aa2f58..2bf3ffd7 100644 --- a/noxfile.py +++ b/noxfile.py @@ -84,9 +84,15 @@ def default(session): constraints_path = str( CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" ) - session.install("asyncmock", "pytest-asyncio", "-c", constraints_path) - - session.install("mock", "pytest", "pytest-cov", "-c", constraints_path) + session.install( + "mock", + "asyncmock", + "pytest", + "pytest-cov", + "pytest-asyncio", + "-c", + constraints_path, + ) session.install("-e", ".", "-c", constraints_path) diff --git a/samples/attestations/requirements.txt b/samples/attestations/requirements.txt index 3f688a1c..c4de97d0 100644 --- a/samples/attestations/requirements.txt +++ b/samples/attestations/requirements.txt @@ -1,2 +1,2 @@ -cryptography==3.4.7 +cryptography==3.4.8 pem==21.2.0 diff --git a/samples/snippets/create_key_asymmetric_decrypt.py b/samples/snippets/create_key_asymmetric_decrypt.py index 4865a266..993d6840 100644 --- a/samples/snippets/create_key_asymmetric_decrypt.py +++ b/samples/snippets/create_key_asymmetric_decrypt.py @@ -30,6 +30,8 @@ def create_key_asymmetric_decrypt(project_id, location_id, key_ring_id, id): # Import the client library. from google.cloud import kms + from google.protobuf import duration_pb2 + import datetime # Create the client. client = kms.KeyManagementServiceClient() @@ -44,7 +46,11 @@ def create_key_asymmetric_decrypt(project_id, location_id, key_ring_id, id): '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. diff --git a/samples/snippets/create_key_asymmetric_sign.py b/samples/snippets/create_key_asymmetric_sign.py index 1b057997..b3fbaa9e 100644 --- a/samples/snippets/create_key_asymmetric_sign.py +++ b/samples/snippets/create_key_asymmetric_sign.py @@ -30,6 +30,8 @@ def create_key_asymmetric_sign(project_id, location_id, key_ring_id, id): # Import the client library. from google.cloud import kms + from google.protobuf import duration_pb2 + import datetime # Create the client. client = kms.KeyManagementServiceClient() @@ -44,7 +46,11 @@ def create_key_asymmetric_sign(project_id, location_id, key_ring_id, id): '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. diff --git a/samples/snippets/create_key_hsm.py b/samples/snippets/create_key_hsm.py index 34a9c8a2..a850391a 100644 --- a/samples/snippets/create_key_hsm.py +++ b/samples/snippets/create_key_hsm.py @@ -30,6 +30,8 @@ def create_key_hsm(project_id, location_id, key_ring_id, id): # Import the client library. from google.cloud import kms + from google.protobuf import duration_pb2 + import datetime # Create the client. client = kms.KeyManagementServiceClient() @@ -46,7 +48,11 @@ def create_key_hsm(project_id, location_id, key_ring_id, id): '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. diff --git a/samples/snippets/create_key_mac.py b/samples/snippets/create_key_mac.py new file mode 100644 index 00000000..db8d086e --- /dev/null +++ b/samples/snippets/create_key_mac.py @@ -0,0 +1,60 @@ +# 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, 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'). + 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': id, 'crypto_key': key}) + print('Created mac key: {}'.format(created_key.name)) + return created_key +# [END kms_create_key_mac] diff --git a/samples/snippets/generate_random_bytes.py b/samples/snippets/generate_random_bytes.py new file mode 100644 index 00000000..9b543831 --- /dev/null +++ b/samples/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/samples/snippets/requirements.txt b/samples/snippets/requirements.txt index 4b56c322..bcd11836 100644 --- a/samples/snippets/requirements.txt +++ b/samples/snippets/requirements.txt @@ -1,3 +1,3 @@ -google-cloud-kms==2.4.3 -cryptography==3.4.7 +google-cloud-kms==2.5.0 +cryptography==3.4.8 crcmod==1.7 diff --git a/samples/snippets/sign_mac.py b/samples/snippets/sign_mac.py new file mode 100644 index 00000000..fa054b42 --- /dev/null +++ b/samples/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/samples/snippets/snippets_test.py b/samples/snippets/snippets_test.py index 8efeb514..048f6f8f 100644 --- a/samples/snippets/snippets_test.py +++ b/samples/snippets/snippets_test.py @@ -32,6 +32,7 @@ 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 @@ -43,6 +44,7 @@ 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 @@ -53,6 +55,7 @@ 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 @@ -60,6 +63,7 @@ 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") @@ -167,6 +171,22 @@ def hsm_key_id(client, project_id, location_id, key_ring_id): 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) @@ -245,6 +265,13 @@ def test_create_key_labels(project_id, location_id, key_ring_id): 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) @@ -345,6 +372,11 @@ def test_encrypt_symmetric(client, project_id, location_id, key_ring_id, symmetr 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'} @@ -412,6 +444,18 @@ def test_sign_asymmetric(client, project_id, location_id, key_ring_id, asymmetri 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) @@ -461,6 +505,16 @@ def test_verify_asymmetric_rsa(client, project_id, location_id, key_ring_id, asy 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/samples/snippets/verify_mac.py b/samples/snippets/verify_mac.py new file mode 100644 index 00000000..6fbcc73b --- /dev/null +++ b/samples/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] diff --git a/scripts/fixup_kms_v1_keywords.py b/scripts/fixup_kms_v1_keywords.py index 167ffd5d..ffd7304c 100644 --- a/scripts/fixup_kms_v1_keywords.py +++ b/scripts/fixup_kms_v1_keywords.py @@ -54,7 +54,7 @@ class kmsCallTransformer(cst.CSTTransformer): 'get_import_job': ('name', ), 'get_key_ring': ('name', ), 'get_public_key': ('name', ), - 'import_crypto_key_version': ('parent', 'algorithm', 'import_job', 'rsa_aes_wrapped_key', ), + 'import_crypto_key_version': ('parent', 'algorithm', 'import_job', 'crypto_key_version', 'rsa_aes_wrapped_key', ), 'list_crypto_keys': ('parent', 'page_size', 'page_token', 'version_view', 'filter', 'order_by', ), 'list_crypto_key_versions': ('parent', 'page_size', 'page_token', 'view', 'filter', 'order_by', ), 'list_import_jobs': ('parent', 'page_size', 'page_token', 'filter', 'order_by', ), diff --git a/scripts/readme-gen/templates/install_deps.tmpl.rst b/scripts/readme-gen/templates/install_deps.tmpl.rst index a0406dba..275d6498 100644 --- a/scripts/readme-gen/templates/install_deps.tmpl.rst +++ b/scripts/readme-gen/templates/install_deps.tmpl.rst @@ -12,7 +12,7 @@ Install Dependencies .. _Python Development Environment Setup Guide: https://cloud.google.com/python/setup -#. Create a virtualenv. Samples are compatible with Python 2.7 and 3.4+. +#. Create a virtualenv. Samples are compatible with Python 3.6+. .. code-block:: bash diff --git a/setup.py b/setup.py index 43ab2450..db45661e 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ name = "google-cloud-kms" description = "Cloud Key Management Service (KMS) API client library" -version = "2.5.0" +version = "2.6.0" release_status = "Development Status :: 5 - Production/Stable" dependencies = [ # NOTE: Maintainers, please do not require google-api-core>=2.x.x diff --git a/tests/unit/gapic/kms_v1/test_key_management_service.py b/tests/unit/gapic/kms_v1/test_key_management_service.py index ca9b896d..8e115daf 100644 --- a/tests/unit/gapic/kms_v1/test_key_management_service.py +++ b/tests/unit/gapic/kms_v1/test_key_management_service.py @@ -2457,6 +2457,7 @@ def test_get_crypto_key_version( algorithm=resources.CryptoKeyVersion.CryptoKeyVersionAlgorithm.GOOGLE_SYMMETRIC_ENCRYPTION, import_job="import_job_value", import_failure_reason="import_failure_reason_value", + reimport_eligible=True, ) response = client.get_crypto_key_version(request) @@ -2479,6 +2480,7 @@ def test_get_crypto_key_version( ) assert response.import_job == "import_job_value" assert response.import_failure_reason == "import_failure_reason_value" + assert response.reimport_eligible is True def test_get_crypto_key_version_from_dict(): @@ -2527,6 +2529,7 @@ async def test_get_crypto_key_version_async( algorithm=resources.CryptoKeyVersion.CryptoKeyVersionAlgorithm.GOOGLE_SYMMETRIC_ENCRYPTION, import_job="import_job_value", import_failure_reason="import_failure_reason_value", + reimport_eligible=True, ) ) response = await client.get_crypto_key_version(request) @@ -2550,6 +2553,7 @@ async def test_get_crypto_key_version_async( ) assert response.import_job == "import_job_value" assert response.import_failure_reason == "import_failure_reason_value" + assert response.reimport_eligible is True @pytest.mark.asyncio @@ -3638,6 +3642,7 @@ def test_create_crypto_key_version( algorithm=resources.CryptoKeyVersion.CryptoKeyVersionAlgorithm.GOOGLE_SYMMETRIC_ENCRYPTION, import_job="import_job_value", import_failure_reason="import_failure_reason_value", + reimport_eligible=True, ) response = client.create_crypto_key_version(request) @@ -3660,6 +3665,7 @@ def test_create_crypto_key_version( ) assert response.import_job == "import_job_value" assert response.import_failure_reason == "import_failure_reason_value" + assert response.reimport_eligible is True def test_create_crypto_key_version_from_dict(): @@ -3708,6 +3714,7 @@ async def test_create_crypto_key_version_async( algorithm=resources.CryptoKeyVersion.CryptoKeyVersionAlgorithm.GOOGLE_SYMMETRIC_ENCRYPTION, import_job="import_job_value", import_failure_reason="import_failure_reason_value", + reimport_eligible=True, ) ) response = await client.create_crypto_key_version(request) @@ -3731,6 +3738,7 @@ async def test_create_crypto_key_version_async( ) assert response.import_job == "import_job_value" assert response.import_failure_reason == "import_failure_reason_value" + assert response.reimport_eligible is True @pytest.mark.asyncio @@ -3912,6 +3920,7 @@ def test_import_crypto_key_version( algorithm=resources.CryptoKeyVersion.CryptoKeyVersionAlgorithm.GOOGLE_SYMMETRIC_ENCRYPTION, import_job="import_job_value", import_failure_reason="import_failure_reason_value", + reimport_eligible=True, ) response = client.import_crypto_key_version(request) @@ -3934,6 +3943,7 @@ def test_import_crypto_key_version( ) assert response.import_job == "import_job_value" assert response.import_failure_reason == "import_failure_reason_value" + assert response.reimport_eligible is True def test_import_crypto_key_version_from_dict(): @@ -3982,6 +3992,7 @@ async def test_import_crypto_key_version_async( algorithm=resources.CryptoKeyVersion.CryptoKeyVersionAlgorithm.GOOGLE_SYMMETRIC_ENCRYPTION, import_job="import_job_value", import_failure_reason="import_failure_reason_value", + reimport_eligible=True, ) ) response = await client.import_crypto_key_version(request) @@ -4005,6 +4016,7 @@ async def test_import_crypto_key_version_async( ) assert response.import_job == "import_job_value" assert response.import_failure_reason == "import_failure_reason_value" + assert response.reimport_eligible is True @pytest.mark.asyncio @@ -4599,6 +4611,7 @@ def test_update_crypto_key_version( algorithm=resources.CryptoKeyVersion.CryptoKeyVersionAlgorithm.GOOGLE_SYMMETRIC_ENCRYPTION, import_job="import_job_value", import_failure_reason="import_failure_reason_value", + reimport_eligible=True, ) response = client.update_crypto_key_version(request) @@ -4621,6 +4634,7 @@ def test_update_crypto_key_version( ) assert response.import_job == "import_job_value" assert response.import_failure_reason == "import_failure_reason_value" + assert response.reimport_eligible is True def test_update_crypto_key_version_from_dict(): @@ -4669,6 +4683,7 @@ async def test_update_crypto_key_version_async( algorithm=resources.CryptoKeyVersion.CryptoKeyVersionAlgorithm.GOOGLE_SYMMETRIC_ENCRYPTION, import_job="import_job_value", import_failure_reason="import_failure_reason_value", + reimport_eligible=True, ) ) response = await client.update_crypto_key_version(request) @@ -4692,6 +4707,7 @@ async def test_update_crypto_key_version_async( ) assert response.import_job == "import_job_value" assert response.import_failure_reason == "import_failure_reason_value" + assert response.reimport_eligible is True @pytest.mark.asyncio @@ -5121,6 +5137,7 @@ def test_destroy_crypto_key_version( algorithm=resources.CryptoKeyVersion.CryptoKeyVersionAlgorithm.GOOGLE_SYMMETRIC_ENCRYPTION, import_job="import_job_value", import_failure_reason="import_failure_reason_value", + reimport_eligible=True, ) response = client.destroy_crypto_key_version(request) @@ -5143,6 +5160,7 @@ def test_destroy_crypto_key_version( ) assert response.import_job == "import_job_value" assert response.import_failure_reason == "import_failure_reason_value" + assert response.reimport_eligible is True def test_destroy_crypto_key_version_from_dict(): @@ -5191,6 +5209,7 @@ async def test_destroy_crypto_key_version_async( algorithm=resources.CryptoKeyVersion.CryptoKeyVersionAlgorithm.GOOGLE_SYMMETRIC_ENCRYPTION, import_job="import_job_value", import_failure_reason="import_failure_reason_value", + reimport_eligible=True, ) ) response = await client.destroy_crypto_key_version(request) @@ -5214,6 +5233,7 @@ async def test_destroy_crypto_key_version_async( ) assert response.import_job == "import_job_value" assert response.import_failure_reason == "import_failure_reason_value" + assert response.reimport_eligible is True @pytest.mark.asyncio @@ -5379,6 +5399,7 @@ def test_restore_crypto_key_version( algorithm=resources.CryptoKeyVersion.CryptoKeyVersionAlgorithm.GOOGLE_SYMMETRIC_ENCRYPTION, import_job="import_job_value", import_failure_reason="import_failure_reason_value", + reimport_eligible=True, ) response = client.restore_crypto_key_version(request) @@ -5401,6 +5422,7 @@ def test_restore_crypto_key_version( ) assert response.import_job == "import_job_value" assert response.import_failure_reason == "import_failure_reason_value" + assert response.reimport_eligible is True def test_restore_crypto_key_version_from_dict(): @@ -5449,6 +5471,7 @@ async def test_restore_crypto_key_version_async( algorithm=resources.CryptoKeyVersion.CryptoKeyVersionAlgorithm.GOOGLE_SYMMETRIC_ENCRYPTION, import_job="import_job_value", import_failure_reason="import_failure_reason_value", + reimport_eligible=True, ) ) response = await client.restore_crypto_key_version(request) @@ -5472,6 +5495,7 @@ async def test_restore_crypto_key_version_async( ) assert response.import_job == "import_job_value" assert response.import_failure_reason == "import_failure_reason_value" + assert response.reimport_eligible is True @pytest.mark.asyncio