├── .github └── workflows │ ├── lint.yaml │ ├── release.yml │ ├── semgrep.yml │ └── tests.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── data.rs ├── hpke.rs ├── main.rs ├── metadata.rs ├── pty.rs └── zip.rs /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | name: Linters 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | rustfmt: 7 | name: Formatter 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | 12 | - name: Install Rust 13 | run: | 14 | rustup update stable 15 | rustup default stable 16 | rustup component add rustfmt 17 | - name: Check Formatting 18 | run: cargo fmt --all -- --check -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - "v*" # Run when tag matches v*, i.e. v1.0, v20.15.10 5 | 6 | name: Release 7 | 8 | env: 9 | RELEASE_BIN: ssh-log-cli 10 | RELEASE_DIR: artifacts 11 | GITHUB_REF: "${{ github.ref }}" 12 | WINDOWS_TARGET: x86_64-pc-windows-msvc 13 | MACOS_TARGET: x86_64-apple-darwin 14 | LINUX_TARGET: x86_64-unknown-linux-musl 15 | 16 | # Space separated paths to include in the archive. 17 | RELEASE_ADDS: README.md 18 | 19 | jobs: 20 | build: 21 | name: Build artifacts 22 | runs-on: ${{ matrix.os }} 23 | strategy: 24 | matrix: 25 | build: [linux, macos, windows] 26 | include: 27 | - build: linux 28 | os: ubuntu-latest 29 | rust: stable 30 | - build: macos 31 | os: macos-latest 32 | rust: stable 33 | - build: windows 34 | os: windows-latest 35 | rust: stable 36 | 37 | steps: 38 | - uses: actions/checkout@v2 39 | 40 | - name: Query version number 41 | id: get_version 42 | shell: bash 43 | run: | 44 | echo "using version tag ${GITHUB_REF:10}" 45 | echo ::set-output name=version::"${GITHUB_REF:10}" 46 | - name: Install Rust 47 | if: matrix.rust 48 | run: | 49 | rustup update ${{ matrix.rust }} --no-self-update 50 | rustup default ${{ matrix.rust }} 51 | - name: Install musl-tools (Linux) 52 | if: matrix.build == 'linux' 53 | run: | 54 | sudo apt-get update -y 55 | sudo apt-get install musl-tools -y 56 | - name: Install p7zip (MacOS) 57 | if: matrix.build == 'macos' 58 | run: brew install p7zip 59 | 60 | - name: Build (Linux) 61 | if: matrix.build == 'linux' 62 | run: | 63 | rustup target add ${{ env.LINUX_TARGET }} 64 | cargo build --release --target ${{ env.LINUX_TARGET }} 65 | - name: Build (MacOS) 66 | if: matrix.build == 'macos' 67 | run: cargo build --release 68 | 69 | - name: Build (Windows) 70 | if: matrix.build == 'windows' 71 | run: cargo build --release 72 | env: 73 | RUSTFLAGS: -Ctarget-feature=+crt-static 74 | 75 | - name: Create artifact directory 76 | run: | 77 | mkdir ${{ env.RELEASE_DIR }} 78 | mkdir dist 79 | - name: Create zip (Linux) 80 | if: matrix.build == 'linux' 81 | run: | 82 | mv ./target/${{ env.LINUX_TARGET }}/release/${{ env.RELEASE_BIN }} ./dist/${{ env.RELEASE_BIN }} 83 | mv ${{ env.RELEASE_ADDS }} ./dist 84 | 7z a ./${{ env.RELEASE_DIR }}/${{ env.RELEASE_BIN }}-${{ steps.get_version.outputs.VERSION }}-${{ env.LINUX_TARGET }}.zip ./dist/* 85 | - name: Create zip (Windows) 86 | if: matrix.build == 'windows' 87 | shell: bash 88 | run: | 89 | mv ./target/release/${{ env.RELEASE_BIN }}.exe ./dist/${{ env.RELEASE_BIN }}.exe 90 | mv ${{ env.RELEASE_ADDS }} ./dist 91 | 7z a ./${{ env.RELEASE_DIR }}/${{ env.RELEASE_BIN }}-${{ steps.get_version.outputs.VERSION }}-${{ env.WINDOWS_TARGET }}.zip ./dist/* 92 | - name: Create zip (MacOS) 93 | if: matrix.build == 'macos' 94 | run: | 95 | mv ./target/release/${{ env.RELEASE_BIN }} ./dist/${{ env.RELEASE_BIN }} 96 | mv ${{ env.RELEASE_ADDS }} ./dist 97 | 7z a ./${{ env.RELEASE_DIR }}/${{ env.RELEASE_BIN }}-${{ steps.get_version.outputs.VERSION }}-${{ env.MACOS_TARGET }}.zip ./dist/* 98 | - name: Upload Zip 99 | uses: actions/upload-artifact@v1 100 | with: 101 | name: ${{ matrix.build }} 102 | path: ./${{ env.RELEASE_DIR }} 103 | 104 | release: 105 | name: GitHub Release 106 | needs: build 107 | runs-on: ubuntu-latest 108 | steps: 109 | - name: Query version number 110 | id: get_version 111 | shell: bash 112 | run: | 113 | echo "using version tag ${GITHUB_REF:10}" 114 | echo ::set-output name=version::"${GITHUB_REF:10}" 115 | - name: Create Release 116 | id: create_release 117 | uses: actions/create-release@v1 118 | env: 119 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 120 | with: 121 | tag_name: ${{ steps.get_version.outputs.VERSION }} 122 | release_name: ${{ steps.get_version.outputs.VERSION }} 123 | 124 | - name: Download Linux tarball 125 | uses: actions/download-artifact@v1 126 | with: 127 | name: linux 128 | 129 | - name: Download Windows tarball 130 | uses: actions/download-artifact@v1 131 | with: 132 | name: windows 133 | 134 | - name: Download MacOS zip 135 | uses: actions/download-artifact@v1 136 | with: 137 | name: macos 138 | 139 | - name: Release Linux zip 140 | uses: actions/upload-release-asset@v1 141 | env: 142 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 143 | with: 144 | upload_url: ${{ steps.create_release.outputs.upload_url }} 145 | asset_path: ./linux/${{ env.RELEASE_BIN }}-${{ steps.get_version.outputs.VERSION }}-${{ env.LINUX_TARGET }}.zip 146 | asset_content_type: application/gzip 147 | asset_name: ${{ env.RELEASE_BIN }}-${{ steps.get_version.outputs.VERSION }}-${{ env.LINUX_TARGET }}.zip 148 | 149 | - name: Release Windows zip 150 | uses: actions/upload-release-asset@v1 151 | env: 152 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 153 | with: 154 | upload_url: ${{ steps.create_release.outputs.upload_url }} 155 | asset_path: ./windows/${{ env.RELEASE_BIN }}-${{ steps.get_version.outputs.VERSION }}-${{ env.WINDOWS_TARGET }}.zip 156 | asset_content_type: application/gzip 157 | asset_name: ${{ env.RELEASE_BIN }}-${{ steps.get_version.outputs.VERSION }}-${{ env.WINDOWS_TARGET }}.zip 158 | 159 | - name: Release MacOS zip 160 | uses: actions/upload-release-asset@v1 161 | env: 162 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 163 | with: 164 | upload_url: ${{ steps.create_release.outputs.upload_url }} 165 | asset_path: ./macos/${{ env.RELEASE_BIN }}-${{ steps.get_version.outputs.VERSION }}-${{ env.MACOS_TARGET }}.zip 166 | asset_content_type: application/gzip 167 | asset_name: ${{ env.RELEASE_BIN }}-${{ steps.get_version.outputs.VERSION }}-${{ env.MACOS_TARGET }}.zip -------------------------------------------------------------------------------- /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | 2 | on: 3 | pull_request: {} 4 | workflow_dispatch: {} 5 | push: 6 | branches: 7 | - main 8 | - master 9 | schedule: 10 | - cron: '0 0 * * *' 11 | name: Semgrep config 12 | jobs: 13 | semgrep: 14 | name: semgrep/ci 15 | runs-on: ubuntu-20.04 16 | env: 17 | SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} 18 | SEMGREP_URL: https://cloudflare.semgrep.dev 19 | SEMGREP_APP_URL: https://cloudflare.semgrep.dev 20 | SEMGREP_VERSION_CHECK_URL: https://cloudflare.semgrep.dev/api/check-version 21 | container: 22 | image: returntocorp/semgrep 23 | steps: 24 | - uses: actions/checkout@v3 25 | - run: semgrep ci 26 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | name: Test 8 | 9 | runs-on: ${{ matrix.os }} 10 | strategy: 11 | matrix: 12 | build: [linux-stable, macos-stable, windows-stable] 13 | include: 14 | - build: linux-stable 15 | os: ubuntu-latest 16 | rust: stable 17 | - build: macos-stable 18 | os: macos-latest 19 | rust: stable 20 | - build: windows-stable 21 | os: windows-latest 22 | rust: stable 23 | 24 | steps: 25 | - uses: actions/checkout@v2 26 | 27 | - name: Install Rust 28 | run: | 29 | rustup update ${{ matrix.rust }} --no-self-update 30 | rustup default ${{ matrix.rust }} 31 | - name: Run Tests 32 | run: | 33 | cargo build 34 | cargo test 35 | cargo build --no-default-features 36 | cargo test --no-default-features 37 | env: 38 | RUST_BACKTRACE: 1 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | **/*.rs.bk 3 | /artifacts 4 | /*.deb 5 | node_modules 6 | *.log 7 | .DS_Store 8 | test-results.xml 9 | bin/gateway-test-proxy/test-cfg.yaml 10 | lib/gateway-firewall/src/file_info/file_type_db_gen.rs 11 | HBEDV.KEY 12 | hfuzz_workspace 13 | hfuzz_target 14 | /fuzz/corpus/*.cov 15 | .idea/ 16 | .vscode/ 17 | images 18 | /data-storage-dir 19 | *.txt 20 | *.zip -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "adler" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 10 | 11 | [[package]] 12 | name = "aead" 13 | version = "0.4.3" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" 16 | dependencies = [ 17 | "generic-array", 18 | ] 19 | 20 | [[package]] 21 | name = "aes" 22 | version = "0.7.5" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" 25 | dependencies = [ 26 | "cfg-if", 27 | "cipher", 28 | "cpufeatures", 29 | "opaque-debug", 30 | ] 31 | 32 | [[package]] 33 | name = "aes-gcm" 34 | version = "0.9.4" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" 37 | dependencies = [ 38 | "aead", 39 | "aes", 40 | "cipher", 41 | "ctr", 42 | "ghash", 43 | "subtle", 44 | ] 45 | 46 | [[package]] 47 | name = "aho-corasick" 48 | version = "0.7.18" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 51 | dependencies = [ 52 | "memchr", 53 | ] 54 | 55 | [[package]] 56 | name = "assert_cmd" 57 | version = "2.0.4" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "93ae1ddd39efd67689deb1979d80bad3bf7f2b09c6e6117c8d1f2443b5e2f83e" 60 | dependencies = [ 61 | "bstr", 62 | "doc-comment", 63 | "predicates", 64 | "predicates-core", 65 | "predicates-tree", 66 | "wait-timeout", 67 | ] 68 | 69 | [[package]] 70 | name = "assert_fs" 71 | version = "1.0.7" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "cf09bb72e00da477c2596865e8873227e2196d263cca35414048875dbbeea1be" 74 | dependencies = [ 75 | "doc-comment", 76 | "globwalk", 77 | "predicates", 78 | "predicates-core", 79 | "predicates-tree", 80 | "tempfile", 81 | ] 82 | 83 | [[package]] 84 | name = "atty" 85 | version = "0.2.14" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 88 | dependencies = [ 89 | "hermit-abi", 90 | "libc", 91 | "winapi", 92 | ] 93 | 94 | [[package]] 95 | name = "autocfg" 96 | version = "1.1.0" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 99 | 100 | [[package]] 101 | name = "base64" 102 | version = "0.13.0" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" 105 | 106 | [[package]] 107 | name = "base64ct" 108 | version = "1.0.1" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b" 111 | 112 | [[package]] 113 | name = "bincode" 114 | version = "1.3.3" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" 117 | dependencies = [ 118 | "serde", 119 | ] 120 | 121 | [[package]] 122 | name = "bitflags" 123 | version = "1.3.2" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 126 | 127 | [[package]] 128 | name = "block-buffer" 129 | version = "0.9.0" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" 132 | dependencies = [ 133 | "generic-array", 134 | ] 135 | 136 | [[package]] 137 | name = "block-buffer" 138 | version = "0.10.2" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" 141 | dependencies = [ 142 | "generic-array", 143 | ] 144 | 145 | [[package]] 146 | name = "bstr" 147 | version = "0.2.17" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" 150 | dependencies = [ 151 | "lazy_static", 152 | "memchr", 153 | "regex-automata", 154 | ] 155 | 156 | [[package]] 157 | name = "byteorder" 158 | version = "1.4.3" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 161 | 162 | [[package]] 163 | name = "bzip2" 164 | version = "0.4.3" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "6afcd980b5f3a45017c57e57a2fcccbb351cc43a356ce117ef760ef8052b89b0" 167 | dependencies = [ 168 | "bzip2-sys", 169 | "libc", 170 | ] 171 | 172 | [[package]] 173 | name = "bzip2-sys" 174 | version = "0.1.11+1.0.8" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" 177 | dependencies = [ 178 | "cc", 179 | "libc", 180 | "pkg-config", 181 | ] 182 | 183 | [[package]] 184 | name = "cc" 185 | version = "1.0.73" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 188 | dependencies = [ 189 | "jobserver", 190 | ] 191 | 192 | [[package]] 193 | name = "cfg-if" 194 | version = "1.0.0" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 197 | 198 | [[package]] 199 | name = "chacha20" 200 | version = "0.8.1" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "01b72a433d0cf2aef113ba70f62634c56fddb0f244e6377185c56a7cadbd8f91" 203 | dependencies = [ 204 | "cfg-if", 205 | "cipher", 206 | "cpufeatures", 207 | "zeroize", 208 | ] 209 | 210 | [[package]] 211 | name = "chacha20poly1305" 212 | version = "0.9.0" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "3b84ed6d1d5f7aa9bdde921a5090e0ca4d934d250ea3b402a5fab3a994e28a2a" 215 | dependencies = [ 216 | "aead", 217 | "chacha20", 218 | "cipher", 219 | "poly1305", 220 | "zeroize", 221 | ] 222 | 223 | [[package]] 224 | name = "chrono" 225 | version = "0.4.19" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 228 | dependencies = [ 229 | "libc", 230 | "num-integer", 231 | "num-traits", 232 | "time 0.1.44", 233 | "winapi", 234 | ] 235 | 236 | [[package]] 237 | name = "cipher" 238 | version = "0.3.0" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" 241 | dependencies = [ 242 | "generic-array", 243 | ] 244 | 245 | [[package]] 246 | name = "clap" 247 | version = "3.0.14" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "b63edc3f163b3c71ec8aa23f9bd6070f77edbf3d1d198b164afa90ff00e4ec62" 250 | dependencies = [ 251 | "atty", 252 | "bitflags", 253 | "clap_derive", 254 | "indexmap", 255 | "lazy_static", 256 | "os_str_bytes", 257 | "strsim", 258 | "termcolor", 259 | "textwrap", 260 | ] 261 | 262 | [[package]] 263 | name = "clap_derive" 264 | version = "3.0.14" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "9a1132dc3944b31c20dd8b906b3a9f0a5d0243e092d59171414969657ac6aa85" 267 | dependencies = [ 268 | "heck", 269 | "proc-macro-error", 270 | "proc-macro2", 271 | "quote", 272 | "syn", 273 | ] 274 | 275 | [[package]] 276 | name = "constant_time_eq" 277 | version = "0.1.5" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" 280 | 281 | [[package]] 282 | name = "cpufeatures" 283 | version = "0.2.1" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" 286 | dependencies = [ 287 | "libc", 288 | ] 289 | 290 | [[package]] 291 | name = "crc32fast" 292 | version = "1.3.2" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 295 | dependencies = [ 296 | "cfg-if", 297 | ] 298 | 299 | [[package]] 300 | name = "crossbeam-utils" 301 | version = "0.8.8" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" 304 | dependencies = [ 305 | "cfg-if", 306 | "lazy_static", 307 | ] 308 | 309 | [[package]] 310 | name = "crypto-bigint" 311 | version = "0.2.11" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "f83bd3bb4314701c568e340cd8cf78c975aa0ca79e03d3f6d1677d5b0c9c0c03" 314 | dependencies = [ 315 | "generic-array", 316 | "rand_core 0.6.3", 317 | "subtle", 318 | "zeroize", 319 | ] 320 | 321 | [[package]] 322 | name = "crypto-common" 323 | version = "0.1.3" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" 326 | dependencies = [ 327 | "generic-array", 328 | "typenum", 329 | ] 330 | 331 | [[package]] 332 | name = "crypto-mac" 333 | version = "0.11.1" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" 336 | dependencies = [ 337 | "generic-array", 338 | "subtle", 339 | ] 340 | 341 | [[package]] 342 | name = "ctr" 343 | version = "0.8.0" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" 346 | dependencies = [ 347 | "cipher", 348 | ] 349 | 350 | [[package]] 351 | name = "curve25519-dalek" 352 | version = "3.2.0" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" 355 | dependencies = [ 356 | "byteorder", 357 | "digest 0.9.0", 358 | "rand_core 0.5.1", 359 | "subtle", 360 | "zeroize", 361 | ] 362 | 363 | [[package]] 364 | name = "difflib" 365 | version = "0.4.0" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" 368 | 369 | [[package]] 370 | name = "digest" 371 | version = "0.9.0" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" 374 | dependencies = [ 375 | "generic-array", 376 | ] 377 | 378 | [[package]] 379 | name = "digest" 380 | version = "0.10.3" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" 383 | dependencies = [ 384 | "block-buffer 0.10.2", 385 | "crypto-common", 386 | "subtle", 387 | ] 388 | 389 | [[package]] 390 | name = "doc-comment" 391 | version = "0.3.3" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 394 | 395 | [[package]] 396 | name = "either" 397 | version = "1.6.1" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 400 | 401 | [[package]] 402 | name = "elliptic-curve" 403 | version = "0.10.6" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "beca177dcb8eb540133e7680baff45e7cc4d93bf22002676cec549f82343721b" 406 | dependencies = [ 407 | "crypto-bigint", 408 | "ff", 409 | "generic-array", 410 | "group", 411 | "rand_core 0.6.3", 412 | "subtle", 413 | "zeroize", 414 | ] 415 | 416 | [[package]] 417 | name = "fastrand" 418 | version = "1.7.0" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" 421 | dependencies = [ 422 | "instant", 423 | ] 424 | 425 | [[package]] 426 | name = "ff" 427 | version = "0.10.1" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f" 430 | dependencies = [ 431 | "rand_core 0.6.3", 432 | "subtle", 433 | ] 434 | 435 | [[package]] 436 | name = "flate2" 437 | version = "1.0.22" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" 440 | dependencies = [ 441 | "cfg-if", 442 | "crc32fast", 443 | "libc", 444 | "miniz_oxide", 445 | ] 446 | 447 | [[package]] 448 | name = "fnv" 449 | version = "1.0.7" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 452 | 453 | [[package]] 454 | name = "generic-array" 455 | version = "0.14.5" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" 458 | dependencies = [ 459 | "serde", 460 | "typenum", 461 | "version_check", 462 | ] 463 | 464 | [[package]] 465 | name = "getrandom" 466 | version = "0.2.4" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" 469 | dependencies = [ 470 | "cfg-if", 471 | "libc", 472 | "wasi", 473 | ] 474 | 475 | [[package]] 476 | name = "ghash" 477 | version = "0.4.4" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" 480 | dependencies = [ 481 | "opaque-debug", 482 | "polyval", 483 | ] 484 | 485 | [[package]] 486 | name = "globset" 487 | version = "0.4.8" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd" 490 | dependencies = [ 491 | "aho-corasick", 492 | "bstr", 493 | "fnv", 494 | "log", 495 | "regex", 496 | ] 497 | 498 | [[package]] 499 | name = "globwalk" 500 | version = "0.8.1" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" 503 | dependencies = [ 504 | "bitflags", 505 | "ignore", 506 | "walkdir", 507 | ] 508 | 509 | [[package]] 510 | name = "group" 511 | version = "0.10.0" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912" 514 | dependencies = [ 515 | "ff", 516 | "rand_core 0.6.3", 517 | "subtle", 518 | ] 519 | 520 | [[package]] 521 | name = "hashbrown" 522 | version = "0.11.2" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 525 | 526 | [[package]] 527 | name = "heck" 528 | version = "0.4.0" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 531 | 532 | [[package]] 533 | name = "hermit-abi" 534 | version = "0.1.19" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 537 | dependencies = [ 538 | "libc", 539 | ] 540 | 541 | [[package]] 542 | name = "hex" 543 | version = "0.4.3" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 546 | 547 | [[package]] 548 | name = "hkdf" 549 | version = "0.11.0" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b" 552 | dependencies = [ 553 | "digest 0.9.0", 554 | "hmac 0.11.0", 555 | ] 556 | 557 | [[package]] 558 | name = "hmac" 559 | version = "0.11.0" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" 562 | dependencies = [ 563 | "crypto-mac", 564 | "digest 0.9.0", 565 | ] 566 | 567 | [[package]] 568 | name = "hmac" 569 | version = "0.12.1" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 572 | dependencies = [ 573 | "digest 0.10.3", 574 | ] 575 | 576 | [[package]] 577 | name = "hpke" 578 | version = "0.8.0" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "849e95e93b040915ae2ea8ae9cc0e73a0dc9804e557fa7959c1ba9dce20215a6" 581 | dependencies = [ 582 | "aead", 583 | "aes-gcm", 584 | "byteorder", 585 | "chacha20poly1305", 586 | "digest 0.9.0", 587 | "generic-array", 588 | "hkdf", 589 | "p256", 590 | "paste", 591 | "rand_core 0.6.3", 592 | "serde", 593 | "sha2 0.9.9", 594 | "subtle", 595 | "x25519-dalek", 596 | "zeroize", 597 | ] 598 | 599 | [[package]] 600 | name = "ignore" 601 | version = "0.4.18" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" 604 | dependencies = [ 605 | "crossbeam-utils", 606 | "globset", 607 | "lazy_static", 608 | "log", 609 | "memchr", 610 | "regex", 611 | "same-file", 612 | "thread_local", 613 | "walkdir", 614 | "winapi-util", 615 | ] 616 | 617 | [[package]] 618 | name = "indexmap" 619 | version = "1.8.0" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" 622 | dependencies = [ 623 | "autocfg", 624 | "hashbrown", 625 | ] 626 | 627 | [[package]] 628 | name = "instant" 629 | version = "0.1.12" 630 | source = "registry+https://github.com/rust-lang/crates.io-index" 631 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 632 | dependencies = [ 633 | "cfg-if", 634 | ] 635 | 636 | [[package]] 637 | name = "itertools" 638 | version = "0.10.3" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" 641 | dependencies = [ 642 | "either", 643 | ] 644 | 645 | [[package]] 646 | name = "itoa" 647 | version = "1.0.1" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" 650 | 651 | [[package]] 652 | name = "jobserver" 653 | version = "0.1.24" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" 656 | dependencies = [ 657 | "libc", 658 | ] 659 | 660 | [[package]] 661 | name = "lazy_static" 662 | version = "1.4.0" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 665 | 666 | [[package]] 667 | name = "libc" 668 | version = "0.2.117" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c" 671 | 672 | [[package]] 673 | name = "log" 674 | version = "0.4.14" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 677 | dependencies = [ 678 | "cfg-if", 679 | ] 680 | 681 | [[package]] 682 | name = "memchr" 683 | version = "2.4.1" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 686 | 687 | [[package]] 688 | name = "miniz_oxide" 689 | version = "0.4.4" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" 692 | dependencies = [ 693 | "adler", 694 | "autocfg", 695 | ] 696 | 697 | [[package]] 698 | name = "num-integer" 699 | version = "0.1.44" 700 | source = "registry+https://github.com/rust-lang/crates.io-index" 701 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 702 | dependencies = [ 703 | "autocfg", 704 | "num-traits", 705 | ] 706 | 707 | [[package]] 708 | name = "num-traits" 709 | version = "0.2.14" 710 | source = "registry+https://github.com/rust-lang/crates.io-index" 711 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 712 | dependencies = [ 713 | "autocfg", 714 | ] 715 | 716 | [[package]] 717 | name = "num_threads" 718 | version = "0.1.5" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "aba1801fb138d8e85e11d0fc70baf4fe1cdfffda7c6cd34a854905df588e5ed0" 721 | dependencies = [ 722 | "libc", 723 | ] 724 | 725 | [[package]] 726 | name = "once_cell" 727 | version = "1.9.0" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" 730 | 731 | [[package]] 732 | name = "opaque-debug" 733 | version = "0.3.0" 734 | source = "registry+https://github.com/rust-lang/crates.io-index" 735 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 736 | 737 | [[package]] 738 | name = "os_str_bytes" 739 | version = "6.0.0" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" 742 | dependencies = [ 743 | "memchr", 744 | ] 745 | 746 | [[package]] 747 | name = "p256" 748 | version = "0.9.0" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "d053368e1bae4c8a672953397bd1bd7183dde1c72b0b7612a15719173148d186" 751 | dependencies = [ 752 | "elliptic-curve", 753 | ] 754 | 755 | [[package]] 756 | name = "password-hash" 757 | version = "0.3.2" 758 | source = "registry+https://github.com/rust-lang/crates.io-index" 759 | checksum = "1d791538a6dcc1e7cb7fe6f6b58aca40e7f79403c45b2bc274008b5e647af1d8" 760 | dependencies = [ 761 | "base64ct", 762 | "rand_core 0.6.3", 763 | "subtle", 764 | ] 765 | 766 | [[package]] 767 | name = "paste" 768 | version = "1.0.6" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" 771 | 772 | [[package]] 773 | name = "pbkdf2" 774 | version = "0.10.1" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "271779f35b581956db91a3e55737327a03aa051e90b1c47aeb189508533adfd7" 777 | dependencies = [ 778 | "digest 0.10.3", 779 | "hmac 0.12.1", 780 | "password-hash", 781 | "sha2 0.10.2", 782 | ] 783 | 784 | [[package]] 785 | name = "pkg-config" 786 | version = "0.3.25" 787 | source = "registry+https://github.com/rust-lang/crates.io-index" 788 | checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" 789 | 790 | [[package]] 791 | name = "poly1305" 792 | version = "0.7.2" 793 | source = "registry+https://github.com/rust-lang/crates.io-index" 794 | checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" 795 | dependencies = [ 796 | "cpufeatures", 797 | "opaque-debug", 798 | "universal-hash", 799 | ] 800 | 801 | [[package]] 802 | name = "polyval" 803 | version = "0.5.3" 804 | source = "registry+https://github.com/rust-lang/crates.io-index" 805 | checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" 806 | dependencies = [ 807 | "cfg-if", 808 | "cpufeatures", 809 | "opaque-debug", 810 | "universal-hash", 811 | ] 812 | 813 | [[package]] 814 | name = "ppv-lite86" 815 | version = "0.2.16" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" 818 | 819 | [[package]] 820 | name = "predicates" 821 | version = "2.1.1" 822 | source = "registry+https://github.com/rust-lang/crates.io-index" 823 | checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c" 824 | dependencies = [ 825 | "difflib", 826 | "itertools", 827 | "predicates-core", 828 | ] 829 | 830 | [[package]] 831 | name = "predicates-core" 832 | version = "1.0.3" 833 | source = "registry+https://github.com/rust-lang/crates.io-index" 834 | checksum = "da1c2388b1513e1b605fcec39a95e0a9e8ef088f71443ef37099fa9ae6673fcb" 835 | 836 | [[package]] 837 | name = "predicates-tree" 838 | version = "1.0.5" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "4d86de6de25020a36c6d3643a86d9a6a9f552107c0559c60ea03551b5e16c032" 841 | dependencies = [ 842 | "predicates-core", 843 | "termtree", 844 | ] 845 | 846 | [[package]] 847 | name = "proc-macro-error" 848 | version = "1.0.4" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 851 | dependencies = [ 852 | "proc-macro-error-attr", 853 | "proc-macro2", 854 | "quote", 855 | "syn", 856 | "version_check", 857 | ] 858 | 859 | [[package]] 860 | name = "proc-macro-error-attr" 861 | version = "1.0.4" 862 | source = "registry+https://github.com/rust-lang/crates.io-index" 863 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 864 | dependencies = [ 865 | "proc-macro2", 866 | "quote", 867 | "version_check", 868 | ] 869 | 870 | [[package]] 871 | name = "proc-macro2" 872 | version = "1.0.36" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" 875 | dependencies = [ 876 | "unicode-xid", 877 | ] 878 | 879 | [[package]] 880 | name = "quote" 881 | version = "1.0.15" 882 | source = "registry+https://github.com/rust-lang/crates.io-index" 883 | checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" 884 | dependencies = [ 885 | "proc-macro2", 886 | ] 887 | 888 | [[package]] 889 | name = "rand" 890 | version = "0.8.4" 891 | source = "registry+https://github.com/rust-lang/crates.io-index" 892 | checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" 893 | dependencies = [ 894 | "libc", 895 | "rand_chacha", 896 | "rand_core 0.6.3", 897 | "rand_hc", 898 | ] 899 | 900 | [[package]] 901 | name = "rand_chacha" 902 | version = "0.3.1" 903 | source = "registry+https://github.com/rust-lang/crates.io-index" 904 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 905 | dependencies = [ 906 | "ppv-lite86", 907 | "rand_core 0.6.3", 908 | ] 909 | 910 | [[package]] 911 | name = "rand_core" 912 | version = "0.5.1" 913 | source = "registry+https://github.com/rust-lang/crates.io-index" 914 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 915 | 916 | [[package]] 917 | name = "rand_core" 918 | version = "0.6.3" 919 | source = "registry+https://github.com/rust-lang/crates.io-index" 920 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 921 | dependencies = [ 922 | "getrandom", 923 | ] 924 | 925 | [[package]] 926 | name = "rand_hc" 927 | version = "0.3.1" 928 | source = "registry+https://github.com/rust-lang/crates.io-index" 929 | checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" 930 | dependencies = [ 931 | "rand_core 0.6.3", 932 | ] 933 | 934 | [[package]] 935 | name = "readext" 936 | version = "0.1.0" 937 | source = "registry+https://github.com/rust-lang/crates.io-index" 938 | checksum = "abdc58f5f18bcf347b55cebb34ed4618b0feff9a9223160f5902adbc1f6a72a6" 939 | 940 | [[package]] 941 | name = "redox_syscall" 942 | version = "0.2.10" 943 | source = "registry+https://github.com/rust-lang/crates.io-index" 944 | checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" 945 | dependencies = [ 946 | "bitflags", 947 | ] 948 | 949 | [[package]] 950 | name = "regex" 951 | version = "1.5.4" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 954 | dependencies = [ 955 | "aho-corasick", 956 | "memchr", 957 | "regex-syntax", 958 | ] 959 | 960 | [[package]] 961 | name = "regex-automata" 962 | version = "0.1.10" 963 | source = "registry+https://github.com/rust-lang/crates.io-index" 964 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 965 | 966 | [[package]] 967 | name = "regex-syntax" 968 | version = "0.6.25" 969 | source = "registry+https://github.com/rust-lang/crates.io-index" 970 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 971 | 972 | [[package]] 973 | name = "remove_dir_all" 974 | version = "0.5.3" 975 | source = "registry+https://github.com/rust-lang/crates.io-index" 976 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 977 | dependencies = [ 978 | "winapi", 979 | ] 980 | 981 | [[package]] 982 | name = "ryu" 983 | version = "1.0.9" 984 | source = "registry+https://github.com/rust-lang/crates.io-index" 985 | checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" 986 | 987 | [[package]] 988 | name = "same-file" 989 | version = "1.0.6" 990 | source = "registry+https://github.com/rust-lang/crates.io-index" 991 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 992 | dependencies = [ 993 | "winapi-util", 994 | ] 995 | 996 | [[package]] 997 | name = "serde" 998 | version = "1.0.136" 999 | source = "registry+https://github.com/rust-lang/crates.io-index" 1000 | checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" 1001 | dependencies = [ 1002 | "serde_derive", 1003 | ] 1004 | 1005 | [[package]] 1006 | name = "serde_derive" 1007 | version = "1.0.136" 1008 | source = "registry+https://github.com/rust-lang/crates.io-index" 1009 | checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" 1010 | dependencies = [ 1011 | "proc-macro2", 1012 | "quote", 1013 | "syn", 1014 | ] 1015 | 1016 | [[package]] 1017 | name = "serde_json" 1018 | version = "1.0.78" 1019 | source = "registry+https://github.com/rust-lang/crates.io-index" 1020 | checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085" 1021 | dependencies = [ 1022 | "itoa", 1023 | "ryu", 1024 | "serde", 1025 | ] 1026 | 1027 | [[package]] 1028 | name = "sha1" 1029 | version = "0.10.1" 1030 | source = "registry+https://github.com/rust-lang/crates.io-index" 1031 | checksum = "c77f4e7f65455545c2153c1253d25056825e77ee2533f0e41deb65a93a34852f" 1032 | dependencies = [ 1033 | "cfg-if", 1034 | "cpufeatures", 1035 | "digest 0.10.3", 1036 | ] 1037 | 1038 | [[package]] 1039 | name = "sha2" 1040 | version = "0.9.9" 1041 | source = "registry+https://github.com/rust-lang/crates.io-index" 1042 | checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" 1043 | dependencies = [ 1044 | "block-buffer 0.9.0", 1045 | "cfg-if", 1046 | "cpufeatures", 1047 | "digest 0.9.0", 1048 | "opaque-debug", 1049 | ] 1050 | 1051 | [[package]] 1052 | name = "sha2" 1053 | version = "0.10.2" 1054 | source = "registry+https://github.com/rust-lang/crates.io-index" 1055 | checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" 1056 | dependencies = [ 1057 | "cfg-if", 1058 | "cpufeatures", 1059 | "digest 0.10.3", 1060 | ] 1061 | 1062 | [[package]] 1063 | name = "ssh-log-cli" 1064 | version = "0.1.0" 1065 | dependencies = [ 1066 | "assert_cmd", 1067 | "assert_fs", 1068 | "base64", 1069 | "bincode", 1070 | "byteorder", 1071 | "chrono", 1072 | "clap", 1073 | "hex", 1074 | "hpke", 1075 | "rand", 1076 | "readext", 1077 | "serde", 1078 | "serde_json", 1079 | "tempfile", 1080 | "thiserror", 1081 | "walkdir", 1082 | "zip", 1083 | ] 1084 | 1085 | [[package]] 1086 | name = "strsim" 1087 | version = "0.10.0" 1088 | source = "registry+https://github.com/rust-lang/crates.io-index" 1089 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 1090 | 1091 | [[package]] 1092 | name = "subtle" 1093 | version = "2.4.1" 1094 | source = "registry+https://github.com/rust-lang/crates.io-index" 1095 | checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" 1096 | 1097 | [[package]] 1098 | name = "syn" 1099 | version = "1.0.86" 1100 | source = "registry+https://github.com/rust-lang/crates.io-index" 1101 | checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" 1102 | dependencies = [ 1103 | "proc-macro2", 1104 | "quote", 1105 | "unicode-xid", 1106 | ] 1107 | 1108 | [[package]] 1109 | name = "synstructure" 1110 | version = "0.12.6" 1111 | source = "registry+https://github.com/rust-lang/crates.io-index" 1112 | checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" 1113 | dependencies = [ 1114 | "proc-macro2", 1115 | "quote", 1116 | "syn", 1117 | "unicode-xid", 1118 | ] 1119 | 1120 | [[package]] 1121 | name = "tempfile" 1122 | version = "3.3.0" 1123 | source = "registry+https://github.com/rust-lang/crates.io-index" 1124 | checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" 1125 | dependencies = [ 1126 | "cfg-if", 1127 | "fastrand", 1128 | "libc", 1129 | "redox_syscall", 1130 | "remove_dir_all", 1131 | "winapi", 1132 | ] 1133 | 1134 | [[package]] 1135 | name = "termcolor" 1136 | version = "1.1.2" 1137 | source = "registry+https://github.com/rust-lang/crates.io-index" 1138 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 1139 | dependencies = [ 1140 | "winapi-util", 1141 | ] 1142 | 1143 | [[package]] 1144 | name = "termtree" 1145 | version = "0.2.4" 1146 | source = "registry+https://github.com/rust-lang/crates.io-index" 1147 | checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" 1148 | 1149 | [[package]] 1150 | name = "textwrap" 1151 | version = "0.14.2" 1152 | source = "registry+https://github.com/rust-lang/crates.io-index" 1153 | checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" 1154 | 1155 | [[package]] 1156 | name = "thiserror" 1157 | version = "1.0.30" 1158 | source = "registry+https://github.com/rust-lang/crates.io-index" 1159 | checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" 1160 | dependencies = [ 1161 | "thiserror-impl", 1162 | ] 1163 | 1164 | [[package]] 1165 | name = "thiserror-impl" 1166 | version = "1.0.30" 1167 | source = "registry+https://github.com/rust-lang/crates.io-index" 1168 | checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" 1169 | dependencies = [ 1170 | "proc-macro2", 1171 | "quote", 1172 | "syn", 1173 | ] 1174 | 1175 | [[package]] 1176 | name = "thread_local" 1177 | version = "1.1.4" 1178 | source = "registry+https://github.com/rust-lang/crates.io-index" 1179 | checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" 1180 | dependencies = [ 1181 | "once_cell", 1182 | ] 1183 | 1184 | [[package]] 1185 | name = "time" 1186 | version = "0.1.44" 1187 | source = "registry+https://github.com/rust-lang/crates.io-index" 1188 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 1189 | dependencies = [ 1190 | "libc", 1191 | "wasi", 1192 | "winapi", 1193 | ] 1194 | 1195 | [[package]] 1196 | name = "time" 1197 | version = "0.3.9" 1198 | source = "registry+https://github.com/rust-lang/crates.io-index" 1199 | checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" 1200 | dependencies = [ 1201 | "itoa", 1202 | "libc", 1203 | "num_threads", 1204 | "time-macros", 1205 | ] 1206 | 1207 | [[package]] 1208 | name = "time-macros" 1209 | version = "0.2.4" 1210 | source = "registry+https://github.com/rust-lang/crates.io-index" 1211 | checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" 1212 | 1213 | [[package]] 1214 | name = "typenum" 1215 | version = "1.15.0" 1216 | source = "registry+https://github.com/rust-lang/crates.io-index" 1217 | checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" 1218 | 1219 | [[package]] 1220 | name = "unicode-xid" 1221 | version = "0.2.2" 1222 | source = "registry+https://github.com/rust-lang/crates.io-index" 1223 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 1224 | 1225 | [[package]] 1226 | name = "universal-hash" 1227 | version = "0.4.1" 1228 | source = "registry+https://github.com/rust-lang/crates.io-index" 1229 | checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" 1230 | dependencies = [ 1231 | "generic-array", 1232 | "subtle", 1233 | ] 1234 | 1235 | [[package]] 1236 | name = "version_check" 1237 | version = "0.9.4" 1238 | source = "registry+https://github.com/rust-lang/crates.io-index" 1239 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1240 | 1241 | [[package]] 1242 | name = "wait-timeout" 1243 | version = "0.2.0" 1244 | source = "registry+https://github.com/rust-lang/crates.io-index" 1245 | checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" 1246 | dependencies = [ 1247 | "libc", 1248 | ] 1249 | 1250 | [[package]] 1251 | name = "walkdir" 1252 | version = "2.3.2" 1253 | source = "registry+https://github.com/rust-lang/crates.io-index" 1254 | checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" 1255 | dependencies = [ 1256 | "same-file", 1257 | "winapi", 1258 | "winapi-util", 1259 | ] 1260 | 1261 | [[package]] 1262 | name = "wasi" 1263 | version = "0.10.0+wasi-snapshot-preview1" 1264 | source = "registry+https://github.com/rust-lang/crates.io-index" 1265 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 1266 | 1267 | [[package]] 1268 | name = "winapi" 1269 | version = "0.3.9" 1270 | source = "registry+https://github.com/rust-lang/crates.io-index" 1271 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1272 | dependencies = [ 1273 | "winapi-i686-pc-windows-gnu", 1274 | "winapi-x86_64-pc-windows-gnu", 1275 | ] 1276 | 1277 | [[package]] 1278 | name = "winapi-i686-pc-windows-gnu" 1279 | version = "0.4.0" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1282 | 1283 | [[package]] 1284 | name = "winapi-util" 1285 | version = "0.1.5" 1286 | source = "registry+https://github.com/rust-lang/crates.io-index" 1287 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1288 | dependencies = [ 1289 | "winapi", 1290 | ] 1291 | 1292 | [[package]] 1293 | name = "winapi-x86_64-pc-windows-gnu" 1294 | version = "0.4.0" 1295 | source = "registry+https://github.com/rust-lang/crates.io-index" 1296 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1297 | 1298 | [[package]] 1299 | name = "x25519-dalek" 1300 | version = "1.2.0" 1301 | source = "registry+https://github.com/rust-lang/crates.io-index" 1302 | checksum = "2392b6b94a576b4e2bf3c5b2757d63f10ada8020a2e4d08ac849ebcf6ea8e077" 1303 | dependencies = [ 1304 | "curve25519-dalek", 1305 | "rand_core 0.5.1", 1306 | "zeroize", 1307 | ] 1308 | 1309 | [[package]] 1310 | name = "zeroize" 1311 | version = "1.3.0" 1312 | source = "registry+https://github.com/rust-lang/crates.io-index" 1313 | checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" 1314 | dependencies = [ 1315 | "zeroize_derive", 1316 | ] 1317 | 1318 | [[package]] 1319 | name = "zeroize_derive" 1320 | version = "1.3.1" 1321 | source = "registry+https://github.com/rust-lang/crates.io-index" 1322 | checksum = "81e8f13fef10b63c06356d65d416b070798ddabcadc10d3ece0c5be9b3c7eddb" 1323 | dependencies = [ 1324 | "proc-macro2", 1325 | "quote", 1326 | "syn", 1327 | "synstructure", 1328 | ] 1329 | 1330 | [[package]] 1331 | name = "zip" 1332 | version = "0.6.2" 1333 | source = "registry+https://github.com/rust-lang/crates.io-index" 1334 | checksum = "bf225bcf73bb52cbb496e70475c7bd7a3f769df699c0020f6c7bd9a96dcf0b8d" 1335 | dependencies = [ 1336 | "aes", 1337 | "byteorder", 1338 | "bzip2", 1339 | "constant_time_eq", 1340 | "crc32fast", 1341 | "crossbeam-utils", 1342 | "flate2", 1343 | "hmac 0.12.1", 1344 | "pbkdf2", 1345 | "sha1", 1346 | "time 0.3.9", 1347 | "zstd", 1348 | ] 1349 | 1350 | [[package]] 1351 | name = "zstd" 1352 | version = "0.10.0+zstd.1.5.2" 1353 | source = "registry+https://github.com/rust-lang/crates.io-index" 1354 | checksum = "3b1365becbe415f3f0fcd024e2f7b45bacfb5bdd055f0dc113571394114e7bdd" 1355 | dependencies = [ 1356 | "zstd-safe", 1357 | ] 1358 | 1359 | [[package]] 1360 | name = "zstd-safe" 1361 | version = "4.1.4+zstd.1.5.2" 1362 | source = "registry+https://github.com/rust-lang/crates.io-index" 1363 | checksum = "2f7cd17c9af1a4d6c24beb1cc54b17e2ef7b593dc92f19e9d9acad8b182bbaee" 1364 | dependencies = [ 1365 | "libc", 1366 | "zstd-sys", 1367 | ] 1368 | 1369 | [[package]] 1370 | name = "zstd-sys" 1371 | version = "1.6.3+zstd.1.5.2" 1372 | source = "registry+https://github.com/rust-lang/crates.io-index" 1373 | checksum = "fc49afa5c8d634e75761feda8c592051e7eeb4683ba827211eb0d731d3402ea8" 1374 | dependencies = [ 1375 | "cc", 1376 | "libc", 1377 | ] 1378 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2021" 3 | name = "ssh-log-cli" 4 | version = "0.1.0" 5 | 6 | [dependencies] 7 | base64 = "0.13.0" 8 | bincode = "1.3.3" 9 | byteorder = "1.4.3" 10 | chrono = "0.4.19" 11 | clap = {version = "3.0.0", features = ["derive"]} 12 | hex = "0.4.2" 13 | hpke = {version = "0.8.0", features = [ 14 | "serde_impls", 15 | ]} 16 | rand = "0.8.4" 17 | readext = "0.1.0" 18 | serde = {version = "1.0.130", features = ["derive"]} 19 | serde_json = "1.0.68" 20 | tempfile = "3.3.0" 21 | thiserror = "1.0.20" 22 | walkdir = "2.3.2" 23 | zip = "0.6.2" 24 | 25 | [dev-dependencies] 26 | assert_cmd = "2.0.2" 27 | assert_fs = "1.0.6" 28 | 29 | [profile.release] 30 | lto = true 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, Cloudflare, Inc. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SSH Log CLI 2 | 3 | CLI tool that decrypts and decodes session replay files captured by Cloudflare's Audit SSH proxy. 4 | 5 | ## Installation 6 | 7 | ### Build from source 8 | 1. Install Rust https://doc.rust-lang.org/cargo/getting-started/installation.html; 9 | 2. Run `cargo build --release`; 10 | 3. Find the compiled binary within `target/release/ssh-log-cli`. 11 | 12 | ## Generating a key pair 13 | 14 | ``` shell 15 | $ ssh-log-cli generate-key-pair -o 16 | ``` 17 | 18 | This command generates an HPKE public and private key, saving each one to its own file. The public key file gets the same name, but with a `.pub` extension. 19 | 20 | # 21 | ## Handling SSH session capture files 22 | 23 | ### Decrypting and parsing any SSH session capture file 24 | ``` shell 25 | $ ssh-log-cli decrypt -i -k [-o ] 26 | ``` 27 | 28 | If no output file name is specified, it defaults it to .decrypted.zip. 29 | 30 | **Note**: the output file path must be a valid ZIP file name. 31 | 32 | ### Decrypting and replaying an SSH PTY session capture file — *Linux/Mac OS X only* 33 | ``` shell 34 | $ ssh-log-cli decrypt -i -k --replay 35 | ``` 36 | 37 | This will decrypt the session capture and replay it to *stdout*. 38 | 39 | **Note**: no output file will be generated. 40 | # 41 | ## Understanding the Output 42 | 43 | ### PTY Sessions 44 | If the encrypted file has a valid interactive session (PTY) capture, then the output ZIP will contain 2 files: 45 | `term_data.txt` and `term_times.txt`. 46 | 47 | You can then extract it and either open term_data.txt and analyse it on your own or watch a replay by running: 48 | `scriptreplay --timing term_times.txt term_data.txt` 49 | 50 | ### Non-PTY Sessions 51 | If the encrypted file has a valid non-PTY session capture, then the output ZIP will contain 2 files: 52 | `data_from_client.txt` and `data_from_server.txt`. These contain upstream and downstream traffic, respectively. 53 | -------------------------------------------------------------------------------- /src/data.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt::Debug, 3 | io::{Read, Write}, 4 | time::Duration, 5 | }; 6 | 7 | use byteorder::{BigEndian, ByteOrder}; 8 | use thiserror::Error; 9 | 10 | #[derive(Copy, Clone)] 11 | pub enum DataSource { 12 | Client, 13 | Origin, 14 | } 15 | 16 | pub struct DataPacket { 17 | pub source: DataSource, 18 | pub code: u32, 19 | pub elapsed: Duration, 20 | pub data: Vec, 21 | } 22 | 23 | #[derive(Error, Debug)] 24 | pub enum DataError { 25 | #[error("read error")] 26 | ReadError(std::io::Error), 27 | #[error("write error")] 28 | WriteError(std::io::Error), 29 | #[error("data packet too small")] 30 | DataPacketTooSmall, 31 | #[error("unexpected data source")] 32 | UnexpectedDataSource, 33 | } 34 | 35 | pub struct DataDecoder(pub R); 36 | 37 | impl DataDecoder { 38 | pub fn next(&mut self) -> Result, DataError> { 39 | let mut total_len: [u8; 4] = [0; 4]; 40 | let read = self.0.read(&mut total_len).map_err(DataError::ReadError)?; 41 | if read == 0 { 42 | return Ok(None); 43 | } 44 | let total_len = u32::from_be_bytes(total_len) as usize; 45 | if total_len < 13 { 46 | return Err(DataError::DataPacketTooSmall); 47 | } 48 | 49 | let mut buf: Vec = vec![0; total_len]; 50 | self.0.read_exact(&mut buf).map_err(DataError::ReadError)?; 51 | 52 | let source = match buf[0] { 53 | 0 => DataSource::Client, 54 | 1 => DataSource::Origin, 55 | _ => return Err(DataError::UnexpectedDataSource), 56 | }; 57 | 58 | Ok(Some(DataPacket { 59 | source, 60 | code: BigEndian::read_u32(&buf[1..5]), 61 | elapsed: Duration::from_micros(BigEndian::read_u64(&buf[5..13])), 62 | data: Vec::from(&buf[13..]), 63 | })) 64 | } 65 | } 66 | 67 | pub fn generate_data_file( 68 | mut decoder: DataDecoder, 69 | mut client_data_writer: W, 70 | mut origin_data_writer: W, 71 | ) -> Result<(), DataError> { 72 | loop { 73 | let data_packet = match decoder.next()? { 74 | Some(packet) => packet, 75 | None => return Ok(()), 76 | }; 77 | let writer = match data_packet.source { 78 | DataSource::Client => &mut client_data_writer, 79 | DataSource::Origin => &mut origin_data_writer, 80 | }; 81 | writer 82 | .write(&data_packet.data) 83 | .map_err(DataError::WriteError)?; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/hpke.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cmp::min, 3 | io::{self, Cursor, Read, Write}, 4 | }; 5 | 6 | use base64::DecodeError; 7 | use hex::FromHexError; 8 | use hpke::{ 9 | aead::{AeadCtxR, ChaCha20Poly1305}, 10 | kdf::HkdfSha256, 11 | kem::X25519HkdfSha256, 12 | Deserializable, HpkeError, Kem as KemTrait, OpModeR, Serializable, 13 | }; 14 | use rand::{rngs::StdRng, SeedableRng}; 15 | use serde::{Deserialize, Serialize}; 16 | use std::fmt::Debug; 17 | use thiserror::Error; 18 | 19 | use crate::metadata::Metadata; 20 | 21 | type Kem = X25519HkdfSha256; 22 | type Aead = ChaCha20Poly1305; 23 | type Kdf = HkdfSha256; 24 | 25 | type PrivateKey = ::PrivateKey; 26 | type EncappedKey = ::EncappedKey; 27 | 28 | #[derive(Error, Debug)] 29 | pub enum HPKEDecryptionError { 30 | #[error("invalid base64 private key")] 31 | PrivateKeyInvalidB64(DecodeError), 32 | #[error("invalid private key")] 33 | PrivateKeyInvalid(HpkeError), 34 | #[error("invalid hex encapsulated key")] 35 | EncappedKeyInvalidHex(FromHexError), 36 | #[error("invalid encapsulated key")] 37 | EncappedKeyInvalid(HpkeError), 38 | #[error("error reading from reader")] 39 | BuffRead(std::io::Error), 40 | #[error("context creation error")] 41 | ContextCreation(HpkeError), 42 | #[error("error decrypting buffer")] 43 | Decrypt(HpkeError), 44 | } 45 | 46 | #[derive(Serialize, Deserialize)] 47 | pub struct KeyPair { 48 | private_key: String, 49 | public_key: String, 50 | } 51 | 52 | impl KeyPair { 53 | // Generates a public-private key pair 54 | pub fn new() -> (String, String) { 55 | let mut csprng = StdRng::from_entropy(); 56 | let (private, public) = Kem::gen_keypair(&mut csprng); 57 | let private = base64::encode(private.to_bytes()); 58 | let public = base64::encode(public.to_bytes()); 59 | (private, public) 60 | } 61 | } 62 | 63 | pub struct Ctx { 64 | inner: AeadCtxR, 65 | reader: R, 66 | pending: Vec, 67 | } 68 | 69 | impl Ctx { 70 | pub fn new( 71 | meta: &Metadata, 72 | private_key: String, 73 | reader: R, 74 | ) -> Result { 75 | let private_key = 76 | base64::decode(private_key).map_err(HPKEDecryptionError::PrivateKeyInvalidB64)?; 77 | let private_key = 78 | PrivateKey::from_bytes(&private_key).map_err(HPKEDecryptionError::PrivateKeyInvalid)?; 79 | 80 | let encapped_key = hex::decode(meta.encapsulated_key.as_bytes()) 81 | .map_err(HPKEDecryptionError::EncappedKeyInvalidHex)?; 82 | let encapped_key = EncappedKey::from_bytes(&encapped_key) 83 | .map_err(HPKEDecryptionError::EncappedKeyInvalid)?; 84 | 85 | let inner = hpke::setup_receiver::( 86 | &OpModeR::Base, 87 | &private_key, 88 | &encapped_key, 89 | &[], 90 | ) 91 | .map_err(HPKEDecryptionError::ContextCreation)?; 92 | 93 | Ok(Ctx { 94 | inner, 95 | reader, 96 | pending: Vec::new(), 97 | }) 98 | } 99 | 100 | pub fn decrypt_block(&mut self) -> Result, HPKEDecryptionError> { 101 | let mut len: [u8; 4] = [0; 4]; 102 | let read = self 103 | .reader 104 | .read(&mut len) 105 | .map_err(HPKEDecryptionError::BuffRead)?; 106 | if read == 0 { 107 | return Ok(Vec::new()); 108 | } 109 | 110 | let len = u32::from_be_bytes(len) as usize; 111 | let mut buf = vec![0; len]; 112 | self.reader 113 | .read_exact(&mut buf) 114 | .map_err(HPKEDecryptionError::BuffRead)?; 115 | self.inner 116 | .open(&buf, &[]) 117 | .map_err(HPKEDecryptionError::Decrypt) 118 | } 119 | } 120 | 121 | fn write_buf(buf: &[u8], cursor: &mut Cursor<&mut [u8]>, n: usize) -> Result, io::Error> { 122 | let n = min(buf.len(), n); 123 | let (to_write, remaining) = buf.split_at(n); 124 | cursor.write_all(to_write)?; 125 | Ok(Vec::from(remaining)) 126 | } 127 | 128 | impl Read for Ctx { 129 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 130 | let buf_len = buf.len(); 131 | let mut c = Cursor::new(buf); 132 | 133 | // read first from the pending buffer 134 | self.pending = write_buf(&self.pending, &mut c, buf_len)?; 135 | 136 | let buf_remaining = buf_len - c.position() as usize; 137 | if buf_remaining > 0 { 138 | // grab some more bytes from the underlying reader 139 | let decrypted = self 140 | .decrypt_block() 141 | .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; 142 | self.pending = write_buf(&decrypted, &mut c, buf_remaining)?; 143 | } 144 | 145 | Ok(c.position() as usize) 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![warn(rust_2018_idioms)] 2 | mod data; 3 | mod hpke; 4 | mod metadata; 5 | mod pty; 6 | mod zip; 7 | 8 | use crate::metadata::Metadata; 9 | use clap::Parser; 10 | use data::{generate_data_file, DataDecoder}; 11 | use std::fs::File; 12 | use std::io::{Read, Write}; 13 | use std::path::Path; 14 | use std::{fs, str}; 15 | use tempfile::tempdir; 16 | use walkdir::WalkDir; 17 | 18 | const CLIENT_DATA_FILE_NAME: &str = "data_from_client.txt"; 19 | const SERVER_DATA_FILE_NAME: &str = "data_from_server.txt"; 20 | const REPLAY_DATA_FILE_NAME: &str = "term_data.txt"; 21 | const REPLAY_TIMES_FILE_NAME: &str = "term_times.txt"; 22 | 23 | #[derive(Parser)] 24 | #[clap(about, author, version)] 25 | struct Options { 26 | #[clap(subcommand)] 27 | command: Command, 28 | } 29 | 30 | #[derive(Parser)] 31 | struct DecryptOptions { 32 | #[clap( 33 | short = 'i', 34 | long, 35 | help = "File containing the base64 encoded encrypted matched data" 36 | )] 37 | input_filename: String, 38 | 39 | #[clap( 40 | short = 'k', 41 | long, 42 | help = "File containing the base64 encoded private key" 43 | )] 44 | private_key_filename: String, 45 | 46 | #[clap( 47 | short = 'r', 48 | long, 49 | help = "Replay this session, only works if PTY was allocated for the session" 50 | )] 51 | replay: bool, 52 | 53 | #[clap( 54 | short = 'o', 55 | long, 56 | conflicts_with = "replay", 57 | help = "Output ZIP file name for the decrypted session data" 58 | )] 59 | output_file_name: Option, 60 | } 61 | 62 | #[derive(Parser)] 63 | struct GenerateKeyPairOptions { 64 | #[clap(short = 'o', long, help = "Output file name for the private key")] 65 | output_file_name: String, 66 | } 67 | 68 | #[derive(Parser)] 69 | enum Command { 70 | Decrypt(DecryptOptions), 71 | GenerateKeyPair(GenerateKeyPairOptions), 72 | } 73 | 74 | fn run_pty_decode( 75 | metadata: &Metadata, 76 | decoder: DataDecoder, 77 | base_path: &Path, 78 | ) -> Result<(String, String), String> { 79 | let replay_data_fname = base_path.join(REPLAY_DATA_FILE_NAME); 80 | let replay_times_fname = base_path.join(REPLAY_TIMES_FILE_NAME); 81 | 82 | let replay_data_fp = 83 | File::create(&replay_data_fname).map_err(|_| "Could not create output replay data file")?; 84 | let replay_times_fp = File::create(&replay_times_fname) 85 | .map_err(|_| "Could not create output replay time series file")?; 86 | 87 | pty::generate_replay(&metadata, decoder, replay_data_fp, replay_times_fp) 88 | .map_err(|_| "Could not parse pty data")?; 89 | 90 | Ok(( 91 | replay_data_fname.to_str().unwrap().into(), 92 | replay_times_fname.to_str().unwrap().into(), 93 | )) 94 | } 95 | 96 | fn run_raw_decode(decoder: DataDecoder, base_path: &Path) -> Result<(), String> { 97 | let client_data_fp = File::create(base_path.join(CLIENT_DATA_FILE_NAME)) 98 | .map_err(|_| "Could not create client data file")?; 99 | let server_data_fp = File::create(base_path.join(SERVER_DATA_FILE_NAME)) 100 | .map_err(|_| "Could not create server data file")?; 101 | 102 | generate_data_file(decoder, client_data_fp, server_data_fp) 103 | .map_err(|_| "Could parse write raw data".into()) 104 | } 105 | 106 | fn replay_pty_session(data_fname: &str, times_fname: &str) -> Result<(), String> { 107 | std::process::Command::new("scriptreplay") 108 | .args(["--timing", times_fname, data_fname]) 109 | .spawn() 110 | .map_err(|_| "Could not launch scriptreplay, make sure you have it in your PATH.")? 111 | .wait() 112 | .map_err(|_| "scriptreplay error")?; 113 | Ok(()) 114 | } 115 | 116 | fn create_zip_output(fname: &str, path: &Path) -> Result<(), String> { 117 | let output = File::create(fname).unwrap(); 118 | let walkdir = WalkDir::new(path); 119 | let it = walkdir.into_iter(); 120 | 121 | zip::zip( 122 | &mut it.filter_map(|e| e.ok()), 123 | path.to_str().unwrap(), 124 | output, 125 | ) 126 | .map_err(|_| "Could not zip output")?; 127 | Ok(()) 128 | } 129 | 130 | fn run_decrypt(opts: DecryptOptions) -> Result<(), String> { 131 | let mut input_file = 132 | File::open(&opts.input_filename).map_err(|_| "Could not open input file")?; 133 | let private_key_base64 = fs::read_to_string(&opts.private_key_filename) 134 | .map_err(|_| "Failed to read private key from file")?; 135 | 136 | let metadata = Metadata::read(&mut input_file).map_err(|_| "Could not read metadata")?; 137 | let reader = hpke::Ctx::new(&metadata, private_key_base64, input_file) 138 | .map_err(|_| "Could not create decryption context")?; 139 | let decoder = DataDecoder(reader); 140 | 141 | let temp_dir = tempdir().map_err(|_| "Could not create temporary directory")?; 142 | let base_path = temp_dir.path(); 143 | match metadata.pty { 144 | Some(_) => { 145 | let (data_fname, times_fname) = run_pty_decode(&metadata, decoder, base_path)?; 146 | if opts.replay { 147 | return replay_pty_session(&data_fname, ×_fname); 148 | } 149 | } 150 | None => { 151 | run_raw_decode(decoder, base_path)?; 152 | } 153 | } 154 | 155 | let out_file_name = opts 156 | .output_file_name 157 | .unwrap_or(format!("{}-decrypted.zip", opts.input_filename)); 158 | if !out_file_name.ends_with(".zip") { 159 | return Err("Output file name must have a .zip extension".into()); 160 | } 161 | 162 | create_zip_output(&out_file_name, &base_path) 163 | } 164 | 165 | fn run_generate_key_pair(opts: GenerateKeyPairOptions) -> Result<(), String> { 166 | let public_fname = format!("{}.pub", opts.output_file_name); 167 | let mut private_fp = 168 | File::create(opts.output_file_name).map_err(|_| "Could not create private key file")?; 169 | let mut public_fp = 170 | File::create(public_fname).map_err(|_| "Could not create public key file")?; 171 | 172 | let key_pair = hpke::KeyPair::new(); 173 | private_fp 174 | .write_all(key_pair.0.as_bytes()) 175 | .map_err(|_| "Could not write private key")?; 176 | public_fp 177 | .write_all(key_pair.1.as_bytes()) 178 | .map_err(|_| "Could not write public key")?; 179 | Ok(()) 180 | } 181 | 182 | fn run(options: Options) -> Result<(), String> { 183 | match options.command { 184 | Command::GenerateKeyPair(opts) => run_generate_key_pair(opts), 185 | Command::Decrypt(opts) => run_decrypt(opts), 186 | } 187 | } 188 | 189 | fn main() -> Result<(), String> { 190 | run(Options::parse()) 191 | } 192 | -------------------------------------------------------------------------------- /src/metadata.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | 3 | use base64::DecodeError; 4 | use serde::{Deserialize, Serialize}; 5 | use thiserror::Error; 6 | 7 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] 8 | pub struct PTYMetadata { 9 | pub term: Option, 10 | pub width: u32, 11 | pub height: u32, 12 | pub modes: Vec<(String, u32)>, 13 | } 14 | 15 | #[derive(Clone, Default, Serialize, Deserialize)] 16 | pub struct ExitData { 17 | pub timestamp: u64, 18 | pub status: Option, 19 | pub signal: Option, 20 | #[serde(default)] 21 | pub core_dumped: bool, 22 | pub error_msg: Option, 23 | } 24 | 25 | #[derive(Clone, Default, Serialize, Deserialize)] 26 | pub struct Metadata { 27 | pub started_at: u64, 28 | pub data_size: u64, 29 | pub encapsulated_key: String, 30 | pub pty: Option, 31 | pub exit_data: Option, 32 | } 33 | 34 | #[derive(Error, Debug)] 35 | pub enum MetadataError { 36 | #[error("could not read metadata")] 37 | ReadError(std::io::Error), 38 | #[error("invalid base64 metadata")] 39 | DecodeB64Meta(DecodeError), 40 | #[error("invalid metadata json")] 41 | DecodeJsonMeta(serde_json::Error), 42 | } 43 | 44 | impl Metadata { 45 | pub fn read(reader: &mut R) -> Result { 46 | let mut b64len: [u8; 4] = [0; 4]; 47 | reader 48 | .read_exact(&mut b64len) 49 | .map_err(MetadataError::ReadError)?; 50 | let b64len = u32::from_be_bytes(b64len) as usize; 51 | let mut b64meta: Vec = vec![0; b64len]; 52 | reader 53 | .read_exact(&mut b64meta) 54 | .map_err(MetadataError::ReadError)?; 55 | 56 | let metadata = base64::decode(&b64meta).map_err(MetadataError::DecodeB64Meta)?; 57 | serde_json::from_slice(&metadata).map_err(MetadataError::DecodeJsonMeta) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/pty.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io::{Read, Write}, 3 | time::{Duration, UNIX_EPOCH}, 4 | }; 5 | 6 | use chrono::{DateTime, Utc}; 7 | use thiserror::Error; 8 | 9 | use crate::{ 10 | data::{DataDecoder, DataError, DataSource}, 11 | metadata::{ExitData, Metadata}, 12 | }; 13 | 14 | const PTY_MODE_ECHO: &str = "ECHO"; 15 | 16 | #[derive(Error, Debug)] 17 | pub enum PTYParserError { 18 | #[error("data decoding error")] 19 | ReadError(DataError), 20 | #[error("could not write data")] 21 | WriteError(std::io::Error), 22 | #[error("ssh session has no PTY allocated")] 23 | PTYNotFound, 24 | } 25 | 26 | pub fn generate_replay( 27 | meta: &Metadata, 28 | mut decoder: DataDecoder, 29 | mut data_writer: W, 30 | mut times_writer: W, 31 | ) -> Result<(), PTYParserError> { 32 | let pty = match &meta.pty { 33 | Some(pty_meta) => pty_meta, 34 | None => return Err(PTYParserError::PTYNotFound), 35 | }; 36 | 37 | let echo_enabled = pty 38 | .modes 39 | .iter() 40 | .any(|(mode, value)| mode == PTY_MODE_ECHO && *value != 0); 41 | 42 | write!( 43 | data_writer, 44 | "Session started on {} [TERM=\"{}\" COLUMNS=\"{}\" LINES=\"{}\"]\n", 45 | format_date(meta.started_at), 46 | pty.term.clone().unwrap_or("unknown".to_string()), 47 | pty.width, 48 | pty.height 49 | ) 50 | .map_err(PTYParserError::WriteError)?; 51 | 52 | let mut latest_elapsed = 0; 53 | loop { 54 | let data_packet = match decoder.next().map_err(PTYParserError::ReadError)? { 55 | Some(packet) => packet, 56 | None => break, 57 | }; 58 | 59 | if matches!(data_packet.source, DataSource::Client) { 60 | // the server playsback the client input 61 | continue; 62 | } 63 | 64 | data_writer 65 | .write(&data_packet.data) 66 | .map_err(PTYParserError::WriteError)?; 67 | let elapsed_micros = data_packet.elapsed.as_micros(); 68 | let elapsed = (elapsed_micros - latest_elapsed) as f32 / 1000000 as f32; 69 | write!(times_writer, "{:.6} {}\n", elapsed, data_packet.data.len()) 70 | .map_err(PTYParserError::WriteError)?; 71 | latest_elapsed = elapsed_micros; 72 | } 73 | 74 | write_exit_data(data_writer, &meta.exit_data) 75 | } 76 | 77 | fn write_exit_data(mut writer: W, data: &Option) -> Result<(), PTYParserError> { 78 | let exit_str = match data { 79 | Some(exit_data) => { 80 | let end = format_date(exit_data.timestamp); 81 | 82 | if let Some(ref e) = exit_data.error_msg { 83 | format!("\nScript done on {} [ERROR=\"{}\"]\n", end, e) 84 | } else if exit_data.core_dumped { 85 | format!("\nScript done on {} [CORE DUMPED]\n", end) 86 | } else { 87 | let code = exit_data.status.unwrap_or(0); 88 | format!( 89 | "\nScript done on {} [COMMAND_EXIT_CODE=\"{}\"]\n", 90 | end, code 91 | ) 92 | } 93 | } 94 | None => format!("\nSession has no termination data\n"), 95 | }; 96 | writer 97 | .write_all(exit_str.as_bytes()) 98 | .map_err(PTYParserError::WriteError)?; 99 | Ok(()) 100 | } 101 | 102 | fn format_date(unix_timestamp: u64) -> String { 103 | let d = UNIX_EPOCH + Duration::from_secs(unix_timestamp); 104 | let datetime = DateTime::::from(d); 105 | datetime.format("%Y-%m-%d %H:%M:%S+00").to_string() 106 | } 107 | -------------------------------------------------------------------------------- /src/zip.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs::File, 3 | io::{Read, Seek, Write}, 4 | path::Path, 5 | }; 6 | 7 | use walkdir::DirEntry; 8 | use zip::write::FileOptions; 9 | 10 | pub fn zip( 11 | it: &mut dyn Iterator, 12 | prefix: &str, 13 | writer: T, 14 | ) -> zip::result::ZipResult<()> 15 | where 16 | T: Write + Seek, 17 | { 18 | let mut zip = zip::ZipWriter::new(writer); 19 | let options = FileOptions::default() 20 | .compression_method(zip::CompressionMethod::Stored) 21 | .unix_permissions(0o755); 22 | 23 | let mut buffer = Vec::new(); 24 | for entry in it { 25 | let path = entry.path(); 26 | let name = path.strip_prefix(Path::new(prefix)).unwrap(); 27 | if path.is_file() { 28 | #[allow(deprecated)] 29 | zip.start_file_from_path(name, options)?; 30 | let mut f = File::open(path)?; 31 | 32 | f.read_to_end(&mut buffer)?; 33 | zip.write_all(&*buffer)?; 34 | buffer.clear(); 35 | } else if !name.as_os_str().is_empty() { 36 | #[allow(deprecated)] 37 | zip.add_directory_from_path(name, options)?; 38 | } 39 | } 40 | zip.finish()?; 41 | Result::Ok(()) 42 | } 43 | --------------------------------------------------------------------------------