commit 510a0b13b99f85379fe10dabadce408aa95a795d Author: southerntofu Date: Sat Feb 19 00:04:09 2022 +0100 Initial version of whck diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..31063c3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +.*.sw* diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..d3cda9d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "spec"] + path = spec + url = https://tildegit.org/forge/endpoints diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..b5a44ee --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,117 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cpufeatures" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "libc" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e509672465a0504304aa87f9f176f2b2b716ed8fb105ebe5c02dc6dce96a94" + +[[package]] +name = "sha2" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "whck" +version = "0.1.0" +dependencies = [ + "hex", + "hmac", + "sha2", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..31d3ee9 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "whck" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +hmac = "0.12" +sha2 = "0.10" +hex = "0.4" diff --git a/spec b/spec new file mode 160000 index 0000000..7eb833a --- /dev/null +++ b/spec @@ -0,0 +1 @@ +Subproject commit 7eb833a59e7987bc73d303a1a261a0a664c791cc diff --git a/src/helpers.rs b/src/helpers.rs new file mode 100644 index 0000000..f9649ed --- /dev/null +++ b/src/helpers.rs @@ -0,0 +1,51 @@ +use std::env::var as env_var; +use std::path::PathBuf; + +pub fn whck_dir() -> PathBuf { + whck_dir_from_env() + .unwrap_or( + whck_dir_from_xdg_config() + .unwrap_or( + whck_dir_from_home() + .unwrap_or( + whck_dir_from_exe() + .expect("Exhausted all methods to find ~/.config/whck/") + ) + ) + ) +} + +pub fn whck_dir_from_env() -> Option { + env_var("WHCK_DIR").ok().map(|x| PathBuf::from(x)) +} +pub fn whck_dir_from_xdg_config() -> Option { + env_var("XDG_CONFIG_HOME").ok().map(|x| PathBuf::from(x).join("whck")) +} +pub fn whck_dir_from_home() -> Option { + env_var("HOME").ok().map(|x| PathBuf::from(x).join(".config/whck")) +} +pub fn whck_dir_from_exe() -> Option { + //std::env::current_exe().ok().and_then(|x| { + println!("{:?}", std::env::current_exe()); + std::env::current_exe().ok().and_then(|x| { + // Canonicalize (resolve symlinks) in case we have a symlink from webdir + let basepath = x.canonicalize().unwrap(); + let mut components = basepath.components(); + println!("{:?}", components); + // We have canonicalized so component 0 is "RootDir" + if let Some(dir) = components.nth(1) { + if dir.as_os_str().to_str().unwrap() == "home" { + if let Some(user) = components.next() { + return Some(PathBuf::from("/home") + .join(user) + .join(".config/whck")); + } else { + println!("no user"); + } + } else { + println!("Not home"); + } + } + return None; + }) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..2c5c010 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,122 @@ +use std::convert::From; +use std::env::args; +use std::fs::read_to_string; +use std::io::{Read, stdin}; +use std::str::FromStr; +use std::process::exit; +use sha2::Sha256; +use hmac::{Hmac, Mac}; +type HmacSha256 = Hmac; + +mod helpers; + +#[derive(Debug)] +pub enum Error { + WrongKind(String), + WrongClaim(String), + IOError(std::io::Error) +} + +impl From for Error { + fn from(error: std::io::Error) -> Error { + Error::IOError(error) + } +} + +pub enum SigType { + SHA256HMAC, +} + +pub enum Kind { + Sig(String, SigType), + Token, +} + +impl FromStr for Kind { + type Err = Error; + fn from_str(s: &str) -> Result { + match s { + "hmac-sha256" => { + let mut body = String::new(); + let mut input = stdin(); + input.read_to_string(&mut body)?; + println!("Body size: {}", body.len()); + Ok(Kind::Sig(body, SigType::SHA256HMAC)) + }, + "token" => { + Ok(Kind::Token) + }, + "help" | "-h" | "--help" => { + help(); + exit(0); + }, + _ => { + Err(Error::WrongKind(s.to_string())) + } + } + } +} + +impl Kind { + // Returns true if the check succeeded, false otherwise + pub fn check(&self, secret: &str, claim: &str) -> bool { + match self { + Kind::Sig(body, sigtype) => { + println!("|{}|", &body); + match sigtype { + SigType::SHA256HMAC => { + let mut mac = HmacSha256::new_from_slice(secret.as_bytes()).unwrap(); + mac.update(body.as_bytes()); + let computed_sig = hex::encode(mac.finalize().into_bytes()); + println!("Computed: {}", &computed_sig); + return computed_sig == claim; + }, + } + }, + Kind::Token => { + return secret == claim; + }, + } + } +} + +fn help() { + println!("whck KIND IDENTIFIER CLAIM BODY"); + println!(" KIND: sig/token"); + println!(" IDENTIFIER: the unique identifier for the stored secret in $XDG_CONFIG_DIR/whck/ folder"); + println!(" CLAIM: the claimed sig/token to verify"); + println!(""); + println!("If you're checking a signature, don't forget to pass the body as STDIN"); + println!("NOTE: If you're protecting secrets from other users (chmod -R 700 $XDG_CONFIG_HOME/whck),"); + println!("don't forget to set the suid bit on the program (chmod u+s $(which whck)).") +} + +// Panic on malformed $XDG_CONFIG_DIR +fn read_secret(id: &str) -> String { + if id.starts_with("/") || id.contains("/../") { + println!("Malformed identifier"); + exit(1); + } + // $WHCK_DIR is used for tests + // Otherwise, try $XDG_CONFIG_HOME/whck or $HOME/.config/whck + // When home isn't defined try to check if $0 starts with /home and if so assume $HOME is there + let mut secret_path = helpers::whck_dir(); + secret_path.push(id); + let secret = read_to_string(&secret_path).expect(&format!("Failed to read secret from: {}", &secret_path.to_str().unwrap())); + return secret; +} + +fn main() -> Result<(), Error> { + let mut cli_args = args(); + let arg_kind = cli_args.nth(1).expect("Missing argument: KIND"); + let kind = Kind::from_str(&arg_kind)?; + let id = cli_args.nth(0).expect("Missing argument: IDENTIFIER"); + let claim = cli_args.nth(0).expect("Missing argument: CLAIM"); + + let secret = read_secret(&id); + if ! kind.check(&secret, &claim) { + return Err(Error::WrongClaim(claim.clone())) + } + + Ok(()) +} diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..e7edb4f --- /dev/null +++ b/test.sh @@ -0,0 +1,24 @@ +#! /usr/bin/env bash + +SCRIPTDIR="$(dirname "$0")" +ORIGDIR="$(pwd)" + +if [ ! -f "$SCRIPTDIR"/spec/test_cli.sh ]; then + echo "Submodule not cloned yet. Doing it now" + cd $SCRIPTDIR + git submodule init + if ! git submodule update; then + echo "Failed to download submodules. Are you connected to the internet?" + exit 1 + fi +fi + +if [ -f "$SCRIPTDIR"/target/release/whck ]; then + cargo build --release + "$SCRIPTDIR"/spec/test_cli.sh "$SCRIPTDIR"/target/release/whck +else + cargo build + "$SCRIPTDIR"/spec/test_cli.sh "$SCRIPTDIR"/target/debug/whck +fi + +cd "$ORIGDIR"