問題描述
在Azure Key Vault中,我們可以從Azure門戶中下載下傳證書PEM檔案到本地。 可以通過OpenSSL把PFX檔案轉換到PEM檔案。然後用TXT方式檢視内容,操作步驟如下圖:

OpenSSL轉換指令為:
openssl pkcs12 -in "C:\Users\Downloads\mykeyvault01-cscert01-20220316.pfx" -nokeys -out "C:\tool\xd12.pem"
當然,Azure也提供了通過PowerShell或CLI指令來下載下傳PEM檔案,操作為:
az keyvault certificate download --vault-name vault -n cert-name -f cert.pem
(Source : https://docs.microsoft.com/en-us/cli/azure/keyvault/certificate?view=azure-cli-latest#az-keyvault-certificate-download)
那麼,如何可以通過Python SDK的代碼擷取到PEM檔案呢?
問題解答
檢視 Python SDK Certificate中公布出來的接口,并沒有 Export, Download 的方法。 Python Azure Key Vault SDK 中并沒有可以直接下載下傳PEM檔案的方法。
Azure SDK For Python KeyVault -- CertificateClient Class : https://docs.microsoft.com/en-us/python/api/azure-keyvault-certificates/azure.keyvault.certificates.aio.certificateclient?view=azure-python#methods
Methods
backup_certificate | Back up a certificate in a protected form useable only by Azure Key Vault. Requires certificates/backup permission. This is intended to allow copying a certificate from one vault to another. Both vaults must be owned by the same Azure subscription. Also, backup / restore cannot be performed across geopolitical boundaries. For example, a backup from a vault in a USA region cannot be restored to a vault in an EU region. |
cancel_certificate_operation | Cancels an in-progress certificate operation. Requires the certificates/update permission. |
create_certificate | Creates a new certificate. If this is the first version, the certificate resource is created. This operation requires the certificates/create permission. The poller requires the certificates/get permission, otherwise raises an HttpResponseError |
create_issuer | Sets the specified certificate issuer. Requires certificates/setissuers permission. |
delete_certificate | Delete all versions of a certificate. Requires certificates/delete permission. If the vault has soft-delete enabled, deletion may take several seconds to complete. |
delete_certificate_operation | Deletes and stops the creation operation for a specific certificate. Requires the certificates/update permission. |
delete_contacts | Deletes the certificate contacts for the key vault. Requires the certificates/managecontacts permission. |
delete_issuer | Deletes the specified certificate issuer. Requires certificates/manageissuers/deleteissuers permission. |
get_certificate | Gets a certificate with its management policy attached. Requires certificates/get permission. Does not accept the version of the certificate as a parameter. To get a specific version of the certificate, call get_certificate_version. |
get_certificate_operation | Gets the creation operation of a certificate. Requires the certificates/get permission. |
get_certificate_policy | Gets the policy for a certificate. Requires certificates/get permission. Returns the specified certificate policy resources in the key vault. |
get_certificate_version | Gets a specific version of a certificate without returning its management policy. Requires certificates/get permission. To get the latest version of the certificate, or to get the certificate's policy as well, call get_certificate. |
get_contacts | Gets the certificate contacts for the key vault. Requires the certificates/managecontacts permission. |
get_deleted_certificate | Get a deleted certificate. Possible only in a vault with soft-delete enabled. Requires certificates/get permission. Retrieves the deleted certificate information plus its attributes, such as retention interval, scheduled permanent deletion, and the current deletion recovery level. |
get_issuer | Gets the specified certificate issuer. Requires certificates/manageissuers/getissuers permission. |
import_certificate | Import a certificate created externally. Requires certificates/import permission. Imports an existing valid certificate, containing a private key, into Azure Key Vault. The certificate to be imported can be in either PFX or PEM format. If the certificate is in PEM format the PEM file must contain the key as well as x509 certificates, and you must provide a with content_type of pem. |
list_deleted_certificates | Lists the currently-recoverable deleted certificates. Possible only if vault is soft-delete enabled. Requires certificates/get/list permission. Retrieves the certificates in the current vault which are in a deleted state and ready for recovery or purging. This operation includes deletion-specific information. |
list_properties_of_certificate_versions | List the identifiers and properties of a certificate's versions. Requires certificates/list permission. |
list_properties_of_certificates | List identifiers and properties of all certificates in the vault. Requires certificates/list permission. |
list_properties_of_issuers | Lists properties of the certificate issuers for the key vault. Requires the certificates/manageissuers/getissuers permission. |
merge_certificate | Merges a certificate or a certificate chain with a key pair existing on the server. Requires the certificates/create permission. Performs the merging of a certificate or certificate chain with a key pair currently available in the service. Make sure when creating the certificate to merge using create_certificate that you set its issuer to 'Unknown'. This way Key Vault knows that the certificate will not be signed by an issuer known to it. |
purge_deleted_certificate | Permanently deletes a deleted certificate. Possible only in vaults with soft-delete enabled. Requires certificates/purge permission. Performs an irreversible deletion of the specified certificate, without possibility for recovery. The operation is not available if the recovery_level does not specify 'Purgeable'. This method is only necessary for purging a certificate before its scheduled_purge_date. |
recover_deleted_certificate | Recover a deleted certificate to its latest version. Possible only in a vault with soft-delete enabled. Requires certificates/recover permission. If the vault does not have soft-delete enabled, delete_certificate is permanent, and this method will raise an error. Attempting to recover a non-deleted certificate will also raise an error. |
restore_certificate_backup | Restore a certificate backup to the vault. Requires certificates/restore permission. This restores all versions of the certificate, with its name, attributes, and access control policies. If the certificate's name is already in use, restoring it will fail. Also, the target vault must be owned by the same Microsoft Azure subscription as the source vault. |
set_contacts | Sets the certificate contacts for the key vault. Requires certificates/managecontacts permission. |
update_certificate_policy | Updates the policy for a certificate. Requires certificiates/update permission. Set specified members in the certificate policy. Leaves others as null. |
update_certificate_properties | Change a certificate's properties. Requires certificates/update permission. |
update_issuer | Updates the specified certificate issuer. Requires certificates/setissuers permission. |
是以使用原始的SDK Methods方法不可行。
尋找解決方案
通過對CLI (az keyvault certificate download)指令的研究,發現CLI使用的是python代碼執行的Get Certificates 操作,實質上是調用的Key Vault的REST API:
Get Certificate: https://docs.microsoft.com/en-us/rest/api/keyvault/certificates/get-certificate/get-certificate#getcertificate
DEBUG az 指令:
az keyvault certificate download --vault-name mykeyvault01 -n cscert01 -f cert2.pem --debug
C:\Users>az keyvault certificate download --vault-name mykeyvault01 -n cscert01 -f cert2.pem --debug
cli.knack.cli: Command arguments: ['keyvault', 'certificate', 'download', '--vault-name', 'mykeyvault01', '-n', 'cscert01', '-f', 'cert2.pem', '--debug']
cli.knack.cli: __init__ debug log:
Enable color in terminal.
Init colorama.
cli.knack.cli: Event: Cli.PreExecute []
cli.knack.cli: Event: CommandParser.OnGlobalArgumentsCreate [<function CLILogging.on_global_arguments at 0x033452F8>, <function OutputProducer.on_global_arguments at 0x034F0190>, <function CLIQuery.on_global_arguments at 0x03607D60>]
cli.knack.cli: Event: CommandInvoker.OnPreCommandTableCreate []
cli.azure.cli.core: Modules found from index for 'keyvault': ['azure.cli.command_modules.keyvault']
cli.azure.cli.core: Loading command modules:
cli.azure.cli.core: Name Load Time Groups Commands
cli.azure.cli.core: keyvault 0.038 19 117
cli.azure.cli.core: Total (1) 0.038 19 117
cli.azure.cli.core: These extensions are not installed and will be skipped: ['azext_ai_examples', 'azext_next']
cli.azure.cli.core: Loading extensions:
cli.azure.cli.core: Name Load Time Groups Commands Directory
cli.azure.cli.core: Total (0) 0.000 0 0
cli.azure.cli.core: Loaded 19 groups, 117 commands.
cli.azure.cli.core: Found a match in the command table.
cli.azure.cli.core: Raw command : keyvault certificate download
cli.azure.cli.core: Command table: keyvault certificate download
cli.knack.cli: Event: CommandInvoker.OnPreCommandTableTruncate [<function AzCliLogging.init_command_file_logging at 0x039701D8>]
cli.azure.cli.core.azlogging: metadata file logging enabled - writing logs to 'C:\Users\.azure\commands\2022-03-16.14-46-58.keyvault_certificate_download.21860.log'.
az_command_data_logger: command args: keyvault certificate download --vault-name {} -n {} -f {} --debug
cli.knack.cli: Event: CommandInvoker.OnPreArgumentLoad [<function register_global_subscription_argument.<locals>.add_subscription_parameter at 0x039B6268>, <function register_global_query_examples_argument.<locals>.register_query_examples at 0x039D8928>]
cli.knack.cli: Event: CommandInvoker.OnPostArgumentLoad []
cli.knack.cli: Event: CommandInvoker.OnPostCommandTableCreate [<function register_ids_argument.<locals>.add_ids_arguments at 0x039D8970>, <function register_cache_arguments.<locals>.add_cache_arguments at 0x039D8A00>]
cli.knack.cli: Event: CommandInvoker.OnCommandTableLoaded []
cli.knack.cli: Event: CommandInvoker.OnPreParseArgs []
cli.knack.cli: Event: CommandInvoker.OnPostParseArgs [<function OutputProducer.handle_output_argument at 0x034F01D8>, <function CLIQuery.handle_query_parameter at 0x03607DA8>, <function register_global_query_examples_argument.<locals>.handle_example_parameter at 0x039D88E0>, <function register_ids_argument.<locals>.parse_ids_arguments at 0x039D89B8>]
msrest.universal_http.requests: Configuring retry: max_retries=4, backoff_factor=0.8, max_backoff=90
msrest.service_client: Accept header absent and forced to application/json
msrest.universal_http: Configuring redirects: allow=True, max=30
msrest.universal_http: Configuring request: timeout=100, verify=True, cert=None
msrest.universal_http: Configuring proxies: ''
msrest.universal_http: Evaluate proxies against ENV settings: True
urllib3.connectionpool: Starting new HTTPS connection (1): mykeyvault01.vault.azure.cn:443
urllib3.connectionpool: https://mykeyvault01.vault.azure.cn:443 "GET /certificates/cscert01/?api-version=7.0 HTTP/1.1" 401 97
cli.azure.cli.core._profile: Profile.get_raw_token invoked with resource='https://vault.azure.cn', subscription=None, tenant=None
cli.azure.cli.core.util: attempting to read file C:\Users\.azure\accessTokens.json as utf-8-sig
adal-python: 3f4f9351-cdc1-4891-b125-1f02ac02741b - Authority:Performing instance discovery: ...
adal-python: 3f4f9351-cdc1-4891-b125-1f02ac02741b - Authority:Performing static instance discovery
adal-python: 3f4f9351-cdc1-4891-b125-1f02ac02741b - Authority:Authority validated via static instance discovery
adal-python: 3f4f9351-cdc1-4891-b125-1f02ac02741b - TokenRequest:Getting token from cache with refresh if necessary.
adal-python: 3f4f9351-cdc1-4891-b125-1f02ac02741b - CacheDriver:finding with query keys: {'_clientId': '...', 'userId': '...'}
adal-python: 3f4f9351-cdc1-4891-b125-1f02ac02741b - CacheDriver:Looking for potential cache entries: {'_clientId': '...', 'userId': '...'}
adal-python: 3f4f9351-cdc1-4891-b125-1f02ac02741b - CacheDriver:Found 9 potential entries.
adal-python: 3f4f9351-cdc1-4891-b125-1f02ac02741b - CacheDriver:Resource specific token found.
adal-python: 3f4f9351-cdc1-4891-b125-1f02ac02741b - CacheDriver:Returning token from cache lookup, AccessTokenId: b'wR5KoJYE=', RefreshTokenId: b'5VNpWgDCg4ydvuf2PeWxGaph/r7KMelGLXVY+jLV89s='
urllib3.connectionpool: https://mykeyvault01.vault.azure.cn:443 "GET /certificates/cscert01/?api-version=7.0 HTTP/1.1" 200 2114
cli.knack.cli: Event: CommandInvoker.OnTransformResult [<function _resource_group_transform at 0x039A5C88>, <function _x509_from_base64_to_hex_transform at 0x039A5CD0>]
cli.knack.cli: Event: CommandInvoker.OnFilterResult []
cli.knack.cli: Event: Cli.SuccessfulExecute []
cli.knack.cli: Event: Cli.PostExecute [<function AzCliLogging.deinit_cmd_metadata_logging at 0x039702F8>]
az_command_data_logger: exit code: 0
cli.__main__: Command ran in 3.206 seconds (init: 0.394, invoke: 2.812)
telemetry.save: Save telemetry record of length 3005 in cache
telemetry.check: Negative: The C:\Users\.azure\telemetry.txt was modified at 2022-03-16 14:43:00.945931, which in less than 600.000000 s
如以上黃色高亮内容, download指令調用了 "GET /certificates/cscert01/?api-version=7.0 HTTP/1.1" 200 2114,且傳回的HTTP Code為200 成功。是以當我們單獨對get certificates接口請求時,在傳回結果中,發現cer屬性值就是證書PEM格式内容。通過Postman發送請求并驗證結果:
那麼,通過Python Azure SDK的 certificate_client.get_certificate方法,擷取到certificate對象後,其中包含的cer值,是否也是PEM的内容呢?
我們通過下面的代碼來進行驗證:
from azure.identity import DefaultAzureCredential
from azure.keyvault.certificates import CertificateClient
import ssl
credential = DefaultAzureCredential()
certificate_client = CertificateClient(vault_url=https://yourkeyvaultname.vault.azure.cn/, credential=credential)
certificate = certificate_client.get_certificate("your certificate name")
print(certificate.name)
print(certificate.properties.version)
print(certificate.policy.issuer_name)
print(str(certificate.cer))
# Convert the certificate to PEM format
cert_PEM = ssl.DER_cert_to_PEM_cert(certificate.cer);
print("Certificate in PEM format:");
print(cert_PEM);
是的,在certificate對象中,cer就是我們證書的内容,但是由于格式為DER,是以使用了SSL包中的 DER_cert_to_PEM_cert 方法完成轉換,最終得到PEM檔案内容。
在 python代碼中擷取到PEM内容後,剩下的部分就是把内容輸出到 .pem 檔案即可。
##接上一段代碼
filename = 'mypem1.pem'
# Open the file in append mode and append the new content in file_object
with open(filename, 'a') as file_object:
file_object.write(cert_PEM)
print("output the PEM file End!")
注意:在建立 certificate_client對象時,需要使用credential,而以上代碼使用的是預設的DefaultAzureCredential(),在執行代碼的本機中,已經配置了如下環境變量:
- AZURE_TENANT_ID
- AZURE_CLIENT_ID
- AZURE_CLIENT_SECRET
使用 ClientSecretCredential 認證方式後,代碼修改如下:
import os
from azure.keyvault.certificates import CertificateClient
import ssl
from azure.identity import ClientSecretCredential
from msrestazure.azure_cloud import AZURE_CHINA_CLOUD
print('AZURE_TENANT_ID:' +os.environ["AZURE_TENANT_ID"])
print('AZURE_CLIENT_ID:' +os.environ["AZURE_CLIENT_ID"])
print('AZURE_CLIENT_SECRET:' +os.environ["AZURE_CLIENT_SECRET"])
credential = ClientSecretCredential (client_id=os.environ["AZURE_CLIENT_ID"],client_secret=os.environ["AZURE_CLIENT_SECRET"],tenant_id=os.environ["AZURE_TENANT_ID"],cloud_environment=AZURE_CHINA_CLOUD,china=True)
certificate_client = CertificateClient(vault_url="https://yourkeyvault.vault.azure.cn/", credential=credential)
certificate = certificate_client.get_certificate("your certificate name")
print(certificate.name)
print(certificate.properties.version)
print(certificate.policy.issuer_name)
print(str(certificate.cer))
# Convert the certificate to PEM format
cert_PEM = ssl.DER_cert_to_PEM_cert(certificate.cer);
print("Certificate in PEM format:");
print(cert_PEM);
filename = 'mypem2.pem'
# Open the file in append mode and append the new content in file_object
with open(filename, 'a') as file_object:
file_object.write(cert_PEM)
print("output the PEM file End!")
參考文檔
Retrieve a Certificate:https://docs.microsoft.com/en-us/python/api/overview/azure/keyvault-certificates-readme?view=azure-python#retrieve-a-certificate
Der_cert_to_pem_cert Function Of Ssl Module In Python: https://pythontic.com/ssl/ssl-module/der_cert_to_pem_cert
A certificate bundle consists of a certificate (X509) plus its attributes: https://docs.microsoft.com/en-us/rest/api/keyvault/certificates/get-certificate/get-certificate#certificatebundle
當在複雜的環境中面臨問題,格物之道需:濁而靜之徐清,安以動之徐生。 雲中,恰是如此!