Browse Source

Initial commit

master
Graham Northup 2 years ago
commit
9ef9591df1
Signed by: grissess GPG Key ID: 5D000E6F539376FB
  1. 1
      .gitignore
  2. 15
      Cargo.toml
  3. 91
      src/args.yml
  4. 68
      src/coding.rs
  5. 6
      src/csrng.rs
  6. 28
      src/error.rs
  7. 19
      src/kdf.rs
  8. 135
      src/key.rs
  9. 144
      src/main.rs
  10. 114
      src/sym.rs

1
.gitignore

@ -0,0 +1 @@
/target

15
Cargo.toml

@ -0,0 +1,15 @@
[package]
name = "ski"
version = "0.1.0"
authors = ["Graham Northup <grissess@nexusg.org>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
base64 = "0.12.3"
rust-crypto = "0.2.36"
clap = {version = "2.27.0", features = ["yaml"]}
rand = "0.5.0"
rpassword = "4.0.5"
argon2rs = "0.2.5"

91
src/args.yml

@ -0,0 +1,91 @@
name: ski
about: Ski Rust Reference Client
author: Graham Northup <grissess@nexusg.org>
version: "0.1-20200629"
before_help: This software HAS NOT BEEN VERIFIED by any third party. Use at your own risk.
subcommands:
- encode:
about: Encode a file to standard out
args:
- FILE:
help: File to encode (default standard in)
index: 1
- decode:
about: Decode a file to standard out
args:
- FILE:
help: File to encode (default standard in)
index: 1
- key:
about: Manipulate keys and en/decipher data
subcommands:
- gen:
about: Generate a new private key and write it to stdout
- pub:
about: Convert a private key to a public key
args:
- KEY:
help: SKI private key URI
required: true
index: 1
- shared:
about: Create a shared key from a private and public key
args:
- PRIVKEY:
help: SKI private key URI
required: true
index: 1
- PUBKEY:
help: SKI public key URI
required: true
index: 2
- derive:
about: Create a new random symmetric key and encrypt it with a shared key
args:
- PRIVKEY:
help: SKI private key URI
required: true
index: 1
- PUBKEY:
help: SKI publik key URI
required: true
index: 2
- encrypt:
about: Encrypt data with a key, writing the ciphertext to stdout
args:
- FILE:
help: Plaintext file to read (default standard in)
index: 1
- decrypt:
about: Decrypt data with a key, writing the plaintext to stdout
args:
- FILE:
help: Ciphertext file to read (default standard in)
index: 1
- sym:
about: Perform symmetric encryption
subcommands:
- gen:
about: Make a new random key and write it to stdout
- derive:
about: Create a symmetric key from repeatably some input (e.g., a password)
- encrypt:
about: Symmetrically encrypt a file to stdout
args:
- KEY:
help: SKI symmetric key URI
required: true
index: 1
- FILE:
help: File to read (default stdin)
index: 2
- decrypt:
about: Symmetrically decrypt a file to stdout
args:
- KEY:
help: SKI symmetric key URI
required: true
index: 1
- FILE:
help: File to read (default stdin)
index: 2

68
src/coding.rs

@ -0,0 +1,68 @@
pub const ENCODING: base64::Config = base64::URL_SAFE;
use crate::error::{self, Error};
use std::convert::TryFrom;
pub fn encode<T: AsRef<[u8]>>(bytes: T) -> String {
base64::encode_config(bytes, ENCODING)
}
pub fn decode<T: AsRef<[u8]>>(bytes: T) -> error::Result<Vec<u8>> {
base64::decode_config(bytes, ENCODING).map_err(Into::into)
}
pub struct CodedObject {
pub bytes: Vec<u8>,
pub scheme: String,
}
impl CodedObject {
pub fn as_uri(&self) -> String {
format!("{}:{}", self.scheme, encode(&self.bytes))
}
pub fn as_binary(&self) -> error::Result<Vec<u8>> {
let mut output = Vec::new();
output.push(u8::try_from(self.scheme.as_bytes().len())?);
output.extend(self.scheme.as_bytes());
output.extend(&self.bytes);
Ok(output)
}
pub fn from_uri(uri: &str) -> error::Result<CodedObject> {
let colon = uri.find(':');
match colon {
None => Err(Error::BadScheme("".into())),
Some(idx) => Ok(CodedObject {
scheme: uri[..idx].into(),
bytes: decode(uri[idx+1..].as_bytes())?,
}),
}
}
pub fn from_binary(bytes: &[u8]) -> error::Result<CodedObject> {
let scheme_len = bytes[0];
let scheme = std::str::from_utf8(&bytes[1 .. scheme_len as usize + 1])?;
Ok(CodedObject {
scheme: scheme.into(),
bytes: bytes[scheme_len as usize + 1 ..].into(),
})
}
pub fn expect_scheme(&self, scheme: &str) -> error::Result<()> {
if scheme == self.scheme {
Ok(())
} else {
Err(Error::BadScheme(self.scheme.clone()))
}
}
}
pub trait Encodable {
fn encode(&self) -> CodedObject;
}
pub trait Decodable: Sized {
fn decode(input: &CodedObject) -> error::Result<Self>;
}

6
src/csrng.rs

@ -0,0 +1,6 @@
use rand::RngCore;
pub fn fill_random(bytes: &mut [u8]) {
let mut rng = rand::rngs::EntropyRng::new();
rng.fill_bytes(bytes);
}

28
src/error.rs

@ -0,0 +1,28 @@
#[derive(Debug)]
pub enum Error {
CodingError(base64::DecodeError),
InvalidLength(usize),
BadScheme(String),
BadEncoding(std::str::Utf8Error),
SchemeTooLong(std::num::TryFromIntError),
}
impl From<base64::DecodeError> for Error {
fn from(err: base64::DecodeError) -> Error {
Error::CodingError(err)
}
}
impl From<std::str::Utf8Error> for Error {
fn from(err: std::str::Utf8Error) -> Error {
Error::BadEncoding(err)
}
}
impl From<std::num::TryFromIntError> for Error {
fn from(err: std::num::TryFromIntError) -> Error {
Error::SchemeTooLong(err)
}
}
pub type Result<T> = std::result::Result<T, Error>;

19
src/kdf.rs

@ -0,0 +1,19 @@
use argon2rs::{Argon2, Variant};
pub struct KeyDerivation {
hasher: Argon2,
}
pub const WELL_KNOWN_SALT: [u8; 8] = [0; 8];
impl KeyDerivation {
pub fn new() -> Self {
KeyDerivation {
hasher: Argon2::new(1, 1, 8, Variant::Argon2i).unwrap(),
}
}
pub fn hash(&self, input: &[u8], output: &mut [u8]) {
self.hasher.hash(output, input, &WELL_KNOWN_SALT, &[], &[]);
}
}

135
src/key.rs

@ -0,0 +1,135 @@
use crate::error::{self, Error};
use crate::coding::{Encodable, Decodable, CodedObject};
use crate::csrng::fill_random;
use crypto::curve25519::{ge_scalarmult_base, curve25519, Fe};
#[derive(Debug, Clone)]
pub struct PubKey {
pub bytes: [u8; 32],
}
#[derive(Debug, Clone)]
pub struct Key {
pub bytes: [u8; 32],
}
#[derive(Debug, Clone)]
pub struct SharedKey {
pub bytes: [u8; 32],
}
impl From<&Key> for PubKey {
fn from(key: &Key) -> PubKey {
PubKey {
bytes: ge_scalarmult_base(&key.bytes).to_bytes(),
}
}
}
impl PubKey {
pub fn montgomery_field(&self) -> Fe {
let this = Fe::from_bytes(&self.bytes);
let z = Fe([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
let x = z + this;
let z = (z - this).invert();
x * z
}
}
impl Key {
pub fn from_bytes(bytes: &[u8]) -> error::Result<Key> {
let blen = bytes.len();
if blen != 32 {
return Err(Error::InvalidLength(blen));
}
let mut key: [u8; 32] = [0; 32]; // FIXME: Can be uninitialized
key.copy_from_slice(bytes);
key[0] &= 248;
key[31] &= 63;
key[31] |= 64;
Ok(Key {
bytes: key,
})
}
pub fn new() -> Key {
let mut key = [0u8; 32];
fill_random(&mut key);
Key::from_bytes(&key).unwrap()
}
pub fn public(&self) -> PubKey { self.into() }
pub fn shared_key(&self, other: &PubKey) -> SharedKey {
SharedKey {
bytes: curve25519(&self.bytes, &other.montgomery_field().to_bytes())
}
}
}
pub const PRIVATE_KEY_SCHEME: &'static str = "ski-prvk";
pub const PUBLIC_KEY_SCHEME: &'static str = "ski-pubk";
pub const SHARED_KEY_SCHEME: &'static str = "ski-shak";
impl Encodable for Key {
fn encode(&self) -> CodedObject {
CodedObject {
scheme: PRIVATE_KEY_SCHEME.into(),
bytes: self.bytes.into(),
}
}
}
impl Decodable for Key {
fn decode(input: &CodedObject) -> error::Result<Self> {
input.expect_scheme(PRIVATE_KEY_SCHEME)?;
let mut key = [0u8; 32];
key.copy_from_slice(&input.bytes);
Ok(Key {
bytes: key,
})
}
}
impl Encodable for PubKey {
fn encode(&self) -> CodedObject {
CodedObject {
scheme: PUBLIC_KEY_SCHEME.into(),
bytes: self.bytes.into(),
}
}
}
impl Decodable for PubKey {
fn decode(input: &CodedObject) -> error::Result<Self> {
input.expect_scheme(PUBLIC_KEY_SCHEME)?;
let mut key = [0u8; 32];
key.copy_from_slice(&input.bytes);
Ok(PubKey {
bytes: key,
})
}
}
impl Encodable for SharedKey {
fn encode(&self) -> CodedObject {
CodedObject {
scheme: SHARED_KEY_SCHEME.into(),
bytes: self.bytes.into(),
}
}
}
impl Decodable for SharedKey {
fn decode(input: &CodedObject) -> error::Result<Self> {
input.expect_scheme(SHARED_KEY_SCHEME)?;
let mut key = [0u8; 32];
key.copy_from_slice(&input.bytes);
Ok(SharedKey {
bytes: key,
})
}
}

144
src/main.rs

@ -0,0 +1,144 @@
extern crate base64;
extern crate crypto;
#[macro_use]
extern crate clap;
extern crate rand;
extern crate rpassword;
extern crate argon2rs;
use std::io::{self, Write};
use std::fs;
use std::ffi::OsStr;
pub mod error;
pub mod coding;
pub mod key;
pub mod sym;
pub mod csrng;
pub mod kdf;
use coding::{Encodable, Decodable, CodedObject};
fn get_input(filename: Option<&OsStr>) -> io::Result<Box<dyn io::Read>> {
match filename {
None => Ok(Box::new(io::stdin())),
Some(name) => fs::File::open(name).map(|file| Box::new(file) as Box<dyn io::Read>),
}
}
fn main() {
let arg_config = load_yaml!("args.yml");
let matches = clap::App::from_yaml(arg_config).get_matches();
std::process::exit(match matches.subcommand() {
("encode", Some(args)) => {
let mut input = get_input(args.value_of_os("FILE")).unwrap();
let mut buffer: Vec<u8> = Vec::new();
input.read_to_end(&mut buffer).unwrap();
io::stdout().write_all(coding::encode(buffer).as_bytes()).unwrap();
0
},
("decode", Some(args)) => {
let mut input = get_input(args.value_of_os("FILE")).unwrap();
let mut buffer: Vec<u8> = Vec::new();
input.read_to_end(&mut buffer).unwrap();
io::stdout().write_all(&coding::decode(buffer).unwrap()).unwrap();
0
},
("key", Some(args)) => {
match args.subcommand() {
("gen", Some(_args)) => {
println!("{}", key::Key::new().encode().as_uri());
0
},
("pub", Some(args)) => {
let key = key::Key::decode(
&CodedObject::from_uri(args.value_of("KEY").unwrap()).unwrap()
).unwrap();
println!("{}", key.public().encode().as_uri());
0
},
("shared", Some(args)) => {
let privkey = key::Key::decode(
&CodedObject::from_uri(args.value_of("PRIVKEY").unwrap()).unwrap()
).unwrap();
let pubkey = key::PubKey::decode(
&CodedObject::from_uri(args.value_of("PUBKEY").unwrap()).unwrap()
).unwrap();
let shkey = privkey.shared_key(&pubkey);
println!("{}", shkey.encode().as_uri());
0
},
_ => {
eprintln!("{}", args.usage());
1
},
}
},
("sym", Some(args)) => {
match args.subcommand() {
("gen", Some(_args)) => {
println!("{}", sym::Key::new().encode().as_uri());
0
},
("derive", Some(_args)) => {
let pass = rpassword::prompt_password_stderr("Password: ").unwrap();
let hasher = kdf::KeyDerivation::new();
let mut output = [0u8; sym::Key::SIZE];
hasher.hash(pass.as_bytes(), &mut output);
println!("{}", (sym::Key { bytes: output }).encode().as_uri());
0
},
("encrypt", Some(args)) => {
let key = sym::Key::decode(
&CodedObject::from_uri(args.value_of("KEY").unwrap()).unwrap()
).unwrap();
let mut input = get_input(args.value_of_os("FILE")).unwrap();
let mut buffer: Vec<u8> = Vec::new();
input.read_to_end(&mut buffer).unwrap();
let cipher = key.cipher();
let data = cipher.encipher(&buffer);
io::stdout().write_all(&data.encode().as_binary().unwrap()).unwrap();
0
},
("decrypt", Some(args)) => {
let key = sym::Key::decode(
&CodedObject::from_uri(args.value_of("KEY").unwrap()).unwrap()
).unwrap();
let mut input = get_input(args.value_of_os("FILE")).unwrap();
let mut buffer: Vec<u8> = Vec::new();
input.read_to_end(&mut buffer).unwrap();
let data = sym::EncipheredData::decode(
&CodedObject::from_binary(&buffer).unwrap()
).unwrap();
let cipher = key.cipher_for_data(&data);
let plain = cipher.decipher(&data);
io::stdout().write_all(&plain).unwrap();
0
},
_ => {
eprintln!("{}", args.usage());
1
},
}
},
_ => {
eprintln!("{}", matches.usage());
1
},
})
}

114
src/sym.rs

@ -0,0 +1,114 @@
use crate::error;
use crate::coding::{Encodable, Decodable, CodedObject};
use crate::csrng::fill_random;
use crypto::chacha20::ChaCha20;
use crypto::symmetriccipher::SynchronousStreamCipher;
#[derive(Debug, Clone)]
pub struct Key {
pub bytes: [u8; 32],
}
pub struct Cipher {
nonce: [u8; 24],
state: ChaCha20,
}
#[derive(Debug, Clone)]
pub struct EncipheredData {
pub nonce: [u8; 24],
pub data: Vec<u8>,
}
impl Key {
pub const SIZE: usize = 32;
pub fn new() -> Key {
let mut bytes = [0u8; 32];
fill_random(&mut bytes);
Key { bytes }
}
pub fn cipher_with_nonce(&self, nonce: [u8; 24]) -> Cipher {
Cipher {
nonce,
state: ChaCha20::new_xchacha20(&self.bytes, &nonce),
}
}
pub fn cipher(&self) -> Cipher {
let mut nonce = [0u8; 24];
fill_random(&mut nonce);
self.cipher_with_nonce(nonce)
}
pub fn cipher_for_data(&self, enciphered_data: &EncipheredData) -> Cipher {
self.cipher_with_nonce(enciphered_data.nonce.clone())
}
}
impl Cipher {
pub fn encipher(mut self, input: &[u8]) -> EncipheredData {
let mut output: Vec<u8> = Vec::with_capacity(input.len());
output.extend(std::iter::repeat(0).take(input.len()));
self.state.process(input, &mut output);
EncipheredData {
nonce: self.nonce,
data: output,
}
}
pub fn decipher(mut self, input: &EncipheredData) -> Vec<u8> {
let mut output: Vec<u8> = Vec::with_capacity(input.data.len());
output.extend(std::iter::repeat(0).take(input.data.len()));
self.state.process(&input.data, &mut output);
output
}
}
pub const SYMMETRIC_KEY_SCHEME: &'static str = "ski-symk";
pub const SYMMETRICALLY_ENCRYPTED_DATA_SCHEME: &'static str = "ski-syed";
impl Encodable for Key {
fn encode(&self) -> CodedObject {
CodedObject {
scheme: SYMMETRIC_KEY_SCHEME.into(),
bytes: self.bytes.into(),
}
}
}
impl Decodable for Key {
fn decode(input: &CodedObject) -> error::Result<Self> {
input.expect_scheme(SYMMETRIC_KEY_SCHEME)?;
let mut bytes = [0u8; 32];
bytes.copy_from_slice(&input.bytes);
Ok(Key { bytes })
}
}
impl Encodable for EncipheredData {
fn encode(&self) -> CodedObject {
let mut bytes = Vec::with_capacity(self.data.len() + self.nonce.len());
bytes.extend(&self.nonce);
bytes.extend(&self.data);
CodedObject {
scheme: SYMMETRICALLY_ENCRYPTED_DATA_SCHEME.into(),
bytes,
}
}
}
impl Decodable for EncipheredData {
fn decode(input: &CodedObject) -> error::Result<Self> {
input.expect_scheme(SYMMETRICALLY_ENCRYPTED_DATA_SCHEME)?;
let mut nonce = [0u8; 24];
nonce.copy_from_slice(&input.bytes[..24]);
Ok(EncipheredData {
nonce,
data: (&input.bytes[24..]).into(),
})
}
}
Loading…
Cancel
Save