txhttputil 1.4.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,750 @@
1
+ # Copyright (c) 2016-2026 Flying Ion Pty Ltd
2
+ #
3
+ # This software is open source; the MIT license applies.
4
+ # See LICENSE for the full license text.
5
+ #
6
+ # Website : https://flyingion.com
7
+ # Support : contact@flyingion.com
8
+
9
+ import logging
10
+ import textwrap
11
+ from collections import namedtuple
12
+ from datetime import datetime
13
+ from functools import partial
14
+ from itertools import chain
15
+ from pathlib import Path
16
+ from typing import List, Dict, Literal
17
+
18
+ import pem
19
+ from OpenSSL.crypto import (
20
+ load_certificate,
21
+ FILETYPE_PEM,
22
+ X509Store,
23
+ X509StoreContext,
24
+ X509StoreContextError,
25
+ )
26
+ from cryptography.hazmat._oid import NameOID
27
+ from cryptography.x509.base import Version
28
+ from cryptography.x509.oid import ExtendedKeyUsageOID
29
+ from cryptography.hazmat.primitives.asymmetric import dh
30
+ from cryptography.hazmat.primitives.serialization import (
31
+ load_pem_private_key,
32
+ Encoding,
33
+ ParameterFormat,
34
+ load_pem_parameters,
35
+ )
36
+ from cryptography.x509 import (
37
+ Certificate,
38
+ load_pem_x509_certificate,
39
+ BasicConstraints,
40
+ ExtensionNotFound,
41
+ Extension,
42
+ ExtendedKeyUsage,
43
+ )
44
+ from pem import AbstractPEMObject
45
+ from treelib import Tree
46
+ from twisted.internet import ssl
47
+ from twisted.internet._sslverify import (
48
+ Certificate as TxCertificate,
49
+ OpenSSLDiffieHellmanParameters,
50
+ )
51
+ from twisted.python.filepath import FilePath
52
+
53
+ logger = logging.getLogger(__name__)
54
+
55
+
56
+ class PEM_CHECK_TYPE:
57
+ CHECK_TYPE_SSL_SERVER_IDENTITY = 0
58
+ CHECK_TYPE_SSL_CLIENT_IDENTITY = 1
59
+ CHECK_TYPE_SSL_MTLS_CA = 2
60
+ CHECK_TYPE_SSL_MTLS_PEER = 3
61
+
62
+
63
+ PemSimpleBank = namedtuple(
64
+ "PemSimpleBank",
65
+ [
66
+ "privateKeys",
67
+ "certificates",
68
+ "rootCAs",
69
+ "intermediateCAs",
70
+ "bundleFilePath",
71
+ ],
72
+ defaults=([], [], [], [], ""),
73
+ )
74
+
75
+
76
+ class PemBank:
77
+ MAX_COUNT = 99
78
+
79
+ def __init__(
80
+ self,
81
+ pemSimpleBank: PemSimpleBank,
82
+ pemMap: Dict[AbstractPEMObject, Certificate],
83
+ ):
84
+ self._pemSimpleBank = pemSimpleBank
85
+ self._pemMap = pemMap
86
+ self._trustTree = Tree()
87
+
88
+ @property
89
+ def trustChains(self) -> List[List[str]]:
90
+ # chains in x509 common names
91
+ return self._trustTree.paths_to_leaves()
92
+
93
+ @property
94
+ def privateKeys(self):
95
+ return self._pemSimpleBank.privateKeys
96
+
97
+ @property
98
+ def certificates(self):
99
+ return self._pemSimpleBank.certificates
100
+
101
+ @property
102
+ def rootCAs(self):
103
+ return self._pemSimpleBank.rootCAs
104
+
105
+ @property
106
+ def intermediateCAs(self):
107
+ return self._pemSimpleBank.intermediateCAs
108
+
109
+ @property
110
+ def bundleFilePath(self):
111
+ return self._pemSimpleBank.bundleFilePath
112
+
113
+ def check(
114
+ self,
115
+ checkType: PEM_CHECK_TYPE,
116
+ showCheck: bool = True,
117
+ targetHostname: str = None,
118
+ ) -> bool:
119
+ if checkType in [
120
+ PEM_CHECK_TYPE.CHECK_TYPE_SSL_SERVER_IDENTITY,
121
+ PEM_CHECK_TYPE.CHECK_TYPE_SSL_CLIENT_IDENTITY,
122
+ ]:
123
+ result = []
124
+ result.append(self._checkExtendedKeyUsage(checkType))
125
+ result.append(self._checkTrustChain(showCheck))
126
+
127
+ if False in result:
128
+ return False
129
+ return True
130
+
131
+ if checkType == PEM_CHECK_TYPE.CHECK_TYPE_SSL_MTLS_CA:
132
+ return self._checkNumberLimits(
133
+ maxCertificateCount=0,
134
+ maxPrivateKeyCount=0,
135
+ maxRootCACount=self.MAX_COUNT,
136
+ maxIntermediateCACount=self.MAX_COUNT,
137
+ )
138
+
139
+ if checkType == PEM_CHECK_TYPE.CHECK_TYPE_SSL_MTLS_PEER:
140
+ return self._checkNumberLimits(
141
+ maxCertificateCount=self.MAX_COUNT,
142
+ maxPrivateKeyCount=0,
143
+ maxRootCACount=0,
144
+ maxIntermediateCACount=0,
145
+ )
146
+
147
+ def _checkExtendedKeyUsage(
148
+ self,
149
+ usageType: Literal[
150
+ PEM_CHECK_TYPE.CHECK_TYPE_SSL_SERVER_IDENTITY,
151
+ PEM_CHECK_TYPE.CHECK_TYPE_SSL_CLIENT_IDENTITY,
152
+ ],
153
+ ) -> bool:
154
+ certificate: AbstractPEMObject = self._pemSimpleBank.certificates[0]
155
+ certificateParsed: Certificate = self._pemMap[certificate]
156
+
157
+ x509Version = X509Util.getX509Version(certificateParsed)
158
+ if x509Version != Version.v3.name:
159
+ logger.error(
160
+ f"The server certificate from bundle "
161
+ f"{self._pemSimpleBank.bundleFilePath} should be in "
162
+ f"version x509 v3, got '{x509Version}'"
163
+ )
164
+ return False
165
+
166
+ usages = X509Util.getExtendedKeyUsages(certificateParsed)
167
+
168
+ if usageType == PEM_CHECK_TYPE.CHECK_TYPE_SSL_SERVER_IDENTITY:
169
+ success = ExtendedKeyUsageOID.SERVER_AUTH in usages
170
+ if not success:
171
+ logger.error(
172
+ f"The server certificate from bundle "
173
+ f"'{self._pemSimpleBank.bundleFilePath}' should contain "
174
+ f"'serverAuth' in extended key usage, got"
175
+ f"{usages}"
176
+ )
177
+ return success
178
+
179
+ if usageType == PEM_CHECK_TYPE.CHECK_TYPE_SSL_CLIENT_IDENTITY:
180
+ success = ExtendedKeyUsageOID.CLIENT_AUTH in usages
181
+ if not success:
182
+ logger.error(
183
+ f"The client certificate from bundle "
184
+ f"'{self._pemSimpleBank.bundleFilePath}' should contain "
185
+ f"'clientAuth' in extended key usage, got"
186
+ f"{usages}"
187
+ )
188
+ return success
189
+
190
+ logger.error(
191
+ f"invalid usageType '{usageType}' to check extended key usage with."
192
+ )
193
+ return False
194
+
195
+ def _checkTrustChain(self, showCheck: bool) -> bool:
196
+ self._trustTree = self._buildTrustChain()
197
+
198
+ if len(self.trustChains) != 1:
199
+ logger.error(
200
+ f"1 and only 1 chain of trust is established, "
201
+ f"got {len(self.trustChains)} "
202
+ f"in '{self._pemSimpleBank.bundleFilePath}'"
203
+ )
204
+ return False
205
+
206
+ # e.g. x509 certificate common names in order of
207
+ # root CA -> intermediate -> sub-intermediate -> cert
208
+ trustChainInCommonName = self.trustChains[0]
209
+
210
+ # drop root CA
211
+ trustChainInCommonName.pop(0)
212
+ # drop certificate
213
+ trustChainInCommonName.pop()
214
+
215
+ # sort intermediate CAs in bank
216
+ self._pemSimpleBank.intermediateCAs.clear()
217
+ for intermediateCACommonName in reversed(
218
+ trustChainInCommonName
219
+ ): # bottom to top
220
+ self._pemSimpleBank.intermediateCAs.append(
221
+ self._trustTree.nodes[intermediateCACommonName].tag
222
+ )
223
+
224
+ # verify the chain by openssl
225
+ if not self._verifyTrustChain():
226
+ logger.error(
227
+ f"PEM bundle failed trust chain check with openssl"
228
+ f"in file {self._pemSimpleBank.bundleFilePath}"
229
+ )
230
+ return False
231
+
232
+ return True
233
+
234
+ def _verifyTrustChain(self) -> bool:
235
+ numberCheckPass = self._checkNumberLimits(
236
+ maxCertificateCount=1,
237
+ maxPrivateKeyCount=1,
238
+ maxIntermediateCACount=self.MAX_COUNT,
239
+ maxRootCACount=1,
240
+ )
241
+ if not numberCheckPass:
242
+ return False
243
+
244
+ store = X509Store()
245
+
246
+ rootCert = load_certificate(
247
+ FILETYPE_PEM, self._pemSimpleBank.rootCAs[0].as_bytes()
248
+ )
249
+ store.add_cert(rootCert)
250
+
251
+ for intermediateCA in self._pemSimpleBank.intermediateCAs:
252
+ intermediateCAObject = load_certificate(
253
+ FILETYPE_PEM, intermediateCA.as_bytes()
254
+ )
255
+ store.add_cert(intermediateCAObject)
256
+
257
+ untrustedCert = load_certificate(
258
+ FILETYPE_PEM, self._pemSimpleBank.certificates[0].as_bytes()
259
+ )
260
+ storeCtx = X509StoreContext(store, untrustedCert)
261
+ try:
262
+ storeCtx.verify_certificate()
263
+ return True
264
+ except X509StoreContextError:
265
+ logger.error(
266
+ f"PEM bundle failed to establish chain of trust in "
267
+ f"{self._pemSimpleBank.bundleFilePath}"
268
+ )
269
+ return False
270
+
271
+ def _buildTrustChain(self) -> Tree:
272
+ trustTree = Tree()
273
+
274
+ numberCheckPass = self._checkNumberLimits(
275
+ maxCertificateCount=1,
276
+ maxPrivateKeyCount=1,
277
+ maxIntermediateCACount=self.MAX_COUNT,
278
+ maxRootCACount=1,
279
+ )
280
+ if not numberCheckPass:
281
+ return trustTree
282
+
283
+ # add root CA as root node
284
+ caCert: AbstractPEMObject = self._pemSimpleBank.rootCAs[0]
285
+ caCertParsed: Certificate = self._pemMap.get(caCert)
286
+ caCommonName = X509Util.getCommonName(caCertParsed)
287
+ trustTree.create_node(tag=caCert, identifier=caCommonName)
288
+
289
+ # add intermediate CAs to the root node
290
+ for intermediateCA in self._pemSimpleBank.intermediateCAs:
291
+ intermediateCA: AbstractPEMObject = intermediateCA
292
+ intermediateCAParsed: Certificate = self._pemMap.get(intermediateCA)
293
+ commonName = X509Util.getCommonName(intermediateCAParsed)
294
+ trustTree.create_node(
295
+ tag=intermediateCA,
296
+ identifier=commonName,
297
+ parent=caCommonName,
298
+ )
299
+
300
+ # add certificate to the root node
301
+ cert: AbstractPEMObject = self._pemSimpleBank.certificates[0]
302
+ certParsed: Certificate = self._pemMap.get(cert)
303
+ certificateCommonName = X509Util.getCommonName(certParsed)
304
+ trustTree.create_node(
305
+ tag=cert,
306
+ identifier=certificateCommonName,
307
+ parent=caCommonName,
308
+ )
309
+
310
+ # correct parent-child relations for all non-rootCA nodes
311
+ # 1) correct certificate
312
+ certificateIssuer = X509Util.getIssuer(certParsed)
313
+ trustTree.move_node(certificateCommonName, certificateIssuer)
314
+ # 2) correct intermediates
315
+ for intermediateCA in self._pemSimpleBank.intermediateCAs:
316
+ intermediateCAParsed: Certificate = self._pemMap.get(intermediateCA)
317
+ commonName = X509Util.getCommonName(intermediateCAParsed)
318
+ issuer = X509Util.getIssuer(intermediateCAParsed)
319
+
320
+ trustTree.move_node(commonName, issuer)
321
+
322
+ return trustTree
323
+
324
+ def _checkNumberLimits(
325
+ self,
326
+ maxCertificateCount,
327
+ maxIntermediateCACount,
328
+ maxPrivateKeyCount,
329
+ maxRootCACount,
330
+ ) -> bool:
331
+ result = True
332
+ if len(self._pemSimpleBank.privateKeys) > maxPrivateKeyCount:
333
+ logger.error(
334
+ f"PEM bundle should contain no more than {maxPrivateKeyCount} "
335
+ f"private keys, "
336
+ f"got {len(self._pemSimpleBank.privateKeys)} "
337
+ f"in {self._pemSimpleBank.bundleFilePath}"
338
+ )
339
+ result = False
340
+ if len(self._pemSimpleBank.certificates) > maxCertificateCount:
341
+ logger.error(
342
+ f"PEM bundle should contain no more than {maxCertificateCount} "
343
+ f"certificates, "
344
+ f"got {len(self._pemSimpleBank.certificates)} "
345
+ f"in {self._pemSimpleBank.bundleFilePath}"
346
+ )
347
+ result = False
348
+ if len(self._pemSimpleBank.intermediateCAs) > maxIntermediateCACount:
349
+ logger.error(
350
+ f"PEM bundle should contain no more than "
351
+ f"{maxIntermediateCACount} intermediate CAs, "
352
+ f"got {len(self._pemSimpleBank.intermediateCAs)} "
353
+ f"in {self._pemSimpleBank.bundleFilePath}"
354
+ )
355
+ result = False
356
+ if len(self._pemSimpleBank.rootCAs) > maxRootCACount:
357
+ logger.error(
358
+ f"PEM bundle should contain no more than {maxRootCACount} "
359
+ f"root CAs "
360
+ f"got {len(self._pemSimpleBank.rootCAs)} "
361
+ f"in {self._pemSimpleBank.bundleFilePath}"
362
+ )
363
+ result = False
364
+
365
+ return result
366
+
367
+
368
+ class X509Util:
369
+ @staticmethod
370
+ def getCommonName(x509: Certificate) -> str:
371
+ return x509.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
372
+
373
+ @staticmethod
374
+ def getSubject(x509: Certificate) -> str:
375
+ return str(x509.subject)
376
+
377
+ @staticmethod
378
+ def getIssuer(x509: Certificate) -> str:
379
+ return x509.issuer.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
380
+
381
+ @staticmethod
382
+ def getIsCA(x509: Certificate) -> bool:
383
+ try:
384
+ basicConstraintExtension: Extension = (
385
+ x509.extensions.get_extension_for_class(BasicConstraints)
386
+ )
387
+ return basicConstraintExtension.value.ca
388
+
389
+ except ExtensionNotFound:
390
+ return False
391
+
392
+ @staticmethod
393
+ def getNotValidBefore(x509: Certificate) -> datetime:
394
+ return x509.not_valid_before
395
+
396
+ @staticmethod
397
+ def getNotValidAfter(x509: Certificate) -> datetime:
398
+ return x509.not_valid_after
399
+
400
+ @staticmethod
401
+ def getExtendedKeyUsages(x509: Certificate) -> List:
402
+ try:
403
+ extension: Extension = x509.extensions.get_extension_for_class(
404
+ ExtendedKeyUsage
405
+ )
406
+ return extension.value._usages
407
+
408
+ except ExtensionNotFound:
409
+ return []
410
+
411
+ @staticmethod
412
+ def getX509Version(x509: Certificate) -> str:
413
+ return x509.version.name
414
+
415
+
416
+ class PemBundleError(Exception):
417
+ pass
418
+
419
+
420
+ class PemBundleParser:
421
+ def __init__(self):
422
+ self._pemMap = {}
423
+ self._pem = None
424
+
425
+ self._privateKeys: List[AbstractPEMObject] = []
426
+ self._certificates: List[AbstractPEMObject] = []
427
+ self._rootCAs: List[AbstractPEMObject] = []
428
+ self._intermediateCAs: List[AbstractPEMObject] = []
429
+
430
+ def parse(self, pemFilePath: str) -> PemBank:
431
+ self._loadPemBundleFile(pemFilePath)
432
+
433
+ # simple classification for PemSimpleBank
434
+ inspectedPems = set([])
435
+ for pemSection, parsedPem in self._pemMap.items():
436
+ # if private key
437
+ if hasattr(parsedPem, "private_bytes"):
438
+ self._privateKeys.append(pemSection)
439
+ inspectedPems.add(pemSection)
440
+
441
+ # if x509 certificate
442
+ if isinstance(parsedPem, Certificate):
443
+ commonName = X509Util.getCommonName(parsedPem)
444
+ issuer = X509Util.getIssuer(parsedPem)
445
+ isCA = X509Util.getIsCA(parsedPem)
446
+
447
+ if isCA:
448
+ if commonName == issuer:
449
+ # found root CA
450
+ self._rootCAs.append(pemSection)
451
+ inspectedPems.add(pemSection)
452
+ else:
453
+ # found intermediate CA
454
+ self._intermediateCAs.append(pemSection)
455
+ inspectedPems.add(pemSection)
456
+ else:
457
+ self._certificates.append(pemSection)
458
+ inspectedPems.add(pemSection)
459
+
460
+ pemSimpleBank = PemSimpleBank(
461
+ privateKeys=self._privateKeys,
462
+ certificates=self._certificates,
463
+ rootCAs=self._rootCAs,
464
+ intermediateCAs=self._intermediateCAs,
465
+ bundleFilePath=pemFilePath,
466
+ )
467
+
468
+ return PemBank(pemSimpleBank, self._pemMap)
469
+
470
+ def _loadPemBundleFile(self, pemFilePath):
471
+ try:
472
+ pemFile = Path(pemFilePath)
473
+ if not pemFile.is_file() or not pemFile.exists():
474
+ logger.error("Pem bundle '%s' is not found", pemFilePath)
475
+ except Exception:
476
+ logger.error("Pem bundle '%s' is not found", pemFilePath)
477
+
478
+ self._pem = pem.parse_file(pemFilePath)
479
+ for p in self._pem:
480
+ # supported types:
481
+ # x509 certificates
482
+ # private keys
483
+ for loadMethod in [
484
+ load_pem_x509_certificate,
485
+ partial(load_pem_private_key, password=None),
486
+ ]:
487
+ try:
488
+ parsed = loadMethod(p.as_bytes())
489
+ self._pemMap[p] = parsed
490
+ except ValueError:
491
+ # on parse error from loadMethod
492
+ continue
493
+
494
+ # check if every PEM section is loaded
495
+ if len(self._pemMap) != len(self._pem):
496
+ unknownSections = set(self._pem) - self._pemMap.keys()
497
+ unknownSectionHeads = [
498
+ f"{s.as_text()[: min(50, len(s.as_text()))]}..."
499
+ for s in unknownSections
500
+ ]
501
+ logger.exception(
502
+ f"unknown PEM section(s) in file {pemFilePath}: "
503
+ f"{unknownSectionHeads}"
504
+ )
505
+ raise
506
+
507
+
508
+ PemInfo = namedtuple(
509
+ "PemInfo",
510
+ ["pemType", "pemStart", "pemEnding", "summary", "pemFor"],
511
+ defaults=(None, None, None, {}, None),
512
+ )
513
+
514
+
515
+ class PemViewer:
516
+ PEM_TYPE_PRIVATE_KEY = "private key"
517
+ PEM_TYPE_CERTIFICATE = "certificate"
518
+ PEM_TYPE_DH_PARAMETER = "Diffie-Hellman parameter"
519
+
520
+ FOR_PRIVATE_KEY = "private key"
521
+ FOR_SERVER_CERTIFICATE = "server certificate"
522
+ FOR_CLIENT_CERTIFICATE = "client certificate"
523
+ FOR_INTERMEDIATE_CA = "intermediate CA"
524
+ FOR_ROOT_CA = "root CA"
525
+ FOR_MTLS_CA = "mTLS CA"
526
+ FOR_MTLS_PEER = "mTLS peer certificate"
527
+ FOR_DH_PARAMETER = "Diffie-Hellman parameter"
528
+
529
+ def __init__(self, pemBundleFilePath: str):
530
+ self._pemBundleFilePath: str = pemBundleFilePath
531
+ self._inspections: Dict[bytes, PemInfo] = {}
532
+
533
+ def log(self, pemBundleName: str):
534
+ defaultLogLevel = logging.root.level
535
+ messageHeader = f"{pemBundleName} PEM bundle contains: \n"
536
+ messages = []
537
+ for pemInfo in self._inspections.values():
538
+ summary = [
539
+ textwrap.indent(f"{key}: '{value}'", " ")
540
+ for key, value in pemInfo.summary.items()
541
+ ]
542
+
543
+ summaryLine = "\n".join(summary)
544
+ if defaultLogLevel < logging.INFO:
545
+ messages.append(
546
+ (
547
+ f" A {pemInfo.pemType} for {pemInfo.pemFor.upper()} with\n"
548
+ f"{summaryLine}\n"
549
+ f" from PEM section "
550
+ f"'{pemInfo.pemStart} ... {pemInfo.pemEnding}'"
551
+ )
552
+ )
553
+ else:
554
+ message = f"a {pemInfo.pemType} for {pemInfo.pemFor.upper()}"
555
+ if pemInfo.pemType == self.PEM_TYPE_CERTIFICATE:
556
+ message += f" with subject {pemInfo.summary['subject']}"
557
+ messages.append(message)
558
+
559
+ if defaultLogLevel < logging.INFO:
560
+ seperator = "\n"
561
+ else:
562
+ seperator = ", "
563
+
564
+ outMsg = messageHeader + seperator.join(messages)
565
+
566
+ for index, line in enumerate(outMsg.splitlines()):
567
+ logger.log(defaultLogLevel, (" " if index else "") + line)
568
+
569
+ def _getPemStart(self, pemBytes: bytes) -> str:
570
+ return pemBytes[: min(50, len(pemBytes))].decode().replace("\n", "")
571
+
572
+ def _getPemEnding(self, pemBytes: bytes) -> str:
573
+ return pemBytes[-min(50, len(pemBytes)) :].decode().replace("\n", "")
574
+
575
+ def viewPrivateKey(self, pemBytes: bytes, pemFor: str = None):
576
+ key = load_pem_private_key(pemBytes, password=None)
577
+ pemInfo = PemInfo(
578
+ pemFor=pemFor,
579
+ pemType=self.PEM_TYPE_PRIVATE_KEY,
580
+ pemStart=self._getPemStart(pemBytes),
581
+ pemEnding=self._getPemEnding(pemBytes),
582
+ summary={"publicKey": key.public_key()},
583
+ )
584
+ self._inspections[pemBytes] = pemInfo
585
+
586
+ def viewCertificate(self, pemBytes: bytes, pemFor: str = None):
587
+ cert = load_pem_x509_certificate(pemBytes)
588
+ pemInfo = PemInfo(
589
+ pemFor=pemFor,
590
+ pemType=self.PEM_TYPE_CERTIFICATE,
591
+ pemStart=self._getPemStart(pemBytes),
592
+ pemEnding=self._getPemEnding(pemBytes),
593
+ summary={
594
+ "subject": X509Util.getSubject(cert),
595
+ "issuer": X509Util.getIssuer(cert),
596
+ "notValidBefore": X509Util.getNotValidBefore(cert),
597
+ "notValidAfter": X509Util.getNotValidAfter(cert),
598
+ "isCa": X509Util.getIsCA(cert),
599
+ "x509Version": X509Util.getX509Version(cert),
600
+ "extendedKeyUsage": X509Util.getExtendedKeyUsages(cert),
601
+ },
602
+ )
603
+ self._inspections[pemBytes] = pemInfo
604
+
605
+ def viewDiffieHellmanParameter(self, pemBytes: bytes, pemFor: str = None):
606
+ dhParam = load_pem_parameters(pemBytes)
607
+ pemInfo = PemInfo(
608
+ pemFor=pemFor,
609
+ pemType=self.PEM_TYPE_DH_PARAMETER,
610
+ pemStart=self._getPemStart(pemBytes),
611
+ pemEnding=self._getPemEnding(pemBytes),
612
+ summary={"length": dhParam.generate_private_key().key_size},
613
+ )
614
+ self._inspections[pemBytes] = pemInfo
615
+
616
+
617
+ def parseTrustRootFromBundle(pemFilePath: str) -> List[TxCertificate]:
618
+ # parse PEM format
619
+ parser = PemBundleParser()
620
+ bank = parser.parse(pemFilePath)
621
+ bank.check(PEM_CHECK_TYPE.CHECK_TYPE_SSL_MTLS_CA)
622
+
623
+ pemInspector = PemViewer(pemFilePath)
624
+ # load certificates in the PEM bundle file
625
+ trustedPeerAuthorities = []
626
+ for trustedPeerCertificateAuthorityPEM in chain(
627
+ bank.intermediateCAs, bank.rootCAs
628
+ ):
629
+ pemBytes = trustedPeerCertificateAuthorityPEM.as_bytes()
630
+ trustedPeerAuthorities.append(ssl.Certificate.loadPEM(pemBytes))
631
+ pemInspector.viewCertificate(pemBytes, pemFor=pemInspector.FOR_MTLS_CA)
632
+ pemInspector.log(pemBundleName="mTLS trusted CA")
633
+
634
+ return trustedPeerAuthorities
635
+
636
+
637
+ PrivateKeyWithFullChain = namedtuple(
638
+ "PrivateKeyWithFullChain",
639
+ ["privateKey", "certificate", "intermediateCAs", "rootCA"],
640
+ defaults=[None, None, [], None],
641
+ )
642
+
643
+
644
+ def parsePemBundleForServer(pemFilePath: str) -> PrivateKeyWithFullChain:
645
+ parser = PemBundleParser()
646
+ bank = parser.parse(pemFilePath)
647
+ bank.check(PEM_CHECK_TYPE.CHECK_TYPE_SSL_SERVER_IDENTITY)
648
+
649
+ pemViewer = PemViewer(pemFilePath)
650
+
651
+ key = bank.privateKeys[0]
652
+ pemViewer.viewPrivateKey(key.as_bytes(), pemFor=pemViewer.FOR_PRIVATE_KEY)
653
+
654
+ cert = bank.certificates[0]
655
+ pemViewer.viewCertificate(
656
+ cert.as_bytes(), pemFor=pemViewer.FOR_SERVER_CERTIFICATE
657
+ )
658
+
659
+ intermediates = bank.intermediateCAs
660
+ for intermediate in intermediates:
661
+ pemViewer.viewCertificate(
662
+ intermediate.as_bytes(), pemFor=pemViewer.FOR_INTERMEDIATE_CA
663
+ )
664
+
665
+ root = bank.rootCAs[0]
666
+ pemViewer.viewCertificate(root.as_bytes(), pemFor=pemViewer.FOR_ROOT_CA)
667
+
668
+ pemViewer.log(pemBundleName="TLS Server")
669
+
670
+ return PrivateKeyWithFullChain(
671
+ privateKey=key,
672
+ certificate=cert,
673
+ intermediateCAs=intermediates,
674
+ rootCA=root,
675
+ )
676
+
677
+
678
+ def parsePemBundleForClient(pemFilePath: str) -> PrivateKeyWithFullChain:
679
+ parser = PemBundleParser()
680
+ bank = parser.parse(pemFilePath)
681
+ bank.check(PEM_CHECK_TYPE.CHECK_TYPE_SSL_CLIENT_IDENTITY)
682
+
683
+ pemViewer = PemViewer(pemFilePath)
684
+
685
+ key = bank.privateKeys[0]
686
+ pemViewer.viewPrivateKey(key.as_bytes(), pemFor=pemViewer.FOR_PRIVATE_KEY)
687
+
688
+ cert = bank.certificates[0]
689
+ pemViewer.viewCertificate(
690
+ cert.as_bytes(), pemFor=pemViewer.FOR_CLIENT_CERTIFICATE
691
+ )
692
+
693
+ intermediates = bank.intermediateCAs
694
+ for intermediate in intermediates:
695
+ pemViewer.viewCertificate(
696
+ intermediate.as_bytes(), pemFor=pemViewer.FOR_INTERMEDIATE_CA
697
+ )
698
+
699
+ root = bank.rootCAs[0]
700
+ pemViewer.viewCertificate(root.as_bytes(), pemFor=pemViewer.FOR_ROOT_CA)
701
+
702
+ pemViewer.log(pemBundleName="TLS Client")
703
+
704
+ return PrivateKeyWithFullChain(
705
+ privateKey=key,
706
+ certificate=cert,
707
+ intermediateCAs=intermediates,
708
+ rootCA=root,
709
+ )
710
+
711
+
712
+ def generateDiffieHellmanParameterBytes(
713
+ outputFilePath: str, length=2048
714
+ ) -> bytes:
715
+ p = dh.generate_parameters(2, length)
716
+ dhBytes = p.parameter_bytes(Encoding.PEM, ParameterFormat.PKCS3)
717
+ with open(outputFilePath, "wb") as outputPem:
718
+ outputPem.write(dhBytes)
719
+ return dhBytes
720
+
721
+
722
+ def parseDiffieHellmanParameter(
723
+ pemFilePath: str,
724
+ ) -> OpenSSLDiffieHellmanParameters:
725
+ file = FilePath(pemFilePath)
726
+ pemViewer = PemViewer(pemFilePath)
727
+ pemViewer.viewDiffieHellmanParameter(
728
+ file.getContent(), pemFor=pemViewer.FOR_DH_PARAMETER
729
+ )
730
+
731
+ pemViewer.log(pemBundleName="Diffie-Hellman Parameter")
732
+ return ssl.DiffieHellmanParameters.fromFile(file)
733
+
734
+
735
+ def parsePemBundleForTrustedPeers(pemFilePath: str) -> List[Certificate]:
736
+ parser = PemBundleParser()
737
+ bank = parser.parse(pemFilePath)
738
+ bank.check(PEM_CHECK_TYPE.CHECK_TYPE_SSL_MTLS_PEER)
739
+
740
+ trustedPeerCertificates = bank.certificates
741
+
742
+ pemViewer = PemViewer(pemFilePath)
743
+ for trustedPeerCertificate in trustedPeerCertificates:
744
+ pemViewer.viewCertificate(
745
+ trustedPeerCertificate.as_bytes(), pemFor=pemViewer.FOR_MTLS_PEER
746
+ )
747
+
748
+ pemViewer.log(pemBundleName="mTLS trusted peers")
749
+
750
+ return trustedPeerCertificates