tigrcorn-security 0.3.16.dev13__tar.gz → 0.3.17.dev1__tar.gz

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.
Files changed (81) hide show
  1. {tigrcorn_security-0.3.16.dev13 → tigrcorn_security-0.3.17.dev1}/PKG-INFO +3 -3
  2. {tigrcorn_security-0.3.16.dev13 → tigrcorn_security-0.3.17.dev1}/pyproject.toml +3 -3
  3. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls/__init__.py +34 -0
  4. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls/certificates.py +56 -0
  5. tigrcorn_security-0.3.16.dev13/src/tigrcorn_security/tls.py → tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls/connection.py +6 -289
  6. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls/context.py +38 -0
  7. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls/imports.py +66 -0
  8. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls/models.py +31 -0
  9. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls/records.py +50 -0
  10. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls/ssl_object.py +40 -0
  11. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls/wrapping.py +16 -0
  12. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/extensions/__init__.py +18 -0
  13. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/extensions/cipher_suites.py +36 -0
  14. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/extensions/common.py +116 -0
  15. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/extensions/imports.py +31 -0
  16. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/extensions/key_share.py +66 -0
  17. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/extensions/models.py +267 -0
  18. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/extensions/psk.py +83 -0
  19. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/extensions/quic.py +14 -0
  20. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/extensions/registry.py +129 -0
  21. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/extensions/vectors.py +74 -0
  22. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/handshake/__init__.py +29 -0
  23. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/handshake/alerts.py +34 -0
  24. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/handshake/base.py +314 -0
  25. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/handshake/certificates.py +54 -0
  26. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/handshake/client.py +216 -0
  27. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/handshake/driver.py +18 -0
  28. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/handshake/imports.py +100 -0
  29. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/handshake/io.py +159 -0
  30. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/handshake/key_exchange.py +62 -0
  31. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/handshake/models.py +102 -0
  32. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/handshake/replay.py +22 -0
  33. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/handshake/server.py +269 -0
  34. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/handshake/signatures.py +76 -0
  35. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/handshake/state_table.py +8 -0
  36. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/handshake/tickets.py +78 -0
  37. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/handshake/utils.py +42 -0
  38. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/messages/__init__.py +31 -0
  39. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/messages/base.py +17 -0
  40. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/messages/certificates.py +95 -0
  41. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/messages/decode.py +44 -0
  42. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/messages/finished.py +87 -0
  43. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/messages/hello.py +110 -0
  44. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/messages/imports.py +23 -0
  45. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/messages/types.py +22 -0
  46. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/tls13/messages/vectors.py +75 -0
  47. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/x509/path/__init__.py +29 -0
  48. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/x509/path/chain.py +81 -0
  49. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/x509/path/constraints.py +171 -0
  50. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/x509/path/hostname.py +82 -0
  51. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/x509/path/imports.py +59 -0
  52. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/x509/path/loading.py +65 -0
  53. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/x509/path/models.py +144 -0
  54. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/x509/path/revocation_fetch.py +315 -0
  55. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/x509/path/revocation_material.py +205 -0
  56. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/x509/path/revocation_policy.py +142 -0
  57. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security/x509/path/time.py +68 -0
  58. {tigrcorn_security-0.3.16.dev13 → tigrcorn_security-0.3.17.dev1}/src/tigrcorn_security.egg-info/PKG-INFO +3 -3
  59. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security.egg-info/SOURCES.txt +73 -0
  60. tigrcorn_security-0.3.17.dev1/src/tigrcorn_security.egg-info/requires.txt +5 -0
  61. tigrcorn_security-0.3.16.dev13/src/tigrcorn_security/tls13/extensions.py +0 -759
  62. tigrcorn_security-0.3.16.dev13/src/tigrcorn_security/tls13/handshake.py +0 -1411
  63. tigrcorn_security-0.3.16.dev13/src/tigrcorn_security/tls13/messages.py +0 -428
  64. tigrcorn_security-0.3.16.dev13/src/tigrcorn_security/x509/path.py +0 -1284
  65. tigrcorn_security-0.3.16.dev13/src/tigrcorn_security.egg-info/SOURCES.txt +0 -23
  66. tigrcorn_security-0.3.16.dev13/src/tigrcorn_security.egg-info/requires.txt +0 -5
  67. {tigrcorn_security-0.3.16.dev13 → tigrcorn_security-0.3.17.dev1}/LICENSE +0 -0
  68. {tigrcorn_security-0.3.16.dev13 → tigrcorn_security-0.3.17.dev1}/README.md +0 -0
  69. {tigrcorn_security-0.3.16.dev13 → tigrcorn_security-0.3.17.dev1}/setup.cfg +0 -0
  70. {tigrcorn_security-0.3.16.dev13 → tigrcorn_security-0.3.17.dev1}/src/tigrcorn_security/__init__.py +0 -0
  71. {tigrcorn_security-0.3.16.dev13 → tigrcorn_security-0.3.17.dev1}/src/tigrcorn_security/alpn.py +0 -0
  72. {tigrcorn_security-0.3.16.dev13 → tigrcorn_security-0.3.17.dev1}/src/tigrcorn_security/certs.py +0 -0
  73. {tigrcorn_security-0.3.16.dev13 → tigrcorn_security-0.3.17.dev1}/src/tigrcorn_security/policies.py +0 -0
  74. {tigrcorn_security-0.3.16.dev13 → tigrcorn_security-0.3.17.dev1}/src/tigrcorn_security/py.typed +0 -0
  75. {tigrcorn_security-0.3.16.dev13 → tigrcorn_security-0.3.17.dev1}/src/tigrcorn_security/tls13/__init__.py +0 -0
  76. {tigrcorn_security-0.3.16.dev13 → tigrcorn_security-0.3.17.dev1}/src/tigrcorn_security/tls13/key_schedule.py +0 -0
  77. {tigrcorn_security-0.3.16.dev13 → tigrcorn_security-0.3.17.dev1}/src/tigrcorn_security/tls13/transcript.py +0 -0
  78. {tigrcorn_security-0.3.16.dev13 → tigrcorn_security-0.3.17.dev1}/src/tigrcorn_security/tls_cipher_policy.py +0 -0
  79. {tigrcorn_security-0.3.16.dev13 → tigrcorn_security-0.3.17.dev1}/src/tigrcorn_security/x509/__init__.py +0 -0
  80. {tigrcorn_security-0.3.16.dev13 → tigrcorn_security-0.3.17.dev1}/src/tigrcorn_security.egg-info/dependency_links.txt +0 -0
  81. {tigrcorn_security-0.3.16.dev13 → tigrcorn_security-0.3.17.dev1}/src/tigrcorn_security.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tigrcorn-security
3
- Version: 0.3.16.dev13
3
+ Version: 0.3.17.dev1
4
4
  Summary: TLS 1.3, mTLS, X.509, ALPN, OCSP, CRL, and certificate policy surfaces for the Tigrcorn Python web server.
5
5
  Author-email: Jacob Stewart <jacob@swarmauri.com>
6
6
  License: Apache License
@@ -187,8 +187,8 @@ Classifier: Typing :: Typed
187
187
  Requires-Python: <3.15,>=3.10
188
188
  Description-Content-Type: text/markdown
189
189
  License-File: LICENSE
190
- Requires-Dist: tigrcorn-core==0.3.16.dev13
191
- Requires-Dist: tigrcorn-config==0.3.16.dev13
190
+ Requires-Dist: tigrcorn-core==0.3.17.dev1
191
+ Requires-Dist: tigrcorn-config==0.3.17.dev1
192
192
  Provides-Extra: x509
193
193
  Requires-Dist: cryptography>=46.0.0; extra == "x509"
194
194
  Dynamic: license-file
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tigrcorn-security"
7
- version = "0.3.16.dev13"
7
+ version = "0.3.17.dev1"
8
8
  description = "TLS 1.3, mTLS, X.509, ALPN, OCSP, CRL, and certificate policy surfaces for the Tigrcorn Python web server."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10,<3.15"
@@ -30,8 +30,8 @@ classifiers = [
30
30
  "Typing :: Typed",
31
31
  ]
32
32
  dependencies = [
33
- "tigrcorn-core==0.3.16.dev13",
34
- "tigrcorn-config==0.3.16.dev13",
33
+ "tigrcorn-core==0.3.17.dev1",
34
+ "tigrcorn-config==0.3.17.dev1",
35
35
  ]
36
36
 
37
37
  [project.optional-dependencies]
@@ -0,0 +1,34 @@
1
+ from __future__ import annotations
2
+
3
+ from .imports import *
4
+ from .models import *
5
+ from .ssl_object import *
6
+ from .records import *
7
+ from .certificates import *
8
+ from .connection import *
9
+ from .context import *
10
+ from .wrapping import *
11
+
12
+ build_server_tls_context = build_server_ssl_context
13
+
14
+ __all__ = [
15
+ 'PackageOwnedSSLObject',
16
+ 'PackageOwnedTLSConnection',
17
+ 'ServerTLSContext',
18
+ 'build_server_ssl_context',
19
+ 'build_server_tls_context',
20
+ 'wrap_server_tls_connection',
21
+ 'tls_extension_payload',
22
+ 'CertificatePurpose',
23
+ 'CertificateValidationPolicy',
24
+ 'RevocationCache',
25
+ 'RevocationCacheEntry',
26
+ 'RevocationFetchPolicy',
27
+ 'RevocationFreshnessPolicy',
28
+ 'RevocationMaterial',
29
+ 'RevocationMode',
30
+ 'load_pem_certificates',
31
+ 'verify_certificate_validity',
32
+ 'verify_certificate_hostname',
33
+ 'verify_certificate_chain',
34
+ ]
@@ -0,0 +1,56 @@
1
+ from __future__ import annotations
2
+
3
+ from .imports import *
4
+
5
+ def describe_peer_certificate(certificate: x509.Certificate) -> dict[str, Any]:
6
+ return {
7
+ 'subject': certificate.subject.rfc4514_string(),
8
+ 'issuer': certificate.issuer.rfc4514_string(),
9
+ 'serial_number': hex(certificate.serial_number),
10
+ 'not_valid_before': _iso_utc(
11
+ certificate.not_valid_before_utc if hasattr(certificate, 'not_valid_before_utc') else certificate.not_valid_before
12
+ ),
13
+ 'not_valid_after': _iso_utc(
14
+ certificate.not_valid_after_utc if hasattr(certificate, 'not_valid_after_utc') else certificate.not_valid_after
15
+ ),
16
+ }
17
+
18
+
19
+ def tls_extension_payload(writer: Any) -> dict[str, Any] | None:
20
+ ssl_object = getattr(writer, 'get_extra_info', lambda *args, **kwargs: None)('ssl_object')
21
+ if ssl_object is None:
22
+ return None
23
+ payload: dict[str, Any] = {}
24
+ selected_alpn = getattr(ssl_object, 'selected_alpn_protocol', lambda: None)()
25
+ if selected_alpn is not None:
26
+ payload['selected_alpn_protocol'] = selected_alpn
27
+ getpeercert = getattr(ssl_object, 'getpeercert', None)
28
+ if callable(getpeercert):
29
+ peer_cert = getpeercert(binary_form=False)
30
+ if peer_cert is not None:
31
+ payload['peer_cert'] = peer_cert
32
+ return payload or None
33
+
34
+
35
+ def verify_certificate_chain(
36
+ chain_pems: Iterable[bytes],
37
+ trust_roots_pems: Iterable[bytes],
38
+ *,
39
+ server_name: str = '',
40
+ moment: datetime | None = None,
41
+ policy: CertificateValidationPolicy | None = None,
42
+ ) -> x509.Certificate:
43
+ return _verify_certificate_chain(
44
+ chain_pems,
45
+ trust_roots_pems,
46
+ server_name=server_name,
47
+ moment=moment,
48
+ policy=policy,
49
+ )
50
+
51
+ def _iso_utc(moment: datetime) -> str:
52
+ if moment.tzinfo is None:
53
+ moment = moment.replace(tzinfo=timezone.utc)
54
+ return moment.astimezone(timezone.utc).isoformat().replace('+00:00', 'Z')
55
+
56
+ __all__ = [name for name in globals() if not name.startswith('__')]
@@ -1,128 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
- import asyncio
4
- import contextlib
5
- import threading
6
- from dataclasses import dataclass
7
- from datetime import datetime, timezone
8
- from pathlib import Path
9
- from typing import Any, Iterable
10
-
11
- class _MissingDependencyProxy:
12
- def __init__(self, package: str) -> None:
13
- self._package = package
14
-
15
- def __getattr__(self, name: str):
16
- raise ModuleNotFoundError(
17
- f"{self._package} is required for this TLS/X.509 operation; install tigrcorn[tls-x509]"
18
- )
19
-
20
-
21
- try:
22
- from cryptography import x509
23
- from cryptography.hazmat.primitives import serialization
24
- except ModuleNotFoundError: # pragma: no cover - exercised in dependency-light environments
25
- x509 = _MissingDependencyProxy("cryptography") # type: ignore[assignment]
26
- serialization = _MissingDependencyProxy("cryptography") # type: ignore[assignment]
27
-
28
- from tigrcorn_config.model import ListenerConfig
29
- from tigrcorn_core.errors import ProtocolError
30
- from tigrcorn_security.tls13.handshake import QuicTlsHandshakeDriver, TlsAlertError
31
- from tigrcorn_security.tls13.key_schedule import Tls13KeySchedule
32
- from tigrcorn_security.tls13.messages import decode_handshake_message
33
- from tigrcorn_security.policies import build_validation_policy_for_listener
34
- from tigrcorn_security.x509.path import (
35
- CertificatePurpose,
36
- CertificateValidationPolicy,
37
- RevocationCache,
38
- RevocationCacheEntry,
39
- RevocationFetchPolicy,
40
- RevocationFreshnessPolicy,
41
- RevocationMaterial,
42
- RevocationMode,
43
- load_pem_certificates,
44
- verify_certificate_chain as _verify_certificate_chain,
45
- verify_certificate_hostname,
46
- verify_certificate_validity,
47
- )
48
- from tigrcorn_transports.quic.crypto import aes_gcm_decrypt, aes_gcm_encrypt
49
-
50
- _TLS_CONTENT_CHANGE_CIPHER_SPEC = 20
51
- _TLS_CONTENT_ALERT = 21
52
- _TLS_CONTENT_HANDSHAKE = 22
53
- _TLS_CONTENT_APPLICATION_DATA = 23
54
- _TLS_LEGACY_RECORD_VERSION = 0x0303
55
- _TLS_MAX_PLAINTEXT = 16384
56
- _TLS_ALERT_LEVEL_FATAL = 2
57
- _TLS_ALERT_CLOSE_NOTIFY = 0
58
-
59
- _CIPHER_NAMES = {
60
- 0x1301: ('TLS_AES_128_GCM_SHA256', 128),
61
- 0x1302: ('TLS_AES_256_GCM_SHA384', 256),
62
- }
63
-
64
-
65
- @dataclass(frozen=True, slots=True)
66
- class ServerTLSContext:
67
- certificate_pem: bytes
68
- private_key_pem: bytes
69
- private_key_password: bytes | None
70
- trusted_certificates: tuple[bytes, ...]
71
- alpn_protocols: tuple[str, ...]
72
- require_client_certificate: bool
73
- validation_policy: CertificateValidationPolicy
74
- cipher_suites: tuple[int, ...] = (0x1302, 0x1301)
75
- server_name: str = 'localhost'
76
-
77
-
78
- @dataclass(slots=True)
79
- class _RecordProtectionState:
80
- key: bytes
81
- iv: bytes
82
- sequence_number: int = 0
83
-
84
- def next_nonce(self) -> bytes:
85
- sequence = self.sequence_number.to_bytes(8, 'big')
86
- padded = b'\x00' * (len(self.iv) - len(sequence)) + sequence
87
- nonce = bytes(left ^ right for left, right in zip(self.iv, padded))
88
- self.sequence_number += 1
89
- return nonce
90
-
91
-
92
- class PackageOwnedSSLObject:
93
- def __init__(
94
- self,
95
- *,
96
- selected_alpn_protocol: str | None,
97
- cipher_suite: int,
98
- peer_certificate: x509.Certificate | None,
99
- ) -> None:
100
- self._selected_alpn_protocol = selected_alpn_protocol
101
- self._cipher_suite = cipher_suite
102
- self._peer_certificate = peer_certificate
103
- self._peer_certificate_der = (
104
- peer_certificate.public_bytes(serialization.Encoding.DER)
105
- if peer_certificate is not None
106
- else None
107
- )
108
-
109
- def selected_alpn_protocol(self) -> str | None:
110
- return self._selected_alpn_protocol
111
-
112
- def version(self) -> str:
113
- return 'TLSv1.3'
114
-
115
- def cipher(self) -> tuple[str, str, int]:
116
- name, bits = _CIPHER_NAMES.get(self._cipher_suite, ('TLS_UNKNOWN', 0))
117
- return name, 'TLSv1.3', bits
118
-
119
- def getpeercert(self, binary_form: bool = False) -> dict[str, Any] | bytes | None:
120
- if self._peer_certificate is None:
121
- return None
122
- if binary_form:
123
- return self._peer_certificate_der
124
- return describe_peer_certificate(self._peer_certificate)
125
-
3
+ from .imports import *
4
+ from .models import *
5
+ from .ssl_object import *
6
+ from .records import *
7
+ from .certificates import *
126
8
 
127
9
  class PackageOwnedTLSConnection:
128
10
  def __init__(
@@ -415,169 +297,4 @@ class PackageOwnedTLSConnection:
415
297
  )
416
298
  await self._raw_writer.drain()
417
299
 
418
-
419
- def _build_record_state(secret: bytes, *, key_length: int, iv_length: int, hash_name: str) -> _RecordProtectionState:
420
- schedule = Tls13KeySchedule(hash_name=hash_name)
421
- return _RecordProtectionState(
422
- key=schedule.expand_label(secret, 'key', b'', key_length),
423
- iv=schedule.expand_label(secret, 'iv', b'', iv_length),
424
- )
425
-
426
-
427
- def _encode_plain_record(content_type: int, payload: bytes) -> bytes:
428
- return bytes([content_type]) + _TLS_LEGACY_RECORD_VERSION.to_bytes(2, 'big') + len(payload).to_bytes(2, 'big') + payload
429
-
430
-
431
- def _encrypt_record(payload: bytes, inner_content_type: int, state: _RecordProtectionState) -> bytes:
432
- inner = payload + bytes([inner_content_type])
433
- nonce = state.next_nonce()
434
- body_length = len(inner) + 16
435
- header = (
436
- bytes([_TLS_CONTENT_APPLICATION_DATA])
437
- + _TLS_LEGACY_RECORD_VERSION.to_bytes(2, 'big')
438
- + body_length.to_bytes(2, 'big')
439
- )
440
- ciphertext, tag = aes_gcm_encrypt(state.key, nonce, inner, aad=header)
441
- return header + ciphertext + tag
442
-
443
-
444
- def _decrypt_record(payload: bytes, state: _RecordProtectionState) -> tuple[bytes, int]:
445
- if len(payload) < 16:
446
- raise ProtocolError('truncated TLS application-data record')
447
- header = (
448
- bytes([_TLS_CONTENT_APPLICATION_DATA])
449
- + _TLS_LEGACY_RECORD_VERSION.to_bytes(2, 'big')
450
- + len(payload).to_bytes(2, 'big')
451
- )
452
- ciphertext = payload[:-16]
453
- tag = payload[-16:]
454
- nonce = state.next_nonce()
455
- plaintext = aes_gcm_decrypt(state.key, nonce, ciphertext, tag, aad=header)
456
- index = len(plaintext) - 1
457
- while index >= 0 and plaintext[index] == 0:
458
- index -= 1
459
- if index < 0:
460
- raise ProtocolError('TLS inner plaintext is missing a content type')
461
- return plaintext[:index], plaintext[index]
462
-
463
-
464
- def describe_peer_certificate(certificate: x509.Certificate) -> dict[str, Any]:
465
- return {
466
- 'subject': certificate.subject.rfc4514_string(),
467
- 'issuer': certificate.issuer.rfc4514_string(),
468
- 'serial_number': hex(certificate.serial_number),
469
- 'not_valid_before': _iso_utc(
470
- certificate.not_valid_before_utc if hasattr(certificate, 'not_valid_before_utc') else certificate.not_valid_before
471
- ),
472
- 'not_valid_after': _iso_utc(
473
- certificate.not_valid_after_utc if hasattr(certificate, 'not_valid_after_utc') else certificate.not_valid_after
474
- ),
475
- }
476
-
477
-
478
- def tls_extension_payload(writer: Any) -> dict[str, Any] | None:
479
- ssl_object = getattr(writer, 'get_extra_info', lambda *args, **kwargs: None)('ssl_object')
480
- if ssl_object is None:
481
- return None
482
- payload: dict[str, Any] = {}
483
- selected_alpn = getattr(ssl_object, 'selected_alpn_protocol', lambda: None)()
484
- if selected_alpn is not None:
485
- payload['selected_alpn_protocol'] = selected_alpn
486
- getpeercert = getattr(ssl_object, 'getpeercert', None)
487
- if callable(getpeercert):
488
- peer_cert = getpeercert(binary_form=False)
489
- if peer_cert is not None:
490
- payload['peer_cert'] = peer_cert
491
- return payload or None
492
-
493
-
494
- def build_server_ssl_context(listener: ListenerConfig) -> ServerTLSContext | None:
495
- if not listener.ssl_enabled:
496
- return None
497
- assert listener.ssl_certfile is not None
498
- assert listener.ssl_keyfile is not None
499
- certificate_pem = Path(listener.ssl_certfile).read_bytes()
500
- private_key_pem = Path(listener.ssl_keyfile).read_bytes()
501
- private_key_password = getattr(listener, 'ssl_keyfile_password', None)
502
- if private_key_password is not None and not isinstance(private_key_password, bytes):
503
- private_key_password = str(private_key_password).encode('utf-8')
504
- trusted = (Path(listener.ssl_ca_certs).read_bytes(),) if listener.ssl_ca_certs else ()
505
- validation_policy = build_validation_policy_for_listener(listener)
506
- server_name = _listener_server_name(listener)
507
- return ServerTLSContext(
508
- certificate_pem=certificate_pem,
509
- private_key_pem=private_key_pem,
510
- private_key_password=private_key_password,
511
- trusted_certificates=trusted,
512
- alpn_protocols=tuple(listener.alpn_protocols),
513
- require_client_certificate=listener.ssl_require_client_cert,
514
- validation_policy=validation_policy,
515
- cipher_suites=tuple(int(item) for item in (getattr(listener, 'resolved_cipher_suites', ()) or (0x1302, 0x1301))),
516
- server_name=server_name,
517
- )
518
-
519
-
520
- async def wrap_server_tls_connection(
521
- raw_reader: asyncio.StreamReader,
522
- raw_writer: asyncio.StreamWriter,
523
- context: ServerTLSContext,
524
- ) -> PackageOwnedTLSConnection:
525
- connection = PackageOwnedTLSConnection(raw_reader, raw_writer, context)
526
- await connection.handshake()
527
- return connection
528
-
529
-
530
- build_server_tls_context = build_server_ssl_context
531
-
532
-
533
- def verify_certificate_chain(
534
- chain_pems: Iterable[bytes],
535
- trust_roots_pems: Iterable[bytes],
536
- *,
537
- server_name: str = '',
538
- moment: datetime | None = None,
539
- policy: CertificateValidationPolicy | None = None,
540
- ) -> x509.Certificate:
541
- return _verify_certificate_chain(
542
- chain_pems,
543
- trust_roots_pems,
544
- server_name=server_name,
545
- moment=moment,
546
- policy=policy,
547
- )
548
-
549
-
550
- def _listener_server_name(listener: ListenerConfig) -> str:
551
- host = listener.host or 'localhost'
552
- if host in {'0.0.0.0', '::', ''}:
553
- return 'localhost'
554
- return host
555
-
556
-
557
- def _iso_utc(moment: datetime) -> str:
558
- if moment.tzinfo is None:
559
- moment = moment.replace(tzinfo=timezone.utc)
560
- return moment.astimezone(timezone.utc).isoformat().replace('+00:00', 'Z')
561
-
562
-
563
- __all__ = [
564
- 'PackageOwnedSSLObject',
565
- 'PackageOwnedTLSConnection',
566
- 'ServerTLSContext',
567
- 'build_server_ssl_context',
568
- 'build_server_tls_context',
569
- 'wrap_server_tls_connection',
570
- 'tls_extension_payload',
571
- 'CertificatePurpose',
572
- 'CertificateValidationPolicy',
573
- 'RevocationCache',
574
- 'RevocationCacheEntry',
575
- 'RevocationFetchPolicy',
576
- 'RevocationFreshnessPolicy',
577
- 'RevocationMaterial',
578
- 'RevocationMode',
579
- 'load_pem_certificates',
580
- 'verify_certificate_validity',
581
- 'verify_certificate_hostname',
582
- 'verify_certificate_chain',
583
- ]
300
+ __all__ = [name for name in globals() if not name.startswith('__')]
@@ -0,0 +1,38 @@
1
+ from __future__ import annotations
2
+
3
+ from .imports import *
4
+ from .models import *
5
+
6
+ def build_server_ssl_context(listener: ListenerConfig) -> ServerTLSContext | None:
7
+ if not listener.ssl_enabled:
8
+ return None
9
+ assert listener.ssl_certfile is not None
10
+ assert listener.ssl_keyfile is not None
11
+ certificate_pem = Path(listener.ssl_certfile).read_bytes()
12
+ private_key_pem = Path(listener.ssl_keyfile).read_bytes()
13
+ private_key_password = getattr(listener, 'ssl_keyfile_password', None)
14
+ if private_key_password is not None and not isinstance(private_key_password, bytes):
15
+ private_key_password = str(private_key_password).encode('utf-8')
16
+ trusted = (Path(listener.ssl_ca_certs).read_bytes(),) if listener.ssl_ca_certs else ()
17
+ validation_policy = build_validation_policy_for_listener(listener)
18
+ server_name = _listener_server_name(listener)
19
+ return ServerTLSContext(
20
+ certificate_pem=certificate_pem,
21
+ private_key_pem=private_key_pem,
22
+ private_key_password=private_key_password,
23
+ trusted_certificates=trusted,
24
+ alpn_protocols=tuple(listener.alpn_protocols),
25
+ require_client_certificate=listener.ssl_require_client_cert,
26
+ validation_policy=validation_policy,
27
+ cipher_suites=tuple(int(item) for item in (getattr(listener, 'resolved_cipher_suites', ()) or (0x1302, 0x1301))),
28
+ server_name=server_name,
29
+ )
30
+
31
+
32
+ def _listener_server_name(listener: ListenerConfig) -> str:
33
+ host = listener.host or 'localhost'
34
+ if host in {'0.0.0.0', '::', ''}:
35
+ return 'localhost'
36
+ return host
37
+
38
+ __all__ = [name for name in globals() if not name.startswith('__')]
@@ -0,0 +1,66 @@
1
+ from __future__ import annotations
2
+
3
+
4
+
5
+ import asyncio
6
+ import contextlib
7
+ import threading
8
+ from dataclasses import dataclass
9
+ from datetime import datetime, timezone
10
+ from pathlib import Path
11
+ from typing import Any, Iterable
12
+
13
+ class _MissingDependencyProxy:
14
+ def __init__(self, package: str) -> None:
15
+ self._package = package
16
+
17
+ def __getattr__(self, name: str):
18
+ raise ModuleNotFoundError(
19
+ f"{self._package} is required for this TLS/X.509 operation; install tigrcorn[tls-x509]"
20
+ )
21
+
22
+
23
+ try:
24
+ from cryptography import x509
25
+ from cryptography.hazmat.primitives import serialization
26
+ except ModuleNotFoundError: # pragma: no cover - exercised in dependency-light environments
27
+ x509 = _MissingDependencyProxy("cryptography") # type: ignore[assignment]
28
+ serialization = _MissingDependencyProxy("cryptography") # type: ignore[assignment]
29
+
30
+ from tigrcorn_config.model import ListenerConfig
31
+ from tigrcorn_core.errors import ProtocolError
32
+ from tigrcorn_security.tls13.handshake import QuicTlsHandshakeDriver, TlsAlertError
33
+ from tigrcorn_security.tls13.key_schedule import Tls13KeySchedule
34
+ from tigrcorn_security.tls13.messages import decode_handshake_message
35
+ from tigrcorn_security.policies import build_validation_policy_for_listener
36
+ from tigrcorn_security.x509.path import (
37
+ CertificatePurpose,
38
+ CertificateValidationPolicy,
39
+ RevocationCache,
40
+ RevocationCacheEntry,
41
+ RevocationFetchPolicy,
42
+ RevocationFreshnessPolicy,
43
+ RevocationMaterial,
44
+ RevocationMode,
45
+ load_pem_certificates,
46
+ verify_certificate_chain as _verify_certificate_chain,
47
+ verify_certificate_hostname,
48
+ verify_certificate_validity,
49
+ )
50
+ from tigrcorn_transports.quic.crypto import aes_gcm_decrypt, aes_gcm_encrypt
51
+
52
+ _TLS_CONTENT_CHANGE_CIPHER_SPEC = 20
53
+ _TLS_CONTENT_ALERT = 21
54
+ _TLS_CONTENT_HANDSHAKE = 22
55
+ _TLS_CONTENT_APPLICATION_DATA = 23
56
+ _TLS_LEGACY_RECORD_VERSION = 0x0303
57
+ _TLS_MAX_PLAINTEXT = 16384
58
+ _TLS_ALERT_LEVEL_FATAL = 2
59
+ _TLS_ALERT_CLOSE_NOTIFY = 0
60
+
61
+ _CIPHER_NAMES = {
62
+ 0x1301: ('TLS_AES_128_GCM_SHA256', 128),
63
+ 0x1302: ('TLS_AES_256_GCM_SHA384', 256),
64
+ }
65
+
66
+ __all__ = [name for name in globals() if not name.startswith('__')]
@@ -0,0 +1,31 @@
1
+ from __future__ import annotations
2
+
3
+ from .imports import *
4
+
5
+ @dataclass(frozen=True, slots=True)
6
+ class ServerTLSContext:
7
+ certificate_pem: bytes
8
+ private_key_pem: bytes
9
+ private_key_password: bytes | None
10
+ trusted_certificates: tuple[bytes, ...]
11
+ alpn_protocols: tuple[str, ...]
12
+ require_client_certificate: bool
13
+ validation_policy: CertificateValidationPolicy
14
+ cipher_suites: tuple[int, ...] = (0x1302, 0x1301)
15
+ server_name: str = 'localhost'
16
+
17
+
18
+ @dataclass(slots=True)
19
+ class _RecordProtectionState:
20
+ key: bytes
21
+ iv: bytes
22
+ sequence_number: int = 0
23
+
24
+ def next_nonce(self) -> bytes:
25
+ sequence = self.sequence_number.to_bytes(8, 'big')
26
+ padded = b'\x00' * (len(self.iv) - len(sequence)) + sequence
27
+ nonce = bytes(left ^ right for left, right in zip(self.iv, padded))
28
+ self.sequence_number += 1
29
+ return nonce
30
+
31
+ __all__ = [name for name in globals() if not name.startswith('__')]
@@ -0,0 +1,50 @@
1
+ from __future__ import annotations
2
+
3
+ from .imports import *
4
+ from .models import *
5
+
6
+ def _build_record_state(secret: bytes, *, key_length: int, iv_length: int, hash_name: str) -> _RecordProtectionState:
7
+ schedule = Tls13KeySchedule(hash_name=hash_name)
8
+ return _RecordProtectionState(
9
+ key=schedule.expand_label(secret, 'key', b'', key_length),
10
+ iv=schedule.expand_label(secret, 'iv', b'', iv_length),
11
+ )
12
+
13
+
14
+ def _encode_plain_record(content_type: int, payload: bytes) -> bytes:
15
+ return bytes([content_type]) + _TLS_LEGACY_RECORD_VERSION.to_bytes(2, 'big') + len(payload).to_bytes(2, 'big') + payload
16
+
17
+
18
+ def _encrypt_record(payload: bytes, inner_content_type: int, state: _RecordProtectionState) -> bytes:
19
+ inner = payload + bytes([inner_content_type])
20
+ nonce = state.next_nonce()
21
+ body_length = len(inner) + 16
22
+ header = (
23
+ bytes([_TLS_CONTENT_APPLICATION_DATA])
24
+ + _TLS_LEGACY_RECORD_VERSION.to_bytes(2, 'big')
25
+ + body_length.to_bytes(2, 'big')
26
+ )
27
+ ciphertext, tag = aes_gcm_encrypt(state.key, nonce, inner, aad=header)
28
+ return header + ciphertext + tag
29
+
30
+
31
+ def _decrypt_record(payload: bytes, state: _RecordProtectionState) -> tuple[bytes, int]:
32
+ if len(payload) < 16:
33
+ raise ProtocolError('truncated TLS application-data record')
34
+ header = (
35
+ bytes([_TLS_CONTENT_APPLICATION_DATA])
36
+ + _TLS_LEGACY_RECORD_VERSION.to_bytes(2, 'big')
37
+ + len(payload).to_bytes(2, 'big')
38
+ )
39
+ ciphertext = payload[:-16]
40
+ tag = payload[-16:]
41
+ nonce = state.next_nonce()
42
+ plaintext = aes_gcm_decrypt(state.key, nonce, ciphertext, tag, aad=header)
43
+ index = len(plaintext) - 1
44
+ while index >= 0 and plaintext[index] == 0:
45
+ index -= 1
46
+ if index < 0:
47
+ raise ProtocolError('TLS inner plaintext is missing a content type')
48
+ return plaintext[:index], plaintext[index]
49
+
50
+ __all__ = [name for name in globals() if not name.startswith('__')]
@@ -0,0 +1,40 @@
1
+ from __future__ import annotations
2
+
3
+ from .imports import *
4
+ from .models import *
5
+
6
+ class PackageOwnedSSLObject:
7
+ def __init__(
8
+ self,
9
+ *,
10
+ selected_alpn_protocol: str | None,
11
+ cipher_suite: int,
12
+ peer_certificate: x509.Certificate | None,
13
+ ) -> None:
14
+ self._selected_alpn_protocol = selected_alpn_protocol
15
+ self._cipher_suite = cipher_suite
16
+ self._peer_certificate = peer_certificate
17
+ self._peer_certificate_der = (
18
+ peer_certificate.public_bytes(serialization.Encoding.DER)
19
+ if peer_certificate is not None
20
+ else None
21
+ )
22
+
23
+ def selected_alpn_protocol(self) -> str | None:
24
+ return self._selected_alpn_protocol
25
+
26
+ def version(self) -> str:
27
+ return 'TLSv1.3'
28
+
29
+ def cipher(self) -> tuple[str, str, int]:
30
+ name, bits = _CIPHER_NAMES.get(self._cipher_suite, ('TLS_UNKNOWN', 0))
31
+ return name, 'TLSv1.3', bits
32
+
33
+ def getpeercert(self, binary_form: bool = False) -> dict[str, Any] | bytes | None:
34
+ if self._peer_certificate is None:
35
+ return None
36
+ if binary_form:
37
+ return self._peer_certificate_der
38
+ return describe_peer_certificate(self._peer_certificate)
39
+
40
+ __all__ = [name for name in globals() if not name.startswith('__')]
@@ -0,0 +1,16 @@
1
+ from __future__ import annotations
2
+
3
+ from .imports import *
4
+ from .models import *
5
+ from .connection import *
6
+
7
+ async def wrap_server_tls_connection(
8
+ raw_reader: asyncio.StreamReader,
9
+ raw_writer: asyncio.StreamWriter,
10
+ context: ServerTLSContext,
11
+ ) -> PackageOwnedTLSConnection:
12
+ connection = PackageOwnedTLSConnection(raw_reader, raw_writer, context)
13
+ await connection.handshake()
14
+ return connection
15
+
16
+ __all__ = [name for name in globals() if not name.startswith('__')]