Skip to content

Commit 100f533

Browse files
lwolfowitz-googledandhlee
authored andcommitted
samples: Add Python code snippets for importing-a-key doc (#67)
1 parent d177aa2 commit 100f533

6 files changed

+318
-1
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Copyright 2020 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
14+
15+
# [START kms_check_state_import_job]
16+
def check_state_import_job(project_id, location_id, key_ring_id, import_job_id):
17+
"""
18+
Check the state of an import job in Cloud KMS.
19+
20+
Args:
21+
project_id (string): Google Cloud project ID (e.g. 'my-project').
22+
location_id (string): Cloud KMS location (e.g. 'us-east1').
23+
key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring').
24+
import_job_id (string): ID of the import job (e.g. 'my-import-job').
25+
"""
26+
27+
# Import the client library.
28+
from google.cloud import kms
29+
30+
# Create the client.
31+
client = kms.KeyManagementServiceClient()
32+
33+
# Retrieve the fully-qualified import_job string.
34+
import_job_name = client.import_job_path(
35+
project_id, location_id, key_ring_id, import_job_id)
36+
37+
# Retrieve the state from an existing import job.
38+
import_job = client.get_import_job(name=import_job_name)
39+
40+
print('Current state of import job {}: {}'.format(import_job.name, import_job.state))
41+
# [END kms_check_state_import_job]
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Copyright 2020 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
14+
15+
# [START kms_check_state_imported_key]
16+
def check_state_imported_key(project_id, location_id, key_ring_id, import_job_id):
17+
"""
18+
Check the state of an import job in Cloud KMS.
19+
20+
Args:
21+
project_id (string): Google Cloud project ID (e.g. 'my-project').
22+
location_id (string): Cloud KMS location (e.g. 'us-east1').
23+
key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring').
24+
import_job_id (string): ID of the import job (e.g. 'my-import-job').
25+
"""
26+
27+
# Import the client library.
28+
from google.cloud import kms
29+
30+
# Create the client.
31+
client = kms.KeyManagementServiceClient()
32+
33+
# Retrieve the fully-qualified import_job string.
34+
import_job_name = client.import_job_path(
35+
project_id, location_id, key_ring_id, import_job_id)
36+
37+
# Retrieve the state from an existing import job.
38+
import_job = client.get_import_job(name=import_job_name)
39+
40+
print('Current state of import job {}: {}'.format(import_job.name, import_job.state))
41+
# [END kms_check_state_imported_key]

kms/snippets/create_import_job.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Copyright 2020 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
14+
15+
# [START kms_create_import_job]
16+
def create_import_job(project_id, location_id, key_ring_id, import_job_id):
17+
"""
18+
Create a new import job in Cloud KMS.
19+
20+
Args:
21+
project_id (string): Google Cloud project ID (e.g. 'my-project').
22+
location_id (string): Cloud KMS location (e.g. 'us-east1').
23+
key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring').
24+
import_job_id (string): ID of the import job (e.g. 'my-import-job').
25+
"""
26+
27+
# Import the client library.
28+
from google.cloud import kms
29+
30+
# Create the client.
31+
client = kms.KeyManagementServiceClient()
32+
33+
# Retrieve the fully-qualified key_ring string.
34+
key_ring_name = client.key_ring_path(project_id, location_id, key_ring_id)
35+
36+
# Set paramaters for the import job, allowed values for ImportMethod and ProtectionLevel found here:
37+
# https://googleapis.dev/python/cloudkms/latest/_modules/google/cloud/kms_v1/types/resources.html
38+
import_job_params = {"import_method": kms.ImportJob.ImportMethod.RSA_OAEP_3072_SHA1_AES_256, "protection_level": kms.ProtectionLevel.HSM}
39+
40+
# Call the client to create a new import job.
41+
import_job = client.create_import_job({"parent": key_ring_name, "import_job_id": import_job_id, "import_job": import_job_params})
42+
43+
print('Created import job: {}'.format(import_job.name))
44+
# [END kms_create_import_job]

kms/snippets/create_key_for_import.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Copyright 2020 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
14+
15+
# [START kms_create_key_for_import]
16+
def create_key_for_import(project_id, location_id, key_ring_id, crypto_key_id):
17+
"""
18+
Generate Cloud KMS-compatible key material locally and sets up an empty CryptoKey within a KeyRing for import.
19+
20+
Args:
21+
project_id (string): Google Cloud project ID (e.g. 'my-project').
22+
location_id (string): Cloud KMS location (e.g. 'us-east1').
23+
key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring').
24+
crypto_key_id (string): ID of the key to import (e.g. 'my-asymmetric-signing-key').
25+
"""
26+
27+
# Import Python standard cryptographic libraries.
28+
from cryptography.hazmat.backends import default_backend
29+
from cryptography.hazmat.primitives import serialization
30+
from cryptography.hazmat.primitives.asymmetric import ec
31+
32+
# Import the client library.
33+
from google.cloud import kms
34+
35+
# Generate some key material in Python and format it in PKCS #8 DER as
36+
# required by Google Cloud KMS.
37+
key = ec.generate_private_key(ec.SECP256R1, default_backend())
38+
formatted_key = key.private_bytes(
39+
serialization.Encoding.DER,
40+
serialization.PrivateFormat.PKCS8,
41+
serialization.NoEncryption())
42+
43+
print('Generated key bytes: {}'.format(formatted_key))
44+
45+
# Create the client.
46+
client = kms.KeyManagementServiceClient()
47+
48+
# Build the key. For more information regarding allowed values of these fields, see:
49+
# https://googleapis.dev/python/cloudkms/latest/_modules/google/cloud/kms_v1/types/resources.html
50+
purpose = kms.CryptoKey.CryptoKeyPurpose.ASYMMETRIC_SIGN
51+
algorithm = kms.CryptoKeyVersion.CryptoKeyVersionAlgorithm.EC_SIGN_P256_SHA256
52+
protection_level = kms.ProtectionLevel.HSM
53+
key = {
54+
'purpose': purpose,
55+
'version_template': {
56+
'algorithm': algorithm,
57+
'protection_level': protection_level
58+
}
59+
}
60+
61+
# Build the parent key ring name.
62+
key_ring_name = client.key_ring_path(project_id, location_id, key_ring_id)
63+
64+
# Call the API.
65+
created_key = client.create_crypto_key(request={'parent': key_ring_name, 'crypto_key_id': crypto_key_id, 'crypto_key': key})
66+
print('Created hsm key: {}'.format(created_key.name))
67+
# [END kms_create_key_for_import]
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Copyright 2020 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
14+
15+
# [START kms_import_manually_wrapped_key]
16+
def import_manually_wrapped_key(project_id, location_id, key_ring_id, crypto_key_id, import_job_id, key_material):
17+
"""
18+
Imports local key material to Cloud KMS.
19+
20+
Args:
21+
project_id (string): Google Cloud project ID (e.g. 'my-project').
22+
location_id (string): Cloud KMS location (e.g. 'us-east1').
23+
key_ring_id (string): ID of the Cloud KMS key ring (e.g. 'my-key-ring').
24+
crypto_key_id (string): ID of the key to import (e.g. 'my-asymmetric-signing-key').
25+
import_job_id (string): ID of the import job (e.g. 'my-import-job').
26+
key_material (bytes): Locally generated key material in PKCS #8 DER format.
27+
Returns:
28+
CryptoKeyVersion: An instance of the imported key in Cloud KMS.
29+
"""
30+
31+
# Import the client library and Python standard cryptographic libraries.
32+
import os
33+
from cryptography.hazmat.backends import default_backend
34+
from cryptography.hazmat.primitives import hashes, keywrap, serialization
35+
from cryptography.hazmat.primitives.asymmetric import padding
36+
from google.cloud import kms
37+
38+
# Create the client.
39+
client = kms.KeyManagementServiceClient()
40+
41+
# Retrieve the fully-qualified crypto_key and import_job string.
42+
crypto_key_name = client.crypto_key_path(
43+
project_id, location_id, key_ring_id, crypto_key_id)
44+
import_job_name = client.import_job_path(
45+
project_id, location_id, key_ring_id, import_job_id)
46+
47+
# Generate a temporary 32-byte key for AES-KWP and wrap the key material.
48+
kwp_key = os.urandom(32)
49+
wrapped_target_key = keywrap.aes_key_wrap_with_padding(
50+
kwp_key, key_material, default_backend())
51+
52+
# Retrieve the public key from the import job.
53+
import_job = client.get_import_job(name=import_job_name)
54+
import_job_pub = serialization.load_pem_public_key(
55+
bytes(import_job.public_key.pem, 'UTF-8'), default_backend())
56+
57+
# Wrap the KWP key using the import job key.
58+
wrapped_kwp_key = import_job_pub.encrypt(
59+
kwp_key,
60+
padding.OAEP(
61+
mgf=padding.MGF1(algorithm=hashes.SHA1()),
62+
algorithm=hashes.SHA1(),
63+
label=None))
64+
65+
# Import the wrapped key material.
66+
client.import_crypto_key_version({
67+
"parent": crypto_key_name,
68+
"import_job": import_job_name,
69+
"algorithm": kms.CryptoKeyVersion.CryptoKeyVersionAlgorithm.EC_SIGN_P256_SHA256,
70+
"rsa_aes_wrapped_key": wrapped_kwp_key + wrapped_target_key,
71+
})
72+
73+
print('Imported: {}'.format(import_job.name))
74+
# [END kms_import_manually_wrapped_key]

kms/snippets/snippets_test.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,16 @@
2020
from cryptography.exceptions import InvalidSignature
2121
from cryptography.hazmat.backends import default_backend
2222
from cryptography.hazmat.primitives import hashes, serialization
23-
from cryptography.hazmat.primitives.asymmetric import padding, utils
23+
from cryptography.hazmat.primitives.asymmetric import ec, padding, utils
2424
from google.cloud import kms
2525
import pytest
2626

27+
from check_state_import_job import check_state_import_job
28+
from check_state_imported_key import check_state_imported_key
29+
from create_import_job import create_import_job
2730
from create_key_asymmetric_decrypt import create_key_asymmetric_decrypt
2831
from create_key_asymmetric_sign import create_key_asymmetric_sign
32+
from create_key_for_import import create_key_for_import
2933
from create_key_hsm import create_key_hsm
3034
from create_key_labels import create_key_labels
3135
from create_key_ring import create_key_ring
@@ -45,6 +49,7 @@
4549
from iam_add_member import iam_add_member
4650
from iam_get_policy import iam_get_policy
4751
from iam_remove_member import iam_remove_member
52+
from import_manually_wrapped_key import import_manually_wrapped_key
4853
from quickstart import quickstart
4954
from restore_key_version import restore_key_version
5055
from sign_asymmetric import sign_asymmetric
@@ -72,6 +77,16 @@ def location_id():
7277
return "us-east1"
7378

7479

80+
@pytest.fixture(scope="module")
81+
def import_job_id():
82+
return "my-import-job"
83+
84+
85+
@pytest.fixture(scope="module")
86+
def import_tests_key_id():
87+
return "my-import-job-ec-key"
88+
89+
7590
@pytest.fixture(scope="module")
7691
def key_ring_id(client, project_id, location_id):
7792
location_name = f"projects/{project_id}/locations/{location_id}"
@@ -176,6 +191,24 @@ def wait_for_ready(client, key_version_name):
176191
pytest.fail('{} not ready'.format(key_version_name))
177192

178193

194+
def test_create_import_job(project_id, location_id, key_ring_id, import_job_id, capsys):
195+
create_import_job(project_id, location_id, key_ring_id, import_job_id)
196+
out, _ = capsys.readouterr()
197+
assert "Created import job" in out
198+
199+
200+
def test_check_state_import_job(project_id, location_id, key_ring_id, import_job_id, capsys):
201+
check_state_import_job(project_id, location_id, key_ring_id, import_job_id)
202+
out, _ = capsys.readouterr()
203+
assert "Current state" in out
204+
205+
206+
def test_check_state_imported_key(project_id, location_id, key_ring_id, import_job_id, capsys):
207+
check_state_imported_key(project_id, location_id, key_ring_id, import_job_id)
208+
out, _ = capsys.readouterr()
209+
assert "Current state" in out
210+
211+
179212
def test_create_key_asymmetric_decrypt(project_id, location_id, key_ring_id):
180213
key_id = '{}'.format(uuid.uuid4())
181214
key = create_key_asymmetric_decrypt(project_id, location_id, key_ring_id, key_id)
@@ -190,6 +223,12 @@ def test_create_key_asymmetric_sign(project_id, location_id, key_ring_id):
190223
assert key.version_template.algorithm == kms.CryptoKeyVersion.CryptoKeyVersionAlgorithm.RSA_SIGN_PKCS1_2048_SHA256
191224

192225

226+
def test_create_key_for_import(project_id, location_id, key_ring_id, import_tests_key_id, capsys):
227+
create_key_for_import(project_id, location_id, key_ring_id, import_tests_key_id)
228+
out, _ = capsys.readouterr()
229+
assert "Generated key" in out
230+
231+
193232
def test_create_key_hsm(project_id, location_id, key_ring_id):
194233
key_id = '{}'.format(uuid.uuid4())
195234
key = create_key_hsm(project_id, location_id, key_ring_id, key_id)
@@ -347,6 +386,17 @@ def test_iam_remove_member(client, project_id, location_id, key_ring_id, asymmet
347386
assert any('group:[email protected]' in b.members for b in policy.bindings)
348387

349388

389+
def test_import_manually_wrapped_key(project_id, location_id, key_ring_id, import_job_id, import_tests_key_id, capsys):
390+
key = ec.generate_private_key(ec.SECP256R1, default_backend())
391+
formatted_key = key.private_bytes(
392+
serialization.Encoding.DER,
393+
serialization.PrivateFormat.PKCS8,
394+
serialization.NoEncryption())
395+
import_manually_wrapped_key(project_id, location_id, key_ring_id, import_tests_key_id, import_job_id, formatted_key)
396+
out, _ = capsys.readouterr()
397+
assert "Imported" in out
398+
399+
350400
def test_sign_asymmetric(client, project_id, location_id, key_ring_id, asymmetric_sign_rsa_key_id):
351401
message = 'my message'
352402

0 commit comments

Comments
 (0)