ssl2 1.0.0__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.
- ssl2-1.0.0/PKG-INFO +180 -0
- ssl2-1.0.0/README.md +172 -0
- ssl2-1.0.0/pyproject.toml +14 -0
- ssl2-1.0.0/pyproject.toml.orig +14 -0
- ssl2-1.0.0/src/ssl2/__init__.py +106 -0
- ssl2-1.0.0/src/ssl2/asynchronous.py +217 -0
- ssl2-1.0.0/src/ssl2/client_hello.py +353 -0
- ssl2-1.0.0/src/ssl2/credentials.py +125 -0
- ssl2-1.0.0/src/ssl2/errors.py +22 -0
- ssl2-1.0.0/src/ssl2/fingerprint.py +326 -0
- ssl2-1.0.0/src/ssl2/interface.py +398 -0
- ssl2-1.0.0/src/ssl2/parameters.py +731 -0
- ssl2-1.0.0/src/ssl2/proxy.py +308 -0
- ssl2-1.0.0/src/ssl2/py.typed +0 -0
ssl2-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: ssl2
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: PEP 748 TLS interface: typed ClientHello model and parser, JA3, JA4 and Akamai fingerprints, proxies, sockets and asyncio streams
|
|
5
|
+
Requires-Python: >=3.14.8
|
|
6
|
+
Project-URL: Repository, https://github.com/baobien-dev/ssl2
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
|
|
9
|
+
# ssl2
|
|
10
|
+
|
|
11
|
+
TLS interface for Python 3.14 after PEP 748, plus what a real client needs around it: typed IANA registries, a ClientHello model with a builder and a wire parser, JA3, JA4 and Akamai fingerprints, proxies, a blocking socket and an asyncio stream that run on any PEP 748 `TLSBuffer`, a session cache and the system trust store. Pure Python, no dependencies. The engine is a separate package, for example `boring-ssl2`.
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
uv add ssl2
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## ClientHello model and fingerprints
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from ssl2 import (
|
|
21
|
+
JA3,
|
|
22
|
+
JA4,
|
|
23
|
+
CertificateCompressionAlgorithm,
|
|
24
|
+
CipherSuite,
|
|
25
|
+
ClientHelloBuilder,
|
|
26
|
+
ECPointFormat,
|
|
27
|
+
ExtensionType,
|
|
28
|
+
NamedGroup,
|
|
29
|
+
NextProtocol,
|
|
30
|
+
ProtocolVersion,
|
|
31
|
+
PskKeyExchangeMode,
|
|
32
|
+
SignatureScheme,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
chrome = (
|
|
36
|
+
ClientHelloBuilder()
|
|
37
|
+
.supported_versions(ProtocolVersion.TLS_1_3, ProtocolVersion.TLS_1_2)
|
|
38
|
+
.cipher_suites(
|
|
39
|
+
CipherSuite.TLS_AES_128_GCM_SHA256,
|
|
40
|
+
CipherSuite.TLS_AES_256_GCM_SHA384,
|
|
41
|
+
CipherSuite.TLS_CHACHA20_POLY1305_SHA256,
|
|
42
|
+
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
|
43
|
+
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
|
44
|
+
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
|
45
|
+
CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
|
46
|
+
CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
|
|
47
|
+
CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
|
|
48
|
+
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
|
49
|
+
CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
|
50
|
+
CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256,
|
|
51
|
+
CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
|
52
|
+
CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,
|
|
53
|
+
CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA,
|
|
54
|
+
)
|
|
55
|
+
.extensions(
|
|
56
|
+
ExtensionType.server_name,
|
|
57
|
+
ExtensionType.extended_main_secret,
|
|
58
|
+
ExtensionType.renegotiation_info,
|
|
59
|
+
ExtensionType.supported_groups,
|
|
60
|
+
ExtensionType.ec_point_formats,
|
|
61
|
+
ExtensionType.session_ticket,
|
|
62
|
+
ExtensionType.application_layer_protocol_negotiation,
|
|
63
|
+
ExtensionType.status_request,
|
|
64
|
+
ExtensionType.signature_algorithms,
|
|
65
|
+
ExtensionType.signed_certificate_timestamp,
|
|
66
|
+
ExtensionType.key_share,
|
|
67
|
+
ExtensionType.psk_key_exchange_modes,
|
|
68
|
+
ExtensionType.supported_versions,
|
|
69
|
+
ExtensionType.compress_certificate,
|
|
70
|
+
ExtensionType.application_settings_old,
|
|
71
|
+
ExtensionType.encrypted_client_hello,
|
|
72
|
+
)
|
|
73
|
+
.supported_groups(NamedGroup.X25519MLKEM768, NamedGroup.x25519, NamedGroup.secp256r1, NamedGroup.secp384r1)
|
|
74
|
+
.key_shares(NamedGroup.X25519MLKEM768, NamedGroup.x25519)
|
|
75
|
+
.ec_point_formats(ECPointFormat.uncompressed)
|
|
76
|
+
.signature_algorithms(
|
|
77
|
+
SignatureScheme.ecdsa_secp256r1_sha256,
|
|
78
|
+
SignatureScheme.rsa_pss_rsae_sha256,
|
|
79
|
+
SignatureScheme.rsa_pkcs1_sha256,
|
|
80
|
+
SignatureScheme.ecdsa_secp384r1_sha384,
|
|
81
|
+
SignatureScheme.rsa_pss_rsae_sha384,
|
|
82
|
+
SignatureScheme.rsa_pkcs1_sha384,
|
|
83
|
+
SignatureScheme.rsa_pss_rsae_sha512,
|
|
84
|
+
SignatureScheme.rsa_pkcs1_sha512,
|
|
85
|
+
)
|
|
86
|
+
.application_protocols(NextProtocol.H2, NextProtocol.HTTP1)
|
|
87
|
+
.application_settings(NextProtocol.H2)
|
|
88
|
+
.certificate_compression(CertificateCompressionAlgorithm.brotli)
|
|
89
|
+
.psk_key_exchange_modes(PskKeyExchangeMode.psk_dhe_ke)
|
|
90
|
+
.grease()
|
|
91
|
+
.build()
|
|
92
|
+
)
|
|
93
|
+
print(JA4.of(chrome).text) # t13d1516h2_8daaf6152771_02713d6af862
|
|
94
|
+
print(JA3.of(chrome).digest) # 5549240a5133e94d2053bfbc454228ef
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Every setter also takes the code point itself, so `.extensions(0, 23, 65281)` and `.cipher_suites(0x1301)` mean the same thing; that is how a captured fingerprint arrives. Unassigned and GREASE code points still parse:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from ssl2 import CipherSuite, ExtensionType
|
|
101
|
+
|
|
102
|
+
print(ExtensionType(17613).name, CipherSuite(0x1A1A).name, CipherSuite(0x1A1A).is_grease) # application_settings unassigned_0x1a1a True
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The parser reads a ClientHello from the TLS record or the bare handshake message, strips GREASE and sets `grease`:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from ssl2 import ClientHelloParser
|
|
109
|
+
|
|
110
|
+
hello = ClientHelloParser.parse(client_hello_bytes)
|
|
111
|
+
print(hello.grease, hello.cipher_suites, hello.key_share_groups)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Fingerprint text forms parse back, except the hashed JA4 form:
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from ssl2 import JA4, AkamaiFingerprint
|
|
118
|
+
|
|
119
|
+
ja4 = JA4.of(chrome)
|
|
120
|
+
print(ja4.raw) # t13d1516h2_002f,0035,...,cca9_0005,000a,...,ff01_0403,0804,...
|
|
121
|
+
print(JA4.parse(ja4.raw).text == ja4.text, JA4.parse(ja4.original).text == ja4.text) # True True
|
|
122
|
+
akamai = AkamaiFingerprint.parse("1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p")
|
|
123
|
+
print(akamai.digest) # 52d84b11737d980aef856699f885ca86
|
|
124
|
+
print(JA3.parse(JA3.of(chrome).text) == JA3.of(chrome)) # True
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`JA3.builder()` starts a `ClientHelloBuilder` from the JA3 fields; the payloads JA3 does not carry, such as signature schemes, key shares and ALPN, go in before `build()`.
|
|
128
|
+
|
|
129
|
+
## Configuration, credentials, trust
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from ssl2 import Certificate, PrivateKey, SigningChain, TLSClientConfiguration, TLSServerConfiguration, TLSVersion, TrustStore
|
|
133
|
+
|
|
134
|
+
client = TLSClientConfiguration(trust_store=TrustStore.system(), lowest_supported_version=TLSVersion.TLSv1_3)
|
|
135
|
+
pinned = TLSClientConfiguration(trust_store=TrustStore.from_file("ca.pem"), client_hello=chrome)
|
|
136
|
+
server = TLSServerConfiguration(certificate_chain=(SigningChain((Certificate.from_file("server.pem"), PrivateKey.from_file("server.key"))),))
|
|
137
|
+
print(TrustStore.system()) # TrustStore(buffer=None, path=PosixPath('/etc/ssl/certs/ca-certificates.crt'), id=None)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`TrustStore.system()` honors `SSL_CERT_FILE` and `SSL_CERT_DIR`, then the usual Linux and macOS bundles, and reads the ROOT and CA stores through crypt32 on Windows.
|
|
141
|
+
|
|
142
|
+
## Proxies
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
from ssl2 import Proxy
|
|
146
|
+
|
|
147
|
+
proxy = Proxy.parse("socks5h://user:secret@proxy.example:1080") # http, https, socks4, socks4a, socks5, socks5h
|
|
148
|
+
print(proxy.scheme, proxy.host, proxy.port, proxy.credentials)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
`socks4` and `socks5` resolve the destination locally, `socks4a`, `socks5h` and the HTTP schemes let the proxy resolve it, and an `https` proxy carries TLS inside TLS with the same client context. A refusal raises `ProxyError` with the proxy's reason.
|
|
152
|
+
|
|
153
|
+
## Sockets and streams on any engine
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
import asyncio
|
|
157
|
+
|
|
158
|
+
from ssl2 import TLSSocket, TLSStream
|
|
159
|
+
|
|
160
|
+
connection = TLSSocket.connect(context, ("www.python.org", 443), proxy=None, timeout=10)
|
|
161
|
+
connection.send(b"HEAD / HTTP/1.1\r\nHost: www.python.org\r\nConnection: close\r\n\r\n")
|
|
162
|
+
print(connection.recv(4096).split(b"\r\n", 1)[0], connection.negotiated_tls_version, connection.cipher())
|
|
163
|
+
connection.close()
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
async def main() -> None:
|
|
167
|
+
stream = await TLSStream.connect(context, ("www.python.org", 443), timeout=10)
|
|
168
|
+
await stream.write(b"HEAD / HTTP/1.1\r\nHost: www.python.org\r\nConnection: close\r\n\r\n")
|
|
169
|
+
print((await stream.read(4096)).split(b"\r\n", 1)[0], stream.negotiated_protocol())
|
|
170
|
+
await stream.close()
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
asyncio.run(main())
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
`context` is an engine's `ClientContext`. A peer that drops TCP without close_notify reads as `b""`, the same default the `ssl` module has.
|
|
177
|
+
|
|
178
|
+
## Writing an engine
|
|
179
|
+
|
|
180
|
+
An engine implements `ClientContext`, `ServerContext` and `TLSBuffer` from `ssl2.interface` and exposes `TLSImplementation(ClientContext, ServerContext, validate_config)`. `TLSSocket` and `TLSStream` drive any such `TLSBuffer`; `SessionCache` stores what the engine records.
|
ssl2-1.0.0/README.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# ssl2
|
|
2
|
+
|
|
3
|
+
TLS interface for Python 3.14 after PEP 748, plus what a real client needs around it: typed IANA registries, a ClientHello model with a builder and a wire parser, JA3, JA4 and Akamai fingerprints, proxies, a blocking socket and an asyncio stream that run on any PEP 748 `TLSBuffer`, a session cache and the system trust store. Pure Python, no dependencies. The engine is a separate package, for example `boring-ssl2`.
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
uv add ssl2
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## ClientHello model and fingerprints
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
from ssl2 import (
|
|
13
|
+
JA3,
|
|
14
|
+
JA4,
|
|
15
|
+
CertificateCompressionAlgorithm,
|
|
16
|
+
CipherSuite,
|
|
17
|
+
ClientHelloBuilder,
|
|
18
|
+
ECPointFormat,
|
|
19
|
+
ExtensionType,
|
|
20
|
+
NamedGroup,
|
|
21
|
+
NextProtocol,
|
|
22
|
+
ProtocolVersion,
|
|
23
|
+
PskKeyExchangeMode,
|
|
24
|
+
SignatureScheme,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
chrome = (
|
|
28
|
+
ClientHelloBuilder()
|
|
29
|
+
.supported_versions(ProtocolVersion.TLS_1_3, ProtocolVersion.TLS_1_2)
|
|
30
|
+
.cipher_suites(
|
|
31
|
+
CipherSuite.TLS_AES_128_GCM_SHA256,
|
|
32
|
+
CipherSuite.TLS_AES_256_GCM_SHA384,
|
|
33
|
+
CipherSuite.TLS_CHACHA20_POLY1305_SHA256,
|
|
34
|
+
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
|
35
|
+
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
|
36
|
+
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
|
37
|
+
CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
|
38
|
+
CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
|
|
39
|
+
CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
|
|
40
|
+
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
|
41
|
+
CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
|
42
|
+
CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256,
|
|
43
|
+
CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
|
44
|
+
CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,
|
|
45
|
+
CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA,
|
|
46
|
+
)
|
|
47
|
+
.extensions(
|
|
48
|
+
ExtensionType.server_name,
|
|
49
|
+
ExtensionType.extended_main_secret,
|
|
50
|
+
ExtensionType.renegotiation_info,
|
|
51
|
+
ExtensionType.supported_groups,
|
|
52
|
+
ExtensionType.ec_point_formats,
|
|
53
|
+
ExtensionType.session_ticket,
|
|
54
|
+
ExtensionType.application_layer_protocol_negotiation,
|
|
55
|
+
ExtensionType.status_request,
|
|
56
|
+
ExtensionType.signature_algorithms,
|
|
57
|
+
ExtensionType.signed_certificate_timestamp,
|
|
58
|
+
ExtensionType.key_share,
|
|
59
|
+
ExtensionType.psk_key_exchange_modes,
|
|
60
|
+
ExtensionType.supported_versions,
|
|
61
|
+
ExtensionType.compress_certificate,
|
|
62
|
+
ExtensionType.application_settings_old,
|
|
63
|
+
ExtensionType.encrypted_client_hello,
|
|
64
|
+
)
|
|
65
|
+
.supported_groups(NamedGroup.X25519MLKEM768, NamedGroup.x25519, NamedGroup.secp256r1, NamedGroup.secp384r1)
|
|
66
|
+
.key_shares(NamedGroup.X25519MLKEM768, NamedGroup.x25519)
|
|
67
|
+
.ec_point_formats(ECPointFormat.uncompressed)
|
|
68
|
+
.signature_algorithms(
|
|
69
|
+
SignatureScheme.ecdsa_secp256r1_sha256,
|
|
70
|
+
SignatureScheme.rsa_pss_rsae_sha256,
|
|
71
|
+
SignatureScheme.rsa_pkcs1_sha256,
|
|
72
|
+
SignatureScheme.ecdsa_secp384r1_sha384,
|
|
73
|
+
SignatureScheme.rsa_pss_rsae_sha384,
|
|
74
|
+
SignatureScheme.rsa_pkcs1_sha384,
|
|
75
|
+
SignatureScheme.rsa_pss_rsae_sha512,
|
|
76
|
+
SignatureScheme.rsa_pkcs1_sha512,
|
|
77
|
+
)
|
|
78
|
+
.application_protocols(NextProtocol.H2, NextProtocol.HTTP1)
|
|
79
|
+
.application_settings(NextProtocol.H2)
|
|
80
|
+
.certificate_compression(CertificateCompressionAlgorithm.brotli)
|
|
81
|
+
.psk_key_exchange_modes(PskKeyExchangeMode.psk_dhe_ke)
|
|
82
|
+
.grease()
|
|
83
|
+
.build()
|
|
84
|
+
)
|
|
85
|
+
print(JA4.of(chrome).text) # t13d1516h2_8daaf6152771_02713d6af862
|
|
86
|
+
print(JA3.of(chrome).digest) # 5549240a5133e94d2053bfbc454228ef
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Every setter also takes the code point itself, so `.extensions(0, 23, 65281)` and `.cipher_suites(0x1301)` mean the same thing; that is how a captured fingerprint arrives. Unassigned and GREASE code points still parse:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from ssl2 import CipherSuite, ExtensionType
|
|
93
|
+
|
|
94
|
+
print(ExtensionType(17613).name, CipherSuite(0x1A1A).name, CipherSuite(0x1A1A).is_grease) # application_settings unassigned_0x1a1a True
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The parser reads a ClientHello from the TLS record or the bare handshake message, strips GREASE and sets `grease`:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from ssl2 import ClientHelloParser
|
|
101
|
+
|
|
102
|
+
hello = ClientHelloParser.parse(client_hello_bytes)
|
|
103
|
+
print(hello.grease, hello.cipher_suites, hello.key_share_groups)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Fingerprint text forms parse back, except the hashed JA4 form:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
from ssl2 import JA4, AkamaiFingerprint
|
|
110
|
+
|
|
111
|
+
ja4 = JA4.of(chrome)
|
|
112
|
+
print(ja4.raw) # t13d1516h2_002f,0035,...,cca9_0005,000a,...,ff01_0403,0804,...
|
|
113
|
+
print(JA4.parse(ja4.raw).text == ja4.text, JA4.parse(ja4.original).text == ja4.text) # True True
|
|
114
|
+
akamai = AkamaiFingerprint.parse("1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p")
|
|
115
|
+
print(akamai.digest) # 52d84b11737d980aef856699f885ca86
|
|
116
|
+
print(JA3.parse(JA3.of(chrome).text) == JA3.of(chrome)) # True
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`JA3.builder()` starts a `ClientHelloBuilder` from the JA3 fields; the payloads JA3 does not carry, such as signature schemes, key shares and ALPN, go in before `build()`.
|
|
120
|
+
|
|
121
|
+
## Configuration, credentials, trust
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from ssl2 import Certificate, PrivateKey, SigningChain, TLSClientConfiguration, TLSServerConfiguration, TLSVersion, TrustStore
|
|
125
|
+
|
|
126
|
+
client = TLSClientConfiguration(trust_store=TrustStore.system(), lowest_supported_version=TLSVersion.TLSv1_3)
|
|
127
|
+
pinned = TLSClientConfiguration(trust_store=TrustStore.from_file("ca.pem"), client_hello=chrome)
|
|
128
|
+
server = TLSServerConfiguration(certificate_chain=(SigningChain((Certificate.from_file("server.pem"), PrivateKey.from_file("server.key"))),))
|
|
129
|
+
print(TrustStore.system()) # TrustStore(buffer=None, path=PosixPath('/etc/ssl/certs/ca-certificates.crt'), id=None)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`TrustStore.system()` honors `SSL_CERT_FILE` and `SSL_CERT_DIR`, then the usual Linux and macOS bundles, and reads the ROOT and CA stores through crypt32 on Windows.
|
|
133
|
+
|
|
134
|
+
## Proxies
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from ssl2 import Proxy
|
|
138
|
+
|
|
139
|
+
proxy = Proxy.parse("socks5h://user:secret@proxy.example:1080") # http, https, socks4, socks4a, socks5, socks5h
|
|
140
|
+
print(proxy.scheme, proxy.host, proxy.port, proxy.credentials)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`socks4` and `socks5` resolve the destination locally, `socks4a`, `socks5h` and the HTTP schemes let the proxy resolve it, and an `https` proxy carries TLS inside TLS with the same client context. A refusal raises `ProxyError` with the proxy's reason.
|
|
144
|
+
|
|
145
|
+
## Sockets and streams on any engine
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
import asyncio
|
|
149
|
+
|
|
150
|
+
from ssl2 import TLSSocket, TLSStream
|
|
151
|
+
|
|
152
|
+
connection = TLSSocket.connect(context, ("www.python.org", 443), proxy=None, timeout=10)
|
|
153
|
+
connection.send(b"HEAD / HTTP/1.1\r\nHost: www.python.org\r\nConnection: close\r\n\r\n")
|
|
154
|
+
print(connection.recv(4096).split(b"\r\n", 1)[0], connection.negotiated_tls_version, connection.cipher())
|
|
155
|
+
connection.close()
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
async def main() -> None:
|
|
159
|
+
stream = await TLSStream.connect(context, ("www.python.org", 443), timeout=10)
|
|
160
|
+
await stream.write(b"HEAD / HTTP/1.1\r\nHost: www.python.org\r\nConnection: close\r\n\r\n")
|
|
161
|
+
print((await stream.read(4096)).split(b"\r\n", 1)[0], stream.negotiated_protocol())
|
|
162
|
+
await stream.close()
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
asyncio.run(main())
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`context` is an engine's `ClientContext`. A peer that drops TCP without close_notify reads as `b""`, the same default the `ssl` module has.
|
|
169
|
+
|
|
170
|
+
## Writing an engine
|
|
171
|
+
|
|
172
|
+
An engine implements `ClientContext`, `ServerContext` and `TLSBuffer` from `ssl2.interface` and exposes `TLSImplementation(ClientContext, ServerContext, validate_config)`. `TLSSocket` and `TLSStream` drive any such `TLSBuffer`; `SessionCache` stores what the engine records.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "ssl2"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "PEP 748 TLS interface: typed ClientHello model and parser, JA3, JA4 and Akamai fingerprints, proxies, sockets and asyncio streams"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.14.8"
|
|
7
|
+
dependencies = []
|
|
8
|
+
|
|
9
|
+
[project.urls]
|
|
10
|
+
Repository = "https://github.com/baobien-dev/ssl2"
|
|
11
|
+
|
|
12
|
+
[build-system]
|
|
13
|
+
requires = ["uv_build>=0.13.0,<0.14.0"]
|
|
14
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "ssl2"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "PEP 748 TLS interface: typed ClientHello model and parser, JA3, JA4 and Akamai fingerprints, proxies, sockets and asyncio streams"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.14.8"
|
|
7
|
+
dependencies = []
|
|
8
|
+
|
|
9
|
+
[project.urls]
|
|
10
|
+
Repository = "https://github.com/baobien-dev/ssl2"
|
|
11
|
+
|
|
12
|
+
[build-system]
|
|
13
|
+
requires = ["uv_build>=0.13.0,<0.14.0"]
|
|
14
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
from ssl2.asynchronous import AsyncTransport, StreamTransport, TLSStream
|
|
2
|
+
from ssl2.client_hello import ClientHello, ClientHelloBuilder, ClientHelloParser
|
|
3
|
+
from ssl2.credentials import (
|
|
4
|
+
Certificate,
|
|
5
|
+
Credential,
|
|
6
|
+
PrivateKey,
|
|
7
|
+
SigningChain,
|
|
8
|
+
TrustStore,
|
|
9
|
+
)
|
|
10
|
+
from ssl2.errors import (
|
|
11
|
+
ConfigurationError,
|
|
12
|
+
EarlyDataRejected,
|
|
13
|
+
RaggedEOF,
|
|
14
|
+
TLSError,
|
|
15
|
+
WantReadError,
|
|
16
|
+
WantWriteError,
|
|
17
|
+
)
|
|
18
|
+
from ssl2.fingerprint import (
|
|
19
|
+
JA3,
|
|
20
|
+
JA4,
|
|
21
|
+
AkamaiFingerprint,
|
|
22
|
+
Http2Priority,
|
|
23
|
+
Http2Setting,
|
|
24
|
+
JA4Transport,
|
|
25
|
+
JA4Version,
|
|
26
|
+
PseudoHeader,
|
|
27
|
+
)
|
|
28
|
+
from ssl2.interface import (
|
|
29
|
+
ClientContext,
|
|
30
|
+
ServerContext,
|
|
31
|
+
SessionCache,
|
|
32
|
+
TLSBuffer,
|
|
33
|
+
TLSClientConfiguration,
|
|
34
|
+
TLSImplementation,
|
|
35
|
+
TLSServerConfiguration,
|
|
36
|
+
TLSSocket,
|
|
37
|
+
Transport,
|
|
38
|
+
)
|
|
39
|
+
from ssl2.parameters import (
|
|
40
|
+
CertificateCompressionAlgorithm,
|
|
41
|
+
CipherSuite,
|
|
42
|
+
CodePoint,
|
|
43
|
+
ECPointFormat,
|
|
44
|
+
ExtensionType,
|
|
45
|
+
Http2SettingIdentifier,
|
|
46
|
+
NamedGroup,
|
|
47
|
+
NextProtocol,
|
|
48
|
+
ProtocolVersion,
|
|
49
|
+
PskKeyExchangeMode,
|
|
50
|
+
SignatureScheme,
|
|
51
|
+
TLSVersion,
|
|
52
|
+
)
|
|
53
|
+
from ssl2.proxy import Proxy, ProxyCredentials, ProxyError, ProxyScheme
|
|
54
|
+
|
|
55
|
+
__all__ = (
|
|
56
|
+
"JA3",
|
|
57
|
+
"JA4",
|
|
58
|
+
"AkamaiFingerprint",
|
|
59
|
+
"AsyncTransport",
|
|
60
|
+
"Certificate",
|
|
61
|
+
"CertificateCompressionAlgorithm",
|
|
62
|
+
"CipherSuite",
|
|
63
|
+
"ClientContext",
|
|
64
|
+
"ClientHello",
|
|
65
|
+
"ClientHelloBuilder",
|
|
66
|
+
"ClientHelloParser",
|
|
67
|
+
"CodePoint",
|
|
68
|
+
"ConfigurationError",
|
|
69
|
+
"Credential",
|
|
70
|
+
"ECPointFormat",
|
|
71
|
+
"EarlyDataRejected",
|
|
72
|
+
"ExtensionType",
|
|
73
|
+
"Http2Priority",
|
|
74
|
+
"Http2Setting",
|
|
75
|
+
"Http2SettingIdentifier",
|
|
76
|
+
"JA4Transport",
|
|
77
|
+
"JA4Version",
|
|
78
|
+
"NamedGroup",
|
|
79
|
+
"NextProtocol",
|
|
80
|
+
"PrivateKey",
|
|
81
|
+
"ProtocolVersion",
|
|
82
|
+
"Proxy",
|
|
83
|
+
"ProxyCredentials",
|
|
84
|
+
"ProxyError",
|
|
85
|
+
"ProxyScheme",
|
|
86
|
+
"PseudoHeader",
|
|
87
|
+
"PskKeyExchangeMode",
|
|
88
|
+
"RaggedEOF",
|
|
89
|
+
"ServerContext",
|
|
90
|
+
"SessionCache",
|
|
91
|
+
"SignatureScheme",
|
|
92
|
+
"SigningChain",
|
|
93
|
+
"StreamTransport",
|
|
94
|
+
"TLSBuffer",
|
|
95
|
+
"TLSClientConfiguration",
|
|
96
|
+
"TLSError",
|
|
97
|
+
"TLSImplementation",
|
|
98
|
+
"TLSServerConfiguration",
|
|
99
|
+
"TLSSocket",
|
|
100
|
+
"TLSStream",
|
|
101
|
+
"TLSVersion",
|
|
102
|
+
"Transport",
|
|
103
|
+
"TrustStore",
|
|
104
|
+
"WantReadError",
|
|
105
|
+
"WantWriteError",
|
|
106
|
+
)
|