Skip to content

Commit 8d41a94

Browse files
authored
typehint x509.base (pyca#5899)
* start typing x509.base * statically type x509.base * typehint X509Backend interface * typehint at least the X509Backend interface * make _CertificateRevocationList/_CertificateSigningRequest actual subclasses of the interface (as done before for Certificate in f16bff2) * tell mypy to ignore lines with deliberately wrong types * signature_hash_algorithm always returns a hash algorithm (it's not optional) * Revert "signature_hash_algorithm always returns a hash algorithm (it's not optional)" This reverts commit f6a5b17. * hash algorithm is actually optional * fix import style * typehint parsed_version to int, which it de facto always is * minimize changes * break import cycle with conditional imports * ignore access to private members of openssl implementation * reformat code with Black * test check for missing public key
1 parent cd2ab9e commit 8d41a94

6 files changed

Lines changed: 121 additions & 35 deletions

File tree

.coveragerc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ exclude_lines =
1515
@abc.abstractmethod
1616
@abc.abstractproperty
1717
@typing.overload
18+
if typing.TYPE_CHECKING

src/cryptography/hazmat/backends/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
_default_backend: typing.Optional[Backend] = None
1010

1111

12-
def default_backend():
12+
def default_backend() -> Backend:
1313
global _default_backend
1414

1515
if _default_backend is None:

src/cryptography/hazmat/backends/interfaces.py

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,23 @@
44

55

66
import abc
7+
import typing
8+
9+
10+
if typing.TYPE_CHECKING:
11+
from cryptography.hazmat._types import _PRIVATE_KEY_TYPES
12+
from cryptography.hazmat.primitives import hashes
13+
from cryptography.x509.base import (
14+
Certificate,
15+
CertificateBuilder,
16+
CertificateRevocationList,
17+
CertificateRevocationListBuilder,
18+
CertificateSigningRequest,
19+
CertificateSigningRequestBuilder,
20+
RevokedCertificate,
21+
RevokedCertificateBuilder,
22+
)
23+
from cryptography.x509.name import Name
724

825

926
class CipherBackend(metaclass=abc.ABCMeta):
@@ -262,69 +279,86 @@ def load_der_parameters(self, data):
262279

263280
class X509Backend(metaclass=abc.ABCMeta):
264281
@abc.abstractmethod
265-
def load_pem_x509_certificate(self, data):
282+
def load_pem_x509_certificate(self, data: bytes) -> "Certificate":
266283
"""
267284
Load an X.509 certificate from PEM encoded data.
268285
"""
269286

270287
@abc.abstractmethod
271-
def load_der_x509_certificate(self, data):
288+
def load_der_x509_certificate(self, data: bytes) -> "Certificate":
272289
"""
273290
Load an X.509 certificate from DER encoded data.
274291
"""
275292

276293
@abc.abstractmethod
277-
def load_der_x509_csr(self, data):
294+
def load_der_x509_csr(self, data: bytes) -> "CertificateSigningRequest":
278295
"""
279296
Load an X.509 CSR from DER encoded data.
280297
"""
281298

282299
@abc.abstractmethod
283-
def load_pem_x509_csr(self, data):
300+
def load_pem_x509_csr(self, data: bytes) -> "CertificateSigningRequest":
284301
"""
285302
Load an X.509 CSR from PEM encoded data.
286303
"""
287304

288305
@abc.abstractmethod
289-
def create_x509_csr(self, builder, private_key, algorithm):
306+
def create_x509_csr(
307+
self,
308+
builder: "CertificateSigningRequestBuilder",
309+
private_key: "_PRIVATE_KEY_TYPES",
310+
algorithm: typing.Optional["hashes.HashAlgorithm"],
311+
) -> "CertificateSigningRequest":
290312
"""
291313
Create and sign an X.509 CSR from a CSR builder object.
292314
"""
293315

294316
@abc.abstractmethod
295-
def create_x509_certificate(self, builder, private_key, algorithm):
317+
def create_x509_certificate(
318+
self,
319+
builder: "CertificateBuilder",
320+
private_key: "_PRIVATE_KEY_TYPES",
321+
algorithm: typing.Optional["hashes.HashAlgorithm"],
322+
) -> "Certificate":
296323
"""
297324
Create and sign an X.509 certificate from a CertificateBuilder object.
298325
"""
299326

300327
@abc.abstractmethod
301-
def create_x509_crl(self, builder, private_key, algorithm):
328+
def create_x509_crl(
329+
self,
330+
builder: "CertificateRevocationListBuilder",
331+
private_key: "_PRIVATE_KEY_TYPES",
332+
algorithm: typing.Optional["hashes.HashAlgorithm"],
333+
) -> "CertificateRevocationList":
302334
"""
303335
Create and sign an X.509 CertificateRevocationList from a
304336
CertificateRevocationListBuilder object.
305337
"""
306338

307339
@abc.abstractmethod
308-
def create_x509_revoked_certificate(self, builder):
340+
def create_x509_revoked_certificate(
341+
self, builder: "RevokedCertificateBuilder"
342+
) -> "RevokedCertificate":
309343
"""
310344
Create a RevokedCertificate object from a RevokedCertificateBuilder
311345
object.
312346
"""
313347

314348
@abc.abstractmethod
315-
def x509_name_bytes(self, name):
349+
def x509_name_bytes(self, name: "Name") -> bytes:
316350
"""
317351
Compute the DER encoded bytes of an X509 Name object.
318352
"""
319353

320354
@abc.abstractmethod
321-
def load_pem_x509_crl(self, data):
355+
def load_pem_x509_crl(self, data: bytes) -> "CertificateRevocationList":
322356
"""
323357
Load an X.509 CRL from PEM encoded data.
324358
"""
325359

326360
@abc.abstractmethod
327-
def load_der_x509_crl(self, data):
361+
def load_der_x509_crl(self, data: bytes) -> "CertificateRevocationList":
328362
"""
329363
Load an X.509 CRL from DER encoded data.
330364
"""

src/cryptography/hazmat/backends/openssl/backend.py

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import collections
77
import contextlib
88
import itertools
9+
import typing
910
import warnings
1011
from contextlib import contextmanager
1112

@@ -18,6 +19,7 @@
1819
encode_der,
1920
encode_der_integer,
2021
)
22+
from cryptography.hazmat._types import _PRIVATE_KEY_TYPES
2123
from cryptography.hazmat.backends.interfaces import Backend as BackendInterface
2224
from cryptography.hazmat.backends.openssl import aead
2325
from cryptography.hazmat.backends.openssl.ciphers import _CipherContext
@@ -137,6 +139,7 @@
137139
from cryptography.hazmat.primitives.kdf import scrypt
138140
from cryptography.hazmat.primitives.serialization import pkcs7, ssh
139141
from cryptography.x509 import ocsp
142+
from cryptography.x509.name import Name
140143

141144

142145
_MemoryBIO = collections.namedtuple("_MemoryBIO", ["bio", "char_ptr"])
@@ -895,7 +898,12 @@ def _x509_check_signature_params(self, private_key, algorithm):
895898
"MD5 hash algorithm is only supported with RSA keys"
896899
)
897900

898-
def create_x509_csr(self, builder, private_key, algorithm):
901+
def create_x509_csr(
902+
self,
903+
builder: x509.CertificateSigningRequestBuilder,
904+
private_key: _PRIVATE_KEY_TYPES,
905+
algorithm: typing.Optional[hashes.HashAlgorithm],
906+
) -> _CertificateSigningRequest:
899907
if not isinstance(builder, x509.CertificateSigningRequestBuilder):
900908
raise TypeError("Builder type mismatch.")
901909
self._x509_check_signature_params(private_key, algorithm)
@@ -920,7 +928,9 @@ def create_x509_csr(self, builder, private_key, algorithm):
920928

921929
# Set subject public key.
922930
public_key = private_key.public_key()
923-
res = self._lib.X509_REQ_set_pubkey(x509_req, public_key._evp_pkey)
931+
res = self._lib.X509_REQ_set_pubkey(
932+
x509_req, public_key._evp_pkey # type: ignore[union-attr]
933+
)
924934
self.openssl_assert(res == 1)
925935

926936
# Add extensions.
@@ -960,16 +970,25 @@ def create_x509_csr(self, builder, private_key, algorithm):
960970
self.openssl_assert(res == 1)
961971

962972
# Sign the request using the requester's private key.
963-
res = self._lib.X509_REQ_sign(x509_req, private_key._evp_pkey, evp_md)
973+
res = self._lib.X509_REQ_sign(
974+
x509_req, private_key._evp_pkey, evp_md # type: ignore[union-attr]
975+
)
964976
if res == 0:
965977
errors = self._consume_errors_with_text()
966978
raise ValueError("Signing failed", errors)
967979

968980
return _CertificateSigningRequest(self, x509_req)
969981

970-
def create_x509_certificate(self, builder, private_key, algorithm):
982+
def create_x509_certificate(
983+
self,
984+
builder: x509.CertificateBuilder,
985+
private_key: _PRIVATE_KEY_TYPES,
986+
algorithm: typing.Optional[hashes.HashAlgorithm],
987+
) -> _Certificate:
971988
if not isinstance(builder, x509.CertificateBuilder):
972989
raise TypeError("Builder type mismatch.")
990+
if builder._public_key is None:
991+
raise TypeError("Builder has no public key.")
973992
self._x509_check_signature_params(private_key, algorithm)
974993

975994
# Resolve the signature algorithm.
@@ -1027,7 +1046,11 @@ def create_x509_certificate(self, builder, private_key, algorithm):
10271046
self.openssl_assert(res == 1)
10281047

10291048
# Sign the certificate with the issuer's private key.
1030-
res = self._lib.X509_sign(x509_cert, private_key._evp_pkey, evp_md)
1049+
res = self._lib.X509_sign(
1050+
x509_cert,
1051+
private_key._evp_pkey, # type: ignore[union-attr]
1052+
evp_md,
1053+
)
10311054
if res == 0:
10321055
errors = self._consume_errors_with_text()
10331056
raise ValueError("Signing failed", errors)
@@ -1058,7 +1081,12 @@ def _create_asn1_time(self, time):
10581081
self._set_asn1_time(asn1_time, time)
10591082
return asn1_time
10601083

1061-
def create_x509_crl(self, builder, private_key, algorithm):
1084+
def create_x509_crl(
1085+
self,
1086+
builder: x509.CertificateRevocationListBuilder,
1087+
private_key: _PRIVATE_KEY_TYPES,
1088+
algorithm: typing.Optional[hashes.HashAlgorithm],
1089+
) -> _CertificateRevocationList:
10621090
if not isinstance(builder, x509.CertificateRevocationListBuilder):
10631091
raise TypeError("Builder type mismatch.")
10641092
self._x509_check_signature_params(private_key, algorithm)
@@ -1109,7 +1137,9 @@ def create_x509_crl(self, builder, private_key, algorithm):
11091137
res = self._lib.X509_CRL_add0_revoked(x509_crl, revoked)
11101138
self.openssl_assert(res == 1)
11111139

1112-
res = self._lib.X509_CRL_sign(x509_crl, private_key._evp_pkey, evp_md)
1140+
res = self._lib.X509_CRL_sign(
1141+
x509_crl, private_key._evp_pkey, evp_md # type: ignore[union-attr]
1142+
)
11131143
if res == 0:
11141144
errors = self._consume_errors_with_text()
11151145
raise ValueError("Signing failed", errors)
@@ -1170,7 +1200,9 @@ def _create_x509_extension(self, handlers, extension):
11701200
nid, 1 if extension.critical else 0, ext_struct
11711201
)
11721202

1173-
def create_x509_revoked_certificate(self, builder):
1203+
def create_x509_revoked_certificate(
1204+
self, builder: x509.RevokedCertificateBuilder
1205+
) -> _RevokedCertificate:
11741206
if not isinstance(builder, x509.RevokedCertificateBuilder):
11751207
raise TypeError("Builder type mismatch.")
11761208

@@ -1316,7 +1348,7 @@ def load_der_parameters(self, data):
13161348

13171349
self._handle_key_loading_error()
13181350

1319-
def load_pem_x509_certificate(self, data):
1351+
def load_pem_x509_certificate(self, data: bytes) -> _Certificate:
13201352
mem_bio = self._bytes_to_bio(data)
13211353
x509 = self._lib.PEM_read_bio_X509(
13221354
mem_bio.bio, self._ffi.NULL, self._ffi.NULL, self._ffi.NULL
@@ -1332,7 +1364,7 @@ def load_pem_x509_certificate(self, data):
13321364
x509 = self._ffi.gc(x509, self._lib.X509_free)
13331365
return _Certificate(self, x509)
13341366

1335-
def load_der_x509_certificate(self, data):
1367+
def load_der_x509_certificate(self, data: bytes) -> _Certificate:
13361368
mem_bio = self._bytes_to_bio(data)
13371369
x509 = self._lib.d2i_X509_bio(mem_bio.bio, self._ffi.NULL)
13381370
if x509 == self._ffi.NULL:
@@ -1342,7 +1374,7 @@ def load_der_x509_certificate(self, data):
13421374
x509 = self._ffi.gc(x509, self._lib.X509_free)
13431375
return _Certificate(self, x509)
13441376

1345-
def load_pem_x509_crl(self, data):
1377+
def load_pem_x509_crl(self, data: bytes) -> _CertificateRevocationList:
13461378
mem_bio = self._bytes_to_bio(data)
13471379
x509_crl = self._lib.PEM_read_bio_X509_CRL(
13481380
mem_bio.bio, self._ffi.NULL, self._ffi.NULL, self._ffi.NULL
@@ -1358,7 +1390,7 @@ def load_pem_x509_crl(self, data):
13581390
x509_crl = self._ffi.gc(x509_crl, self._lib.X509_CRL_free)
13591391
return _CertificateRevocationList(self, x509_crl)
13601392

1361-
def load_der_x509_crl(self, data):
1393+
def load_der_x509_crl(self, data: bytes) -> _CertificateRevocationList:
13621394
mem_bio = self._bytes_to_bio(data)
13631395
x509_crl = self._lib.d2i_X509_CRL_bio(mem_bio.bio, self._ffi.NULL)
13641396
if x509_crl == self._ffi.NULL:
@@ -1368,7 +1400,7 @@ def load_der_x509_crl(self, data):
13681400
x509_crl = self._ffi.gc(x509_crl, self._lib.X509_CRL_free)
13691401
return _CertificateRevocationList(self, x509_crl)
13701402

1371-
def load_pem_x509_csr(self, data):
1403+
def load_pem_x509_csr(self, data: bytes) -> _CertificateSigningRequest:
13721404
mem_bio = self._bytes_to_bio(data)
13731405
x509_req = self._lib.PEM_read_bio_X509_REQ(
13741406
mem_bio.bio, self._ffi.NULL, self._ffi.NULL, self._ffi.NULL
@@ -1384,7 +1416,7 @@ def load_pem_x509_csr(self, data):
13841416
x509_req = self._ffi.gc(x509_req, self._lib.X509_REQ_free)
13851417
return _CertificateSigningRequest(self, x509_req)
13861418

1387-
def load_der_x509_csr(self, data):
1419+
def load_der_x509_csr(self, data: bytes) -> _CertificateSigningRequest:
13881420
mem_bio = self._bytes_to_bio(data)
13891421
x509_req = self._lib.d2i_X509_REQ_bio(mem_bio.bio, self._ffi.NULL)
13901422
if x509_req == self._ffi.NULL:
@@ -2200,7 +2232,7 @@ def dh_parameters_supported(self, p, g, q=None):
22002232
def dh_x942_serialization_supported(self):
22012233
return self._lib.Cryptography_HAS_EVP_PKEY_DHX == 1
22022234

2203-
def x509_name_bytes(self, name):
2235+
def x509_name_bytes(self, name: Name) -> bytes:
22042236
x509_name = _encode_name_gc(self, name)
22052237
pp = self._ffi.new("unsigned char **")
22062238
res = self._lib.i2d_X509_NAME(x509_name, pp)

src/cryptography/hazmat/backends/openssl/x509.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -204,8 +204,7 @@ def extensions(self) -> x509.Extensions:
204204
)
205205

206206

207-
@utils.register_interface(x509.CertificateRevocationList)
208-
class _CertificateRevocationList(object):
207+
class _CertificateRevocationList(x509.CertificateRevocationList):
209208
def __init__(self, backend, x509_crl):
210209
self._backend = backend
211210
self._x509_crl = x509_crl
@@ -385,8 +384,7 @@ def is_signature_valid(self, public_key: _PUBLIC_KEY_TYPES) -> bool:
385384
return True
386385

387386

388-
@utils.register_interface(x509.CertificateSigningRequest)
389-
class _CertificateSigningRequest(object):
387+
class _CertificateSigningRequest(x509.CertificateSigningRequest):
390388
def __init__(self, backend, x509_req):
391389
self._backend = backend
392390
self._x509_req = x509_req

tests/hazmat/backends/test_openssl.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,20 @@ def test_requires_certificate_builder(self):
478478

479479
with pytest.raises(TypeError):
480480
backend.create_x509_certificate(
481-
object(), private_key, DummyHashAlgorithm()
481+
object(), # type: ignore[arg-type]
482+
private_key,
483+
DummyHashAlgorithm(),
484+
)
485+
486+
def test_builder_requires_public_key(self):
487+
builder = x509.CertificateBuilder()
488+
private_key = RSA_KEY_2048.private_key(backend)
489+
490+
with pytest.raises(TypeError):
491+
backend.create_x509_certificate(
492+
builder,
493+
private_key,
494+
DummyHashAlgorithm(),
482495
)
483496

484497

@@ -488,7 +501,9 @@ def test_requires_csr_builder(self):
488501

489502
with pytest.raises(TypeError):
490503
backend.create_x509_csr(
491-
object(), private_key, DummyHashAlgorithm()
504+
object(), # type: ignore[arg-type]
505+
private_key,
506+
DummyHashAlgorithm(),
492507
)
493508

494509

@@ -497,13 +512,19 @@ def test_invalid_builder(self):
497512
private_key = RSA_KEY_2048.private_key(backend)
498513

499514
with pytest.raises(TypeError):
500-
backend.create_x509_crl(object(), private_key, hashes.SHA256())
515+
backend.create_x509_crl(
516+
object(), # type: ignore[arg-type]
517+
private_key,
518+
hashes.SHA256(),
519+
)
501520

502521

503522
class TestOpenSSLCreateRevokedCertificate(object):
504523
def test_invalid_builder(self):
505524
with pytest.raises(TypeError):
506-
backend.create_x509_revoked_certificate(object())
525+
backend.create_x509_revoked_certificate(
526+
object() # type: ignore[arg-type]
527+
)
507528

508529

509530
class TestOpenSSLSerializationWithOpenSSL(object):

0 commit comments

Comments
 (0)