# ------------------------------------
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# -------------------------------------
from collections import namedtuple
from datetime import datetime
from typing import Any, Dict, List, Optional, Union, TYPE_CHECKING
from ._enums import KeyOperation, KeyRotationPolicyAction, KeyType
from ._shared import parse_key_vault_id
from ._generated.models import JsonWebKey as _JsonWebKey
if TYPE_CHECKING:
# pylint:disable=unused-import
from ._generated import models as _models
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.
:keyword str kid: Key identifier.
:keyword kty: Key Type (kty), as defined in https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40
:paramtype kty: ~azure.keyvault.keys.KeyType or str
:keyword key_ops: Allowed operations for the key
:paramtype key_ops: list[str or ~azure.keyvault.keys.KeyOperation]
:keyword bytes n: RSA modulus.
:keyword bytes e: RSA public exponent.
:keyword bytes d: RSA private exponent, or the D component of an EC private key.
:keyword bytes dp: RSA private key parameter.
:keyword bytes dq: RSA private key parameter.
:keyword bytes qi: RSA private key parameter.
:keyword bytes p: RSA secret prime.
:keyword bytes q: RSA secret prime, with p < q.
:keyword bytes k: Symmetric key.
:keyword bytes t: HSM Token, used with 'Bring Your Own Key'.
:keyword crv: Elliptic curve name.
:paramtype crv: ~azure.keyvault.keys.KeyCurveName or str
:keyword bytes x: X component of an EC public key.
:keyword 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: Any) -> None:
for field in self._FIELDS:
setattr(self, field, kwargs.get(field))
def _to_generated_model(self) -> _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.
:param str key_id: The key ID.
:param attributes: The key attributes.
:type attributes: ~azure.keyvault.keys._generated.models.KeyAttributes
:keyword bool managed: Whether the key's lifetime is managed by Key Vault.
:keyword tags: Application specific metadata in the form of key-value pairs.
:paramtype tags: dict[str, str] or None
:keyword release_policy: The azure.keyvault.keys.KeyReleasePolicy specifying the rules under which the key
can be exported.
:paramtype release_policy: ~azure.keyvault.keys.KeyReleasePolicy or None
"""
def __init__(self, key_id: str, attributes: "Optional[_models.KeyAttributes]" = None, **kwargs: 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) -> str:
return f"<KeyProperties [{self.id}]>"[:1024]
@classmethod
def _from_key_bundle(cls, key_bundle: "_models.KeyBundle") -> "KeyProperties":
# pylint:disable=line-too-long
# 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(
encoded_policy=key_bundle.release_policy.encoded_policy, # type: ignore
content_type=key_bundle.release_policy.content_type, # type: ignore[attr-defined]
immutable=key_bundle.release_policy.immutable, # 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: "_models.KeyItem") -> "KeyProperties":
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) -> str:
"""The key ID.
:returns: The key ID.
:rtype: str
"""
return self._id
@property
def name(self) -> str:
"""The key name.
:returns: The key name.
:rtype: str
"""
return self._vault_id.name
@property
def version(self) -> Optional[str]:
"""The key version.
:returns: The key version.
:rtype: str or None
"""
return self._vault_id.version
@property
def enabled(self) -> Optional[bool]:
"""Whether the key is enabled for use.
:returns: True if the key is enabled for use; False otherwise.
:rtype: bool or None
"""
return self._attributes.enabled if self._attributes else None
@property
def not_before(self) -> Optional[datetime]:
"""The time before which the key can not be used, in UTC.
:returns: 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) -> Optional[datetime]:
"""When the key will expire, in UTC.
:returns: 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) -> Optional[datetime]:
"""When the key was created, in UTC.
:returns: 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) -> Optional[datetime]:
"""When the key was last updated, in UTC.
:returns: 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) -> str:
"""URL of the vault containing the key.
:returns: URL of the vault containing the key.
:rtype: str
"""
return self._vault_id.vault_url
@property
def recoverable_days(self) -> Optional[int]:
"""The number of days the key is retained before being deleted from a soft-delete enabled Key Vault.
:returns: 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) -> Optional[str]:
"""The vault's deletion recovery level for keys.
:returns: 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) -> Dict[str, str]:
"""Application specific metadata in the form of key-value pairs.
:returns: A dictionary of tags attached to the key.
:rtype: dict[str, str]
"""
return self._tags
@property
def managed(self) -> Optional[bool]:
"""Whether the key's lifetime is managed by Key Vault. If the key backs a certificate, this will be true.
:returns: True if the key's lifetime is managed by Key Vault; False otherwise.
:rtype: bool or None
"""
return self._managed
@property
def exportable(self) -> Optional[bool]:
"""Whether the private key can be exported.
:returns: True if the private key can be exported; False otherwise.
:rtype: bool or None
"""
# exportable was added in 7.3-preview
if self._attributes:
return getattr(self._attributes, "exportable", None)
return None
@property
def release_policy(self) -> "Optional[KeyReleasePolicy]":
"""The :class:`~azure.keyvault.keys.KeyReleasePolicy` specifying the rules under which the key can be exported.
:returns: The key's release policy specifying the rules for exporting.
:rtype: ~azure.keyvault.keys.KeyReleasePolicy or None
"""
return self._release_policy
@property
def hsm_platform(self) -> Optional[str]:
"""The underlying HSM platform.
:returns: The underlying HSM platform.
:rtype: str or None
"""
# hsm_platform was added in 7.5-preview.1
if self._attributes:
return getattr(self._attributes, "hsm_platform", None)
return None
[docs]class KeyReleasePolicy(object):
"""The policy rules under which a key can be exported.
:param bytes encoded_policy: The policy rules under which the key can be released. Encoded based on the
``content_type``. For more information regarding release policy grammar, please refer to:
https://aka.ms/policygrammarkeys for Azure Key Vault; https://aka.ms/policygrammarmhsm for Azure Managed HSM.
:keyword str content_type: Content type and version of the release policy. Defaults to "application/json;
charset=utf-8" if omitted.
:keyword bool immutable: Marks a release policy as immutable. An immutable release policy cannot be changed or
updated after being marked immutable. Release policies are mutable by default.
"""
def __init__(self, encoded_policy: bytes, **kwargs: Any) -> None:
self.encoded_policy = encoded_policy
self.content_type = kwargs.get("content_type", None)
self.immutable = kwargs.get("immutable", None)
[docs]class ReleaseKeyResult(object):
"""The result of a key release operation.
:ivar str value: A signed token containing the released key.
:param str value: A signed token containing the released key.
"""
def __init__(self, value: 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 time_after_create: Time after creation to attempt the specified action, as an ISO 8601 duration.
For example, 90 days is "P90D". See `Wikipedia <https://wikipedia.org/wiki/ISO_8601#Durations>`_ for more
information on ISO 8601 durations.
:paramtype time_after_create: str or None
:keyword time_before_expiry: Time before expiry to attempt the specified action, as an ISO 8601 duration.
For example, 90 days is "P90D". See `Wikipedia <https://wikipedia.org/wiki/ISO_8601#Durations>`_ for more
information on ISO 8601 durations.
:paramtype time_before_expiry: str or None
"""
def __init__(self, action: Union[KeyRotationPolicyAction, str], **kwargs: Any) -> None:
self.action = action
self.time_after_create: Optional[str] = kwargs.get("time_after_create", None)
self.time_before_expiry: Optional[str] = kwargs.get("time_before_expiry", None)
@classmethod
def _from_generated(cls, lifetime_action: "_models.LifetimeActions") -> "KeyRotationLifetimeAction":
if lifetime_action.action:
if lifetime_action.trigger:
return cls(
action=lifetime_action.action.type, # type: ignore
time_after_create=lifetime_action.trigger.time_after_create,
time_before_expiry=lifetime_action.trigger.time_before_expiry,
)
return cls(action=lifetime_action.action) # type: ignore
raise ValueError("Provided LifetimeActions model is missing a required lifetime action property.")
[docs]class KeyRotationPolicy(object):
"""The key rotation policy that belongs to a key.
:ivar id: The identifier of the key rotation policy.
:vartype id: str or None
:ivar lifetime_actions: Actions that will be performed by Key Vault over the lifetime of a key.
:vartype lifetime_actions: list[~azure.keyvault.keys.KeyRotationLifetimeAction]
:ivar 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". See `Wikipedia <https://wikipedia.org/wiki/ISO_8601#Durations>`_ for
more information on ISO 8601 durations.
:vartype expires_in: str or None
:ivar created_on: When the policy was created, in UTC
:vartype created_on: ~datetime.datetime or None
:ivar updated_on: When the policy was last updated, in UTC
:vartype updated_on: ~datetime.datetime or None
"""
def __init__(self, **kwargs: Any) -> None:
self.id = kwargs.get("policy_id", None)
self.lifetime_actions: List[KeyRotationLifetimeAction] = kwargs.get("lifetime_actions", [])
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: "_models.KeyRotationPolicy") -> "KeyRotationPolicy":
lifetime_actions = (
[]
if policy.lifetime_actions is None
else [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.
:type jwk: Dict[str, Any]
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: str, jwk: Optional[Dict[str, Any]] = None, **kwargs) -> None:
self._properties: KeyProperties = 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) -> str:
return f"<KeyVaultKey [{self.id}]>"[:1024]
@classmethod
def _from_key_bundle(cls, key_bundle: "_models.KeyBundle") -> "KeyVaultKey":
# 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) -> str:
"""The key ID.
:returns: The key ID.
:rtype: str
"""
return self._properties.id
@property
def name(self) -> str:
"""The key name.
:returns: The key name.
:rtype: str
"""
return self._properties.name
@property
def properties(self) -> KeyProperties:
"""The key properties.
:returns: The key properties.
:rtype: ~azure.keyvault.keys.KeyProperties
"""
return self._properties
@property
def key(self) -> JsonWebKey:
"""The JSON Web Key (JWK) for the key.
:returns: The JSON Web Key (JWK) for the key.
:rtype: ~azure.keyvault.keys.JsonWebKey
"""
return self._key_material
@property
def key_type(self) -> Union[str, KeyType]:
"""The key's type. See :class:`~azure.keyvault.keys.KeyType` for possible values.
:returns: 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) -> List[Union[str, KeyOperation]]:
"""Permitted operations. See :class:`~azure.keyvault.keys.KeyOperation` for possible values.
:returns: 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: str) -> None:
self._resource_id = parse_key_vault_id(source_id)
@property
def source_id(self) -> str:
return self._resource_id.source_id
@property
def vault_url(self) -> str:
return self._resource_id.vault_url
@property
def name(self) -> str:
return self._resource_id.name
@property
def version(self) -> 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.
:param properties: Properties of the deleted key.
:type properties: ~azure.keyvault.keys.KeyProperties
:param deleted_date: When the key was deleted, in UTC.
:type deleted_date: ~datetime.datetime or None
:param recovery_id: An identifier used to recover the deleted key. Returns ``None`` if soft-delete is disabled.
:type recovery_id: str or None
:param scheduled_purge_date: When the key is scheduled to be purged, in UTC. Returns ``None`` if soft-delete is
disabled.
:type scheduled_purge_date: ~datetime.datetime or None
"""
def __init__(
self,
properties: KeyProperties,
deleted_date: Optional[datetime] = None,
recovery_id: Optional[str] = None,
scheduled_purge_date: Optional[datetime] = None,
**kwargs: Any,
) -> 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) -> str:
return f"<DeletedKey [{self.id}]>"[:1024]
@classmethod
def _from_deleted_key_bundle(cls, deleted_key_bundle: "_models.DeletedKeyBundle") -> "DeletedKey":
# 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: "_models.DeletedKeyItem") -> "DeletedKey":
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) -> Optional[datetime]:
"""When the key was deleted, in UTC.
:returns: When the key was deleted, in UTC.
:rtype: ~datetime.datetime or None
"""
return self._deleted_date
@property
def recovery_id(self) -> Optional[str]:
"""An identifier used to recover the deleted key. Returns ``None`` if soft-delete is disabled.
:returns: 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) -> Optional[datetime]:
"""When the key is scheduled to be purged, in UTC. Returns ``None`` if soft-delete is disabled.
:returns: 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