2017-03-10 11:43:32 +00:00
|
|
|
use std::collections::HashMap;
|
2017-03-18 16:22:11 +00:00
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use std::io;
|
|
|
|
use std::fs::{self, File};
|
2017-04-12 06:30:42 +00:00
|
|
|
use std::sync::{Once, ONCE_INIT};
|
2017-03-18 16:22:11 +00:00
|
|
|
|
|
|
|
use serde_yaml;
|
2017-04-27 11:35:48 +00:00
|
|
|
use serde_bytes::ByteBuf;
|
2017-03-18 16:22:11 +00:00
|
|
|
|
2017-05-08 13:11:07 +00:00
|
|
|
use libsodium_sys;
|
2017-04-12 06:30:42 +00:00
|
|
|
use sodiumoxide;
|
2017-03-18 16:22:11 +00:00
|
|
|
use sodiumoxide::crypto::sealedbox;
|
2017-04-12 06:30:42 +00:00
|
|
|
use sodiumoxide::crypto::box_;
|
2017-05-08 13:11:07 +00:00
|
|
|
use sodiumoxide::crypto::pwhash;
|
2017-04-12 06:30:42 +00:00
|
|
|
pub use sodiumoxide::crypto::box_::{SecretKey, PublicKey};
|
2017-03-18 16:22:11 +00:00
|
|
|
|
|
|
|
use ::util::*;
|
|
|
|
|
2017-03-10 11:43:32 +00:00
|
|
|
|
2017-04-12 06:30:42 +00:00
|
|
|
static INIT: Once = ONCE_INIT;
|
|
|
|
|
|
|
|
fn sodium_init() {
|
|
|
|
INIT.call_once(|| {
|
|
|
|
if !sodiumoxide::init() {
|
|
|
|
panic!("Failed to initialize sodiumoxide");
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-03-16 08:42:30 +00:00
|
|
|
quick_error!{
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum EncryptionError {
|
2017-03-18 16:22:11 +00:00
|
|
|
InvalidKey {
|
|
|
|
description("Invalid key")
|
|
|
|
}
|
|
|
|
MissingKey(key: PublicKey) {
|
|
|
|
description("Missing key")
|
|
|
|
display("Missing key: {}", to_hex(&key[..]))
|
|
|
|
}
|
2017-03-16 08:42:30 +00:00
|
|
|
Operation(reason: &'static str) {
|
|
|
|
description("Operation failed")
|
|
|
|
display("Operation failed: {}", reason)
|
|
|
|
}
|
2017-03-18 16:22:11 +00:00
|
|
|
Io(err: io::Error) {
|
|
|
|
from()
|
|
|
|
cause(err)
|
|
|
|
description("IO error")
|
|
|
|
display("IO error: {}", err)
|
|
|
|
}
|
|
|
|
Yaml(err: serde_yaml::Error) {
|
|
|
|
from()
|
|
|
|
cause(err)
|
|
|
|
description("Yaml format error")
|
|
|
|
display("Yaml format error: {}", err)
|
|
|
|
}
|
2017-03-16 08:42:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-03-18 16:22:11 +00:00
|
|
|
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
|
|
|
#[allow(unknown_lints,non_camel_case_types)]
|
2017-03-10 11:43:32 +00:00
|
|
|
pub enum EncryptionMethod {
|
2017-03-18 16:22:11 +00:00
|
|
|
Sodium,
|
2017-03-10 11:43:32 +00:00
|
|
|
}
|
|
|
|
serde_impl!(EncryptionMethod(u64) {
|
2017-03-18 16:22:11 +00:00
|
|
|
Sodium => 0
|
|
|
|
});
|
|
|
|
|
|
|
|
impl EncryptionMethod {
|
|
|
|
pub fn from_string(val: &str) -> Result<Self, &'static str> {
|
|
|
|
match val {
|
|
|
|
"sodium" => Ok(EncryptionMethod::Sodium),
|
|
|
|
_ => Err("Unsupported encryption method")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn to_string(&self) -> String {
|
|
|
|
match *self {
|
|
|
|
EncryptionMethod::Sodium => "sodium".to_string()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub type Encryption = (EncryptionMethod, ByteBuf);
|
|
|
|
|
|
|
|
|
2017-07-03 09:48:25 +00:00
|
|
|
pub struct KeyfileYaml {
|
2017-03-18 16:22:11 +00:00
|
|
|
public: String,
|
|
|
|
secret: String
|
|
|
|
}
|
|
|
|
impl Default for KeyfileYaml {
|
|
|
|
fn default() -> Self {
|
|
|
|
KeyfileYaml {
|
|
|
|
public: "".to_string(),
|
|
|
|
secret: "".to_string()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
serde_impl!(KeyfileYaml(String) {
|
|
|
|
public: String => "public",
|
|
|
|
secret: String => "secret"
|
2017-03-10 11:43:32 +00:00
|
|
|
});
|
|
|
|
|
2017-03-18 16:22:11 +00:00
|
|
|
impl KeyfileYaml {
|
|
|
|
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, EncryptionError> {
|
|
|
|
let f = try!(File::open(path));
|
|
|
|
Ok(try!(serde_yaml::from_reader(f)))
|
|
|
|
}
|
2017-03-10 11:43:32 +00:00
|
|
|
|
2017-03-18 16:22:11 +00:00
|
|
|
pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<(), EncryptionError> {
|
|
|
|
let mut f = try!(File::create(path));
|
|
|
|
Ok(try!(serde_yaml::to_writer(&mut f, &self)))
|
|
|
|
}
|
|
|
|
}
|
2017-03-10 11:43:32 +00:00
|
|
|
|
|
|
|
|
|
|
|
pub struct Crypto {
|
2017-07-03 09:48:25 +00:00
|
|
|
path: Option<PathBuf>,
|
2017-03-18 16:22:11 +00:00
|
|
|
keys: HashMap<PublicKey, SecretKey>
|
2017-03-10 11:43:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Crypto {
|
|
|
|
#[inline]
|
2017-03-20 17:11:03 +00:00
|
|
|
pub fn dummy() -> Self {
|
2017-04-12 06:30:42 +00:00
|
|
|
sodium_init();
|
2017-07-03 09:48:25 +00:00
|
|
|
Crypto { path: None, keys: HashMap::new() }
|
2017-03-10 11:43:32 +00:00
|
|
|
}
|
|
|
|
|
2017-03-18 16:22:11 +00:00
|
|
|
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, EncryptionError> {
|
2017-04-12 06:30:42 +00:00
|
|
|
sodium_init();
|
2017-03-18 16:22:11 +00:00
|
|
|
let path = path.as_ref().to_owned();
|
|
|
|
let mut keys: HashMap<PublicKey, SecretKey> = HashMap::default();
|
|
|
|
for entry in try!(fs::read_dir(&path)) {
|
|
|
|
let entry = try!(entry);
|
|
|
|
let keyfile = try!(KeyfileYaml::load(entry.path()));
|
|
|
|
let public = try!(parse_hex(&keyfile.public).map_err(|_| EncryptionError::InvalidKey));
|
|
|
|
let public = try!(PublicKey::from_slice(&public).ok_or(EncryptionError::InvalidKey));
|
|
|
|
let secret = try!(parse_hex(&keyfile.secret).map_err(|_| EncryptionError::InvalidKey));
|
|
|
|
let secret = try!(SecretKey::from_slice(&secret).ok_or(EncryptionError::InvalidKey));
|
|
|
|
keys.insert(public, secret);
|
|
|
|
}
|
2017-07-03 09:48:25 +00:00
|
|
|
Ok(Crypto { path: Some(path), keys: keys })
|
2017-03-10 11:43:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2017-03-18 16:22:11 +00:00
|
|
|
pub fn add_secret_key(&mut self, public: PublicKey, secret: SecretKey) {
|
|
|
|
self.keys.insert(public, secret);
|
2017-03-10 11:43:32 +00:00
|
|
|
}
|
|
|
|
|
2017-03-22 16:28:45 +00:00
|
|
|
#[inline]
|
|
|
|
pub fn register_keyfile<P: AsRef<Path>>(&mut self, path: P) -> Result<(), EncryptionError> {
|
|
|
|
let (public, secret) = try!(Self::load_keypair_from_file(path));
|
|
|
|
self.register_secret_key(public, secret)
|
|
|
|
}
|
|
|
|
|
2017-07-03 09:48:25 +00:00
|
|
|
#[inline]
|
2017-03-22 16:28:45 +00:00
|
|
|
pub fn load_keypair_from_file<P: AsRef<Path>>(path: P) -> Result<(PublicKey, SecretKey), EncryptionError> {
|
2017-07-03 09:48:25 +00:00
|
|
|
Self::load_keypair_from_file_data(&try!(KeyfileYaml::load(path)))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn load_keypair_from_file_data(keyfile: &KeyfileYaml) -> Result<(PublicKey, SecretKey), EncryptionError> {
|
2017-03-22 16:28:45 +00:00
|
|
|
let public = try!(parse_hex(&keyfile.public).map_err(|_| EncryptionError::InvalidKey));
|
|
|
|
let public = try!(PublicKey::from_slice(&public).ok_or(EncryptionError::InvalidKey));
|
|
|
|
let secret = try!(parse_hex(&keyfile.secret).map_err(|_| EncryptionError::InvalidKey));
|
|
|
|
let secret = try!(SecretKey::from_slice(&secret).ok_or(EncryptionError::InvalidKey));
|
|
|
|
Ok((public, secret))
|
|
|
|
}
|
|
|
|
|
2017-07-03 09:48:25 +00:00
|
|
|
#[inline]
|
|
|
|
pub fn save_keypair_to_file_data(public: &PublicKey, secret: &SecretKey) -> KeyfileYaml {
|
|
|
|
KeyfileYaml { public: to_hex(&public[..]), secret: to_hex(&secret[..]) }
|
|
|
|
}
|
|
|
|
|
2017-03-22 16:28:45 +00:00
|
|
|
#[inline]
|
|
|
|
pub fn save_keypair_to_file<P: AsRef<Path>>(public: &PublicKey, secret: &SecretKey, path: P) -> Result<(), EncryptionError> {
|
2017-07-03 09:48:25 +00:00
|
|
|
Self::save_keypair_to_file_data(public, secret).save(path)
|
2017-03-22 16:28:45 +00:00
|
|
|
}
|
|
|
|
|
2017-03-10 11:43:32 +00:00
|
|
|
#[inline]
|
2017-03-18 16:22:11 +00:00
|
|
|
pub fn register_secret_key(&mut self, public: PublicKey, secret: SecretKey) -> Result<(), EncryptionError> {
|
2017-07-03 09:48:25 +00:00
|
|
|
if let Some(ref path) = self.path {
|
|
|
|
let path = path.join(to_hex(&public[..]) + ".yaml");
|
|
|
|
try!(Self::save_keypair_to_file(&public, &secret, path));
|
|
|
|
}
|
2017-03-18 16:22:11 +00:00
|
|
|
self.keys.insert(public, secret);
|
|
|
|
Ok(())
|
2017-03-10 11:43:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2017-03-18 16:22:11 +00:00
|
|
|
pub fn contains_secret_key(&mut self, public: &PublicKey) -> bool {
|
|
|
|
self.keys.contains_key(public)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_secret_key(&self, public: &PublicKey) -> Result<&SecretKey, EncryptionError> {
|
|
|
|
self.keys.get(public).ok_or_else(|| EncryptionError::MissingKey(*public))
|
2017-03-10 11:43:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2017-03-18 16:22:11 +00:00
|
|
|
pub fn encrypt(&self, enc: &Encryption, data: &[u8]) -> Result<Vec<u8>, EncryptionError> {
|
|
|
|
let &(ref method, ref public) = enc;
|
|
|
|
let public = try!(PublicKey::from_slice(public).ok_or(EncryptionError::InvalidKey));
|
|
|
|
match *method {
|
|
|
|
EncryptionMethod::Sodium => {
|
|
|
|
Ok(sealedbox::seal(data, &public))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn decrypt(&self, enc: &Encryption, data: &[u8]) -> Result<Vec<u8>, EncryptionError> {
|
|
|
|
let &(ref method, ref public) = enc;
|
|
|
|
let public = try!(PublicKey::from_slice(public).ok_or(EncryptionError::InvalidKey));
|
|
|
|
let secret = try!(self.get_secret_key(&public));
|
|
|
|
match *method {
|
|
|
|
EncryptionMethod::Sodium => {
|
|
|
|
sealedbox::open(data, &public, secret).map_err(|_| EncryptionError::Operation("Decryption failed"))
|
|
|
|
}
|
|
|
|
}
|
2017-03-10 11:43:32 +00:00
|
|
|
}
|
2017-04-12 06:30:42 +00:00
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn gen_keypair() -> (PublicKey, SecretKey) {
|
|
|
|
sodium_init();
|
|
|
|
box_::gen_keypair()
|
|
|
|
}
|
2017-05-08 13:11:07 +00:00
|
|
|
|
|
|
|
pub fn keypair_from_password(password: &str) -> (PublicKey, SecretKey) {
|
|
|
|
let salt = pwhash::Salt::from_slice(b"the_great_zvault_password_salt_1").unwrap();
|
|
|
|
let mut key = [0u8; pwhash::HASHEDPASSWORDBYTES];
|
|
|
|
let key = pwhash::derive_key(&mut key, password.as_bytes(), &salt, pwhash::OPSLIMIT_INTERACTIVE, pwhash::MEMLIMIT_INTERACTIVE).unwrap();
|
|
|
|
let mut seed = [0u8; 32];
|
|
|
|
let offset = key.len()-seed.len();
|
|
|
|
for (i, b) in seed.iter_mut().enumerate() {
|
|
|
|
*b = key[i+offset];
|
|
|
|
}
|
|
|
|
let mut pk = [0u8; 32];
|
|
|
|
let mut sk = [0u8; 32];
|
|
|
|
if unsafe { libsodium_sys::crypto_box_seed_keypair(&mut pk, &mut sk, &seed) } != 0 {
|
|
|
|
panic!("Libsodium failed");
|
|
|
|
}
|
|
|
|
(PublicKey::from_slice(&pk).unwrap(), SecretKey::from_slice(&sk).unwrap())
|
|
|
|
}
|
2017-03-10 11:43:32 +00:00
|
|
|
}
|
2017-07-03 09:48:25 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mod tests {
|
|
|
|
|
|
|
|
#[allow(unused_imports)]
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_gen_keypair() {
|
|
|
|
let key1 = Crypto::gen_keypair();
|
|
|
|
let key2 = Crypto::gen_keypair();
|
|
|
|
assert!(key1.0 != key2.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_keypair_from_password() {
|
|
|
|
let key1 = Crypto::keypair_from_password("foo");
|
|
|
|
let key2 = Crypto::keypair_from_password("foo");
|
|
|
|
assert_eq!(key1.0, key2.0);
|
|
|
|
let key3 = Crypto::keypair_from_password("bar");
|
|
|
|
assert!(key1.0 != key3.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_add_keypair() {
|
|
|
|
let mut crypto = Crypto::dummy();
|
|
|
|
let (pk, sk) = Crypto::gen_keypair();
|
|
|
|
assert!(!crypto.contains_secret_key(&pk));
|
|
|
|
crypto.add_secret_key(pk, sk);
|
|
|
|
assert!(crypto.contains_secret_key(&pk));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_save_load_keyfile() {
|
|
|
|
let (pk, sk) = Crypto::gen_keypair();
|
|
|
|
let data = Crypto::save_keypair_to_file_data(&pk, &sk);
|
|
|
|
let res = Crypto::load_keypair_from_file_data(&data);
|
|
|
|
assert!(res.is_ok());
|
|
|
|
let (pk2, sk2) = res.unwrap();
|
|
|
|
assert_eq!(pk, pk2);
|
|
|
|
assert_eq!(sk, sk2);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_encrypt_decrpyt() {
|
|
|
|
let mut crypto = Crypto::dummy();
|
|
|
|
let (pk, sk) = Crypto::gen_keypair();
|
|
|
|
crypto.add_secret_key(pk, sk);
|
|
|
|
let encryption = (EncryptionMethod::Sodium, ByteBuf::from(&pk[..]));
|
|
|
|
let cleartext = b"test123";
|
|
|
|
let result = crypto.encrypt(&encryption, cleartext);
|
|
|
|
assert!(result.is_ok());
|
|
|
|
let ciphertext = result.unwrap();
|
|
|
|
assert!(&ciphertext != cleartext);
|
|
|
|
let result = crypto.decrypt(&encryption, &ciphertext);
|
|
|
|
assert!(result.is_ok());
|
|
|
|
let unciphered = result.unwrap();
|
|
|
|
assert_eq!(&cleartext[..] as &[u8], &unciphered as &[u8]);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_wrong_key() {
|
|
|
|
let mut crypto = Crypto::dummy();
|
|
|
|
let (pk, sk) = Crypto::gen_keypair();
|
|
|
|
crypto.add_secret_key(pk, sk.clone());
|
|
|
|
let encryption = (EncryptionMethod::Sodium, ByteBuf::from(&pk[..]));
|
|
|
|
let cleartext = b"test123";
|
|
|
|
let result = crypto.encrypt(&encryption, cleartext);
|
|
|
|
assert!(result.is_ok());
|
|
|
|
let ciphertext = result.unwrap();
|
|
|
|
assert!(&ciphertext != cleartext);
|
|
|
|
let mut crypto2 = Crypto::dummy();
|
|
|
|
let mut sk2 = sk[..].to_vec();
|
|
|
|
sk2[4] ^= 53;
|
|
|
|
assert!(&sk[..] as &[u8] != &sk2[..] as &[u8]);
|
|
|
|
crypto2.add_secret_key(pk, SecretKey::from_slice(&sk2).unwrap());
|
|
|
|
let result = crypto2.decrypt(&encryption, &ciphertext);
|
|
|
|
assert!(result.is_err());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_modified_ciphertext() {
|
|
|
|
let mut crypto = Crypto::dummy();
|
|
|
|
let (pk, sk) = Crypto::gen_keypair();
|
|
|
|
crypto.add_secret_key(pk, sk.clone());
|
|
|
|
let encryption = (EncryptionMethod::Sodium, ByteBuf::from(&pk[..]));
|
|
|
|
let cleartext = b"test123";
|
|
|
|
let result = crypto.encrypt(&encryption, cleartext);
|
|
|
|
assert!(result.is_ok());
|
|
|
|
let mut ciphertext = result.unwrap();
|
|
|
|
assert!(&ciphertext != cleartext);
|
|
|
|
ciphertext[4] ^= 53;
|
|
|
|
let result = crypto.decrypt(&encryption, &ciphertext);
|
|
|
|
assert!(result.is_err());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "bench")]
|
|
|
|
mod benches {
|
|
|
|
|
|
|
|
#[allow(unused_imports)]
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
use test::Bencher;
|
|
|
|
|
|
|
|
|
|
|
|
#[allow(dead_code, needless_range_loop)]
|
|
|
|
fn test_data(n: usize) -> Vec<u8> {
|
|
|
|
let mut input = vec![0; n];
|
|
|
|
for i in 0..input.len() {
|
|
|
|
input[i] = (i * i * i) as u8;
|
|
|
|
}
|
|
|
|
input
|
|
|
|
}
|
|
|
|
|
|
|
|
#[bench]
|
|
|
|
fn bench_key_generate(b: &mut Bencher) {
|
|
|
|
b.iter(|| Crypto::gen_keypair());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[bench]
|
|
|
|
fn bench_encrypt(b: &mut Bencher) {
|
|
|
|
let mut crypto = Crypto::dummy();
|
|
|
|
let (pk, sk) = Crypto::gen_keypair();
|
|
|
|
crypto.add_secret_key(pk, sk.clone());
|
|
|
|
let encryption = (EncryptionMethod::Sodium, ByteBuf::from(&pk[..]));
|
|
|
|
let input = test_data(512*1024);
|
|
|
|
b.iter(|| crypto.encrypt(&encryption, &input));
|
|
|
|
b.bytes = input.len() as u64;
|
|
|
|
}
|
|
|
|
|
|
|
|
#[bench]
|
|
|
|
fn bench_decrypt(b: &mut Bencher) {
|
|
|
|
let mut crypto = Crypto::dummy();
|
|
|
|
let (pk, sk) = Crypto::gen_keypair();
|
|
|
|
crypto.add_secret_key(pk, sk.clone());
|
|
|
|
let encryption = (EncryptionMethod::Sodium, ByteBuf::from(&pk[..]));
|
|
|
|
let input = test_data(512*1024);
|
|
|
|
let output = crypto.encrypt(&encryption, &input).unwrap();
|
|
|
|
b.iter(|| crypto.decrypt(&encryption, &output));
|
|
|
|
b.bytes = input.len() as u64;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|