dtool 0.17.0

A command-line tool collection to assist development
use crate::modules::base::Hex;
use crate::modules::{base, Command, Module};
use clap::{Arg, ArgMatches, SubCommand};
use ring::pbkdf2::{
	derive, PBKDF2_HMAC_SHA1, PBKDF2_HMAC_SHA256, PBKDF2_HMAC_SHA384, PBKDF2_HMAC_SHA512,
};
use std::num::NonZeroU32;

pub fn module<'a, 'b>() -> Module<'a, 'b> {
	Module {
		desc: "Pbkdf2".to_string(),
		commands: commands(),
		get_cases: cases::cases,
	}
}

pub fn commands<'a, 'b>() -> Vec<Command<'a, 'b>> {
	vec![
		Command {
			app: SubCommand::with_name("pbkdf2").about("Pbkdf2")
				.arg(
					Arg::with_name("ITERATIONS")
						.long("iterations")
						.short("i").help("Iterations")
						.takes_value(true)
						.default_value("1")
						.required(false))
				.arg(
					Arg::with_name("ALGORITHM")
						.long("algorithm")
						.short("a").help("Algorithm\nsha1: SHA-1\nsha2_256: SHA-2 256\nsha2_384: SHA-2 384\nsha2_512: SHA-2 512")
						.takes_value(true)
						.default_value("sha1")
						.required(false))
				.arg(
					Arg::with_name("SALT")
						.long("salt")
						.short("s").help("Salt (Hex)")
						.takes_value(true)
						.default_value("0x")
						.required(false))
				.arg(
					Arg::with_name("KEY_LENGTH")
						.long("key-length")
						.short("l").help("Key length")
						.help("Key length: must be a multiple of 8")
						.takes_value(true)
						.default_value("128")
						.required(false))
				.arg(
					Arg::with_name("INPUT")
						.help("Secret (Hex)")
						.required(false)
						.index(1)),

			f: pbkdf2,
		}
	]
}

fn pbkdf2(matches: &ArgMatches) -> Result<Vec<String>, String> {
	let algo = match matches.value_of("ALGORITHM") {
		Some("sha1") => PBKDF2_HMAC_SHA1,
		Some("sha2_256") => PBKDF2_HMAC_SHA256,
		Some("sha2_384") => PBKDF2_HMAC_SHA384,
		Some("sha2_512") => PBKDF2_HMAC_SHA512,
		_ => return Err("Invalid algorithm".to_string()),
	};

	let iterations = match matches.value_of("ITERATIONS") {
		Some(iterations) => iterations
			.parse::<u32>()
			.map_err(|_| "Invalid Iterations".to_string()),
		_ => Err("Invalid Iterations".to_string()),
	}
	.and_then(|x| {
		if x > 0 {
			NonZeroU32::new(x).ok_or_else(|| "Invalid Iterations".to_string())
		} else {
			Err("Invalid Iterations".to_string())
		}
	})?;

	let salt: Vec<u8> = match matches.value_of("SALT") {
		Some(salt) => salt.parse::<Hex>().map_err(|_| "Invalid salt".to_string()),
		_ => Err("Invalid salt".to_string()),
	}?
	.into();

	let key_length = match matches.value_of("KEY_LENGTH") {
		Some(key_length) => key_length
			.parse::<u32>()
			.map_err(|_| "Invalid key length".to_string()),
		_ => Err("Invalid key length".to_string()),
	}
	.and_then(|x| {
		if x > 0 {
			Ok(x)
		} else {
			Err("Invalid key length".to_string())
		}
	})?;

	let key_byte_length = if key_length % 8 == 0 {
		Ok(key_length / 8)
	} else {
		Err("Invalid key length (must be a multiple of 8)".to_string())
	}?;

	let secret = base::input_string(matches)?;
	let secret: Vec<u8> = secret.parse::<Hex>().map_err(|_| "Invalid secret")?.into();

	let mut result = vec![0u8; key_byte_length as usize];

	derive(algo, iterations, &salt, &secret, &mut result);

	let result = Hex::from(result).into();

	Ok(vec![result])
}

mod cases {
	use crate::modules::Case;
	use linked_hash_map::LinkedHashMap;

	pub fn cases() -> LinkedHashMap<&'static str, Vec<Case>> {
		vec![(
			"pbkdf2",
			vec![Case {
				desc: "".to_string(),
				input: vec![
					"-a", "sha2_256", "-s", "0x646566", "-i", "2", "-l", "256", "0x616263",
				]
				.into_iter()
				.map(Into::into)
				.collect(),
				output: vec!["0x51a30556d0d133d859d3f3da86f861b7b12546c4f9a193ebb374397467872514"]
					.into_iter()
					.map(Into::into)
					.collect(),
				is_example: true,
				is_test: true,
				since: "0.5.0".to_string(),
			}],
		)]
		.into_iter()
		.collect()
	}
}

#[cfg(test)]
mod tests {
	use super::*;
	use crate::modules::base::test::test_module;

	#[test]
	fn test_cases() {
		test_module(module());
	}
}