Verified Commit aac00e88 authored by Katharina Fey's avatar Katharina Fey 🏴
Browse files

Getting the `encrypt` module to work initially

parent 834a3f0f
This diff is collapsed.
......@@ -5,7 +5,12 @@ authors = ["Katharina Fey <kookie@spacekookie.de>"]
edition = "2018"
[dependencies]
actix_web = "*"
actix-web = "*"
clap = "2.0"
serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.8"
miscreant = { version = "0.4", features = ["soft-aes"] }
blake2 = "0.7"
keybob = "0.3"
base64 = "*"
clap = "2.0"
......@@ -38,3 +38,11 @@ services.forge = {
There are many more options exposed via the `forge.nix` module so do check those out
if you want to have more fine-grained control over your server.
## Environment Variables
It's possible to configure `forge` via it's environment.
This might be useful when running `forgectl` via `execline` or in an automated setup.
- `FORGE_DATA` the data that should be encrypted
- `FORGE_SECRET` the token that is used as the encryption key base
use libforge::clap::{App, AppSettings, Arg, SubCommand};
mod secret;
mod setup;
use clap::{App, AppSettings, Arg, SubCommand};
use forge::{secret, setup};
fn main() {
let app = App::new("forge-cli")
......@@ -10,22 +8,29 @@ fn main() {
.setting(AppSettings::SubcommandRequired)
.setting(AppSettings::ColorAuto)
.subcommand(
SubCommand::with_name("secret")
SubCommand::with_name("encrypt")
.about("Create embeddable configuration secrets")
.version(secret::VERSION)
.arg(
Arg::with_name("TYPE")
.short("t")
.long("type")
Arg::with_name("SECRET")
.takes_value(true)
.required(true)
.help("The type of secret")
.possible_values(&["token", "key"]),
.short("s")
.long("secret")
.help("A secret that can remain secret"),
).arg(
Arg::with_name("SECRET")
Arg::with_name("DATA")
.takes_value(true)
.required(true)
.help("Some secret data"),
.help("The thing to encrypt and store"),
).arg(
Arg::with_name("CLI_FLAG")
.short("c")
.long("cli")
.help("Provide data via CLI options, instead of stdin or env"),
)
.arg(Arg::with_name("ENVIRONMENT")
.short("e")
.long("env")
.help("Provide all data via env variables instead of cli or STDIN")
),
).subcommand(
SubCommand::with_name("setup")
......@@ -34,10 +39,8 @@ fn main() {
);
match app.get_matches().subcommand() {
("setup", _) => setup::run(),
("secret", Some(m)) => {
secret::run(m.value_of("TYPE").unwrap(), m.value_of("SECRET").unwrap())
}
("setup", _) => unimplemented!(),
("encrypt", Some(m)) => secret::run(m),
_ => unreachable!(),
}
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
pub mod secret;
pub mod setup;
use blake2::digest::{Input, VariableOutput};
use blake2::Blake2s;
use keybob::{Key, KeyType};
use miscreant::siv::Aes256Siv;
use base64;
use serde::{Deserialize, Serialize};
use serde_yaml;
const BLAKE_16_LENGTH: usize = 16;
/// A blake16 hash, consisting of a byte array
pub type Hash = [u8; BLAKE_16_LENGTH];
pub struct PackedData {
pub nonce: Vec<u8>,
pub iv: Vec<u8>,
pub data: Vec<u8>,
}
/// Hash a value with blake2
pub fn blake2(data: &str, salt: &str) -> Hash {
let mut hasher = match Blake2s::new(BLAKE_16_LENGTH) {
Ok(res) => res,
Err(some) => panic!(some),
};
let to_hash = format!("{}{}", data, salt);
hasher.process(to_hash.as_bytes());
let mut buffer = [0u8; BLAKE_16_LENGTH];
match hasher.variable_result(&mut buffer) {
Ok(res) => res,
Err(e) => panic!(e),
};
return buffer;
}
pub fn verify_token_hash(rec: Vec<Hash>, given: Hash) -> Result<(), ()> {
// Iterator API is the hill I will die on
rec.iter()
.map(|rec| rec == &given)
.map(|b| if b { Ok(()) } else { Err(()) })
.fold(Err(()), |mut acc, x| {
if x.is_ok() {
acc = x
};
acc
})
}
/// Take a hashed secret and turn it into the ACTUAL encryption key
pub fn make_key(hashed_token: Hash, salt: &str) -> Key {
let hashed2 = blake2(
unsafe { ::std::str::from_utf8_unchecked(&hashed_token) },
salt,
);
Key::from_pw(
KeyType::Aes256,
unsafe { ::std::str::from_utf8_unchecked(&hashed2) },
salt,
)
}
/// Decrypt some packed secret
pub fn decrypt(key: &Key, data: PackedData) -> Vec<u8> {
let mut ctx = Aes256Siv::new(key.as_slice());
ctx.open(
vec![data.nonce.as_slice(), data.iv.as_slice()],
data.data.as_slice(),
).unwrap()
}
/// Encrypt some data into a packed secret
pub fn encrypt(key: &Key, data: Vec<u8>) -> PackedData {
let mut ctx = Aes256Siv::new(key.as_slice());
let iv = Key::new(KeyType::Aes256);
let nonce = Key::new(KeyType::Aes256);
let ciph = ctx.seal(vec![nonce.as_slice(), iv.as_slice()], data.as_slice());
PackedData {
iv: iv.as_slice().into(),
nonce: nonce.as_slice().into(),
data: ciph,
}
}
/// Encode a piece of arbitary data into a bse64 string
pub fn base64_encode(data: &Vec<u8>) -> String {
return base64::encode(data);
}
/// Decode a base64 string into arbitrary data
pub fn base64_decode(data: &String) -> Vec<u8> {
return base64::decode(data).unwrap();
}
/// Same as `PackedData` but all bas64 encoded because readability
#[derive(Serialize, Deserialize)]
struct YamlData {
iv: String,
nonce: String,
data: String,
}
impl From<PackedData> for YamlData {
fn from(pd: PackedData) -> Self {
Self {
iv: base64_encode(&pd.iv),
nonce: base64_encode(&pd.nonce),
data: base64_encode(&pd.data),
}
}
}
impl From<YamlData> for PackedData {
fn from(pd: YamlData) -> Self {
Self {
iv: base64_decode(&pd.iv),
nonce: base64_decode(&pd.nonce),
data: base64_decode(&pd.data),
}
}
}
pub fn to_yaml(pd: PackedData) -> String {
let yd: YamlData = pd.into();
serde_yaml::to_string(&yd).unwrap()
}
pub fn from_yaml(yaml: String) -> PackedData {
let yd: YamlData = serde_yaml::from_str(&yaml).unwrap();
yd.into()
}
//! The `secret` utility module which hashes tokens and encrypts user keys
use libforge;
use clap::ArgMatches;
use std::env;
use std::io::{stdin, stdout, Read, Write};
mod utils {
pub use crate::*;
}
struct Operands {
data: Vec<u8>,
secret: String,
}
/// The module version
pub const VERSION: &'static str = "0.1.0";
/// Run the `secret` module
pub fn run(tt: &str, secret: &str) {
pub fn run(args: &ArgMatches<'_>) {
let from_env = args.is_present("ENVIRONMENT");
let from_cli = args.is_present("CLI_FLAG");
// Either we read from STDIN or the user provided a DATA (or not)
let op: Operands = if from_cli {
let secret = args.value_of("SECRET").unwrap().into();
let data = args
.value_of("DATA")
.expect("Not reading from STDIN but not providing DATA")
.into();
Operands { data, secret }
} else if from_env {
Operands {
data: env::var("FORGE_DATA")
.expect("Env variable `FORGE_DATA` not found")
.as_bytes()
.into(),
secret: env::var("FORGE_SECRET").expect("Env variable `FORGE_SECRET` not found!"),
}
} else {
let secret = args.value_of("SECRET").unwrap().into();
let mut data = Vec::new();
stdin()
.read_to_end(&mut data)
.expect("Failed to read from STDIN");
Operands { data, secret }
};
/* hash secret, make key, then encrypt DATA */
let hashed = utils::blake2(&op.secret, "forgectl");
let key = utils::make_key(hashed, "forgectl");
let encrypted = utils::encrypt(&key, op.data);
let encoded = utils::to_yaml(encrypted);
stdout()
.write_all(encoded.as_bytes())
.expect("Failed to write to STDOUT");
}
use actix_web::{http, server, App, HttpMessage, HttpRequest, Json, Responder};
use libforge::clap::{App as ClapApp, Arg, ArgMatches};
use clap::{App as ClapApp, Arg, ArgMatches};
use std::convert::From;
use std::env;
use forge;
#[derive(Clone, Debug)]
struct Params {
......@@ -37,6 +38,8 @@ fn handle_token(req: &HttpRequest) -> impl Responder {
let event = h.get("x-gitlab-event").expect("Request didn't include `x-gitlab-event`");
let token = h.get("x-gitlab-token").expect("Request didn't include `x-gitlab-event`");
let token_hash = forge::blake2("<data>", "<salt>");
format!("")
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment