1# ------------------------------------
2# Copyright (c) Microsoft Corporation.
3# Licensed under the MIT License.
4# -------------------------------------
5from collections import namedtuple
6from ._shared import parse_key_vault_id
7from ._generated.v7_1.models import JsonWebKey as _JsonWebKey
8
9try:
10    from typing import TYPE_CHECKING
11except ImportError:
12    TYPE_CHECKING = False
13
14if TYPE_CHECKING:
15    # pylint:disable=unused-import
16    from typing import Any, Dict, Optional
17    from datetime import datetime
18    from ._generated.v7_0 import models as _models
19    from ._enums import KeyOperation
20
21KeyOperationResult = namedtuple("KeyOperationResult", ["id", "value"])
22
23
24class JsonWebKey(object):
25    # pylint:disable=too-many-instance-attributes
26    """As defined in http://tools.ietf.org/html/draft-ietf-jose-json-web-key-18. All parameters are optional.
27
28    :param str kid: Key identifier.
29    :param kty: Key Type (kty), as defined in https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40
30    :type kty: ~azure.keyvault.keys.KeyType or str
31    :param key_ops: Allowed operations for the key
32    :type key_ops: list[str or ~azure.keyvault.keys.KeyOperation]
33    :param bytes n: RSA modulus.
34    :param bytes e: RSA public exponent.
35    :param bytes d: RSA private exponent, or the D component of an EC private key.
36    :param bytes dp: RSA private key parameter.
37    :param bytes dq: RSA private key parameter.
38    :param bytes qi: RSA private key parameter.
39    :param bytes p: RSA secret prime.
40    :param bytes q: RSA secret prime, with p < q.
41    :param bytes k: Symmetric key.
42    :param bytes t: HSM Token, used with 'Bring Your Own Key'.
43    :param crv: Elliptic curve name.
44    :type crv: ~azure.keyvault.keys.KeyCurveName or str
45    :param bytes x: X component of an EC public key.
46    :param bytes y: Y component of an EC public key.
47    """
48
49    _FIELDS = ("kid", "kty", "key_ops", "n", "e", "d", "dp", "dq", "qi", "p", "q", "k", "t", "crv", "x", "y")
50
51    def __init__(self, **kwargs):
52        # type: (**Any) -> None
53        for field in self._FIELDS:
54            setattr(self, field, kwargs.get(field))
55
56    def _to_generated_model(self):
57        # type: () -> _JsonWebKey
58        jwk = _JsonWebKey()
59        for field in self._FIELDS:
60            setattr(jwk, field, getattr(self, field))
61        return jwk
62
63
64class KeyProperties(object):
65    """A key's id and attributes."""
66
67    def __init__(self, key_id, attributes=None, **kwargs):
68        # type: (str, Optional[_models.KeyAttributes], **Any) -> None
69        self._attributes = attributes
70        self._id = key_id
71        self._vault_id = KeyVaultKeyIdentifier(key_id)
72        self._managed = kwargs.get("managed", None)
73        self._tags = kwargs.get("tags", None)
74
75    def __repr__(self):
76        # type () -> str
77        return "<KeyProperties [{}]>".format(self.id)[:1024]
78
79    @classmethod
80    def _from_key_bundle(cls, key_bundle):
81        # type: (_models.KeyBundle) -> KeyProperties
82        """Construct a KeyProperties from an autorest-generated KeyBundle"""
83        return cls(
84            key_bundle.key.kid, attributes=key_bundle.attributes, managed=key_bundle.managed, tags=key_bundle.tags
85        )
86
87    @classmethod
88    def _from_key_item(cls, key_item):
89        # type: (_models.KeyItem) -> KeyProperties
90        """Construct a KeyProperties from an autorest-generated KeyItem"""
91        return cls(key_id=key_item.kid, attributes=key_item.attributes, managed=key_item.managed, tags=key_item.tags)
92
93    @property
94    def id(self):
95        # type: () -> str
96        """The key's id
97
98        :rtype: str
99        """
100        return self._id
101
102    @property
103    def name(self):
104        # type: () -> str
105        """The key's name
106
107        :rtype: str
108        """
109        return self._vault_id.name
110
111    @property
112    def version(self):
113        # type: () -> str
114        """The key's version
115
116        :rtype: str
117        """
118        return self._vault_id.version
119
120    @property
121    def enabled(self):
122        # type: () -> bool
123        """Whether the key is enabled for use
124
125        :rtype: bool
126        """
127        return self._attributes.enabled
128
129    @property
130    def not_before(self):
131        # type: () -> datetime
132        """The time before which the key can not be used, in UTC
133
134        :rtype: ~datetime.datetime
135        """
136        return self._attributes.not_before
137
138    @property
139    def expires_on(self):
140        # type: () -> datetime
141        """When the key will expire, in UTC
142
143        :rtype: ~datetime.datetime
144        """
145        return self._attributes.expires
146
147    @property
148    def created_on(self):
149        # type: () -> datetime
150        """When the key was created, in UTC
151
152        :rtype: ~datetime.datetime
153        """
154        return self._attributes.created
155
156    @property
157    def updated_on(self):
158        # type: () -> datetime
159        """When the key was last updated, in UTC
160
161        :rtype: ~datetime.datetime
162        """
163        return self._attributes.updated
164
165    @property
166    def vault_url(self):
167        # type: () -> str
168        """URL of the vault containing the key
169
170        :rtype: str
171        """
172        return self._vault_id.vault_url
173
174    @property
175    def recoverable_days(self):
176        # type: () -> Optional[int]
177        """The number of days the key is retained before being deleted from a soft-delete enabled Key Vault.
178
179        :rtype: int
180        """
181        # recoverable_days was added in 7.1-preview
182        if self._attributes and hasattr(self._attributes, "recoverable_days"):
183            return self._attributes.recoverable_days
184        return None
185
186    @property
187    def recovery_level(self):
188        # type: () -> str
189        """The vault's deletion recovery level for keys
190
191        :rtype: str
192        """
193        return self._attributes.recovery_level
194
195    @property
196    def tags(self):
197        # type: () -> Dict[str, str]
198        """Application specific metadata in the form of key-value pairs
199
200        :rtype: dict[str, str]
201        """
202        return self._tags
203
204    @property
205    def managed(self):
206        # type: () -> bool
207        """Returns whether the key's lifetime is managed by key vault
208
209        :rtype: bool
210        """
211        return self._managed
212
213
214class KeyVaultKey(object):
215    """A key's attributes and cryptographic material.
216
217    :param str key_id:
218        Key Vault's identifier for the key. Typically a URI, e.g. https://myvault.vault.azure.net/keys/my-key/version
219    :param jwk:
220        The key's cryptographic material as a JSON Web Key (https://tools.ietf.org/html/rfc7517). This may be provided
221        as a dictionary or keyword arguments. See :class:`~azure.keyvault.keys.models.JsonWebKey` for field names.
222
223    Providing cryptographic material as keyword arguments:
224
225    .. code-block:: python
226
227        from azure.keyvault.keys.models import KeyVaultKey
228
229        key_id = 'https://myvault.vault.azure.net/keys/my-key/my-key-version'
230        key_bytes = os.urandom(32)
231        key = KeyVaultKey(key_id, k=key_bytes, kty='oct', key_ops=['unwrapKey', 'wrapKey'])
232
233    Providing cryptographic material as a dictionary:
234
235    .. code-block:: python
236
237        from azure.keyvault.keys.models import KeyVaultKey
238
239        key_id = 'https://myvault.vault.azure.net/keys/my-key/my-key-version'
240        key_bytes = os.urandom(32)
241        jwk = {'k': key_bytes, 'kty': 'oct', 'key_ops': ['unwrapKey', 'wrapKey']}
242        key = KeyVaultKey(key_id, jwk=jwk)
243
244    """
245
246    def __init__(self, key_id, jwk=None, **kwargs):
247        # type: (str, Optional[dict], **Any) -> None
248        self._properties = kwargs.pop("properties", None) or KeyProperties(key_id, **kwargs)
249        if isinstance(jwk, dict):
250            if any(field in kwargs for field in JsonWebKey._FIELDS):  # pylint:disable=protected-access
251                raise ValueError(
252                    "Individual keyword arguments for key material and the 'jwk' argument are mutually exclusive."
253                )
254            self._key_material = JsonWebKey(**jwk)
255        else:
256            self._key_material = JsonWebKey(**kwargs)
257
258    def __repr__(self):
259        # type () -> str
260        return "<KeyVaultKey [{}]>".format(self.id)[:1024]
261
262    @classmethod
263    def _from_key_bundle(cls, key_bundle):
264        # type: (_models.KeyBundle) -> KeyVaultKey
265        """Construct a KeyVaultKey from an autorest-generated KeyBundle"""
266        # pylint:disable=protected-access
267        return cls(
268            key_id=key_bundle.key.kid,
269            jwk={field: getattr(key_bundle.key, field, None) for field in JsonWebKey._FIELDS},
270            properties=KeyProperties._from_key_bundle(key_bundle),
271        )
272
273    @property
274    def id(self):
275        # type: () -> str
276        """The key's id
277
278        :rtype: str
279        """
280        return self._properties.id
281
282    @property
283    def name(self):
284        # type: () -> str
285        """The key's name
286
287        :rtype: str
288        """
289        return self._properties.name
290
291    @property
292    def properties(self):
293        # type: () -> KeyProperties
294        """The key's properties
295
296        :rtype: ~azure.keyvault.keys.KeyProperties
297        """
298        return self._properties
299
300    @property
301    def key(self):
302        # type: () -> JsonWebKey
303        """The JSON web key
304
305        :rtype: ~azure.keyvault.keys.JsonWebKey
306        """
307        return self._key_material
308
309    @property
310    def key_type(self):
311        # type: () -> str
312        """The key's type. See :class:`~azure.keyvault.keys.KeyType` for possible values.
313
314        :rtype: ~azure.keyvault.keys.KeyType or str
315        """
316        return self._key_material.kty  # pylint:disable=no-member
317
318    @property
319    def key_operations(self):
320        # type: () -> list[KeyOperation]
321        """Permitted operations. See :class:`~azure.keyvault.keys.KeyOperation` for possible values.
322
323        :rtype: list[~azure.keyvault.keys.KeyOperation or str]
324        """
325        return self._key_material.key_ops  # pylint:disable=no-member
326
327
328class KeyVaultKeyIdentifier(object):
329    """Information about a KeyVaultKey parsed from a key ID.
330
331    :param str source_id: the full original identifier of a key
332    :raises ValueError: if the key ID is improperly formatted
333    Example:
334        .. literalinclude:: ../tests/test_parse_id.py
335            :start-after: [START parse_key_vault_key_id]
336            :end-before: [END parse_key_vault_key_id]
337            :language: python
338            :caption: Parse a key's ID
339            :dedent: 8
340    """
341
342    def __init__(self, source_id):
343        # type: (str) -> None
344        self._resource_id = parse_key_vault_id(source_id)
345
346    @property
347    def source_id(self):
348        # type: () -> str
349        return self._resource_id.source_id
350
351    @property
352    def vault_url(self):
353        # type: () -> str
354        return self._resource_id.vault_url
355
356    @property
357    def name(self):
358        # type: () -> str
359        return self._resource_id.name
360
361    @property
362    def version(self):
363        # type: () -> Optional[str]
364        return self._resource_id.version
365
366
367class DeletedKey(KeyVaultKey):
368    """A deleted key's properties, cryptographic material and its deletion information. If soft-delete
369    is enabled, returns information about its recovery as well."""
370
371    def __init__(
372        self,
373        properties,  # type: KeyProperties
374        deleted_date=None,  # type: Optional[datetime]
375        recovery_id=None,  # type: Optional[str]
376        scheduled_purge_date=None,  # type: Optional[datetime]
377        **kwargs  # type: Any
378    ):
379        # type: (...) -> None
380        super(DeletedKey, self).__init__(properties=properties, **kwargs)
381        self._deleted_date = deleted_date
382        self._recovery_id = recovery_id
383        self._scheduled_purge_date = scheduled_purge_date
384
385    def __repr__(self):
386        # type () -> str
387        return "<DeletedKey [{}]>".format(self.id)[:1024]
388
389    @classmethod
390    def _from_deleted_key_bundle(cls, deleted_key_bundle):
391        # type: (_models.DeletedKeyBundle) -> DeletedKey
392        """Construct a DeletedKey from an autorest-generated DeletedKeyBundle"""
393        # pylint:disable=protected-access
394        return cls(
395            properties=KeyProperties._from_key_bundle(deleted_key_bundle),
396            key_id=deleted_key_bundle.key.kid,
397            jwk={field: getattr(deleted_key_bundle.key, field, None) for field in JsonWebKey._FIELDS},
398            deleted_date=deleted_key_bundle.deleted_date,
399            recovery_id=deleted_key_bundle.recovery_id,
400            scheduled_purge_date=deleted_key_bundle.scheduled_purge_date,
401        )
402
403    @classmethod
404    def _from_deleted_key_item(cls, deleted_key_item):
405        # type: (_models.DeletedKeyItem) -> DeletedKey
406        """Construct a DeletedKey from an autorest-generated DeletedKeyItem"""
407        return cls(
408            properties=KeyProperties._from_key_item(deleted_key_item),  # pylint: disable=protected-access
409            key_id=deleted_key_item.kid,
410            deleted_date=deleted_key_item.deleted_date,
411            recovery_id=deleted_key_item.recovery_id,
412            scheduled_purge_date=deleted_key_item.scheduled_purge_date,
413        )
414
415    @property
416    def deleted_date(self):
417        # type: () -> datetime
418        """When the key was deleted, in UTC
419
420        :rtype: ~datetime.datetime
421        """
422        return self._deleted_date
423
424    @property
425    def recovery_id(self):
426        # type: () -> str
427        """An identifier used to recover the deleted key. Returns ``None`` if soft-delete is disabled.
428
429        :rtype: str
430        """
431        return self._recovery_id
432
433    @property
434    def scheduled_purge_date(self):
435        # type: () -> datetime
436        """When the key is scheduled to be purged, in UTC. Returns ``None`` if soft-delete is disabled.
437
438        :rtype: ~datetime.datetime
439        """
440        return self._scheduled_purge_date
441