# ------------------------------------
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# -------------------------------------
from collections import namedtuple
from ._shared import parse_key_vault_id
from ._generated.v7_1.models import JsonWebKey as _JsonWebKey
try:
from typing import TYPE_CHECKING
except ImportError:
TYPE_CHECKING = False
if TYPE_CHECKING:
# pylint:disable=unused-import
from typing import Any, Dict, Optional, List
from datetime import datetime
from ._generated.v7_0 import models as _models
from ._enums import KeyOperation, KeyRotationPolicyAction, KeyType
KeyOperationResult = namedtuple("KeyOperationResult", ["id", "value"])
[docs]class JsonWebKey(object):
# pylint:disable=too-many-instance-attributes
"""As defined in http://tools.ietf.org/html/draft-ietf-jose-json-web-key-18. All parameters are optional.
:param str kid: Key identifier.
:param kty: Key Type (kty), as defined in https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40
:type kty: ~azure.keyvault.keys.KeyType or str
:param key_ops: Allowed operations for the key
:type key_ops: list[str or ~azure.keyvault.keys.KeyOperation]
:param bytes n: RSA modulus.
:param bytes e: RSA public exponent.
:param bytes d: RSA private exponent, or the D component of an EC private key.
:param bytes dp: RSA private key parameter.
:param bytes dq: RSA private key parameter.
:param bytes qi: RSA private key parameter.
:param bytes p: RSA secret prime.
:param bytes q: RSA secret prime, with p < q.
:param bytes k: Symmetric key.
:param bytes t: HSM Token, used with 'Bring Your Own Key'.
:param crv: Elliptic curve name.
:type crv: ~azure.keyvault.keys.KeyCurveName or str
:param bytes x: X component of an EC public key.
:param bytes y: Y component of an EC public key.
"""
_FIELDS = ("kid", "kty", "key_ops", "n", "e", "d", "dp", "dq", "qi", "p", "q", "k", "t", "crv", "x", "y")
def __init__(self, **kwargs):
# type: (**Any) -> None
for field in self._FIELDS:
setattr(self, field, kwargs.get(field))
def _to_generated_model(self):
# type: () -> _JsonWebKey
jwk = _JsonWebKey()
for field in self._FIELDS:
setattr(jwk, field, getattr(self, field))
return jwk
[docs]class KeyProperties(object):
"""A key's id and attributes."""
def __init__(self, key_id, attributes=None, **kwargs):
# type: (str, Optional[_models.KeyAttributes], **Any) -> None
self._attributes = attributes
self._id = key_id
self._vault_id = KeyVaultKeyIdentifier(key_id)
self._managed = kwargs.get("managed", None)
self._tags = kwargs.get("tags", None)
self._release_policy = kwargs.pop("release_policy", None)
def __repr__(self):
# type () -> str
return "<KeyProperties [{}]>".format(self.id)[:1024]
@classmethod
def _from_key_bundle(cls, key_bundle):
# type: (_models.KeyBundle) -> KeyProperties
"""Construct a KeyProperties from an autorest-generated KeyBundle"""
# release_policy was added in 7.3-preview
release_policy = None
if (hasattr(key_bundle, "release_policy") and
key_bundle.release_policy is not None): # type: ignore[attr-defined]
release_policy = KeyReleasePolicy(
data=key_bundle.release_policy.data, # type: ignore[attr-defined]
content_type=key_bundle.release_policy.content_type # type: ignore[attr-defined]
)
return cls(
key_bundle.key.kid, # type: ignore
attributes=key_bundle.attributes,
managed=key_bundle.managed,
tags=key_bundle.tags,
release_policy=release_policy,
)
@classmethod
def _from_key_item(cls, key_item):
# type: (_models.KeyItem) -> KeyProperties
"""Construct a KeyProperties from an autorest-generated KeyItem"""
return cls(
key_id=key_item.kid, # type: ignore
attributes=key_item.attributes,
managed=key_item.managed,
tags=key_item.tags
)
@property
def id(self):
# type: () -> str
"""The key's id
:rtype: str
"""
return self._id
@property
def name(self):
# type: () -> str
"""The key's name
:rtype: str
"""
return self._vault_id.name
@property
def version(self):
# type: () -> Optional[str]
"""The key's version
:rtype: str or None
"""
return self._vault_id.version
@property
def enabled(self):
# type: () -> Optional[bool]
"""Whether the key is enabled for use
:rtype: bool or None
"""
return self._attributes.enabled if self._attributes else None
@property
def not_before(self):
# type: () -> Optional[datetime]
"""The time before which the key can not be used, in UTC
:rtype: ~datetime.datetime or None
"""
return self._attributes.not_before if self._attributes else None
@property
def expires_on(self):
# type: () -> Optional[datetime]
"""When the key will expire, in UTC
:rtype: ~datetime.datetime or None
"""
return self._attributes.expires if self._attributes else None
@property
def created_on(self):
# type: () -> Optional[datetime]
"""When the key was created, in UTC
:rtype: ~datetime.datetime or None
"""
return self._attributes.created if self._attributes else None
@property
def updated_on(self):
# type: () -> Optional[datetime]
"""When the key was last updated, in UTC
:rtype: ~datetime.datetime or None
"""
return self._attributes.updated if self._attributes else None
@property
def vault_url(self):
# type: () -> str
"""URL of the vault containing the key
:rtype: str
"""
return self._vault_id.vault_url
@property
def recoverable_days(self):
# type: () -> Optional[int]
"""The number of days the key is retained before being deleted from a soft-delete enabled Key Vault.
:rtype: int or None
"""
# recoverable_days was added in 7.1-preview
if self._attributes:
return getattr(self._attributes, "recoverable_days", None)
return None
@property
def recovery_level(self):
# type: () -> Optional[str]
"""The vault's deletion recovery level for keys
:rtype: str or None
"""
return self._attributes.recovery_level if self._attributes else None
@property
def tags(self):
# type: () -> Dict[str, str]
"""Application specific metadata in the form of key-value pairs
:rtype: dict[str, str]
"""
return self._tags
@property
def managed(self):
# type: () -> bool
"""Returns whether the key's lifetime is managed by key vault
:rtype: bool
"""
return self._managed
@property
def exportable(self):
# type: () -> Optional[bool]
"""Whether the private key can be exported
:rtype: bool
"""
# exportable was added in 7.3-preview
if self._attributes:
return getattr(self._attributes, "exportable", None)
return None
@property
def release_policy(self):
# type: () -> Optional[KeyReleasePolicy]
"""The :class:`~azure.keyvault.keys.KeyReleasePolicy` specifying the rules under which the key can be exported.
:rtype: ~azure.keyvault.keys.KeyReleasePolicy
"""
return self._release_policy
[docs]class KeyReleasePolicy(object):
"""The policy rules under which a key can be exported.
:param data: Blob encoding the policy rules under which the key can be released.
:type data: bytes
:keyword content_type: Content type and version of the release policy. Defaults to "application/json; charset=utf-8"
if omitted.
:paramtype content_type: str
"""
def __init__(self, data, **kwargs):
# type: (bytes, **Any) -> None
self.encoded_policy = data
self.content_type = kwargs.get("content_type", None)
[docs]class ReleaseKeyResult(object):
"""The result of a key release operation.
:ivar str value: A signed token containing the released key.
"""
def __init__(self, value):
# type: (str) -> None
self.value = value
[docs]class KeyRotationLifetimeAction(object):
"""An action and its corresponding trigger that will be performed by Key Vault over the lifetime of a key.
:param action: The action that will be executed.
:type action: ~azure.keyvault.keys.KeyRotationPolicyAction or str
:keyword str time_after_create: Time after creation to attempt the specified action, as an ISO 8601 duration.
For example, 90 days is "P90D".
:keyword str time_before_expiry: Time before expiry to attempt the specified action, as an ISO 8601 duration.
For example, 90 days is "P90D".
"""
def __init__(self, action, **kwargs):
# type: (KeyRotationPolicyAction, **Any) -> None
self.action = action
self.time_after_create = kwargs.get("time_after_create", None)
self.time_before_expiry = kwargs.get("time_before_expiry", None)
@classmethod
def _from_generated(cls, lifetime_action):
if lifetime_action.trigger:
return cls(
action=lifetime_action.action.type,
time_after_create=lifetime_action.trigger.time_after_create,
time_before_expiry=lifetime_action.trigger.time_before_expiry,
)
return cls(action=lifetime_action.action)
[docs]class KeyRotationPolicy(object):
"""The key rotation policy that belongs to a key.
:ivar str id: The identifier of the key rotation policy.
:ivar lifetime_actions: Actions that will be performed by Key Vault over the lifetime of a key.
:type lifetime_actions: list[~azure.keyvault.keys.KeyRotationLifetimeAction]
:ivar str expires_in: The expiry time of the policy that will be applied on new key versions, defined as an ISO
8601 duration. For example, 90 days is "P90D".
:ivar created_on: When the policy was created, in UTC
:type created_on: ~datetime.datetime
:ivar updated_on: When the policy was last updated, in UTC
:type updated_on: ~datetime.datetime
"""
def __init__(self, policy_id, **kwargs):
# type: (str, **Any) -> None
self.id = policy_id
self.lifetime_actions = kwargs.get("lifetime_actions", None)
self.expires_in = kwargs.get("expires_in", None)
self.created_on = kwargs.get("created_on", None)
self.updated_on = kwargs.get("updated_on", None)
@classmethod
def _from_generated(cls, policy):
lifetime_actions = [KeyRotationLifetimeAction._from_generated(action) for action in policy.lifetime_actions] # pylint:disable=protected-access
if policy.attributes:
return cls(
policy_id=policy.id,
lifetime_actions=lifetime_actions,
expires_in=policy.attributes.expiry_time,
created_on=policy.attributes.created,
updated_on=policy.attributes.updated,
)
return cls(policy_id=policy.id, lifetime_actions=lifetime_actions)
[docs]class KeyVaultKey(object):
"""A key's attributes and cryptographic material.
:param str key_id:
Key Vault's identifier for the key. Typically a URI, e.g. https://myvault.vault.azure.net/keys/my-key/version
:param jwk:
The key's cryptographic material as a JSON Web Key (https://tools.ietf.org/html/rfc7517). This may be provided
as a dictionary or keyword arguments. See :class:`~azure.keyvault.keys.models.JsonWebKey` for field names.
Providing cryptographic material as keyword arguments:
.. code-block:: python
from azure.keyvault.keys.models import KeyVaultKey
key_id = 'https://myvault.vault.azure.net/keys/my-key/my-key-version'
key_bytes = os.urandom(32)
key = KeyVaultKey(key_id, k=key_bytes, kty='oct', key_ops=['unwrapKey', 'wrapKey'])
Providing cryptographic material as a dictionary:
.. code-block:: python
from azure.keyvault.keys.models import KeyVaultKey
key_id = 'https://myvault.vault.azure.net/keys/my-key/my-key-version'
key_bytes = os.urandom(32)
jwk = {'k': key_bytes, 'kty': 'oct', 'key_ops': ['unwrapKey', 'wrapKey']}
key = KeyVaultKey(key_id, jwk=jwk)
"""
def __init__(self, key_id, jwk=None, **kwargs):
# type: (str, Optional[dict], **Any) -> None
self._properties = kwargs.pop("properties", None) or KeyProperties(key_id, **kwargs)
if isinstance(jwk, dict):
if any(field in kwargs for field in JsonWebKey._FIELDS): # pylint:disable=protected-access
raise ValueError(
"Individual keyword arguments for key material and the 'jwk' argument are mutually exclusive."
)
self._key_material = JsonWebKey(**jwk)
else:
self._key_material = JsonWebKey(**kwargs)
def __repr__(self):
# type () -> str
return "<KeyVaultKey [{}]>".format(self.id)[:1024]
@classmethod
def _from_key_bundle(cls, key_bundle):
# type: (_models.KeyBundle) -> KeyVaultKey
"""Construct a KeyVaultKey from an autorest-generated KeyBundle"""
# pylint:disable=protected-access
return cls(
key_id=key_bundle.key.kid, # type: ignore
jwk={field: getattr(key_bundle.key, field, None) for field in JsonWebKey._FIELDS},
properties=KeyProperties._from_key_bundle(key_bundle),
)
@property
def id(self):
# type: () -> str
"""The key's id
:rtype: str
"""
return self._properties.id
@property
def name(self):
# type: () -> str
"""The key's name
:rtype: str
"""
return self._properties.name
@property
def properties(self):
# type: () -> KeyProperties
"""The key's properties
:rtype: ~azure.keyvault.keys.KeyProperties
"""
return self._properties
@property
def key(self):
# type: () -> JsonWebKey
"""The JSON web key
:rtype: ~azure.keyvault.keys.JsonWebKey
"""
return self._key_material
@property
def key_type(self):
# type: () -> KeyType
"""The key's type. See :class:`~azure.keyvault.keys.KeyType` for possible values.
:rtype: ~azure.keyvault.keys.KeyType or str
"""
# pylint:disable=no-member
return self._key_material.kty # type: ignore[attr-defined]
@property
def key_operations(self):
# type: () -> List[KeyOperation]
"""Permitted operations. See :class:`~azure.keyvault.keys.KeyOperation` for possible values.
:rtype: list[~azure.keyvault.keys.KeyOperation or str]
"""
# pylint:disable=no-member
return self._key_material.key_ops # type: ignore[attr-defined]
[docs]class KeyVaultKeyIdentifier(object):
"""Information about a KeyVaultKey parsed from a key ID.
:param str source_id: the full original identifier of a key
:raises ValueError: if the key ID is improperly formatted
Example:
.. literalinclude:: ../tests/test_parse_id.py
:start-after: [START parse_key_vault_key_id]
:end-before: [END parse_key_vault_key_id]
:language: python
:caption: Parse a key's ID
:dedent: 8
"""
def __init__(self, source_id):
# type: (str) -> None
self._resource_id = parse_key_vault_id(source_id)
@property
def source_id(self):
# type: () -> str
return self._resource_id.source_id
@property
def vault_url(self):
# type: () -> str
return self._resource_id.vault_url
@property
def name(self):
# type: () -> str
return self._resource_id.name
@property
def version(self):
# type: () -> Optional[str]
return self._resource_id.version
[docs]class DeletedKey(KeyVaultKey):
"""A deleted key's properties, cryptographic material and its deletion information. If soft-delete
is enabled, returns information about its recovery as well."""
def __init__(
self,
properties, # type: KeyProperties
deleted_date=None, # type: Optional[datetime]
recovery_id=None, # type: Optional[str]
scheduled_purge_date=None, # type: Optional[datetime]
**kwargs # type: Any
):
# type: (...) -> None
super(DeletedKey, self).__init__(properties=properties, **kwargs)
self._deleted_date = deleted_date
self._recovery_id = recovery_id
self._scheduled_purge_date = scheduled_purge_date
def __repr__(self):
# type () -> str
return "<DeletedKey [{}]>".format(self.id)[:1024]
@classmethod
def _from_deleted_key_bundle(cls, deleted_key_bundle):
# type: (_models.DeletedKeyBundle) -> DeletedKey
"""Construct a DeletedKey from an autorest-generated DeletedKeyBundle"""
# pylint:disable=protected-access
return cls(
properties=KeyProperties._from_key_bundle(deleted_key_bundle),
key_id=deleted_key_bundle.key.kid, # type: ignore
jwk={field: getattr(deleted_key_bundle.key, field, None) for field in JsonWebKey._FIELDS},
deleted_date=deleted_key_bundle.deleted_date,
recovery_id=deleted_key_bundle.recovery_id,
scheduled_purge_date=deleted_key_bundle.scheduled_purge_date,
)
@classmethod
def _from_deleted_key_item(cls, deleted_key_item):
# type: (_models.DeletedKeyItem) -> DeletedKey
"""Construct a DeletedKey from an autorest-generated DeletedKeyItem"""
return cls(
properties=KeyProperties._from_key_item(deleted_key_item), # pylint: disable=protected-access
key_id=deleted_key_item.kid,
deleted_date=deleted_key_item.deleted_date,
recovery_id=deleted_key_item.recovery_id,
scheduled_purge_date=deleted_key_item.scheduled_purge_date,
)
@property
def deleted_date(self):
# type: () -> Optional[datetime]
"""When the key was deleted, in UTC
:rtype: ~datetime.datetime or None
"""
return self._deleted_date
@property
def recovery_id(self):
# type: () -> Optional[str]
"""An identifier used to recover the deleted key. Returns ``None`` if soft-delete is disabled.
:rtype: str or None
"""
return self._recovery_id
@property
def scheduled_purge_date(self):
# type: () -> Optional[datetime]
"""When the key is scheduled to be purged, in UTC. Returns ``None`` if soft-delete is disabled.
:rtype: ~datetime.datetime or None
"""
return self._scheduled_purge_date