1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
//! An implementation of the [Master Password algorithm](https://ssl.masterpasswordapp.com/algorithm.html).
//!
//! Don't forget to initialize libsodium before using.

extern crate libc;
extern crate secstr;
extern crate byteorder;
extern crate libsodium_sys as ffi;

use std::io;
use std::mem::uninitialized;
use secstr::*;
use libc::size_t;
use byteorder::{BigEndian, WriteBytesExt};

const SALT_PREFIX : &'static [u8] = b"com.lyndir.masterpassword";

pub const TEMPLATES_MAXIMUM : &'static [&'static str] = &["anoxxxxxxxxxxxxxxxxx", "axxxxxxxxxxxxxxxxxno"];
pub const TEMPLATES_LONG : &'static [&'static str] = &[
    "CvcvnoCvcvCvcv", "CvcvCvcvnoCvcv",
    "CvcvCvcvCvcvno", "CvccnoCvcvCvcv",
    "CvccCvcvnoCvcv", "CvccCvcvCvcvno",
    "CvcvnoCvccCvcv", "CvcvCvccnoCvcv",
    "CvcvCvccCvcvno", "CvcvnoCvcvCvcc",
    "CvcvCvcvnoCvcc", "CvcvCvcvCvccno",
    "CvccnoCvccCvcv", "CvccCvccnoCvcv",
    "CvccCvccCvcvno", "CvcvnoCvccCvcc",
    "CvcvCvccnoCvcc", "CvcvCvccCvccno",
    "CvccnoCvcvCvcc", "CvccCvcvnoCvcc",
    "CvccCvcvCvccno"
];
pub const TEMPLATES_MEDIUM : &'static [&'static str] = &["CvcnoCvc", "CvcCvcno"];
pub const TEMPLATES_SHORT : &'static [&'static str] = &["Cvcn"];
pub const TEMPLATES_BASIC : &'static [&'static str] = &["aaanaaan", "aannaaan", "aaannaaa"];
pub const TEMPLATES_PIN : &'static [&'static str] = &["nnnn"];

const IDENTICON_LEFT_ARMS: &'static [&'static str] = &["╔", "╚", "╰", "═"];
const IDENTICON_RIGHT_ARMS: &'static [&'static str] = &["╗", "╝", "╯", "═"];
const IDENTICON_BODIES: &'static [&'static str] = &["█", "░", "▒", "▓", "☺", "☻"];
const IDENTICON_ACCESSORIES: &'static [&'static str] = &[
    "◈", "◎", "◐", "◑", "◒", "◓", "☀", "☁", "☂", "☃", "", "★", "☆", "☎", "☏", "⎈", "⌂", "☘", "☢", "☣",
    "☕", "⌚", "⌛", "⏰", "⚡", "⛄", "⛅", "☔", "♔", "♕", "♖", "♗", "♘", "♙", "♚", "♛", "♜", "♝", "♞", "♟",
    "♨", "♩", "♪", "♫", "⚐", "⚑", "⚔", "⚖", "⚙", "⚠", "⌘", "⏎", "✄", "✆", "✈", "✉", "✌"
];

/// Low level master key generation function (basically, scrypt).
pub fn gen_master_key_custom(password: SecStr, salt: SecStr, n: u64, r: u32, p: u32, result_len: usize) -> io::Result<SecStr> {
    let password_a = password.unsecure();
    let salt_a = salt.unsecure();
    let mut dst = Vec::<u8>::with_capacity(result_len);
    if unsafe {
        ffi::crypto_pwhash_scryptsalsa208sha256_ll(
            password_a.as_ptr(), password_a.len() as size_t,
            salt_a.as_ptr(), salt_a.len() as size_t,
            n, r, p,
            dst.as_mut_ptr(), result_len as size_t)
    } == 0 {
        unsafe { dst.set_len(result_len); }
        Ok(SecStr::new(dst))
    } else {
        Err(io::Error::new(io::ErrorKind::Other, "Scrypt failed"))
    }
}

/// Generate a 512-bit (64-byte) master key using scrypt.
pub fn gen_master_key(password: SecStr, name: &str) -> io::Result<SecStr> {
    let mut salt = vec![];
    salt.extend(SALT_PREFIX);
    try!(salt.write_u32::<BigEndian>(name.len() as u32));
    salt.extend(name.bytes());
    gen_master_key_custom(password, SecStr::new(salt), 32768, 8, 2, 64)
}

/// Generate a 256-bit (32-byte) site seed using HMAC-SHA-256.
pub fn gen_site_seed(master_key: &SecStr, site_name: &str, counter: u32) -> io::Result<SecStr> {
    let mut msg = vec![];
    msg.extend(SALT_PREFIX);
    try!(msg.write_u32::<BigEndian>(site_name.len() as u32));
    msg.extend(site_name.bytes());
    try!(msg.write_u32::<BigEndian>(counter));
    hash_hmac_sha256(master_key, &msg)
}

/// Generate a readable password from a site seed using templates.
pub fn gen_site_password(site_seed: &SecStr, templates: &[&str]) -> SecStr {
    let site_seed_a = site_seed.unsecure();
    let template = templates[site_seed_a[0] as usize % templates.len()];
    let mut i = 0;
    SecStr::from(template.chars().map(|x| {
        i += 1;
        match x {
            'V' => b"AEIOU"[(site_seed_a[i] % 5) as usize],
            'C' => b"BCDFGHJKLMNPQRSTVWXYZ"[(site_seed_a[i] % 21) as usize],
            'v' => b"aeiou"[(site_seed_a[i] % 5) as usize],
            'c' => b"bcdfghjklmnpqrstvwxyz"[(site_seed_a[i] % 21) as usize],
            'A' => b"AEIOUBCDFGHJKLMNPQRSTVWXYZ"[(site_seed_a[i] % 26) as usize],
            'a' => b"AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz"[(site_seed_a[i] % 52) as usize],
            'n' => b"0123456789"[(site_seed_a[i] % 10) as usize],
            'o' => b"@&%?,=[]_:-+*$#!'^~;()/."[(site_seed_a[i] % 24) as usize],
            _   => b"AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz0123456789!@#$%^&*()"[(site_seed_a[i] % 72) as usize]
        }
    }).collect::<Vec<_>>())
}

/// Create identicon for password and name combination
pub fn create_identicon(master_pass: &SecStr, name: &str) -> Identicon {
    let seed = hash_hmac_sha256(master_pass, name.as_bytes()).expect("Failed to generate identicon seed");
    Identicon {
        left_arm: IDENTICON_LEFT_ARMS[seed.unsecure()[0] as usize % (IDENTICON_LEFT_ARMS.len())],
        body: IDENTICON_BODIES[seed.unsecure()[1] as usize % (IDENTICON_BODIES.len())],
        right_arm: IDENTICON_RIGHT_ARMS[seed.unsecure()[2] as usize % (IDENTICON_RIGHT_ARMS.len())],
        accessory: IDENTICON_ACCESSORIES[seed.unsecure()[3] as usize % (IDENTICON_ACCESSORIES.len())],
        color: (seed.unsecure()[4] % 7 + 1) as u8,
    }
}

/// Generate a 256-bit (32-byte) hash using HMAC-SHA-256.
fn hash_hmac_sha256(key: &SecStr, msg: &[u8]) -> io::Result<SecStr> {
    let mut dst = Vec::<u8>::with_capacity(32);
    if unsafe {
        let mut state = uninitialized::<ffi::crypto_auth_hmacsha256_state>();
        let mut ret = 0;
        ret += ffi::crypto_auth_hmacsha256_init(
            &mut state,
            key.unsecure().as_ptr() as *const u8,
            key.unsecure().len() as size_t);
        ret += ffi::crypto_auth_hmacsha256_update(
            &mut state,
            msg.as_ptr(),
            msg.len() as u64);
        ret += ffi::crypto_auth_hmacsha256_final(
            &mut state,
            dst.as_mut_ptr() as *mut _);
        ret
    } == 0 {
        unsafe { dst.set_len(32); }
        Ok(SecStr::new(dst))
    } else {
        Err(io::Error::new(io::ErrorKind::Other, "HMAC-SHA-256 failed"))
    }
}

#[derive(Debug,PartialEq)]
pub struct Identicon {
    pub left_arm: &'static str,
    pub right_arm: &'static str,
    pub body: &'static str,
    pub accessory: &'static str,
    pub color: u8,
}

#[cfg(test)]
mod tests {
    use super::*;
    use secstr::*;

    #[test]
    fn test_pin() {
        let master_key = gen_master_key(SecStr::from("Correct Horse Battery Staple"), "Cosima Niehaus").unwrap();
        let site_seed = gen_site_seed(&master_key, "bank.com", 1).unwrap();
        assert_eq!(gen_site_password(&site_seed, TEMPLATES_PIN).unsecure(), b"7404")
    }

    #[test]
    fn test_long() {
        let master_key = gen_master_key(SecStr::from("Correct Horse Battery Staple"), "Cosima Niehaus").unwrap();
        let site_seed = gen_site_seed(&master_key, "twitter.com", 5).unwrap();
        assert_eq!(gen_site_password(&site_seed, TEMPLATES_LONG).unsecure(), b"Kiwe2^BecuRodw")
    }

    #[test]
    fn test_maximum() {
        let master_key = gen_master_key(SecStr::from("hunter2"), "UserName").unwrap();
        let site_seed = gen_site_seed(&master_key, "test", 1).unwrap();
        assert_eq!(gen_site_password(&site_seed, TEMPLATES_MAXIMUM).unsecure(), b"e5:kl#V@0uAZ02xKUic5")
    }
  
    #[test]
    fn test_identicon() {
        let master_pass = SecStr::from("test1234");
        let name = "test";
        let expected = Identicon { left_arm: "═", right_arm: "╗", body: "▓", accessory: "♪", color: 7u8 };
        assert_eq!(create_identicon(&master_pass, name), expected);
    }
}