File: cloudhsm.rst

package info (click to toggle)
python-cryptography 46.0.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 5,684 kB
  • sloc: python: 52,706; java: 319; makefile: 160
file content (169 lines) | stat: -rw-r--r-- 6,792 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
.. hazmat::

Cloud KMS and HSM Asymmetric Keys
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. testsetup::

    """
    We need to have this exist so the doctest below allows us to
    test that we're satisfying the base class requirements.
    """
    class Response:
        def __init__(self, signature):
            self.signature = signature

    class SomeCloudClient:
        def __init__(self, creds):
            pass

        def sign(self, key_id, algorithm, message):
            return Response(b"\x00" * (self.key_size(key_id) // 8))

        def key_size(self, key_id):
            return 2048

``cryptography`` provides a set of abstract base classes for asymmetric keys
that can be used to integrate with cloud key management services, HSMs, and other ways of managing keys that are not in-memory.
A minimal example with a hypothetical cloud key management service for an RSA
key is provided below, but this works for all asymmetric types. You must provide
all methods of the base class, but many methods can be stubs with no implementation
if you only need a subset of functionality.

.. doctest::

    >>> import typing
    >>> from cryptography.hazmat.primitives.asymmetric import rsa, utils
    >>> from cryptography.hazmat.primitives import hashes, serialization
    >>> from cryptography.hazmat.primitives.asymmetric.padding import AsymmetricPadding, PKCS1v15
    >>>
    >>> class CloudRSAPrivateKey(rsa.RSAPrivateKey):
    ...     def __init__(self, creds, key_id):
    ...         self._creds = creds
    ...         self._cloud_client = SomeCloudClient(creds)
    ...         self._key_id = key_id
    ...
    ...     def sign(
    ...         self,
    ...         data: bytes,
    ...         padding: AsymmetricPadding,
    ...         algorithm: typing.Union[utils.Prehashed, hashes.HashAlgorithm],
    ...     ) -> bytes:
    ...         """
    ...         Signs data using the cloud KMS. You'll need to define a mapping
    ...         between the way your cloud provider represents padding and algorithms
    ...         and the way cryptography represents them.
    ...         """
    ...
    ...         # Hash the data if necessary
    ...         if not isinstance(algorithm, utils.Prehashed):
    ...             h = hashes.Hash(algorithm)
    ...             h.update(data)
    ...             digest = h.finalize()
    ...             hash_alg = algorithm
    ...         else:
    ...             digest = data
    ...             hash_alg = algorithm._algorithm
    ...         # Map cryptography padding/algorithm to KMS signing algorithm
    ...         kms_algorithm = self._map_to_kms_algorithm(padding, hash_alg)
    ...
    ...         # Call KMS API to sign the digest
    ...         response = self._cloud_client.sign(
    ...             key_id=self._key_id,
    ...             algorithm=kms_algorithm,
    ...             message=digest,
    ...         )
    ...
    ...         return response.signature
    ...
    ...     def decrypt(self, ciphertext: bytes, padding: AsymmetricPadding) -> bytes:
    ...         raise NotImplementedError()
    ...
    ...     def _map_to_kms_algorithm(
    ...         self,
    ...         padding: AsymmetricPadding,
    ...         algorithm: hashes.HashAlgorithm
    ...     ) -> bytes:
    ...         """
    ...         Maps the cryptography padding and algorithm to the corresponding KMS signing algorithm.
    ...         This is specific to your implementation.
    ...         """
    ...         if isinstance(padding, PKCS1v15) and isinstance(algorithm, hashes.SHA256):
    ...             return b"RSA_PKCS1_V1_5_SHA_256"
    ...         else:
    ...             raise NotImplementedError()
    ...
    ...     @property
    ...     def key_size(self) -> int:
    ...         return self._cloud_client.key_size(self._key_id)
    ...
    ...     def public_key(self) -> rsa.RSAPublicKey:
    ...         raise NotImplementedError()
    ...
    ...     def private_numbers(self) -> rsa.RSAPrivateNumbers:
    ...         """
    ...         This method typically can't be implemented for cloud KMS keys
    ...         as the private key material is not accessible.
    ...         """
    ...         raise NotImplementedError()
    ...
    ...     def private_bytes(
    ...         self,
    ...         encoding: serialization.Encoding,
    ...         format: serialization.PrivateFormat,
    ...         encryption_algorithm: serialization.KeySerializationEncryption,
    ...     ) -> bytes:
    ...         """
    ...         This method typically can't be implemented for cloud KMS keys
    ...         as the private key material is not accessible.
    ...         """
    ...         raise NotImplementedError()
    ...
    ...     def __copy__(self) -> "CloudRSAPrivateKey":
    ...         return self
    ...
    >>> cloud_private_key = CloudRSAPrivateKey("creds", "key_id")
    >>> sig = cloud_private_key.sign(b"message", PKCS1v15(), hashes.SHA256())
    >>> isinstance(sig, bytes)
    True

This key can then be used with other parts of ``cryptography``, such as the X.509 APIs.
In the example below we assume that we are using our cloud private key to sign
a leaf certificate (not self-signed).

.. doctest::

        >>> from cryptography import x509
        >>> from cryptography.x509.oid import NameOID
        >>> import datetime
        >>> one_day = datetime.timedelta(1, 0, 0)
        >>> leaf_private_key = rsa.generate_private_key(
        ...     public_exponent=65537,
        ...     key_size=2048,
        ... )
        >>> leaf_public_key = leaf_private_key.public_key()
        >>> builder = x509.CertificateBuilder()
        >>> builder = builder.subject_name(x509.Name([
        ...     x509.NameAttribute(NameOID.COMMON_NAME, 'cryptography.io'),
        ... ]))
        >>> builder = builder.issuer_name(x509.Name([
        ...     x509.NameAttribute(NameOID.COMMON_NAME, 'My Cloud CA'),
        ... ]))
        >>> builder = builder.not_valid_before(datetime.datetime.today() - one_day)
        >>> builder = builder.not_valid_after(datetime.datetime.today() + (one_day * 30))
        >>> builder = builder.serial_number(x509.random_serial_number())
        >>> builder = builder.public_key(leaf_public_key)
        >>> builder = builder.add_extension(
        ...     x509.SubjectAlternativeName(
        ...         [x509.DNSName('cryptography.io')]
        ...     ),
        ...     critical=False
        ... )
        >>> builder = builder.add_extension(
        ...     x509.BasicConstraints(ca=False, path_length=None), critical=True,
        ... )
        >>> certificate = builder.sign(
        ...     private_key=cloud_private_key, algorithm=hashes.SHA256(),
        ... )
        >>> isinstance(certificate, x509.Certificate)
        True