-
Notifications
You must be signed in to change notification settings - Fork 3.3k
[Key Vault] Add a new command az keyvault key download for downloading keys
#11912
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 10 commits
4fa331b
d8278b8
c424926
b05738d
7ef0d8e
e207611
ff01618
788ca69
cf25aeb
f761cb7
6bed8cd
269c43a
453cfb5
d35dda7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,19 +4,21 @@ | |
| # -------------------------------------------------------------------------------------------- | ||
|
|
||
| # pylint: disable=too-many-lines | ||
|
|
||
| import codecs | ||
| import json | ||
| import math | ||
| import os | ||
| import time | ||
| import struct | ||
|
|
||
|
|
||
| from knack.log import get_logger | ||
| from knack.util import CLIError | ||
|
|
||
| from OpenSSL import crypto | ||
| from cryptography.hazmat.backends import default_backend | ||
| from cryptography.hazmat.primitives.asymmetric import rsa, ec | ||
| from cryptography.hazmat.primitives.serialization import load_pem_private_key | ||
| from cryptography.hazmat.primitives.serialization import load_pem_private_key, Encoding, PublicFormat | ||
| from cryptography.exceptions import UnsupportedAlgorithm | ||
|
|
||
|
|
||
|
|
@@ -620,51 +622,54 @@ def restore_key(client, vault_base_url, file_path): | |
| return client.restore_key(vault_base_url, data) | ||
|
|
||
|
|
||
| def _int_to_bytes(i): | ||
| h = hex(i) | ||
| if len(h) > 1 and h[0:2] == '0x': | ||
| h = h[2:] | ||
| # need to strip L in python 2.x | ||
| h = h.strip('L') | ||
| if len(h) % 2: | ||
| h = '0' + h | ||
| return codecs.decode(h, 'hex') | ||
|
|
||
|
|
||
| def _private_rsa_key_to_jwk(rsa_key, jwk): | ||
| priv = rsa_key.private_numbers() | ||
| jwk.n = _int_to_bytes(priv.public_numbers.n) | ||
| jwk.e = _int_to_bytes(priv.public_numbers.e) | ||
| jwk.q = _int_to_bytes(priv.q) | ||
| jwk.p = _int_to_bytes(priv.p) | ||
| jwk.d = _int_to_bytes(priv.d) | ||
| jwk.dq = _int_to_bytes(priv.dmq1) | ||
| jwk.dp = _int_to_bytes(priv.dmp1) | ||
| jwk.qi = _int_to_bytes(priv.iqmp) | ||
|
|
||
|
|
||
| def _private_ec_key_to_jwk(ec_key, jwk): | ||
| supported_curves = { | ||
| 'secp256r1': 'P-256', | ||
| 'secp384r1': 'P-384', | ||
| 'secp521r1': 'P-521', | ||
| 'secp256k1': 'SECP256K1' | ||
| } | ||
| curve = ec_key.private_numbers().public_numbers.curve.name | ||
|
|
||
| jwk.crv = supported_curves.get(curve, None) | ||
| if not jwk.crv: | ||
| raise CLIError("Import failed: Unsupported curve, {}.".format(curve)) | ||
|
|
||
| jwk.x = _int_to_bytes(ec_key.private_numbers().public_numbers.x) | ||
| jwk.y = _int_to_bytes(ec_key.private_numbers().public_numbers.y) | ||
| jwk.d = _int_to_bytes(ec_key.private_numbers().private_value) | ||
|
|
||
|
|
||
| def import_key(cmd, client, vault_base_url, key_name, protection=None, key_ops=None, disabled=False, expires=None, | ||
| not_before=None, tags=None, pem_file=None, pem_password=None, byok_file=None): | ||
| """ Import a private key. Supports importing base64 encoded private keys from PEM files. | ||
| Supports importing BYOK keys into HSM for premium key vaults. """ | ||
| KeyAttributes = cmd.get_models('KeyAttributes', resource_type=ResourceType.DATA_KEYVAULT) | ||
| JsonWebKey = cmd.get_models('JsonWebKey', resource_type=ResourceType.DATA_KEYVAULT) | ||
|
|
||
| def _int_to_bytes(i): | ||
| h = hex(i) | ||
| if len(h) > 1 and h[0:2] == '0x': | ||
| h = h[2:] | ||
| # need to strip L in python 2.x | ||
| h = h.strip('L') | ||
| if len(h) % 2: | ||
| h = '0' + h | ||
| return codecs.decode(h, 'hex') | ||
|
|
||
| def _private_rsa_key_to_jwk(rsa_key, jwk): | ||
| priv = rsa_key.private_numbers() | ||
| jwk.n = _int_to_bytes(priv.public_numbers.n) | ||
| jwk.e = _int_to_bytes(priv.public_numbers.e) | ||
| jwk.q = _int_to_bytes(priv.q) | ||
| jwk.p = _int_to_bytes(priv.p) | ||
| jwk.d = _int_to_bytes(priv.d) | ||
| jwk.dq = _int_to_bytes(priv.dmq1) | ||
| jwk.dp = _int_to_bytes(priv.dmp1) | ||
| jwk.qi = _int_to_bytes(priv.iqmp) | ||
|
|
||
| def _private_ec_key_to_jwk(ec_key, jwk): | ||
| supported_curves = { | ||
| 'secp256r1': 'P-256', | ||
| 'secp384r1': 'P-384', | ||
| 'secp521r1': 'P-521', | ||
| 'secp256k1': 'SECP256K1' | ||
| } | ||
| curve = ec_key.private_numbers().public_numbers.curve.name | ||
|
|
||
| jwk.crv = supported_curves.get(curve, None) | ||
| if not jwk.crv: | ||
| raise CLIError("Import failed: Unsupported curve, {}.".format(curve)) | ||
|
|
||
| jwk.x = _int_to_bytes(ec_key.private_numbers().public_numbers.x) | ||
| jwk.y = _int_to_bytes(ec_key.private_numbers().public_numbers.y) | ||
| jwk.d = _int_to_bytes(ec_key.private_numbers().private_value) | ||
|
|
||
| key_attrs = KeyAttributes(enabled=not disabled, not_before=not_before, expires=expires) | ||
| key_obj = JsonWebKey(key_ops=key_ops) | ||
| if pem_file: | ||
|
|
@@ -694,6 +699,144 @@ def _private_ec_key_to_jwk(ec_key, jwk): | |
| key_obj.t = byok_data | ||
|
|
||
| return client.import_key(vault_base_url, key_name, key_obj, protection == 'hsm', key_attrs, tags) | ||
|
|
||
|
|
||
| def _bytes_to_int(b): | ||
| """Convert bytes to hex integer""" | ||
| len_diff = 4 - len(b) % 4 if len(b) % 4 > 0 else 0 | ||
| b = len_diff * b'\x00' + b # We have to patch leading zeros for using struct.unpack | ||
| bytes_num = int(math.floor(len(b) / 4)) | ||
| ans = 0 | ||
| items = struct.unpack('>' + 'I' * bytes_num, b) | ||
| for sub_int in items: # Accumulate all items into a big integer | ||
| ans *= 2 ** 32 | ||
| ans += sub_int | ||
| return ans | ||
|
|
||
|
|
||
| def _jwk_to_dict(jwk): | ||
| """Convert a `JsonWebKey` struct to a python dict""" | ||
| d = {} | ||
| if jwk.crv: | ||
| d['crv'] = jwk.crv | ||
| if jwk.kid: | ||
| d['kid'] = jwk.kid | ||
| if jwk.kty: | ||
| d['kty'] = jwk.kty | ||
| if jwk.d: | ||
| d['d'] = _bytes_to_int(jwk.d) | ||
| if jwk.dp: | ||
| d['dp'] = _bytes_to_int(jwk.dp) | ||
| if jwk.dq: | ||
| d['dq'] = _bytes_to_int(jwk.dq) | ||
| if jwk.e: | ||
| d['e'] = _bytes_to_int(jwk.e) | ||
| if jwk.k: | ||
| d['k'] = _bytes_to_int(jwk.k) | ||
| if jwk.n: | ||
| d['n'] = _bytes_to_int(jwk.n) | ||
| if jwk.p: | ||
| d['p'] = _bytes_to_int(jwk.p) | ||
| if jwk.q: | ||
| d['q'] = _bytes_to_int(jwk.q) | ||
| if jwk.qi: | ||
| d['qi'] = _bytes_to_int(jwk.qi) | ||
| if jwk.t: | ||
| d['t'] = _bytes_to_int(jwk.t) | ||
| if jwk.x: | ||
| d['x'] = _bytes_to_int(jwk.x) | ||
| if jwk.y: | ||
| d['y'] = _bytes_to_int(jwk.y) | ||
|
|
||
| return d | ||
|
|
||
|
|
||
| def _extract_rsa_public_key_from_jwk(jwk_dict): | ||
| """ | ||
| Please refer to: | ||
| https://github.com/mpdavis/python-jose/blob/eed086d7650ccbd4ea8b555157aff3b1b99f14b9/jose/backends | ||
| /cryptography_backend.py#L249-L254 | ||
|
||
| """ | ||
| # | ||
| e = jwk_dict.get('e', 256) | ||
| n = jwk_dict.get('n') | ||
| public = rsa.RSAPublicNumbers(e, n) | ||
| return public.public_key(default_backend()) | ||
|
|
||
|
|
||
| def _extract_ec_public_key_from_jwk(jwk_dict): | ||
| """ | ||
| Please refer to: | ||
| https://github.com/mpdavis/python-jose/blob/eed086d7650ccbd4ea8b555157aff3b1b99f14b9/jose/backends | ||
| /cryptography_backend.py#L81-L100 | ||
| """ | ||
| if not all(k in jwk_dict for k in ['x', 'y', 'crv']): | ||
| raise CLIError('Invalid EC key: missing properties(x, y, crv)') | ||
|
|
||
| x = jwk_dict.get('x') | ||
| y = jwk_dict.get('y') | ||
| curves = { | ||
| 'P-256': ec.SECP256R1, | ||
| 'P-384': ec.SECP384R1, | ||
| 'P-521': ec.SECP521R1, | ||
| 'P-256K': ec.SECP256K1, | ||
| 'SECP256K1': ec.SECP256K1 | ||
| } | ||
| curve = curves[jwk_dict['crv']] | ||
| public = ec.EllipticCurvePublicNumbers(x, y, curve()) | ||
| return public.public_key(default_backend()) | ||
|
|
||
|
|
||
| def download_key(client, file_path, vault_base_url=None, key_name=None, key_version='', | ||
| encoding=None, identifier=None): # pylint: disable=unused-argument | ||
| """ Download a key from a KeyVault. """ | ||
| if os.path.isfile(file_path) or os.path.isdir(file_path): | ||
| raise CLIError("File or directory named '{}' already exists.".format(file_path)) | ||
|
|
||
| key = client.get_key(vault_base_url, key_name, key_version) | ||
| json_web_key = _jwk_to_dict(key.key) | ||
| key_type = json_web_key['kty'] | ||
| pub_key = '' | ||
|
|
||
| if key_type in ['RSA', 'RSA-HSM']: | ||
| pub_key = _extract_rsa_public_key_from_jwk(json_web_key) | ||
| elif key_type in ['EC', 'EC-HSM']: | ||
| pub_key = _extract_ec_public_key_from_jwk(json_web_key) | ||
| else: | ||
| raise CLIError('Unsupported key type: {}'.format(key_type)) | ||
|
|
||
| def _to_der(k): | ||
| return k.public_bytes( | ||
| encoding=Encoding.DER, | ||
| format=PublicFormat.SubjectPublicKeyInfo | ||
| ) | ||
|
|
||
| def _to_pem(k): | ||
| """ | ||
| Please refer to: | ||
| https://github.com/mpdavis/python-jose/blob/eed086d7650ccbd4ea8b555157aff3b1b99f14b9/jose/backends | ||
| /cryptography_backend.py#L329-L332 | ||
| """ | ||
| return k.public_bytes( | ||
| encoding=Encoding.PEM, | ||
| format=PublicFormat.SubjectPublicKeyInfo | ||
| ) | ||
|
|
||
| methods = { | ||
| 'DER': _to_der, | ||
| 'PEM': _to_pem | ||
| } | ||
|
|
||
| if encoding not in methods.keys(): | ||
| raise CLIError('Unsupported encoding: {}'.format(encoding)) | ||
|
||
|
|
||
| try: | ||
| with open(file_path, 'wb') as f: | ||
| f.write(methods[encoding](pub_key)) | ||
| except Exception as ex: # pylint: disable=broad-except | ||
| if os.path.isfile(file_path): | ||
| os.remove(file_path) | ||
| raise ex | ||
| # endregion | ||
|
|
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| -----BEGIN EC PARAMETERS----- | ||
| BggqhkjOPQMBBw== | ||
| -----END EC PARAMETERS----- | ||
| -----BEGIN EC PRIVATE KEY----- | ||
| MHcCAQEEIIe338KHD5vHeNeB1Fhs68Kpx2jHjpqh/QZptSME/iL0oAoGCCqGSM49 | ||
| AwEHoUQDQgAEclUrtHdYtNdfl7cueN47hqM6SuyPn18rpsLutGJ/qHeHqvZdnL1b | ||
| +aJBNReZlaS24LlJVjsmxn/Zcg1BID0RWg== | ||
| -----END EC PRIVATE KEY----- |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| -----BEGIN PUBLIC KEY----- | ||
| MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEclUrtHdYtNdfl7cueN47hqM6SuyP | ||
| n18rpsLutGJ/qHeHqvZdnL1b+aJBNReZlaS24LlJVjsmxn/Zcg1BID0RWg== | ||
| -----END PUBLIC KEY----- |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| -----BEGIN EC PARAMETERS----- | ||
| BgUrgQQACg== | ||
| -----END EC PARAMETERS----- | ||
| -----BEGIN EC PRIVATE KEY----- | ||
| MHQCAQEEIMy+ZSqbwORvuWLCmEH12TJUM6zD7QGxgYczeU/2AZCyoAcGBSuBBAAK | ||
| oUQDQgAEH7Thoa/qhy7HoBSFPKOL9edHRqr305sCa7ozYkRLIe0WR98MA4LwXpZD | ||
| F3do0CMNqIYLzTJsG37DLngQZ4rJPw== | ||
| -----END EC PRIVATE KEY----- |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| -----BEGIN PUBLIC KEY----- | ||
| MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEH7Thoa/qhy7HoBSFPKOL9edHRqr305sC | ||
| a7ozYkRLIe0WR98MA4LwXpZDF3do0CMNqIYLzTJsG37DLngQZ4rJPw== | ||
| -----END PUBLIC KEY----- |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| -----BEGIN EC PARAMETERS----- | ||
| BgUrgQQAIg== | ||
| -----END EC PARAMETERS----- | ||
| -----BEGIN EC PRIVATE KEY----- | ||
| MIGkAgEBBDBG234N7Zwr021c4vi6M9mMz+Q3DVsAyahEqsNNY/XT3k5X3z4tfnth | ||
| /rgciYIxv1agBwYFK4EEACKhZANiAAReoBk8F60AVNazrJ9zUK1BU887MT41d5OA | ||
| oxPC5OocJGPvkSaK4NDZOOYj0IEvIw5F5y1QybXqhyfvj/0ydAxDulvvfDfmdcJ/ | ||
| 5o6rVip7XyFq9Kz6gnBNbQVVMbR7E/M= | ||
| -----END EC PRIVATE KEY----- |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| -----BEGIN PUBLIC KEY----- | ||
| MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXqAZPBetAFTWs6yfc1CtQVPPOzE+NXeT | ||
| gKMTwuTqHCRj75EmiuDQ2TjmI9CBLyMORectUMm16ocn74/9MnQMQ7pb73w35nXC | ||
| f+aOq1Yqe18havSs+oJwTW0FVTG0exPz | ||
| -----END PUBLIC KEY----- |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| -----BEGIN EC PARAMETERS----- | ||
| BgUrgQQAIw== | ||
| -----END EC PARAMETERS----- | ||
| -----BEGIN EC PRIVATE KEY----- | ||
| MIHcAgEBBEIBnqXLzys3RKGPLPC7jIbo6rIAXiXVjx4bnZu/CpuiFpnZiqUB5+pf | ||
| Kd/X1Av6ikeVYCmM1bXAxIfP4BGCUWGy2wugBwYFK4EEACOhgYkDgYYABAB8/6kF | ||
| Fy9ujL9GRaW1c2Fz9wXb1h6bg1zCWCgNWljOIEXcis8m+9Q4wfYFu51zKUI3QXqs | ||
| DkPJts9ru/xm2NOZJQDJaU+9LNPHryvkd44xniyWbWDbvPD/js6zKDCVMlvmo7CS | ||
| aOYj3QWEzoWDMVxV39KrxYVYeSrRQW2czlYrtGQQGg== | ||
| -----END EC PRIVATE KEY----- |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| -----BEGIN PUBLIC KEY----- | ||
| MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAfP+pBRcvboy/RkWltXNhc/cF29Ye | ||
| m4NcwlgoDVpYziBF3IrPJvvUOMH2BbudcylCN0F6rA5DybbPa7v8ZtjTmSUAyWlP | ||
| vSzTx68r5HeOMZ4slm1g27zw/47OsygwlTJb5qOwkmjmI90FhM6FgzFcVd/Sq8WF | ||
| WHkq0UFtnM5WK7RkEBo= | ||
| -----END PUBLIC KEY----- |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| -----BEGIN RSA PRIVATE KEY----- | ||
| MIIEowIBAAKCAQEAzJEYKuhIeHvKqZZtJRDtQjg17ChAZtY/yJfNqv30xnzkzOZw | ||
| Vmy7NViVQIRnkCYvAMCstja+EpEpeW02LD83teNQRKM9Nie74g7ehDn6ZZCSdQQ5 | ||
| 4TQemt26hMfnFVaUl376p0E398IP1leHcvYOaiVgT/ffUhNhgIUJ5sIphtf4D1p5 | ||
| bWFYvs/Gd+A+Fk38hnerpl7JRF4y6p5WGJzvXKKs0QVSaQ1NHD7L1tQ0xTKra3FS | ||
| /BGwD7ZdgBZerqzBPgxRjuxzq2DQJlHtvINsFjJhsMtwI11UV5+tIBOEWPSBTIoQ | ||
| pUWGB+/4jZ44IpKlBDQuc1kqa2Y8+eO53Gxn+QIDAQABAoIBAQDK3zIai1YjtpDb | ||
| 8oS3d7v0Kg6/74M++Uc0Ref/pe90UTQPSJEsBJT8aKdL3oNeX5/JnUsrQcrqWu/I | ||
| rlhFNUSoq5BVIZZ4+JrJq3ldpKoAw4mbZt+Hycp4R2DMgftYHA8s1w75hCJfISPX | ||
| q+J2TjMpbXvAks/0c6gEbuvM382THSDa+bA+BvYwilBBT347WkLdtuN9J+9qFyV9 | ||
| ROJRRMYh3xGOcgs/mmlOgTSubU3Q2sIQHBTLtngOA4g456g4IH5G09GoEpAIQA8e | ||
| G7xzmkv+ECtqe9V0A/9njWIXNbO+V+01dUDiAKnnT5uzbPGT48hnA05WLUAQO2qP | ||
| 5ZE4H6shAoGBAPQUXR2ByYX0e/VBF3lUo94yikRae6giM0k8MQwE1Y49FfuZwqp8 | ||
| 7E4hmuxlOJnvAQjzIQoHawL0fO7qLkvYcDJPxT5ROCeHKOCyJ6tGmVojppqiuq1G | ||
| RpOZ2XfIEt00rQguufYIr0V7OeP2X27Sveq0sJ/CYgX4rGABvq5OOW91AoGBANaO | ||
| t5T4qkuuxvxARx65S+imTg66U4aGz25rCj7LvcVhKmm2XCYz1ynAR81Tnh/t0RMd | ||
| 8M3mUxRZU1MrbMCeYZbt5ClWmMfHKtZa29PYYcvVvqNBnqlbPGD7fBK+JyGnnd57 | ||
| CQLA0WnmSDT0vBOSN+wH/Gk2IX3ekLMGBk1Zzyn1AoGAUhsfj7N/NR6fLEtvOBNu | ||
| 5Gof9QpzGoYWtoYXAbIGnMiTwoVg5LUNUOMhGHCcb7vknzwaWyNPrjjMZhpE5KK0 | ||
| a1hGQ8ZSm4luCNglXAptv9LKUq53GZ7QUwqoCxE0t1Dm/B+r0sXtH/Rp7vOL+t3N | ||
| oUyTNcrP6q5SXiF4IW6TB5kCgYAwNDhCm+uGvWmvWrGf0Xmgd1yqKmqBmuAXqqzO | ||
| lu+33LCut23Ul2kL1EtNci/gdIm4hc2INOsNc1QpJ2RzkiHSyver4ezJVZHmPtuM | ||
| qNyv8wG1pBSFcB4Mm/OwMlCQWxw40+OeXrut0zL90s4+h2dQ/CpVaPf1U3+m+P+J | ||
| eVf10QKBgDJZ0xVeun+aPMXqcwNppqvfKfMWsLb/Xx77O3q9SYG1JRXgIIneL8nD | ||
| 8RwVQn8PbPcqdayFHZrmwhdgzIH/7dVEwd+QQKzeYos8Fvc4egpTZUrmZ1y5XeFt | ||
| OHl9TmHLSA1h8cJj0HrWCjyyi2cW+2PhY9K88V57KEn0yv5hjoBg | ||
| -----END RSA PRIVATE KEY----- |
Uh oh!
There was an error while loading. Please reload this page.