Skip to content

Commit 6d4efde

Browse files
mccoypchlowell
andauthored
[Key Vault] Add sample for parsing private key/public certificate from certificate (Azure#15863)
* Add certificate key/cert parsing samples * Thanks, Charles! * Better wording Co-authored-by: Charles Lowell <chlowe@microsoft.com>
1 parent b62d6a0 commit 6d4efde

File tree

5 files changed

+236
-19
lines changed

5 files changed

+236
-19
lines changed

sdk/keyvault/azure-keyvault-certificates/samples/README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ recover certificates
1919
* [recover_purge_operations.py][recover_purge_operations_sample] and [recover_purge_operations_async.py][recover_purge_operations_async_sample] - recover and purge certificates
2020
* [issuers.py][issuers_sample] and [issuers_async.py][issuers_async_sample] - manage certificate issuers
2121
* [contacts.py][contacts_sample] and [contacts_async.py][contacts_async_sample] - manage certificate contacts
22+
* [parse_certificate.py][parse_sample] and [parse_certificate_async.py][parse_async_sample] - extract a certificate's private key
2223

2324
[backup_operations_sample]: https://github.com/Azure/azure-sdk-for-python/tree/master/sdk/keyvault/azure-keyvault-certificates/samples/backup_restore_operations.py
2425
[backup_operations_async_sample]: https://github.com/Azure/azure-sdk-for-python/tree/master/sdk/keyvault/azure-keyvault-certificates/samples/backup_restore_operations_async.py
@@ -32,4 +33,6 @@ recover certificates
3233
[contacts_sample]: https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/keyvault/azure-keyvault-certificates/samples/contacts.py
3334
[contacts_async_sample]: https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/keyvault/azure-keyvault-certificates/samples/contacts_async.py
3435
[issuers_sample]: https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/keyvault/azure-keyvault-certificates/samples/issuers.py
35-
[issuers_async_sample]: https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/keyvault/azure-keyvault-certificates/samples/issuers_async.py
36+
[issuers_async_sample]: https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/keyvault/azure-keyvault-certificates/samples/issuers_async.py
37+
[parse_sample]: https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/keyvault/azure-keyvault-certificates/samples/parse_certificate.py
38+
[parse_async_sample]: https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/keyvault/azure-keyvault-certificates/samples/parse_certificate_async.py

sdk/keyvault/azure-keyvault-certificates/samples/hello_world.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
#
1414
# 2. azure-keyvault-certificates and azure-identity packages (pip install these)
1515
#
16-
# 3. Set Environment variables AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_CLIENT_SECRET, VAULT_URL
17-
# (See https://github.com/Azure/azure-sdk-for-python/tree/master/sdk/keyvault/azure-keyvault-keys#authenticate-the-client)
16+
# 3. Set Environment variables AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_CLIENT_SECRET, VAULT_URL. (See
17+
# https://github.com/Azure/azure-sdk-for-python/tree/master/sdk/keyvault/azure-keyvault-certificates#authenticate-the-client)
1818
#
1919
# ----------------------------------------------------------------------------------------------------------
2020
# Sample - demonstrates the basic CRUD operations on a vault(certificate) resource for Azure Key Vault
@@ -39,15 +39,14 @@
3939
try:
4040
# Let's create a certificate for holding bank account credentials valid for 1 year.
4141
# if the certificate already exists in the Key Vault, then a new version of the certificate is created.
42-
print("\n.. Create Certificate")
42+
print("\n.. Create certificate")
4343

4444
# Before creating your certificate, let's create the management policy for your certificate.
4545
# Here you specify the properties of the key, secret, and issuer backing your certificate,
4646
# the X509 component of your certificate, and any lifetime actions you would like to be taken
4747
# on your certificate
4848

49-
# Alternatively, if you would like to use our default policy, don't pass a policy parameter to
50-
# our certificate creation method
49+
# Alternatively, if you would like to use our default policy, use CertificatePolicy.get_default()
5150
cert_policy = CertificatePolicy(
5251
issuer_name=WellKnownIssuerNames.self,
5352
subject="CN=*.microsoft.com",
@@ -70,12 +69,12 @@
7069
print("Certificate with name '{0}' created".format(certificate.name))
7170

7271
# Let's get the bank certificate using its name
73-
print("\n.. Get a Certificate by name")
72+
print("\n.. Get a certificate by name")
7473
bank_certificate = client.get_certificate(cert_name)
7574
print("Certificate with name '{0}' was found'.".format(bank_certificate.name))
7675

7776
# After one year, the bank account is still active, and we have decided to update the tags.
78-
print("\n.. Update a Certificate by name")
77+
print("\n.. Update a certificate by name")
7978
tags = {"a": "b"}
8079
updated_certificate = client.update_certificate_properties(
8180
certificate_name=bank_certificate.name, tags=tags
@@ -92,7 +91,7 @@
9291
)
9392

9493
# The bank account was closed, need to delete its credentials from the Key Vault.
95-
print("\n.. Delete Certificate")
94+
print("\n.. Delete certificate")
9695
deleted_certificate = client.begin_delete_certificate(bank_certificate.name).result()
9796
print("Certificate with name '{0}' was deleted.".format(deleted_certificate.name))
9897

sdk/keyvault/azure-keyvault-certificates/samples/hello_world_async.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
#
1616
# 2. azure-keyvault-certificates and azure-identity packages (pip install these)
1717
#
18-
# 3. Set Environment variables AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_CLIENT_SECRET, VAULT_URL
19-
# (See https://github.com/Azure/azure-sdk-for-python/tree/master/sdk/keyvault/azure-keyvault-keys#authenticate-the-client)
18+
# 3. Set Environment variables AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_CLIENT_SECRET, VAULT_URL. (See
19+
# https://github.com/Azure/azure-sdk-for-python/tree/master/sdk/keyvault/azure-keyvault-certificates#authenticate-the-client)
2020
#
2121
# ----------------------------------------------------------------------------------------------------------
2222
# Sample - demonstrates the basic CRUD operations on a vault(certificate) resource for Azure Key Vault
@@ -43,15 +43,14 @@ async def run_sample():
4343
try:
4444
# Let's create a certificate for holding bank account credentials valid for 1 year.
4545
# if the certificate already exists in the Key Vault, then a new version of the certificate is created.
46-
print("\n.. Create Certificate")
46+
print("\n.. Create certificate")
4747

4848
# Before creating your certificate, let's create the management policy for your certificate.
4949
# Here you specify the properties of the key, secret, and issuer backing your certificate,
5050
# the X509 component of your certificate, and any lifetime actions you would like to be taken
5151
# on your certificate
5252

53-
# Alternatively, if you would like to use our default policy, don't pass a policy parameter to
54-
# our certificate creation method
53+
# Alternatively, if you would like to use our default policy, use CertificatePolicy.get_default()
5554
cert_policy = CertificatePolicy(
5655
issuer_name=WellKnownIssuerNames.self,
5756
subject="CN=*.microsoft.com",
@@ -71,12 +70,12 @@ async def run_sample():
7170
print("Certificate with name '{0}' created".format(certificate.name))
7271

7372
# Let's get the bank certificate using its name
74-
print("\n.. Get a Certificate by name")
73+
print("\n.. Get a certificate by name")
7574
bank_certificate = await client.get_certificate(cert_name)
7675
print("Certificate with name '{0}' was found.".format(bank_certificate.name))
7776

7877
# After one year, the bank account is still active, and we have decided to update the tags.
79-
print("\n.. Update a Certificate by name")
78+
print("\n.. Update a certificate by name")
8079
tags = {"a": "b"}
8180
updated_certificate = await client.update_certificate_properties(
8281
certificate_name=bank_certificate.name, tags=tags
@@ -93,9 +92,9 @@ async def run_sample():
9392
)
9493

9594
# The bank account was closed, need to delete its credentials from the Key Vault.
96-
print("\n.. Delete Certificate")
95+
print("\n.. Delete certificate")
9796
deleted_certificate = await client.delete_certificate(bank_certificate.name)
98-
print("Deleting Certificate..")
97+
print("Deleting certificate..")
9998
print("Certificate with name '{0}' was deleted.".format(deleted_certificate.name))
10099

101100
except HttpResponseError as e:
@@ -114,4 +113,4 @@ async def run_sample():
114113
loop.close()
115114

116115
except Exception as e:
117-
print("Top level Error: {0}".format(str(e)))
116+
print("Top level error: {0}".format(str(e)))
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# ------------------------------------
2+
# Copyright (c) Microsoft Corporation.
3+
# Licensed under the MIT License.
4+
# ------------------------------------
5+
import base64
6+
import os
7+
from azure.identity import DefaultAzureCredential
8+
from azure.keyvault.certificates import CertificateClient, CertificatePolicy
9+
from azure.keyvault.secrets import SecretClient
10+
from azure.core.exceptions import HttpResponseError
11+
from cryptography.hazmat.primitives.serialization import pkcs12
12+
13+
# ----------------------------------------------------------------------------------------------------------
14+
# Prerequisites:
15+
# 1. An Azure Key Vault. (https://docs.microsoft.com/en-us/azure/key-vault/quick-create-cli)
16+
#
17+
# 2. A service principal with certificate get, delete, and purge permissions, as well as secret get
18+
# permissions.
19+
#
20+
# 3. azure-keyvault-certificates, azure-keyvault-secrets, azure-identity, and cryptography (v3.3+) packages
21+
# (pip install these).
22+
#
23+
# 4. Set Environment variables AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_CLIENT_SECRET, VAULT_URL. (See
24+
# https://github.com/Azure/azure-sdk-for-python/tree/master/sdk/keyvault/azure-keyvault-certificates#authenticate-the-client)
25+
#
26+
# ----------------------------------------------------------------------------------------------------------
27+
# Sample - demonstrates how to get the private key of an existing Key Vault certificate
28+
#
29+
# 1. Create a new certificate (CertificateClient.begin_create_certificate)
30+
#
31+
# 2. Get a certificate secret (SecretClient.get_secret)
32+
#
33+
# 3. Delete a certificate (CertificateClient.begin_delete_certificate)
34+
#
35+
# 4. Purge a certificate (CertificateClient.purge_deleted_secret)
36+
#
37+
# ----------------------------------------------------------------------------------------------------------
38+
39+
# Instantiate a certificate client that will be used to call the service.
40+
# Notice that the client is using default Azure credentials.
41+
# To make default credentials work, ensure that environment variables 'AZURE_CLIENT_ID',
42+
# 'AZURE_CLIENT_SECRET' and 'AZURE_TENANT_ID' are set with the service principal credentials.
43+
VAULT_URL = os.environ["VAULT_URL"]
44+
credential = DefaultAzureCredential()
45+
certificate_client = CertificateClient(vault_url=VAULT_URL, credential=credential)
46+
47+
# Instantiate a secret client that will be used to call the service.
48+
# Notice that this client can reuse the credential object created above.
49+
secret_client = SecretClient(vault_url=VAULT_URL, credential=credential)
50+
try:
51+
# Let's create a certificate in the vault.
52+
# If the certificate already exists in the Key Vault, then a new version of the certificate is created.
53+
print("\n.. Create certificate")
54+
55+
# Before creating your certificate, let's create the management policy for your certificate.
56+
# Here we use the default policy.
57+
cert_name = "PrivateKeyCertificate"
58+
cert_policy = CertificatePolicy.get_default()
59+
60+
# begin_create_certificate returns a poller. Calling result() on the poller will return the certificate
61+
# as a KeyVaultCertificate if creation is successful, and the CertificateOperation if not. The wait()
62+
# call on the poller will wait until the long running operation is complete.
63+
created_certificate = certificate_client.begin_create_certificate(
64+
certificate_name=cert_name, policy=cert_policy
65+
).result()
66+
print("Certificate with name '{}' was created".format(created_certificate.name))
67+
68+
# Key Vault also creates a secret with the same name as the created certificate.
69+
# This secret contains the certificate's bytes, which include the private key if the certificate's
70+
# policy indicates that the key is exportable.
71+
print("\n.. Get a secret by name")
72+
certificate_secret = secret_client.get_secret(name=cert_name)
73+
print("Certificate secret with name '{}' was found.".format(certificate_secret.name))
74+
75+
# Now we can extract the private key and public certificate from the secret using the cryptography
76+
# package. `additional_certificates` will be empty since the secret only contains one certificate.
77+
# This example shows how to parse a certificate in PKCS12 format since it's the default in Key Vault,
78+
# but PEM certificates are supported as well. With a PEM certificate, you could use load_pem_private_key
79+
# in place of load_key_and_certificates.
80+
cert_bytes = base64.b64decode(certificate_secret.value)
81+
private_key, public_certificate, additional_certificates = pkcs12.load_key_and_certificates(
82+
data=cert_bytes,
83+
password=None
84+
)
85+
print("Certificate with name '{}' was parsed.".format(certificate_secret.name))
86+
87+
# Now we can clean up the vault by deleting, then purging, the certificate.
88+
print("\n.. Delete certificate")
89+
delete_operation_poller = certificate_client.begin_delete_certificate(
90+
certificate_name=cert_name
91+
)
92+
deleted_certificate = delete_operation_poller.result()
93+
delete_operation_poller.wait()
94+
print("Certificate with name '{}' was deleted.".format(deleted_certificate.name))
95+
96+
certificate_client.purge_deleted_certificate(certificate_name=deleted_certificate.name)
97+
print("Certificate with name '{}' is being purged.".format(deleted_certificate.name))
98+
99+
except HttpResponseError as e:
100+
print("\nrun_sample has caught an error. {}".format(e.message))
101+
102+
finally:
103+
print("\nrun_sample done")
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# ------------------------------------
2+
# Copyright (c) Microsoft Corporation.
3+
# Licensed under the MIT License.
4+
# ------------------------------------
5+
import asyncio
6+
import base64
7+
import os
8+
from azure.identity.aio import DefaultAzureCredential
9+
from azure.keyvault.certificates.aio import CertificateClient
10+
from azure.keyvault.certificates import CertificatePolicy
11+
from azure.keyvault.secrets.aio import SecretClient
12+
from azure.core.exceptions import HttpResponseError
13+
from cryptography.hazmat.primitives.serialization import pkcs12
14+
15+
# ----------------------------------------------------------------------------------------------------------
16+
# Prerequisites:
17+
# 1. An Azure Key Vault. (https://docs.microsoft.com/en-us/azure/key-vault/quick-create-cli)
18+
#
19+
# 2. A service principal with certificate get, delete, and purge permissions, as well as secret get
20+
# permissions.
21+
#
22+
# 3. azure-keyvault-certificates, azure-keyvault-secrets, azure-identity, and cryptography (v3.3+) packages
23+
# (pip install these).
24+
#
25+
# 4. Set Environment variables AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_CLIENT_SECRET, VAULT_URL. (See
26+
# https://github.com/Azure/azure-sdk-for-python/tree/master/sdk/keyvault/azure-keyvault-certificates#authenticate-the-client)
27+
#
28+
# ----------------------------------------------------------------------------------------------------------
29+
# Sample - demonstrates how to get the private key of an existing Key Vault certificate
30+
#
31+
# 1. Create a new certificate (CertificateClient.create_certificate)
32+
#
33+
# 2. Get a certificate secret (SecretClient.get_secret)
34+
#
35+
# 3. Delete a certificate (CertificateClient.delete_certificate)
36+
#
37+
# 4. Purge a certificate (CertificateClient.purge_deleted_secret)
38+
#
39+
# ----------------------------------------------------------------------------------------------------------
40+
41+
async def run_sample():
42+
# Instantiate a certificate client that will be used to call the service.
43+
# Notice that the client is using default Azure credentials.
44+
# To make default credentials work, ensure that environment variables 'AZURE_CLIENT_ID',
45+
# 'AZURE_CLIENT_SECRET' and 'AZURE_TENANT_ID' are set with the service principal credentials.
46+
VAULT_URL = os.environ["VAULT_URL"]
47+
credential = DefaultAzureCredential()
48+
certificate_client = CertificateClient(vault_url=VAULT_URL, credential=credential)
49+
50+
# Instantiate a secret client that will be used to call the service.
51+
# Notice that this client can reuse the credential object created above.
52+
secret_client = SecretClient(vault_url=VAULT_URL, credential=credential)
53+
try:
54+
# Let's create a certificate in the vault.
55+
# If the certificate already exists in the Key Vault, then a new version of the certificate is created.
56+
print("\n.. Create certificate")
57+
58+
# Before creating your certificate, let's create the management policy for your certificate.
59+
# Here we use the default policy.
60+
cert_name = "PrivateKeyCertificate"
61+
cert_policy = CertificatePolicy.get_default()
62+
63+
# Awaiting create_certificate will return the certificate as a KeyVaultCertificate
64+
# if creation is successful, and the CertificateOperation if not.
65+
created_certificate = await certificate_client.create_certificate(
66+
certificate_name=cert_name, policy=cert_policy
67+
)
68+
print("Certificate with name '{}' was created".format(created_certificate.name))
69+
70+
# Key Vault also creates a secret with the same name as the created certificate.
71+
# This secret contains protected information about the certificate, such as its private key.
72+
print("\n.. Get a secret by name")
73+
certificate_secret = await secret_client.get_secret(name=cert_name)
74+
print("Certificate secret with name '{}' was found.".format(certificate_secret.name))
75+
76+
# Now we can extract the private key and public certificate from the secret using the cryptography
77+
# package. `additional_certificates` will be empty since the secret only contains one certificate.
78+
# This example shows how to parse a certificate in PKCS12 format since it's the default in Key Vault,
79+
# but PEM certificates are supported as well. With a PEM certificate, you could use load_pem_private_key
80+
# in place of load_key_and_certificates.
81+
cert_bytes = base64.b64decode(certificate_secret.value)
82+
private_key, public_certificate, additional_certificates = pkcs12.load_key_and_certificates(
83+
data=cert_bytes,
84+
password=None
85+
)
86+
print("Certificate with name '{}' was parsed.".format(certificate_secret.name))
87+
88+
# Now we can clean up the vault by deleting, then purging, the certificate.
89+
print("\n.. Delete certificate")
90+
deleted_certificate = await certificate_client.delete_certificate(certificate_name=cert_name)
91+
print("Certificate with name '{}' was deleted.".format(deleted_certificate.name))
92+
93+
await certificate_client.purge_deleted_certificate(certificate_name=deleted_certificate.name)
94+
print("Certificate with name '{}' is being purged.".format(deleted_certificate.name))
95+
96+
except HttpResponseError as e:
97+
print("\nrun_sample has caught an error. {}".format(e.message))
98+
99+
finally:
100+
print("\nrun_sample done")
101+
await credential.close()
102+
await certificate_client.close()
103+
await secret_client.close()
104+
105+
106+
if __name__ == "__main__":
107+
try:
108+
loop = asyncio.get_event_loop()
109+
loop.run_until_complete(run_sample())
110+
loop.close()
111+
112+
except Exception as e:
113+
print("Top level error: {}".format(str(e)))

0 commit comments

Comments
 (0)