Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pem/twisted.py: 9%
43 statements
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-25 06:17 +0000
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-25 06:17 +0000
1# SPDX-FileCopyrightText: 2013 Hynek Schlawack <hs@ox.cx>
2#
3# SPDX-License-Identifier: MIT
5"""
6Twisted-specific convenience helpers.
7"""
9from __future__ import annotations
11from typing import TYPE_CHECKING
13from OpenSSL.crypto import FILETYPE_PEM
14from twisted.internet import ssl
16from ._core import parse_file
17from ._object_types import Certificate, DHParameters, Key
20if TYPE_CHECKING:
21 from ._object_types import AbstractPEMObject
24def certificateOptionsFromPEMs(
25 pemObjects: list[AbstractPEMObject], **kw: object
26) -> ssl.CertificateOptions:
27 """
28 Load a CertificateOptions from the given collection of PEM objects
29 (already-loaded private keys and certificates).
31 In those PEM objects, identify one private key and its corresponding
32 certificate to use as the primary certificate. Then use the rest of the
33 certificates found as chain certificates. Raise a ValueError if no
34 certificate matching a private key is found.
36 Args:
37 pemObjects (list[AbstractPEMObject]): A list of PEM objects to load.
39 Returns:
40 `twisted.internet.ssl.CertificateOptions`_: A TLS context factory using *pemObjects*
42 .. _`twisted.internet.ssl.CertificateOptions`: https://docs.twistedmatrix.com/en/stable/api/twisted.internet.ssl.CertificateOptions.html
43 """
44 keys = [key for key in pemObjects if isinstance(key, Key)]
45 if not len(keys):
46 msg = "Supplied PEM file(s) does *not* contain a key."
47 raise ValueError(msg)
48 if len(keys) > 1:
49 msg = "Supplied PEM file(s) contains *more* than one key."
50 raise ValueError(msg)
52 privateKey = ssl.KeyPair.load(str(keys[0]), FILETYPE_PEM) # type: ignore[no-untyped-call]
54 certs = [cert for cert in pemObjects if isinstance(cert, Certificate)]
55 if not len(certs):
56 msg = "*At least one* certificate is required."
57 raise ValueError(msg)
58 certificates = [
59 ssl.Certificate.loadPEM(str(certPEM)) # type: ignore[no-untyped-call]
60 for certPEM in certs
61 ]
63 certificatesByFingerprint = {
64 certificate.getPublicKey().keyHash(): certificate
65 for certificate in certificates
66 }
68 if privateKey.keyHash() not in certificatesByFingerprint:
69 msg = f"No certificate matching {privateKey.keyHash()} found."
70 raise ValueError(msg)
72 primaryCertificate = certificatesByFingerprint.pop(privateKey.keyHash())
74 if "dhParameters" in kw:
75 msg = "Passing DH parameters as a keyword argument instead of a PEM object is not supported anymore."
76 raise TypeError(msg)
78 dhparams = [o for o in pemObjects if isinstance(o, DHParameters)]
79 if len(dhparams) > 1:
80 msg = "Supplied PEM file(s) contain(s) *more* than one set of DH parameters."
81 raise ValueError(msg)
83 if len(dhparams) == 1:
84 kw["dhParameters"] = ssl.DiffieHellmanParameters( # type: ignore[no-untyped-call]
85 str(dhparams[0])
86 )
88 return ssl.CertificateOptions( # type: ignore[no-any-return]
89 privateKey=privateKey.original,
90 certificate=primaryCertificate.original,
91 extraCertChain=[
92 chain.original for chain in certificatesByFingerprint.values()
93 ],
94 **kw,
95 )
98def certificateOptionsFromFiles(
99 *pemFiles: str, **kw: object
100) -> ssl.CertificateOptions:
101 """
102 Read all files named by *pemFiles*, and parse them using
103 :func:`certificateOptionsFromPEMs`.
105 Args:
106 pemFiles (str): All positional arguments are used as filenames to
107 read.
109 Returns:
110 `twisted.internet.ssl.CertificateOptions`_: A TLS context factory using
111 PEM objects from *pemFiles*.
112 """
113 pems: list[AbstractPEMObject] = []
114 for pemFile in pemFiles:
115 pems += parse_file(pemFile)
117 return certificateOptionsFromPEMs(pems, **kw)