This commit is contained in:
nora 2024-08-09 02:03:48 +02:00
commit 26fd89d9f8
6 changed files with 1756 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

919
Cargo.lock generated Normal file
View file

@ -0,0 +1,919 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "addr2line"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "autocfg"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "backtrace"
version = "0.3.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "base64ct"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "bitflags"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
[[package]]
name = "cc"
version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "504bdec147f2cc13c8b57ed9401fd8a147cc66b67ad5cb241394244f2c947549"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "const-oid"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "cpufeatures"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
dependencies = [
"libc",
]
[[package]]
name = "crypto-bigint"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
dependencies = [
"rand_core",
"subtle",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "curve25519-dalek"
version = "4.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
dependencies = [
"cfg-if",
"cpufeatures",
"curve25519-dalek-derive",
"digest",
"fiat-crypto",
"rustc_version",
"subtle",
"zeroize",
]
[[package]]
name = "curve25519-dalek-derive"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "der"
version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
dependencies = [
"const-oid",
"zeroize",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "ed25519"
version = "2.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
dependencies = [
"pkcs8",
"signature",
]
[[package]]
name = "ed25519-dalek"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871"
dependencies = [
"curve25519-dalek",
"ed25519",
"serde",
"sha2",
"subtle",
"zeroize",
]
[[package]]
name = "eyre"
version = "0.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec"
dependencies = [
"indenter",
"once_cell",
]
[[package]]
name = "fakessh"
version = "0.1.0"
dependencies = [
"crypto-bigint",
"ed25519-dalek",
"eyre",
"rand",
"sha2",
"tokio",
"tracing",
"tracing-subscriber",
"x25519-dalek",
]
[[package]]
name = "fiat-crypto"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "gimli"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
[[package]]
name = "hermit-abi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "indenter"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]]
name = "lock_api"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "matchers"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
dependencies = [
"regex-automata 0.1.10",
]
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "miniz_oxide"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
dependencies = [
"adler",
]
[[package]]
name = "mio"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4"
dependencies = [
"hermit-abi",
"libc",
"wasi",
"windows-sys",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
dependencies = [
"overload",
"winapi",
]
[[package]]
name = "object"
version = "0.36.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "parking_lot"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets",
]
[[package]]
name = "pin-project-lite"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
[[package]]
name = "pkcs8"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
dependencies = [
"der",
"spki",
]
[[package]]
name = "ppv-lite86"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro2"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "redox_syscall"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4"
dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata 0.4.7",
"regex-syntax 0.8.4",
]
[[package]]
name = "regex-automata"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
dependencies = [
"regex-syntax 0.6.29",
]
[[package]]
name = "regex-automata"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax 0.8.4",
]
[[package]]
name = "regex-syntax"
version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "regex-syntax"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
[[package]]
name = "rustc-demangle"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "semver"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]]
name = "serde"
version = "1.0.205"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e33aedb1a7135da52b7c21791455563facbbcc43d0f0f66165b42c21b3dfb150"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.205"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "692d6f5ac90220161d6774db30c662202721e64aed9058d2c394f451261420c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "sha2"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "sharded-slab"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
dependencies = [
"lazy_static",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
dependencies = [
"libc",
]
[[package]]
name = "signature"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
dependencies = [
"rand_core",
]
[[package]]
name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "socket2"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "spki"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
dependencies = [
"base64ct",
"der",
]
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thread_local"
version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]]
name = "tokio"
version = "1.39.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1"
dependencies = [
"backtrace",
"bytes",
"libc",
"mio",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys",
]
[[package]]
name = "tokio-macros"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing"
version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
dependencies = [
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
dependencies = [
"once_cell",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
dependencies = [
"matchers",
"nu-ansi-term",
"once_cell",
"regex",
"sharded-slab",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
"tracing-log",
]
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "valuable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "x25519-dalek"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277"
dependencies = [
"curve25519-dalek",
"rand_core",
"serde",
"zeroize",
]
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"byteorder",
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zeroize"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
dependencies = [
"zeroize_derive",
]
[[package]]
name = "zeroize_derive"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

15
Cargo.toml Normal file
View file

@ -0,0 +1,15 @@
[package]
name = "fakessh"
version = "0.1.0"
edition = "2021"
[dependencies]
crypto-bigint = "0.5.5"
ed25519-dalek = "2.1.1"
eyre = "0.6.12"
rand = "0.8.5"
sha2 = "0.10.8"
tokio = { version = "1.39.2", features = ["full"] }
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
x25519-dalek = "2.0.1"

609
src/lib.rs Normal file
View file

@ -0,0 +1,609 @@
mod parse;
#[derive(Debug)]
pub enum SshError {
/// The client did something wrong.
/// The connection should be closed and a notice may be logged,
/// but this does not require operator intervention.
ClientError(String),
/// Something went wrong on the server.
/// The connection should be closed and an error should be logged.
ServerError(eyre::Report),
}
pub type Result<T, E = SshError> = std::result::Result<T, E>;
impl From<eyre::Report> for SshError {
fn from(value: eyre::Report) -> Self {
Self::ServerError(value)
}
}
macro_rules! client_error {
($($tt:tt)*) => {
$crate::SshError::ClientError(::std::format!($($tt)*))
};
}
use std::mem::take;
use client_error;
use ed25519_dalek::ed25519::signature::SignerMut;
use parse::{MpInt, NameList, Parser, Writer};
use sha2::Digest;
use x25519_dalek::{EphemeralSecret, PublicKey};
// This is definitely who we are.
pub const SERVER_IDENTIFICATION: &[u8] = b"SSH-2.0-OpenSSH_9.7\r\n";
#[derive(Default)]
pub struct ServerConnection {
state: ServerState,
send_queue: Vec<Msg>,
}
enum ServerState {
ProtoExchange {
received: Vec<u8>,
},
KeyExchangeInit {
client_packet: PacketParser,
client_identification: Vec<u8>,
},
DhKeyInit {
client_packet: PacketParser,
client_identification: Vec<u8>,
client_kexinit: Vec<u8>,
server_kexinit: Vec<u8>,
},
ServiceRequest {},
}
impl Default for ServerState {
fn default() -> Self {
Self::ProtoExchange {
received: Vec::new(),
}
}
}
impl ServerConnection {
pub fn recv_bytes(&mut self, mut bytes: &[u8]) -> Result<()> {
while let Some(consumed) = self.recv_bytes_step(bytes)? {
bytes = &bytes[consumed..];
if bytes.is_empty() {
break;
}
}
Ok(())
}
fn recv_bytes_step(&mut self, bytes: &[u8]) -> Result<Option<usize>> {
let result = match &mut self.state {
ServerState::ProtoExchange { received } => {
// TODO: get rid of this allocation :(
received.extend_from_slice(bytes);
if received.windows(2).find(|win| win == b"\r\n").is_some() {
// TODO: care that its SSH 2.0 instead of anythin anything else
// The client will not send any more information than this until we respond, so discord the rest of the bytes.
let client_identification = received.to_owned();
self.queue_msg(MsgKind::ServerProtocolInfo);
self.state = ServerState::KeyExchangeInit {
client_packet: PacketParser::new(),
client_identification,
};
}
None
}
ServerState::KeyExchangeInit {
client_packet: packet,
client_identification,
} => match packet.recv_bytes(bytes, ())? {
Some((consumed, data)) => {
let kex = KeyExchangeInitPacket::parse(&data.payload)?;
let require_algorithm =
|expected: &'static str, list: NameList<'_>| -> Result<&'static str> {
if list.iter().any(|alg| alg == expected) {
Ok(expected)
} else {
Err(client_error!(
"client does not supported algorithm {expected}"
))
}
};
let key_algorithm = require_algorithm("curve25519-sha256", kex.kex_algorithms)?;
let server_host_key_algorithm =
require_algorithm("ssh-ed25519", kex.server_host_key_algorithms)?;
let encryption_algorithm_client_to_server = require_algorithm(
"chacha20-poly1305@openssh.com",
kex.encryption_algorithms_client_to_server,
)?;
let encryption_algorithm_server_to_client = require_algorithm(
"chacha20-poly1305@openssh.com",
kex.encryption_algorithms_server_to_client,
)?;
let mac_algorithm_client_to_server =
require_algorithm("hmac-sha2-256", kex.mac_algorithms_client_to_server)?;
let mac_algorithm_server_to_client =
require_algorithm("hmac-sha2-256", kex.mac_algorithms_server_to_client)?;
let compression_algorithm_client_to_server =
require_algorithm("none", kex.compression_algorithms_client_to_server)?;
let compression_algorithm_server_to_client =
require_algorithm("none", kex.compression_algorithms_server_to_client)?;
let _ = kex.languages_client_to_server;
let _ = kex.languages_server_to_client;
if kex.first_kex_packet_follows {
return Err(client_error!(
"the client wants to send a guessed packet, that's annoying :("
));
}
let my_own_kex_init = KeyExchangeInitPacket {
cookie: [0; 16],
kex_algorithms: NameList::one(key_algorithm),
server_host_key_algorithms: NameList::one(server_host_key_algorithm),
encryption_algorithms_client_to_server: NameList::one(
encryption_algorithm_client_to_server,
),
encryption_algorithms_server_to_client: NameList::one(
encryption_algorithm_server_to_client,
),
mac_algorithms_client_to_server: NameList::one(
mac_algorithm_client_to_server,
),
mac_algorithms_server_to_client: NameList::one(
mac_algorithm_server_to_client,
),
compression_algorithms_client_to_server: NameList::one(
compression_algorithm_client_to_server,
),
compression_algorithms_server_to_client: NameList::one(
compression_algorithm_server_to_client,
),
languages_client_to_server: NameList::none(),
languages_server_to_client: NameList::none(),
first_kex_packet_follows: false,
};
let client_identification = take(client_identification);
let server_kexinit_payload = my_own_kex_init.to_bytes();
self.queue_msg(MsgKind::Packet(Packet {
payload: server_kexinit_payload.clone(),
}));
self.state = ServerState::DhKeyInit {
client_packet: PacketParser::new(),
client_identification,
client_kexinit: data.payload,
server_kexinit: server_kexinit_payload,
};
Some(consumed)
}
None => None,
},
ServerState::DhKeyInit {
client_packet: packet,
client_identification,
client_kexinit,
server_kexinit,
} => match packet.recv_bytes(bytes, ())? {
Some((consumed, data)) => {
let dh = DhKeyExchangeInitPacket::parse(&data.payload)?;
let secret = EphemeralSecret::random_from_rng(rand::thread_rng());
let server_public = PublicKey::from(&secret);
let shared_secret = secret.diffie_hellman(&dh.e.to_x25519_public_key()?);
let mut hash = sha2::Sha256::new();
let mut hash_string = |bytes: &[u8]| {
hash.update(u32::to_be_bytes(bytes.len() as u32));
hash.update(bytes);
};
hash_string(&client_identification[..(client_identification.len() - 2)]);
hash_string(&SERVER_IDENTIFICATION[..(SERVER_IDENTIFICATION.len() - 2)]);
hash_string(client_kexinit);
hash_string(server_kexinit);
let mut hash_mpint = hash_string;
hash_mpint(&dh.e.0);
hash_mpint(server_public.as_bytes());
hash_mpint(shared_secret.as_bytes());
let hash = hash.finalize();
let mut host_priv_key = ed25519_dalek::SigningKey::from_bytes(PRIVKEY_BYTES);
let signature = host_priv_key.sign(&hash);
let packet = DhKeyExchangeInitReplyPacket {
pubkey: SshPublicKey {
format: b"ssh-ed25519",
data: PUBKEY_BYTES,
},
f: MpInt(server_public.as_bytes()),
signature: SshSignature {
format: b"ssh-ed25519",
data: &signature.to_bytes(),
},
};
self.queue_msg(MsgKind::Packet(Packet {
payload: packet.to_bytes(),
}));
self.state = ServerState::ServiceRequest {};
Some(consumed)
}
None => None,
},
ServerState::ServiceRequest {} => todo!(),
};
Ok(result)
}
pub fn next_message_to_send(&mut self) -> Option<Msg> {
self.send_queue.pop()
}
fn queue_msg(&mut self, msg: MsgKind) {
self.send_queue.push(Msg(msg));
}
}
#[derive(Debug)]
pub struct Msg(MsgKind);
#[derive(Debug, PartialEq)]
enum MsgKind {
ServerProtocolInfo,
Packet(Packet),
}
impl Msg {
// TODO: MAKE THIS ZERO ALLOC AAAAAA
pub fn to_bytes_inefficient(self) -> Vec<u8> {
match self.0 {
MsgKind::ServerProtocolInfo => SERVER_IDENTIFICATION.to_vec(),
MsgKind::Packet(v) => v.to_bytes(),
}
}
}
#[derive(Debug, PartialEq)]
struct Packet {
payload: Vec<u8>,
}
impl Packet {
const SSH_MSG_KEXINIT: u8 = 20;
const SSH_MSG_KEXDH_INIT: u8 = 30;
const SSH_MSG_KEXDH_REPLY: u8 = 31;
fn from_raw(bytes: &[u8]) -> Result<Self> {
let Some(padding_length) = bytes.get(0) else {
return Err(client_error!("empty packet"));
};
// TODO: mac?
let Some(payload_len) = (bytes.len() - 1).checked_sub(*padding_length as usize) else {
return Err(client_error!("packet padding longer than packet"));
};
let payload = &bytes[1..][..payload_len];
if (bytes.len() + 4) % 8 != 0 {
return Err(client_error!("full packet length must be multiple of 8"));
}
Ok(Self {
payload: payload.to_vec(),
})
}
fn to_bytes(&self) -> Vec<u8> {
let mut new = Vec::new();
let min_full_length = self.payload.len() + 4 + 1;
// The padding must give a factor of 8.
let min_padding_len = (min_full_length.next_multiple_of(8) - min_full_length) as u8;
// > There MUST be at least four bytes of padding.
// So let's satisfy this by just adding 8. We can always properly randomize it later if desired.
let padding_len = min_padding_len + 8;
let packet_len = self.payload.len() + (padding_len as usize) + 1;
new.extend_from_slice(&u32::to_be_bytes(packet_len as u32));
new.extend_from_slice(&[padding_len]);
new.extend_from_slice(&self.payload);
new.extend(std::iter::repeat(0).take(padding_len as usize));
// mac...
assert!((4 + 1 + self.payload.len() + (padding_len as usize)) % 8 == 0);
assert!(new.len() % 8 == 0);
new
}
}
#[derive(Debug)]
struct KeyExchangeInitPacket<'a> {
cookie: [u8; 16],
kex_algorithms: NameList<'a>,
server_host_key_algorithms: NameList<'a>,
encryption_algorithms_client_to_server: NameList<'a>,
encryption_algorithms_server_to_client: NameList<'a>,
mac_algorithms_client_to_server: NameList<'a>,
mac_algorithms_server_to_client: NameList<'a>,
compression_algorithms_client_to_server: NameList<'a>,
compression_algorithms_server_to_client: NameList<'a>,
languages_client_to_server: NameList<'a>,
languages_server_to_client: NameList<'a>,
first_kex_packet_follows: bool,
}
impl<'a> KeyExchangeInitPacket<'a> {
fn parse(payload: &'a [u8]) -> Result<KeyExchangeInitPacket<'_>> {
let mut c = Parser::new(payload);
let kind = c.u8()?;
if kind != Packet::SSH_MSG_KEXINIT {
return Err(client_error!(
"expected SSH_MSG_KEXINIT packet, found {kind}"
));
}
let cookie = c.read_array::<16>()?;
let kex_algorithms = c.name_list()?;
let server_host_key_algorithms = c.name_list()?;
let encryption_algorithms_client_to_server = c.name_list()?;
let encryption_algorithms_server_to_client = c.name_list()?;
let mac_algorithms_client_to_server = c.name_list()?;
let mac_algorithms_server_to_client = c.name_list()?;
let compression_algorithms_client_to_server = c.name_list()?;
let compression_algorithms_server_to_client = c.name_list()?;
let languages_client_to_server = c.name_list()?;
let languages_server_to_client = c.name_list()?;
let first_kex_packet_follows = c.bool()?;
let _ = c.u32()?; // Reserved.
Ok(Self {
cookie,
kex_algorithms,
server_host_key_algorithms,
encryption_algorithms_client_to_server,
encryption_algorithms_server_to_client,
mac_algorithms_client_to_server,
mac_algorithms_server_to_client,
compression_algorithms_client_to_server,
compression_algorithms_server_to_client,
languages_client_to_server,
languages_server_to_client,
first_kex_packet_follows,
})
}
fn to_bytes(&self) -> Vec<u8> {
let mut data = Writer::new();
data.u8(Packet::SSH_MSG_KEXINIT);
data.write(&self.cookie);
data.name_list(self.kex_algorithms);
data.name_list(self.server_host_key_algorithms);
data.name_list(self.encryption_algorithms_client_to_server);
data.name_list(self.encryption_algorithms_server_to_client);
data.name_list(self.mac_algorithms_client_to_server);
data.name_list(self.mac_algorithms_server_to_client);
data.name_list(self.compression_algorithms_client_to_server);
data.name_list(self.compression_algorithms_server_to_client);
data.name_list(self.languages_client_to_server);
data.name_list(self.languages_server_to_client);
data.u8(self.first_kex_packet_follows as u8);
data.u32(0); // Reserved.
data.finish()
}
}
#[derive(Debug)]
struct DhKeyExchangeInitPacket<'a> {
e: MpInt<'a>,
}
impl<'a> DhKeyExchangeInitPacket<'a> {
fn parse(payload: &'a [u8]) -> Result<DhKeyExchangeInitPacket<'_>> {
let mut c = Parser::new(payload);
let kind = c.u8()?;
if kind != Packet::SSH_MSG_KEXDH_INIT {
return Err(client_error!(
"expected SSH_MSG_KEXDH_INIT packet, found {kind}"
));
}
let e = c.mpint()?;
Ok(Self { e })
}
}
#[derive(Debug)]
struct SshPublicKey<'a> {
format: &'a [u8],
data: &'a [u8],
}
#[derive(Debug)]
struct SshSignature<'a> {
format: &'a [u8],
data: &'a [u8],
}
#[derive(Debug)]
struct DhKeyExchangeInitReplyPacket<'a> {
pubkey: SshPublicKey<'a>,
f: MpInt<'a>,
signature: SshSignature<'a>,
}
impl<'a> DhKeyExchangeInitReplyPacket<'a> {
fn to_bytes(&self) -> Vec<u8> {
let mut data = Writer::new();
data.u8(Packet::SSH_MSG_KEXDH_REPLY);
data.u32((4 + self.pubkey.format.len() + 4 + self.pubkey.data.len()) as u32);
// ed25519-specific!
// <https://datatracker.ietf.org/doc/html/rfc8709#section-4>
data.string(&self.pubkey.format);
data.string(&self.pubkey.data);
data.mpint(self.f);
data.u32((4 + self.signature.format.len() + 4 + self.signature.data.len()) as u32);
// <https://datatracker.ietf.org/doc/html/rfc8709#section-6>
data.string(&self.signature.format);
data.string(&self.signature.data);
data.finish()
}
}
struct PacketParser {
// The length of the packet.
packet_length: Option<usize>,
// Before we've read the length fully, this stores the length.
// Afterwards, this stores the packet data *after* the length.
data: Vec<u8>,
}
impl PacketParser {
fn new() -> Self {
Self {
packet_length: None,
data: Vec::new(),
}
}
fn recv_bytes(&mut self, bytes: &[u8], mac: ()) -> Result<Option<(usize, Packet)>> {
let Some((consumed, data)) = self.recv_bytes_inner(bytes, mac)? else {
return Ok(None);
};
Ok(Some((consumed, Packet::from_raw(&data)?)))
}
fn recv_bytes_inner(&mut self, mut bytes: &[u8], _mac: ()) -> Result<Option<(usize, Vec<u8>)>> {
let mut consumed = 0;
let packet_length = match self.packet_length {
Some(packet_length) => packet_length,
None => {
let remaining_len = std::cmp::min(bytes.len(), 4 - self.data.len());
// Try to read the bytes of the length.
self.data.extend_from_slice(&bytes[..remaining_len]);
if self.data.len() < 4 {
// Not enough data yet :(.
return Ok(None);
}
let packet_length = u32::from_be_bytes(self.data.as_slice().try_into().unwrap());
let packet_length = packet_length.try_into().unwrap();
self.data.clear();
self.packet_length = Some(packet_length);
// We have the data.
bytes = &bytes[remaining_len..];
consumed += remaining_len;
packet_length
}
};
let remaining_len = std::cmp::min(bytes.len(), packet_length - self.data.len());
self.data.extend_from_slice(&bytes[..remaining_len]);
consumed += remaining_len;
if self.data.len() == packet_length {
// We have the full data.
Ok(Some((consumed, std::mem::take(&mut self.data))))
} else {
Ok(None)
}
}
#[cfg(test)]
fn test_recv_bytes(&mut self, bytes: &[u8], mac: ()) -> Option<(usize, Vec<u8>)> {
self.recv_bytes_inner(bytes, mac).unwrap()
}
}
// hardcoded test keys. lol.
const _PUBKEY: &str =
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOk5zfpvwNc3MztTTpE90zLI1Ref4AwwRVdSFyJLGbj2 testkey";
/// Manually extracted, even worse, <https://superuser.com/questions/1477472/openssh-public-key-file-format>, help
const PUBKEY_BYTES: &[u8; 32] = &[
0xe9, 0x39, 0xcd, 0xfa, 0x6f, 0xc0, 0xd7, 0x37, 0x33, 0x3b, 0x53, 0x4e, 0x91, 0x3d, 0xd3, 0x32,
0xc8, 0xd5, 0x17, 0x9f, 0xe0, 0x0c, 0x30, 0x45, 0x57, 0x52, 0x17, 0x22, 0x4b, 0x19, 0xb8, 0xf6,
];
const _PRIVKEY: &str = "-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACDpOc36b8DXNzM7U06RPdMyyNUXn+AMMEVXUhciSxm49gAAAJDpgLSk6YC0
pAAAAAtzc2gtZWQyNTUxOQAAACDpOc36b8DXNzM7U06RPdMyyNUXn+AMMEVXUhciSxm49g
AAAECSeskxuEtJrr9L7ZkbpogXC5pKRNVHx1ueMX2h1XUnmek5zfpvwNc3MztTTpE90zLI
1Ref4AwwRVdSFyJLGbj2AAAAB3Rlc3RrZXkBAgMEBQY=
-----END OPENSSH PRIVATE KEY-----
";
/// Manually extracted from the key using <https://dnaeon.github.io/openssh-private-key-binary-format/>, probably wrong
const PRIVKEY_BYTES: &[u8; 32] = &[
0xb8, 0x4b, 0x49, 0xae, 0xbf, 0x4b, 0xed, 0x99, 0x1b, 0xa6, 0x88, 0x17, 0x0b, 0x9a, 0x4a, 0x44,
0xd5, 0x47, 0xc7, 0x5b, 0x9e, 0x31, 0x7d, 0xa1, 0xd5, 0x75, 0x27, 0x99, 0xe9, 0x39, 0xcd, 0xfa,
];
#[cfg(test)]
mod tests {
use crate::{MsgKind, PacketParser, ServerConnection};
trait OptionExt {
fn unwrap_none(self);
}
impl<T> OptionExt for Option<T> {
#[track_caller]
fn unwrap_none(self) {
assert!(self.is_none());
}
}
#[test]
fn protocol_exchange() {
let mut con = ServerConnection::default();
con.recv_bytes(b"SSH-2.0-OpenSSH_9.7\r\n").unwrap();
let msg = con.next_message_to_send().unwrap();
assert_eq!(msg.0, MsgKind::ServerProtocolInfo);
}
#[test]
fn protocol_exchange_slow_client() {
let mut con = ServerConnection::default();
con.recv_bytes(b"SSH-2.0-").unwrap();
con.recv_bytes(b"OpenSSH_9.7\r\n").unwrap();
let msg = con.next_message_to_send().unwrap();
assert_eq!(msg.0, MsgKind::ServerProtocolInfo);
}
#[test]
fn packet_parser() {
let mut p = PacketParser::new();
p.test_recv_bytes(&2_u32.to_be_bytes(), ()).unwrap_none();
p.test_recv_bytes(&[1], ()).unwrap_none();
let (consumed, data) = p.test_recv_bytes(&[2], ()).unwrap();
assert_eq!(consumed, 1);
assert_eq!(data, &[1, 2]);
}
#[test]
fn packet_parser_split_len() {
let mut p = PacketParser::new();
let len = &2_u32.to_be_bytes();
p.test_recv_bytes(&len[0..2], ()).unwrap_none();
p.test_recv_bytes(&len[2..4], ()).unwrap_none();
p.test_recv_bytes(&[1], ()).unwrap_none();
let (consumed, data) = p.test_recv_bytes(&[2], ()).unwrap();
assert_eq!(consumed, 1);
assert_eq!(data, &[1, 2]);
}
#[test]
fn packet_parser_all() {
let mut p = PacketParser::new();
let (consumed, data) = p.test_recv_bytes(&[0, 0, 0, 2, 1, 2], ()).unwrap();
assert_eq!(consumed, 6);
assert_eq!(data, &[1, 2]);
}
}

65
src/main.rs Normal file
View file

@ -0,0 +1,65 @@
use std::net::SocketAddr;
use eyre::{Context, Result};
use fakessh::{ServerConnection, SshError};
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::{TcpListener, TcpStream},
};
use tracing::{error, info};
#[tokio::main]
async fn main() -> eyre::Result<()> {
tracing_subscriber::fmt().init();
let listener = TcpListener::bind("0.0.0.0:2222")
.await
.wrap_err("binding listener")?;
loop {
let next = listener.accept().await?;
tokio::spawn(async {
if let Err(err) = handle_connection(next).await {
error!(?err, "error handling connection");
}
});
}
}
async fn handle_connection(next: (TcpStream, SocketAddr)) -> Result<()> {
let (mut conn, addr) = next;
info!(?addr, "Received a new connection");
let mut state = ServerConnection::default();
loop {
let mut buf = [0; 1024];
let read = conn
.read(&mut buf)
.await
.wrap_err("reading from connection")?;
if read == 0 {
return Ok(());
}
if let Err(err) = state.recv_bytes(&buf[..read]) {
match err {
SshError::ClientError(err) => {
info!(?err, "disconnecting client after invalid operation");
return Ok(());
}
SshError::ServerError(err) => {
return Err(err);
}
}
}
while let Some(msg) = state.next_message_to_send() {
conn.write_all(&msg.to_bytes_inefficient())
.await
.wrap_err("writing response")?;
}
}
}

147
src/parse.rs Normal file
View file

@ -0,0 +1,147 @@
use core::str;
use std::fmt::Debug;
use crate::Result;
/// A simplified `byteorder` clone that emits client errors when the data is too short.
pub(crate) struct Parser<'a>(&'a [u8]);
impl<'a> Parser<'a> {
pub(crate) fn new(data: &'a [u8]) -> Self {
Self(data)
}
pub(crate) fn u8(&mut self) -> Result<u8> {
let arr = self.read_array::<1>()?;
Ok(arr[0])
}
pub(crate) fn u16(&mut self) -> Result<u16> {
let arr = self.read_array()?;
Ok(u16::from_be_bytes(arr))
}
pub(crate) fn u32(&mut self) -> Result<u32> {
let arr = self.read_array()?;
Ok(u32::from_be_bytes(arr))
}
pub(crate) fn read_array<const N: usize>(&mut self) -> Result<[u8; N]> {
if self.0.len() < N {
return Err(crate::client_error!("packet too short"));
}
let result = self.0[..N].try_into().unwrap();
self.0 = &self.0[N..];
Ok(result)
}
pub(crate) fn read_slice(&mut self, len: usize) -> Result<&'a [u8]> {
if self.0.len() < len {
return Err(crate::client_error!("packet too short"));
}
let result = &self.0[..len];
self.0 = &self.0[len..];
Ok(result)
}
pub(crate) fn bool(&mut self) -> Result<bool> {
let b = self.u8()?;
match b {
0 => Ok(false),
1 => Ok(true),
_ => return Err(crate::client_error!("invalid bool: {b}")),
}
}
pub(crate) fn name_list(&mut self) -> Result<NameList<'a>> {
let len = self.u32()?;
let list = self.read_slice(len.try_into().unwrap())?;
let Ok(list) = str::from_utf8(list) else {
return Err(crate::client_error!("name-list is invalid UTF-8"));
};
Ok(NameList(list))
}
pub(crate) fn mpint(&mut self) -> Result<MpInt<'a>> {
let len = self.u32()?;
let data = self.read_slice(len as usize)?;
Ok(MpInt(data))
}
}
/// A simplified `byteorder` clone that emits client errors when the data is too short.
pub(crate) struct Writer(Vec<u8>);
impl Writer {
pub(crate) fn new() -> Self {
Self(Vec::new())
}
pub(crate) fn u8(&mut self, v: u8) {
self.write(&[v]);
}
pub(crate) fn u32(&mut self, v: u32) {
self.write(&u32::to_be_bytes(v));
}
pub(crate) fn write(&mut self, v: &[u8]) {
self.0.extend_from_slice(v);
}
pub(crate) fn name_list(&mut self, list: NameList<'_>) {
self.string(list.0.as_bytes());
}
pub(crate) fn mpint(&mut self, mpint: MpInt<'_>) {
self.string(mpint.0);
}
pub(crate) fn string(&mut self, data: &[u8]) {
self.u32(data.len() as u32);
self.write(data);
}
pub(crate) fn finish(self) -> Vec<u8> {
self.0
}
}
#[derive(Clone, Copy)]
pub struct NameList<'a>(&'a str);
impl<'a> NameList<'a> {
pub(crate) fn one(item: &'a str) -> Self {
if item.contains(',') {
panic!("tried creating name list with comma in item: {item}");
}
Self(item)
}
pub(crate) fn none() -> NameList<'static> {
NameList("")
}
pub(crate) fn iter(&self) -> std::str::Split<char> {
self.0.split(',')
}
}
impl Debug for NameList<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
#[derive(Debug, Clone, Copy)]
pub struct MpInt<'a>(pub(crate) &'a [u8]);
impl<'a> MpInt<'a> {
pub(crate) fn to_x25519_public_key(&self) -> Result<x25519_dalek::PublicKey> {
let Ok(arr) = <[u8; 32]>::try_from(self.0) else {
return Err(crate::client_error!(
"invalid x25519 public key length, should be 32, was: {}",
self.0.len()
));
};
Ok(x25519_dalek::PublicKey::from(arr))
}
}