├── .cspell.yml ├── .envrc ├── .git-cliff.toml ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── dependabot.yml │ ├── deploy.yml │ └── release.yml ├── .gitignore ├── .prettierrc.yml ├── .release-plz.toml ├── .rustfmt.toml ├── .taplo.toml ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── README.md ├── flake.lock ├── flake.nix ├── justfile ├── roots ├── Autoridad.crt.pem ├── DigiCertTLSECCP384RootG5.crt.pem ├── affirmtrust_commercial.crt.pem └── ietf-org-chain.pem └── src ├── ext.rs ├── fetch.rs ├── info.rs ├── logging.rs ├── main.rs ├── tui.rs └── util.rs /.cspell.yml: -------------------------------------------------------------------------------- 1 | version: "0.2" 2 | words: 3 | - arboard 4 | - binstall 5 | - byteorder 6 | - chrono 7 | - clippy 8 | - crossterm 9 | - datetime 10 | - docsrs 11 | - extn 12 | - itertools 13 | - memchr 14 | - memmem 15 | - nextest 16 | - nixpkgs 17 | - ocsp 18 | - pemfile 19 | - pkgs 20 | - pkix 21 | - powerset 22 | - ratatui 23 | - rustls 24 | - scts 25 | - spki 26 | - taplo 27 | - webpki 28 | - yansi 29 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /.git-cliff.toml: -------------------------------------------------------------------------------- 1 | [changelog] 2 | header = """ 3 | # Changelog 4 | 5 | """ 6 | body = """ 7 | {% if version %}\ 8 | ## {{ version | trim_start_matches(pat="v") }} 9 | 10 | {% else %}\ 11 | ## Unreleased 12 | 13 | {% endif %}\ 14 | {% for group, commits in commits | filter(attribute="group", value="Features") | group_by(attribute="group") %}\ 15 | ### {{ group | upper_first }} 16 | {% for commit in commits %} 17 | - {{ commit.message | upper_first }}\ 18 | {% endfor %}\n 19 | {% endfor %}\ 20 | {% for group, commits in commits | filter(attribute="group", value="Fixes") | group_by(attribute="group") %}\ 21 | ### {{ group | upper_first }} 22 | {% for commit in commits %} 23 | - {{ commit.message | upper_first }}\ 24 | {% endfor %}\n 25 | {% endfor %}\ 26 | """ 27 | trim = true 28 | 29 | [git] 30 | conventional_commits = true 31 | filter_unconventional = true 32 | commit_parsers = [ 33 | { message = "^feat", group = "Features" }, 34 | { message = "^fix", group = "Fixes" }, 35 | { message = "^docs", group = "Documentation", skip = true }, 36 | ] 37 | # filter_commits = true 38 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [robjtede] 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: monthly 7 | - package-ecosystem: cargo 8 | directory: / 9 | schedule: 10 | interval: weekly 11 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: { types: [opened, synchronize, reopened, ready_for_review] } 5 | push: { branches: [main] } 6 | 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.ref }} 9 | cancel-in-progress: true 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | build_and_test: 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | # prettier-ignore 20 | target: 21 | - { name: Linux, os: ubuntu-latest, triple: x86_64-unknown-linux-gnu } 22 | - { name: macOS, os: macos-latest, triple: x86_64-apple-darwin } 23 | - { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc } 24 | version: 25 | - { name: stable, version: stable } 26 | - { name: beta, version: beta } 27 | 28 | name: Test (${{ matrix.target.name }} / ${{ matrix.version.name }}) 29 | runs-on: ${{ matrix.target.os }} 30 | 31 | steps: 32 | - uses: actions/checkout@v4 33 | 34 | - name: Install nasm 35 | if: matrix.target.os == 'windows-latest' 36 | uses: ilammy/setup-nasm@v1.5.2 37 | 38 | - name: Install Rust (${{ matrix.version.version }}) 39 | uses: actions-rust-lang/setup-rust-toolchain@v1.12.0 40 | with: 41 | toolchain: ${{ matrix.version.version }} 42 | 43 | - name: Install just, nextest 44 | uses: taiki-e/install-action@v2.52.4 45 | with: 46 | tool: just,nextest 47 | 48 | - name: Test 49 | run: just test 50 | 51 | - name: Install cargo-ci-cache-clean 52 | uses: taiki-e/install-action@v2.52.4 53 | with: 54 | tool: cargo-ci-cache-clean 55 | 56 | - name: CI cache clean 57 | run: cargo-ci-cache-clean 58 | -------------------------------------------------------------------------------- /.github/workflows/dependabot.yml: -------------------------------------------------------------------------------- 1 | name: Dependabot 2 | 3 | on: pull_request_target 4 | 5 | permissions: 6 | contents: write 7 | pull-requests: write 8 | 9 | jobs: 10 | review-dependabot-pr: 11 | name: Approve PR 12 | runs-on: ubuntu-latest 13 | if: ${{ github.actor == 'dependabot[bot]' }} 14 | env: 15 | PR_URL: ${{ github.event.pull_request.html_url }} 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | steps: 18 | - name: FetchDependabot metadata 19 | id: dependabot-metadata 20 | uses: dependabot/fetch-metadata@v2.4.0 21 | 22 | - name: Enable auto-merge for Dependabot PRs 23 | run: gh pr merge --auto --squash "$PR_URL" 24 | 25 | - name: Approve patch and minor updates 26 | if: ${{ steps.dependabot-metadata.outputs.update-type == 'version-update:semver-patch'|| steps.dependabot-metadata.outputs.update-type == 'version-update:semver-minor'}} 27 | run: | 28 | gh pr review "$PR_URL" --approve --body "I'm **approving** this pull request because **it only includes patch or minor updates**." 29 | 30 | - name: Approve major updates of dev dependencies 31 | if: ${{ steps.dependabot-metadata.outputs.update-type == 'version-update:semver-major' && steps.dependabot-metadata.outputs.dependency-type == 'direct:development'}} 32 | run: | 33 | gh pr review "$PR_URL" --approve --body "I'm **approving** this pull request because **it only includes major updates of dev dependencies**." 34 | 35 | - name: Comment on major updates of normal dependencies 36 | if: ${{ steps.dependabot-metadata.outputs.update-type == 'version-update:semver-major' && steps.dependabot-metadata.outputs.dependency-type == 'direct:production'}} 37 | run: | 38 | gh pr comment "$PR_URL" --body "I'm **not approving** this PR because **it includes major updates of normal dependencies**." 39 | gh pr edit "$PR_URL" --add-label "requires-manual-qa" 40 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: CD 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | permissions: 8 | contents: write 9 | id-token: write 10 | attestations: write 11 | 12 | env: 13 | CARGO_INCREMENTAL: 0 14 | CARGO_NET_GIT_FETCH_WITH_CLI: true 15 | CARGO_NET_RETRY: 10 16 | CARGO_TERM_COLOR: always 17 | RUST_BACKTRACE: 1 18 | RUSTUP_MAX_RETRIES: 10 19 | 20 | defaults: 21 | run: 22 | shell: bash 23 | 24 | jobs: 25 | upload-assets: 26 | strategy: 27 | fail-fast: false 28 | matrix: 29 | include: 30 | - { os: macos-latest, target: aarch64-apple-darwin } 31 | - { os: macos-latest, target: universal-apple-darwin } 32 | - { os: macos-latest, target: x86_64-apple-darwin } 33 | - { os: ubuntu-latest, target: aarch64-unknown-linux-gnu } 34 | - { os: ubuntu-latest, target: aarch64-unknown-linux-musl } 35 | # - { os: ubuntu-latest, target: x86_64-unknown-freebsd } # not supported by aws-lc 36 | - { os: ubuntu-latest, target: x86_64-unknown-linux-gnu } 37 | - { os: ubuntu-latest, target: x86_64-unknown-linux-musl } 38 | # - { os: windows-latest, target: aarch64-pc-windows-msvc } # not supported by aws-lc 39 | - { os: windows-latest, target: x86_64-pc-windows-msvc } 40 | 41 | name: Deploy (${{ matrix.target }}) 42 | runs-on: ${{ matrix.os }} 43 | if: github.repository_owner == 'robjtede' && startsWith(github.event.release.name, 'v') 44 | timeout-minutes: 60 45 | 46 | steps: 47 | - uses: actions/checkout@v4 48 | 49 | - name: Install nasm 50 | if: matrix.target.os == 'windows-latest' 51 | uses: ilammy/setup-nasm@v1.5.2 52 | 53 | - name: Install Rust 54 | uses: actions-rust-lang/setup-rust-toolchain@v1.12.0 55 | with: 56 | cache: false 57 | 58 | - uses: taiki-e/setup-cross-toolchain-action@v1.29.1 59 | with: 60 | target: ${{ matrix.target }} 61 | 62 | # TODO: see if this is needed after next deploy 63 | # - if: endsWith(matrix.target, 'windows-msvc') 64 | # run: echo "RUSTFLAGS=${RUSTFLAGS} -C target-feature=+crt-static" >> "${GITHUB_ENV}" 65 | 66 | - name: Build and upload to release 67 | id: upload-release 68 | uses: taiki-e/upload-rust-binary-action@v1.26.0 69 | with: 70 | bin: inspect-cert-chain 71 | target: ${{ matrix.target }} 72 | checksum: sha256 73 | token: ${{ secrets.GITHUB_TOKEN }} 74 | 75 | - name: Generate artifact attestation 76 | uses: actions/attest-build-provenance@v2 77 | with: 78 | subject-path: "${{ steps.upload-release.outputs.archive }}.*" 79 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: { branches: [main] } 5 | 6 | permissions: 7 | contents: write 8 | pull-requests: write 9 | 10 | jobs: 11 | release: 12 | name: Release 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Install Rust 21 | uses: actions-rust-lang/setup-rust-toolchain@v1.12.0 22 | 23 | - name: release-plz 24 | uses: MarcoIeni/release-plz-action@v0.5.107 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # direnv 2 | .direnv 3 | 4 | # x52 toolchain 5 | .toolchain 6 | 7 | # Rust 8 | target/ 9 | -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | overrides: 2 | - files: "*.md" 3 | options: 4 | printWidth: 9999 5 | proseWrap: never 6 | -------------------------------------------------------------------------------- /.release-plz.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | changelog_config = ".git-cliff.toml" 3 | dependencies_update = false 4 | git_release_draft = true 5 | pr_draft = true 6 | pr_labels = ["release"] 7 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | group_imports = "StdExternalCrate" 2 | imports_granularity = "Crate" 3 | use_field_init_shorthand = true 4 | -------------------------------------------------------------------------------- /.taplo.toml: -------------------------------------------------------------------------------- 1 | exclude = ["target/*"] 2 | include = ["**/*.toml"] 3 | 4 | [formatting] 5 | column_width = 100 6 | 7 | [[rule]] 8 | include = ["**/Cargo.toml"] 9 | keys = [ 10 | "dependencies", 11 | "*-dependencies", 12 | "workspace.dependencies", 13 | "workspace.*-dependencies", 14 | "target.*.dependencies", 15 | "target.*.*-dependencies", 16 | ] 17 | formatting.reorder_keys = true 18 | 19 | [[rule]] 20 | include = ["**/Cargo.toml"] 21 | keys = [ 22 | "dependencies.*", 23 | "*-dependencies.*", 24 | "workspace.dependencies.*", 25 | "workspace.*-dependencies.*", 26 | "target.*.dependencies", 27 | "target.*.*-dependencies", 28 | ] 29 | formatting.reorder_keys = false 30 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.0.22 4 | 5 | ### Features 6 | 7 | - Use platform certificate verifier. 8 | 9 | ## 0.0.21 10 | 11 | ### Features 12 | 13 | - Stylize important info in interactive view. 14 | 15 | ## 0.0.20 16 | 17 | ### Features 18 | 19 | - Add `--port` option. 20 | 21 | ## 0.0.19 22 | 23 | ### Features 24 | 25 | - Support ACME identifier extension. 26 | 27 | ## 0.0.18 28 | 29 | ### Fixes 30 | 31 | - Disallow interactive mode when reading from stdin. 32 | 33 | ## 0.0.17 34 | 35 | ### Features 36 | 37 | - Copy public key to clipboard in interactive mode. 38 | - Add page up/down keybinds for interactive mode. 39 | 40 | ## 0.0.16 41 | 42 | ### Features 43 | 44 | - Add interactive mode (`-i` / `--interactive`). 45 | 46 | ## 0.0.15 47 | 48 | ### Changes 49 | 50 | - Update `rustls` dependency to v0.23. 51 | 52 | ## 0.0.14 53 | 54 | ### Fixes 55 | 56 | - Use readable format for SCT timestamps. 57 | 58 | ## 0.0.13 59 | 60 | - No significant changes. 61 | 62 | ## 0.0.12 63 | 64 | ### Changes 65 | 66 | - Update `rustls` dependency to `0.22`. 67 | 68 | ## 0.0.11 69 | 70 | ### Features 71 | 72 | - Add verbose `-v` flag. 73 | - Add `--dump` argument. 74 | 75 | ### Fixes 76 | 77 | - Improve errors when reading from file fails. 78 | 79 | ## 0.0.10 80 | 81 | ### Fixes 82 | 83 | - Improve errors when connecting to remote host fails. 84 | - Improve errors when reading from remote host fails. 85 | - Prevent exiting too early on EoF errors. 86 | 87 | ## 0.0.9 88 | 89 | - No significant changes. 90 | 91 | ## 0.0.8 92 | 93 | - Build binaries for Linux x86. 94 | - Build binaries for Windows. 95 | - Build non-universal binaries for macOS. 96 | 97 | ## 0.0.7 98 | 99 | ### Features 100 | 101 | - Add `--version` flag. 102 | 103 | ## 0.0.6 104 | 105 | - No significant changes. 106 | 107 | ## 0.0.5 108 | 109 | - No significant changes. 110 | 111 | ## 0.0.4 112 | 113 | - No significant changes. 114 | 115 | ## 0.0.3 116 | 117 | - No significant changes. 118 | 119 | ## 0.0.2 120 | 121 | ### Features 122 | 123 | - Add support for local files and stdin. 124 | 125 | ### Fixes 126 | 127 | - Print serial number correctly. 128 | -------------------------------------------------------------------------------- /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 = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "allocator-api2" 31 | version = "0.2.21" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 34 | 35 | [[package]] 36 | name = "android-tzdata" 37 | version = "0.1.1" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 40 | 41 | [[package]] 42 | name = "android_system_properties" 43 | version = "0.1.5" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 46 | dependencies = [ 47 | "libc", 48 | ] 49 | 50 | [[package]] 51 | name = "ansi-to-tui" 52 | version = "7.0.0" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "67555e1f1ece39d737e28c8a017721287753af3f93225e4a445b29ccb0f5912c" 55 | dependencies = [ 56 | "nom", 57 | "ratatui", 58 | "simdutf8", 59 | "smallvec", 60 | "thiserror", 61 | ] 62 | 63 | [[package]] 64 | name = "anstream" 65 | version = "0.6.18" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 68 | dependencies = [ 69 | "anstyle", 70 | "anstyle-parse", 71 | "anstyle-query", 72 | "anstyle-wincon", 73 | "colorchoice", 74 | "is_terminal_polyfill", 75 | "utf8parse", 76 | ] 77 | 78 | [[package]] 79 | name = "anstyle" 80 | version = "1.0.10" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 83 | 84 | [[package]] 85 | name = "anstyle-parse" 86 | version = "0.2.6" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 89 | dependencies = [ 90 | "utf8parse", 91 | ] 92 | 93 | [[package]] 94 | name = "anstyle-query" 95 | version = "1.1.2" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 98 | dependencies = [ 99 | "windows-sys 0.59.0", 100 | ] 101 | 102 | [[package]] 103 | name = "anstyle-wincon" 104 | version = "3.0.7" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" 107 | dependencies = [ 108 | "anstyle", 109 | "once_cell", 110 | "windows-sys 0.59.0", 111 | ] 112 | 113 | [[package]] 114 | name = "arboard" 115 | version = "3.5.0" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "c1df21f715862ede32a0c525ce2ca4d52626bb0007f8c18b87a384503ac33e70" 118 | dependencies = [ 119 | "clipboard-win", 120 | "log", 121 | "objc2", 122 | "objc2-app-kit", 123 | "objc2-foundation", 124 | "parking_lot", 125 | "percent-encoding", 126 | "x11rb", 127 | ] 128 | 129 | [[package]] 130 | name = "autocfg" 131 | version = "1.4.0" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 134 | 135 | [[package]] 136 | name = "aws-lc-rs" 137 | version = "1.12.4" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "4cd755adf9707cf671e31d944a189be3deaaeee11c8bc1d669bb8022ac90fbd0" 140 | dependencies = [ 141 | "aws-lc-sys", 142 | "paste", 143 | "zeroize", 144 | ] 145 | 146 | [[package]] 147 | name = "aws-lc-sys" 148 | version = "0.26.0" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "0f9dd2e03ee80ca2822dd6ea431163d2ef259f2066a4d6ccaca6d9dcb386aa43" 151 | dependencies = [ 152 | "bindgen", 153 | "cc", 154 | "cmake", 155 | "dunce", 156 | "fs_extra", 157 | "paste", 158 | ] 159 | 160 | [[package]] 161 | name = "backtrace" 162 | version = "0.3.71" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" 165 | dependencies = [ 166 | "addr2line", 167 | "cc", 168 | "cfg-if", 169 | "libc", 170 | "miniz_oxide", 171 | "object", 172 | "rustc-demangle", 173 | ] 174 | 175 | [[package]] 176 | name = "base64ct" 177 | version = "1.6.0" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" 180 | 181 | [[package]] 182 | name = "bindgen" 183 | version = "0.69.5" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" 186 | dependencies = [ 187 | "bitflags", 188 | "cexpr", 189 | "clang-sys", 190 | "itertools 0.12.1", 191 | "lazy_static", 192 | "lazycell", 193 | "log", 194 | "prettyplease", 195 | "proc-macro2", 196 | "quote", 197 | "regex", 198 | "rustc-hash", 199 | "shlex", 200 | "syn", 201 | "which", 202 | ] 203 | 204 | [[package]] 205 | name = "bitflags" 206 | version = "2.9.0" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 209 | 210 | [[package]] 211 | name = "bumpalo" 212 | version = "3.17.0" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 215 | 216 | [[package]] 217 | name = "byteorder" 218 | version = "1.5.0" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 221 | 222 | [[package]] 223 | name = "bytes" 224 | version = "1.10.1" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 227 | 228 | [[package]] 229 | name = "camino" 230 | version = "1.1.9" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" 233 | 234 | [[package]] 235 | name = "cassowary" 236 | version = "0.3.0" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" 239 | 240 | [[package]] 241 | name = "castaway" 242 | version = "0.2.3" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" 245 | dependencies = [ 246 | "rustversion", 247 | ] 248 | 249 | [[package]] 250 | name = "cc" 251 | version = "1.2.15" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af" 254 | dependencies = [ 255 | "jobserver", 256 | "libc", 257 | "shlex", 258 | ] 259 | 260 | [[package]] 261 | name = "cesu8" 262 | version = "1.1.0" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" 265 | 266 | [[package]] 267 | name = "cexpr" 268 | version = "0.6.0" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 271 | dependencies = [ 272 | "nom", 273 | ] 274 | 275 | [[package]] 276 | name = "cfg-if" 277 | version = "1.0.0" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 280 | 281 | [[package]] 282 | name = "chrono" 283 | version = "0.4.41" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" 286 | dependencies = [ 287 | "android-tzdata", 288 | "iana-time-zone", 289 | "js-sys", 290 | "num-traits", 291 | "wasm-bindgen", 292 | "windows-link", 293 | ] 294 | 295 | [[package]] 296 | name = "clang-sys" 297 | version = "1.8.1" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" 300 | dependencies = [ 301 | "glob", 302 | "libc", 303 | "libloading", 304 | ] 305 | 306 | [[package]] 307 | name = "clap" 308 | version = "4.5.38" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" 311 | dependencies = [ 312 | "clap_builder", 313 | "clap_derive", 314 | ] 315 | 316 | [[package]] 317 | name = "clap_builder" 318 | version = "4.5.38" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" 321 | dependencies = [ 322 | "anstream", 323 | "anstyle", 324 | "clap_lex", 325 | "strsim", 326 | ] 327 | 328 | [[package]] 329 | name = "clap_derive" 330 | version = "4.5.32" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" 333 | dependencies = [ 334 | "heck", 335 | "proc-macro2", 336 | "quote", 337 | "syn", 338 | ] 339 | 340 | [[package]] 341 | name = "clap_lex" 342 | version = "0.7.4" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 345 | 346 | [[package]] 347 | name = "clipboard-win" 348 | version = "5.4.0" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" 351 | dependencies = [ 352 | "error-code", 353 | ] 354 | 355 | [[package]] 356 | name = "cmake" 357 | version = "0.1.54" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" 360 | dependencies = [ 361 | "cc", 362 | ] 363 | 364 | [[package]] 365 | name = "color-eyre" 366 | version = "0.6.3" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" 369 | dependencies = [ 370 | "backtrace", 371 | "color-spantrace", 372 | "eyre", 373 | "indenter", 374 | "once_cell", 375 | "owo-colors", 376 | "tracing-error", 377 | ] 378 | 379 | [[package]] 380 | name = "color-spantrace" 381 | version = "0.2.1" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" 384 | dependencies = [ 385 | "once_cell", 386 | "owo-colors", 387 | "tracing-core", 388 | "tracing-error", 389 | ] 390 | 391 | [[package]] 392 | name = "colorchoice" 393 | version = "1.0.3" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 396 | 397 | [[package]] 398 | name = "combine" 399 | version = "4.6.7" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" 402 | dependencies = [ 403 | "bytes", 404 | "memchr", 405 | ] 406 | 407 | [[package]] 408 | name = "compact_str" 409 | version = "0.8.1" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" 412 | dependencies = [ 413 | "castaway", 414 | "cfg-if", 415 | "itoa", 416 | "rustversion", 417 | "ryu", 418 | "static_assertions", 419 | ] 420 | 421 | [[package]] 422 | name = "const-oid" 423 | version = "0.9.6" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" 426 | 427 | [[package]] 428 | name = "convert_case" 429 | version = "0.7.1" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" 432 | dependencies = [ 433 | "unicode-segmentation", 434 | ] 435 | 436 | [[package]] 437 | name = "core-foundation" 438 | version = "0.10.0" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" 441 | dependencies = [ 442 | "core-foundation-sys", 443 | "libc", 444 | ] 445 | 446 | [[package]] 447 | name = "core-foundation-sys" 448 | version = "0.8.7" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 451 | 452 | [[package]] 453 | name = "crossterm" 454 | version = "0.28.1" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" 457 | dependencies = [ 458 | "bitflags", 459 | "crossterm_winapi", 460 | "mio", 461 | "parking_lot", 462 | "rustix 0.38.44", 463 | "signal-hook", 464 | "signal-hook-mio", 465 | "winapi", 466 | ] 467 | 468 | [[package]] 469 | name = "crossterm" 470 | version = "0.29.0" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" 473 | dependencies = [ 474 | "bitflags", 475 | "crossterm_winapi", 476 | "derive_more", 477 | "document-features", 478 | "mio", 479 | "parking_lot", 480 | "rustix 1.0.5", 481 | "signal-hook", 482 | "signal-hook-mio", 483 | "winapi", 484 | ] 485 | 486 | [[package]] 487 | name = "crossterm_winapi" 488 | version = "0.9.1" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 491 | dependencies = [ 492 | "winapi", 493 | ] 494 | 495 | [[package]] 496 | name = "darling" 497 | version = "0.20.10" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" 500 | dependencies = [ 501 | "darling_core", 502 | "darling_macro", 503 | ] 504 | 505 | [[package]] 506 | name = "darling_core" 507 | version = "0.20.10" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" 510 | dependencies = [ 511 | "fnv", 512 | "ident_case", 513 | "proc-macro2", 514 | "quote", 515 | "strsim", 516 | "syn", 517 | ] 518 | 519 | [[package]] 520 | name = "darling_macro" 521 | version = "0.20.10" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" 524 | dependencies = [ 525 | "darling_core", 526 | "quote", 527 | "syn", 528 | ] 529 | 530 | [[package]] 531 | name = "der" 532 | version = "0.7.10" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" 535 | dependencies = [ 536 | "const-oid", 537 | "der_derive", 538 | "flagset", 539 | "pem-rfc7468", 540 | "zeroize", 541 | ] 542 | 543 | [[package]] 544 | name = "der_derive" 545 | version = "0.7.3" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" 548 | dependencies = [ 549 | "proc-macro2", 550 | "quote", 551 | "syn", 552 | ] 553 | 554 | [[package]] 555 | name = "derive_more" 556 | version = "2.0.1" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" 559 | dependencies = [ 560 | "derive_more-impl", 561 | ] 562 | 563 | [[package]] 564 | name = "derive_more-impl" 565 | version = "2.0.1" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" 568 | dependencies = [ 569 | "convert_case", 570 | "proc-macro2", 571 | "quote", 572 | "syn", 573 | ] 574 | 575 | [[package]] 576 | name = "document-features" 577 | version = "0.2.11" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" 580 | dependencies = [ 581 | "litrs", 582 | ] 583 | 584 | [[package]] 585 | name = "dunce" 586 | version = "1.0.5" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" 589 | 590 | [[package]] 591 | name = "either" 592 | version = "1.14.0" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d" 595 | 596 | [[package]] 597 | name = "equivalent" 598 | version = "1.0.2" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 601 | 602 | [[package]] 603 | name = "errno" 604 | version = "0.3.10" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" 607 | dependencies = [ 608 | "libc", 609 | "windows-sys 0.59.0", 610 | ] 611 | 612 | [[package]] 613 | name = "error-code" 614 | version = "3.3.1" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" 617 | 618 | [[package]] 619 | name = "error_reporter" 620 | version = "1.0.0" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "31ae425815400e5ed474178a7a22e275a9687086a12ca63ec793ff292d8fdae8" 623 | 624 | [[package]] 625 | name = "eyre" 626 | version = "0.6.12" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" 629 | dependencies = [ 630 | "indenter", 631 | "once_cell", 632 | ] 633 | 634 | [[package]] 635 | name = "flagset" 636 | version = "0.4.6" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "b3ea1ec5f8307826a5b71094dd91fc04d4ae75d5709b20ad351c7fb4815c86ec" 639 | 640 | [[package]] 641 | name = "fnv" 642 | version = "1.0.7" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 645 | 646 | [[package]] 647 | name = "foldhash" 648 | version = "0.1.4" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" 651 | 652 | [[package]] 653 | name = "fs_extra" 654 | version = "1.3.0" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" 657 | 658 | [[package]] 659 | name = "gethostname" 660 | version = "0.4.3" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" 663 | dependencies = [ 664 | "libc", 665 | "windows-targets 0.48.5", 666 | ] 667 | 668 | [[package]] 669 | name = "getrandom" 670 | version = "0.2.15" 671 | source = "registry+https://github.com/rust-lang/crates.io-index" 672 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 673 | dependencies = [ 674 | "cfg-if", 675 | "libc", 676 | "wasi", 677 | ] 678 | 679 | [[package]] 680 | name = "gimli" 681 | version = "0.28.1" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 684 | 685 | [[package]] 686 | name = "glob" 687 | version = "0.3.2" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" 690 | 691 | [[package]] 692 | name = "hashbrown" 693 | version = "0.15.2" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 696 | dependencies = [ 697 | "allocator-api2", 698 | "equivalent", 699 | "foldhash", 700 | ] 701 | 702 | [[package]] 703 | name = "heck" 704 | version = "0.5.0" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 707 | 708 | [[package]] 709 | name = "home" 710 | version = "0.5.11" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" 713 | dependencies = [ 714 | "windows-sys 0.59.0", 715 | ] 716 | 717 | [[package]] 718 | name = "iana-time-zone" 719 | version = "0.1.61" 720 | source = "registry+https://github.com/rust-lang/crates.io-index" 721 | checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" 722 | dependencies = [ 723 | "android_system_properties", 724 | "core-foundation-sys", 725 | "iana-time-zone-haiku", 726 | "js-sys", 727 | "wasm-bindgen", 728 | "windows-core", 729 | ] 730 | 731 | [[package]] 732 | name = "iana-time-zone-haiku" 733 | version = "0.1.2" 734 | source = "registry+https://github.com/rust-lang/crates.io-index" 735 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 736 | dependencies = [ 737 | "cc", 738 | ] 739 | 740 | [[package]] 741 | name = "ident_case" 742 | version = "1.0.1" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 745 | 746 | [[package]] 747 | name = "indenter" 748 | version = "0.3.3" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" 751 | 752 | [[package]] 753 | name = "indoc" 754 | version = "2.0.5" 755 | source = "registry+https://github.com/rust-lang/crates.io-index" 756 | checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" 757 | 758 | [[package]] 759 | name = "inspect-cert-chain" 760 | version = "0.0.22" 761 | dependencies = [ 762 | "ansi-to-tui", 763 | "arboard", 764 | "byteorder", 765 | "camino", 766 | "cfg-if", 767 | "chrono", 768 | "clap", 769 | "color-eyre", 770 | "const-oid", 771 | "crossterm 0.29.0", 772 | "der", 773 | "error_reporter", 774 | "eyre", 775 | "itertools 0.14.0", 776 | "pem-rfc7468", 777 | "pkcs1", 778 | "ratatui", 779 | "rustls", 780 | "rustls-pemfile", 781 | "rustls-pki-types", 782 | "rustls-platform-verifier", 783 | "tracing", 784 | "tracing-subscriber", 785 | "x509-cert", 786 | "yansi", 787 | ] 788 | 789 | [[package]] 790 | name = "instability" 791 | version = "0.3.7" 792 | source = "registry+https://github.com/rust-lang/crates.io-index" 793 | checksum = "0bf9fed6d91cfb734e7476a06bde8300a1b94e217e1b523b6f0cd1a01998c71d" 794 | dependencies = [ 795 | "darling", 796 | "indoc", 797 | "proc-macro2", 798 | "quote", 799 | "syn", 800 | ] 801 | 802 | [[package]] 803 | name = "is_terminal_polyfill" 804 | version = "1.70.1" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 807 | 808 | [[package]] 809 | name = "itertools" 810 | version = "0.12.1" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" 813 | dependencies = [ 814 | "either", 815 | ] 816 | 817 | [[package]] 818 | name = "itertools" 819 | version = "0.13.0" 820 | source = "registry+https://github.com/rust-lang/crates.io-index" 821 | checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" 822 | dependencies = [ 823 | "either", 824 | ] 825 | 826 | [[package]] 827 | name = "itertools" 828 | version = "0.14.0" 829 | source = "registry+https://github.com/rust-lang/crates.io-index" 830 | checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" 831 | dependencies = [ 832 | "either", 833 | ] 834 | 835 | [[package]] 836 | name = "itoa" 837 | version = "1.0.14" 838 | source = "registry+https://github.com/rust-lang/crates.io-index" 839 | checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" 840 | 841 | [[package]] 842 | name = "jni" 843 | version = "0.21.1" 844 | source = "registry+https://github.com/rust-lang/crates.io-index" 845 | checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" 846 | dependencies = [ 847 | "cesu8", 848 | "cfg-if", 849 | "combine", 850 | "jni-sys", 851 | "log", 852 | "thiserror", 853 | "walkdir", 854 | "windows-sys 0.45.0", 855 | ] 856 | 857 | [[package]] 858 | name = "jni-sys" 859 | version = "0.3.0" 860 | source = "registry+https://github.com/rust-lang/crates.io-index" 861 | checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" 862 | 863 | [[package]] 864 | name = "jobserver" 865 | version = "0.1.32" 866 | source = "registry+https://github.com/rust-lang/crates.io-index" 867 | checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" 868 | dependencies = [ 869 | "libc", 870 | ] 871 | 872 | [[package]] 873 | name = "js-sys" 874 | version = "0.3.77" 875 | source = "registry+https://github.com/rust-lang/crates.io-index" 876 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 877 | dependencies = [ 878 | "once_cell", 879 | "wasm-bindgen", 880 | ] 881 | 882 | [[package]] 883 | name = "lazy_static" 884 | version = "1.5.0" 885 | source = "registry+https://github.com/rust-lang/crates.io-index" 886 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 887 | 888 | [[package]] 889 | name = "lazycell" 890 | version = "1.3.0" 891 | source = "registry+https://github.com/rust-lang/crates.io-index" 892 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 893 | 894 | [[package]] 895 | name = "libc" 896 | version = "0.2.170" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" 899 | 900 | [[package]] 901 | name = "libloading" 902 | version = "0.8.6" 903 | source = "registry+https://github.com/rust-lang/crates.io-index" 904 | checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" 905 | dependencies = [ 906 | "cfg-if", 907 | "windows-targets 0.52.6", 908 | ] 909 | 910 | [[package]] 911 | name = "linux-raw-sys" 912 | version = "0.4.15" 913 | source = "registry+https://github.com/rust-lang/crates.io-index" 914 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 915 | 916 | [[package]] 917 | name = "linux-raw-sys" 918 | version = "0.9.3" 919 | source = "registry+https://github.com/rust-lang/crates.io-index" 920 | checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" 921 | 922 | [[package]] 923 | name = "litrs" 924 | version = "0.4.1" 925 | source = "registry+https://github.com/rust-lang/crates.io-index" 926 | checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" 927 | 928 | [[package]] 929 | name = "lock_api" 930 | version = "0.4.12" 931 | source = "registry+https://github.com/rust-lang/crates.io-index" 932 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 933 | dependencies = [ 934 | "autocfg", 935 | "scopeguard", 936 | ] 937 | 938 | [[package]] 939 | name = "log" 940 | version = "0.4.26" 941 | source = "registry+https://github.com/rust-lang/crates.io-index" 942 | checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" 943 | 944 | [[package]] 945 | name = "lru" 946 | version = "0.12.5" 947 | source = "registry+https://github.com/rust-lang/crates.io-index" 948 | checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" 949 | dependencies = [ 950 | "hashbrown", 951 | ] 952 | 953 | [[package]] 954 | name = "memchr" 955 | version = "2.7.4" 956 | source = "registry+https://github.com/rust-lang/crates.io-index" 957 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 958 | 959 | [[package]] 960 | name = "minimal-lexical" 961 | version = "0.2.1" 962 | source = "registry+https://github.com/rust-lang/crates.io-index" 963 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 964 | 965 | [[package]] 966 | name = "miniz_oxide" 967 | version = "0.7.4" 968 | source = "registry+https://github.com/rust-lang/crates.io-index" 969 | checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" 970 | dependencies = [ 971 | "adler", 972 | ] 973 | 974 | [[package]] 975 | name = "mio" 976 | version = "1.0.3" 977 | source = "registry+https://github.com/rust-lang/crates.io-index" 978 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" 979 | dependencies = [ 980 | "libc", 981 | "log", 982 | "wasi", 983 | "windows-sys 0.52.0", 984 | ] 985 | 986 | [[package]] 987 | name = "nom" 988 | version = "7.1.3" 989 | source = "registry+https://github.com/rust-lang/crates.io-index" 990 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 991 | dependencies = [ 992 | "memchr", 993 | "minimal-lexical", 994 | ] 995 | 996 | [[package]] 997 | name = "nu-ansi-term" 998 | version = "0.46.0" 999 | source = "registry+https://github.com/rust-lang/crates.io-index" 1000 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 1001 | dependencies = [ 1002 | "overload", 1003 | "winapi", 1004 | ] 1005 | 1006 | [[package]] 1007 | name = "num-traits" 1008 | version = "0.2.19" 1009 | source = "registry+https://github.com/rust-lang/crates.io-index" 1010 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1011 | dependencies = [ 1012 | "autocfg", 1013 | ] 1014 | 1015 | [[package]] 1016 | name = "objc2" 1017 | version = "0.6.0" 1018 | source = "registry+https://github.com/rust-lang/crates.io-index" 1019 | checksum = "3531f65190d9cff863b77a99857e74c314dd16bf56c538c4b57c7cbc3f3a6e59" 1020 | dependencies = [ 1021 | "objc2-encode", 1022 | ] 1023 | 1024 | [[package]] 1025 | name = "objc2-app-kit" 1026 | version = "0.3.0" 1027 | source = "registry+https://github.com/rust-lang/crates.io-index" 1028 | checksum = "5906f93257178e2f7ae069efb89fbd6ee94f0592740b5f8a1512ca498814d0fb" 1029 | dependencies = [ 1030 | "bitflags", 1031 | "objc2", 1032 | "objc2-core-graphics", 1033 | "objc2-foundation", 1034 | ] 1035 | 1036 | [[package]] 1037 | name = "objc2-core-foundation" 1038 | version = "0.3.0" 1039 | source = "registry+https://github.com/rust-lang/crates.io-index" 1040 | checksum = "daeaf60f25471d26948a1c2f840e3f7d86f4109e3af4e8e4b5cd70c39690d925" 1041 | dependencies = [ 1042 | "bitflags", 1043 | "objc2", 1044 | ] 1045 | 1046 | [[package]] 1047 | name = "objc2-core-graphics" 1048 | version = "0.3.0" 1049 | source = "registry+https://github.com/rust-lang/crates.io-index" 1050 | checksum = "f8dca602628b65356b6513290a21a6405b4d4027b8b250f0b98dddbb28b7de02" 1051 | dependencies = [ 1052 | "bitflags", 1053 | "objc2", 1054 | "objc2-core-foundation", 1055 | "objc2-io-surface", 1056 | ] 1057 | 1058 | [[package]] 1059 | name = "objc2-encode" 1060 | version = "4.1.0" 1061 | source = "registry+https://github.com/rust-lang/crates.io-index" 1062 | checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" 1063 | 1064 | [[package]] 1065 | name = "objc2-foundation" 1066 | version = "0.3.0" 1067 | source = "registry+https://github.com/rust-lang/crates.io-index" 1068 | checksum = "3a21c6c9014b82c39515db5b396f91645182611c97d24637cf56ac01e5f8d998" 1069 | dependencies = [ 1070 | "bitflags", 1071 | "objc2", 1072 | "objc2-core-foundation", 1073 | ] 1074 | 1075 | [[package]] 1076 | name = "objc2-io-surface" 1077 | version = "0.3.0" 1078 | source = "registry+https://github.com/rust-lang/crates.io-index" 1079 | checksum = "161a8b87e32610086e1a7a9e9ec39f84459db7b3a0881c1f16ca5a2605581c19" 1080 | dependencies = [ 1081 | "bitflags", 1082 | "objc2", 1083 | "objc2-core-foundation", 1084 | ] 1085 | 1086 | [[package]] 1087 | name = "object" 1088 | version = "0.32.2" 1089 | source = "registry+https://github.com/rust-lang/crates.io-index" 1090 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 1091 | dependencies = [ 1092 | "memchr", 1093 | ] 1094 | 1095 | [[package]] 1096 | name = "once_cell" 1097 | version = "1.20.3" 1098 | source = "registry+https://github.com/rust-lang/crates.io-index" 1099 | checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" 1100 | 1101 | [[package]] 1102 | name = "openssl-probe" 1103 | version = "0.1.6" 1104 | source = "registry+https://github.com/rust-lang/crates.io-index" 1105 | checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" 1106 | 1107 | [[package]] 1108 | name = "overload" 1109 | version = "0.1.1" 1110 | source = "registry+https://github.com/rust-lang/crates.io-index" 1111 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 1112 | 1113 | [[package]] 1114 | name = "owo-colors" 1115 | version = "3.5.0" 1116 | source = "registry+https://github.com/rust-lang/crates.io-index" 1117 | checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" 1118 | 1119 | [[package]] 1120 | name = "parking_lot" 1121 | version = "0.12.3" 1122 | source = "registry+https://github.com/rust-lang/crates.io-index" 1123 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 1124 | dependencies = [ 1125 | "lock_api", 1126 | "parking_lot_core", 1127 | ] 1128 | 1129 | [[package]] 1130 | name = "parking_lot_core" 1131 | version = "0.9.10" 1132 | source = "registry+https://github.com/rust-lang/crates.io-index" 1133 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 1134 | dependencies = [ 1135 | "cfg-if", 1136 | "libc", 1137 | "redox_syscall", 1138 | "smallvec", 1139 | "windows-targets 0.52.6", 1140 | ] 1141 | 1142 | [[package]] 1143 | name = "paste" 1144 | version = "1.0.15" 1145 | source = "registry+https://github.com/rust-lang/crates.io-index" 1146 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 1147 | 1148 | [[package]] 1149 | name = "pem-rfc7468" 1150 | version = "0.7.0" 1151 | source = "registry+https://github.com/rust-lang/crates.io-index" 1152 | checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" 1153 | dependencies = [ 1154 | "base64ct", 1155 | ] 1156 | 1157 | [[package]] 1158 | name = "percent-encoding" 1159 | version = "2.3.1" 1160 | source = "registry+https://github.com/rust-lang/crates.io-index" 1161 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1162 | 1163 | [[package]] 1164 | name = "pin-project-lite" 1165 | version = "0.2.16" 1166 | source = "registry+https://github.com/rust-lang/crates.io-index" 1167 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 1168 | 1169 | [[package]] 1170 | name = "pkcs1" 1171 | version = "0.7.5" 1172 | source = "registry+https://github.com/rust-lang/crates.io-index" 1173 | checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" 1174 | dependencies = [ 1175 | "der", 1176 | "pkcs8", 1177 | "spki", 1178 | ] 1179 | 1180 | [[package]] 1181 | name = "pkcs8" 1182 | version = "0.10.2" 1183 | source = "registry+https://github.com/rust-lang/crates.io-index" 1184 | checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" 1185 | dependencies = [ 1186 | "der", 1187 | "spki", 1188 | ] 1189 | 1190 | [[package]] 1191 | name = "prettyplease" 1192 | version = "0.2.29" 1193 | source = "registry+https://github.com/rust-lang/crates.io-index" 1194 | checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" 1195 | dependencies = [ 1196 | "proc-macro2", 1197 | "syn", 1198 | ] 1199 | 1200 | [[package]] 1201 | name = "proc-macro2" 1202 | version = "1.0.93" 1203 | source = "registry+https://github.com/rust-lang/crates.io-index" 1204 | checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" 1205 | dependencies = [ 1206 | "unicode-ident", 1207 | ] 1208 | 1209 | [[package]] 1210 | name = "quote" 1211 | version = "1.0.38" 1212 | source = "registry+https://github.com/rust-lang/crates.io-index" 1213 | checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" 1214 | dependencies = [ 1215 | "proc-macro2", 1216 | ] 1217 | 1218 | [[package]] 1219 | name = "ratatui" 1220 | version = "0.29.0" 1221 | source = "registry+https://github.com/rust-lang/crates.io-index" 1222 | checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" 1223 | dependencies = [ 1224 | "bitflags", 1225 | "cassowary", 1226 | "compact_str", 1227 | "crossterm 0.28.1", 1228 | "indoc", 1229 | "instability", 1230 | "itertools 0.13.0", 1231 | "lru", 1232 | "paste", 1233 | "strum", 1234 | "unicode-segmentation", 1235 | "unicode-truncate", 1236 | "unicode-width 0.2.0", 1237 | ] 1238 | 1239 | [[package]] 1240 | name = "redox_syscall" 1241 | version = "0.5.9" 1242 | source = "registry+https://github.com/rust-lang/crates.io-index" 1243 | checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f" 1244 | dependencies = [ 1245 | "bitflags", 1246 | ] 1247 | 1248 | [[package]] 1249 | name = "regex" 1250 | version = "1.11.1" 1251 | source = "registry+https://github.com/rust-lang/crates.io-index" 1252 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 1253 | dependencies = [ 1254 | "aho-corasick", 1255 | "memchr", 1256 | "regex-automata", 1257 | "regex-syntax", 1258 | ] 1259 | 1260 | [[package]] 1261 | name = "regex-automata" 1262 | version = "0.4.9" 1263 | source = "registry+https://github.com/rust-lang/crates.io-index" 1264 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 1265 | dependencies = [ 1266 | "aho-corasick", 1267 | "memchr", 1268 | "regex-syntax", 1269 | ] 1270 | 1271 | [[package]] 1272 | name = "regex-syntax" 1273 | version = "0.8.5" 1274 | source = "registry+https://github.com/rust-lang/crates.io-index" 1275 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 1276 | 1277 | [[package]] 1278 | name = "ring" 1279 | version = "0.17.11" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "da5349ae27d3887ca812fb375b45a4fbb36d8d12d2df394968cd86e35683fe73" 1282 | dependencies = [ 1283 | "cc", 1284 | "cfg-if", 1285 | "getrandom", 1286 | "libc", 1287 | "untrusted", 1288 | "windows-sys 0.52.0", 1289 | ] 1290 | 1291 | [[package]] 1292 | name = "rustc-demangle" 1293 | version = "0.1.24" 1294 | source = "registry+https://github.com/rust-lang/crates.io-index" 1295 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1296 | 1297 | [[package]] 1298 | name = "rustc-hash" 1299 | version = "1.1.0" 1300 | source = "registry+https://github.com/rust-lang/crates.io-index" 1301 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 1302 | 1303 | [[package]] 1304 | name = "rustix" 1305 | version = "0.38.44" 1306 | source = "registry+https://github.com/rust-lang/crates.io-index" 1307 | checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 1308 | dependencies = [ 1309 | "bitflags", 1310 | "errno", 1311 | "libc", 1312 | "linux-raw-sys 0.4.15", 1313 | "windows-sys 0.59.0", 1314 | ] 1315 | 1316 | [[package]] 1317 | name = "rustix" 1318 | version = "1.0.5" 1319 | source = "registry+https://github.com/rust-lang/crates.io-index" 1320 | checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" 1321 | dependencies = [ 1322 | "bitflags", 1323 | "errno", 1324 | "libc", 1325 | "linux-raw-sys 0.9.3", 1326 | "windows-sys 0.59.0", 1327 | ] 1328 | 1329 | [[package]] 1330 | name = "rustls" 1331 | version = "0.23.27" 1332 | source = "registry+https://github.com/rust-lang/crates.io-index" 1333 | checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" 1334 | dependencies = [ 1335 | "aws-lc-rs", 1336 | "log", 1337 | "once_cell", 1338 | "rustls-pki-types", 1339 | "rustls-webpki", 1340 | "subtle", 1341 | "zeroize", 1342 | ] 1343 | 1344 | [[package]] 1345 | name = "rustls-native-certs" 1346 | version = "0.8.1" 1347 | source = "registry+https://github.com/rust-lang/crates.io-index" 1348 | checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" 1349 | dependencies = [ 1350 | "openssl-probe", 1351 | "rustls-pki-types", 1352 | "schannel", 1353 | "security-framework", 1354 | ] 1355 | 1356 | [[package]] 1357 | name = "rustls-pemfile" 1358 | version = "2.2.0" 1359 | source = "registry+https://github.com/rust-lang/crates.io-index" 1360 | checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" 1361 | dependencies = [ 1362 | "rustls-pki-types", 1363 | ] 1364 | 1365 | [[package]] 1366 | name = "rustls-pki-types" 1367 | version = "1.12.0" 1368 | source = "registry+https://github.com/rust-lang/crates.io-index" 1369 | checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" 1370 | dependencies = [ 1371 | "zeroize", 1372 | ] 1373 | 1374 | [[package]] 1375 | name = "rustls-platform-verifier" 1376 | version = "0.5.3" 1377 | source = "registry+https://github.com/rust-lang/crates.io-index" 1378 | checksum = "19787cda76408ec5404443dc8b31795c87cd8fec49762dc75fa727740d34acc1" 1379 | dependencies = [ 1380 | "core-foundation", 1381 | "core-foundation-sys", 1382 | "jni", 1383 | "log", 1384 | "once_cell", 1385 | "rustls", 1386 | "rustls-native-certs", 1387 | "rustls-platform-verifier-android", 1388 | "rustls-webpki", 1389 | "security-framework", 1390 | "security-framework-sys", 1391 | "webpki-root-certs 0.26.11", 1392 | "windows-sys 0.59.0", 1393 | ] 1394 | 1395 | [[package]] 1396 | name = "rustls-platform-verifier-android" 1397 | version = "0.1.1" 1398 | source = "registry+https://github.com/rust-lang/crates.io-index" 1399 | checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" 1400 | 1401 | [[package]] 1402 | name = "rustls-webpki" 1403 | version = "0.103.3" 1404 | source = "registry+https://github.com/rust-lang/crates.io-index" 1405 | checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" 1406 | dependencies = [ 1407 | "aws-lc-rs", 1408 | "ring", 1409 | "rustls-pki-types", 1410 | "untrusted", 1411 | ] 1412 | 1413 | [[package]] 1414 | name = "rustversion" 1415 | version = "1.0.19" 1416 | source = "registry+https://github.com/rust-lang/crates.io-index" 1417 | checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" 1418 | 1419 | [[package]] 1420 | name = "ryu" 1421 | version = "1.0.19" 1422 | source = "registry+https://github.com/rust-lang/crates.io-index" 1423 | checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" 1424 | 1425 | [[package]] 1426 | name = "same-file" 1427 | version = "1.0.6" 1428 | source = "registry+https://github.com/rust-lang/crates.io-index" 1429 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 1430 | dependencies = [ 1431 | "winapi-util", 1432 | ] 1433 | 1434 | [[package]] 1435 | name = "schannel" 1436 | version = "0.1.27" 1437 | source = "registry+https://github.com/rust-lang/crates.io-index" 1438 | checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" 1439 | dependencies = [ 1440 | "windows-sys 0.59.0", 1441 | ] 1442 | 1443 | [[package]] 1444 | name = "scopeguard" 1445 | version = "1.2.0" 1446 | source = "registry+https://github.com/rust-lang/crates.io-index" 1447 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1448 | 1449 | [[package]] 1450 | name = "security-framework" 1451 | version = "3.2.0" 1452 | source = "registry+https://github.com/rust-lang/crates.io-index" 1453 | checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" 1454 | dependencies = [ 1455 | "bitflags", 1456 | "core-foundation", 1457 | "core-foundation-sys", 1458 | "libc", 1459 | "security-framework-sys", 1460 | ] 1461 | 1462 | [[package]] 1463 | name = "security-framework-sys" 1464 | version = "2.14.0" 1465 | source = "registry+https://github.com/rust-lang/crates.io-index" 1466 | checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" 1467 | dependencies = [ 1468 | "core-foundation-sys", 1469 | "libc", 1470 | ] 1471 | 1472 | [[package]] 1473 | name = "sharded-slab" 1474 | version = "0.1.7" 1475 | source = "registry+https://github.com/rust-lang/crates.io-index" 1476 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 1477 | dependencies = [ 1478 | "lazy_static", 1479 | ] 1480 | 1481 | [[package]] 1482 | name = "shlex" 1483 | version = "1.3.0" 1484 | source = "registry+https://github.com/rust-lang/crates.io-index" 1485 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1486 | 1487 | [[package]] 1488 | name = "signal-hook" 1489 | version = "0.3.17" 1490 | source = "registry+https://github.com/rust-lang/crates.io-index" 1491 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 1492 | dependencies = [ 1493 | "libc", 1494 | "signal-hook-registry", 1495 | ] 1496 | 1497 | [[package]] 1498 | name = "signal-hook-mio" 1499 | version = "0.2.4" 1500 | source = "registry+https://github.com/rust-lang/crates.io-index" 1501 | checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" 1502 | dependencies = [ 1503 | "libc", 1504 | "mio", 1505 | "signal-hook", 1506 | ] 1507 | 1508 | [[package]] 1509 | name = "signal-hook-registry" 1510 | version = "1.4.2" 1511 | source = "registry+https://github.com/rust-lang/crates.io-index" 1512 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 1513 | dependencies = [ 1514 | "libc", 1515 | ] 1516 | 1517 | [[package]] 1518 | name = "simdutf8" 1519 | version = "0.1.5" 1520 | source = "registry+https://github.com/rust-lang/crates.io-index" 1521 | checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" 1522 | 1523 | [[package]] 1524 | name = "smallvec" 1525 | version = "1.14.0" 1526 | source = "registry+https://github.com/rust-lang/crates.io-index" 1527 | checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" 1528 | 1529 | [[package]] 1530 | name = "spki" 1531 | version = "0.7.3" 1532 | source = "registry+https://github.com/rust-lang/crates.io-index" 1533 | checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" 1534 | dependencies = [ 1535 | "base64ct", 1536 | "der", 1537 | ] 1538 | 1539 | [[package]] 1540 | name = "static_assertions" 1541 | version = "1.1.0" 1542 | source = "registry+https://github.com/rust-lang/crates.io-index" 1543 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 1544 | 1545 | [[package]] 1546 | name = "strsim" 1547 | version = "0.11.1" 1548 | source = "registry+https://github.com/rust-lang/crates.io-index" 1549 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1550 | 1551 | [[package]] 1552 | name = "strum" 1553 | version = "0.26.3" 1554 | source = "registry+https://github.com/rust-lang/crates.io-index" 1555 | checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" 1556 | dependencies = [ 1557 | "strum_macros", 1558 | ] 1559 | 1560 | [[package]] 1561 | name = "strum_macros" 1562 | version = "0.26.4" 1563 | source = "registry+https://github.com/rust-lang/crates.io-index" 1564 | checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" 1565 | dependencies = [ 1566 | "heck", 1567 | "proc-macro2", 1568 | "quote", 1569 | "rustversion", 1570 | "syn", 1571 | ] 1572 | 1573 | [[package]] 1574 | name = "subtle" 1575 | version = "2.6.1" 1576 | source = "registry+https://github.com/rust-lang/crates.io-index" 1577 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1578 | 1579 | [[package]] 1580 | name = "syn" 1581 | version = "2.0.98" 1582 | source = "registry+https://github.com/rust-lang/crates.io-index" 1583 | checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" 1584 | dependencies = [ 1585 | "proc-macro2", 1586 | "quote", 1587 | "unicode-ident", 1588 | ] 1589 | 1590 | [[package]] 1591 | name = "thiserror" 1592 | version = "1.0.69" 1593 | source = "registry+https://github.com/rust-lang/crates.io-index" 1594 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 1595 | dependencies = [ 1596 | "thiserror-impl", 1597 | ] 1598 | 1599 | [[package]] 1600 | name = "thiserror-impl" 1601 | version = "1.0.69" 1602 | source = "registry+https://github.com/rust-lang/crates.io-index" 1603 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 1604 | dependencies = [ 1605 | "proc-macro2", 1606 | "quote", 1607 | "syn", 1608 | ] 1609 | 1610 | [[package]] 1611 | name = "thread_local" 1612 | version = "1.1.8" 1613 | source = "registry+https://github.com/rust-lang/crates.io-index" 1614 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 1615 | dependencies = [ 1616 | "cfg-if", 1617 | "once_cell", 1618 | ] 1619 | 1620 | [[package]] 1621 | name = "tls_codec" 1622 | version = "0.4.2" 1623 | source = "registry+https://github.com/rust-lang/crates.io-index" 1624 | checksum = "0de2e01245e2bb89d6f05801c564fa27624dbd7b1846859876c7dad82e90bf6b" 1625 | dependencies = [ 1626 | "tls_codec_derive", 1627 | "zeroize", 1628 | ] 1629 | 1630 | [[package]] 1631 | name = "tls_codec_derive" 1632 | version = "0.4.2" 1633 | source = "registry+https://github.com/rust-lang/crates.io-index" 1634 | checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" 1635 | dependencies = [ 1636 | "proc-macro2", 1637 | "quote", 1638 | "syn", 1639 | ] 1640 | 1641 | [[package]] 1642 | name = "tracing" 1643 | version = "0.1.41" 1644 | source = "registry+https://github.com/rust-lang/crates.io-index" 1645 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 1646 | dependencies = [ 1647 | "log", 1648 | "pin-project-lite", 1649 | "tracing-attributes", 1650 | "tracing-core", 1651 | ] 1652 | 1653 | [[package]] 1654 | name = "tracing-attributes" 1655 | version = "0.1.28" 1656 | source = "registry+https://github.com/rust-lang/crates.io-index" 1657 | checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" 1658 | dependencies = [ 1659 | "proc-macro2", 1660 | "quote", 1661 | "syn", 1662 | ] 1663 | 1664 | [[package]] 1665 | name = "tracing-core" 1666 | version = "0.1.33" 1667 | source = "registry+https://github.com/rust-lang/crates.io-index" 1668 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 1669 | dependencies = [ 1670 | "once_cell", 1671 | "valuable", 1672 | ] 1673 | 1674 | [[package]] 1675 | name = "tracing-error" 1676 | version = "0.2.1" 1677 | source = "registry+https://github.com/rust-lang/crates.io-index" 1678 | checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" 1679 | dependencies = [ 1680 | "tracing", 1681 | "tracing-subscriber", 1682 | ] 1683 | 1684 | [[package]] 1685 | name = "tracing-log" 1686 | version = "0.2.0" 1687 | source = "registry+https://github.com/rust-lang/crates.io-index" 1688 | checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 1689 | dependencies = [ 1690 | "log", 1691 | "once_cell", 1692 | "tracing-core", 1693 | ] 1694 | 1695 | [[package]] 1696 | name = "tracing-subscriber" 1697 | version = "0.3.19" 1698 | source = "registry+https://github.com/rust-lang/crates.io-index" 1699 | checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" 1700 | dependencies = [ 1701 | "nu-ansi-term", 1702 | "sharded-slab", 1703 | "smallvec", 1704 | "thread_local", 1705 | "tracing-core", 1706 | "tracing-log", 1707 | ] 1708 | 1709 | [[package]] 1710 | name = "unicode-ident" 1711 | version = "1.0.17" 1712 | source = "registry+https://github.com/rust-lang/crates.io-index" 1713 | checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" 1714 | 1715 | [[package]] 1716 | name = "unicode-segmentation" 1717 | version = "1.12.0" 1718 | source = "registry+https://github.com/rust-lang/crates.io-index" 1719 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 1720 | 1721 | [[package]] 1722 | name = "unicode-truncate" 1723 | version = "1.1.0" 1724 | source = "registry+https://github.com/rust-lang/crates.io-index" 1725 | checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" 1726 | dependencies = [ 1727 | "itertools 0.13.0", 1728 | "unicode-segmentation", 1729 | "unicode-width 0.1.14", 1730 | ] 1731 | 1732 | [[package]] 1733 | name = "unicode-width" 1734 | version = "0.1.14" 1735 | source = "registry+https://github.com/rust-lang/crates.io-index" 1736 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 1737 | 1738 | [[package]] 1739 | name = "unicode-width" 1740 | version = "0.2.0" 1741 | source = "registry+https://github.com/rust-lang/crates.io-index" 1742 | checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" 1743 | 1744 | [[package]] 1745 | name = "untrusted" 1746 | version = "0.9.0" 1747 | source = "registry+https://github.com/rust-lang/crates.io-index" 1748 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 1749 | 1750 | [[package]] 1751 | name = "utf8parse" 1752 | version = "0.2.2" 1753 | source = "registry+https://github.com/rust-lang/crates.io-index" 1754 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1755 | 1756 | [[package]] 1757 | name = "valuable" 1758 | version = "0.1.1" 1759 | source = "registry+https://github.com/rust-lang/crates.io-index" 1760 | checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 1761 | 1762 | [[package]] 1763 | name = "walkdir" 1764 | version = "2.5.0" 1765 | source = "registry+https://github.com/rust-lang/crates.io-index" 1766 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 1767 | dependencies = [ 1768 | "same-file", 1769 | "winapi-util", 1770 | ] 1771 | 1772 | [[package]] 1773 | name = "wasi" 1774 | version = "0.11.0+wasi-snapshot-preview1" 1775 | source = "registry+https://github.com/rust-lang/crates.io-index" 1776 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1777 | 1778 | [[package]] 1779 | name = "wasm-bindgen" 1780 | version = "0.2.100" 1781 | source = "registry+https://github.com/rust-lang/crates.io-index" 1782 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 1783 | dependencies = [ 1784 | "cfg-if", 1785 | "once_cell", 1786 | "rustversion", 1787 | "wasm-bindgen-macro", 1788 | ] 1789 | 1790 | [[package]] 1791 | name = "wasm-bindgen-backend" 1792 | version = "0.2.100" 1793 | source = "registry+https://github.com/rust-lang/crates.io-index" 1794 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 1795 | dependencies = [ 1796 | "bumpalo", 1797 | "log", 1798 | "proc-macro2", 1799 | "quote", 1800 | "syn", 1801 | "wasm-bindgen-shared", 1802 | ] 1803 | 1804 | [[package]] 1805 | name = "wasm-bindgen-macro" 1806 | version = "0.2.100" 1807 | source = "registry+https://github.com/rust-lang/crates.io-index" 1808 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 1809 | dependencies = [ 1810 | "quote", 1811 | "wasm-bindgen-macro-support", 1812 | ] 1813 | 1814 | [[package]] 1815 | name = "wasm-bindgen-macro-support" 1816 | version = "0.2.100" 1817 | source = "registry+https://github.com/rust-lang/crates.io-index" 1818 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 1819 | dependencies = [ 1820 | "proc-macro2", 1821 | "quote", 1822 | "syn", 1823 | "wasm-bindgen-backend", 1824 | "wasm-bindgen-shared", 1825 | ] 1826 | 1827 | [[package]] 1828 | name = "wasm-bindgen-shared" 1829 | version = "0.2.100" 1830 | source = "registry+https://github.com/rust-lang/crates.io-index" 1831 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 1832 | dependencies = [ 1833 | "unicode-ident", 1834 | ] 1835 | 1836 | [[package]] 1837 | name = "webpki-root-certs" 1838 | version = "0.26.11" 1839 | source = "registry+https://github.com/rust-lang/crates.io-index" 1840 | checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e" 1841 | dependencies = [ 1842 | "webpki-root-certs 1.0.0", 1843 | ] 1844 | 1845 | [[package]] 1846 | name = "webpki-root-certs" 1847 | version = "1.0.0" 1848 | source = "registry+https://github.com/rust-lang/crates.io-index" 1849 | checksum = "01a83f7e1a9f8712695c03eabe9ed3fbca0feff0152f33f12593e5a6303cb1a4" 1850 | dependencies = [ 1851 | "rustls-pki-types", 1852 | ] 1853 | 1854 | [[package]] 1855 | name = "which" 1856 | version = "4.4.2" 1857 | source = "registry+https://github.com/rust-lang/crates.io-index" 1858 | checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" 1859 | dependencies = [ 1860 | "either", 1861 | "home", 1862 | "once_cell", 1863 | "rustix 0.38.44", 1864 | ] 1865 | 1866 | [[package]] 1867 | name = "winapi" 1868 | version = "0.3.9" 1869 | source = "registry+https://github.com/rust-lang/crates.io-index" 1870 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1871 | dependencies = [ 1872 | "winapi-i686-pc-windows-gnu", 1873 | "winapi-x86_64-pc-windows-gnu", 1874 | ] 1875 | 1876 | [[package]] 1877 | name = "winapi-i686-pc-windows-gnu" 1878 | version = "0.4.0" 1879 | source = "registry+https://github.com/rust-lang/crates.io-index" 1880 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1881 | 1882 | [[package]] 1883 | name = "winapi-util" 1884 | version = "0.1.9" 1885 | source = "registry+https://github.com/rust-lang/crates.io-index" 1886 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 1887 | dependencies = [ 1888 | "windows-sys 0.59.0", 1889 | ] 1890 | 1891 | [[package]] 1892 | name = "winapi-x86_64-pc-windows-gnu" 1893 | version = "0.4.0" 1894 | source = "registry+https://github.com/rust-lang/crates.io-index" 1895 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1896 | 1897 | [[package]] 1898 | name = "windows-core" 1899 | version = "0.52.0" 1900 | source = "registry+https://github.com/rust-lang/crates.io-index" 1901 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" 1902 | dependencies = [ 1903 | "windows-targets 0.52.6", 1904 | ] 1905 | 1906 | [[package]] 1907 | name = "windows-link" 1908 | version = "0.1.0" 1909 | source = "registry+https://github.com/rust-lang/crates.io-index" 1910 | checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" 1911 | 1912 | [[package]] 1913 | name = "windows-sys" 1914 | version = "0.45.0" 1915 | source = "registry+https://github.com/rust-lang/crates.io-index" 1916 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 1917 | dependencies = [ 1918 | "windows-targets 0.42.2", 1919 | ] 1920 | 1921 | [[package]] 1922 | name = "windows-sys" 1923 | version = "0.52.0" 1924 | source = "registry+https://github.com/rust-lang/crates.io-index" 1925 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1926 | dependencies = [ 1927 | "windows-targets 0.52.6", 1928 | ] 1929 | 1930 | [[package]] 1931 | name = "windows-sys" 1932 | version = "0.59.0" 1933 | source = "registry+https://github.com/rust-lang/crates.io-index" 1934 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1935 | dependencies = [ 1936 | "windows-targets 0.52.6", 1937 | ] 1938 | 1939 | [[package]] 1940 | name = "windows-targets" 1941 | version = "0.42.2" 1942 | source = "registry+https://github.com/rust-lang/crates.io-index" 1943 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 1944 | dependencies = [ 1945 | "windows_aarch64_gnullvm 0.42.2", 1946 | "windows_aarch64_msvc 0.42.2", 1947 | "windows_i686_gnu 0.42.2", 1948 | "windows_i686_msvc 0.42.2", 1949 | "windows_x86_64_gnu 0.42.2", 1950 | "windows_x86_64_gnullvm 0.42.2", 1951 | "windows_x86_64_msvc 0.42.2", 1952 | ] 1953 | 1954 | [[package]] 1955 | name = "windows-targets" 1956 | version = "0.48.5" 1957 | source = "registry+https://github.com/rust-lang/crates.io-index" 1958 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1959 | dependencies = [ 1960 | "windows_aarch64_gnullvm 0.48.5", 1961 | "windows_aarch64_msvc 0.48.5", 1962 | "windows_i686_gnu 0.48.5", 1963 | "windows_i686_msvc 0.48.5", 1964 | "windows_x86_64_gnu 0.48.5", 1965 | "windows_x86_64_gnullvm 0.48.5", 1966 | "windows_x86_64_msvc 0.48.5", 1967 | ] 1968 | 1969 | [[package]] 1970 | name = "windows-targets" 1971 | version = "0.52.6" 1972 | source = "registry+https://github.com/rust-lang/crates.io-index" 1973 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1974 | dependencies = [ 1975 | "windows_aarch64_gnullvm 0.52.6", 1976 | "windows_aarch64_msvc 0.52.6", 1977 | "windows_i686_gnu 0.52.6", 1978 | "windows_i686_gnullvm", 1979 | "windows_i686_msvc 0.52.6", 1980 | "windows_x86_64_gnu 0.52.6", 1981 | "windows_x86_64_gnullvm 0.52.6", 1982 | "windows_x86_64_msvc 0.52.6", 1983 | ] 1984 | 1985 | [[package]] 1986 | name = "windows_aarch64_gnullvm" 1987 | version = "0.42.2" 1988 | source = "registry+https://github.com/rust-lang/crates.io-index" 1989 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 1990 | 1991 | [[package]] 1992 | name = "windows_aarch64_gnullvm" 1993 | version = "0.48.5" 1994 | source = "registry+https://github.com/rust-lang/crates.io-index" 1995 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1996 | 1997 | [[package]] 1998 | name = "windows_aarch64_gnullvm" 1999 | version = "0.52.6" 2000 | source = "registry+https://github.com/rust-lang/crates.io-index" 2001 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2002 | 2003 | [[package]] 2004 | name = "windows_aarch64_msvc" 2005 | version = "0.42.2" 2006 | source = "registry+https://github.com/rust-lang/crates.io-index" 2007 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 2008 | 2009 | [[package]] 2010 | name = "windows_aarch64_msvc" 2011 | version = "0.48.5" 2012 | source = "registry+https://github.com/rust-lang/crates.io-index" 2013 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 2014 | 2015 | [[package]] 2016 | name = "windows_aarch64_msvc" 2017 | version = "0.52.6" 2018 | source = "registry+https://github.com/rust-lang/crates.io-index" 2019 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2020 | 2021 | [[package]] 2022 | name = "windows_i686_gnu" 2023 | version = "0.42.2" 2024 | source = "registry+https://github.com/rust-lang/crates.io-index" 2025 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 2026 | 2027 | [[package]] 2028 | name = "windows_i686_gnu" 2029 | version = "0.48.5" 2030 | source = "registry+https://github.com/rust-lang/crates.io-index" 2031 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 2032 | 2033 | [[package]] 2034 | name = "windows_i686_gnu" 2035 | version = "0.52.6" 2036 | source = "registry+https://github.com/rust-lang/crates.io-index" 2037 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2038 | 2039 | [[package]] 2040 | name = "windows_i686_gnullvm" 2041 | version = "0.52.6" 2042 | source = "registry+https://github.com/rust-lang/crates.io-index" 2043 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2044 | 2045 | [[package]] 2046 | name = "windows_i686_msvc" 2047 | version = "0.42.2" 2048 | source = "registry+https://github.com/rust-lang/crates.io-index" 2049 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 2050 | 2051 | [[package]] 2052 | name = "windows_i686_msvc" 2053 | version = "0.48.5" 2054 | source = "registry+https://github.com/rust-lang/crates.io-index" 2055 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 2056 | 2057 | [[package]] 2058 | name = "windows_i686_msvc" 2059 | version = "0.52.6" 2060 | source = "registry+https://github.com/rust-lang/crates.io-index" 2061 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2062 | 2063 | [[package]] 2064 | name = "windows_x86_64_gnu" 2065 | version = "0.42.2" 2066 | source = "registry+https://github.com/rust-lang/crates.io-index" 2067 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 2068 | 2069 | [[package]] 2070 | name = "windows_x86_64_gnu" 2071 | version = "0.48.5" 2072 | source = "registry+https://github.com/rust-lang/crates.io-index" 2073 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 2074 | 2075 | [[package]] 2076 | name = "windows_x86_64_gnu" 2077 | version = "0.52.6" 2078 | source = "registry+https://github.com/rust-lang/crates.io-index" 2079 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2080 | 2081 | [[package]] 2082 | name = "windows_x86_64_gnullvm" 2083 | version = "0.42.2" 2084 | source = "registry+https://github.com/rust-lang/crates.io-index" 2085 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 2086 | 2087 | [[package]] 2088 | name = "windows_x86_64_gnullvm" 2089 | version = "0.48.5" 2090 | source = "registry+https://github.com/rust-lang/crates.io-index" 2091 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 2092 | 2093 | [[package]] 2094 | name = "windows_x86_64_gnullvm" 2095 | version = "0.52.6" 2096 | source = "registry+https://github.com/rust-lang/crates.io-index" 2097 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2098 | 2099 | [[package]] 2100 | name = "windows_x86_64_msvc" 2101 | version = "0.42.2" 2102 | source = "registry+https://github.com/rust-lang/crates.io-index" 2103 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 2104 | 2105 | [[package]] 2106 | name = "windows_x86_64_msvc" 2107 | version = "0.48.5" 2108 | source = "registry+https://github.com/rust-lang/crates.io-index" 2109 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 2110 | 2111 | [[package]] 2112 | name = "windows_x86_64_msvc" 2113 | version = "0.52.6" 2114 | source = "registry+https://github.com/rust-lang/crates.io-index" 2115 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2116 | 2117 | [[package]] 2118 | name = "x11rb" 2119 | version = "0.13.1" 2120 | source = "registry+https://github.com/rust-lang/crates.io-index" 2121 | checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" 2122 | dependencies = [ 2123 | "gethostname", 2124 | "rustix 0.38.44", 2125 | "x11rb-protocol", 2126 | ] 2127 | 2128 | [[package]] 2129 | name = "x11rb-protocol" 2130 | version = "0.13.1" 2131 | source = "registry+https://github.com/rust-lang/crates.io-index" 2132 | checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" 2133 | 2134 | [[package]] 2135 | name = "x509-cert" 2136 | version = "0.2.5" 2137 | source = "registry+https://github.com/rust-lang/crates.io-index" 2138 | checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94" 2139 | dependencies = [ 2140 | "const-oid", 2141 | "der", 2142 | "spki", 2143 | "tls_codec", 2144 | ] 2145 | 2146 | [[package]] 2147 | name = "yansi" 2148 | version = "1.0.1" 2149 | source = "registry+https://github.com/rust-lang/crates.io-index" 2150 | checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" 2151 | 2152 | [[package]] 2153 | name = "zeroize" 2154 | version = "1.8.1" 2155 | source = "registry+https://github.com/rust-lang/crates.io-index" 2156 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 2157 | dependencies = [ 2158 | "zeroize_derive", 2159 | ] 2160 | 2161 | [[package]] 2162 | name = "zeroize_derive" 2163 | version = "1.4.2" 2164 | source = "registry+https://github.com/rust-lang/crates.io-index" 2165 | checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" 2166 | dependencies = [ 2167 | "proc-macro2", 2168 | "quote", 2169 | "syn", 2170 | ] 2171 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "inspect-cert-chain" 3 | version = "0.0.22" 4 | description = "OpenSSL-like text output for debugging certificate chains" 5 | authors = ["Rob Ede "] 6 | keywords = ["inspect", "cert", "chain", "openssl", "x509"] 7 | repository = "https://github.com/robjtede/inspect-cert-chain" 8 | license = "MIT OR Apache-2.0" 9 | edition = "2021" 10 | rust-version = "1.70" 11 | 12 | [dependencies] 13 | ansi-to-tui = "7" 14 | arboard = { version = "3", default-features = false } 15 | byteorder = "1" 16 | camino = "1" 17 | cfg-if = "1" 18 | chrono = "0.4.41" 19 | clap = { version = "4", features = ["derive"] } 20 | color-eyre = "0.6" 21 | const-oid = { version = "0.9.6", features = ["std", "db"] } 22 | crossterm = "0.29" 23 | der = { version = "0.7", features = ["std"] } 24 | error_reporter = "1" 25 | eyre = "0.6" 26 | itertools = "0.14" 27 | pem-rfc7468 = { version = "0.7", features = ["std"] } 28 | pkcs1 = { version = "0.7", features = ["std"] } 29 | ratatui = "0.29" 30 | rustls = "0.23" 31 | rustls-pemfile = "2" 32 | rustls-pki-types = "1" 33 | rustls-platform-verifier = "0.5" 34 | tracing = { version = "0.1.41", features = ["log", "release_max_level_debug"] } 35 | tracing-subscriber = "0.3" 36 | x509-cert = { version = "0.2.5", features = ["sct"] } 37 | yansi = "1" 38 | 39 | [profile.release] 40 | lto = true 41 | codegen-units = 1 42 | 43 | [lints.rust] 44 | future-incompatible = "deny" 45 | nonstandard-style = "deny" 46 | rust-2018-idioms = "deny" 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `inspect-cert-chain` 2 | 3 | > Inspect and debug TLS certificate chains (without OpenSSL) 4 | 5 | [![asciicast](https://asciinema.org/a/657965.svg)](https://asciinema.org/a/657965) 6 | 7 | # Install 8 | 9 | With [`Homebrew`]: 10 | 11 | ```console 12 | $ brew install x52dev/tap/inspect-cert-chain 13 | ``` 14 | 15 | With [`cargo-binstall`]: 16 | 17 | ```console 18 | $ cargo binstall inspect-cert-chain 19 | ``` 20 | 21 | From source: 22 | 23 | ```console 24 | $ cargo install inspect-cert-chain 25 | ``` 26 | 27 | # Usage 28 | 29 | From remote host: 30 | 31 | ```console 32 | inspect-cert-chain --host 33 | ``` 34 | 35 | From chain file: 36 | 37 | ```console 38 | inspect-cert-chain --file 39 | ``` 40 | 41 | From stdin: 42 | 43 | ```console 44 | cat | inspect-cert-chain --file - 45 | ``` 46 | 47 | # Roadmap 48 | 49 | - [x] OpenSSL-like text info. 50 | - [x] Fetch certificate chain from remote host. 51 | - [x] Read certificate chain from file and stdin. 52 | - [x] Interpret standard X.509 extensions. 53 | - [x] Option to read local chain files. 54 | - [ ] Determine chain validity. 55 | 56 | [`homebrew`]: https://brew.sh 57 | [`cargo-binstall`]: https://github.com/cargo-bins/cargo-binstall 58 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-parts": { 4 | "inputs": { 5 | "nixpkgs-lib": "nixpkgs-lib" 6 | }, 7 | "locked": { 8 | "lastModified": 1738453229, 9 | "narHash": "sha256-7H9XgNiGLKN1G1CgRh0vUL4AheZSYzPm+zmZ7vxbJdo=", 10 | "owner": "hercules-ci", 11 | "repo": "flake-parts", 12 | "rev": "32ea77a06711b758da0ad9bd6a844c5740a87abd", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "hercules-ci", 17 | "repo": "flake-parts", 18 | "type": "github" 19 | } 20 | }, 21 | "naersk": { 22 | "inputs": { 23 | "nixpkgs": [ 24 | "nixpkgs" 25 | ] 26 | }, 27 | "locked": { 28 | "lastModified": 1718727675, 29 | "narHash": "sha256-uFsCwWYI2pUpt0awahSBorDUrUfBhaAiyz+BPTS2MHk=", 30 | "owner": "nix-community", 31 | "repo": "naersk", 32 | "rev": "941ce6dc38762a7cfb90b5add223d584feed299b", 33 | "type": "github" 34 | }, 35 | "original": { 36 | "owner": "nix-community", 37 | "repo": "naersk", 38 | "type": "github" 39 | } 40 | }, 41 | "nixpkgs": { 42 | "locked": { 43 | "lastModified": 1740339700, 44 | "narHash": "sha256-cbrw7EgQhcdFnu6iS3vane53bEagZQy/xyIkDWpCgVE=", 45 | "owner": "NixOS", 46 | "repo": "nixpkgs", 47 | "rev": "04ef94c4c1582fd485bbfdb8c4a8ba250e359195", 48 | "type": "github" 49 | }, 50 | "original": { 51 | "owner": "NixOS", 52 | "ref": "nixos-24.11", 53 | "repo": "nixpkgs", 54 | "type": "github" 55 | } 56 | }, 57 | "nixpkgs-lib": { 58 | "locked": { 59 | "lastModified": 1740337157, 60 | "narHash": "sha256-vJzFZGaCpnmo7I6i416HaBLpC+hvcURh/BQwROcGIp8=", 61 | "type": "tarball", 62 | "url": "https://github.com/NixOS/nixpkgs/archive/072a6db25e947df2f31aab9eccd0ab75d5b2da11.tar.gz" 63 | }, 64 | "original": { 65 | "type": "tarball", 66 | "url": "https://github.com/NixOS/nixpkgs/archive/072a6db25e947df2f31aab9eccd0ab75d5b2da11.tar.gz" 67 | } 68 | }, 69 | "root": { 70 | "inputs": { 71 | "flake-parts": "flake-parts", 72 | "naersk": "naersk", 73 | "nixpkgs": "nixpkgs", 74 | "x52": "x52" 75 | } 76 | }, 77 | "x52": { 78 | "inputs": { 79 | "flake-parts": [ 80 | "flake-parts" 81 | ], 82 | "nixpkgs": [ 83 | "nixpkgs" 84 | ] 85 | }, 86 | "locked": { 87 | "lastModified": 1716077176, 88 | "narHash": "sha256-CJLStpZHEVZ+fgXG3H61R97KzQePkdNXpnhkDBtHUec=", 89 | "owner": "x52dev", 90 | "repo": "nix", 91 | "rev": "4afdfa74f1bef9a7e2e99498c47a25b0753a43e0", 92 | "type": "github" 93 | }, 94 | "original": { 95 | "owner": "x52dev", 96 | "repo": "nix", 97 | "type": "github" 98 | } 99 | } 100 | }, 101 | "root": "root", 102 | "version": 7 103 | } 104 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; 4 | flake-parts.url = "github:hercules-ci/flake-parts"; 5 | x52 = { 6 | url = "github:x52dev/nix"; 7 | inputs.nixpkgs.follows = "nixpkgs"; 8 | inputs.flake-parts.follows = "flake-parts"; 9 | }; 10 | naersk = { 11 | url = "github:nix-community/naersk"; 12 | inputs.nixpkgs.follows = "nixpkgs"; 13 | }; 14 | }; 15 | 16 | outputs = inputs@{ flake-parts, naersk, ... }: 17 | flake-parts.lib.mkFlake { inherit inputs; } { 18 | systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; 19 | perSystem = { pkgs, config, inputs', system, lib, ... }: 20 | let 21 | x52just = inputs'.x52.packages.x52-just; 22 | naersk' = pkgs.callPackage naersk { }; 23 | macSdk = [ ] ++ lib.optional pkgs.stdenv.isDarwin [ 24 | pkgs.pkgsBuildHost.libiconv 25 | pkgs.pkgsBuildHost.darwin.apple_sdk.frameworks.AppKit 26 | pkgs.pkgsBuildHost.darwin.apple_sdk.frameworks.Security 27 | pkgs.pkgsBuildHost.darwin.apple_sdk.frameworks.CoreFoundation 28 | pkgs.pkgsBuildHost.darwin.apple_sdk.frameworks.SystemConfiguration 29 | ]; 30 | in 31 | rec 32 | { 33 | formatter = pkgs.nixpkgs-fmt; 34 | 35 | packages.default = naersk'.buildPackage { 36 | src = ./.; 37 | buildInputs = macSdk; 38 | doCheck = true; 39 | }; 40 | 41 | devShells.default = pkgs.mkShell { 42 | buildInputs = [ x52just ]; 43 | 44 | packages = [ 45 | config.formatter 46 | pkgs.just 47 | pkgs.nodePackages.prettier 48 | pkgs.taplo 49 | pkgs.watchexec 50 | ] ++ macSdk; 51 | 52 | shellHook = '' 53 | mkdir -p .toolchain 54 | cp --update=older ${x52just}/*.just .toolchain/ 55 | ''; 56 | }; 57 | }; 58 | }; 59 | } 60 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | _list: 2 | @just --list 3 | 4 | # Lint workspace. 5 | clippy: 6 | cargo clippy --workspace --no-default-features 7 | cargo clippy --workspace --all-features 8 | cargo hack --feature-powerset --depth=3 clippy --workspace 9 | 10 | # Lint workspace and watch for changes. 11 | clippy-watch: 12 | cargo watch -- cargo clippy --workspace --all-features 13 | 14 | # Apply possible linting fixes in the workspace. 15 | clippy-fix *args: 16 | cargo clippy --workspace --all-features --fix {{ args }} 17 | cargo +nightly fmt 18 | 19 | # Test workspace. 20 | test: 21 | cargo nextest run --workspace --all-features --no-tests=pass 22 | 23 | # Document workspace. 24 | doc *args: 25 | RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc --no-deps --workspace --all-features {{ args }} 26 | 27 | # Document workspace and watch for changes. 28 | doc-watch: (doc "--open") 29 | cargo watch -- RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc --no-deps --workspace --all-features 30 | 31 | # Check project formatting. 32 | check: 33 | just --unstable --fmt --check 34 | nixpkgs-fmt . 35 | fd --hidden --extension=md --extension=yml --exec-batch prettier --check 36 | fd --hidden --extension=toml --exec-batch taplo format --check 37 | fd --hidden --extension=toml --exec-batch taplo lint 38 | cargo +nightly fmt -- --check 39 | cargo clippy --workspace --all-targets -- -D warnings 40 | 41 | # Format project. 42 | fmt: 43 | just --unstable --fmt 44 | nixpkgs-fmt . 45 | fd --hidden --extension=md --extension=yml --exec-batch prettier --write 46 | fd --hidden --extension=toml --exec-batch taplo format 47 | cargo +nightly fmt 48 | -------------------------------------------------------------------------------- /roots/Autoridad.crt.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIF3DCCA8SgAwIBAgIIUzAV4JqeuGYwDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UE 3 | BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h 4 | cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0wOTA4MjUxMDA1NTRaFw0zMDA2 5 | MTYxMDA1NTRaMIIBFjELMAkGA1UEBhMCRVMxJzAlBgkqhkiG9w0BCQEWGGNhMUBm 6 | aXJtYXByb2Zlc2lvbmFsLmNvbTEiMCAGA1UEBxMZQy8gTXVudGFuZXIgMjQ0IEJh 7 | cmNlbG9uYTExMC8GA1UECxMoQ29uc3VsdGUgaHR0cDovL3d3dy5maXJtYXByb2Zl 8 | c2lvbmFsLmNvbTE0MDIGA1UECxMrSmVyYXJxdWlhIGRlIENlcnRpZmljYWNpb24g 9 | RmlybWFwcm9mZXNpb25hbDEtMCsGA1UEChMkRmlybWFwcm9mZXNpb25hbCBTLkEu 10 | IE5JRiBBLTYyNjM0MDY4MSIwIAYDVQQDExlBQyBGaXJtYXByb2Zlc2lvbmFsIC0g 11 | Q0ExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx6ueMJNjb6i6tdxq 12 | 8ak6BWbjlZ23jEFapQyGdYLolQAY94yiPaX/i3XEJEqanwqlfG63okc/PWRl/le8 13 | Q6QhJVTNR4yOaVxbGFvF9nIk9T7425YChgqoru0T/o8iU6ItZJ8byZSHiuZnycOw 14 | EFVZ6eQX9CyvJ/vNhCMW1iXTuQKLckU3CSSAPHKYEE0m1FMhbArWazbI0eFk8/9V 15 | HFf9/ZyyyaThZEyE+OFs2i8r7fADsd7wzeFxSjajVouFOnOtu0YUV6vpl4hCcNqs 16 | N4A5TupRCftPx9l5r4dOguCJLNBq6JEPejb73sFU3HFviXTcVpVhXUxpLQzOf6xN 17 | ZD0bUwIDAQABo4HwMIHtMBIGA1UdEwEB/wQIMAYBAf8CAQAwHwYDVR0jBBgwFoAU 18 | Zc3rqzUeAD5+1XTAHLRzRw4aZC8wOwYDVR0fBDQwMjAwoC6gLIYqaHR0cDovL2Ny 19 | bC5maXJtYXByb2Zlc2lvbmFsLmNvbS9mcHJvb3QuY3JsMA4GA1UdDwEB/wQEAwIB 20 | BjAdBgNVHQ4EFgQUi1BXWsG9kgCmt+WFXcPdtrWGkNIwSgYDVR0gBEMwQTA/Bgor 21 | BgEEAYphCgoBMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNp 22 | b25hbC5jb20vY3BzMA0GCSqGSIb3DQEBBQUAA4ICAQBVflkYOaplyB9sBjEEfyUz 23 | pFFUdI3xVJTBPLLBPz9t/OwfK7Xzfanm/QLQTID5gcOUpPaSUFrh98al+RUSLDHC 24 | wKwzniwjQpZs2ggp+n8XNSVeX+oSS+okj4ovOT7JbY0IY9XVt3rhffCvppptEqqP 25 | aSqbo6Cq08vZ+lgGRXmekdvT3L44NUQtgfjnF9lUoXoZXiCMPjM0rHzZTFBv0Itw 26 | Xjhn6TdZoOUHZJN9LROctr0FuRzOjVfIvInuq+zIqXfzqsLXM8i3ONva+nlmDC/a 27 | 2sqkCi2oyAa4OvRAaS4YO6c1PPe2A+JHDuIRjT1u7IDZBQqS1vWsu4r1SnOsjkCt 28 | nRzw3ISZXCcP27JpNqyJVti6JwQ0DFetMtalhXDeqRvuTyIdHes3VWDpBw6SRTcc 29 | mI6Yz2ZiL2k27fjo4EwoyihMkSXtFkBiTQWym0FoWBZFh436GQlygn5BczHlTHRl 30 | vKZ0fx4NcIzr2pYVFqnTSzKFA6a6R30sv7Q2U05GlIsYOpnoraKSahZOIKP0uIG/ 31 | e/6JF0j7kyVDl1SvwnSNXY/LM3slsR+c2s+kacIXXO/szON8aszjT5h67sRrl/Su 32 | T4SfpWh6pJMD9OLmvtXftJcXEzTMM4b/TyKqPBkzgJd5HPgsVlTJt33u4LYOvS0r 33 | b/RBIGXsRG3pC4ihftt6OQ== 34 | -----END CERTIFICATE----- 35 | -------------------------------------------------------------------------------- /roots/DigiCertTLSECCP384RootG5.crt.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQsw 3 | CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURp 4 | Z2lDZXJ0IFRMUyBFQ0MgUDM4NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2 5 | MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ 6 | bmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQgUm9vdCBHNTB2MBAG 7 | ByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1TzvdlHJS 8 | 7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp 9 | 0zVozptjn4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICIS 10 | B4CIfBFqMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49 11 | BAMDA2gAMGUCMQCJao1H5+z8blUD2WdsJk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQ 12 | LgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIxAJSdYsiJvRmEFOml+wG4 13 | DXZDjC5Ty3zfDBeWUA== 14 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /roots/affirmtrust_commercial.crt.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE 3 | BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz 4 | dCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL 5 | MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp 6 | cm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC 7 | AQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6EqdbDuKP 8 | Hx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yr 9 | ba0F8PrVC8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPAL 10 | MeIrJmqbTFeurCA+ukV6BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1 11 | yHp52UKqK39c/s4mT6NmgTWvRLpUHhwwMmWd5jyTXlBOeuM61G7MGvv50jeuJCqr 12 | VwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNVHQ4EFgQUnZPGU4teyq8/ 13 | nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ 14 | KoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYG 15 | XUPGhi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNj 16 | vbz4YYCanrHOQnDiqX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivt 17 | Z8SOyUOyXGsViQK8YvxO8rUzqrJv0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9g 18 | N53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0khsUlHRUe072o0EclNmsxZt9YC 19 | nlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /roots/ietf-org-chain.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEWjCCA0KgAwIBAgISBL4S5MCGHctzBTBxXdlK16WAMA0GCSqGSIb3DQEBCwUA 3 | MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD 4 | EwJSMzAeFw0yMzAzMTYyMzU2MjRaFw0yMzA2MTQyMzU2MjNaMBUxEzARBgNVBAMM 5 | CiouaWV0Zi5vcmcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARlDs01vA6mN7df 6 | 1LItzFxSFi7E0sC0E4RK58A7bUrrSurev5n21DMZA17AuNqPz39eTPqt2/sL6A+E 7 | pB4tjGNCo4ICUDCCAkwwDgYDVR0PAQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUF 8 | BwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBQLwJaDhnP5qXVt 9 | oGqD+ncgeBR0xjAfBgNVHSMEGDAWgBQULrMXt1hWy65QCUDmH6+dixTCxjBVBggr 10 | BgEFBQcBAQRJMEcwIQYIKwYBBQUHMAGGFWh0dHA6Ly9yMy5vLmxlbmNyLm9yZzAi 11 | BggrBgEFBQcwAoYWaHR0cDovL3IzLmkubGVuY3Iub3JnLzAfBgNVHREEGDAWggoq 12 | LmlldGYub3JngghpZXRmLm9yZzBMBgNVHSAERTBDMAgGBmeBDAECATA3BgsrBgEE 13 | AYLfEwEBATAoMCYGCCsGAQUFBwIBFhpodHRwOi8vY3BzLmxldHNlbmNyeXB0Lm9y 14 | ZzCCAQUGCisGAQQB1nkCBAIEgfYEgfMA8QB3ALc++yTfnE26dfI5xbpY9Gxd/ELP 15 | ep81xJ4dCYEl7bSZAAABhu0RYuMAAAQDAEgwRgIhAIcgmC3XMWAzv4ly5vjLWT8W 16 | PyjMRZ2gOUNwZevxvu9WAiEA3p/rlCUAGAb1FnihOYHIGcKAbWZsjn/w+ZfP1sWe 17 | GAAAdgDoPtDaPvUGNTLnVyi8iWvJA9PL0RFr7Otp4Xd9bQa9bgAAAYbtEWLtAAAE 18 | AwBHMEUCIA+peqYnTDFVPAdtci8pytAXyAxrPiurBpz0hCFJBrZJAiEAqmrMMYlw 19 | sY59e6/7rlzU1Qg5cYQCvoMYQmwRp/zLF7swDQYJKoZIhvcNAQELBQADggEBAC6g 20 | q5C21Rk3lff3ZgZb5shaDgFFXTt6LoBeABaVI/hHLuLksTx1Sp5dgAsuNUzD7Y5a 21 | iwa5OpUF6tBka888bDvU7coRHKjmaGxbZJzDBYfFSYs2XUWmIa03kHNt+lPtZdJ4 22 | 9wjbNwdrrl66qff/HiYXoeKH79+EIN9gdasDausBs1Y1IBsDDDc1nEAYDDYECLm6 23 | z2hiNhlPABqrWtyPB2W1MwPiHJN8QSlr5NUHxBqhAeEYIMwIphe6ZaZ825hERuBj 24 | TD+Q69IeUsarjS2Cj6R7LuTuCZ9cMGInuPDnNVm4WHVRUVwINd85JOvrru0hP2P/ 25 | lB/yA4GmETc7lsNjWBA= 26 | -----END CERTIFICATE----- 27 | -----BEGIN CERTIFICATE----- 28 | MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw 29 | TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh 30 | cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw 31 | WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg 32 | RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK 33 | AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP 34 | R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx 35 | sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm 36 | NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg 37 | Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG 38 | /kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC 39 | AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB 40 | Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA 41 | FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw 42 | AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw 43 | Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB 44 | gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W 45 | PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl 46 | ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz 47 | CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm 48 | lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4 49 | avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2 50 | yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O 51 | yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids 52 | hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+ 53 | HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv 54 | MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX 55 | nLRbwHOoq7hHwg== 56 | -----END CERTIFICATE----- 57 | -----BEGIN CERTIFICATE----- 58 | MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw 59 | TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh 60 | cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 61 | WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu 62 | ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY 63 | MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc 64 | h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ 65 | 0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U 66 | A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW 67 | T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH 68 | B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC 69 | B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv 70 | KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn 71 | OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn 72 | jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw 73 | qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI 74 | rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV 75 | HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq 76 | hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL 77 | ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ 78 | 3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK 79 | NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 80 | ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur 81 | TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC 82 | jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc 83 | oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq 84 | 4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA 85 | mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d 86 | emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= 87 | -----END CERTIFICATE----- 88 | -------------------------------------------------------------------------------- /src/ext.rs: -------------------------------------------------------------------------------- 1 | use std::{borrow::Cow, net::IpAddr}; 2 | 3 | use const_oid::AssociatedOid as _; 4 | use der::Decode; 5 | use itertools::Itertools; 6 | use x509_cert::ext::{ 7 | pkix::{self, crl::dp, name::GeneralName, sct, AuthorityKeyIdentifier}, 8 | Extension, 9 | }; 10 | 11 | use crate::util::{oid_desc_or_raw, openssl_hex}; 12 | 13 | pub(crate) fn interpret_val(ext: &Extension) -> String { 14 | match ext.extn_id { 15 | pkix::SubjectKeyIdentifier::OID => fmt_subject_key_identifier(ext), 16 | pkix::SubjectAltName::OID => fmt_subject_alt_name(ext), 17 | pkix::CertificatePolicies::OID => fmt_certificate_policies(ext), 18 | pkix::BasicConstraints::OID => fmt_basic_constraints(ext), 19 | pkix::AuthorityInfoAccessSyntax::OID => fmt_authority_info_access_syntax(ext), 20 | pkix::KeyUsage::OID => fmt_key_usage(ext), 21 | pkix::ExtendedKeyUsage::OID => fmt_extended_key_usage(ext), 22 | pkix::AuthorityKeyIdentifier::OID => fmt_authority_key_identifier(ext), 23 | pkix::CrlDistributionPoints::OID => fmt_crl_distribution_points(ext), 24 | sct::SignedCertificateTimestampList::OID => fmt_sct_list(ext), 25 | _ => openssl_hex(ext.extn_value.as_bytes(), 80).join("\n "), 26 | } 27 | } 28 | 29 | fn fmt_key_usage(ext: &Extension) -> String { 30 | let key_usage = pkix::KeyUsage::from_der(ext.extn_value.as_bytes()).unwrap(); 31 | key_usage 32 | .0 33 | .into_iter() 34 | .map(|ku| format!("{ku:?}")) 35 | .join(", ") 36 | } 37 | 38 | fn fmt_extended_key_usage(ext: &Extension) -> String { 39 | let key_usage = pkix::ExtendedKeyUsage::from_der(ext.extn_value.as_bytes()).unwrap(); 40 | key_usage.0.iter().map(oid_desc_or_raw).join("\n ") 41 | } 42 | 43 | fn fmt_authority_key_identifier(ext: &Extension) -> String { 44 | let aki = pkix::AuthorityKeyIdentifier::from_der(ext.extn_value.as_bytes()).unwrap(); 45 | let key_id = fmt_aki_key_id(&aki); 46 | let issuer = fmt_aki_issuer(&aki); 47 | let serial = fmt_aki_serial(&aki); 48 | format!("{key_id}{issuer}{serial}") 49 | } 50 | 51 | fn fmt_aki_key_id(aki: &AuthorityKeyIdentifier) -> String { 52 | if let Some(ref key_id) = aki.key_identifier { 53 | format!("KeyId: {}", openssl_hex(key_id.as_bytes(), 20).join("\n")) 54 | } else { 55 | String::new() 56 | } 57 | } 58 | 59 | fn fmt_aki_issuer(aki: &AuthorityKeyIdentifier) -> String { 60 | if let Some(ref issuer) = aki.authority_cert_issuer { 61 | format!( 62 | "{}Issuer: {}", 63 | if aki.key_identifier.is_some() { 64 | "\n " 65 | } else { 66 | " " 67 | }, 68 | issuer.iter().map(fmt_general_name).join(", ") 69 | ) 70 | } else { 71 | String::new() 72 | } 73 | } 74 | 75 | fn fmt_aki_serial(aki: &AuthorityKeyIdentifier) -> String { 76 | if let Some(ref serial) = aki.authority_cert_serial_number { 77 | format!( 78 | "{}Serial: {}", 79 | if aki.key_identifier.is_some() || aki.authority_cert_issuer.is_some() { 80 | "\n " 81 | } else { 82 | " " 83 | }, 84 | openssl_hex(serial.as_bytes(), 20).join("\n") 85 | ) 86 | } else { 87 | String::new() 88 | } 89 | } 90 | 91 | fn fmt_crl_distribution_points(ext: &Extension) -> String { 92 | let crl_dp = pkix::CrlDistributionPoints::from_der(ext.extn_value.as_bytes()).unwrap(); 93 | crl_dp.0.iter().map(fmt_crl_distribution_point).join(", ") 94 | } 95 | 96 | fn fmt_crl_distribution_point(dp: &dp::DistributionPoint) -> String { 97 | let name = fmt_dp_name(dp); 98 | let issuer = fmt_dp_crl_issuer(dp); 99 | let reason = fmt_dp_reasons(dp); 100 | format!("{name}{issuer}{reason}") 101 | } 102 | 103 | fn fmt_dp_name(dp: &dp::DistributionPoint) -> String { 104 | if let Some(ref dp_name) = dp.distribution_point { 105 | match dp_name { 106 | pkix::name::DistributionPointName::FullName(names) => { 107 | format!( 108 | "FullName:\n {}", 109 | names.iter().map(fmt_general_name).join(", ") 110 | ) 111 | } 112 | pkix::name::DistributionPointName::NameRelativeToCRLIssuer(name) => { 113 | format!("RelativeName:\n {name}") 114 | } 115 | } 116 | } else { 117 | String::new() 118 | } 119 | } 120 | 121 | fn fmt_dp_crl_issuer(dp: &dp::DistributionPoint) -> String { 122 | if let Some(ref issuer) = dp.crl_issuer { 123 | format!( 124 | "{}Issuer: {}", 125 | if dp.distribution_point.is_some() { 126 | "\n " 127 | } else { 128 | " " 129 | }, 130 | issuer.iter().map(fmt_general_name).join(", ") 131 | ) 132 | } else { 133 | String::new() 134 | } 135 | } 136 | 137 | fn fmt_dp_reasons(dp: &dp::DistributionPoint) -> String { 138 | if let Some(ref reasons) = dp.reasons { 139 | format!( 140 | "{}Reasons: {}", 141 | if dp.distribution_point.is_some() || dp.crl_issuer.is_some() { 142 | "\n " 143 | } else { 144 | " " 145 | }, 146 | reasons.into_iter().map(fmt_reason).join(", ") 147 | ) 148 | } else { 149 | String::new() 150 | } 151 | } 152 | 153 | fn fmt_reason(reason: dp::Reasons) -> &'static str { 154 | match reason { 155 | dp::Reasons::Unused => "Unused", 156 | dp::Reasons::KeyCompromise => "KeyCompromise", 157 | dp::Reasons::CaCompromise => "CaCompromise", 158 | dp::Reasons::AffiliationChanged => "AffiliationChanged", 159 | dp::Reasons::Superseded => "Superseded", 160 | dp::Reasons::CessationOfOperation => "CessationOfOperation", 161 | dp::Reasons::CertificateHold => "CertificateHold", 162 | dp::Reasons::PrivilegeWithdrawn => "PrivilegeWithdrawn", 163 | dp::Reasons::AaCompromise => "AaCompromise", 164 | } 165 | } 166 | 167 | fn fmt_sct_list(ext: &Extension) -> String { 168 | sct::SignedCertificateTimestampList::from_der(ext.extn_value.as_bytes()) 169 | .expect("Failed to deserialize SCT list") 170 | .parse_timestamps() 171 | .expect("Failed to parse SCT timestamps") 172 | .into_iter() 173 | .map(|ser_sct| { 174 | ser_sct 175 | .parse_timestamp() 176 | .expect("Failed to deserialize SCT timestamp") 177 | }) 178 | .map(fmt_sct) 179 | .join("\n ") 180 | } 181 | 182 | fn fmt_sct(sct: sct::SignedCertificateTimestamp) -> String { 183 | let extensions = openssl_hex(sct.extensions.as_slice(), 16).join("\n "); 184 | format!( 185 | "Signed Certificate Timestamp:\n Version : {:?}\n Log ID : {}\n Timestamp : {}\n Extensions: {}\n Signature : {}\n {}", 186 | sct.version, 187 | openssl_hex(&sct.log_id.key_id, 16).join("\n "), 188 | sct.timestamp().expect("SCT timestamp is outside supported range"), 189 | if extensions.is_empty() { 190 | "none" 191 | } else { 192 | &extensions 193 | }, 194 | fmt_signature_and_hash_alg(&sct.signature.algorithm), 195 | openssl_hex(sct.signature.signature.as_slice(), 16).join("\n "), 196 | ) 197 | } 198 | 199 | fn fmt_signature_and_hash_alg(alg: &sct::SignatureAndHashAlgorithm) -> String { 200 | format!( 201 | "{}-with-{}", 202 | format!("{:?}", alg.signature).to_ascii_uppercase(), 203 | format!("{:?}", alg.hash).to_ascii_uppercase(), 204 | ) 205 | } 206 | 207 | fn fmt_authority_info_access_syntax(ext: &Extension) -> String { 208 | let authority_info_access = 209 | pkix::AuthorityInfoAccessSyntax::from_der(ext.extn_value.as_bytes()).unwrap(); 210 | 211 | authority_info_access 212 | .0 213 | .into_iter() 214 | .map(|access_description| { 215 | format!( 216 | "{} {}", 217 | oid_desc_or_raw(&access_description.access_method), 218 | fmt_general_name(&access_description.access_location) 219 | ) 220 | }) 221 | .join("\n ") 222 | } 223 | 224 | fn fmt_basic_constraints(ext: &Extension) -> String { 225 | let constraints = pkix::BasicConstraints::from_der(ext.extn_value.as_bytes()).unwrap(); 226 | let path_len = constraints 227 | .path_len_constraint 228 | .map_or(Cow::Borrowed("None"), |c| Cow::Owned(format!("{c}"))); 229 | format!( 230 | "CA: {}\n Path Length Constraint: {path_len}", 231 | constraints.ca 232 | ) 233 | } 234 | 235 | fn fmt_certificate_policies(ext: &Extension) -> String { 236 | let policies = pkix::CertificatePolicies::from_der(ext.extn_value.as_bytes()).unwrap(); 237 | policies 238 | .0 239 | .into_iter() 240 | .map(|info| { 241 | let qualifiers = info 242 | .policy_qualifiers 243 | .map(|qualifiers| { 244 | format!( 245 | " (qualifiers: {})", 246 | qualifiers 247 | .into_iter() 248 | .map(|qualifier| oid_desc_or_raw(&qualifier.policy_qualifier_id)) 249 | .join(", ") 250 | ) 251 | }) 252 | .unwrap_or_default(); 253 | 254 | format!("{}{}", oid_desc_or_raw(&info.policy_identifier), qualifiers) 255 | }) 256 | .join("\n ") 257 | } 258 | 259 | fn fmt_subject_alt_name(ext: &Extension) -> String { 260 | let san = pkix::SubjectAltName::from_der(ext.extn_value.as_bytes()).unwrap(); 261 | san.0 262 | .into_iter() 263 | .map(|name| fmt_general_name(&name)) 264 | .join(", ") 265 | } 266 | 267 | fn fmt_subject_key_identifier(ext: &Extension) -> String { 268 | let ski = pkix::SubjectKeyIdentifier::from_der(ext.extn_value.as_bytes()).unwrap(); 269 | let mut iter = openssl_hex(ski.0.as_bytes(), 20); 270 | iter.join("\n ") 271 | } 272 | 273 | //TODO: remove debug format for OtherName, EdiPartyName 274 | fn fmt_general_name(name: &GeneralName) -> String { 275 | match name { 276 | GeneralName::OtherName(other) => format!("OTHER{:?}", other), 277 | GeneralName::Rfc822Name(rfc) => format!("RFC:{}", rfc.as_str()), 278 | GeneralName::DnsName(dns) => format!("DNS:{}", dns.as_str()), 279 | GeneralName::DirectoryName(dir) => format!("DIR:{}", dir), 280 | GeneralName::EdiPartyName(edi) => format!("EDI:{:?}", edi), 281 | GeneralName::UniformResourceIdentifier(uri) => format!("URI:{}", uri.as_str()), 282 | GeneralName::IpAddress(ip) => match ip_try_from_bytes(ip.as_bytes()) { 283 | Some(ip) => format!("IP:{}", ip), 284 | None => format!("IP:{:?}", ip), 285 | }, 286 | GeneralName::RegisteredId(id) => oid_desc_or_raw(id), 287 | } 288 | } 289 | 290 | fn ip_try_from_bytes(bytes: &[u8]) -> Option { 291 | Some(match bytes.len() { 292 | 4 => IpAddr::from(<[u8; 4]>::try_from(bytes).unwrap()), 293 | 16 => IpAddr::from(<[u8; 16]>::try_from(bytes).unwrap()), 294 | _ => return None, 295 | }) 296 | } 297 | -------------------------------------------------------------------------------- /src/fetch.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io::{Read as _, Write as _}, 3 | net::TcpStream, 4 | sync::Arc, 5 | }; 6 | 7 | use der::Decode; 8 | use error_reporter::Report; 9 | use eyre::WrapErr as _; 10 | use rustls_pki_types::ServerName; 11 | use rustls_platform_verifier::BuilderVerifierExt as _; 12 | use x509_cert::Certificate; 13 | 14 | pub(crate) fn cert_chain(host: &str, port: u16) -> eyre::Result> { 15 | let server_name = ServerName::try_from(host) 16 | .with_context(|| format!("failed to convert given host (\"{host}\") to server name"))? 17 | .to_owned(); 18 | 19 | let mut config = 20 | rustls::ClientConfig::builder_with_protocol_versions(&[&rustls::version::TLS12]) 21 | .with_platform_verifier() 22 | .with_no_client_auth(); 23 | 24 | config 25 | .dangerous() 26 | .set_certificate_verifier(Arc::new(NoopServerCertVerifier)); 27 | 28 | let mut conn = rustls::ClientConnection::new(Arc::new(config), server_name)?; 29 | let mut sock = TcpStream::connect(format!("{host}:{port}")) 30 | .wrap_err_with(|| format!("failed to connect to host: {host}:{port}"))?; 31 | let mut tls = rustls::Stream::new(&mut conn, &mut sock); 32 | 33 | let req = format!( 34 | r#"GET / HTTP/1.1 35 | Host: {host} 36 | Connection: close 37 | User-Agent: inspect-cert-chain/{} 38 | Accept-Encoding: identity 39 | 40 | "#, 41 | env!("CARGO_PKG_VERSION"), 42 | ) 43 | .replace('\n', "\r\n"); 44 | 45 | tracing::debug!("writing to socket:\n{req}"); 46 | 47 | tls.write_all(req.as_bytes()) 48 | .wrap_err("failed to write to socket")?; 49 | tls.flush().wrap_err("failed to flush socket")?; 50 | 51 | let mut plaintext = Vec::new(); 52 | match tls.read_to_end(&mut plaintext) { 53 | Ok(_) => {} 54 | Err(err) => { 55 | tracing::warn!("failed to read from {host}: {}", Report::new(err)); 56 | } 57 | } 58 | 59 | // peer_certificates method will return certificates by now 60 | // because app data has already been written 61 | Ok(tls 62 | .conn 63 | .peer_certificates() 64 | .map(|c| { 65 | c.iter() 66 | .filter_map(|c| Certificate::from_der(c).ok()) 67 | .collect() 68 | }) 69 | .unwrap_or_default()) 70 | } 71 | 72 | #[derive(Debug)] 73 | struct NoopServerCertVerifier; 74 | 75 | impl rustls::client::danger::ServerCertVerifier for NoopServerCertVerifier { 76 | fn verify_server_cert( 77 | &self, 78 | _end_entity: &rustls_pki_types::CertificateDer<'_>, 79 | _intermediates: &[rustls_pki_types::CertificateDer<'_>], 80 | _server_name: &ServerName<'_>, 81 | _ocsp_response: &[u8], 82 | _now: rustls_pki_types::UnixTime, 83 | ) -> Result { 84 | Ok(rustls::client::danger::ServerCertVerified::assertion()) 85 | } 86 | 87 | fn verify_tls12_signature( 88 | &self, 89 | _message: &[u8], 90 | _cert: &rustls_pki_types::CertificateDer<'_>, 91 | _dss: &rustls::DigitallySignedStruct, 92 | ) -> Result { 93 | Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) 94 | } 95 | 96 | fn verify_tls13_signature( 97 | &self, 98 | _message: &[u8], 99 | _cert: &rustls_pki_types::CertificateDer<'_>, 100 | _dss: &rustls::DigitallySignedStruct, 101 | ) -> Result { 102 | Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) 103 | } 104 | 105 | fn supported_verify_schemes(&self) -> Vec { 106 | rustls::crypto::aws_lc_rs::default_provider() 107 | .signature_verification_algorithms 108 | .supported_schemes() 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/info.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use byteorder::{BigEndian, ByteOrder as _}; 4 | use const_oid::{ 5 | db::rfc5912::{ID_EC_PUBLIC_KEY, RSA_ENCRYPTION}, 6 | ObjectIdentifier, 7 | }; 8 | use crossterm::style::Stylize as _; 9 | use der::Decode as _; 10 | use itertools::Itertools as _; 11 | use x509_cert::Certificate; 12 | 13 | use crate::{ext, util}; 14 | 15 | pub(crate) fn write_cert_info( 16 | cert: &Certificate, 17 | mut wrt: impl io::Write, 18 | stylize: bool, 19 | ) -> io::Result<()> { 20 | let tbs = &cert.tbs_certificate; 21 | 22 | let tbs_cert = &tbs; 23 | writeln!( 24 | wrt, 25 | "Subject: {}", 26 | tbs_cert.subject.to_string().yellow().bold() 27 | )?; 28 | 29 | writeln!(wrt, "Issuer: {}", tbs.issuer.to_string().blue().bold())?; 30 | 31 | writeln!(wrt, "Version: {:?}", tbs.version)?; 32 | writeln!( 33 | wrt, 34 | "Serial Number:\n {}", 35 | util::openssl_hex(tbs.serial_number.as_bytes(), 20).join("\n ") 36 | )?; 37 | 38 | writeln!( 39 | wrt, 40 | "Signature Algorithm: {}", 41 | util::oid_desc_or_raw(&cert.signature_algorithm.oid) 42 | )?; 43 | util::assert_null_params(&cert.signature_algorithm); 44 | 45 | // TODO: doesn't work ? 46 | writeln!( 47 | wrt, 48 | "Issuer Serial Number:\n {}", 49 | tbs.issuer_unique_id 50 | .as_ref() 51 | .map(|serial| util::openssl_hex(serial.as_bytes().unwrap(), 20).join("\n ")) 52 | .unwrap_or_else(|| "".to_owned()) 53 | )?; 54 | 55 | let (nbf, nbf_in_future) = util::duration_since_now_fmt(tbs.validity.not_before); 56 | let (exp, exp_in_future) = util::duration_since_now_fmt(tbs.validity.not_after); 57 | 58 | writeln!( 59 | wrt, 60 | "{}", 61 | if stylize { 62 | if !nbf_in_future && exp_in_future { 63 | "Validity:".green().bold() 64 | } else { 65 | "Validity:".red() 66 | } 67 | } else { 68 | "Validity:".stylize() 69 | } 70 | )?; 71 | 72 | writeln!( 73 | wrt, 74 | " Not Before: {} ({})", 75 | tbs.validity.not_before, 76 | if stylize { 77 | if nbf_in_future { 78 | nbf.red().bold() 79 | } else { 80 | nbf.green().bold() 81 | } 82 | } else { 83 | nbf.stylize() 84 | }, 85 | )?; 86 | 87 | writeln!( 88 | wrt, 89 | " Not After: {} ({})", 90 | tbs.validity.not_after, 91 | if stylize { 92 | if exp_in_future { 93 | exp.green().bold() 94 | } else { 95 | exp.red().bold() 96 | } 97 | } else { 98 | exp.stylize() 99 | }, 100 | )?; 101 | 102 | // if let Some(name_constraints) = anchor.name_constraints { 103 | // writeln!(wrt, "Name Constraints: {:?}", name_constraints); 104 | // } 105 | 106 | writeln!(wrt, "Subject Public Key Info:")?; 107 | 108 | let spki = &tbs_cert.subject_public_key_info; 109 | let alg = &spki.algorithm; 110 | 111 | match () { 112 | _ if alg.oid == ID_EC_PUBLIC_KEY => { 113 | let ec_subtype = alg 114 | .parameters 115 | .as_ref() 116 | .unwrap() 117 | .decode_as::() 118 | .unwrap(); 119 | 120 | let ec_type = util::oid_desc_or_raw(&alg.oid); 121 | let ec_subtype = util::oid_desc_or_raw(&ec_subtype); 122 | 123 | let public_key_bytes = spki.subject_public_key.as_bytes().unwrap(); 124 | let public_key = util::openssl_hex(public_key_bytes, 15).join("\n "); 125 | 126 | writeln!(wrt, " Algorithm: {ec_type} ({ec_subtype})")?; 127 | writeln!(wrt, " Public Key:\n {public_key}")?; 128 | } 129 | 130 | _ if alg.oid == RSA_ENCRYPTION => { 131 | let algorithm = util::oid_desc_or_raw(&alg.oid); 132 | writeln!(wrt, " Algorithm: {algorithm}")?; 133 | 134 | let rsa_details = 135 | pkcs1::RsaPublicKey::from_der(spki.subject_public_key.as_bytes().unwrap()).unwrap(); 136 | 137 | writeln!(wrt, " RSA:")?; 138 | 139 | let exp_bytes = rsa_details.public_exponent.as_bytes(); 140 | let exp = BigEndian::read_uint(exp_bytes, exp_bytes.len()); 141 | writeln!(wrt, " Exponent: {exp} (0x{exp:0x})")?; 142 | let mod_bytes = rsa_details.modulus.as_bytes(); 143 | writeln!( 144 | wrt, 145 | " Modulus({} bit):\n {}", 146 | mod_bytes.len() * 8, 147 | util::openssl_hex(mod_bytes, 32).join("\n ") 148 | )?; 149 | } 150 | 151 | _ => { 152 | let alg = util::oid_desc_or_raw(&alg.oid); 153 | writeln!(wrt, " Algorithm: {alg}")?; 154 | } 155 | } 156 | 157 | if let Some(extensions) = &tbs.extensions { 158 | writeln!(wrt, "Extensions:")?; 159 | 160 | for ext in extensions { 161 | writeln!( 162 | wrt, 163 | " ID: {}{}", 164 | util::oid_desc_or_raw(&ext.extn_id), 165 | if ext.critical { " (critical)" } else { "" } 166 | )?; 167 | writeln!(wrt, " Extension value:\n {}", ext::interpret_val(ext))?; 168 | writeln!(wrt)?; 169 | } 170 | } 171 | 172 | writeln!(wrt, "Signature:")?; 173 | writeln!( 174 | wrt, 175 | " {}", 176 | util::openssl_hex(cert.signature.as_bytes().unwrap(), 20).join("\n ") 177 | )?; 178 | 179 | Ok(()) 180 | } 181 | -------------------------------------------------------------------------------- /src/logging.rs: -------------------------------------------------------------------------------- 1 | use eyre::eyre; 2 | use tracing::{level_filters::LevelFilter, Level}; 3 | use tracing_subscriber::prelude::*; 4 | 5 | /// Initializes logging. 6 | pub(crate) fn init(verbose: u8) -> eyre::Result<()> { 7 | let level = match verbose { 8 | 0 => Level::WARN, 9 | 1 => Level::INFO, 10 | 2 => Level::DEBUG, 11 | 3 => Level::TRACE, 12 | _ => return Err(eyre!("Unsupported logging level")), 13 | }; 14 | 15 | let fmt = tracing_subscriber::fmt::layer() 16 | .without_time() 17 | .with_filter(LevelFilter::from_level(level)); 18 | 19 | tracing_subscriber::registry().with(fmt).try_init()?; 20 | 21 | Ok(()) 22 | } 23 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs, 3 | io::{self, Read as _, Write as _}, 4 | }; 5 | 6 | use clap::{CommandFactory as _, Parser}; 7 | use der::{Decode as _, Encode as _}; 8 | use eyre::{eyre, WrapErr as _}; 9 | use pem_rfc7468::{LineEnding, PemLabel as _}; 10 | use x509_cert::Certificate; 11 | 12 | mod ext; 13 | mod fetch; 14 | mod info; 15 | mod logging; 16 | mod tui; 17 | mod util; 18 | 19 | cfg_if::cfg_if! { 20 | if #[cfg(windows)] { 21 | const LINE_ENDING: LineEnding = LineEnding::CRLF; 22 | } else { 23 | const LINE_ENDING: LineEnding = LineEnding::LF; 24 | } 25 | } 26 | 27 | #[derive(Debug, Parser)] 28 | #[command(author, version, about, long_about = None)] 29 | struct Args { 30 | /// Download certificate chain from remote host. 31 | #[clap(long, conflicts_with = "file")] 32 | host: Option, 33 | 34 | /// Port to use with --host. 35 | #[clap(long, conflicts_with = "file", default_value_t = 443)] 36 | port: u16, 37 | 38 | /// When provided, writes downloaded chain to file in PEM format. 39 | #[clap(long, conflicts_with = "file")] 40 | dump: Option, 41 | 42 | /// Inspect a local certificate chain in PEM format. 43 | #[clap(long, conflicts_with = "host")] 44 | file: Option, 45 | 46 | /// View certificate chain using interactive (TUI) mode. 47 | #[arg(short, long)] 48 | interactive: bool, 49 | 50 | #[arg(short, long, action = clap::ArgAction::Count)] 51 | verbose: u8, 52 | } 53 | 54 | // let anchor = &TLS_SERVER_ROOTS.0[3]; // seems to have wrong modulus ?!? 55 | 56 | fn main() -> eyre::Result<()> { 57 | color_eyre::install()?; 58 | 59 | let args = Args::parse(); 60 | 61 | logging::init(args.verbose)?; 62 | 63 | rustls::crypto::aws_lc_rs::default_provider() 64 | .install_default() 65 | .unwrap(); 66 | 67 | let certs = if let Some(host) = &args.host { 68 | tracing::info!(%host, "fetching certificate chain from remote host"); 69 | fetch::cert_chain(host, args.port)? 70 | } else if let Some(path) = &args.file { 71 | let mut input = if path == "-" { 72 | if args.interactive { 73 | let mut err = clap::Error::new(clap::error::ErrorKind::ArgumentConflict) 74 | .with_cmd(&Args::command()); 75 | 76 | err.insert( 77 | clap::error::ContextKind::InvalidArg, 78 | clap::error::ContextValue::String("--interactive".to_owned()), 79 | ); 80 | err.insert( 81 | clap::error::ContextKind::PriorArg, 82 | clap::error::ContextValue::String("--file -".to_owned()), 83 | ); 84 | 85 | err.exit(); 86 | } 87 | 88 | tracing::info!("reading certificate chain from stdin"); 89 | 90 | let mut buf = String::new(); 91 | let n_bytes = io::stdin().read_to_string(&mut buf).unwrap(); 92 | tracing::trace!("read {n_bytes} from stdin"); 93 | Box::new(io::Cursor::new(buf)) as Box 94 | } else { 95 | tracing::info!(%path, "reading certificate chain from file"); 96 | 97 | let file = 98 | fs::File::open(path).wrap_err_with(|| format!("could not open file: {path}"))?; 99 | Box::new(io::BufReader::new(file)) as Box 100 | }; 101 | 102 | tracing::debug!("reading certificate chain PEM files"); 103 | let certs = rustls_pemfile::certs(&mut input).collect::, _>>()?; 104 | 105 | tracing::debug!("parsing certificate chain"); 106 | certs 107 | .into_iter() 108 | .map(|der| x509_cert::Certificate::from_der(&der)) 109 | .collect::>()? 110 | } else { 111 | return Err(eyre!("use --host or --file")); 112 | }; 113 | 114 | let n_certs = certs.len(); 115 | tracing::info!("chain contains {n_certs} certificates"); 116 | 117 | if n_certs == 0 { 118 | return Err(eyre!("chain contained 0 certificates")); 119 | } 120 | 121 | if args.interactive { 122 | let mut tui = tui::init()?; 123 | let mut app = tui::App::new(&certs); 124 | app.run(&mut tui)?; 125 | tui::restore()?; 126 | } else { 127 | let mut stdout = io::stdout(); 128 | 129 | for cert in &certs { 130 | writeln!(&mut stdout, "Certificate")?; 131 | writeln!(&mut stdout, "===========")?; 132 | 133 | info::write_cert_info(cert, &mut stdout, false)?; 134 | 135 | writeln!(&mut stdout)?; 136 | writeln!(&mut stdout)?; 137 | } 138 | } 139 | 140 | if let Some(dump_path) = args.dump { 141 | tracing::info!(%dump_path, "writing chain"); 142 | 143 | let mut der_buf = Vec::with_capacity(1_024); 144 | 145 | let pem_cap = certs.len() * 2_048; // ~2Kb per cert 146 | 147 | let pem_chain = certs.into_iter().try_fold( 148 | String::with_capacity(pem_cap), 149 | |buf, cert| -> eyre::Result<_> { 150 | der_buf.clear(); 151 | 152 | cert.encode_to_vec(&mut der_buf) 153 | .wrap_err("failed to convert certificate back to DER encoding")?; 154 | 155 | let pem = pem_rfc7468::encode_string(Certificate::PEM_LABEL, LINE_ENDING, &der_buf) 156 | .wrap_err("failed to encode DER certificate to PEM format")?; 157 | 158 | Ok(buf + &pem) 159 | }, 160 | )?; 161 | 162 | fs::write(&dump_path, pem_chain) 163 | .wrap_err_with(|| format!("failed to dump downloaded cert chain to {dump_path}"))?; 164 | } 165 | 166 | Ok(()) 167 | } 168 | -------------------------------------------------------------------------------- /src/tui.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use ansi_to_tui::IntoText as _; 4 | use crossterm::{ 5 | event::{self, KeyCode, KeyModifiers}, 6 | execute, 7 | terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, 8 | }; 9 | use der::EncodePem as _; 10 | use ratatui::{ 11 | layout::Constraint, 12 | prelude::*, 13 | symbols::border, 14 | widgets::{ 15 | block::Block, Borders, List, ListState, Padding, Paragraph, Scrollbar, 16 | ScrollbarOrientation, ScrollbarState, 17 | }, 18 | }; 19 | use x509_cert::Certificate; 20 | 21 | use crate::{info::write_cert_info, LINE_ENDING}; 22 | 23 | pub type Tui = Terminal>; 24 | 25 | /// Sets up terminal for TUI display. 26 | pub(crate) fn init() -> io::Result { 27 | execute!(io::stdout(), EnterAlternateScreen)?; 28 | enable_raw_mode()?; 29 | Terminal::new(CrosstermBackend::new(io::stdout())) 30 | } 31 | 32 | /// Restores terminal to original state.. 33 | pub(crate) fn restore() -> io::Result<()> { 34 | execute!(io::stdout(), LeaveAlternateScreen)?; 35 | disable_raw_mode()?; 36 | Ok(()) 37 | } 38 | 39 | #[derive(Debug)] 40 | pub(crate) struct App { 41 | certs: Vec<(Certificate, String, usize)>, 42 | list_state: ListState, 43 | details_scroll: usize, 44 | exit: bool, 45 | } 46 | 47 | impl App { 48 | /// Constructs new TUI app widget. 49 | pub(crate) fn new(certs: &[Certificate]) -> Self { 50 | Self { 51 | exit: false, 52 | list_state: ListState::default().with_selected(Some(0)), 53 | details_scroll: 0, 54 | certs: certs 55 | .iter() 56 | .cloned() 57 | .map(|cert| { 58 | let mut details = Vec::with_capacity(4_096); // roughly ~4Kb of output 59 | 60 | write_cert_info(&cert, &mut details, true) 61 | .expect("io::Write-ing to a Vec always succeeds"); 62 | 63 | let details = String::from_utf8(details) 64 | .expect("everything written to details buffer should be UTF-8"); 65 | 66 | let lines = details.lines().count(); 67 | 68 | (cert, details, lines) 69 | }) 70 | .collect(), 71 | } 72 | } 73 | 74 | /// Runs main execution loop for TUI app. 75 | pub(crate) fn run(&mut self, tui: &mut Tui) -> io::Result<()> { 76 | while !self.exit { 77 | tui.draw(|frame| self.render_frame(frame))?; 78 | self.handle_events()?; 79 | } 80 | 81 | Ok(()) 82 | } 83 | 84 | fn render_frame(&mut self, frame: &mut Frame<'_>) { 85 | // layout 86 | 87 | let (outer_block, list_area, details_area) = self.create_layout(frame); 88 | 89 | // content 90 | 91 | let list = self.create_list(); 92 | let (details, (scrollbar, mut scrollbar_state)) = self.create_details(); 93 | 94 | // rendering 95 | 96 | frame.render_widget(outer_block, frame.area()); 97 | frame.render_stateful_widget(list, list_area, &mut self.list_state); 98 | frame.render_widget(details, details_area); 99 | frame.render_stateful_widget(scrollbar, details_area, &mut scrollbar_state) 100 | } 101 | 102 | fn create_layout(&mut self, frame: &mut Frame<'_>) -> (Block<'static>, Rect, Rect) { 103 | let title = Line::from("inspect-cert-chain".bold()); 104 | 105 | let instructions = Line::from(vec![ 106 | " Scroll Up ".into(), 107 | " ".blue().bold(), 108 | " Scroll Down ".into(), 109 | " ".blue().bold(), 110 | " Copy Public Key ".into(), 111 | " ".blue().bold(), 112 | " Quit ".into(), 113 | " ".blue().bold(), 114 | ]); 115 | 116 | let outer_block = Block::default() 117 | .title_top(title.centered()) 118 | .title_bottom(instructions.centered()) 119 | .borders(Borders::ALL) 120 | .border_set(border::THICK); 121 | 122 | let layout = Layout::vertical([ 123 | Constraint::Length(self.certs.len() as u16 + 1), // list + border 124 | Constraint::Fill(1), // details 125 | ]); 126 | 127 | let outer_block_area = outer_block.inner(frame.area()); 128 | let [list_area, details_area] = layout.areas(outer_block_area); 129 | 130 | (outer_block, list_area, details_area) 131 | } 132 | 133 | fn create_list(&self) -> List<'static> { 134 | let list = self 135 | .certs 136 | .iter() 137 | .map(|(cert, _, _)| cert.tbs_certificate.subject.to_string()) 138 | .collect::>(); 139 | 140 | let instructions = Line::from(vec![ 141 | symbols::line::HORIZONTAL.into(), 142 | " Certificate ".yellow().bold(), 143 | " Next ".into(), 144 | " ".blue().bold(), 145 | " Prev ".into(), 146 | " ".blue().bold(), 147 | ]); 148 | 149 | list.highlight_style(Style::new().bold()) 150 | .highlight_symbol("› ") 151 | .block( 152 | Block::default() 153 | // visual separation from cert details 154 | .borders(Borders::BOTTOM) 155 | .title_bottom(instructions.centered()), 156 | ) 157 | } 158 | 159 | fn create_details(&self) -> (Paragraph<'static>, (Scrollbar<'static>, ScrollbarState)) { 160 | let selected = self.list_state.selected().unwrap(); 161 | 162 | let details = self.certs[selected].1.to_owned(); 163 | 164 | let scrollbar_state = 165 | ScrollbarState::new(details.lines().count()).position(self.details_scroll); 166 | 167 | let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight).track_symbol(None); 168 | 169 | let details = Paragraph::new(details.into_text().expect("should be valid ANSI")) 170 | .scroll((self.details_scroll as u16, 0)) 171 | .block(Block::default().padding(Padding::new(1, 2, 1, 1))); 172 | 173 | (details, (scrollbar, scrollbar_state)) 174 | } 175 | 176 | fn handle_events(&mut self) -> io::Result<()> { 177 | match event::read()? { 178 | // check that the event is a key press event as crossterm also emits 179 | // key release and repeat events on Windows 180 | event::Event::Key(ev) if ev.kind == event::KeyEventKind::Press => { 181 | self.handle_key_event(ev); 182 | } 183 | 184 | event::Event::Mouse(ev) => { 185 | self.handle_mouse_event(ev); 186 | } 187 | 188 | _ => {} 189 | }; 190 | 191 | Ok(()) 192 | } 193 | 194 | fn handle_key_event(&mut self, ev: event::KeyEvent) { 195 | let max = self.certs.len() - 1; 196 | let selected = self.list_state.selected().unwrap(); 197 | let selected_cert_lines = self.certs[selected].2.saturating_sub(1); 198 | 199 | match ev.code { 200 | event::KeyCode::Char('q') => { 201 | self.exit = true; 202 | } 203 | 204 | KeyCode::Char('c') if ev.modifiers.contains(KeyModifiers::CONTROL) => { 205 | self.exit = true; 206 | } 207 | 208 | event::KeyCode::Char('k') => { 209 | self.list_state.select(Some(selected.saturating_sub(1))); 210 | 211 | self.details_scroll = 0; 212 | } 213 | 214 | event::KeyCode::Char('p') if ev.modifiers.contains(KeyModifiers::CONTROL) => { 215 | let spki = &self.certs[selected] 216 | .0 217 | .tbs_certificate 218 | .subject_public_key_info 219 | .to_pem(LINE_ENDING) 220 | .expect("SPKI should encode to PEM successfully"); 221 | 222 | let mut clipboard = arboard::Clipboard::new().expect("clipboard should initialize"); 223 | clipboard 224 | .set_text(spki) 225 | .expect("clipboard should be usable"); 226 | } 227 | 228 | event::KeyCode::Char('j') => { 229 | self.list_state 230 | .select(Some(selected.saturating_add(1).clamp(0, max))); 231 | 232 | self.details_scroll = 0; 233 | } 234 | 235 | event::KeyCode::Up => { 236 | self.details_scroll = self.details_scroll.saturating_sub(1); 237 | } 238 | 239 | event::KeyCode::PageUp => { 240 | self.details_scroll = self.details_scroll.saturating_sub(10); 241 | } 242 | 243 | event::KeyCode::Down => { 244 | self.details_scroll = self 245 | .details_scroll 246 | .saturating_add(1) 247 | .clamp(0, selected_cert_lines); 248 | } 249 | 250 | event::KeyCode::PageDown => { 251 | self.details_scroll = self 252 | .details_scroll 253 | .saturating_add(10) 254 | .clamp(0, selected_cert_lines); 255 | } 256 | 257 | _ => {} 258 | } 259 | } 260 | 261 | fn handle_mouse_event(&mut self, ev: event::MouseEvent) { 262 | match ev.kind { 263 | event::MouseEventKind::ScrollUp => { 264 | self.details_scroll = self.details_scroll.saturating_sub(1); 265 | } 266 | 267 | event::MouseEventKind::ScrollDown => { 268 | self.details_scroll = self.details_scroll.saturating_add(1); 269 | } 270 | 271 | _ => {} 272 | } 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | use std::io::Read; 4 | 5 | use chrono::TimeZone as _; 6 | use const_oid::{ 7 | db::{rfc5280, rfc5912, rfc6962, Database, DB}, 8 | ObjectIdentifier, 9 | }; 10 | use itertools::Itertools as _; 11 | use x509_cert::spki::{AlgorithmIdentifier, AlgorithmIdentifierOwned}; 12 | 13 | /// Taken from . 14 | pub(crate) fn wrap_in_asn1_len(bytes: &mut Vec) { 15 | let len = bytes.len(); 16 | 17 | if len <= 0x7f { 18 | bytes.insert(0, len as u8); 19 | } else { 20 | bytes.insert(0, 0x80u8); 21 | let mut left = len; 22 | while left > 0 { 23 | let byte = (left & 0xff) as u8; 24 | bytes.insert(1, byte); 25 | bytes[0] += 1; 26 | left >>= 8; 27 | } 28 | } 29 | } 30 | 31 | /// Prepend stuff to `bytes` to put it in a DER SEQUENCE. 32 | /// 33 | /// Taken from . 34 | pub(crate) fn wrap_in_sequence(bytes: &mut Vec) { 35 | wrap_in_asn1_len(bytes); 36 | bytes.insert(0, u8::from(der::Tag::Sequence)); 37 | } 38 | 39 | #[track_caller] 40 | pub(crate) fn assert_null_params(alg: &AlgorithmIdentifierOwned) { 41 | assert!(alg.parameters.is_none() || alg.parameters.as_ref().unwrap().is_null()); 42 | } 43 | 44 | pub(crate) fn oid_desc_or_raw(oid: &ObjectIdentifier) -> String { 45 | get_oid_desc(oid) 46 | .or(DB.by_oid(oid)) 47 | .map(ToOwned::to_owned) 48 | .unwrap_or_else(|| oid.to_string()) 49 | } 50 | 51 | /// Returns formatted date and true if date is in the future. 52 | pub(crate) fn duration_since_now_fmt(time: x509_cert::time::Time) -> (String, bool) { 53 | use chrono::{DateTime, Utc}; 54 | 55 | let ts = time.to_unix_duration().as_secs() as i64; 56 | 57 | let date = DateTime::from_timestamp(ts, 0).unwrap(); 58 | let now = Utc::now(); 59 | 60 | let duration = if now > date { now - date } else { date - now }; 61 | 62 | let days = duration.num_days(); 63 | 64 | if now > date { 65 | (format!("{} days ago", days), false) 66 | } else { 67 | (format!("in {} days", days), true) 68 | } 69 | } 70 | 71 | pub(crate) fn openssl_hex(bytes: &[u8], width: usize) -> impl Iterator + '_ { 72 | let n_chunks = bytes.len() / width; 73 | 74 | bytes.chunks(width).enumerate().map(move |(i, chunk)| { 75 | let mut chunk = chunk.iter().map(|byte| format!("{byte:0>2x}:")).join(""); 76 | if i == n_chunks { 77 | let _ = chunk.pop(); 78 | } 79 | chunk 80 | }) 81 | } 82 | 83 | fn get_oid_desc(oid: &ObjectIdentifier) -> Option<&str> { 84 | OID_DESCS 85 | .iter() 86 | .find(|(&id, _)| id == *oid) 87 | .map(|&(_, desc)| desc) 88 | } 89 | 90 | //TODO: convert into a phf if it grows too large 91 | /// Contains human readable descriptions for commonly used OIDs. 92 | const OID_DESCS: &[(&ObjectIdentifier, &str)] = &[ 93 | ( 94 | &rfc5280::ID_CE_SUBJECT_KEY_IDENTIFIER, 95 | "Subject Key Identifier", 96 | ), 97 | ( 98 | &rfc5280::ID_CE_AUTHORITY_KEY_IDENTIFIER, 99 | "Authority Key Identifier", 100 | ), 101 | (&rfc5280::ID_CE_KEY_USAGE, "Key Usage"), 102 | (&rfc5280::ID_CE_EXT_KEY_USAGE, "Extended Key Usage"), 103 | (&rfc5280::ID_CE_SUBJECT_ALT_NAME, "Subject Alternate Name"), 104 | (&rfc5912::ID_KP_CLIENT_AUTH, "Client Authentication"), 105 | (&rfc5912::ID_KP_SERVER_AUTH, "Server Authentication"), 106 | (&rfc5912::ID_CE_BASIC_CONSTRAINTS, "Basic Constraints"), 107 | ( 108 | &rfc5912::ID_PE_AUTHORITY_INFO_ACCESS, 109 | "Authority Information Access", 110 | ), 111 | ( 112 | &rfc5912::ID_CE_CRL_DISTRIBUTION_POINTS, 113 | "CRL Distribution Points", 114 | ), 115 | (&rfc5912::ID_CE_CERTIFICATE_POLICIES, "Certificate Policies"), 116 | (&rfc5912::ID_AD_OCSP, "OCSP"), 117 | (&rfc5912::ID_AD_CA_ISSUERS, "CA Issuers"), 118 | (&rfc6962::CT_PRECERT_SCTS, "CT Precertificate SCTs"), 119 | ( 120 | &ObjectIdentifier::new_unwrap("2.23.140.1.1"), 121 | "Extended Validation (EV) Guidelines", 122 | ), 123 | ( 124 | &ObjectIdentifier::new_unwrap("2.23.140.1.2.1"), 125 | "Domain Validated", 126 | ), 127 | ( 128 | &ObjectIdentifier::new_unwrap("2.23.140.1.2.2"), 129 | "Organization Validated", 130 | ), 131 | ( 132 | &ObjectIdentifier::new_unwrap("2.23.140.1.2.3"), 133 | "Individual Validated", 134 | ), 135 | ( 136 | &ObjectIdentifier::new_unwrap("2.16.840.1.114412.2.1"), 137 | "DigiCert Extended Validation (EV) Guidelines", 138 | ), 139 | ( 140 | &ObjectIdentifier::new_unwrap("1.3.6.1.5.5.7.1.31"), 141 | "ACME Identifier", 142 | ), 143 | ]; 144 | --------------------------------------------------------------------------------