├── .gitignore ├── rust-toolchain.toml ├── assets ├── masked.txt ├── tomask.txt ├── amber-encrypt.yaml └── amber-masking.yaml ├── helper_scripts ├── install.sh └── bootstrap_aws.sh ├── src ├── exec.rs ├── mask.rs ├── cli.rs ├── main.rs └── config.rs ├── LICENSE ├── Cargo.toml ├── CHANGELOG.md ├── tests ├── masking.rs └── encrypt.rs ├── .github └── workflows │ └── rust.yml ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.79.0" 3 | -------------------------------------------------------------------------------- /assets/masked.txt: -------------------------------------------------------------------------------- 1 | This should be unmasked. 2 | 3 | This is a ******, but this is just ******. 4 | 5 | Something is OK, but ****** is hidden. 6 | -------------------------------------------------------------------------------- /assets/tomask.txt: -------------------------------------------------------------------------------- 1 | This should be unmasked. 2 | 3 | This is a SECRET123, but this is just secret1. 4 | 5 | Something is OK, but something is hidden. 6 | -------------------------------------------------------------------------------- /assets/amber-encrypt.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | file_format_version: 1 3 | public_key: e0e3127400c150de1d9dc33047a990c79d0e4423071a107e278c17d4d30c5446 4 | secrets: [] 5 | -------------------------------------------------------------------------------- /helper_scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu -o pipefail 4 | 5 | version_prefix='' 6 | tool='fpco/amber' 7 | 8 | tags_url="https://github.com/${tool}/tags" 9 | tags="$(wget -O- "${tags_url}" | grep -o "/${tool}/releases/tag/[^\"]*")" 10 | [ -z "${version_prefix}" ] && tag="$(echo "${tags}" | head -1)" || tag="$(echo "${tags}" | grep "/${version_prefix}" | head -1)" 11 | target="https://github.com/$(wget -O- "https://github.com${tag}" | grep -o "/${tool}[^\"]*linux[^\"]*")" 12 | 13 | wget -O /tmp/amber "${target}" 14 | chmod a+x /tmp/amber 15 | 16 | echo 'Amber has been downloaded to /tmp/amber.' 17 | -------------------------------------------------------------------------------- /assets/amber-masking.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | file_format_version: 1 3 | public_key: f25ff634eb2e6d46b18ab5e846e5c051fafd15af1520b4f2cdc2bc09297bb442 4 | secrets: 5 | - name: BAR 6 | sha256: 7dacf9c63bcfb108c2e298e9a53c0e75681866d5041a73cba714cf250ce6a212 7 | cipher: 1a05b5e3755e7a6aebff8ad284d3b63d24bdab189935b0bf0f7f99d88d786710723b9b977962060f5a2b3950697b7e50ebce00fe0760fba687 8 | - name: BAZ 9 | sha256: 3fc9b689459d738f8c88a3a48aa9e33542016b7a4052e001aaa536fca74813cb 10 | cipher: 16e48778118b6a31f62e21cb4d31657238c3e4f75f9f44709899c85880d2dd210654364f0066bfd0a070595f8a8a6b482b622b22a942993488 11 | - name: FOO 12 | sha256: 5b11618c2e44027877d0cd0921ed166b9f176f50587fc91e7534dd2946db77d6 13 | cipher: cac62fe0e5dbbf506a042526826481e8c140ebd1ea4cce30efce1e00a0b06c46c169e75ac5d54382a0a56180113546ed8225a8ceb7338f 14 | -------------------------------------------------------------------------------- /src/exec.rs: -------------------------------------------------------------------------------- 1 | use anyhow::*; 2 | 3 | pub trait CommandExecExt { 4 | fn emulate_exec(&mut self, desc: &str) -> Result<()>; 5 | } 6 | 7 | impl CommandExecExt for std::process::Command { 8 | #[cfg(not(unix))] 9 | fn emulate_exec(&mut self, desc: &str) -> Result<()> { 10 | let mut child = self.spawn().with_context(|| desc.to_owned())?; 11 | 12 | let status = child.wait().with_context(|| desc.to_owned())?; 13 | 14 | let code = status 15 | .code() 16 | .ok_or_else(|| anyhow!("Unexpected exit status from {}", desc))?; 17 | 18 | std::process::exit(code) 19 | } 20 | 21 | #[cfg(unix)] 22 | fn emulate_exec(&mut self, desc: &str) -> Result<()> { 23 | use std::os::unix::process::CommandExt; 24 | let err = self.exec(); 25 | Err(err).context(desc.to_owned()) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 FP Complete 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "amber" 3 | version = "0.1.5" 4 | authors = ["Michael Snoyman "] 5 | edition = "2018" 6 | description = "Manage secret values in-repo via public key cryptography" 7 | readme = "README.md" 8 | homepage = "https://github.com/fpco/amber" 9 | repository = "https://github.com/fpco/amber" 10 | license = "MIT" 11 | keywords = ["continuous-integration", "secrets", "encryption"] 12 | categories = ["command-line-utilities"] 13 | 14 | [dependencies] 15 | aho-corasick = "1.1.3" 16 | anyhow = "1.0.86" 17 | clap = { version = "4.5.7", features = ["derive", "env"] } 18 | env_logger = "0.11.3" 19 | fs-err = "2.11.0" 20 | log = "0.4.21" 21 | once_cell = "1.19.0" 22 | serde = { version = "1.0.203", features = ["derive"] } 23 | serde_yaml = "0.9.34" 24 | serde_json = "1.0.117" 25 | crypto_box = { version = "0.9.1", features = ["seal", "rand_core", "getrandom"]} 26 | hex = "0.4.3" 27 | sha2 = "0.10.8" 28 | base64 = "0.22.1" 29 | 30 | [build-dependencies] 31 | anyhow = "1.0.86" 32 | vergen = { version = "8.3.1", default-features = false, features = ["gitoxide", "git"] } 33 | 34 | [dev-dependencies] 35 | assert_cmd = "2.0.14" 36 | tempfile = "3.10.1" 37 | 38 | [profile.dev] 39 | # Disabling debug info speeds up builds a bunch, 40 | # and we don't rely on it for debugging that much. 41 | debug = 0 42 | 43 | [profile.release] 44 | panic = "abort" 45 | opt-level = "z" 46 | lto = true 47 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log for Amber 2 | 3 | ## 0.1.7 (2024-12-10) 4 | 5 | * Add ARM64 static binary. 6 | 7 | ## 0.1.6 (2024-06-24) 8 | 9 | * Upgrade rust toolchain to 1.79.0 10 | * Upgrade dependencies. 11 | 12 | ## 0.1.5 (2022-12-23) 13 | 14 | * Update rust toolchain to 1.67.0 15 | * Fix cargo lock file issue causing packaging problem. 16 | 17 | ## 0.1.4 (2022-11-29) 18 | 19 | * Upgrade to clap v4 20 | * Add `--only-secret-key` option for `amber init` 21 | * Switch to crypto_box from sodiumoxide. 22 | 23 | ## 0.1.3 (2022-03-13) 24 | 25 | * Add the `write-file` command 26 | 27 | ## 0.1.2 (2022-01-18) 28 | 29 | * Allow `encrypt` subcommand to take secret value from `stdin` [#15](https://github.com/fpco/amber/issues/15) 30 | * Amber searches the parent directory for the amber.yaml file if 31 | amber.yaml isn't present in the current working directory. This 32 | check is only done when no explicit amber-yaml is specificed (unless 33 | the specified amber yaml itself is amber.yaml which is the default 34 | value) 35 | 36 | ## 0.1.1 (2021-08-31) 37 | 38 | * Add masking support [#1](https://github.com/fpco/amber/issues/1) 39 | * Add subcommand `generate` [#7](https://github.com/fpco/amber/pull/7) 40 | * Do `vergen` initialization only when `.git` directory is 41 | present. This makes amber easy to package for distributions like 42 | NixOS. 43 | * Rework print subcommand to provide more output styles. 44 | [#11](https://github.com/fpco/amber/pull/11) 45 | 46 | ## 0.1.0 (2021-08) 47 | 48 | Initial release 49 | -------------------------------------------------------------------------------- /tests/masking.rs: -------------------------------------------------------------------------------- 1 | use assert_cmd::prelude::*; 2 | use std::process::Command; 3 | 4 | const AMBER_YAML: &str = "assets/amber-masking.yaml"; 5 | const SECRET_KEY: &str = "ac2af4852f3de2dc6feb19b718d1cbf6c64c1ef618dafaf2b0a89cadcde240ac"; 6 | const TO_MASK: &str = include_str!("../assets/tomask.txt"); 7 | const MASKED: &str = include_str!("../assets/masked.txt"); 8 | 9 | #[test] 10 | fn masking() { 11 | let output = Command::cargo_bin("amber") 12 | .unwrap() 13 | .arg("exec") 14 | .arg("cat") 15 | .arg("assets/tomask.txt") 16 | .env("AMBER_YAML", AMBER_YAML) 17 | .env("AMBER_SECRET", SECRET_KEY) 18 | .output() 19 | .unwrap(); 20 | if !output.status.success() { 21 | eprintln!("{}", std::str::from_utf8(&output.stderr).unwrap()); 22 | panic!("Did not exit successfully"); 23 | } 24 | assert_eq!(std::str::from_utf8(&output.stdout).unwrap(), MASKED); 25 | } 26 | 27 | #[test] 28 | fn disable_masking() { 29 | let output = Command::cargo_bin("amber") 30 | .unwrap() 31 | .arg("exec") 32 | .arg("--unmasked") 33 | .arg("cat") 34 | .arg("assets/tomask.txt") 35 | .env("AMBER_YAML", AMBER_YAML) 36 | .env("AMBER_SECRET", SECRET_KEY) 37 | .output() 38 | .unwrap(); 39 | if !output.status.success() { 40 | eprintln!("{}", std::str::from_utf8(&output.stderr).unwrap()); 41 | panic!("Did not exit successfully"); 42 | } 43 | assert_eq!(std::str::from_utf8(&output.stdout).unwrap(), TO_MASK); 44 | } 45 | -------------------------------------------------------------------------------- /helper_scripts/bootstrap_aws.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu -o pipefail 4 | 5 | chk_tools () { 6 | ok=true 7 | for i in $@; do 8 | if ! which "${i}" > /dev/null 2>&1; then 9 | ok=false 10 | echo "Tool ${i} does not exist in env PATH. Please install it." >&2 11 | fi 12 | done 13 | ${ok} 14 | } 15 | 16 | chk_envs () { 17 | ok=true 18 | for i in $@; do 19 | eval "x=\"\${${i}-~~~}\"" 20 | if [ "${x}" = '~~~' ]; then 21 | ok=false 22 | echo "Environment variable ${i} needs to be set." >&2 23 | fi 24 | done 25 | ${ok} 26 | } 27 | 28 | get_envs () { 29 | ok=true 30 | for i in $@; do 31 | if ! (chk_envs "${i}" > /dev/null 2>&1 || 32 | eval "$(amber print | grep " ${i}=")"); then 33 | ok=false 34 | echo "Environment variable ${i} needs to be set, or in Amber secrets." >&2 35 | fi 36 | done 37 | ${ok} 38 | } 39 | 40 | ### main 41 | 42 | # Due to awscli takes value of secret in commandline, and some secrets are in environments, both are easily to be observed. This script must be run in a trusted environment. 43 | 44 | chk_tools 'amber' 'jq' 'aws' 45 | chk_envs 'AMBER_SECRET' 'AWS_REGION' 46 | get_envs 'AWS_ACCESS_KEY_ID' 'AWS_SECRET_ACCESS_KEY' 47 | 48 | size="$(amber print --style json | jq -r '. | length')" 49 | 50 | for i in $(seq "${size}"); do 51 | i="$(( i - 1 ))" 52 | aws secretsmanager create-secret --name "$(amber print --style json | jq -r ".[${i}].key")" --secret-string "$(amber print --style json | jq -r ".[${i}].value")" 53 | done 54 | -------------------------------------------------------------------------------- /src/mask.rs: -------------------------------------------------------------------------------- 1 | //! Logic for handling the masking of secret values when running an executable. 2 | 3 | use std::{ 4 | io::{Read, Write}, 5 | process::{Command, Stdio}, 6 | thread::spawn, 7 | }; 8 | 9 | use aho_corasick::{AhoCorasick, Match}; 10 | use anyhow::*; 11 | 12 | /// Run the given command with stdout and stderr values masked. 13 | pub fn run_masked(mut cmd: Command, secrets: &[impl AsRef<[u8]>]) -> Result<()> { 14 | // Unfortunately LeftmostLongest isn't supported by the streaming interface 15 | // let ac = AhoCorasickBuilder::new().auto_configure(secrets).match_kind(MatchKind::LeftmostLongest).build(secrets); 16 | 17 | let ac = AhoCorasick::new(secrets.iter()).context("Error creating AhoCorasick type")?; 18 | let mut child = cmd 19 | .stdout(Stdio::piped()) 20 | .stderr(Stdio::piped()) 21 | .spawn() 22 | .context("Unable to spawn child process")?; 23 | 24 | let stdout = child.stdout.take().context("No stdout available")?; 25 | let stderr = child.stderr.take().context("No stderr available")?; 26 | 27 | let ac_clone = ac.clone(); 28 | let handle_out = spawn(move || mask_stream(stdout, std::io::stdout(), ac_clone)); 29 | let handle_err = spawn(move || mask_stream(stderr, std::io::stderr(), ac)); 30 | 31 | handle_out 32 | .join() 33 | .map_err(|e| anyhow!("stdout thread panicked: {:?}", e))??; 34 | handle_err 35 | .join() 36 | .map_err(|e| anyhow!("stderr thread panicked: {:?}", e))??; 37 | 38 | let exit = child.wait().context("Unable to wait for child to exit")?; 39 | 40 | if exit.success() { 41 | Ok(()) 42 | } else { 43 | match exit.code() { 44 | Some(ec) => std::process::exit(ec), 45 | None => Err(anyhow!("Child process died with unknown error code")), 46 | } 47 | } 48 | } 49 | 50 | fn mask_stream(input: impl Read, output: impl Write, ac: AhoCorasick) -> Result<()> { 51 | ac.try_stream_replace_all_with(input, output, replacer) 52 | .context("Error while masking a stream") 53 | } 54 | 55 | /// Always use the same length of masked value to avoid exposing information 56 | /// about the secret length. 57 | fn replacer(_: &Match, _: &[u8], w: &mut impl Write) -> std::io::Result<()> { 58 | w.write_all(b"******") 59 | } 60 | -------------------------------------------------------------------------------- /tests/encrypt.rs: -------------------------------------------------------------------------------- 1 | use assert_cmd::prelude::*; 2 | use std::io::Write; 3 | use std::process::Command; 4 | use std::{path::Path, process::Stdio}; 5 | 6 | const AMBER_YAML: &str = "assets/amber-encrypt.yaml"; 7 | const SECRET_KEY: &str = "2a0fb64171010cd4584e2b658fc0a5effca4cd9ada2b2eea0262356852c60872"; 8 | 9 | fn temp_amber_yaml() -> tempfile::TempPath { 10 | let path = tempfile::NamedTempFile::new().unwrap().into_temp_path(); 11 | std::fs::copy(AMBER_YAML, &path).unwrap(); 12 | path 13 | } 14 | 15 | #[derive(serde::Deserialize, PartialEq, Eq, Debug)] 16 | struct Pair { 17 | key: String, 18 | value: String, 19 | } 20 | 21 | fn get_vars(path: impl AsRef) -> Vec { 22 | let output = Command::cargo_bin("amber") 23 | .unwrap() 24 | .arg("print") 25 | .arg("--style") 26 | .arg("json") 27 | .env("AMBER_YAML", path.as_ref()) 28 | .env("AMBER_SECRET", SECRET_KEY) 29 | .output() 30 | .unwrap(); 31 | if !output.status.success() { 32 | eprintln!("{}", std::str::from_utf8(&output.stderr).unwrap()); 33 | panic!("Did not print successfully"); 34 | } 35 | serde_json::from_slice(&output.stdout).unwrap() 36 | } 37 | 38 | #[test] 39 | fn empty_file() { 40 | let temp = temp_amber_yaml(); 41 | assert_eq!(get_vars(&temp), vec![]); 42 | } 43 | 44 | #[test] 45 | fn encrypt_cli() { 46 | let temp = temp_amber_yaml(); 47 | let status = Command::cargo_bin("amber") 48 | .unwrap() 49 | .arg("encrypt") 50 | .arg("FOO") 51 | .arg("foovalue") 52 | .env("AMBER_YAML", temp.as_os_str()) 53 | .status() 54 | .unwrap(); 55 | assert!(status.success()); 56 | assert_eq!( 57 | get_vars(&temp), 58 | vec![Pair { 59 | key: "FOO".to_owned(), 60 | value: "foovalue".to_owned(), 61 | }] 62 | ); 63 | } 64 | 65 | #[test] 66 | fn encrypt_stdin() { 67 | let temp = temp_amber_yaml(); 68 | let mut child = Command::cargo_bin("amber") 69 | .unwrap() 70 | .arg("encrypt") 71 | .arg("FOO") 72 | .env("AMBER_YAML", temp.as_os_str()) 73 | .stdin(Stdio::piped()) 74 | .spawn() 75 | .unwrap(); 76 | let mut stdin = child.stdin.take().unwrap(); 77 | write!(&mut stdin, "foovalue via stdin").unwrap(); 78 | std::mem::drop(stdin); 79 | let status = child.wait().unwrap(); 80 | assert!(status.success()); 81 | assert_eq!( 82 | get_vars(&temp), 83 | vec![Pair { 84 | key: "FOO".to_owned(), 85 | value: "foovalue via stdin".to_owned(), 86 | }] 87 | ); 88 | } 89 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | tags: 7 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 8 | pull_request: 9 | 10 | env: 11 | CARGO_TERM_COLOR: always 12 | CARGO_INCREMENTAL: 0 13 | CARGO_NET_RETRY: 10 14 | RUST_BACKTRACE: short 15 | RUSTUP_MAX_RETRIES: 10 16 | 17 | jobs: 18 | checks: 19 | runs-on: ${{ matrix.os }} 20 | 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | os: 25 | - ubuntu-latest 26 | - macos-latest 27 | - windows-latest 28 | 29 | toolchain: 30 | - 1.79.0 31 | - stable 32 | - nightly 33 | 34 | steps: 35 | - uses: actions/checkout@v2 36 | - uses: dtolnay/rust-toolchain@master 37 | with: 38 | toolchain: ${{ matrix.toolchain }} 39 | components: clippy, rustfmt 40 | - uses: Swatinem/rust-cache@v2 41 | with: 42 | key: ${{ runner.os }}-${{ hashFiles('Cargo.lock') }}-${{ matrix.toolchain }} 43 | - name: Build 44 | run: cargo build 45 | - name: Run tests 46 | run: cargo test 47 | - name: clippy 48 | run: cargo clippy -- --deny "warnings" 49 | - name: fmt 50 | run: cargo fmt -- --check 51 | 52 | linux-binary: 53 | runs-on: ubuntu-latest 54 | 55 | steps: 56 | - uses: actions/checkout@v2 57 | - uses: taiki-e/install-action@v2 58 | with: 59 | tool: cross@0.2.5 60 | - uses: dtolnay/rust-toolchain@master 61 | with: 62 | toolchain: 1.79.0 63 | components: clippy, rustfmt 64 | targets: x86_64-unknown-linux-musl,aarch64-unknown-linux-musl 65 | - uses: Swatinem/rust-cache@v2 66 | with: 67 | key: ${{ runner.os }}-${{ hashFiles('Cargo.lock') }}-1.79.0-binary 68 | - name: Install musl tools 69 | run: sudo apt-get install -y musl-tools 70 | - name: Build (x86) 71 | run: | 72 | cargo build --release --target x86_64-unknown-linux-musl 73 | strip target/x86_64-unknown-linux-musl/release/amber 74 | - name: Build (ARM64) 75 | run: cross build --release --target aarch64-unknown-linux-musl 76 | - name: Rename 77 | run: | 78 | mkdir artifacts 79 | cp target/x86_64-unknown-linux-musl/release/amber artifacts/amber-x86_64-unknown-linux-musl 80 | cp target/aarch64-unknown-linux-musl/release/amber artifacts/amber-aarch64-unknown-linux-musl 81 | - uses: actions/upload-artifact@v4 82 | with: 83 | name: binaries-linux 84 | path: artifacts/* 85 | - name: Release 86 | uses: softprops/action-gh-release@v2 87 | if: startsWith(github.ref, 'refs/tags/') 88 | with: 89 | files: | 90 | artifacts/amber-x86_64-unknown-linux-musl 91 | artifacts/amber-aarch64-unknown-linux-musl 92 | generate_release_notes: true 93 | 94 | macos-binary: 95 | runs-on: macos-latest 96 | 97 | steps: 98 | - uses: actions/checkout@v2 99 | - uses: dtolnay/rust-toolchain@master 100 | with: 101 | toolchain: 1.79.0 102 | components: clippy, rustfmt 103 | - uses: Swatinem/rust-cache@v2 104 | with: 105 | key: ${{ runner.os }}-${{ hashFiles('Cargo.lock') }}-1.79.0-binary 106 | - name: Build 107 | run: cargo build --release 108 | - name: Rename 109 | run: | 110 | mkdir artifacts 111 | cp target/release/amber artifacts/amber-x86_64-apple-darwin 112 | - uses: actions/upload-artifact@v4 113 | with: 114 | name: binaries-macos 115 | path: artifacts/* 116 | - name: Release 117 | uses: softprops/action-gh-release@v2 118 | if: startsWith(github.ref, 'refs/tags/') 119 | with: 120 | files: artifacts/amber-x86_64-apple-darwin 121 | generate_release_notes: true 122 | 123 | windows-binary: 124 | runs-on: windows-latest 125 | 126 | steps: 127 | - uses: actions/checkout@v2 128 | - uses: dtolnay/rust-toolchain@master 129 | with: 130 | toolchain: 1.79.0 131 | components: clippy, rustfmt 132 | - uses: Swatinem/rust-cache@v2 133 | with: 134 | key: ${{ runner.os }}-${{ hashFiles('Cargo.lock') }}-1.79.0-binary 135 | - name: Build 136 | run: cargo build --release 137 | - name: Rename 138 | run: | 139 | mkdir artifacts 140 | cp target/release/amber.exe artifacts/amber-x86_64-pc-windows-gnu.exe 141 | - uses: actions/upload-artifact@v4 142 | with: 143 | name: binaries-windows 144 | path: artifacts/* 145 | - name: Release 146 | uses: softprops/action-gh-release@v2 147 | if: startsWith(github.ref, 'refs/tags/') 148 | with: 149 | files: artifacts/amber-x86_64-pc-windows-gnu.exe 150 | generate_release_notes: true 151 | -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | 3 | use anyhow::*; 4 | use clap::{Parser, Subcommand}; 5 | use once_cell::sync::Lazy; 6 | 7 | pub fn init() -> Cmd { 8 | let cmd = Cmd::parse(); 9 | cmd.opt.init_logger(); 10 | cmd 11 | } 12 | 13 | #[derive(Parser, Debug)] 14 | #[clap(version = VERSION_SHA.as_str())] 15 | pub struct Cmd { 16 | #[clap(flatten)] 17 | pub opt: Opt, 18 | #[clap(subcommand)] 19 | pub sub: SubCommand, 20 | } 21 | 22 | #[derive(Subcommand, Debug)] 23 | pub enum SubCommand { 24 | /// Initialize a new directory 25 | Init { 26 | /// Display only secret key 27 | #[clap(long, global = true)] 28 | only_secret_key: bool, 29 | }, 30 | /// Add or update a secret 31 | Encrypt { 32 | /// Key, must be all capital ASCII characters, digits, and underscores 33 | key: String, 34 | /// Value. If omitted, read from stdin 35 | value: Option, 36 | }, 37 | /// Generate a new strong secret value, and add it to the repository 38 | Generate { 39 | /// Key, must be all capital ASCII characters, digits, and underscores 40 | key: String, 41 | }, 42 | /// Remove a secret 43 | Remove { 44 | /// Key, must be all capital ASCII characters, digits, and underscores 45 | key: String, 46 | }, 47 | /// Print all of the secrets 48 | Print { 49 | /// Secrets output style, possible values are: setenv, json, yaml, pure. The default is setenv. 50 | #[clap(long, default_value = "setenv")] 51 | style: PrintStyle, 52 | }, 53 | /// Run a command with all of the secrets set as environment variables 54 | Exec { 55 | /// Command to run 56 | cmd: String, 57 | /// Command line arguments to pass to the command 58 | args: Vec, 59 | }, 60 | /// Write the contents of a secret to the given file. 61 | WriteFile { 62 | /// The key for the secret 63 | #[clap(long)] 64 | key: String, 65 | /// File path to write to 66 | #[clap(long)] 67 | dest: PathBuf, 68 | }, 69 | } 70 | 71 | #[derive(Parser, Clone, Debug)] 72 | pub enum PrintStyle { 73 | /// Output with `export` prefix, can be evaled in shell. 74 | SetEnv, 75 | /// Output as object with `key` and `value` attributes. 76 | Json, 77 | /// Output as object with `key` and `value` attributes. 78 | Yaml, 79 | } 80 | 81 | impl core::str::FromStr for PrintStyle { 82 | type Err = anyhow::Error; 83 | 84 | fn from_str(s: &str) -> Result { 85 | match s { 86 | "setenv" => Ok(PrintStyle::SetEnv), 87 | "json" => Ok(PrintStyle::Json), 88 | "yaml" => Ok(PrintStyle::Yaml), 89 | _ => Err(anyhow!("Invalid option for Print command")), 90 | } 91 | } 92 | } 93 | 94 | static VERSION_SHA: Lazy = Lazy::new(|| { 95 | let pkgver = env!("CARGO_PKG_VERSION"); 96 | match option_env!("VERGEN_GIT_SHA") { 97 | None => pkgver.to_owned(), 98 | Some(gitsha) => format!("{pkgver} (Git SHA1 {gitsha})"), 99 | } 100 | }); 101 | 102 | const DEFAULT_AMBER_YAML: &str = "amber.yaml"; 103 | 104 | /// Utility to store encrypted secrets in version trackable plain text files. 105 | #[derive(Parser, Debug)] 106 | pub struct Opt { 107 | /// Turn on verbose output 108 | #[clap(short, long, global = true)] 109 | pub verbose: bool, 110 | /// amber.yaml file location 111 | #[clap(long, global = true, env = "AMBER_YAML")] 112 | pub amber_yaml: Option, 113 | /// Disable masking of secret values during exec 114 | #[clap(long, global = true)] 115 | pub unmasked: bool, 116 | } 117 | 118 | impl Opt { 119 | /// Initialize the logger based on command line settings 120 | pub fn init_logger(&self) { 121 | use env_logger::{Builder, Target}; 122 | use log::LevelFilter::*; 123 | let mut builder = Builder::from_default_env(); 124 | let level = if self.verbose { Debug } else { Info }; 125 | builder.filter_module(env!("CARGO_CRATE_NAME"), level); 126 | builder.target(Target::Stderr).init(); 127 | } 128 | 129 | pub fn find_amber_yaml(&mut self) -> Result<&Path> { 130 | if self.amber_yaml.is_none() { 131 | for dir in std::env::current_dir()?.ancestors() { 132 | let amber_yaml: PathBuf = dir.join(DEFAULT_AMBER_YAML); 133 | log::debug!("Checking if file {:?} exists", &amber_yaml); 134 | if amber_yaml.exists() { 135 | self.amber_yaml = Some(amber_yaml); 136 | break; 137 | } 138 | } 139 | } 140 | self.amber_yaml 141 | .as_deref() 142 | .with_context(|| format!("No file named {DEFAULT_AMBER_YAML} found")) 143 | } 144 | 145 | pub fn find_amber_yaml_or_default(&mut self) -> &Path { 146 | self.amber_yaml 147 | .get_or_insert_with(|| Path::new(DEFAULT_AMBER_YAML).to_owned()) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # amber 2 | 3 | [![Rust](https://github.com/fpco/amber/actions/workflows/rust.yml/badge.svg)](https://github.com/fpco/amber/actions/workflows/rust.yml) 4 | 5 | Manage secret values in-repo via public key cryptography. See [the announcement blog post](https://www.fpcomplete.com/blog/announcing-amber-ci-secret-tool/) for more motivation. 6 | 7 | Amber provides the ability to securely store secret data in a plain-text file. Secrets can be encrypted by anyone with access to the file, without the ability to read those files without a secret key. The file format is a plain text YAML file which minimizes diffs on value changes, making it amenable to tracking changes in version control. 8 | 9 | The primary use case for Amber is storing secret values for Continuous Integration systems. In most CI secrets management systems, there is no way to track the changes in values over time. With Amber, the public key and encrypted values live inside the repo, ensuring future runs of the same commit will either fail (if you've misplaced/changed the key) or have identical inputs. 10 | 11 | ## Install 12 | 13 | See below for OS specific packages. Alternatively, you can install from source by [installing Rust](https://www.rust-lang.org/tools/install) and running `cargo install --git https://github.com/fpco/amber`. Binaries are available on the [release page](https://github.com/fpco/amber/releases). Place the executable on your `PATH` and ensure that the executable bit is set (for non-Windows platforms). 14 | 15 | ### Arch Linux 16 | 17 | There is a [AUR package available for Amber](https://aur.archlinux.org/packages/amber-secrets/). Install with `makepkg` or your preferred helper: 18 | 19 | ``` 20 | git clone https://aur.archlinux.org/amber-secrets.git 21 | cd amber-secrets 22 | makepkg -si 23 | ``` 24 | 25 | ### Nix/NixOS 26 | 27 | Amber is available as part of nixpkgs under the name `amber-secret`. 28 | 29 | ### GitHub actions 30 | 31 | For installing and caching `amber`, in GitHub actions workflow you can 32 | use [psibi/setup-amber](https://github.com/psibi/setup-amber). 33 | 34 | Example usage: 35 | 36 | ``` yaml 37 | - uses: psibi/setup-amber@v1.0 38 | with: 39 | amber-version: 'v0.1.3' # Optional version, otherwise latest 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | ``` 43 | 44 | The 45 | [GITHUB_TOKEN](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#about-the-github_token-secret) 46 | secret is optional, but is recommended to avoid rate limiting. You 47 | don't have to set up anything specific for it since for each workflow 48 | run, GitHub automatically populates that token for you. 49 | 50 | The [amber-demo](https://github.com/psibi/amber-demo) repository has an example workflow showcasing the 51 | usage of this GitHub action. 52 | 53 | ## Usage 54 | 55 | Running `amber --help` will give you full, up to date set of instructions. The `--amber-yaml` option, or the `AMBER_YAML` environment variable, can be used to specify the location of the file containing your secret values. If unspecified, it will default to `amber.yaml`. The typical workflow is: 56 | 57 | * `amber init` to create a new secret key and `amber.yaml` file. 58 | * Securely store that secret key, such as in a password manager. Additionally, if desired, put that secret key in your CI system's secrets. 59 | * Add additional secrets with `amber encrypt`. 60 | * Use the "read from stdin" feature to encrypt whole files `amber encrypt SECRET_SAUCE < my-secret-sauce.txt` 61 | * Commit your `amber.yaml` file into your repository. 62 | * Within your CI scripts, or when using your secrets on your own system: 63 | * Set the `AMBER_SECRET` environment variable to your secret key. 64 | * Use `amber print` to see a list of your secrets. 65 | * Use `amber exec ...` to execute subcommands with the secrets available. 66 | * Over time, use `amber encrypt` to add new secrets or update existing secrets, and `amber remove` to remove a secret entirely. 67 | * By storing the secrets in Git, you'll always be able to recover old secret values. 68 | 69 | Here's a sample shell session: 70 | 71 | ```shellsession 72 | $ amber init 73 | Your secret key is: 15aa07775395303732870cff2cc35c26f94af3344cf0f85d230aa004234d9764 74 | Please save this key immediately! If you lose it, you will lose access to your secrets. 75 | Recommendation: keep it in a password manager 76 | If you're using this for CI, please update your CI configuration with a secret environment variable 77 | export AMBER_SECRET=15aa07775395303732870cff2cc35c26f94af3344cf0f85d230aa004234d9764 78 | $ amber encrypt PASSWORD deadbeef 79 | $ amber print 80 | Error: Error loading secret key from environment variable AMBER_SECRET 81 | 82 | Caused by: 83 | environment variable not found 84 | $ export AMBER_SECRET=15aa07775395303732870cff2cc35c26f94af3344cf0f85d230aa004234d9764 85 | $ amber print 86 | export PASSWORD="deadbeef" 87 | $ amber exec -- sh -c 'echo $PASSWORD' 88 | deadbeef 89 | $ cat amber.yaml 90 | --- 91 | file_format_version: 1 92 | public_key: 9a4eb57571201fe413a5a9d583a070d180669928f0b98152ad93454cf5079860 93 | secrets: 94 | - name: PASSWORD 95 | sha256: 2baf1f40105d9501fe319a8ec463fdf4325a2a5df445adf3f572f626253678c9 96 | cipher: c7f3d90e15b2d37801055d9773e6bd1e4b36120987bf31c6f111d5d69acb6d020a5f532ea035c272465f2a6e43c55fb009bf03a5c7a93581 97 | $ amber encrypt PASSWORD deadbeef 98 | [2021-08-13T10:45:13Z INFO amber::config] New value matches old value, doing nothing 99 | $ amber encrypt PASSWORD deadbeef2 100 | [2021-08-13T10:45:16Z WARN amber::config] Overwriting old secret value 101 | $ amber print 102 | export PASSWORD="deadbeef2" 103 | $ amber remove PASSWORD 104 | $ amber print 105 | $ cat amber.yaml 106 | --- 107 | file_format_version: 1 108 | public_key: 9a4eb57571201fe413a5a9d583a070d180669928f0b98152ad93454cf5079860 109 | secrets: [] 110 | ``` 111 | 112 | ## Authors 113 | 114 | This tool was written by the [FP Complete](https://www.fpcomplete.com/) engineering team. It was originally part of a deployment system for our [Kube360 Kubernetes software collection](https://www.fpcomplete.com/products/kube360/). We decided to extract the generalizable parts to a standalone tool to improve Continuous Integration workflows. 115 | 116 | If you have a use case outside of CI, or additional features you think would fit in well, please let us know in the issue tracker! 117 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod cli; 2 | mod config; 3 | mod exec; 4 | mod mask; 5 | 6 | use std::{io::Read, path::Path}; 7 | 8 | use anyhow::*; 9 | use base64::Engine; 10 | use crypto_box::{aead::OsRng, SecretKey}; 11 | use exec::CommandExecExt; 12 | use serde::Serialize; 13 | 14 | #[derive(Serialize)] 15 | struct KeyValue<'a> { 16 | key: &'a str, 17 | value: &'a str, 18 | } 19 | 20 | impl<'a, K, V> From<&'a (K, V)> for KeyValue<'a> 21 | where 22 | K: AsRef, 23 | V: AsRef, 24 | { 25 | fn from((key, value): &'a (K, V)) -> Self { 26 | KeyValue { 27 | key: key.as_ref(), 28 | value: value.as_ref(), 29 | } 30 | } 31 | } 32 | 33 | fn main() -> Result<()> { 34 | let cmd = cli::init(); 35 | log::debug!("{:?}", cmd); 36 | match cmd.sub { 37 | cli::SubCommand::Init { only_secret_key } => init(cmd.opt, only_secret_key), 38 | cli::SubCommand::Encrypt { key, value } => encrypt(cmd.opt, key, value), 39 | cli::SubCommand::Generate { key } => generate(cmd.opt, key), 40 | cli::SubCommand::Remove { key } => remove(cmd.opt, key), 41 | cli::SubCommand::Print { style } => print(cmd.opt, style), 42 | cli::SubCommand::Exec { cmd: cmd_, args } => exec(cmd.opt, cmd_, args), 43 | cli::SubCommand::WriteFile { key, dest } => write_file(cmd.opt, &key, &dest), 44 | } 45 | } 46 | 47 | fn init(mut opt: cli::Opt, only_secret_key: bool) -> Result<()> { 48 | let (secret_key, config) = config::Config::new(); 49 | let secret_key = hex::encode(secret_key.to_bytes()); 50 | 51 | config.save(opt.find_amber_yaml_or_default())?; 52 | 53 | if only_secret_key { 54 | print!("{secret_key}"); 55 | } else { 56 | eprintln!("Your secret key is: {secret_key}"); 57 | eprintln!( 58 | "Please save this key immediately! If you lose it, you will lose access to your secrets." 59 | ); 60 | eprintln!("Recommendation: keep it in a password manager"); 61 | eprintln!("If you're using this for CI, please update your CI configuration with a secret environment variable"); 62 | println!("export {}={}", config::SECRET_KEY_ENV, secret_key); 63 | } 64 | Ok(()) 65 | } 66 | 67 | fn validate_key(key: &str) -> Result<()> { 68 | ensure!(!key.is_empty(), "Cannot provide an empty key"); 69 | if key 70 | .chars() 71 | .all(|c| c.is_ascii_digit() || c.is_ascii_uppercase() || c == '_') 72 | { 73 | Ok(()) 74 | } else { 75 | Err(anyhow!( 76 | "Key must be exclusively upper case ASCII, digits, and underscores" 77 | )) 78 | } 79 | } 80 | 81 | fn encrypt(mut opt: cli::Opt, key: String, value: Option) -> Result<()> { 82 | validate_key(&key)?; 83 | let amber_yaml = opt.find_amber_yaml()?; 84 | let mut config = config::Config::load(amber_yaml)?; 85 | let value = value.map_or_else( 86 | || { 87 | log::debug!("No value provided on command line, taking from stdin"); 88 | eprintln!("Enter secret value (send EOF when done)"); 89 | eprintln!(); 90 | let stdin = std::io::stdin(); 91 | let mut stdin = stdin.lock(); 92 | let mut buffer = String::new(); 93 | stdin 94 | .read_to_string(&mut buffer) 95 | .map(|_size| buffer) 96 | .map_err(anyhow::Error::new) 97 | }, 98 | Ok, 99 | )?; 100 | config.encrypt(key, &value)?; 101 | config.save(amber_yaml) 102 | } 103 | 104 | fn generate(opt: cli::Opt, key: String) -> Result<()> { 105 | let value = SecretKey::generate(&mut OsRng); 106 | let value = base64::engine::general_purpose::STANDARD.encode(value.to_bytes()); 107 | let msg = format!("Your new secret value is {key}: {value}"); 108 | encrypt(opt, key, Some(value))?; 109 | println!("{}", &msg); 110 | Ok(()) 111 | } 112 | 113 | fn remove(mut opt: cli::Opt, key: String) -> Result<()> { 114 | validate_key(&key)?; 115 | let amber_yaml = opt.find_amber_yaml()?; 116 | let mut config = config::Config::load(amber_yaml)?; 117 | config.remove(&key); 118 | config.save(amber_yaml) 119 | } 120 | 121 | fn print(mut opt: cli::Opt, style: cli::PrintStyle) -> Result<()> { 122 | let config = config::Config::load(opt.find_amber_yaml()?)?; 123 | let secret = config.load_secret_key()?; 124 | let pairs: Result> = config.iter_secrets(&secret).collect(); 125 | let mut pairs = pairs?; 126 | pairs.sort_by(|x, y| x.0.cmp(y.0)); 127 | 128 | fn to_objs<'a, K, V, I>(p: I) -> Vec> 129 | where 130 | I: IntoIterator, 131 | K: AsRef + 'a, 132 | V: AsRef + 'a, 133 | { 134 | p.into_iter().map(KeyValue::from).collect::>() 135 | } 136 | match style { 137 | cli::PrintStyle::SetEnv => pairs 138 | .iter() 139 | .for_each(|(key, value)| println!("export {key}={value:?}")), 140 | cli::PrintStyle::Json => { 141 | let secrets = to_objs(&pairs); 142 | serde_json::to_writer(std::io::stdout(), &secrets)?; 143 | } 144 | cli::PrintStyle::Yaml => { 145 | let secrets = to_objs(&pairs); 146 | serde_yaml::to_writer(std::io::stdout(), &secrets)?; 147 | } 148 | } 149 | 150 | Ok(()) 151 | } 152 | 153 | fn exec(mut opt: cli::Opt, cmd: String, args: Vec) -> Result<()> { 154 | let config = config::Config::load(opt.find_amber_yaml()?)?; 155 | let secret_key = config.load_secret_key()?; 156 | 157 | let mut cmd = std::process::Command::new(cmd); 158 | cmd.args(args); 159 | 160 | let mut secrets = Vec::new(); 161 | for pair in config.iter_secrets(&secret_key) { 162 | let (name, value) = pair?; 163 | log::debug!("Setting env var in child process: {}", name); 164 | cmd.env(name, &value); 165 | if !opt.unmasked { 166 | secrets.push(value); 167 | } 168 | } 169 | 170 | if opt.unmasked { 171 | cmd.emulate_exec("Launching child process")?; 172 | } else { 173 | mask::run_masked(cmd, &secrets)?; 174 | } 175 | 176 | Ok(()) 177 | } 178 | 179 | fn write_file(mut opt: cli::Opt, key: &str, dest: &Path) -> Result<()> { 180 | let config = config::Config::load(opt.find_amber_yaml()?)?; 181 | let secret_key = config.load_secret_key()?; 182 | let value = config.get_secret(key, &secret_key)?; 183 | std::fs::write(dest, value) 184 | .with_context(|| format!("Unable to write to file {}", dest.display())) 185 | } 186 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryInto; 2 | use std::{collections::HashMap, path::Path}; 3 | 4 | use anyhow::*; 5 | use crypto_box::aead::OsRng; 6 | use crypto_box::{PublicKey, SecretKey}; 7 | use serde::{Deserialize, Serialize}; 8 | use sha2::Digest; 9 | use sha2::Sha256; 10 | 11 | /// Environment variable name containing the secret key 12 | pub const SECRET_KEY_ENV: &str = "AMBER_SECRET"; 13 | 14 | /// Current version of the file format 15 | const FILE_FORMAT_VERSION: u32 = 1; 16 | 17 | /// Raw version of [Config], the thing actually serialized/deserialized 18 | #[derive(Serialize, Deserialize, Debug)] 19 | #[serde(deny_unknown_fields)] 20 | struct ConfigRaw { 21 | /// Version of the file format represented here 22 | file_format_version: u32, 23 | 24 | /// Hex encoded public key 25 | public_key: String, 26 | 27 | /// Use a Vec instead of a HashMap to get guaranteed order in the output for 28 | /// minimal deltas 29 | secrets: Vec, 30 | } 31 | 32 | /// Raw version of [Secret], allowing for consistent ordering 33 | #[derive(Serialize, Deserialize, Debug)] 34 | #[serde(deny_unknown_fields)] 35 | struct SecretRaw { 36 | name: String, 37 | sha256: String, 38 | cipher: String, 39 | } 40 | 41 | /// Config file 42 | #[derive(Debug)] 43 | pub struct Config { 44 | /// Public key in hex 45 | public_key: PublicKey, 46 | /// Encrypted secrets 47 | secrets: HashMap, 48 | } 49 | 50 | /// The contents of an individual secret, still encrypted 51 | #[derive(Serialize, Deserialize, Debug)] 52 | #[serde(deny_unknown_fields)] 53 | struct Secret { 54 | /// Digest of the plaintext, to avoid unnecessary updates and minimize diffs 55 | sha256: [u8; 32], 56 | /// Ciphertext encrypted with our public key 57 | cipher: Vec, 58 | } 59 | 60 | impl Config { 61 | /// Create a new keypair and config file 62 | pub fn new() -> (SecretKey, Self) { 63 | let secret_key = SecretKey::generate(&mut OsRng); 64 | let config = Config { 65 | public_key: secret_key.public_key(), 66 | secrets: HashMap::new(), 67 | }; 68 | (secret_key, config) 69 | } 70 | 71 | fn from_raw(raw: ConfigRaw) -> Result { 72 | ensure!( 73 | raw.file_format_version == FILE_FORMAT_VERSION, 74 | "Unsupported file format detected. Detected format is {}, we only support {}.", 75 | raw.file_format_version, 76 | FILE_FORMAT_VERSION 77 | ); 78 | let public_key: [u8; 32] = hex::decode(&raw.public_key) 79 | .ok() 80 | .context("Public key is not hex")? 81 | .try_into() 82 | .map_err(|_| anyhow!("Invalid Public key"))?; 83 | 84 | let public_key = PublicKey::from(public_key); 85 | 86 | let mut secrets = HashMap::new(); 87 | 88 | for pair in raw.secrets.into_iter().map(Secret::from_raw) { 89 | let (key, secret) = pair?; 90 | ensure!( 91 | !secrets.contains_key(&key), 92 | "Duplicated secret key: {}", 93 | key 94 | ); 95 | let old = secrets.insert(key, secret); 96 | assert!(old.is_none()); 97 | } 98 | Ok(Config { 99 | public_key, 100 | secrets, 101 | }) 102 | } 103 | 104 | fn to_raw(&self) -> ConfigRaw { 105 | let mut secrets: Vec = self 106 | .secrets 107 | .iter() 108 | .map(|(key, value)| SecretRaw { 109 | name: key.clone(), 110 | sha256: hex::encode(value.sha256), 111 | cipher: hex::encode(&value.cipher), 112 | }) 113 | .collect(); 114 | secrets.sort_unstable_by(|x, y| x.name.cmp(&y.name)); 115 | ConfigRaw { 116 | file_format_version: FILE_FORMAT_VERSION, 117 | public_key: hex::encode(&self.public_key), 118 | secrets, 119 | } 120 | } 121 | 122 | pub fn load>(path: P) -> Result { 123 | let path = path.as_ref(); 124 | let res: Result = (|| { 125 | let mut file = fs_err::File::open(path)?; 126 | let config = serde_yaml::from_reader(&mut file)?; 127 | Config::from_raw(config) 128 | })(); 129 | res.with_context(|| format!("Unable to read file {}", path.display())) 130 | } 131 | 132 | pub fn save>(&self, path: P) -> Result<()> { 133 | let path = path.as_ref(); 134 | let res: Result<()> = (|| { 135 | let parent = path.parent().context("File must have a parent directory")?; 136 | fs_err::create_dir_all(parent).context("Unable to create parent directory")?; 137 | let mut file = fs_err::File::create(path)?; 138 | serde_yaml::to_writer(&mut file, &self.to_raw())?; 139 | Ok(()) 140 | })(); 141 | res.with_context(|| format!("Unable to write file {}", path.display())) 142 | } 143 | 144 | /// Encrypt a new value, replacing as necessary 145 | pub fn encrypt(&mut self, key: String, value: &str) -> Result<()> { 146 | let mut hasher = Sha256::new(); 147 | hasher.update(value); 148 | let hash = hasher.finalize_reset().into(); 149 | if let Some(old_secret) = self.secrets.get(&key) { 150 | if old_secret.sha256 == hash { 151 | log::info!("New value matches old value, doing nothing"); 152 | return Ok(()); 153 | } else { 154 | log::warn!("Overwriting old secret value"); 155 | } 156 | } 157 | 158 | let cipher = self 159 | .public_key 160 | .seal(&mut OsRng, value.as_bytes()) 161 | .map_err(|_| anyhow!("Error during encryption"))?; 162 | 163 | self.secrets.insert( 164 | key, 165 | Secret { 166 | cipher, 167 | sha256: hash, 168 | }, 169 | ); 170 | Ok(()) 171 | } 172 | 173 | /// Remove a value, if present 174 | pub fn remove(&mut self, key: &str) { 175 | if self.secrets.remove(key).is_none() { 176 | log::warn!("Asked to remove non-present secret {}, doing nothing", key); 177 | } 178 | } 179 | 180 | /// Get the secret key from the environment variable 181 | /// 182 | /// Validates that it matches up with the public key 183 | pub fn load_secret_key(&self) -> Result { 184 | (|| { 185 | let hex = std::env::var(SECRET_KEY_ENV)?; 186 | let bs: [u8; 32] = hex::decode(hex) 187 | .ok() 188 | .context("Invalid hex encoding")? 189 | .try_into() 190 | .map_err(|_| anyhow!("Invalid secret key"))?; 191 | let secret: SecretKey = SecretKey::from(bs); 192 | ensure!( 193 | secret.public_key() == self.public_key, 194 | "Secret key does not match config file's public key" 195 | ); 196 | Ok(secret) 197 | })() 198 | .with_context(|| { 199 | format!("Error loading secret key from environment variable {SECRET_KEY_ENV}") 200 | }) 201 | } 202 | 203 | /// Iterate over the secrets 204 | pub fn iter_secrets<'a>( 205 | &'a self, 206 | secret_key: &'a SecretKey, 207 | ) -> impl Iterator> { 208 | self.secrets 209 | .iter() 210 | .map(move |(key, secret)| secret.decrypt(secret_key, key).map(|plain| (key, plain))) 211 | } 212 | 213 | /// Look up a specific secret value 214 | pub(crate) fn get_secret(&self, key: &str, secret_key: &SecretKey) -> Result { 215 | self.secrets 216 | .get(key) 217 | .with_context(|| format!("Key does not exist: {key}")) 218 | .and_then(|secret| secret.decrypt(secret_key, key)) 219 | } 220 | } 221 | 222 | impl Secret { 223 | fn from_raw(raw: SecretRaw) -> Result<(String, Self)> { 224 | let digest: [u8; 32] = hex::decode(&raw.sha256) 225 | .ok() 226 | .context("Non-hex sha256")? 227 | .try_into() 228 | .map_err(|_| anyhow!("Error parsing into digest"))?; 229 | Ok(( 230 | raw.name, 231 | Secret { 232 | sha256: digest, 233 | cipher: hex::decode(&raw.cipher) 234 | .ok() 235 | .context("Non-hex ciphertext")?, 236 | }, 237 | )) 238 | } 239 | 240 | /// Decrypt this secret, key is used for error message displays only 241 | fn decrypt(&self, secret_key: &SecretKey, key: &str) -> Result { 242 | (|| { 243 | let plain = secret_key 244 | .unseal(&self.cipher[..]) 245 | .map_err(|_| anyhow!("Unable to decrypt secret"))?; 246 | let mut hasher = Sha256::new(); 247 | hasher.update(&plain); 248 | let digest: [u8; 32] = hasher.finalize_reset().into(); 249 | ensure!( 250 | digest == self.sha256, 251 | "Hash mismatch, expected {}, received {}", 252 | hex::encode(self.sha256), 253 | hex::encode(digest) 254 | ); 255 | String::from_utf8(plain.to_vec()).context("Invalid UTF-8 encoding") 256 | })() 257 | .with_context(|| format!("Error while decrypting secret named {key}")) 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "adler" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 10 | 11 | [[package]] 12 | name = "aead" 13 | version = "0.5.2" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" 16 | dependencies = [ 17 | "crypto-common", 18 | "generic-array", 19 | ] 20 | 21 | [[package]] 22 | name = "aho-corasick" 23 | version = "1.1.3" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 26 | dependencies = [ 27 | "memchr", 28 | ] 29 | 30 | [[package]] 31 | name = "amber" 32 | version = "0.1.5" 33 | dependencies = [ 34 | "aho-corasick", 35 | "anyhow", 36 | "assert_cmd", 37 | "base64", 38 | "clap", 39 | "crypto_box", 40 | "env_logger", 41 | "fs-err", 42 | "hex", 43 | "log", 44 | "once_cell", 45 | "serde", 46 | "serde_json", 47 | "serde_yaml", 48 | "sha2", 49 | "tempfile", 50 | "vergen", 51 | ] 52 | 53 | [[package]] 54 | name = "anstream" 55 | version = "0.6.14" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" 58 | dependencies = [ 59 | "anstyle", 60 | "anstyle-parse", 61 | "anstyle-query", 62 | "anstyle-wincon", 63 | "colorchoice", 64 | "is_terminal_polyfill", 65 | "utf8parse", 66 | ] 67 | 68 | [[package]] 69 | name = "anstyle" 70 | version = "1.0.7" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" 73 | 74 | [[package]] 75 | name = "anstyle-parse" 76 | version = "0.2.4" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" 79 | dependencies = [ 80 | "utf8parse", 81 | ] 82 | 83 | [[package]] 84 | name = "anstyle-query" 85 | version = "1.1.0" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" 88 | dependencies = [ 89 | "windows-sys", 90 | ] 91 | 92 | [[package]] 93 | name = "anstyle-wincon" 94 | version = "3.0.3" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" 97 | dependencies = [ 98 | "anstyle", 99 | "windows-sys", 100 | ] 101 | 102 | [[package]] 103 | name = "anyhow" 104 | version = "1.0.86" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" 107 | 108 | [[package]] 109 | name = "arc-swap" 110 | version = "1.7.1" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" 113 | 114 | [[package]] 115 | name = "assert_cmd" 116 | version = "2.0.14" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "ed72493ac66d5804837f480ab3766c72bdfab91a65e565fc54fa9e42db0073a8" 119 | dependencies = [ 120 | "anstyle", 121 | "bstr", 122 | "doc-comment", 123 | "predicates", 124 | "predicates-core", 125 | "predicates-tree", 126 | "wait-timeout", 127 | ] 128 | 129 | [[package]] 130 | name = "autocfg" 131 | version = "1.3.0" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 134 | 135 | [[package]] 136 | name = "base64" 137 | version = "0.22.1" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 140 | 141 | [[package]] 142 | name = "bitflags" 143 | version = "1.3.2" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 146 | 147 | [[package]] 148 | name = "bitflags" 149 | version = "2.5.0" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" 152 | 153 | [[package]] 154 | name = "blake2" 155 | version = "0.10.6" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" 158 | dependencies = [ 159 | "digest", 160 | ] 161 | 162 | [[package]] 163 | name = "block-buffer" 164 | version = "0.10.4" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 167 | dependencies = [ 168 | "generic-array", 169 | ] 170 | 171 | [[package]] 172 | name = "bstr" 173 | version = "1.9.1" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" 176 | dependencies = [ 177 | "memchr", 178 | "regex-automata", 179 | "serde", 180 | ] 181 | 182 | [[package]] 183 | name = "btoi" 184 | version = "0.4.3" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "9dd6407f73a9b8b6162d8a2ef999fe6afd7cc15902ebf42c5cd296addf17e0ad" 187 | dependencies = [ 188 | "num-traits", 189 | ] 190 | 191 | [[package]] 192 | name = "cfg-if" 193 | version = "1.0.0" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 196 | 197 | [[package]] 198 | name = "cipher" 199 | version = "0.4.4" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" 202 | dependencies = [ 203 | "crypto-common", 204 | "inout", 205 | "zeroize", 206 | ] 207 | 208 | [[package]] 209 | name = "clap" 210 | version = "4.5.7" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" 213 | dependencies = [ 214 | "clap_builder", 215 | "clap_derive", 216 | ] 217 | 218 | [[package]] 219 | name = "clap_builder" 220 | version = "4.5.7" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" 223 | dependencies = [ 224 | "anstream", 225 | "anstyle", 226 | "clap_lex", 227 | "strsim", 228 | ] 229 | 230 | [[package]] 231 | name = "clap_derive" 232 | version = "4.5.5" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" 235 | dependencies = [ 236 | "heck", 237 | "proc-macro2", 238 | "quote", 239 | "syn", 240 | ] 241 | 242 | [[package]] 243 | name = "clap_lex" 244 | version = "0.7.1" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" 247 | 248 | [[package]] 249 | name = "clru" 250 | version = "0.6.1" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "b8191fa7302e03607ff0e237d4246cc043ff5b3cb9409d995172ba3bea16b807" 253 | 254 | [[package]] 255 | name = "colorchoice" 256 | version = "1.0.1" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" 259 | 260 | [[package]] 261 | name = "cpufeatures" 262 | version = "0.2.12" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" 265 | dependencies = [ 266 | "libc", 267 | ] 268 | 269 | [[package]] 270 | name = "crc32fast" 271 | version = "1.4.2" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 274 | dependencies = [ 275 | "cfg-if", 276 | ] 277 | 278 | [[package]] 279 | name = "crypto-common" 280 | version = "0.1.6" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 283 | dependencies = [ 284 | "generic-array", 285 | "rand_core", 286 | "typenum", 287 | ] 288 | 289 | [[package]] 290 | name = "crypto_box" 291 | version = "0.9.1" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "16182b4f39a82ec8a6851155cc4c0cda3065bb1db33651726a29e1951de0f009" 294 | dependencies = [ 295 | "aead", 296 | "blake2", 297 | "crypto_secretbox", 298 | "curve25519-dalek", 299 | "salsa20", 300 | "subtle", 301 | "zeroize", 302 | ] 303 | 304 | [[package]] 305 | name = "crypto_secretbox" 306 | version = "0.1.1" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "b9d6cf87adf719ddf43a805e92c6870a531aedda35ff640442cbaf8674e141e1" 309 | dependencies = [ 310 | "aead", 311 | "cipher", 312 | "generic-array", 313 | "poly1305", 314 | "salsa20", 315 | "subtle", 316 | "zeroize", 317 | ] 318 | 319 | [[package]] 320 | name = "curve25519-dalek" 321 | version = "4.1.3" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" 324 | dependencies = [ 325 | "cfg-if", 326 | "cpufeatures", 327 | "curve25519-dalek-derive", 328 | "fiat-crypto", 329 | "rustc_version", 330 | "subtle", 331 | "zeroize", 332 | ] 333 | 334 | [[package]] 335 | name = "curve25519-dalek-derive" 336 | version = "0.1.1" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" 339 | dependencies = [ 340 | "proc-macro2", 341 | "quote", 342 | "syn", 343 | ] 344 | 345 | [[package]] 346 | name = "deranged" 347 | version = "0.3.11" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 350 | dependencies = [ 351 | "powerfmt", 352 | ] 353 | 354 | [[package]] 355 | name = "difflib" 356 | version = "0.4.0" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" 359 | 360 | [[package]] 361 | name = "digest" 362 | version = "0.10.7" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 365 | dependencies = [ 366 | "block-buffer", 367 | "crypto-common", 368 | "subtle", 369 | ] 370 | 371 | [[package]] 372 | name = "doc-comment" 373 | version = "0.3.3" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 376 | 377 | [[package]] 378 | name = "dunce" 379 | version = "1.0.4" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" 382 | 383 | [[package]] 384 | name = "env_filter" 385 | version = "0.1.0" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" 388 | dependencies = [ 389 | "log", 390 | "regex", 391 | ] 392 | 393 | [[package]] 394 | name = "env_logger" 395 | version = "0.11.3" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" 398 | dependencies = [ 399 | "anstream", 400 | "anstyle", 401 | "env_filter", 402 | "humantime", 403 | "log", 404 | ] 405 | 406 | [[package]] 407 | name = "equivalent" 408 | version = "1.0.1" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 411 | 412 | [[package]] 413 | name = "errno" 414 | version = "0.3.9" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 417 | dependencies = [ 418 | "libc", 419 | "windows-sys", 420 | ] 421 | 422 | [[package]] 423 | name = "faster-hex" 424 | version = "0.9.0" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183" 427 | 428 | [[package]] 429 | name = "fastrand" 430 | version = "2.1.0" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" 433 | 434 | [[package]] 435 | name = "fiat-crypto" 436 | version = "0.2.9" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" 439 | 440 | [[package]] 441 | name = "filetime" 442 | version = "0.2.23" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" 445 | dependencies = [ 446 | "cfg-if", 447 | "libc", 448 | "redox_syscall 0.4.1", 449 | "windows-sys", 450 | ] 451 | 452 | [[package]] 453 | name = "flate2" 454 | version = "1.0.30" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" 457 | dependencies = [ 458 | "crc32fast", 459 | "miniz_oxide", 460 | ] 461 | 462 | [[package]] 463 | name = "form_urlencoded" 464 | version = "1.2.1" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 467 | dependencies = [ 468 | "percent-encoding", 469 | ] 470 | 471 | [[package]] 472 | name = "fs-err" 473 | version = "2.11.0" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" 476 | dependencies = [ 477 | "autocfg", 478 | ] 479 | 480 | [[package]] 481 | name = "generic-array" 482 | version = "0.14.7" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 485 | dependencies = [ 486 | "typenum", 487 | "version_check", 488 | "zeroize", 489 | ] 490 | 491 | [[package]] 492 | name = "getrandom" 493 | version = "0.2.15" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 496 | dependencies = [ 497 | "cfg-if", 498 | "libc", 499 | "wasi", 500 | ] 501 | 502 | [[package]] 503 | name = "gix" 504 | version = "0.57.1" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "6dd025382892c7b500a9ce1582cd803f9c2ebfe44aff52e9c7f86feee7ced75e" 507 | dependencies = [ 508 | "gix-actor", 509 | "gix-commitgraph", 510 | "gix-config", 511 | "gix-date", 512 | "gix-diff", 513 | "gix-discover", 514 | "gix-features", 515 | "gix-fs", 516 | "gix-glob", 517 | "gix-hash", 518 | "gix-hashtable", 519 | "gix-index", 520 | "gix-lock", 521 | "gix-macros", 522 | "gix-object", 523 | "gix-odb", 524 | "gix-pack", 525 | "gix-path", 526 | "gix-ref", 527 | "gix-refspec", 528 | "gix-revision", 529 | "gix-revwalk", 530 | "gix-sec", 531 | "gix-tempfile", 532 | "gix-trace", 533 | "gix-traverse", 534 | "gix-url", 535 | "gix-utils", 536 | "gix-validate", 537 | "once_cell", 538 | "parking_lot", 539 | "signal-hook", 540 | "smallvec", 541 | "thiserror", 542 | "unicode-normalization", 543 | ] 544 | 545 | [[package]] 546 | name = "gix-actor" 547 | version = "0.29.1" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "da27b5ab4ab5c75ff891dccd48409f8cc53c28a79480f1efdd33184b2dc1d958" 550 | dependencies = [ 551 | "bstr", 552 | "btoi", 553 | "gix-date", 554 | "itoa", 555 | "thiserror", 556 | "winnow", 557 | ] 558 | 559 | [[package]] 560 | name = "gix-bitmap" 561 | version = "0.2.11" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "a371db66cbd4e13f0ed9dc4c0fea712d7276805fccc877f77e96374d317e87ae" 564 | dependencies = [ 565 | "thiserror", 566 | ] 567 | 568 | [[package]] 569 | name = "gix-chunk" 570 | version = "0.4.8" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "45c8751169961ba7640b513c3b24af61aa962c967aaf04116734975cd5af0c52" 573 | dependencies = [ 574 | "thiserror", 575 | ] 576 | 577 | [[package]] 578 | name = "gix-commitgraph" 579 | version = "0.23.2" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "7e8dcbf434951fa477063e05fea59722615af70dc2567377e58c2f7853b010fc" 582 | dependencies = [ 583 | "bstr", 584 | "gix-chunk", 585 | "gix-features", 586 | "gix-hash", 587 | "memmap2", 588 | "thiserror", 589 | ] 590 | 591 | [[package]] 592 | name = "gix-config" 593 | version = "0.33.1" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "367304855b369cadcac4ee5fb5a3a20da9378dd7905106141070b79f85241079" 596 | dependencies = [ 597 | "bstr", 598 | "gix-config-value", 599 | "gix-features", 600 | "gix-glob", 601 | "gix-path", 602 | "gix-ref", 603 | "gix-sec", 604 | "memchr", 605 | "once_cell", 606 | "smallvec", 607 | "thiserror", 608 | "unicode-bom", 609 | "winnow", 610 | ] 611 | 612 | [[package]] 613 | name = "gix-config-value" 614 | version = "0.14.6" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "fbd06203b1a9b33a78c88252a625031b094d9e1b647260070c25b09910c0a804" 617 | dependencies = [ 618 | "bitflags 2.5.0", 619 | "bstr", 620 | "gix-path", 621 | "libc", 622 | "thiserror", 623 | ] 624 | 625 | [[package]] 626 | name = "gix-date" 627 | version = "0.8.6" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "367ee9093b0c2b04fd04c5c7c8b6a1082713534eab537597ae343663a518fa99" 630 | dependencies = [ 631 | "bstr", 632 | "itoa", 633 | "thiserror", 634 | "time", 635 | ] 636 | 637 | [[package]] 638 | name = "gix-diff" 639 | version = "0.39.1" 640 | source = "registry+https://github.com/rust-lang/crates.io-index" 641 | checksum = "fd6a0454f8c42d686f17e7f084057c717c082b7dbb8209729e4e8f26749eb93a" 642 | dependencies = [ 643 | "bstr", 644 | "gix-hash", 645 | "gix-object", 646 | "thiserror", 647 | ] 648 | 649 | [[package]] 650 | name = "gix-discover" 651 | version = "0.28.1" 652 | source = "registry+https://github.com/rust-lang/crates.io-index" 653 | checksum = "b8d7b2896edc3d899d28a646ccc6df729827a6600e546570b2783466404a42d6" 654 | dependencies = [ 655 | "bstr", 656 | "dunce", 657 | "gix-hash", 658 | "gix-path", 659 | "gix-ref", 660 | "gix-sec", 661 | "thiserror", 662 | ] 663 | 664 | [[package]] 665 | name = "gix-features" 666 | version = "0.37.2" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "d50270e8dcc665f30ba0735b17984b9535bdf1e646c76e638e007846164d57af" 669 | dependencies = [ 670 | "crc32fast", 671 | "flate2", 672 | "gix-hash", 673 | "gix-trace", 674 | "libc", 675 | "once_cell", 676 | "prodash", 677 | "sha1_smol", 678 | "thiserror", 679 | "walkdir", 680 | ] 681 | 682 | [[package]] 683 | name = "gix-fs" 684 | version = "0.9.1" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "7555c23a005537434bbfcb8939694e18cad42602961d0de617f8477cc2adecdd" 687 | dependencies = [ 688 | "gix-features", 689 | ] 690 | 691 | [[package]] 692 | name = "gix-glob" 693 | version = "0.15.1" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "ae6232f18b262770e343dcdd461c0011c9b9ae27f0c805e115012aa2b902c1b8" 696 | dependencies = [ 697 | "bitflags 2.5.0", 698 | "bstr", 699 | "gix-features", 700 | "gix-path", 701 | ] 702 | 703 | [[package]] 704 | name = "gix-hash" 705 | version = "0.14.2" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "f93d7df7366121b5018f947a04d37f034717e113dcf9ccd85c34b58e57a74d5e" 708 | dependencies = [ 709 | "faster-hex", 710 | "thiserror", 711 | ] 712 | 713 | [[package]] 714 | name = "gix-hashtable" 715 | version = "0.5.2" 716 | source = "registry+https://github.com/rust-lang/crates.io-index" 717 | checksum = "7ddf80e16f3c19ac06ce415a38b8591993d3f73aede049cb561becb5b3a8e242" 718 | dependencies = [ 719 | "gix-hash", 720 | "hashbrown", 721 | "parking_lot", 722 | ] 723 | 724 | [[package]] 725 | name = "gix-index" 726 | version = "0.28.2" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "9e50e63df6c8d4137f7fb882f27643b3a9756c468a1a2cdbe1ce443010ca8778" 729 | dependencies = [ 730 | "bitflags 2.5.0", 731 | "bstr", 732 | "btoi", 733 | "filetime", 734 | "gix-bitmap", 735 | "gix-features", 736 | "gix-fs", 737 | "gix-hash", 738 | "gix-lock", 739 | "gix-object", 740 | "gix-traverse", 741 | "itoa", 742 | "libc", 743 | "memmap2", 744 | "rustix", 745 | "smallvec", 746 | "thiserror", 747 | ] 748 | 749 | [[package]] 750 | name = "gix-lock" 751 | version = "12.0.1" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | checksum = "f40a439397f1e230b54cf85d52af87e5ea44cc1e7748379785d3f6d03d802b00" 754 | dependencies = [ 755 | "gix-tempfile", 756 | "gix-utils", 757 | "thiserror", 758 | ] 759 | 760 | [[package]] 761 | name = "gix-macros" 762 | version = "0.1.5" 763 | source = "registry+https://github.com/rust-lang/crates.io-index" 764 | checksum = "999ce923619f88194171a67fb3e6d613653b8d4d6078b529b15a765da0edcc17" 765 | dependencies = [ 766 | "proc-macro2", 767 | "quote", 768 | "syn", 769 | ] 770 | 771 | [[package]] 772 | name = "gix-object" 773 | version = "0.40.1" 774 | source = "registry+https://github.com/rust-lang/crates.io-index" 775 | checksum = "0c89402e8faa41b49fde348665a8f38589e461036475af43b6b70615a6a313a2" 776 | dependencies = [ 777 | "bstr", 778 | "btoi", 779 | "gix-actor", 780 | "gix-date", 781 | "gix-features", 782 | "gix-hash", 783 | "gix-validate", 784 | "itoa", 785 | "smallvec", 786 | "thiserror", 787 | "winnow", 788 | ] 789 | 790 | [[package]] 791 | name = "gix-odb" 792 | version = "0.56.1" 793 | source = "registry+https://github.com/rust-lang/crates.io-index" 794 | checksum = "46ae6da873de41c6c2b73570e82c571b69df5154dcd8f46dfafc6687767c33b1" 795 | dependencies = [ 796 | "arc-swap", 797 | "gix-date", 798 | "gix-features", 799 | "gix-hash", 800 | "gix-object", 801 | "gix-pack", 802 | "gix-path", 803 | "gix-quote", 804 | "parking_lot", 805 | "tempfile", 806 | "thiserror", 807 | ] 808 | 809 | [[package]] 810 | name = "gix-pack" 811 | version = "0.46.1" 812 | source = "registry+https://github.com/rust-lang/crates.io-index" 813 | checksum = "782b4d42790a14072d5c400deda9851f5765f50fe72bca6dece0da1cd6f05a9a" 814 | dependencies = [ 815 | "clru", 816 | "gix-chunk", 817 | "gix-features", 818 | "gix-hash", 819 | "gix-hashtable", 820 | "gix-object", 821 | "gix-path", 822 | "gix-tempfile", 823 | "memmap2", 824 | "parking_lot", 825 | "smallvec", 826 | "thiserror", 827 | ] 828 | 829 | [[package]] 830 | name = "gix-path" 831 | version = "0.10.7" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | checksum = "23623cf0f475691a6d943f898c4d0b89f5c1a2a64d0f92bce0e0322ee6528783" 834 | dependencies = [ 835 | "bstr", 836 | "gix-trace", 837 | "home", 838 | "once_cell", 839 | "thiserror", 840 | ] 841 | 842 | [[package]] 843 | name = "gix-quote" 844 | version = "0.4.12" 845 | source = "registry+https://github.com/rust-lang/crates.io-index" 846 | checksum = "cbff4f9b9ea3fa7a25a70ee62f545143abef624ac6aa5884344e70c8b0a1d9ff" 847 | dependencies = [ 848 | "bstr", 849 | "gix-utils", 850 | "thiserror", 851 | ] 852 | 853 | [[package]] 854 | name = "gix-ref" 855 | version = "0.40.1" 856 | source = "registry+https://github.com/rust-lang/crates.io-index" 857 | checksum = "64d9bd1984638d8f3511a2fcbe84fcedb8a5b5d64df677353620572383f42649" 858 | dependencies = [ 859 | "gix-actor", 860 | "gix-date", 861 | "gix-features", 862 | "gix-fs", 863 | "gix-hash", 864 | "gix-lock", 865 | "gix-object", 866 | "gix-path", 867 | "gix-tempfile", 868 | "gix-validate", 869 | "memmap2", 870 | "thiserror", 871 | "winnow", 872 | ] 873 | 874 | [[package]] 875 | name = "gix-refspec" 876 | version = "0.21.1" 877 | source = "registry+https://github.com/rust-lang/crates.io-index" 878 | checksum = "be219df5092c1735abb2a53eccdf775e945eea6986ee1b6e7a5896dccc0be704" 879 | dependencies = [ 880 | "bstr", 881 | "gix-hash", 882 | "gix-revision", 883 | "gix-validate", 884 | "smallvec", 885 | "thiserror", 886 | ] 887 | 888 | [[package]] 889 | name = "gix-revision" 890 | version = "0.25.1" 891 | source = "registry+https://github.com/rust-lang/crates.io-index" 892 | checksum = "aa78e1df3633bc937d4db15f8dca2abdb1300ca971c0fabcf9fa97e38cf4cd9f" 893 | dependencies = [ 894 | "bstr", 895 | "gix-date", 896 | "gix-hash", 897 | "gix-hashtable", 898 | "gix-object", 899 | "gix-revwalk", 900 | "gix-trace", 901 | "thiserror", 902 | ] 903 | 904 | [[package]] 905 | name = "gix-revwalk" 906 | version = "0.11.1" 907 | source = "registry+https://github.com/rust-lang/crates.io-index" 908 | checksum = "702de5fe5c2bbdde80219f3a8b9723eb927466e7ecd187cfd1b45d986408e45f" 909 | dependencies = [ 910 | "gix-commitgraph", 911 | "gix-date", 912 | "gix-hash", 913 | "gix-hashtable", 914 | "gix-object", 915 | "smallvec", 916 | "thiserror", 917 | ] 918 | 919 | [[package]] 920 | name = "gix-sec" 921 | version = "0.10.6" 922 | source = "registry+https://github.com/rust-lang/crates.io-index" 923 | checksum = "fddc27984a643b20dd03e97790555804f98cf07404e0e552c0ad8133266a79a1" 924 | dependencies = [ 925 | "bitflags 2.5.0", 926 | "gix-path", 927 | "libc", 928 | "windows-sys", 929 | ] 930 | 931 | [[package]] 932 | name = "gix-tempfile" 933 | version = "12.0.1" 934 | source = "registry+https://github.com/rust-lang/crates.io-index" 935 | checksum = "a8ef376d718b1f5f119b458e21b00fbf576bc9d4e26f8f383d29f5ffe3ba3eaa" 936 | dependencies = [ 937 | "gix-fs", 938 | "libc", 939 | "once_cell", 940 | "parking_lot", 941 | "signal-hook", 942 | "signal-hook-registry", 943 | "tempfile", 944 | ] 945 | 946 | [[package]] 947 | name = "gix-trace" 948 | version = "0.1.9" 949 | source = "registry+https://github.com/rust-lang/crates.io-index" 950 | checksum = "f924267408915fddcd558e3f37295cc7d6a3e50f8bd8b606cee0808c3915157e" 951 | 952 | [[package]] 953 | name = "gix-traverse" 954 | version = "0.36.2" 955 | source = "registry+https://github.com/rust-lang/crates.io-index" 956 | checksum = "65109e445ba7a409b48f34f570a4d7db72eade1dc1bcff81990a490e86c07161" 957 | dependencies = [ 958 | "gix-commitgraph", 959 | "gix-date", 960 | "gix-hash", 961 | "gix-hashtable", 962 | "gix-object", 963 | "gix-revwalk", 964 | "smallvec", 965 | "thiserror", 966 | ] 967 | 968 | [[package]] 969 | name = "gix-url" 970 | version = "0.26.1" 971 | source = "registry+https://github.com/rust-lang/crates.io-index" 972 | checksum = "8f0f17cceb7552a231d1fec690bc2740c346554e3be6f5d2c41dfa809594dc44" 973 | dependencies = [ 974 | "bstr", 975 | "gix-features", 976 | "gix-path", 977 | "home", 978 | "thiserror", 979 | "url", 980 | ] 981 | 982 | [[package]] 983 | name = "gix-utils" 984 | version = "0.1.12" 985 | source = "registry+https://github.com/rust-lang/crates.io-index" 986 | checksum = "35192df7fd0fa112263bad8021e2df7167df4cc2a6e6d15892e1e55621d3d4dc" 987 | dependencies = [ 988 | "fastrand", 989 | "unicode-normalization", 990 | ] 991 | 992 | [[package]] 993 | name = "gix-validate" 994 | version = "0.8.5" 995 | source = "registry+https://github.com/rust-lang/crates.io-index" 996 | checksum = "82c27dd34a49b1addf193c92070bcbf3beaf6e10f16a78544de6372e146a0acf" 997 | dependencies = [ 998 | "bstr", 999 | "thiserror", 1000 | ] 1001 | 1002 | [[package]] 1003 | name = "hashbrown" 1004 | version = "0.14.5" 1005 | source = "registry+https://github.com/rust-lang/crates.io-index" 1006 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 1007 | 1008 | [[package]] 1009 | name = "heck" 1010 | version = "0.5.0" 1011 | source = "registry+https://github.com/rust-lang/crates.io-index" 1012 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 1013 | 1014 | [[package]] 1015 | name = "hex" 1016 | version = "0.4.3" 1017 | source = "registry+https://github.com/rust-lang/crates.io-index" 1018 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 1019 | 1020 | [[package]] 1021 | name = "home" 1022 | version = "0.5.9" 1023 | source = "registry+https://github.com/rust-lang/crates.io-index" 1024 | checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" 1025 | dependencies = [ 1026 | "windows-sys", 1027 | ] 1028 | 1029 | [[package]] 1030 | name = "humantime" 1031 | version = "2.1.0" 1032 | source = "registry+https://github.com/rust-lang/crates.io-index" 1033 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 1034 | 1035 | [[package]] 1036 | name = "idna" 1037 | version = "0.5.0" 1038 | source = "registry+https://github.com/rust-lang/crates.io-index" 1039 | checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" 1040 | dependencies = [ 1041 | "unicode-bidi", 1042 | "unicode-normalization", 1043 | ] 1044 | 1045 | [[package]] 1046 | name = "indexmap" 1047 | version = "2.2.6" 1048 | source = "registry+https://github.com/rust-lang/crates.io-index" 1049 | checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" 1050 | dependencies = [ 1051 | "equivalent", 1052 | "hashbrown", 1053 | ] 1054 | 1055 | [[package]] 1056 | name = "inout" 1057 | version = "0.1.3" 1058 | source = "registry+https://github.com/rust-lang/crates.io-index" 1059 | checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" 1060 | dependencies = [ 1061 | "generic-array", 1062 | ] 1063 | 1064 | [[package]] 1065 | name = "is_terminal_polyfill" 1066 | version = "1.70.0" 1067 | source = "registry+https://github.com/rust-lang/crates.io-index" 1068 | checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" 1069 | 1070 | [[package]] 1071 | name = "itoa" 1072 | version = "1.0.11" 1073 | source = "registry+https://github.com/rust-lang/crates.io-index" 1074 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 1075 | 1076 | [[package]] 1077 | name = "libc" 1078 | version = "0.2.155" 1079 | source = "registry+https://github.com/rust-lang/crates.io-index" 1080 | checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" 1081 | 1082 | [[package]] 1083 | name = "linux-raw-sys" 1084 | version = "0.4.14" 1085 | source = "registry+https://github.com/rust-lang/crates.io-index" 1086 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 1087 | 1088 | [[package]] 1089 | name = "lock_api" 1090 | version = "0.4.12" 1091 | source = "registry+https://github.com/rust-lang/crates.io-index" 1092 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 1093 | dependencies = [ 1094 | "autocfg", 1095 | "scopeguard", 1096 | ] 1097 | 1098 | [[package]] 1099 | name = "log" 1100 | version = "0.4.21" 1101 | source = "registry+https://github.com/rust-lang/crates.io-index" 1102 | checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 1103 | 1104 | [[package]] 1105 | name = "memchr" 1106 | version = "2.7.4" 1107 | source = "registry+https://github.com/rust-lang/crates.io-index" 1108 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 1109 | 1110 | [[package]] 1111 | name = "memmap2" 1112 | version = "0.9.4" 1113 | source = "registry+https://github.com/rust-lang/crates.io-index" 1114 | checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" 1115 | dependencies = [ 1116 | "libc", 1117 | ] 1118 | 1119 | [[package]] 1120 | name = "miniz_oxide" 1121 | version = "0.7.4" 1122 | source = "registry+https://github.com/rust-lang/crates.io-index" 1123 | checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" 1124 | dependencies = [ 1125 | "adler", 1126 | ] 1127 | 1128 | [[package]] 1129 | name = "num-conv" 1130 | version = "0.1.0" 1131 | source = "registry+https://github.com/rust-lang/crates.io-index" 1132 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 1133 | 1134 | [[package]] 1135 | name = "num-traits" 1136 | version = "0.2.19" 1137 | source = "registry+https://github.com/rust-lang/crates.io-index" 1138 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1139 | dependencies = [ 1140 | "autocfg", 1141 | ] 1142 | 1143 | [[package]] 1144 | name = "num_threads" 1145 | version = "0.1.7" 1146 | source = "registry+https://github.com/rust-lang/crates.io-index" 1147 | checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" 1148 | dependencies = [ 1149 | "libc", 1150 | ] 1151 | 1152 | [[package]] 1153 | name = "once_cell" 1154 | version = "1.19.0" 1155 | source = "registry+https://github.com/rust-lang/crates.io-index" 1156 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 1157 | 1158 | [[package]] 1159 | name = "opaque-debug" 1160 | version = "0.3.1" 1161 | source = "registry+https://github.com/rust-lang/crates.io-index" 1162 | checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" 1163 | 1164 | [[package]] 1165 | name = "parking_lot" 1166 | version = "0.12.3" 1167 | source = "registry+https://github.com/rust-lang/crates.io-index" 1168 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 1169 | dependencies = [ 1170 | "lock_api", 1171 | "parking_lot_core", 1172 | ] 1173 | 1174 | [[package]] 1175 | name = "parking_lot_core" 1176 | version = "0.9.10" 1177 | source = "registry+https://github.com/rust-lang/crates.io-index" 1178 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 1179 | dependencies = [ 1180 | "cfg-if", 1181 | "libc", 1182 | "redox_syscall 0.5.2", 1183 | "smallvec", 1184 | "windows-targets", 1185 | ] 1186 | 1187 | [[package]] 1188 | name = "percent-encoding" 1189 | version = "2.3.1" 1190 | source = "registry+https://github.com/rust-lang/crates.io-index" 1191 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1192 | 1193 | [[package]] 1194 | name = "poly1305" 1195 | version = "0.8.0" 1196 | source = "registry+https://github.com/rust-lang/crates.io-index" 1197 | checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" 1198 | dependencies = [ 1199 | "cpufeatures", 1200 | "opaque-debug", 1201 | "universal-hash", 1202 | ] 1203 | 1204 | [[package]] 1205 | name = "powerfmt" 1206 | version = "0.2.0" 1207 | source = "registry+https://github.com/rust-lang/crates.io-index" 1208 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1209 | 1210 | [[package]] 1211 | name = "predicates" 1212 | version = "3.1.0" 1213 | source = "registry+https://github.com/rust-lang/crates.io-index" 1214 | checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8" 1215 | dependencies = [ 1216 | "anstyle", 1217 | "difflib", 1218 | "predicates-core", 1219 | ] 1220 | 1221 | [[package]] 1222 | name = "predicates-core" 1223 | version = "1.0.6" 1224 | source = "registry+https://github.com/rust-lang/crates.io-index" 1225 | checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" 1226 | 1227 | [[package]] 1228 | name = "predicates-tree" 1229 | version = "1.0.9" 1230 | source = "registry+https://github.com/rust-lang/crates.io-index" 1231 | checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" 1232 | dependencies = [ 1233 | "predicates-core", 1234 | "termtree", 1235 | ] 1236 | 1237 | [[package]] 1238 | name = "proc-macro2" 1239 | version = "1.0.86" 1240 | source = "registry+https://github.com/rust-lang/crates.io-index" 1241 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 1242 | dependencies = [ 1243 | "unicode-ident", 1244 | ] 1245 | 1246 | [[package]] 1247 | name = "prodash" 1248 | version = "28.0.0" 1249 | source = "registry+https://github.com/rust-lang/crates.io-index" 1250 | checksum = "744a264d26b88a6a7e37cbad97953fa233b94d585236310bcbc88474b4092d79" 1251 | 1252 | [[package]] 1253 | name = "quote" 1254 | version = "1.0.36" 1255 | source = "registry+https://github.com/rust-lang/crates.io-index" 1256 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 1257 | dependencies = [ 1258 | "proc-macro2", 1259 | ] 1260 | 1261 | [[package]] 1262 | name = "rand_core" 1263 | version = "0.6.4" 1264 | source = "registry+https://github.com/rust-lang/crates.io-index" 1265 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1266 | dependencies = [ 1267 | "getrandom", 1268 | ] 1269 | 1270 | [[package]] 1271 | name = "redox_syscall" 1272 | version = "0.4.1" 1273 | source = "registry+https://github.com/rust-lang/crates.io-index" 1274 | checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" 1275 | dependencies = [ 1276 | "bitflags 1.3.2", 1277 | ] 1278 | 1279 | [[package]] 1280 | name = "redox_syscall" 1281 | version = "0.5.2" 1282 | source = "registry+https://github.com/rust-lang/crates.io-index" 1283 | checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" 1284 | dependencies = [ 1285 | "bitflags 2.5.0", 1286 | ] 1287 | 1288 | [[package]] 1289 | name = "regex" 1290 | version = "1.10.5" 1291 | source = "registry+https://github.com/rust-lang/crates.io-index" 1292 | checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" 1293 | dependencies = [ 1294 | "aho-corasick", 1295 | "memchr", 1296 | "regex-automata", 1297 | "regex-syntax", 1298 | ] 1299 | 1300 | [[package]] 1301 | name = "regex-automata" 1302 | version = "0.4.7" 1303 | source = "registry+https://github.com/rust-lang/crates.io-index" 1304 | checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" 1305 | dependencies = [ 1306 | "aho-corasick", 1307 | "memchr", 1308 | "regex-syntax", 1309 | ] 1310 | 1311 | [[package]] 1312 | name = "regex-syntax" 1313 | version = "0.8.4" 1314 | source = "registry+https://github.com/rust-lang/crates.io-index" 1315 | checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" 1316 | 1317 | [[package]] 1318 | name = "rustc_version" 1319 | version = "0.4.0" 1320 | source = "registry+https://github.com/rust-lang/crates.io-index" 1321 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" 1322 | dependencies = [ 1323 | "semver", 1324 | ] 1325 | 1326 | [[package]] 1327 | name = "rustix" 1328 | version = "0.38.34" 1329 | source = "registry+https://github.com/rust-lang/crates.io-index" 1330 | checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" 1331 | dependencies = [ 1332 | "bitflags 2.5.0", 1333 | "errno", 1334 | "libc", 1335 | "linux-raw-sys", 1336 | "windows-sys", 1337 | ] 1338 | 1339 | [[package]] 1340 | name = "rustversion" 1341 | version = "1.0.17" 1342 | source = "registry+https://github.com/rust-lang/crates.io-index" 1343 | checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" 1344 | 1345 | [[package]] 1346 | name = "ryu" 1347 | version = "1.0.18" 1348 | source = "registry+https://github.com/rust-lang/crates.io-index" 1349 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 1350 | 1351 | [[package]] 1352 | name = "salsa20" 1353 | version = "0.10.2" 1354 | source = "registry+https://github.com/rust-lang/crates.io-index" 1355 | checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" 1356 | dependencies = [ 1357 | "cipher", 1358 | ] 1359 | 1360 | [[package]] 1361 | name = "same-file" 1362 | version = "1.0.6" 1363 | source = "registry+https://github.com/rust-lang/crates.io-index" 1364 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 1365 | dependencies = [ 1366 | "winapi-util", 1367 | ] 1368 | 1369 | [[package]] 1370 | name = "scopeguard" 1371 | version = "1.2.0" 1372 | source = "registry+https://github.com/rust-lang/crates.io-index" 1373 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1374 | 1375 | [[package]] 1376 | name = "semver" 1377 | version = "1.0.23" 1378 | source = "registry+https://github.com/rust-lang/crates.io-index" 1379 | checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" 1380 | 1381 | [[package]] 1382 | name = "serde" 1383 | version = "1.0.203" 1384 | source = "registry+https://github.com/rust-lang/crates.io-index" 1385 | checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" 1386 | dependencies = [ 1387 | "serde_derive", 1388 | ] 1389 | 1390 | [[package]] 1391 | name = "serde_derive" 1392 | version = "1.0.203" 1393 | source = "registry+https://github.com/rust-lang/crates.io-index" 1394 | checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" 1395 | dependencies = [ 1396 | "proc-macro2", 1397 | "quote", 1398 | "syn", 1399 | ] 1400 | 1401 | [[package]] 1402 | name = "serde_json" 1403 | version = "1.0.117" 1404 | source = "registry+https://github.com/rust-lang/crates.io-index" 1405 | checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" 1406 | dependencies = [ 1407 | "itoa", 1408 | "ryu", 1409 | "serde", 1410 | ] 1411 | 1412 | [[package]] 1413 | name = "serde_yaml" 1414 | version = "0.9.34+deprecated" 1415 | source = "registry+https://github.com/rust-lang/crates.io-index" 1416 | checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" 1417 | dependencies = [ 1418 | "indexmap", 1419 | "itoa", 1420 | "ryu", 1421 | "serde", 1422 | "unsafe-libyaml", 1423 | ] 1424 | 1425 | [[package]] 1426 | name = "sha1_smol" 1427 | version = "1.0.0" 1428 | source = "registry+https://github.com/rust-lang/crates.io-index" 1429 | checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" 1430 | 1431 | [[package]] 1432 | name = "sha2" 1433 | version = "0.10.8" 1434 | source = "registry+https://github.com/rust-lang/crates.io-index" 1435 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 1436 | dependencies = [ 1437 | "cfg-if", 1438 | "cpufeatures", 1439 | "digest", 1440 | ] 1441 | 1442 | [[package]] 1443 | name = "signal-hook" 1444 | version = "0.3.17" 1445 | source = "registry+https://github.com/rust-lang/crates.io-index" 1446 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 1447 | dependencies = [ 1448 | "libc", 1449 | "signal-hook-registry", 1450 | ] 1451 | 1452 | [[package]] 1453 | name = "signal-hook-registry" 1454 | version = "1.4.2" 1455 | source = "registry+https://github.com/rust-lang/crates.io-index" 1456 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 1457 | dependencies = [ 1458 | "libc", 1459 | ] 1460 | 1461 | [[package]] 1462 | name = "smallvec" 1463 | version = "1.13.2" 1464 | source = "registry+https://github.com/rust-lang/crates.io-index" 1465 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1466 | 1467 | [[package]] 1468 | name = "strsim" 1469 | version = "0.11.1" 1470 | source = "registry+https://github.com/rust-lang/crates.io-index" 1471 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1472 | 1473 | [[package]] 1474 | name = "subtle" 1475 | version = "2.6.0" 1476 | source = "registry+https://github.com/rust-lang/crates.io-index" 1477 | checksum = "0d0208408ba0c3df17ed26eb06992cb1a1268d41b2c0e12e65203fbe3972cee5" 1478 | 1479 | [[package]] 1480 | name = "syn" 1481 | version = "2.0.67" 1482 | source = "registry+https://github.com/rust-lang/crates.io-index" 1483 | checksum = "ff8655ed1d86f3af4ee3fd3263786bc14245ad17c4c7e85ba7187fb3ae028c90" 1484 | dependencies = [ 1485 | "proc-macro2", 1486 | "quote", 1487 | "unicode-ident", 1488 | ] 1489 | 1490 | [[package]] 1491 | name = "tempfile" 1492 | version = "3.10.1" 1493 | source = "registry+https://github.com/rust-lang/crates.io-index" 1494 | checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" 1495 | dependencies = [ 1496 | "cfg-if", 1497 | "fastrand", 1498 | "rustix", 1499 | "windows-sys", 1500 | ] 1501 | 1502 | [[package]] 1503 | name = "termtree" 1504 | version = "0.4.1" 1505 | source = "registry+https://github.com/rust-lang/crates.io-index" 1506 | checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" 1507 | 1508 | [[package]] 1509 | name = "thiserror" 1510 | version = "1.0.61" 1511 | source = "registry+https://github.com/rust-lang/crates.io-index" 1512 | checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" 1513 | dependencies = [ 1514 | "thiserror-impl", 1515 | ] 1516 | 1517 | [[package]] 1518 | name = "thiserror-impl" 1519 | version = "1.0.61" 1520 | source = "registry+https://github.com/rust-lang/crates.io-index" 1521 | checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" 1522 | dependencies = [ 1523 | "proc-macro2", 1524 | "quote", 1525 | "syn", 1526 | ] 1527 | 1528 | [[package]] 1529 | name = "time" 1530 | version = "0.3.36" 1531 | source = "registry+https://github.com/rust-lang/crates.io-index" 1532 | checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" 1533 | dependencies = [ 1534 | "deranged", 1535 | "itoa", 1536 | "libc", 1537 | "num-conv", 1538 | "num_threads", 1539 | "powerfmt", 1540 | "serde", 1541 | "time-core", 1542 | "time-macros", 1543 | ] 1544 | 1545 | [[package]] 1546 | name = "time-core" 1547 | version = "0.1.2" 1548 | source = "registry+https://github.com/rust-lang/crates.io-index" 1549 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 1550 | 1551 | [[package]] 1552 | name = "time-macros" 1553 | version = "0.2.18" 1554 | source = "registry+https://github.com/rust-lang/crates.io-index" 1555 | checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" 1556 | dependencies = [ 1557 | "num-conv", 1558 | "time-core", 1559 | ] 1560 | 1561 | [[package]] 1562 | name = "tinyvec" 1563 | version = "1.6.0" 1564 | source = "registry+https://github.com/rust-lang/crates.io-index" 1565 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 1566 | dependencies = [ 1567 | "tinyvec_macros", 1568 | ] 1569 | 1570 | [[package]] 1571 | name = "tinyvec_macros" 1572 | version = "0.1.1" 1573 | source = "registry+https://github.com/rust-lang/crates.io-index" 1574 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1575 | 1576 | [[package]] 1577 | name = "typenum" 1578 | version = "1.17.0" 1579 | source = "registry+https://github.com/rust-lang/crates.io-index" 1580 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 1581 | 1582 | [[package]] 1583 | name = "unicode-bidi" 1584 | version = "0.3.15" 1585 | source = "registry+https://github.com/rust-lang/crates.io-index" 1586 | checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" 1587 | 1588 | [[package]] 1589 | name = "unicode-bom" 1590 | version = "2.0.3" 1591 | source = "registry+https://github.com/rust-lang/crates.io-index" 1592 | checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" 1593 | 1594 | [[package]] 1595 | name = "unicode-ident" 1596 | version = "1.0.12" 1597 | source = "registry+https://github.com/rust-lang/crates.io-index" 1598 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1599 | 1600 | [[package]] 1601 | name = "unicode-normalization" 1602 | version = "0.1.23" 1603 | source = "registry+https://github.com/rust-lang/crates.io-index" 1604 | checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" 1605 | dependencies = [ 1606 | "tinyvec", 1607 | ] 1608 | 1609 | [[package]] 1610 | name = "universal-hash" 1611 | version = "0.5.1" 1612 | source = "registry+https://github.com/rust-lang/crates.io-index" 1613 | checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" 1614 | dependencies = [ 1615 | "crypto-common", 1616 | "subtle", 1617 | ] 1618 | 1619 | [[package]] 1620 | name = "unsafe-libyaml" 1621 | version = "0.2.11" 1622 | source = "registry+https://github.com/rust-lang/crates.io-index" 1623 | checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" 1624 | 1625 | [[package]] 1626 | name = "url" 1627 | version = "2.5.2" 1628 | source = "registry+https://github.com/rust-lang/crates.io-index" 1629 | checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" 1630 | dependencies = [ 1631 | "form_urlencoded", 1632 | "idna", 1633 | "percent-encoding", 1634 | ] 1635 | 1636 | [[package]] 1637 | name = "utf8parse" 1638 | version = "0.2.2" 1639 | source = "registry+https://github.com/rust-lang/crates.io-index" 1640 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1641 | 1642 | [[package]] 1643 | name = "vergen" 1644 | version = "8.3.1" 1645 | source = "registry+https://github.com/rust-lang/crates.io-index" 1646 | checksum = "e27d6bdd219887a9eadd19e1c34f32e47fa332301184935c6d9bca26f3cca525" 1647 | dependencies = [ 1648 | "anyhow", 1649 | "cfg-if", 1650 | "gix", 1651 | "rustversion", 1652 | "time", 1653 | ] 1654 | 1655 | [[package]] 1656 | name = "version_check" 1657 | version = "0.9.4" 1658 | source = "registry+https://github.com/rust-lang/crates.io-index" 1659 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1660 | 1661 | [[package]] 1662 | name = "wait-timeout" 1663 | version = "0.2.0" 1664 | source = "registry+https://github.com/rust-lang/crates.io-index" 1665 | checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" 1666 | dependencies = [ 1667 | "libc", 1668 | ] 1669 | 1670 | [[package]] 1671 | name = "walkdir" 1672 | version = "2.5.0" 1673 | source = "registry+https://github.com/rust-lang/crates.io-index" 1674 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 1675 | dependencies = [ 1676 | "same-file", 1677 | "winapi-util", 1678 | ] 1679 | 1680 | [[package]] 1681 | name = "wasi" 1682 | version = "0.11.0+wasi-snapshot-preview1" 1683 | source = "registry+https://github.com/rust-lang/crates.io-index" 1684 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1685 | 1686 | [[package]] 1687 | name = "winapi-util" 1688 | version = "0.1.8" 1689 | source = "registry+https://github.com/rust-lang/crates.io-index" 1690 | checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" 1691 | dependencies = [ 1692 | "windows-sys", 1693 | ] 1694 | 1695 | [[package]] 1696 | name = "windows-sys" 1697 | version = "0.52.0" 1698 | source = "registry+https://github.com/rust-lang/crates.io-index" 1699 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1700 | dependencies = [ 1701 | "windows-targets", 1702 | ] 1703 | 1704 | [[package]] 1705 | name = "windows-targets" 1706 | version = "0.52.5" 1707 | source = "registry+https://github.com/rust-lang/crates.io-index" 1708 | checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" 1709 | dependencies = [ 1710 | "windows_aarch64_gnullvm", 1711 | "windows_aarch64_msvc", 1712 | "windows_i686_gnu", 1713 | "windows_i686_gnullvm", 1714 | "windows_i686_msvc", 1715 | "windows_x86_64_gnu", 1716 | "windows_x86_64_gnullvm", 1717 | "windows_x86_64_msvc", 1718 | ] 1719 | 1720 | [[package]] 1721 | name = "windows_aarch64_gnullvm" 1722 | version = "0.52.5" 1723 | source = "registry+https://github.com/rust-lang/crates.io-index" 1724 | checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" 1725 | 1726 | [[package]] 1727 | name = "windows_aarch64_msvc" 1728 | version = "0.52.5" 1729 | source = "registry+https://github.com/rust-lang/crates.io-index" 1730 | checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" 1731 | 1732 | [[package]] 1733 | name = "windows_i686_gnu" 1734 | version = "0.52.5" 1735 | source = "registry+https://github.com/rust-lang/crates.io-index" 1736 | checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" 1737 | 1738 | [[package]] 1739 | name = "windows_i686_gnullvm" 1740 | version = "0.52.5" 1741 | source = "registry+https://github.com/rust-lang/crates.io-index" 1742 | checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" 1743 | 1744 | [[package]] 1745 | name = "windows_i686_msvc" 1746 | version = "0.52.5" 1747 | source = "registry+https://github.com/rust-lang/crates.io-index" 1748 | checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" 1749 | 1750 | [[package]] 1751 | name = "windows_x86_64_gnu" 1752 | version = "0.52.5" 1753 | source = "registry+https://github.com/rust-lang/crates.io-index" 1754 | checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" 1755 | 1756 | [[package]] 1757 | name = "windows_x86_64_gnullvm" 1758 | version = "0.52.5" 1759 | source = "registry+https://github.com/rust-lang/crates.io-index" 1760 | checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" 1761 | 1762 | [[package]] 1763 | name = "windows_x86_64_msvc" 1764 | version = "0.52.5" 1765 | source = "registry+https://github.com/rust-lang/crates.io-index" 1766 | checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" 1767 | 1768 | [[package]] 1769 | name = "winnow" 1770 | version = "0.5.40" 1771 | source = "registry+https://github.com/rust-lang/crates.io-index" 1772 | checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" 1773 | dependencies = [ 1774 | "memchr", 1775 | ] 1776 | 1777 | [[package]] 1778 | name = "zeroize" 1779 | version = "1.8.1" 1780 | source = "registry+https://github.com/rust-lang/crates.io-index" 1781 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 1782 | --------------------------------------------------------------------------------