Browse Source

Add signature_utils module

This change ports Nova's signature_utils module
into the cursive library.

Change-Id: Ic54dc204e41b3758bc2e8e1571d697931b371889
Partial-Bug: #1528349
dane-fichter 2 years ago
parent
commit
b2aba64263

+ 52
- 0
cursive/exception.py View File

@@ -0,0 +1,52 @@
1
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+#    not use this file except in compliance with the License. You may obtain
3
+#    a copy of the License at
4
+#
5
+#         http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+#    Unless required by applicable law or agreed to in writing, software
8
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+#    License for the specific language governing permissions and limitations
11
+#    under the License.
12
+
13
+"""Cursive base exception handling"""
14
+
15
+from cursive.i18n import _
16
+
17
+
18
+class CursiveException(Exception):
19
+    """Base Cursive Exception
20
+
21
+    To correctly use this class, inherit from it and define
22
+    a 'msg_fmt' property. That msg_fmt will get printf'd
23
+    with the keyword arguments provided to the constructor.
24
+
25
+    """
26
+    msg_fmt = _("An unknown exception occurred.")
27
+    headers = {}
28
+    safe = False
29
+
30
+    def __init__(self, message=None, **kwargs):
31
+        self.kwargs = kwargs
32
+
33
+        if not message:
34
+            try:
35
+                message = self.msg_fmt % kwargs
36
+
37
+            except Exception:
38
+                # at least get the core message out if something happened
39
+                message = self.msg_fmt
40
+
41
+        self.message = message
42
+        super(CursiveException, self).__init__(message)
43
+
44
+    def format_message(self):
45
+        # NOTE(dane-fichter): use the first argument to the python Exception
46
+        # object which should be our full CursiveException message
47
+        return self.args[0]
48
+
49
+
50
+class SignatureVerificationError(CursiveException):
51
+    msg_fmt = _("Signature verification for the image "
52
+                "failed: %(reason)s.")

+ 36
- 0
cursive/i18n.py View File

@@ -0,0 +1,36 @@
1
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+# not use this file except in compliance with the License. You may obtain
3
+# a copy of the License at
4
+#
5
+#      http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+# Unless required by applicable law or agreed to in writing, software
8
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+# License for the specific language governing permissions and limitations
11
+# under the License.
12
+
13
+"""oslo.i18n integration module.
14
+
15
+See http://docs.openstack.org/developer/oslo.i18n/usage.html .
16
+
17
+"""
18
+
19
+import oslo_i18n
20
+
21
+DOMAIN = 'cursive'
22
+
23
+_translators = oslo_i18n.TranslatorFactory(domain=DOMAIN)
24
+
25
+# The primary translation function using the well-known name "_"
26
+_ = _translators.primary
27
+
28
+# Translators for log levels.
29
+#
30
+# The abbreviated names are meant to reflect the usual use of a short
31
+# name like '_'. The "L" is for "log" and the other letter comes from
32
+# the level.
33
+_LI = _translators.log_info
34
+_LW = _translators.log_warning
35
+_LE = _translators.log_error
36
+_LC = _translators.log_critical

+ 339
- 0
cursive/signature_utils.py View File

@@ -0,0 +1,339 @@
1
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+#    not use this file except in compliance with the License. You may obtain
3
+#    a copy of the License at
4
+#
5
+#         http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+#    Unless required by applicable law or agreed to in writing, software
8
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+#    License for the specific language governing permissions and limitations
11
+#    under the License.
12
+
13
+"""Support signature verification."""
14
+
15
+import binascii
16
+
17
+from castellan.common.exception import KeyManagerError
18
+from castellan import key_manager
19
+from cryptography.hazmat.backends import default_backend
20
+from cryptography.hazmat.primitives.asymmetric import dsa
21
+from cryptography.hazmat.primitives.asymmetric import ec
22
+from cryptography.hazmat.primitives.asymmetric import padding
23
+from cryptography.hazmat.primitives.asymmetric import rsa
24
+from cryptography.hazmat.primitives import hashes
25
+from cryptography import x509
26
+from oslo_log import log as logging
27
+from oslo_serialization import base64
28
+from oslo_utils import encodeutils
29
+from oslo_utils import timeutils
30
+
31
+from cursive import exception
32
+from cursive.i18n import _, _LE
33
+
34
+LOG = logging.getLogger(__name__)
35
+
36
+
37
+HASH_METHODS = {
38
+    'SHA-224': hashes.SHA224(),
39
+    'SHA-256': hashes.SHA256(),
40
+    'SHA-384': hashes.SHA384(),
41
+    'SHA-512': hashes.SHA512(),
42
+}
43
+
44
+# Currently supported signature key types
45
+# RSA Options
46
+RSA_PSS = 'RSA-PSS'
47
+# DSA Options
48
+DSA = 'DSA'
49
+
50
+# ECC curves -- note that only those with key sizes >=384 are included
51
+# Note also that some of these may not be supported by the cryptography backend
52
+ECC_CURVES = (
53
+    ec.SECT571K1(),
54
+    ec.SECT409K1(),
55
+    ec.SECT571R1(),
56
+    ec.SECT409R1(),
57
+    ec.SECP521R1(),
58
+    ec.SECP384R1(),
59
+)
60
+
61
+# These are the currently supported certificate formats
62
+X_509 = 'X.509'
63
+
64
+CERTIFICATE_FORMATS = {
65
+    X_509,
66
+}
67
+
68
+# These are the currently supported MGF formats, used for RSA-PSS signatures
69
+MASK_GEN_ALGORITHMS = {
70
+    'MGF1': padding.MGF1,
71
+}
72
+
73
+
74
+class SignatureKeyType(object):
75
+
76
+    _REGISTERED_TYPES = {}
77
+
78
+    def __init__(self, name, public_key_type, create_verifier):
79
+        self.name = name
80
+        self.public_key_type = public_key_type
81
+        self.create_verifier = create_verifier
82
+
83
+    @classmethod
84
+    def register(cls, name, public_key_type, create_verifier):
85
+        """Register a signature key type.
86
+
87
+        :param name: the name of the signature key type
88
+        :param public_key_type: e.g. RSAPublicKey, DSAPublicKey, etc.
89
+        :param create_verifier: a function to create a verifier for this type
90
+        """
91
+        cls._REGISTERED_TYPES[name] = cls(name,
92
+                                          public_key_type,
93
+                                          create_verifier)
94
+
95
+    @classmethod
96
+    def lookup(cls, name):
97
+        """Look up the signature key type.
98
+
99
+        :param name: the name of the signature key type
100
+        :returns: the SignatureKeyType object
101
+        :raises: SignatureVerificationError if signature key type is invalid
102
+        """
103
+        if name not in cls._REGISTERED_TYPES:
104
+            raise exception.SignatureVerificationError(
105
+                reason=_('Invalid signature key type: %s') % name)
106
+        return cls._REGISTERED_TYPES[name]
107
+
108
+
109
+# each key type will require its own verifier
110
+def create_verifier_for_pss(signature, hash_method, public_key):
111
+    """Create the verifier to use when the key type is RSA-PSS.
112
+
113
+    :param signature: the decoded signature to use
114
+    :param hash_method: the hash method to use, as a cryptography object
115
+    :param public_key: the public key to use, as a cryptography object
116
+    :raises: SignatureVerificationError if the RSA-PSS specific properties
117
+                                        are invalid
118
+    :returns: the verifier to use to verify the signature for RSA-PSS
119
+    """
120
+    # default to MGF1
121
+    mgf = padding.MGF1(hash_method)
122
+
123
+    # default to max salt length
124
+    salt_length = padding.PSS.MAX_LENGTH
125
+
126
+    # return the verifier
127
+    return public_key.verifier(
128
+        signature,
129
+        padding.PSS(mgf=mgf, salt_length=salt_length),
130
+        hash_method
131
+    )
132
+
133
+
134
+def create_verifier_for_ecc(signature, hash_method, public_key):
135
+    """Create the verifier to use when the key type is ECC_*.
136
+
137
+    :param signature: the decoded signature to use
138
+    :param hash_method: the hash method to use, as a cryptography object
139
+    :param public_key: the public key to use, as a cryptography object
140
+    :returns: the verifier to use to verify the signature for ECC_*.
141
+    """
142
+    # return the verifier
143
+    return public_key.verifier(
144
+        signature,
145
+        ec.ECDSA(hash_method)
146
+    )
147
+
148
+
149
+def create_verifier_for_dsa(signature, hash_method, public_key):
150
+    """Create the verifier to use when the key type is DSA
151
+
152
+    :param signature: the decoded signature to use
153
+    :param hash_method: the hash method to use, as a cryptography object
154
+    :param public_key: the public key to use, as a cryptography object
155
+    :returns: the verifier to use to verify the signature for DSA
156
+    """
157
+    # return the verifier
158
+    return public_key.verifier(
159
+        signature,
160
+        hash_method
161
+    )
162
+
163
+
164
+SignatureKeyType.register(RSA_PSS, rsa.RSAPublicKey, create_verifier_for_pss)
165
+SignatureKeyType.register(DSA, dsa.DSAPublicKey, create_verifier_for_dsa)
166
+
167
+# Register the elliptic curves which are supported by the backend
168
+for curve in ECC_CURVES:
169
+    if default_backend().elliptic_curve_supported(curve):
170
+        SignatureKeyType.register('ECC_' + curve.name.upper(),
171
+                                  ec.EllipticCurvePublicKey,
172
+                                  create_verifier_for_ecc)
173
+
174
+
175
+def get_verifier(context, img_signature_certificate_uuid,
176
+                 img_signature_hash_method, img_signature,
177
+                 img_signature_key_type):
178
+    """Instantiate signature properties and use them to create a verifier.
179
+
180
+    :param context: the user context for authentication
181
+    :param img_signature_certificate_uuid:
182
+           uuid of signing certificate stored in key manager
183
+    :param img_signature_hash_method:
184
+           string denoting hash method used to compute signature
185
+    :param img_signature: string of base64 encoding of signature
186
+    :param img_signature_key_type:
187
+           string denoting type of keypair used to compute signature
188
+    :returns: instance of
189
+       cryptography.hazmat.primitives.asymmetric.AsymmetricVerificationContext
190
+    :raises: SignatureVerificationError if we fail to build the verifier
191
+    """
192
+    image_meta_props = {'img_signature_uuid': img_signature_certificate_uuid,
193
+                        'img_signature_hash_method': img_signature_hash_method,
194
+                        'img_signature': img_signature,
195
+                        'img_signature_key_type': img_signature_key_type}
196
+    for key in image_meta_props.keys():
197
+        if image_meta_props[key] is None:
198
+            raise exception.SignatureVerificationError(
199
+                reason=_('Required image properties for signature verification'
200
+                         ' do not exist. Cannot verify signature. Missing'
201
+                         ' property: %s') % key)
202
+
203
+    signature = get_signature(img_signature)
204
+    hash_method = get_hash_method(img_signature_hash_method)
205
+    signature_key_type = SignatureKeyType.lookup(img_signature_key_type)
206
+    public_key = get_public_key(context,
207
+                                img_signature_certificate_uuid,
208
+                                signature_key_type)
209
+
210
+    # create the verifier based on the signature key type
211
+    verifier = signature_key_type.create_verifier(signature,
212
+                                                  hash_method,
213
+                                                  public_key)
214
+    if verifier:
215
+        return verifier
216
+    else:
217
+        # Error creating the verifier
218
+        raise exception.SignatureVerificationError(
219
+            reason=_('Error occurred while creating the verifier'))
220
+
221
+
222
+def get_signature(signature_data):
223
+    """Decode the signature data and returns the signature.
224
+
225
+    :param signature_data: the base64-encoded signature data
226
+    :returns: the decoded signature
227
+    :raises: SignatureVerificationError if the signature data is malformatted
228
+    """
229
+    try:
230
+        signature = base64.decode_as_bytes(signature_data)
231
+    except (TypeError, binascii.Error):
232
+        raise exception.SignatureVerificationError(
233
+            reason=_('The signature data was not properly '
234
+                     'encoded using base64'))
235
+
236
+    return signature
237
+
238
+
239
+def get_hash_method(hash_method_name):
240
+    """Verify the hash method name and create the hash method.
241
+
242
+    :param hash_method_name: the name of the hash method to retrieve
243
+    :returns: the hash method, a cryptography object
244
+    :raises: SignatureVerificationError if the hash method name is invalid
245
+    """
246
+    if hash_method_name not in HASH_METHODS:
247
+        raise exception.SignatureVerificationError(
248
+            reason=_('Invalid signature hash method: %s') % hash_method_name)
249
+
250
+    return HASH_METHODS[hash_method_name]
251
+
252
+
253
+def get_public_key(context, signature_certificate_uuid, signature_key_type):
254
+    """Create the public key object from a retrieved certificate.
255
+
256
+    :param context: the user context for authentication
257
+    :param signature_certificate_uuid: the uuid to use to retrieve the
258
+                                       certificate
259
+    :param signature_key_type: a SignatureKeyType object
260
+    :returns: the public key cryptography object
261
+    :raises: SignatureVerificationError if public key format is invalid
262
+    """
263
+    certificate = get_certificate(context, signature_certificate_uuid)
264
+
265
+    # Note that this public key could either be
266
+    # RSAPublicKey, DSAPublicKey, or EllipticCurvePublicKey
267
+    public_key = certificate.public_key()
268
+
269
+    # Confirm the type is of the type expected based on the signature key type
270
+    if not isinstance(public_key, signature_key_type.public_key_type):
271
+        raise exception.SignatureVerificationError(
272
+            reason=_('Invalid public key type for signature key type: %s')
273
+            % signature_key_type.name)
274
+
275
+    return public_key
276
+
277
+
278
+def get_certificate(context, signature_certificate_uuid):
279
+    """Create the certificate object from the retrieved certificate data.
280
+
281
+    :param context: the user context for authentication
282
+    :param signature_certificate_uuid: the uuid to use to retrieve the
283
+                                       certificate
284
+    :returns: the certificate cryptography object
285
+    :raises: SignatureVerificationError if the retrieval fails or the format
286
+             is invalid
287
+    """
288
+    keymgr_api = key_manager.API()
289
+
290
+    try:
291
+        # The certificate retrieved here is a castellan certificate object
292
+        cert = keymgr_api.get(context, signature_certificate_uuid)
293
+    except KeyManagerError as e:
294
+        # The problem encountered may be backend-specific, since castellan
295
+        # can use different backends.  Rather than importing all possible
296
+        # backends here, the generic "Exception" is used.
297
+        msg = (_LE("Unable to retrieve certificate with ID %(id)s: %(e)s")
298
+               % {'id': signature_certificate_uuid,
299
+                  'e': encodeutils.exception_to_unicode(e)})
300
+        LOG.error(msg)
301
+        raise exception.SignatureVerificationError(
302
+            reason=_('Unable to retrieve certificate with ID: %s')
303
+            % signature_certificate_uuid)
304
+
305
+    if cert.format not in CERTIFICATE_FORMATS:
306
+        raise exception.SignatureVerificationError(
307
+            reason=_('Invalid certificate format: %s') % cert.format)
308
+
309
+    if cert.format == X_509:
310
+        # castellan always encodes certificates in DER format
311
+        cert_data = cert.get_encoded()
312
+        certificate = x509.load_der_x509_certificate(cert_data,
313
+                                                     default_backend())
314
+
315
+    # verify the certificate
316
+    verify_certificate(certificate)
317
+
318
+    return certificate
319
+
320
+
321
+def verify_certificate(certificate):
322
+    """Verify that the certificate has not expired.
323
+
324
+    :param certificate: the cryptography certificate object
325
+    :raises: SignatureVerificationError if the certificate valid time range
326
+             does not include now
327
+    """
328
+    # Get now in UTC, since certificate returns times in UTC
329
+    now = timeutils.utcnow()
330
+
331
+    # Confirm the certificate valid time range includes now
332
+    if now < certificate.not_valid_before:
333
+        raise exception.SignatureVerificationError(
334
+            reason=_('Certificate is not valid before: %s UTC')
335
+            % certificate.not_valid_before)
336
+    elif now > certificate.not_valid_after:
337
+        raise exception.SignatureVerificationError(
338
+            reason=_('Certificate is not valid after: %s UTC')
339
+            % certificate.not_valid_after)

+ 0
- 28
cursive/tests/test_cursive.py View File

@@ -1,28 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-
3
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
4
-# not use this file except in compliance with the License. You may obtain
5
-# a copy of the License at
6
-#
7
-#      http://www.apache.org/licenses/LICENSE-2.0
8
-#
9
-# Unless required by applicable law or agreed to in writing, software
10
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
-# License for the specific language governing permissions and limitations
13
-# under the License.
14
-
15
-"""
16
-test_cursive
17
-----------------------------------
18
-
19
-Tests for `cursive` module.
20
-"""
21
-
22
-from cursive.tests import base
23
-
24
-
25
-class TestCursive(base.TestCase):
26
-
27
-    def test_something(self):
28
-        pass

+ 19
- 0
cursive/tests/unit/__init__.py View File

@@ -0,0 +1,19 @@
1
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+#    not use this file except in compliance with the License. You may obtain
3
+#    a copy of the License at
4
+#
5
+#         http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+#    Unless required by applicable law or agreed to in writing, software
8
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+#    License for the specific language governing permissions and limitations
11
+#    under the License.
12
+
13
+"""
14
+:mod:`cursive.tests.unit` -- Cursive Unittests
15
+=====================================================
16
+
17
+.. automodule:: cursive.tests.unit
18
+   :platform: Unix
19
+"""

+ 347
- 0
cursive/tests/unit/test_signature_utils.py View File

@@ -0,0 +1,347 @@
1
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+#    not use this file except in compliance with the License. You may obtain
3
+#    a copy of the License at
4
+#
5
+#         http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+#    Unless required by applicable law or agreed to in writing, software
8
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+#    License for the specific language governing permissions and limitations
11
+#    under the License.
12
+
13
+import base64
14
+import datetime
15
+
16
+from castellan.common.exception import KeyManagerError
17
+import cryptography.exceptions as crypto_exceptions
18
+from cryptography.hazmat.backends import default_backend
19
+from cryptography.hazmat.primitives.asymmetric import dsa
20
+from cryptography.hazmat.primitives.asymmetric import ec
21
+from cryptography.hazmat.primitives.asymmetric import padding
22
+from cryptography.hazmat.primitives.asymmetric import rsa
23
+import mock
24
+from oslo_utils import timeutils
25
+
26
+from cursive import exception
27
+from cursive import signature_utils
28
+from cursive.tests import base
29
+
30
+TEST_RSA_PRIVATE_KEY = rsa.generate_private_key(public_exponent=3,
31
+                                                key_size=1024,
32
+                                                backend=default_backend())
33
+
34
+# secp521r1 is assumed to be available on all supported platforms
35
+TEST_ECC_PRIVATE_KEY = ec.generate_private_key(ec.SECP521R1(),
36
+                                               default_backend())
37
+
38
+TEST_DSA_PRIVATE_KEY = dsa.generate_private_key(key_size=3072,
39
+                                                backend=default_backend())
40
+
41
+
42
+class FakeKeyManager(object):
43
+
44
+    def __init__(self):
45
+        self.certs = {'invalid_format_cert':
46
+                      FakeCastellanCertificate('A' * 256, 'BLAH'),
47
+                      'valid_format_cert':
48
+                      FakeCastellanCertificate('A' * 256, 'X.509')}
49
+
50
+    def get(self, context, cert_uuid):
51
+        cert = self.certs.get(cert_uuid)
52
+
53
+        if cert is None:
54
+            raise KeyManagerError("No matching certificate found.")
55
+
56
+        return cert
57
+
58
+
59
+class FakeCastellanCertificate(object):
60
+
61
+    def __init__(self, data, cert_format):
62
+        self.data = data
63
+        self.cert_format = cert_format
64
+
65
+    @property
66
+    def format(self):
67
+        return self.cert_format
68
+
69
+    def get_encoded(self):
70
+        return self.data
71
+
72
+
73
+class FakeCryptoCertificate(object):
74
+
75
+    def __init__(self, pub_key=TEST_RSA_PRIVATE_KEY.public_key(),
76
+                 not_valid_before=(timeutils.utcnow() -
77
+                                   datetime.timedelta(hours=1)),
78
+                 not_valid_after=(timeutils.utcnow() +
79
+                                  datetime.timedelta(hours=2))):
80
+        self.pub_key = pub_key
81
+        self.cert_not_valid_before = not_valid_before
82
+        self.cert_not_valid_after = not_valid_after
83
+
84
+    def public_key(self):
85
+        return self.pub_key
86
+
87
+    @property
88
+    def not_valid_before(self):
89
+        return self.cert_not_valid_before
90
+
91
+    @property
92
+    def not_valid_after(self):
93
+        return self.cert_not_valid_after
94
+
95
+
96
+class BadPublicKey(object):
97
+
98
+    def verifier(self, signature, padding, hash_method):
99
+        return None
100
+
101
+
102
+class TestSignatureUtils(base.TestCase):
103
+    """Test methods of signature_utils"""
104
+
105
+    @mock.patch('cursive.signature_utils.get_public_key')
106
+    def test_verify_signature_PSS(self, mock_get_pub_key):
107
+        data = b'224626ae19824466f2a7f39ab7b80f7f'
108
+        mock_get_pub_key.return_value = TEST_RSA_PRIVATE_KEY.public_key()
109
+        for hash_name, hash_alg in signature_utils.HASH_METHODS.items():
110
+            signer = TEST_RSA_PRIVATE_KEY.signer(
111
+                padding.PSS(
112
+                    mgf=padding.MGF1(hash_alg),
113
+                    salt_length=padding.PSS.MAX_LENGTH
114
+                ),
115
+                hash_alg
116
+            )
117
+            signer.update(data)
118
+            signature = base64.b64encode(signer.finalize())
119
+            img_sig_cert_uuid = 'fea14bc2-d75f-4ba5-bccc-b5c924ad0693'
120
+            verifier = signature_utils.get_verifier(None, img_sig_cert_uuid,
121
+                                                    hash_name, signature,
122
+                                                    signature_utils.RSA_PSS)
123
+            verifier.update(data)
124
+            verifier.verify()
125
+
126
+    @mock.patch('cursive.signature_utils.get_public_key')
127
+    def test_verify_signature_ECC(self, mock_get_pub_key):
128
+        data = b'224626ae19824466f2a7f39ab7b80f7f'
129
+        # test every ECC curve
130
+        for curve in signature_utils.ECC_CURVES:
131
+            key_type_name = 'ECC_' + curve.name.upper()
132
+            try:
133
+                signature_utils.SignatureKeyType.lookup(key_type_name)
134
+            except exception.SignatureVerificationError:
135
+                import warnings
136
+                warnings.warn("ECC curve '%s' not supported" % curve.name)
137
+                continue
138
+
139
+            # Create a private key to use
140
+            private_key = ec.generate_private_key(curve,
141
+                                                  default_backend())
142
+            mock_get_pub_key.return_value = private_key.public_key()
143
+            for hash_name, hash_alg in signature_utils.HASH_METHODS.items():
144
+                signer = private_key.signer(
145
+                    ec.ECDSA(hash_alg)
146
+                )
147
+                signer.update(data)
148
+                signature = base64.b64encode(signer.finalize())
149
+                img_sig_cert_uuid = 'fea14bc2-d75f-4ba5-bccc-b5c924ad0693'
150
+                verifier = signature_utils.get_verifier(None,
151
+                                                        img_sig_cert_uuid,
152
+                                                        hash_name, signature,
153
+                                                        key_type_name)
154
+                verifier.update(data)
155
+                verifier.verify()
156
+
157
+    @mock.patch('cursive.signature_utils.get_public_key')
158
+    def test_verify_signature_DSA(self, mock_get_pub_key):
159
+        data = b'224626ae19824466f2a7f39ab7b80f7f'
160
+        mock_get_pub_key.return_value = TEST_DSA_PRIVATE_KEY.public_key()
161
+        for hash_name, hash_alg in signature_utils.HASH_METHODS.items():
162
+            signer = TEST_DSA_PRIVATE_KEY.signer(
163
+                hash_alg
164
+            )
165
+            signer.update(data)
166
+            signature = base64.b64encode(signer.finalize())
167
+            img_sig_cert_uuid = 'fea14bc2-d75f-4ba5-bccc-b5c924ad0693'
168
+            verifier = signature_utils.get_verifier(None, img_sig_cert_uuid,
169
+                                                    hash_name, signature,
170
+                                                    signature_utils.DSA)
171
+            verifier.update(data)
172
+            verifier.verify()
173
+
174
+    @mock.patch('cursive.signature_utils.get_public_key')
175
+    def test_verify_signature_bad_signature(self, mock_get_pub_key):
176
+        data = b'224626ae19824466f2a7f39ab7b80f7f'
177
+        mock_get_pub_key.return_value = TEST_RSA_PRIVATE_KEY.public_key()
178
+        img_sig_cert_uuid = 'fea14bc2-d75f-4ba5-bccc-b5c924ad0693'
179
+        verifier = signature_utils.get_verifier(None, img_sig_cert_uuid,
180
+                                                'SHA-256', 'BLAH',
181
+                                                signature_utils.RSA_PSS)
182
+        verifier.update(data)
183
+        self.assertRaises(crypto_exceptions.InvalidSignature,
184
+                          verifier.verify)
185
+
186
+    def test_get_verifier_invalid_image_props(self):
187
+        self.assertRaisesRegex(exception.SignatureVerificationError,
188
+                               'Required image properties for signature'
189
+                               ' verification do not exist. Cannot verify'
190
+                               ' signature. Missing property: .*',
191
+                               signature_utils.get_verifier,
192
+                               None, None, 'SHA-256', 'BLAH',
193
+                               signature_utils.RSA_PSS)
194
+
195
+    @mock.patch('cursive.signature_utils.get_public_key')
196
+    def test_verify_signature_bad_sig_key_type(self, mock_get_pub_key):
197
+        mock_get_pub_key.return_value = TEST_RSA_PRIVATE_KEY.public_key()
198
+        img_sig_cert_uuid = 'fea14bc2-d75f-4ba5-bccc-b5c924ad0693'
199
+        self.assertRaisesRegex(exception.SignatureVerificationError,
200
+                               'Invalid signature key type: .*',
201
+                               signature_utils.get_verifier,
202
+                               None, img_sig_cert_uuid, 'SHA-256',
203
+                               'BLAH', 'BLAH')
204
+
205
+    @mock.patch('cursive.signature_utils.get_public_key')
206
+    def test_get_verifier_none(self, mock_get_pub_key):
207
+        mock_get_pub_key.return_value = BadPublicKey()
208
+        img_sig_cert_uuid = 'fea14bc2-d75f-4ba5-bccc-b5c924ad0693'
209
+        self.assertRaisesRegex(exception.SignatureVerificationError,
210
+                               'Error occurred while creating'
211
+                               ' the verifier',
212
+                               signature_utils.get_verifier,
213
+                               None, img_sig_cert_uuid, 'SHA-256',
214
+                               'BLAH', signature_utils.RSA_PSS)
215
+
216
+    def test_get_signature(self):
217
+        signature = b'A' * 256
218
+        data = base64.b64encode(signature)
219
+        self.assertEqual(signature,
220
+                         signature_utils.get_signature(data))
221
+
222
+    def test_get_signature_fail(self):
223
+        self.assertRaisesRegex(exception.SignatureVerificationError,
224
+                               'The signature data was not properly'
225
+                               ' encoded using base64',
226
+                               signature_utils.get_signature, '///')
227
+
228
+    def test_get_hash_method(self):
229
+        hash_dict = signature_utils.HASH_METHODS
230
+        for hash_name in hash_dict.keys():
231
+            hash_class = signature_utils.get_hash_method(hash_name).__class__
232
+            self.assertIsInstance(hash_dict[hash_name], hash_class)
233
+
234
+    def test_get_hash_method_fail(self):
235
+        self.assertRaisesRegex(exception.SignatureVerificationError,
236
+                               'Invalid signature hash method: .*',
237
+                               signature_utils.get_hash_method, 'SHA-2')
238
+
239
+    def test_signature_key_type_lookup(self):
240
+        for sig_format in [signature_utils.RSA_PSS, signature_utils.DSA]:
241
+            sig_key_type = signature_utils.SignatureKeyType.lookup(sig_format)
242
+            self.assertIsInstance(sig_key_type,
243
+                                  signature_utils.SignatureKeyType)
244
+            self.assertEqual(sig_format, sig_key_type.name)
245
+
246
+    def test_signature_key_type_lookup_fail(self):
247
+        self.assertRaisesRegex(exception.SignatureVerificationError,
248
+                               'Invalid signature key type: .*',
249
+                               signature_utils.SignatureKeyType.lookup,
250
+                               'RSB-PSS')
251
+
252
+    @mock.patch('cursive.signature_utils.get_certificate')
253
+    def test_get_public_key_rsa(self, mock_get_cert):
254
+        fake_cert = FakeCryptoCertificate()
255
+        mock_get_cert.return_value = fake_cert
256
+        sig_key_type = signature_utils.SignatureKeyType.lookup(
257
+            signature_utils.RSA_PSS
258
+        )
259
+        result_pub_key = signature_utils.get_public_key(None, None,
260
+                                                        sig_key_type)
261
+        self.assertEqual(fake_cert.public_key(), result_pub_key)
262
+
263
+    @mock.patch('cursive.signature_utils.get_certificate')
264
+    def test_get_public_key_ecc(self, mock_get_cert):
265
+        fake_cert = FakeCryptoCertificate(TEST_ECC_PRIVATE_KEY.public_key())
266
+        mock_get_cert.return_value = fake_cert
267
+        sig_key_type = signature_utils.SignatureKeyType.lookup('ECC_SECP521R1')
268
+        result_pub_key = signature_utils.get_public_key(None, None,
269
+                                                        sig_key_type)
270
+        self.assertEqual(fake_cert.public_key(), result_pub_key)
271
+
272
+    @mock.patch('cursive.signature_utils.get_certificate')
273
+    def test_get_public_key_dsa(self, mock_get_cert):
274
+        fake_cert = FakeCryptoCertificate(TEST_DSA_PRIVATE_KEY.public_key())
275
+        mock_get_cert.return_value = fake_cert
276
+        sig_key_type = signature_utils.SignatureKeyType.lookup(
277
+            signature_utils.DSA
278
+        )
279
+        result_pub_key = signature_utils.get_public_key(None, None,
280
+                                                        sig_key_type)
281
+        self.assertEqual(fake_cert.public_key(), result_pub_key)
282
+
283
+    @mock.patch('cursive.signature_utils.get_certificate')
284
+    def test_get_public_key_invalid_key(self, mock_get_certificate):
285
+        bad_pub_key = 'A' * 256
286
+        mock_get_certificate.return_value = FakeCryptoCertificate(bad_pub_key)
287
+        sig_key_type = signature_utils.SignatureKeyType.lookup(
288
+            signature_utils.RSA_PSS
289
+        )
290
+        self.assertRaisesRegex(exception.SignatureVerificationError,
291
+                               'Invalid public key type for '
292
+                               'signature key type: .*',
293
+                               signature_utils.get_public_key, None,
294
+                               None, sig_key_type)
295
+
296
+    @mock.patch('cryptography.x509.load_der_x509_certificate')
297
+    @mock.patch('castellan.key_manager.API', return_value=FakeKeyManager())
298
+    def test_get_certificate(self, mock_key_manager_API, mock_load_cert):
299
+        cert_uuid = 'valid_format_cert'
300
+        x509_cert = FakeCryptoCertificate()
301
+        mock_load_cert.return_value = x509_cert
302
+        self.assertEqual(x509_cert,
303
+                         signature_utils.get_certificate(None, cert_uuid))
304
+
305
+    @mock.patch('cryptography.x509.load_der_x509_certificate')
306
+    @mock.patch('castellan.key_manager.API', return_value=FakeKeyManager())
307
+    def test_get_expired_certificate(self, mock_key_manager_API,
308
+                                     mock_load_cert):
309
+        cert_uuid = 'valid_format_cert'
310
+        x509_cert = FakeCryptoCertificate(
311
+            not_valid_after=timeutils.utcnow() -
312
+            datetime.timedelta(hours=1))
313
+        mock_load_cert.return_value = x509_cert
314
+        self.assertRaisesRegex(exception.SignatureVerificationError,
315
+                               'Certificate is not valid after: .*',
316
+                               signature_utils.get_certificate, None,
317
+                               cert_uuid)
318
+
319
+    @mock.patch('cryptography.x509.load_der_x509_certificate')
320
+    @mock.patch('castellan.key_manager.API', return_value=FakeKeyManager())
321
+    def test_get_not_yet_valid_certificate(self, mock_key_manager_API,
322
+                                           mock_load_cert):
323
+        cert_uuid = 'valid_format_cert'
324
+        x509_cert = FakeCryptoCertificate(
325
+            not_valid_before=timeutils.utcnow() +
326
+            datetime.timedelta(hours=1))
327
+        mock_load_cert.return_value = x509_cert
328
+        self.assertRaisesRegex(exception.SignatureVerificationError,
329
+                               'Certificate is not valid before: .*',
330
+                               signature_utils.get_certificate, None,
331
+                               cert_uuid)
332
+
333
+    @mock.patch('castellan.key_manager.API', return_value=FakeKeyManager())
334
+    def test_get_certificate_key_manager_fail(self, mock_key_manager_API):
335
+        bad_cert_uuid = 'fea14bc2-d75f-4ba5-bccc-b5c924ad0695'
336
+        self.assertRaisesRegex(exception.SignatureVerificationError,
337
+                               'Unable to retrieve certificate with ID: .*',
338
+                               signature_utils.get_certificate, None,
339
+                               bad_cert_uuid)
340
+
341
+    @mock.patch('castellan.key_manager.API', return_value=FakeKeyManager())
342
+    def test_get_certificate_invalid_format(self, mock_API):
343
+        cert_uuid = 'invalid_format_cert'
344
+        self.assertRaisesRegex(exception.SignatureVerificationError,
345
+                               'Invalid certificate format: .*',
346
+                               signature_utils.get_certificate, None,
347
+                               cert_uuid)

+ 8
- 0
requirements.txt View File

@@ -3,3 +3,11 @@
3 3
 # process, which may cause wedges in the gate later.
4 4
 
5 5
 pbr>=1.6 # Apache-2.0
6
+lxml>=2.3 # BSD
7
+cryptography!=1.3.0,>=1.0 # BSD/Apache-2.0
8
+netifaces>=0.10.4 # MIT
9
+six>=1.9.0 # MIT
10
+oslo.serialization>=1.10.0 # Apache-2.0
11
+oslo.utils>=3.16.0 # Apache-2.0
12
+oslo.i18n>=2.1.0 # Apache-2.0
13
+castellan>=0.4.0 # Apache-2.0

+ 1
- 1
tox.ini View File

@@ -59,6 +59,6 @@ commands = oslo_debug_helper {posargs}
59 59
 # E123, E125 skipped as they are invalid PEP-8.
60 60
 
61 61
 show-source = True
62
-ignore = E123,E125
62
+ignore = E123,E125,H301
63 63
 builtins = _
64 64
 exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build

Loading…
Cancel
Save