├── .gitignore ├── .github └── workflows │ └── ci.yml ├── Cargo.toml ├── src ├── ui.rs ├── lib.rs ├── bin.rs ├── encryption.rs ├── git.rs ├── fs.rs └── configure.rs ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .DS_Store 3 | /.configure-files 4 | /.configure 5 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ${{ matrix.os }} 16 | 17 | strategy: 18 | matrix: 19 | os: [ubuntu-latest, windows-latest, macos-latest] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Build 24 | run: cargo build --verbose 25 | - name: Run tests 26 | run: cargo test --verbose 27 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "configure" 3 | version = "0.1.0" 4 | authors = ["Jeremy Massel "] 5 | edition = "2018" 6 | 7 | [lib] 8 | name = "configure" 9 | path = "src/lib.rs" 10 | 11 | [[bin]] 12 | name = "configure" 13 | path = "src/bin.rs" 14 | 15 | [profile.release] 16 | opt-level = "s" 17 | lto = true 18 | codegen-units = 1 19 | 20 | [dependencies] 21 | log = "0.4.0" 22 | dirs = "3.0.1" 23 | simplelog = "^0.7.6" 24 | sodiumoxide = "0.2.6" 25 | structopt = { version = "0.3", default-features = false } 26 | structopt-flags = "0.3" 27 | git2 = "0.13" 28 | console = "0.13.0" 29 | dialoguer = "0.7.1" 30 | indicatif = "0.15.0" 31 | serde = { version = "1.0", features = ["derive"] } 32 | serde_json = {version = "1.0", features = ["preserve_order"]} 33 | 34 | thiserror = "1.0" 35 | ring = "0.16.18" 36 | base64 = "0.13.0" 37 | 38 | chrono = "0.4" 39 | -------------------------------------------------------------------------------- /src/ui.rs: -------------------------------------------------------------------------------- 1 | use console::{style, Term}; 2 | use dialoguer::{theme::ColorfulTheme, Confirm, Input, Select}; 3 | 4 | /// 5 | /// Print a heading-style message to the console 6 | pub fn heading(string: &str) { 7 | println!("{}", style(string).green()); 8 | } 9 | 10 | /// 11 | /// Print a warning to the console 12 | pub fn warn(string: &str) { 13 | println!("{}", style(string).yellow()); 14 | } 15 | 16 | /// 17 | /// Print a blank line to the console 18 | pub fn newline() { 19 | println!(); 20 | } 21 | 22 | /// 23 | /// Prompt the user to input text on the command line 24 | pub fn prompt(message: &str) -> String { 25 | heading(message); 26 | Input::::new().interact_text().unwrap() 27 | } 28 | 29 | /// 30 | /// Ask the user for confirmation 31 | pub fn confirm(message: &str) -> bool { 32 | Confirm::new().with_prompt(message).interact().unwrap() 33 | } 34 | 35 | /// 36 | /// Allow the user to provide a list of items to select from 37 | pub fn select(items: Vec, selected: &str) -> Result { 38 | let index_of_current_branch = items 39 | .iter() 40 | .position(|name| *name == selected) 41 | .expect("Unable to find current branch in repo branch list"); 42 | 43 | let selection = Select::with_theme(&ColorfulTheme::default()) 44 | .items(&items) 45 | .default(index_of_current_branch) 46 | .interact_on_opt(&Term::stderr()) 47 | .expect("You must select an option") 48 | .unwrap(); 49 | 50 | Ok(items[selection].clone()) 51 | } 52 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod configure; 2 | mod encryption; 3 | mod fs; 4 | mod git; 5 | mod ui; 6 | 7 | use crate::configure::*; 8 | use crate::fs::*; 9 | use log::debug; 10 | 11 | /// Set up a project to use the configure tool 12 | /// 13 | pub fn init() { 14 | init_encryption(); 15 | let configuration = read_configuration(); 16 | setup_configuration(configuration); 17 | } 18 | 19 | /// Decrypts secrets already present in the repository 20 | /// 21 | /// To get secrets into the repository, use `configure_update` 22 | /// 23 | /// # Arguments 24 | /// 25 | /// * `configuration` - The project's parsed `ConfigurationFile` object. 26 | /// 27 | pub fn apply() { 28 | init_encryption(); 29 | let configuration = read_configuration(); 30 | 31 | if !configuration.is_empty() { 32 | apply_configuration(configuration); 33 | } else { 34 | setup_configuration(configuration); 35 | } 36 | } 37 | 38 | /// Adds encrypted secrets files to the configuration, or updates existing ones. 39 | /// 40 | /// Prompts the user to decrypt them when it finishes. 41 | /// 42 | /// # Arguments 43 | /// 44 | /// * `configuration` - The project's parsed `ConfigurationFile` object. 45 | /// 46 | pub fn update() { 47 | init_encryption(); 48 | let configuration = read_configuration(); 49 | 50 | if !configuration.is_empty() { 51 | update_configuration(configuration); 52 | } else { 53 | setup_configuration(configuration); 54 | } 55 | } 56 | 57 | /// Validate a project's .configure file 58 | /// 59 | pub fn validate() { 60 | init_encryption(); 61 | let configuration = read_configuration(); 62 | 63 | if !configuration.is_empty() { 64 | validate_configuration(configuration); 65 | } else { 66 | setup_configuration(configuration); 67 | } 68 | } 69 | 70 | pub fn generate_encryption_key() -> String { 71 | crate::encryption::generate_key() 72 | } 73 | 74 | fn init_encryption() { 75 | debug!("libConfigure initializing encryption"); 76 | encryption::init() 77 | .expect("Encryption unavailable – there's no CSPRNG available on this machine"); 78 | 79 | debug!("libConfigure encryption initialization successful"); 80 | } 81 | -------------------------------------------------------------------------------- /src/bin.rs: -------------------------------------------------------------------------------- 1 | use log::{debug, LevelFilter}; 2 | use simplelog::CombinedLogger; 3 | use simplelog::Config; 4 | use simplelog::TermLogger; 5 | use simplelog::TerminalMode; 6 | use structopt::StructOpt; 7 | use structopt_flags::GetWithDefault; 8 | 9 | #[derive(StructOpt)] 10 | #[structopt( 11 | name = "configure", 12 | about = "A command-line utility for applying configuration secrets with strong encryption" 13 | )] 14 | struct Options { 15 | #[structopt(subcommand)] 16 | command: Command, 17 | 18 | #[structopt(flatten)] 19 | verbose: structopt_flags::VerboseNoDef, 20 | } 21 | 22 | #[derive(StructOpt)] 23 | enum Command { 24 | /// Update this project's encrypted secrets to the latest version 25 | /// 26 | /// This command will walk the user through updating a project's secrets by: 27 | /// 1. Ensuring that the secrets repository has all the latest data from the server 28 | /// 2. Checking if the user wants to change which secrets branch being used to fetch secrets 29 | /// 3. Prompting the user to update to the latest secrets 30 | /// 4. 31 | 32 | //switch the secrets repo to the pinned commit hash 33 | /// in the `.configure` file, then copy the files specified in the `files_to_copy` hash 34 | /// to their specified destination, encrypting them with the format $filename+".enc". 35 | 36 | /// This command will download the latest secrets commits from the repo 37 | /// and update the pinned commit hash in the `.configure` file to the newest commit 38 | /// in the branch specified by `.configure`. 39 | Update, 40 | 41 | /// Decrypt the current secrets for this project. 42 | /// 43 | Apply, 44 | 45 | /// Change secrets settings 46 | /// 47 | /// This command will provide step-by-step help to make changes to the secrets configuration. 48 | Init, 49 | 50 | /// Ensure the `.configure` file is valid 51 | Validate, 52 | 53 | /// Create a new encryption key for use with a project 54 | CreateKey, 55 | } 56 | 57 | pub fn main() { 58 | let options = Options::from_args(); 59 | 60 | CombinedLogger::init(vec![TermLogger::new( 61 | options.verbose.get_with_default(LevelFilter::Info), 62 | Config::default(), 63 | TerminalMode::Mixed, 64 | ) 65 | .unwrap()]) 66 | .unwrap(); 67 | 68 | debug!("libconfigure initialized"); 69 | 70 | match Options::from_args().command { 71 | Command::Apply => configure::apply(), 72 | Command::Update => configure::update(), 73 | Command::Init => configure::init(), 74 | Command::Validate => configure::validate(), 75 | Command::CreateKey => println!("{:?}", configure::generate_encryption_key()), 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Configure 2 | 3 | A tool for storing encrypted secrets in your repository, and decrypting them in CI. It allows you to store your configuration files in your source repository, even if that repository is public. 4 | 5 | ## How it works 6 | This tool assumes you want to copy your configuration file (such as API keys for third-party services) into your project in an encrypted form. When running on developer machines or CI, you'll then be able to decrypt the files when building. 7 | 8 | The configuration is specified in a `.configure` file at the root of your project. It defines the following fields: 9 | 10 | **Project Name** 11 | The `project_name` field matches a key in a `keys.json` file defined in the root of your secrets repository. That file contains the encryption/decryption key for the project. 12 | 13 | **Branch** 14 | The default branch to pull new secrets from when running `configure update`. 15 | 16 | **Pinned Hash** 17 | The `pinned_hash` refers to the commit hash associated with the version of the secrets current in use for this project. 18 | 19 | **Files to Copy** 20 | The `files_to_copy` is a list of file hashes, each containing a `file` and `destination` key. The `file` key is the path to the file relative to the secrets repo root. The `destination` key is the path to where the file should be placed relative to the project root. 21 | 22 | A sample `.configure` file looks like: 23 | 24 | ```json 25 | { 26 | "project_name": "my-sample-project", 27 | "branch": "main", 28 | "pinned_hash": "f6b64d80449499a0e45ba5ef2cb53b0179a9b8b8", 29 | "files_to_copy": [ 30 | { 31 | "file": "gradle.properties", 32 | "destination": "my-project/gradle.properties" 33 | } 34 | ] 35 | } 36 | ``` 37 | 38 | ## How to use it 39 | 40 | The configure tool has two main jobs: copy plain-text secrets files from your secrets repository into the project as encrypted blobs, and decrypting those blogs back into the plain-text files on developer and build machines. 41 | 42 | ### Setup 43 | 44 | `configure init` will walk you through the process of setting up your project. It will: 45 | 46 | - Create the `.configure` file for you 47 | - Create an entry in your `keys.json` file for the project (if one doesn't already exist) 48 | - Prompt you to add files from the secrets repo to the the `.configure` file 49 | - Run the initial encryption process on the files provided, storing them in the repository 50 | - Run the initial decryption process on the files proided, making the project ready for development 51 | 52 | ### Update 53 | 54 | `configure update` is used to update the encrypted secrets in the project to the latest version in the secrets repo. 55 | 56 | 57 | ### Apply 58 | `configure apply` is used to decrypt the secrets in the project and apply the decrypted secrets to their destination. -------------------------------------------------------------------------------- /src/encryption.rs: -------------------------------------------------------------------------------- 1 | use crate::ConfigureError; 2 | use log::debug; 3 | use sodiumoxide::base64::Variant; 4 | use sodiumoxide::base64::{decode, encode}; 5 | use sodiumoxide::crypto::secretbox; 6 | use std::fs::{read, write}; 7 | use std::io::{Error, ErrorKind}; 8 | use std::path::PathBuf; 9 | 10 | pub fn init() -> Result<(), ConfigureError> { 11 | return match sodiumoxide::init() { 12 | Ok(()) => Ok(()), 13 | Err(()) => Err(ConfigureError::EncryptionUnavailable), 14 | } 15 | } 16 | 17 | pub fn generate_key() -> String { 18 | debug!("Generating an encryption key"); 19 | let key_bytes = secretbox::gen_key(); 20 | encode_key(key_bytes) 21 | } 22 | 23 | pub fn encrypt_file( 24 | input_path: &PathBuf, 25 | output_path: &PathBuf, 26 | secret: &str, 27 | ) -> Result<(), std::io::Error> { 28 | let content = read(input_path)?; 29 | let ciphertext = encrypt_bytes(content, decode_key(secret)); 30 | write(&output_path, &ciphertext)?; 31 | 32 | Ok(()) 33 | } 34 | 35 | pub fn decrypt_file( 36 | input_path: &PathBuf, 37 | output_path: &PathBuf, 38 | secret: &str, 39 | ) -> Result<(), std::io::Error> { 40 | let content = read(input_path)?; 41 | 42 | match decrypt_bytes(content, decode_key(secret)) { 43 | Ok(decrypted_bytes) => Ok(write(&output_path, decrypted_bytes)?), 44 | Err(_err) => Err(Error::new(ErrorKind::InvalidData, "Unable to decrypt file")), 45 | } 46 | } 47 | 48 | fn encrypt_bytes(input: Vec, key: sodiumoxide::crypto::secretbox::Key) -> Vec { 49 | let nonce = secretbox::gen_nonce(); 50 | let secret_bytes = secretbox::seal(&input, &nonce, &key); 51 | [&nonce[..], &secret_bytes].concat() 52 | } 53 | 54 | fn decrypt_bytes(input: Vec, key: sodiumoxide::crypto::secretbox::Key) -> Result, ()> { 55 | // Encoded Format byte layout: 56 | // |======================================|=====================================| 57 | // | 0 23 | 24 ∞ | 58 | // |======================================|=====================================| 59 | // | nonce | encrypted data | 60 | // |======================================|=====================================| 61 | 62 | const NONCE_SIZE: usize = 24; 63 | 64 | // Read the nonce bytes 65 | let mut nonce_bytes: [u8; NONCE_SIZE] = Default::default(); 66 | nonce_bytes.copy_from_slice(&input[0..NONCE_SIZE]); 67 | let nonce = sodiumoxide::crypto::secretbox::Nonce(nonce_bytes); 68 | 69 | // Read the encrypted data bytes 70 | let data_bytes = &input[NONCE_SIZE..]; 71 | 72 | Ok(secretbox::open(&data_bytes, &nonce, &key)?) 73 | } 74 | 75 | fn encode_key(key: sodiumoxide::crypto::secretbox::Key) -> String { 76 | encode(&key, Variant::Original) 77 | } 78 | 79 | fn decode_key(key: &str) -> sodiumoxide::crypto::secretbox::Key { 80 | let decoded_key_bytes = decode(key, Variant::Original).expect("Unable to decode key"); 81 | 82 | let mut key_bytes: [u8; 32] = Default::default(); 83 | key_bytes.copy_from_slice(&decoded_key_bytes); 84 | 85 | sodiumoxide::crypto::secretbox::Key(key_bytes) 86 | } 87 | 88 | #[cfg(test)] 89 | mod tests { 90 | // Note this useful idiom: importing names from outer (for mod tests) scope. 91 | use super::*; 92 | 93 | #[test] 94 | fn test_init_does_not_fail() { 95 | assert!(init().is_ok()); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/git.rs: -------------------------------------------------------------------------------- 1 | use crate::ConfigureError; 2 | use git2::Oid; 3 | use git2::{BranchType, Error, ErrorCode, Repository, ResetType}; 4 | use log::debug; 5 | 6 | pub fn get_current_secrets_branch() -> Result { 7 | let repo = get_secrets_repo()?; 8 | let head = match repo.head() { 9 | Ok(head) => Some(head), 10 | Err(ref e) if e.code() == ErrorCode::UnbornBranch || e.code() == ErrorCode::NotFound => { 11 | None 12 | } 13 | Err(e) => return Err(e), 14 | }; 15 | 16 | let head = head.as_ref().and_then(|h| h.shorthand()); 17 | 18 | Ok(head.unwrap().to_string()) 19 | } 20 | 21 | pub fn get_secrets_branches() -> Result, Error> { 22 | let repo = get_secrets_repo()?; 23 | let branches = repo.branches(Some(BranchType::Local))?; 24 | let branch_names: Vec = branches 25 | .into_iter() 26 | .map(|branch| branch.expect("Unable to read branch")) 27 | .map(|branch| { 28 | String::from( 29 | branch 30 | .0 31 | .name() 32 | .expect("Unable to read branch name") 33 | .unwrap(), 34 | ) 35 | }) 36 | .collect::>(); 37 | 38 | Ok(branch_names) 39 | } 40 | 41 | // Assumes you're using `origin` as the remote name 42 | pub fn fetch_secrets_latest_remote_data() -> Result<(), std::io::Error> { 43 | let path = crate::fs::find_secrets_repo().unwrap(); 44 | 45 | std::process::Command::new("git") 46 | .arg("fetch") 47 | .current_dir(std::fs::canonicalize(path).unwrap()) 48 | .output()?; // Wait for it to finish and collect its output 49 | 50 | debug!("Fetch Complete"); 51 | 52 | Ok(()) 53 | } 54 | 55 | pub fn get_secrets_current_hash() -> Result { 56 | let repo = get_secrets_repo()?; 57 | let latest_commit = repo.head()?.peel_to_commit()?; 58 | Ok(latest_commit.id().to_string()) 59 | } 60 | 61 | // Fetches the latest hash on the specified branch 62 | // 63 | // You should run `fetch_secrets_latest_remote_data` before this method, otherwise your info might be out-of-date 64 | pub fn get_secrets_latest_hash(branch: &str) -> Result { 65 | if get_current_secrets_branch().unwrap() != branch {} 66 | 67 | let repo = get_secrets_repo()?; 68 | let latest_commit = repo.head()?.peel_to_commit()?; 69 | 70 | Ok(latest_commit.id().to_string()) 71 | } 72 | 73 | pub fn get_latest_hash_for_remote_branch(branch: &str) -> Result { 74 | let path = crate::fs::find_secrets_repo().unwrap(); 75 | 76 | let remote_ref = "origin/".to_owned() + branch; 77 | 78 | debug!("Looking for remote ref: {:?}", remote_ref); 79 | 80 | let output = std::process::Command::new("git") 81 | .arg("rev-parse") 82 | .arg(remote_ref) 83 | .current_dir(std::fs::canonicalize(path).unwrap()) 84 | .output()?; // Wait for it to finish and collect its output 85 | 86 | let string = std::str::from_utf8(&output.stdout).expect("Unable to parse output"); 87 | 88 | debug!("Result: {}", string); 89 | 90 | Ok(String::from(string.trim_end())) 91 | } 92 | 93 | pub fn check_out_hash(hash: &str) -> Result<(), Error> { 94 | let repo = get_secrets_repo()?; 95 | 96 | let oid = Oid::from_str(hash).expect("Invalid Hash"); 97 | 98 | let obj = repo 99 | .find_commit(oid) 100 | .expect("No commit exists with that hash") 101 | .into_object(); 102 | 103 | repo.set_head_detached(oid)?; 104 | repo.reset(&obj, ResetType::Hard, None)?; 105 | 106 | Ok(()) 107 | } 108 | 109 | pub fn check_out_branch(branch_name: &str) -> Result<(), Error> { 110 | debug!("Trying to check out branch: {:?}", branch_name); 111 | let repo = get_secrets_repo()?; 112 | let ref_name = "refs/heads/".to_owned() + branch_name; 113 | debug!("Checking out: {:?}", ref_name); 114 | 115 | repo.set_head(&ref_name)?; 116 | 117 | debug!("Checkout successful"); 118 | Ok(()) 119 | } 120 | 121 | pub fn check_out_branch_at_revision(branch_name: &str, hash: &str) -> Result<(), Error> { 122 | // If we're asked to check out a commit that's not currently on a branch, 123 | // just switch to it directly 124 | if branch_name == "HEAD" { 125 | return check_out_hash(hash); 126 | } 127 | 128 | let repo = get_secrets_repo()?; 129 | let ref_name = "refs/heads/".to_owned() + branch_name; 130 | 131 | repo.set_head(&ref_name)?; 132 | 133 | let oid = Oid::from_str(hash).expect("Invalid Hash"); 134 | 135 | let obj = repo 136 | .find_commit(oid) 137 | .expect("No commit exists with that hash") 138 | .into_object(); 139 | 140 | repo.reset(&obj, ResetType::Hard, None)?; 141 | 142 | Ok(()) 143 | } 144 | 145 | // Returns the number of commits between two hashes. If the hashes aren't part of the same history 146 | // or if `hash2` comes before `hash1`, the result will be `0` 147 | pub fn secrets_repo_distance_between(hash1: &str, hash2: &str) -> Result { 148 | // If we're asked to calculate the distance between two of the same hash, we can skip a lot of work 149 | if hash1 == hash2 { 150 | return Ok(0); 151 | } 152 | 153 | let path = crate::fs::find_secrets_repo().unwrap(); 154 | 155 | let output = std::process::Command::new("git") 156 | .arg("--no-pager") 157 | .arg("log") 158 | .arg("-10000") 159 | .arg("--pretty=format:%H") 160 | .current_dir(std::fs::canonicalize(path).unwrap()) 161 | .output()?; 162 | 163 | let iter = std::str::from_utf8(&output.stdout) 164 | .expect("Unable to read hash list") 165 | .lines() 166 | .rev(); 167 | 168 | let index_of_configure_file_hash = iter 169 | .clone() 170 | .position(|r| r == "7a44543420761867bfb80f95c8864702d41059e3") 171 | .unwrap_or_else(|| { 172 | panic!( 173 | "The pinned hash in .configure {} doesn't exist in the repository history", 174 | &hash1 175 | ) 176 | }); 177 | 178 | let index_of_latest_repo_hash = iter.clone().position(|r| r == hash2).unwrap_or_else(|| { 179 | panic!( 180 | "The provided hash {} doesn't exist in the repository history", 181 | &hash2 182 | ) 183 | }); 184 | 185 | let distance = std::cmp::min(index_of_latest_repo_hash - index_of_configure_file_hash, 0); 186 | 187 | Ok(distance as i32) 188 | } 189 | 190 | pub enum RepoSyncState { 191 | /// The local secrets repository has commits that the server does not have 192 | Ahead, 193 | 194 | /// The server has commits that the local secrets repository does not have 195 | Behind, 196 | 197 | /// The local secrets repository and server are in sync 198 | Synced, 199 | } 200 | 201 | pub struct RepoStatus { 202 | /// The local repository sync state – ahead of, behind, or in sync with the server 203 | pub sync_state: RepoSyncState, 204 | 205 | /// How many commits the local repository is out of sync by. If the repository is in sync, 206 | /// this value will be `0` 207 | pub distance: i32, 208 | } 209 | 210 | impl RepoStatus { 211 | fn synced() -> RepoStatus { 212 | RepoStatus { 213 | sync_state: RepoSyncState::Synced, 214 | distance: 0, 215 | } 216 | } 217 | } 218 | 219 | pub fn get_secrets_repo_status() -> Result { 220 | let path = crate::fs::find_secrets_repo()?; 221 | 222 | let output = std::process::Command::new("git") 223 | .arg("status") 224 | .arg("--porcelain") 225 | .arg("-b") 226 | .current_dir(std::fs::canonicalize(path).unwrap()) 227 | .output()?; // Wait for it to finish and collect its output 228 | 229 | let status = std::str::from_utf8(&output.stdout).expect("Unable to read output data"); 230 | 231 | Ok(parse_repo_status(status)?) 232 | } 233 | 234 | fn parse_repo_status(status: &str) -> Result { 235 | if status.contains("...") { 236 | return Ok(RepoStatus::synced()); 237 | } 238 | 239 | let digits = status 240 | .chars() 241 | .filter(|c| c.is_digit(10)) 242 | .collect::() 243 | .parse::()?; 244 | 245 | if status.contains("ahead") { 246 | return Ok(RepoStatus { 247 | sync_state: RepoSyncState::Ahead, 248 | distance: digits, 249 | }); 250 | } 251 | 252 | if status.contains("behind") { 253 | return Ok(RepoStatus { 254 | sync_state: RepoSyncState::Behind, 255 | distance: digits, 256 | }); 257 | } 258 | 259 | Err(ConfigureError::GitStatusUnknownError {}) 260 | } 261 | 262 | fn get_secrets_repo() -> Result { 263 | let path = crate::fs::find_secrets_repo().unwrap(); 264 | Ok(Repository::open(path)?) 265 | } 266 | -------------------------------------------------------------------------------- /src/fs.rs: -------------------------------------------------------------------------------- 1 | use crate::encryption::{decrypt_file, encrypt_file}; 2 | use crate::ConfigurationFile; 3 | use crate::ConfigureError; 4 | use log::{debug, info}; 5 | use ring::digest::{Context, SHA256}; 6 | use std::env; 7 | use std::fs::{create_dir_all, remove_file, rename, File}; 8 | use std::io::{BufReader, Error, Read, Write}; 9 | use std::path::PathBuf; 10 | use serde_json::json; 11 | 12 | /// Find the .configure file in the current project 13 | pub fn find_configure_file() -> PathBuf { 14 | let project_root = find_project_root(); 15 | 16 | let configure_file_path = project_root.join(".configure"); 17 | 18 | debug!("Configure file found at: {:?}", configure_file_path); 19 | 20 | if !configure_file_path.exists() { 21 | info!( 22 | "No configure file found at: {:?}. Creating one for you", 23 | configure_file_path 24 | ); 25 | 26 | save_configuration(&ConfigurationFile::default()) 27 | .expect("There is no `configure.json` file in your project, and creating one failed"); 28 | } 29 | 30 | configure_file_path 31 | } 32 | 33 | pub fn find_keys_file() -> Result { 34 | let secrets_root = find_secrets_repo(); 35 | let keys_file_path = secrets_root?.join("keys.json"); 36 | 37 | debug!("Keys file found at: {:?}", keys_file_path); 38 | 39 | if !keys_file_path.exists() { 40 | info!( 41 | "No keys file found at: {:?}. Creating one for you", 42 | keys_file_path 43 | ); 44 | write_file_with_contents(&keys_file_path, "{}").expect( 45 | "There is no `keys.json` file in your secrets repository, and creating one failed", 46 | ); 47 | } 48 | 49 | Ok(keys_file_path) 50 | } 51 | 52 | pub fn find_project_root() -> PathBuf { 53 | let path = env::current_dir().expect("Unable to determine current directory"); 54 | 55 | let repo = git2::Repository::discover(&path) 56 | .expect("Unable to find the root of the respository – are you sure you're running this inside a git repo?"); 57 | 58 | debug!("Discovered Repository at {:?}", &path); 59 | 60 | repo.workdir().unwrap().to_path_buf() 61 | } 62 | 63 | pub fn find_secrets_repo() -> Result { 64 | // TODO: Allow the user to set their own secrets path using an environment variable 65 | 66 | let home_dir = dirs::home_dir().expect("Unable to determine user home directory"); 67 | 68 | let root_secrets_path = home_dir.join(".mobile-secrets"); 69 | 70 | if root_secrets_path.exists() && root_secrets_path.is_dir() { 71 | return Ok(root_secrets_path); 72 | } 73 | 74 | // If the user has a `Projects` directory 75 | let projects_path = home_dir.join("Projects"); 76 | if projects_path.exists() { 77 | let projects_secrets_path = projects_path.join(".mobile-secrets"); 78 | if projects_secrets_path.exists() && projects_secrets_path.is_dir() { 79 | return Ok(projects_secrets_path); 80 | } 81 | } 82 | 83 | Err(crate::configure::ConfigureError::SecretsNotPresent) 84 | } 85 | 86 | pub fn read_configuration() -> ConfigurationFile { 87 | let configure_file_path = find_configure_file(); 88 | let mut file = File::open(&configure_file_path).expect("Unable to open configuration file"); 89 | 90 | let mut file_contents = String::new(); 91 | file.read_to_string(&mut file_contents) 92 | .expect("Unable to read configuration file"); 93 | 94 | let result: ConfigurationFile = serde_json::from_str(&file_contents) 95 | .expect("Unable to parse configuration file – the JSON is probably invalid"); 96 | 97 | result 98 | } 99 | 100 | pub fn save_configuration(configuration: &ConfigurationFile) -> Result<(), Error> { 101 | let serialized = serde_json::to_string_pretty(&configuration)?; 102 | 103 | let configure_file = find_configure_file(); 104 | 105 | debug!("Writing to: {:?}", configure_file); 106 | 107 | let mut file = File::create(configure_file)?; 108 | file.write_all(serialized.as_bytes())?; 109 | Ok(()) 110 | } 111 | 112 | pub fn read_encryption_key(configuration: &ConfigurationFile) -> Result, ConfigureError> { 113 | let keys_file_path = find_keys_file()?; 114 | 115 | debug!("Reading keys from {:?}", keys_file_path); 116 | 117 | let file = match File::open(keys_file_path) { 118 | Ok(file) => file, 119 | Err(_) => return Err(ConfigureError::KeysFileCannotBeRead), 120 | }; 121 | 122 | let json: serde_json::Value = match serde_json::from_reader(file) { 123 | Ok(json) => json, 124 | Err(_) => return Err(ConfigureError::KeysFileIsNotValidJSON), 125 | }; 126 | 127 | match json.get(&configuration.project_name) { 128 | Some(key) => return Ok(Some(String::from(key.as_str().unwrap()))), 129 | None => return Ok(None), 130 | }; 131 | } 132 | 133 | pub fn generate_encryption_key(configuration: &ConfigurationFile) -> Result<(), ConfigureError> { 134 | let keys_file_path = find_keys_file()?; 135 | 136 | let file = match File::open(&keys_file_path) { 137 | Ok(file) => file, 138 | Err(_) => return Err(ConfigureError::KeysFileCannotBeRead), 139 | }; 140 | 141 | let mut json: serde_json::Value = match serde_json::from_reader(file) { 142 | Ok(json) => json, 143 | Err(_) => return Err(ConfigureError::KeysFileIsNotValidJSON), 144 | }; 145 | 146 | json[&configuration.project_name] = json!("Foo!"); 147 | 148 | write_file_with_contents(&keys_file_path, &serde_json::to_string_pretty(&json).unwrap())?; 149 | 150 | Ok(()) 151 | } 152 | 153 | pub fn decrypt_files_for_configuration( 154 | configuration: &ConfigurationFile, 155 | ) -> Result<(), ConfigureError> { 156 | let project_root = find_project_root(); 157 | let encryption_key = match read_encryption_key(configuration) { 158 | Ok(key) => match key { 159 | Some(value) => value, 160 | None => return Err(ConfigureError::MissingProjectKey), 161 | }, 162 | Err(err) => return Err(err), 163 | }; 164 | 165 | for file in &configuration.files_to_copy { 166 | let source = project_root.join(&file.get_encrypted_destination()); 167 | let destination = project_root.join(&file.get_decrypted_destination()); 168 | 169 | create_parent_directory_for_path_if_not_exists(&destination)?; 170 | 171 | // If the developer tries to run `configure_apply` while missing the encrypted originals, this script will crash saying "missing file" 172 | // We can try to detect this scenario and fix things for the developer if the secrets are available locally, but it's tricky because 173 | // we'd need to basically run `configure update` inside this method for just the one file. For now, we'll just error out. 174 | if !source.exists() { 175 | info!("Encrypted original file at {:?} not found", source); 176 | return Err(ConfigureError::EncryptedFileMissing {}); 177 | } 178 | 179 | // If the file already exists, make a backup of the old one in case we need it later 180 | if destination.exists() { 181 | let backup_destination = project_root.join(&file.get_backup_destination()); 182 | 183 | debug!( 184 | "{:?} already exists – making a backup at {:?}", 185 | destination, backup_destination 186 | ); 187 | rename(&destination, &backup_destination)?; 188 | 189 | // Encrypt the file and write the encrypted contents to the destination 190 | debug!( 191 | "Encrypting file at {:?} and storing contents at {:?}", 192 | source, destination 193 | ); 194 | decrypt_file(&source, &destination, &encryption_key)?; 195 | 196 | // If the backup file is identical to the old file, remove the backup 197 | let new_file_hash = hash_file(&destination); 198 | let original_file_hash = hash_file(&backup_destination); 199 | 200 | debug!("Original File Hash: {:?}", original_file_hash); 201 | debug!("New File hash: {:?}", new_file_hash); 202 | 203 | if hash_file(&destination)? == hash_file(&backup_destination)? { 204 | debug!("Removing backup file because it's the same as the original"); 205 | remove_file(&backup_destination)?; 206 | } else { 207 | debug!("Keeping backup file because it differs from the original"); 208 | } 209 | 210 | } else { 211 | // Encrypt the file and write the encrypted contents to the destination 212 | debug!( 213 | "Encrypting file at {:?} and storing contents at {:?}", 214 | source, destination 215 | ); 216 | decrypt_file(&source, &destination, &encryption_key)?; 217 | } 218 | } 219 | 220 | Ok(()) 221 | } 222 | 223 | pub fn write_encrypted_files_for_configuration( 224 | configuration: &ConfigurationFile, 225 | ) -> Result<(), ConfigureError> { 226 | let project_root = find_project_root(); 227 | let secrets_root = find_secrets_repo().unwrap(); 228 | let encryption_key = match read_encryption_key(configuration) { 229 | Ok(key) => match key { 230 | Some(value) => value, 231 | None => return Err(ConfigureError::MissingProjectKey), 232 | }, 233 | Err(err) => return Err(err), 234 | }; 235 | 236 | for file in &configuration.files_to_copy { 237 | let source = &secrets_root.join(&file.source); 238 | let destination = project_root.join(&file.get_encrypted_destination()); 239 | 240 | create_parent_directory_for_path_if_not_exists(&destination)?; 241 | 242 | // Encrypt the file and write the encrypted contents to the destination 243 | debug!( 244 | "Encrypting file at {:?} and storing contents at {:?}", 245 | source, destination 246 | ); 247 | 248 | encrypt_file(&source, &destination, &encryption_key)?; 249 | } 250 | 251 | Ok(()) 252 | } 253 | 254 | /// Helper method to create an empty file 255 | fn write_file_with_contents(path: &PathBuf, contents: &str) -> Result<(), std::io::Error> { 256 | let mut file = File::create(path)?; 257 | file.write_all(contents.as_bytes())?; 258 | Ok(()) 259 | } 260 | 261 | /// Returns the SHA-256 hash of a file at the given path 262 | fn hash_file(path: &PathBuf) -> Result { 263 | let input = File::open(path)?; 264 | let mut reader = BufReader::new(input); 265 | let mut context = Context::new(&SHA256); 266 | let mut buffer = [0; 1024]; 267 | 268 | loop { 269 | let count = reader.read(&mut buffer)?; 270 | if count == 0 { 271 | break; 272 | } 273 | context.update(&buffer[..count]); 274 | } 275 | 276 | let digest = context.finish(); 277 | 278 | Ok(base64::encode(digest.as_ref())) 279 | } 280 | 281 | fn create_parent_directory_for_path_if_not_exists(path: &PathBuf) -> Result<(), Error> { 282 | let parent = match path.parent() { 283 | Some(parent) => parent, 284 | None => return Ok(()), // if we're in the root of the filesystem, we have no work to do 285 | }; 286 | 287 | Ok(create_dir_all(parent)?) 288 | } 289 | -------------------------------------------------------------------------------- /src/configure.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::*; 2 | use crate::git::*; 3 | use crate::ui::*; 4 | use indicatif::ProgressBar; 5 | use chrono::prelude::*; 6 | 7 | use console::style; 8 | use log::{debug, info}; 9 | use serde::{Deserialize, Serialize}; 10 | 11 | use thiserror::Error; 12 | 13 | #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)] 14 | pub struct ConfigurationFile { 15 | pub project_name: String, 16 | pub branch: String, 17 | pub pinned_hash: String, 18 | pub files_to_copy: Vec, 19 | } 20 | 21 | impl ConfigurationFile { 22 | pub fn is_empty(&self) -> bool { 23 | self == &ConfigurationFile::default() 24 | } 25 | 26 | fn needs_project_name(&self) -> bool { 27 | self.project_name == "" 28 | } 29 | 30 | fn needs_branch(&self) -> bool { 31 | self.branch == "" 32 | } 33 | 34 | fn needs_pinned_hash(&self) -> bool { 35 | self.pinned_hash == "" 36 | } 37 | } 38 | 39 | impl Default for ConfigurationFile { 40 | fn default() -> Self { 41 | let files_to_copy: Vec = Vec::new(); 42 | ConfigurationFile { 43 | project_name: "".to_string(), 44 | branch: "".to_string(), 45 | pinned_hash: "".to_string(), 46 | files_to_copy, 47 | } 48 | } 49 | } 50 | 51 | #[derive(Error, Debug)] 52 | pub enum ConfigureError { 53 | 54 | #[error("Unable to initialize underlying encryption")] 55 | EncryptionUnavailable, 56 | 57 | #[error("Unable to decrypt file")] 58 | DataDecryptionError(#[from] std::io::Error), 59 | 60 | #[error("Invalid git status")] 61 | GitStatusParsingError(#[from] std::num::ParseIntError), 62 | 63 | #[error("Invalid git status")] 64 | GitStatusUnknownError, 65 | 66 | #[error("No secrets repository could be found on this machine")] 67 | SecretsNotPresent, 68 | 69 | #[error("An encrypted file is missing – unable to apply secrets to project. Run `configure update` to fix this")] 70 | EncryptedFileMissing, 71 | 72 | #[error("Unable to read keys.json file in your secrets repo")] 73 | KeysFileCannotBeRead, 74 | 75 | #[error("keys.json file in your secrets repo is not valid json")] 76 | KeysFileIsNotValidJSON, 77 | 78 | #[error("That project key is not defined in keys.json")] 79 | MissingProjectKey 80 | } 81 | 82 | #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)] 83 | pub struct File { 84 | #[serde(rename = "file")] 85 | pub source: String, 86 | pub destination: String, 87 | } 88 | 89 | impl File { 90 | pub fn get_encrypted_destination(&self) -> String { 91 | self.destination.clone() + &".enc".to_owned() 92 | } 93 | 94 | pub fn get_decrypted_destination(&self) -> String { 95 | self.destination.clone() 96 | } 97 | 98 | pub fn get_backup_destination(&self) -> String { 99 | let path = std::path::Path::new(&self.destination); 100 | 101 | let directory = match path.parent() { 102 | Some(parent) => parent, 103 | None => std::path::Path::new("/"), 104 | }; 105 | 106 | 107 | let file_stem = path.file_stem().unwrap().to_str().unwrap_or(""); 108 | let datetime = Local::now().format("%Y-%m-%d-%H-%M-%S").to_string(); 109 | let extension = path.extension().unwrap_or(std::ffi::OsStr::new("")).to_str().unwrap_or(""); 110 | 111 | let filename = format!("{:}-{:}.{:}.bak", file_stem, datetime, extension); 112 | 113 | return directory 114 | .join(filename) 115 | .to_str() 116 | .unwrap() 117 | .to_string(); 118 | 119 | } 120 | } 121 | 122 | pub fn apply_configuration(configuration: ConfigurationFile) { 123 | // Decrypt the project's configuration files 124 | decrypt_files_for_configuration(&configuration).expect("Unable to decrypt and copy files"); 125 | 126 | debug!("All Files Copied!"); 127 | 128 | info!("Done") 129 | } 130 | 131 | pub fn update_configuration(mut configuration: ConfigurationFile) { 132 | let starting_branch = 133 | get_current_secrets_branch().expect("Unable to determine current secrets branch"); 134 | let starting_ref = 135 | get_secrets_current_hash().expect("Unable to determine current secrets commit hash"); 136 | 137 | heading("Configure Update"); 138 | 139 | // 140 | // Step 1 – Fetch the latest secrets from the server 141 | // We need them in order to update the pinned hash 142 | // 143 | let bar = ProgressBar::new_spinner(); 144 | bar.enable_steady_tick(125); 145 | bar.set_message("Fetching Latest Secrets"); 146 | 147 | fetch_secrets_latest_remote_data().expect("Unable to fetch latest secrets"); 148 | 149 | bar.finish_and_clear(); 150 | 151 | // 152 | // Step 2 – Check if the user wants to use a different secrets branch 153 | // 154 | configuration = prompt_for_branch(configuration, true); 155 | 156 | // 157 | // Step 3 – Check if the currente configuration branch is in sync with the server or not.or 158 | // If not, check with the user whether they'd like to continue 159 | // 160 | let status = get_secrets_repo_status().expect("Unable to get secrets repo status"); 161 | 162 | let should_continue = match status.sync_state { 163 | RepoSyncState::Ahead => { 164 | warn(&format!( 165 | "Your local secrets repo has {:?} change(s) that the server does not", 166 | status.distance 167 | )); 168 | confirm("Would you like to continue?") 169 | } 170 | RepoSyncState::Behind => { 171 | warn(&format!( 172 | "The server has {:?} change(s) that your local secrets repo does not", 173 | status.distance 174 | )); 175 | confirm("Would you like to continue?") 176 | } 177 | RepoSyncState::Synced => true, 178 | }; 179 | 180 | if !should_continue { 181 | return; 182 | } 183 | 184 | // 185 | // Step 4 – Check if the project's secrets are out of date compared to the server. 186 | // If they out of date, we'll prompt the user to pull the latest remote 187 | // changes into the local secrets repo before continuing. 188 | // 189 | let distance = 190 | configure_file_distance_behind_secrets_repo(&configuration, &configuration.branch); 191 | if distance > 0 { 192 | let message = format!( 193 | "This project is {:?} commit(s) behind the latest secrets. Would you like to use the latest secrets?", 194 | distance 195 | ); 196 | 197 | // Prompt to update to most recent secrets data in the branch 198 | if confirm(&message) { 199 | let latest_commit_hash = get_latest_hash_for_remote_branch(&configuration.branch) 200 | .expect("Unable to fetch latest commit hash"); 201 | 202 | debug!( 203 | "Moving the repo to {:?} at {:?}", 204 | &configuration.branch, latest_commit_hash 205 | ); 206 | 207 | check_out_branch_at_revision(&configuration.branch, &latest_commit_hash) 208 | .expect("Unable to check out branch at revision"); 209 | configuration.pinned_hash = latest_commit_hash; 210 | } 211 | } 212 | 213 | // 214 | // Step 5 – Write out encrypted files as needed 215 | // 216 | save_configuration(&configuration).expect("Unable to save updated configuration"); 217 | 218 | // 219 | // Step 6 – Write out encrypted files as needed 220 | // 221 | write_encrypted_files_for_configuration(&configuration) 222 | .expect("Unable to copy encrypted files"); 223 | 224 | // 225 | // Step 7 – Roll everything back to how it was before we started 226 | // 227 | crate::git::check_out_branch_at_revision(&starting_branch, &starting_ref) 228 | .expect("Unable to roll back to branch"); 229 | 230 | // 231 | // Step 8 – Apply these changes to the current repo 232 | // 233 | apply_configuration(configuration); 234 | } 235 | 236 | pub fn validate_configuration(configuration: ConfigurationFile) { 237 | println!("{:?}", configuration); 238 | } 239 | 240 | pub fn setup_configuration(mut configuration: ConfigurationFile) { 241 | heading("Configure Setup"); 242 | println!("Let's get configuration set up for this project."); 243 | newline(); 244 | 245 | // Help the user set the `project_name` field 246 | configuration = prompt_for_project_name_if_needed(configuration); 247 | 248 | // Help the user set the `branch` field 249 | configuration = prompt_for_branch(configuration, true); 250 | 251 | // Set the latest automatically hash based on the selected branch 252 | configuration = set_latest_hash_if_needed(configuration); 253 | 254 | // Help the user add files 255 | configuration = prompt_to_add_files(configuration); 256 | 257 | info!("Writing changes to .configure"); 258 | 259 | 260 | save_configuration(&configuration).expect("Unable to save configure file"); 261 | 262 | // Create a key in `keys.json` for the project if one doesn't already exist 263 | if read_encryption_key(&configuration).unwrap() == None { 264 | generate_encryption_key(&configuration).expect("Unable to automatically generate an encryption key for this project"); 265 | } 266 | } 267 | 268 | fn prompt_for_project_name_if_needed(mut configuration: ConfigurationFile) -> ConfigurationFile { 269 | // If there's already a project name, don't bother updating it 270 | if !configuration.needs_project_name() { 271 | return configuration; 272 | } 273 | 274 | let project_name = prompt("What is the name of your project?"); 275 | configuration.project_name = project_name.clone(); 276 | println!("Project Name set to: {:?}", project_name); 277 | 278 | configuration 279 | } 280 | 281 | fn prompt_for_branch(mut configuration: ConfigurationFile, force: bool) -> ConfigurationFile { 282 | // If there's already a branch set, don't bother updating it 283 | if !configuration.needs_branch() && !force { 284 | return configuration; 285 | } 286 | 287 | let secrets_repo_path = find_secrets_repo(); 288 | let current_branch = 289 | get_current_secrets_branch().expect("Unable to determine current secrets branch"); 290 | let branches = get_secrets_branches().expect("Unable to fetch secrets branches"); 291 | 292 | println!( 293 | "We've found your secrets repository at {:?}", 294 | secrets_repo_path 295 | ); 296 | newline(); 297 | println!("Which branch would you like to use?"); 298 | println!("Current Branch: {}", style(¤t_branch).green()); 299 | 300 | let selected_branch = 301 | select(branches, ¤t_branch).expect("Unable to read selected branch"); 302 | 303 | configuration.branch = selected_branch.clone(); 304 | println!("Secrets repo branch set to: {:?}", selected_branch); 305 | 306 | configuration 307 | } 308 | 309 | fn set_latest_hash_if_needed(mut configuration: ConfigurationFile) -> ConfigurationFile { 310 | if !configuration.needs_pinned_hash() { 311 | return configuration; 312 | } 313 | 314 | let latest_hash = get_secrets_latest_hash(&configuration.branch) 315 | .expect("Unable to fetch the latest secrets hash"); 316 | configuration.pinned_hash = latest_hash; 317 | 318 | configuration 319 | } 320 | 321 | fn prompt_to_add_files(mut configuration: ConfigurationFile) -> ConfigurationFile { 322 | let mut files = configuration.files_to_copy; 323 | 324 | let mut message = "Would you like to add files?"; 325 | 326 | if !files.is_empty() { 327 | message = "Would you like to add additional files?"; 328 | } 329 | 330 | while confirm(message) { 331 | match prompt_to_add_file() { 332 | Some(file) => files.push(file), 333 | None => continue, 334 | } 335 | } 336 | 337 | configuration.files_to_copy = files; 338 | 339 | configuration 340 | } 341 | 342 | fn prompt_to_add_file() -> Option { 343 | let relative_source_file_path = 344 | prompt("Enter the source file path (relative to the secrets root):"); 345 | 346 | let secrets_root = match find_secrets_repo() { 347 | Ok(repo_path) => repo_path, 348 | Err(_) => return None, 349 | }; 350 | 351 | let full_source_file_path = secrets_root.join(&relative_source_file_path); 352 | 353 | if !full_source_file_path.exists() { 354 | println!("Source File does not exist: {:?}", full_source_file_path); 355 | return None; 356 | } 357 | 358 | let relative_destination_file_path = 359 | prompt("Enter the destination file path (relative to the project root):"); 360 | 361 | let project_root = find_project_root(); 362 | let full_destination_file_path = project_root.join(&relative_destination_file_path); 363 | 364 | debug!("Destination: {:?}", full_destination_file_path); 365 | 366 | Some(File { 367 | source: relative_source_file_path, 368 | destination: relative_destination_file_path, 369 | }) 370 | } 371 | 372 | fn configure_file_distance_behind_secrets_repo( 373 | configuration: &ConfigurationFile, 374 | branch_name: &str, 375 | ) -> i32 { 376 | debug!("Checking if configure file is behind secrets repo"); 377 | 378 | let current_branch = 379 | get_current_secrets_branch().expect("Unable to get current secrets branch"); 380 | debug!("Current branch is: {:?}", current_branch); 381 | 382 | let current_hash = 383 | get_secrets_current_hash().expect("Unable to get current secrets hash"); 384 | debug!("Current hash is: {:?}", current_hash); 385 | 386 | check_out_branch(branch_name).expect("Unable to switch branches"); 387 | 388 | let latest_hash = get_secrets_current_hash().unwrap(); 389 | let distance = secrets_repo_distance_between(&configuration.pinned_hash, &latest_hash).unwrap(); 390 | 391 | // Put things back how we found them 392 | crate::git::check_out_branch_at_revision(¤t_branch, ¤t_hash) 393 | .expect("Unable to roll back to branch"); 394 | 395 | distance 396 | } 397 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "ansi_term" 5 | version = "0.11.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 8 | dependencies = [ 9 | "winapi", 10 | ] 11 | 12 | [[package]] 13 | name = "arrayref" 14 | version = "0.3.6" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" 17 | 18 | [[package]] 19 | name = "arrayvec" 20 | version = "0.5.2" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" 23 | 24 | [[package]] 25 | name = "atty" 26 | version = "0.2.14" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 29 | dependencies = [ 30 | "hermit-abi", 31 | "libc", 32 | "winapi", 33 | ] 34 | 35 | [[package]] 36 | name = "autocfg" 37 | version = "1.0.1" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 40 | 41 | [[package]] 42 | name = "base64" 43 | version = "0.12.3" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" 46 | 47 | [[package]] 48 | name = "base64" 49 | version = "0.13.0" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" 52 | 53 | [[package]] 54 | name = "bitflags" 55 | version = "1.2.1" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 58 | 59 | [[package]] 60 | name = "blake2b_simd" 61 | version = "0.5.11" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" 64 | dependencies = [ 65 | "arrayref", 66 | "arrayvec", 67 | "constant_time_eq", 68 | ] 69 | 70 | [[package]] 71 | name = "bumpalo" 72 | version = "3.4.0" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" 75 | 76 | [[package]] 77 | name = "bytecount" 78 | version = "0.6.1" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "c39a773ba75db12126d8d383f1bdbf7eb92ea47ec27dd0557aff1fedf172764c" 81 | 82 | [[package]] 83 | name = "cargo_metadata" 84 | version = "0.10.0" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "b8de60b887edf6d74370fc8eb177040da4847d971d6234c7b13a6da324ef0caf" 87 | dependencies = [ 88 | "semver", 89 | "serde", 90 | "serde_derive", 91 | "serde_json", 92 | ] 93 | 94 | [[package]] 95 | name = "cc" 96 | version = "1.0.65" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "95752358c8f7552394baf48cd82695b345628ad3f170d607de3ca03b8dacca15" 99 | dependencies = [ 100 | "jobserver", 101 | ] 102 | 103 | [[package]] 104 | name = "cfg-if" 105 | version = "0.1.10" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 108 | 109 | [[package]] 110 | name = "chrono" 111 | version = "0.4.19" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 114 | dependencies = [ 115 | "libc", 116 | "num-integer", 117 | "num-traits", 118 | "time", 119 | "winapi", 120 | ] 121 | 122 | [[package]] 123 | name = "clap" 124 | version = "2.33.3" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" 127 | dependencies = [ 128 | "ansi_term", 129 | "atty", 130 | "bitflags", 131 | "strsim", 132 | "textwrap", 133 | "unicode-width", 134 | "vec_map", 135 | ] 136 | 137 | [[package]] 138 | name = "configure" 139 | version = "0.1.0" 140 | dependencies = [ 141 | "base64 0.13.0", 142 | "chrono", 143 | "console", 144 | "dialoguer", 145 | "dirs 3.0.1", 146 | "git2", 147 | "indicatif", 148 | "log", 149 | "ring", 150 | "serde", 151 | "serde_json", 152 | "simplelog", 153 | "sodiumoxide", 154 | "structopt", 155 | "structopt-flags", 156 | "thiserror", 157 | ] 158 | 159 | [[package]] 160 | name = "console" 161 | version = "0.13.0" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "a50aab2529019abfabfa93f1e6c41ef392f91fbf179b347a7e96abb524884a08" 164 | dependencies = [ 165 | "encode_unicode", 166 | "lazy_static", 167 | "libc", 168 | "regex", 169 | "terminal_size", 170 | "unicode-width", 171 | "winapi", 172 | "winapi-util", 173 | ] 174 | 175 | [[package]] 176 | name = "constant_time_eq" 177 | version = "0.1.5" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" 180 | 181 | [[package]] 182 | name = "crossbeam-utils" 183 | version = "0.7.2" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" 186 | dependencies = [ 187 | "autocfg", 188 | "cfg-if", 189 | "lazy_static", 190 | ] 191 | 192 | [[package]] 193 | name = "dialoguer" 194 | version = "0.7.1" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "70f807b2943dc90f9747497d9d65d7e92472149be0b88bf4ce1201b4ac979c26" 197 | dependencies = [ 198 | "console", 199 | "lazy_static", 200 | "tempfile", 201 | "zeroize", 202 | ] 203 | 204 | [[package]] 205 | name = "dirs" 206 | version = "2.0.2" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" 209 | dependencies = [ 210 | "cfg-if", 211 | "dirs-sys", 212 | ] 213 | 214 | [[package]] 215 | name = "dirs" 216 | version = "3.0.1" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "142995ed02755914747cc6ca76fc7e4583cd18578746716d0508ea6ed558b9ff" 219 | dependencies = [ 220 | "dirs-sys", 221 | ] 222 | 223 | [[package]] 224 | name = "dirs-sys" 225 | version = "0.3.5" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" 228 | dependencies = [ 229 | "libc", 230 | "redox_users", 231 | "winapi", 232 | ] 233 | 234 | [[package]] 235 | name = "encode_unicode" 236 | version = "0.3.6" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 239 | 240 | [[package]] 241 | name = "error-chain" 242 | version = "0.12.4" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" 245 | dependencies = [ 246 | "version_check", 247 | ] 248 | 249 | [[package]] 250 | name = "form_urlencoded" 251 | version = "1.0.0" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "ece68d15c92e84fa4f19d3780f1294e5ca82a78a6d515f1efaabcc144688be00" 254 | dependencies = [ 255 | "matches", 256 | "percent-encoding", 257 | ] 258 | 259 | [[package]] 260 | name = "getrandom" 261 | version = "0.1.15" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" 264 | dependencies = [ 265 | "cfg-if", 266 | "libc", 267 | "wasi 0.9.0+wasi-snapshot-preview1", 268 | ] 269 | 270 | [[package]] 271 | name = "git2" 272 | version = "0.13.12" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "ca6f1a0238d7f8f8fd5ee642f4ebac4dbc03e03d1f78fbe7a3ede35dcf7e2224" 275 | dependencies = [ 276 | "bitflags", 277 | "libc", 278 | "libgit2-sys", 279 | "log", 280 | "openssl-probe", 281 | "openssl-sys", 282 | "url", 283 | ] 284 | 285 | [[package]] 286 | name = "glob" 287 | version = "0.3.0" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" 290 | 291 | [[package]] 292 | name = "hashbrown" 293 | version = "0.9.1" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" 296 | 297 | [[package]] 298 | name = "heck" 299 | version = "0.3.1" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" 302 | dependencies = [ 303 | "unicode-segmentation", 304 | ] 305 | 306 | [[package]] 307 | name = "hermit-abi" 308 | version = "0.1.17" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" 311 | dependencies = [ 312 | "libc", 313 | ] 314 | 315 | [[package]] 316 | name = "idna" 317 | version = "0.2.0" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" 320 | dependencies = [ 321 | "matches", 322 | "unicode-bidi", 323 | "unicode-normalization", 324 | ] 325 | 326 | [[package]] 327 | name = "indexmap" 328 | version = "1.6.0" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2" 331 | dependencies = [ 332 | "autocfg", 333 | "hashbrown", 334 | ] 335 | 336 | [[package]] 337 | name = "indicatif" 338 | version = "0.15.0" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "7baab56125e25686df467fe470785512329883aab42696d661247aca2a2896e4" 341 | dependencies = [ 342 | "console", 343 | "lazy_static", 344 | "number_prefix", 345 | "regex", 346 | ] 347 | 348 | [[package]] 349 | name = "itoa" 350 | version = "0.4.6" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" 353 | 354 | [[package]] 355 | name = "jobserver" 356 | version = "0.1.21" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2" 359 | dependencies = [ 360 | "libc", 361 | ] 362 | 363 | [[package]] 364 | name = "js-sys" 365 | version = "0.3.45" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "ca059e81d9486668f12d455a4ea6daa600bd408134cd17e3d3fb5a32d1f016f8" 368 | dependencies = [ 369 | "wasm-bindgen", 370 | ] 371 | 372 | [[package]] 373 | name = "lazy_static" 374 | version = "1.4.0" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 377 | 378 | [[package]] 379 | name = "libc" 380 | version = "0.2.80" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" 383 | 384 | [[package]] 385 | name = "libgit2-sys" 386 | version = "0.12.14+1.1.0" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "8f25af58e6495f7caf2919d08f212de550cfa3ed2f5e744988938ea292b9f549" 389 | dependencies = [ 390 | "cc", 391 | "libc", 392 | "libssh2-sys", 393 | "libz-sys", 394 | "openssl-sys", 395 | "pkg-config", 396 | ] 397 | 398 | [[package]] 399 | name = "libsodium-sys" 400 | version = "0.2.6" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "a685b64f837b339074115f2e7f7b431ac73681d08d75b389db7498b8892b8a58" 403 | dependencies = [ 404 | "cc", 405 | "libc", 406 | "pkg-config", 407 | ] 408 | 409 | [[package]] 410 | name = "libssh2-sys" 411 | version = "0.2.20" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "df40b13fe7ea1be9b9dffa365a51273816c345fc1811478b57ed7d964fbfc4ce" 414 | dependencies = [ 415 | "cc", 416 | "libc", 417 | "libz-sys", 418 | "openssl-sys", 419 | "pkg-config", 420 | "vcpkg", 421 | ] 422 | 423 | [[package]] 424 | name = "libz-sys" 425 | version = "1.1.2" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "602113192b08db8f38796c4e85c39e960c145965140e918018bcde1952429655" 428 | dependencies = [ 429 | "cc", 430 | "libc", 431 | "pkg-config", 432 | "vcpkg", 433 | ] 434 | 435 | [[package]] 436 | name = "log" 437 | version = "0.4.11" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" 440 | dependencies = [ 441 | "cfg-if", 442 | ] 443 | 444 | [[package]] 445 | name = "matches" 446 | version = "0.1.8" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" 449 | 450 | [[package]] 451 | name = "num-integer" 452 | version = "0.1.44" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 455 | dependencies = [ 456 | "autocfg", 457 | "num-traits", 458 | ] 459 | 460 | [[package]] 461 | name = "num-traits" 462 | version = "0.2.14" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 465 | dependencies = [ 466 | "autocfg", 467 | ] 468 | 469 | [[package]] 470 | name = "number_prefix" 471 | version = "0.3.0" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "17b02fc0ff9a9e4b35b3342880f48e896ebf69f2967921fe8646bf5b7125956a" 474 | 475 | [[package]] 476 | name = "once_cell" 477 | version = "1.5.2" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" 480 | 481 | [[package]] 482 | name = "openssl-probe" 483 | version = "0.1.2" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" 486 | 487 | [[package]] 488 | name = "openssl-sys" 489 | version = "0.9.58" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de" 492 | dependencies = [ 493 | "autocfg", 494 | "cc", 495 | "libc", 496 | "pkg-config", 497 | "vcpkg", 498 | ] 499 | 500 | [[package]] 501 | name = "percent-encoding" 502 | version = "2.1.0" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 505 | 506 | [[package]] 507 | name = "pkg-config" 508 | version = "0.3.19" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" 511 | 512 | [[package]] 513 | name = "ppv-lite86" 514 | version = "0.2.10" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" 517 | 518 | [[package]] 519 | name = "proc-macro-error" 520 | version = "1.0.4" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 523 | dependencies = [ 524 | "proc-macro-error-attr", 525 | "proc-macro2", 526 | "quote", 527 | "syn", 528 | "version_check", 529 | ] 530 | 531 | [[package]] 532 | name = "proc-macro-error-attr" 533 | version = "1.0.4" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 536 | dependencies = [ 537 | "proc-macro2", 538 | "quote", 539 | "version_check", 540 | ] 541 | 542 | [[package]] 543 | name = "proc-macro2" 544 | version = "1.0.24" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" 547 | dependencies = [ 548 | "unicode-xid", 549 | ] 550 | 551 | [[package]] 552 | name = "pulldown-cmark" 553 | version = "0.2.0" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "eef52fac62d0ea7b9b4dc7da092aa64ea7ec3d90af6679422d3d7e0e14b6ee15" 556 | dependencies = [ 557 | "bitflags", 558 | ] 559 | 560 | [[package]] 561 | name = "quote" 562 | version = "1.0.7" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 565 | dependencies = [ 566 | "proc-macro2", 567 | ] 568 | 569 | [[package]] 570 | name = "rand" 571 | version = "0.7.3" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 574 | dependencies = [ 575 | "getrandom", 576 | "libc", 577 | "rand_chacha", 578 | "rand_core", 579 | "rand_hc", 580 | ] 581 | 582 | [[package]] 583 | name = "rand_chacha" 584 | version = "0.2.2" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 587 | dependencies = [ 588 | "ppv-lite86", 589 | "rand_core", 590 | ] 591 | 592 | [[package]] 593 | name = "rand_core" 594 | version = "0.5.1" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 597 | dependencies = [ 598 | "getrandom", 599 | ] 600 | 601 | [[package]] 602 | name = "rand_hc" 603 | version = "0.2.0" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 606 | dependencies = [ 607 | "rand_core", 608 | ] 609 | 610 | [[package]] 611 | name = "redox_syscall" 612 | version = "0.1.57" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" 615 | 616 | [[package]] 617 | name = "redox_users" 618 | version = "0.3.5" 619 | source = "registry+https://github.com/rust-lang/crates.io-index" 620 | checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" 621 | dependencies = [ 622 | "getrandom", 623 | "redox_syscall", 624 | "rust-argon2", 625 | ] 626 | 627 | [[package]] 628 | name = "regex" 629 | version = "1.4.2" 630 | source = "registry+https://github.com/rust-lang/crates.io-index" 631 | checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c" 632 | dependencies = [ 633 | "regex-syntax", 634 | ] 635 | 636 | [[package]] 637 | name = "regex-syntax" 638 | version = "0.6.21" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189" 641 | 642 | [[package]] 643 | name = "remove_dir_all" 644 | version = "0.5.3" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 647 | dependencies = [ 648 | "winapi", 649 | ] 650 | 651 | [[package]] 652 | name = "ring" 653 | version = "0.16.18" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "70017ed5c555d79ee3538fc63ca09c70ad8f317dcadc1adc2c496b60c22bb24f" 656 | dependencies = [ 657 | "cc", 658 | "libc", 659 | "once_cell", 660 | "spin", 661 | "untrusted", 662 | "web-sys", 663 | "winapi", 664 | ] 665 | 666 | [[package]] 667 | name = "rust-argon2" 668 | version = "0.8.2" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "9dab61250775933275e84053ac235621dfb739556d5c54a2f2e9313b7cf43a19" 671 | dependencies = [ 672 | "base64 0.12.3", 673 | "blake2b_simd", 674 | "constant_time_eq", 675 | "crossbeam-utils", 676 | ] 677 | 678 | [[package]] 679 | name = "ryu" 680 | version = "1.0.5" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 683 | 684 | [[package]] 685 | name = "same-file" 686 | version = "1.0.6" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 689 | dependencies = [ 690 | "winapi-util", 691 | ] 692 | 693 | [[package]] 694 | name = "semver" 695 | version = "0.9.0" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 698 | dependencies = [ 699 | "semver-parser", 700 | "serde", 701 | ] 702 | 703 | [[package]] 704 | name = "semver-parser" 705 | version = "0.7.0" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 708 | 709 | [[package]] 710 | name = "serde" 711 | version = "1.0.117" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a" 714 | dependencies = [ 715 | "serde_derive", 716 | ] 717 | 718 | [[package]] 719 | name = "serde_derive" 720 | version = "1.0.117" 721 | source = "registry+https://github.com/rust-lang/crates.io-index" 722 | checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e" 723 | dependencies = [ 724 | "proc-macro2", 725 | "quote", 726 | "syn", 727 | ] 728 | 729 | [[package]] 730 | name = "serde_json" 731 | version = "1.0.59" 732 | source = "registry+https://github.com/rust-lang/crates.io-index" 733 | checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95" 734 | dependencies = [ 735 | "indexmap", 736 | "itoa", 737 | "ryu", 738 | "serde", 739 | ] 740 | 741 | [[package]] 742 | name = "simplelog" 743 | version = "0.7.6" 744 | source = "registry+https://github.com/rust-lang/crates.io-index" 745 | checksum = "3cf9a002ccce717d066b3ccdb8a28829436249867229291e91b25d99bd723f0d" 746 | dependencies = [ 747 | "chrono", 748 | "log", 749 | "term", 750 | ] 751 | 752 | [[package]] 753 | name = "skeptic" 754 | version = "0.13.5" 755 | source = "registry+https://github.com/rust-lang/crates.io-index" 756 | checksum = "7a6deb8efaf3ad8fd784139db8bbd51806bfbcee87c7be7578e9c930981fb808" 757 | dependencies = [ 758 | "bytecount", 759 | "cargo_metadata", 760 | "error-chain", 761 | "glob", 762 | "pulldown-cmark", 763 | "tempfile", 764 | "walkdir", 765 | ] 766 | 767 | [[package]] 768 | name = "sodiumoxide" 769 | version = "0.2.6" 770 | source = "registry+https://github.com/rust-lang/crates.io-index" 771 | checksum = "7038b67c941e23501573cb7242ffb08709abe9b11eb74bceff875bbda024a6a8" 772 | dependencies = [ 773 | "libc", 774 | "libsodium-sys", 775 | "serde", 776 | ] 777 | 778 | [[package]] 779 | name = "spin" 780 | version = "0.5.2" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 783 | 784 | [[package]] 785 | name = "strsim" 786 | version = "0.8.0" 787 | source = "registry+https://github.com/rust-lang/crates.io-index" 788 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 789 | 790 | [[package]] 791 | name = "structopt" 792 | version = "0.3.20" 793 | source = "registry+https://github.com/rust-lang/crates.io-index" 794 | checksum = "126d630294ec449fae0b16f964e35bf3c74f940da9dca17ee9b905f7b3112eb8" 795 | dependencies = [ 796 | "clap", 797 | "lazy_static", 798 | "structopt-derive", 799 | ] 800 | 801 | [[package]] 802 | name = "structopt-derive" 803 | version = "0.4.13" 804 | source = "registry+https://github.com/rust-lang/crates.io-index" 805 | checksum = "65e51c492f9e23a220534971ff5afc14037289de430e3c83f9daf6a1b6ae91e8" 806 | dependencies = [ 807 | "heck", 808 | "proc-macro-error", 809 | "proc-macro2", 810 | "quote", 811 | "syn", 812 | ] 813 | 814 | [[package]] 815 | name = "structopt-flags" 816 | version = "0.3.6" 817 | source = "registry+https://github.com/rust-lang/crates.io-index" 818 | checksum = "4654ef901a3897697bc76c48c1d0e73f925e5d801959db6d870d39a87beeae85" 819 | dependencies = [ 820 | "log", 821 | "skeptic", 822 | "structopt", 823 | ] 824 | 825 | [[package]] 826 | name = "syn" 827 | version = "1.0.50" 828 | source = "registry+https://github.com/rust-lang/crates.io-index" 829 | checksum = "443b4178719c5a851e1bde36ce12da21d74a0e60b4d982ec3385a933c812f0f6" 830 | dependencies = [ 831 | "proc-macro2", 832 | "quote", 833 | "unicode-xid", 834 | ] 835 | 836 | [[package]] 837 | name = "tempfile" 838 | version = "3.1.0" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" 841 | dependencies = [ 842 | "cfg-if", 843 | "libc", 844 | "rand", 845 | "redox_syscall", 846 | "remove_dir_all", 847 | "winapi", 848 | ] 849 | 850 | [[package]] 851 | name = "term" 852 | version = "0.6.1" 853 | source = "registry+https://github.com/rust-lang/crates.io-index" 854 | checksum = "c0863a3345e70f61d613eab32ee046ccd1bcc5f9105fe402c61fcd0c13eeb8b5" 855 | dependencies = [ 856 | "dirs 2.0.2", 857 | "winapi", 858 | ] 859 | 860 | [[package]] 861 | name = "terminal_size" 862 | version = "0.1.15" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "4bd2d183bd3fac5f5fe38ddbeb4dc9aec4a39a9d7d59e7491d900302da01cbe1" 865 | dependencies = [ 866 | "libc", 867 | "winapi", 868 | ] 869 | 870 | [[package]] 871 | name = "textwrap" 872 | version = "0.11.0" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 875 | dependencies = [ 876 | "unicode-width", 877 | ] 878 | 879 | [[package]] 880 | name = "thiserror" 881 | version = "1.0.22" 882 | source = "registry+https://github.com/rust-lang/crates.io-index" 883 | checksum = "0e9ae34b84616eedaaf1e9dd6026dbe00dcafa92aa0c8077cb69df1fcfe5e53e" 884 | dependencies = [ 885 | "thiserror-impl", 886 | ] 887 | 888 | [[package]] 889 | name = "thiserror-impl" 890 | version = "1.0.22" 891 | source = "registry+https://github.com/rust-lang/crates.io-index" 892 | checksum = "9ba20f23e85b10754cd195504aebf6a27e2e6cbe28c17778a0c930724628dd56" 893 | dependencies = [ 894 | "proc-macro2", 895 | "quote", 896 | "syn", 897 | ] 898 | 899 | [[package]] 900 | name = "time" 901 | version = "0.1.44" 902 | source = "registry+https://github.com/rust-lang/crates.io-index" 903 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 904 | dependencies = [ 905 | "libc", 906 | "wasi 0.10.0+wasi-snapshot-preview1", 907 | "winapi", 908 | ] 909 | 910 | [[package]] 911 | name = "tinyvec" 912 | version = "1.1.0" 913 | source = "registry+https://github.com/rust-lang/crates.io-index" 914 | checksum = "ccf8dbc19eb42fba10e8feaaec282fb50e2c14b2726d6301dbfeed0f73306a6f" 915 | dependencies = [ 916 | "tinyvec_macros", 917 | ] 918 | 919 | [[package]] 920 | name = "tinyvec_macros" 921 | version = "0.1.0" 922 | source = "registry+https://github.com/rust-lang/crates.io-index" 923 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 924 | 925 | [[package]] 926 | name = "unicode-bidi" 927 | version = "0.3.4" 928 | source = "registry+https://github.com/rust-lang/crates.io-index" 929 | checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" 930 | dependencies = [ 931 | "matches", 932 | ] 933 | 934 | [[package]] 935 | name = "unicode-normalization" 936 | version = "0.1.16" 937 | source = "registry+https://github.com/rust-lang/crates.io-index" 938 | checksum = "a13e63ab62dbe32aeee58d1c5408d35c36c392bba5d9d3142287219721afe606" 939 | dependencies = [ 940 | "tinyvec", 941 | ] 942 | 943 | [[package]] 944 | name = "unicode-segmentation" 945 | version = "1.7.0" 946 | source = "registry+https://github.com/rust-lang/crates.io-index" 947 | checksum = "db8716a166f290ff49dabc18b44aa407cb7c6dbe1aa0971b44b8a24b0ca35aae" 948 | 949 | [[package]] 950 | name = "unicode-width" 951 | version = "0.1.8" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 954 | 955 | [[package]] 956 | name = "unicode-xid" 957 | version = "0.2.1" 958 | source = "registry+https://github.com/rust-lang/crates.io-index" 959 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 960 | 961 | [[package]] 962 | name = "untrusted" 963 | version = "0.7.1" 964 | source = "registry+https://github.com/rust-lang/crates.io-index" 965 | checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" 966 | 967 | [[package]] 968 | name = "url" 969 | version = "2.2.0" 970 | source = "registry+https://github.com/rust-lang/crates.io-index" 971 | checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e" 972 | dependencies = [ 973 | "form_urlencoded", 974 | "idna", 975 | "matches", 976 | "percent-encoding", 977 | ] 978 | 979 | [[package]] 980 | name = "vcpkg" 981 | version = "0.2.10" 982 | source = "registry+https://github.com/rust-lang/crates.io-index" 983 | checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" 984 | 985 | [[package]] 986 | name = "vec_map" 987 | version = "0.8.2" 988 | source = "registry+https://github.com/rust-lang/crates.io-index" 989 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 990 | 991 | [[package]] 992 | name = "version_check" 993 | version = "0.9.2" 994 | source = "registry+https://github.com/rust-lang/crates.io-index" 995 | checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" 996 | 997 | [[package]] 998 | name = "walkdir" 999 | version = "2.3.1" 1000 | source = "registry+https://github.com/rust-lang/crates.io-index" 1001 | checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" 1002 | dependencies = [ 1003 | "same-file", 1004 | "winapi", 1005 | "winapi-util", 1006 | ] 1007 | 1008 | [[package]] 1009 | name = "wasi" 1010 | version = "0.9.0+wasi-snapshot-preview1" 1011 | source = "registry+https://github.com/rust-lang/crates.io-index" 1012 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 1013 | 1014 | [[package]] 1015 | name = "wasi" 1016 | version = "0.10.0+wasi-snapshot-preview1" 1017 | source = "registry+https://github.com/rust-lang/crates.io-index" 1018 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 1019 | 1020 | [[package]] 1021 | name = "wasm-bindgen" 1022 | version = "0.2.68" 1023 | source = "registry+https://github.com/rust-lang/crates.io-index" 1024 | checksum = "1ac64ead5ea5f05873d7c12b545865ca2b8d28adfc50a49b84770a3a97265d42" 1025 | dependencies = [ 1026 | "cfg-if", 1027 | "wasm-bindgen-macro", 1028 | ] 1029 | 1030 | [[package]] 1031 | name = "wasm-bindgen-backend" 1032 | version = "0.2.68" 1033 | source = "registry+https://github.com/rust-lang/crates.io-index" 1034 | checksum = "f22b422e2a757c35a73774860af8e112bff612ce6cb604224e8e47641a9e4f68" 1035 | dependencies = [ 1036 | "bumpalo", 1037 | "lazy_static", 1038 | "log", 1039 | "proc-macro2", 1040 | "quote", 1041 | "syn", 1042 | "wasm-bindgen-shared", 1043 | ] 1044 | 1045 | [[package]] 1046 | name = "wasm-bindgen-macro" 1047 | version = "0.2.68" 1048 | source = "registry+https://github.com/rust-lang/crates.io-index" 1049 | checksum = "6b13312a745c08c469f0b292dd2fcd6411dba5f7160f593da6ef69b64e407038" 1050 | dependencies = [ 1051 | "quote", 1052 | "wasm-bindgen-macro-support", 1053 | ] 1054 | 1055 | [[package]] 1056 | name = "wasm-bindgen-macro-support" 1057 | version = "0.2.68" 1058 | source = "registry+https://github.com/rust-lang/crates.io-index" 1059 | checksum = "f249f06ef7ee334cc3b8ff031bfc11ec99d00f34d86da7498396dc1e3b1498fe" 1060 | dependencies = [ 1061 | "proc-macro2", 1062 | "quote", 1063 | "syn", 1064 | "wasm-bindgen-backend", 1065 | "wasm-bindgen-shared", 1066 | ] 1067 | 1068 | [[package]] 1069 | name = "wasm-bindgen-shared" 1070 | version = "0.2.68" 1071 | source = "registry+https://github.com/rust-lang/crates.io-index" 1072 | checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307" 1073 | 1074 | [[package]] 1075 | name = "web-sys" 1076 | version = "0.3.45" 1077 | source = "registry+https://github.com/rust-lang/crates.io-index" 1078 | checksum = "4bf6ef87ad7ae8008e15a355ce696bed26012b7caa21605188cfd8214ab51e2d" 1079 | dependencies = [ 1080 | "js-sys", 1081 | "wasm-bindgen", 1082 | ] 1083 | 1084 | [[package]] 1085 | name = "winapi" 1086 | version = "0.3.9" 1087 | source = "registry+https://github.com/rust-lang/crates.io-index" 1088 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1089 | dependencies = [ 1090 | "winapi-i686-pc-windows-gnu", 1091 | "winapi-x86_64-pc-windows-gnu", 1092 | ] 1093 | 1094 | [[package]] 1095 | name = "winapi-i686-pc-windows-gnu" 1096 | version = "0.4.0" 1097 | source = "registry+https://github.com/rust-lang/crates.io-index" 1098 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1099 | 1100 | [[package]] 1101 | name = "winapi-util" 1102 | version = "0.1.5" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1105 | dependencies = [ 1106 | "winapi", 1107 | ] 1108 | 1109 | [[package]] 1110 | name = "winapi-x86_64-pc-windows-gnu" 1111 | version = "0.4.0" 1112 | source = "registry+https://github.com/rust-lang/crates.io-index" 1113 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1114 | 1115 | [[package]] 1116 | name = "zeroize" 1117 | version = "0.9.3" 1118 | source = "registry+https://github.com/rust-lang/crates.io-index" 1119 | checksum = "45af6a010d13e4cf5b54c94ba5a2b2eba5596b9e46bf5875612d332a1f2b3f86" 1120 | --------------------------------------------------------------------------------