ccssh 1.0.0__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,442 @@
1
+ Metadata-Version: 2.4
2
+ Name: ccssh
3
+ Version: 1.0.0
4
+ Summary: From-scratch SSH client in Python (sync + async + CLI), OpenSSH-interop verified
5
+ Author: MishaKorzhik_He1Zen
6
+ Author-email: developer.mishakorzhik@gmail.com
7
+ License: MIT
8
+ Project-URL: Bug Tracker, https://github.com/mishakorzik/ccssh/issues
9
+ Project-URL: Donate, https://www.buymeacoffee.com/misakorzik
10
+ Keywords: ssh,shell,remote,tcp,forward,tunnel,async,sync,openssh,open,fast,encrypt,encryption,server,network
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Programming Language :: Python :: 3.15
22
+ Classifier: Programming Language :: Python :: 3.16
23
+ Classifier: Topic :: System :: Networking
24
+ Classifier: Topic :: Security :: Cryptography
25
+ Requires-Python: >=3.9
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: cryptography>=41
29
+ Dynamic: author
30
+ Dynamic: author-email
31
+ Dynamic: classifier
32
+ Dynamic: description
33
+ Dynamic: description-content-type
34
+ Dynamic: keywords
35
+ Dynamic: license
36
+ Dynamic: license-file
37
+ Dynamic: project-url
38
+ Dynamic: requires-dist
39
+ Dynamic: requires-python
40
+ Dynamic: summary
41
+
42
+ # Concurrent-Channels SSH
43
+
44
+ From-scratch SSH-2 client in Python (sync + async + CLI), verified against
45
+ real OpenSSH servers - including post-quantum `mlkem768x25519-sha256` KEX
46
+ and `ssh-mldsa44-ed25519` host/user keys.
47
+
48
+ ```bash
49
+ pip install ccssh
50
+ ccssh user@host # interactive shell
51
+ ccssh user@host uptime # one command
52
+ python -m ccssh user@host -N -L 8080:127.0.0.1:80
53
+ ```
54
+
55
+ See `tests/test_live_sshd.py` for the live interop proof matrix.
56
+
57
+ ## CLI
58
+
59
+ Options work before or after the destination (`ccssh -p 2022 h` and
60
+ `ccssh h -p 2022` are the same); the first bare word is the destination,
61
+ everything after it is the remote command, verbatim.
62
+
63
+ ```bash
64
+ ccssh -4 -i ./id_ed25519 he1zen@host -p 2022 # shell over IPv4 with a key
65
+ ccssh he1zen@host -p 2022 uptime # one command, port after host
66
+ ccssh -t he1zen@host htop # command on a pty
67
+ ccssh -T he1zen@host # shell without pty
68
+ ccssh -N -L 8080:127.0.0.1:80 he1zen@host # local forwarding only
69
+ ccssh -L 8080:127.0.0.1:80 he1zen@host # forward + shell (shared connection)
70
+ ccssh -R 8080:127.0.0.1:80 he1zen@host uptime # forward + one command
71
+ ccssh -N -R 2022:127.0.0.1:22 he1zen@host # remote forwarding only
72
+ ccssh -J jumpuser@jumphost he1zen@target # ProxyJump (comma chains ok)
73
+ ccssh -q he1zen@host 'echo quiet' # quiet: no info output
74
+ ccssh -n he1zen@host < script.sh # stdin from /dev/null
75
+ ccssh -v he1zen@host # verbose: debug1 progress
76
+ ccssh -vv he1zen@host # debug2: full offers + traces
77
+ ccssh -V # version
78
+ ccssh -l he1zen -p 2022 host # explicit login + port
79
+ ccssh --cert ~/.ssh/id_ed25519-cert.pub -i ~/.ssh/id_ed25519 he1zen@host
80
+ ccssh --password-env SSHPASS he1zen@host # password from env (never argv)
81
+ ccssh -o KexAlgorithms=curve25519-sha256 -o Ciphers=aes256-ctr \
82
+ -o MACs=hmac-sha2-512 -o HostKeyAlgorithms=ssh-ed25519 \
83
+ -o ConnectTimeout=5 -o StrictHostKeyChecking=accept-new \
84
+ -o UserKnownHostsFile=~/.ssh/known_hosts he1zen@host
85
+ ccssh -c aes256-ctr -m hmac-sha2-512 he1zen@host # like ssh -c/-m
86
+ ccssh -o HostKeyAlgorithms=+ssh-dss he1zen@oldbox # append to the default set
87
+ ccssh -Q cipher # list supported ciphers (mac, kex, key too)
88
+ ```
89
+
90
+ No password or key given on a terminal? ccssh prompts
91
+ (`he1zen@host's password:`), retrying up to 3 times; encrypted `-i` keys
92
+ prompt for their passphrase. Without a terminal it fails fast instead of
93
+ hanging - use `--password-env` for scripts.
94
+
95
+ ## Sync client
96
+
97
+ ```python
98
+ from ccssh import CCSSHClient
99
+
100
+ with CCSSHClient() as c:
101
+ c.connect("host", username="user", password="secret")
102
+ print(c.exec_command("uptime")) # (stdout, stderr, exit_status)
103
+ print(c.exec_command("cat disk.iso", raw_bytes=True)) # bytes, no decoding
104
+ ```
105
+
106
+ All `connect()` options:
107
+
108
+ ```python
109
+ c = CCSSHClient(
110
+ timeout=10.0, # socket/connect timeout, seconds
111
+ known_hosts="~/.ssh/known_hosts", # None -> default path
112
+ host_policy="strict", # strict | accept-new | off
113
+ kex=["curve25519-sha256"], # any subset of algs.SUPPORTED_KEX
114
+ ciphers=["aes256-ctr"], # any subset of algs.SUPPORTED_CIPHERS
115
+ macs=["hmac-sha2-512"], # any subset of algs.SUPPORTED_MACS
116
+ hostkeys=["ssh-ed25519"], # any subset of algs.SUPPORTED_HOSTKEYS
117
+ rekey_bytes=4 * 1024**3, # auto-rekey after this much traffic
118
+ rekey_interval=3600.0, # ... or this many seconds
119
+ )
120
+ c.connect("host", port=2222, username="user",
121
+ password="secret", # or key_filename= / cert_filename=
122
+ key_filename="~/.ssh/id_ed25519",
123
+ passphrase="key-passphrase", # for encrypted keys
124
+ cert_filename="~/.ssh/id_ed25519-cert.pub",
125
+ family=0) # 0:any, socket.AF_INET (-4), AF_INET6 (-6)
126
+ print(c.negotiated()) # {"cipher": ..., "mac": ..., "strict": ...}
127
+ c.close()
128
+ ```
129
+
130
+ Key auth tries each `-i`/`key_filename` in turn, then falls back to the
131
+ password - like OpenSSH. If the password method fails, the same secret
132
+ is tried via keyboard-interactive (RFC 4256, capped rounds/prompts):
133
+ servers like serveo.net that only offer `keyboard-interactive` work with
134
+ no extra flags. Certificates pass through opaquely from the
135
+ `ssh-ed25519-cert-v01@openssh.com` file (user certs) and are verified
136
+ against `@cert-authority` lines in known_hosts (host certs).
137
+
138
+ Host-key policies: `strict` refuses unknown/changed keys (hashed
139
+ known_hosts entries supported), `accept-new` adds unknown hosts,
140
+ `ask` prompts on unknown hosts (tty only, fail-closed otherwise),
141
+ `off` skips verification (tests only). `UserKnownHostsFile` accepts a
142
+ list and the magic name `none`; unknown `-o` keys fail loudly.
143
+
144
+ Exit codes follow ssh: `0` ok, `255` everything else (usage, auth
145
+ `Permission denied`, connect/forward failures), `141` on broken pipe,
146
+ remote exit status passed through for commands.
147
+
148
+ ## SFTP (v3)
149
+
150
+ Bytes in, bytes out - encode text yourself:
151
+
152
+ ```python
153
+ s = c.open_sftp()
154
+ s.realpath("/") # canonical path, ends with "/"
155
+ s.mkdir("/sub") # mode= kwarg, default 0o755
156
+ s.put("/sub/f.bin", data) # mode= kwarg, default 0o644
157
+ s.stat("/sub/f.bin") # {"size": ..., "mtime": ..., ...}
158
+ s.listdir("/sub") # ["f.bin"]
159
+ assert s.get("/sub/f.bin") == data
160
+ s.remove("/sub/f.bin")
161
+ s.close() # closes the subsystem channel
162
+ ```
163
+
164
+ Text files, error handling, big downloads:
165
+
166
+ ```python
167
+ s.put("/notes.txt", "привіт\n".encode())
168
+ text = s.get("/notes.txt").decode()
169
+
170
+ from ccssh.sftp import SFTPError
171
+ try:
172
+ s.get("/missing")
173
+ except SFTPError as e: # status code + server message, never silent
174
+ print("failed:", e)
175
+
176
+ big = s.get("/disk.iso", max_bytes=4 * 1024**3) # cap a runaway server
177
+ ```
178
+
179
+ `put` overwrites (`CREAT|TRUNC`) and splits into ~32KB chunks for the
180
+ server window automatically; `get` streams in 32KB reads (a mid-transfer
181
+ server error raises instead of truncating silently).
182
+
183
+ Recipes - `mkdir -p` and a recursive walk:
184
+
185
+ ```python
186
+ def mkdir_p(s, path):
187
+ parts, cur = [p for p in path.split("/") if p], ""
188
+ for p in parts:
189
+ cur += "/" + p
190
+ try:
191
+ s.mkdir(cur)
192
+ except SFTPError: # exists (or no permission - then the put fails next)
193
+ pass
194
+
195
+ def walk(s, path):
196
+ """Yield (dirpath, filenames) like os.walk."""
197
+ yield path, s.listdir(path)
198
+
199
+ mkdir_p(s, "/a/b/c")
200
+ s.put("/a/b/c/f.txt", b"x")
201
+ for dirpath, names in walk(s, "/a"):
202
+ print(dirpath, names)
203
+ ```
204
+
205
+ ## Interactive shell
206
+
207
+ ```python
208
+ from ccssh import shell as SH
209
+
210
+ code = SH.run_interactive(c) # ssh-like shell, returns exit status
211
+ code = SH.run_interactive(c, pty=False) # ssh -T: no pty
212
+ code = SH.run_interactive(c, command="htop") # ssh -t: pty exec
213
+ ```
214
+
215
+ Ctrl+D exits (the remote shell sees EOF and closes; no hanging, no
216
+ Ctrl+C needed). Idle sessions stay up: steady-state read timeouts send
217
+ SSH IGNORE keepalives instead of dropping (like OpenSSH); handshake and
218
+ SFTP window waits still fail fast on a dead peer. Terminal goes to raw
219
+ and is always restored. Programmatic use without a terminal:
220
+
221
+ ```python
222
+ sess = c.open_shell(cols=80, rows=24) # pty + shell
223
+ sess = c.open_shell(pty=False) # shell without pty
224
+ sess = c.open_shell(command="top") # pty exec channel
225
+ sess.send(b"echo hi\n") # stdin bytes (window-aware)
226
+ sess.send_eof() # half-close stdin
227
+ sess.window_change(132, 50) # terminal resize
228
+ sess.close() # send CLOSE, returns last exit status or None
229
+ print(sess.exit_status, sess.eof_received, sess.no_pty)
230
+ ```
231
+
232
+ Read server output from `sess._client._t.recv_packet()` and parse
233
+ `CHANNEL_DATA` / `CHANNEL_EXTENDED_DATA` / `exit-status` / `EOF` / `CLOSE`
234
+ (see `tests/test_shell.py` for a full loop). Exactly one thread may
235
+ receive per connection.
236
+
237
+ ## Local forwarding (`-L`)
238
+
239
+ ```python
240
+ fwd = c.forward_local(8080, "internal.db", 5432) # -L 8080:internal.db:5432
241
+ print(fwd.port) # actual port (0 means auto-assign)
242
+ import socket
243
+ s = socket.create_connection(("127.0.0.1", fwd.port))
244
+ s.sendall(b"ping")
245
+ print(s.recv(4))
246
+ print(c.exec_command("uptime")) # exec works on the same connection (mux)
247
+ fwd.close()
248
+ ```
249
+
250
+ Exec/sftp/shell share one connection with forwarding through the
251
+ channel multiplexer (`ccssh/mux.py`): a single reader thread owns the
252
+ transport and routes packets to per-channel queues, so concurrent
253
+ channels never interleave the framing. Rekey still needs idle channels
254
+ (close channels and forwarders first); auto-rekey is deferred while the
255
+ mux is busy.
256
+
257
+ ## Remote forwarding (`-R`) and ProxyJump (`-J`)
258
+
259
+ ```python
260
+ rfwd = c.forward_remote(0, "127.0.0.1", 22) # server listens (0 = auto)
261
+ print(rfwd.port) # real server-side port, reported back
262
+ rfwd.close() # also sends cancel-tcpip-forward
263
+
264
+ jump = CCSSHClient()
265
+ jump.connect("jumphost", username="juser", key_filename="~/.ssh/id_ed25519")
266
+ tun = jump.open_channel_socket("target", 22) # direct-tcpip, socket-shaped
267
+ c.connect("target", username="user", password="pw", sock=tun)
268
+ ```
269
+
270
+ `-L` and `-R` cannot share one connection (one forwarder per
271
+ connection); either one shares it with exec/sftp/shell freely.
272
+
273
+ ## Rekey
274
+
275
+ ```python
276
+ out, _, _ = c.exec_command("one")
277
+ c.rekey() # new KEX, session_id kept, sequence numbers reset
278
+ out, _, _ = c.exec_command("two")
279
+ ```
280
+
281
+ Call only with no open channels (between operations, forwarders
282
+ closed). Rekey also happens automatically per `rekey_bytes` /
283
+ `rekey_interval` (deferred while channels are open).
284
+
285
+ ## Algorithms
286
+
287
+ ```python
288
+ from ccssh import algs
289
+ print(algs.coverage())
290
+ # kex 14/16, ciphers 6/10, MACs 6/16 + certs - all live-verified vs OpenSSH
291
+ print(algs.SUPPORTED_KEX) # incl. "mlkem768x25519-sha256"
292
+ print(algs.SUPPORTED_CIPHERS) # aes*-ctr, aes*gcm, chacha20-poly1305
293
+ print(algs.SUPPORTED_MACS) # hmac-sha2-*, hmac-sha1 (+-etm)
294
+ print(algs.SUPPORTED_HOSTKEYS) # incl. "ssh-mldsa44-ed25519"
295
+ ```
296
+
297
+ The strict top tier (`algs.MAX_KEX`, `MAX_CIPHERS`, `MAX_MACS`,
298
+ `MAX_HOSTKEYS`: PQ hybrids, 256-bit ciphers/MACs only) can be passed
299
+ explicitly, e.g. `CCSSHClient(ciphers=algs.MAX_CIPHERS)`. Negotiation
300
+ still intersects with the server, so it fails closed when the server
301
+ lacks top-tier algorithms instead of silently downgrading.
302
+
303
+ Legacy algorithms (`algs.LEGACY_KEX`, `LEGACY_CIPHERS`, `LEGACY_MACS`,
304
+ `LEGACY_HOSTKEYS`: CBC ciphers, 3DES, MD5/SHA1-96/UMAC MACs, group1-sha1,
305
+ gex-sha1, ssh-rsa host keys) are implemented and live-tested but never
306
+ offered by default - name them explicitly to interoperate with old
307
+ servers. The server side needs them enabled too:
308
+
309
+ ```bash
310
+ # sshd_config on the old box
311
+ Ciphers +aes256-cbc,3des-cbc
312
+ MACs +hmac-md5,umac-64@openssh.com
313
+ KexAlgorithms +diffie-hellman-group1-sha1
314
+ HostKeyAlgorithms +ssh-rsa
315
+ ```
316
+
317
+ ```bash
318
+ ccssh -o Ciphers=aes256-cbc -o MACs=hmac-sha2-256 he1zen@oldbox
319
+ ```
320
+
321
+ Legacy tier: `ssh-dss` (1024-bit DSA/SHA-1, removed from OpenSSH 10.x) is
322
+ implemented like every other legacy primitive - never offered by default,
323
+ only when named explicitly (e.g. `-o HostKeyAlgorithms=ssh-dss`), and the
324
+ server side needs it enabled too.
325
+
326
+ ## Certificates
327
+
328
+ ```python
329
+ import base64
330
+ from ccssh import certs as CERT
331
+
332
+ line = open("host-cert.pub").read().split() # "ssh-ed25519-cert-v01@openssh.com AAAA..."
333
+ info = CERT.parse_cert(base64.b64decode(line[1]))
334
+ print(info["principals"], info["role"]) # role: CERT.CERT_TYPE_HOST / CERT_TYPE_USER
335
+
336
+ CERT.verify_cert(base64.b64decode(line[1]), expected_principal="db.internal",
337
+ is_host=True, ca_blobs=[ca_public_blob]) # raises CERT.CertError if bad
338
+ ```
339
+
340
+ In practice you rarely call this: user certs go through
341
+ `connect(..., cert_filename=...)`, host certs are verified automatically
342
+ from `@cert-authority` lines in known_hosts. To mint test certs:
343
+
344
+ ```python
345
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
346
+ from ccssh import wire as W
347
+
348
+ ca = Ed25519PrivateKey.generate()
349
+ host = Ed25519PrivateKey.generate()
350
+ pub_fields = W.enc_string(host.public_key().public_bytes_raw())
351
+ blob = CERT.build_cert(
352
+ keytype="ssh-ed25519-cert-v01@openssh.com", pub_fields=pub_fields,
353
+ role=CERT.CERT_TYPE_HOST, principals="db.internal", ca_priv=ca)
354
+ ```
355
+
356
+ ## ML-DSA (`ssh-mldsa44-ed25519`)
357
+
358
+ Needs a `cryptography` build with ML-DSA (else `available()` is False
359
+ and these raise):
360
+
361
+ ```python
362
+ from ccssh import mldsa as ML
363
+ print(ML.available())
364
+
365
+ kp = ML.KeyPair.generate() # or from_seeds(mldsa32, ed32)
366
+ open("id_mldsa.pub", "w").write(kp.public_line("comment") + "\n")
367
+ ML.save_openssh_private("id_mldsa", kp, "comment")
368
+ kp2 = ML.load_openssh_private("id_mldsa") # passphrase=b"..." if encrypted
369
+ ```
370
+
371
+ The files are standard `openssh-key-v1` / one-line `.pub`, so
372
+ `ssh-keygen` (10.6+) and `sshd` accept them, and ccssh uses them as
373
+ `-i` keys and host keys directly.
374
+
375
+ ## Post-quantum sntrup761 (`sntrup761x25519-sha512`)
376
+
377
+ Needs liboqs installed where the loader finds it (else `max`/defaults
378
+ skip it and forcing it raises `BackendMissing` with install hints):
379
+
380
+ ```python
381
+ from ccssh import sntrup as SN
382
+ be = SN.load_backend() # LibOQSBackend via ctypes, no Python deps
383
+ ek, sk = be.generate() # ek 1158 B
384
+ ct, ss1 = be.encap(ek) # ct 1039 B, ss 32 B
385
+ assert be.decap(sk, ct) == ss1
386
+ ```
387
+
388
+ ```bash
389
+ ccssh -o KexAlgorithms=sntrup761x25519-sha512 he1zen@host
390
+ ```
391
+
392
+ ## FIDO2 security keys (`sk-ssh-ed25519`, `sk-ecdsa-sha2-nistp256`)
393
+
394
+ Parsing, verification and userauth assembly follow OpenSSH
395
+ PROTOCOL.u2f; only the hardware touch itself needs a token. The touch
396
+ is an injectable callback, so any middleware fits:
397
+
398
+ ```python
399
+ from ccssh import sk as SK
400
+
401
+ key = SK.load_openssh_private("~/.ssh/id_sk") # needs key_handle inside
402
+ key.signer = lambda app, handle, msg: my_token_sign(app, handle, msg)
403
+ c.connect("host", username="u", key_filename="~/.ssh/id_sk") # uses it
404
+
405
+ SK.verify_nested(key.public_blob(), message, sig) # True/False, never raises
406
+ ```
407
+
408
+ Without a signer, signing raises a clear error (never a silent server
409
+ rejection). `-i sk_file` works out of the box; login itself needs the
410
+ token attached, so there is no live test - only spec-level tests plus
411
+ `ssh-keygen -L` cross-checks of our blobs.
412
+
413
+ ## Async client
414
+
415
+ ```python
416
+ import asyncio
417
+ from ccssh import AsyncCCSSHClient
418
+
419
+ async def go():
420
+ c = AsyncCCSSHClient(host_policy="off") # same knobs as sync (no rekey timers yet)
421
+ await c.connect("host", username="u", password="pw", family=0)
422
+ out, err, code = await c.exec_command("echo hi")
423
+ await c.rekey()
424
+ await c.close()
425
+ return out, code
426
+
427
+ print(asyncio.run(go()))
428
+ ```
429
+
430
+ ## Errors
431
+
432
+ ```python
433
+ from ccssh import CCSSHClient, AuthError, HostKeyError, CCSSHError
434
+ try:
435
+ ...
436
+ except AuthError: # bad password / rejected key
437
+ ...
438
+ except HostKeyError: # unknown or changed host key (strict policy)
439
+ ...
440
+ except CCSSHError: # protocol/transport failures (base of the above two)
441
+ ...
442
+ ```
@@ -0,0 +1,36 @@
1
+ ccssh/__init__.py,sha256=QqPBzmGtE0HoVFap12TPHrGtt0wCGQqmt1ac0GsnX44,941
2
+ ccssh/__main__.py,sha256=AcMe8msKBVlWR2Yg_EjwJxLKdMhwdDF8Fhl6iCwPoOg,87
3
+ ccssh/algs.py,sha256=W1uoHFRZVMVf7Fg-mSE1jkKzuYz7B1ZIsoUNYnyscVg,8972
4
+ ccssh/async_client.py,sha256=_lvlO9h3-6lIJjY7eVssKz-BwUMRWJtNkdfB_HLse-k,20005
5
+ ccssh/auth.py,sha256=nJQBPZZZa5wpUSoN8FlLlG1mUNHLEbZrR28iVFNtUh8,6264
6
+ ccssh/certs.py,sha256=E3b9MFFmT6fYlf9DAFFUy7jrbBNimoMj0zQjeNAP-Xc,10940
7
+ ccssh/chacha.py,sha256=JGh1BPt2NWTJzGpEEZkO566lOKYlcYh-c_Fk1GqbgCc,5044
8
+ ccssh/channel.py,sha256=sB5N_5Or9dPdHYtpybi9Jknnsj3ekSdn2Lnz3zJRlss,4437
9
+ ccssh/chansock.py,sha256=6gFHuFJagO2-KK9CSvYTsuM7SfpwyU5M3onyODzXIUI,8361
10
+ ccssh/cli.py,sha256=O3q1DLdWsk3XX94zQV_IvB7PQCEFrWUc_7fEnLuebU8,37318
11
+ ccssh/client.py,sha256=rX9g41dush-KfLePNgDJx4N7w1b_xsVHUjttXKWZfvs,56709
12
+ ccssh/crypto.py,sha256=gQy5ULkO6mKMa8qG9kt1MQHS6tb7wOnhNbZofV20xg8,12607
13
+ ccssh/dh_groups.py,sha256=QKylfGtyfCYU3cPbinlXxAxQgVJKnhw-vqftw41QPFg,5207
14
+ ccssh/forward.py,sha256=TqaFzanOi2VdPe9LSEnjKqeCg3uXCLCvxBrl0oBolA4,31682
15
+ ccssh/gcm.py,sha256=h69z2_NN1no7osKGAtyTZC7XnP7Ytv9VyVmIBDDW3as,3411
16
+ ccssh/hostcheck.py,sha256=0Jo6mDMZ-cYbBiKnLmKR7bUcDvb54Roe_noZ_vM0Nh8,9018
17
+ ccssh/hostkeys.py,sha256=MQHajCWCsZvFEc5qcHuIGqAQzgEcl7GG0zv8rzm6trM,10842
18
+ ccssh/kex.py,sha256=y-N2836xnAcXnzuaKVzT8BaH7g6s5TwHpn6okMIsmGw,8719
19
+ ccssh/kex_ext.py,sha256=DoERyhqUGyWUHeSzjkfuCf56nKOulXgWOunAfTTG7cY,17817
20
+ ccssh/mldsa.py,sha256=WfQSBQ3xoiX_vM1C9gbMZTi6WOJ9TYzkawG6cSYavk0,7820
21
+ ccssh/mlkem.py,sha256=jZNI5RZWt_vaY0262HiD0jZ3RV7j2kaFj8Jrk-6NIC8,14587
22
+ ccssh/mux.py,sha256=74PpT9_otQVPBTS0oHQh7qMvVhv2AbF0631dqmTcqYE,11115
23
+ ccssh/sftp.py,sha256=fTL195Eqf2xcsBn8fDf0hpA1r1KwDBZHVbblv6SDJK4,9901
24
+ ccssh/shell.py,sha256=lczksHJG8fee2P5qI-jNVrODujWGo9Yqt_YehZNcNUQ,9758
25
+ ccssh/sk.py,sha256=gyk8Ff5HlAPh9Q8o56xfqhP7W8yc4MbvLsXl-uBqGnA,9048
26
+ ccssh/sntrup.py,sha256=hq5oBwNFrBGOXmFr4JKb3UcnmqtFslvbWhNqonOlMp0,11767
27
+ ccssh/stub.py,sha256=S5NXoFh49XMEqRlmhFP5R3rXPzEKt6IJhh4XBAvy8SE,27375
28
+ ccssh/transport.py,sha256=KlxFSxXfU-P9YTzeoiPn0vpCPUeOiZtgeJ7C7zzbVfU,29869
29
+ ccssh/umac.py,sha256=MtyEr58PMEbHsWwmmAybS8VEdSks1EVQ21qQ0o0TkZ0,8872
30
+ ccssh/wire.py,sha256=w4dUtTDTXAw4g-_3vZV_UgY_9J-9tjdVI-J2taExN5Q,6012
31
+ ccssh-1.0.0.dist-info/licenses/LICENSE,sha256=i8xLGzrJvxNOqUMMQYXKnjCWcen5TClKEnK2eMH1kqs,1063
32
+ ccssh-1.0.0.dist-info/METADATA,sha256=X5HL6BRDWbe9ECxxWiswyr1y4LrIpgLEIjjemHBJzv0,16672
33
+ ccssh-1.0.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
34
+ ccssh-1.0.0.dist-info/entry_points.txt,sha256=dqt06_TW3vYOajcE7nMk2loHKQG_wgjRGItGrnuSgfw,41
35
+ ccssh-1.0.0.dist-info/top_level.txt,sha256=4ZIHvA4WQnH_Ry70-7y8WzyoBIlyF5RTLxLJilIGeLA,6
36
+ ccssh-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ccssh = ccssh.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 He1Zen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ ccssh