1extern crate libc;
22extern crate rand;
23
24use std::str::from_utf8_unchecked;
25use std::ffi::{CString, CStr};
26use std::error::Error;
27use std::fmt;
28use libc::{c_int, strerror};
29use rand::Rng;
30use rand::os::OsRng;
31
32extern "C" {
33 fn c_gen_salt(cost: u8, random16: *const u8, result: *mut u8) -> c_int;
35
36 fn c_hash(key: *const u8, salt: *const u8, result: *mut u8) -> c_int;
38}
39
40pub fn gen_salt(cost: u8) -> Result<[u8; 30], CryptError> {
44 let mut salt: [u8; 30] = [0; 30];
45 let mut random: [u8; 16] = [0; 16];
46
47 fill_random(&mut random);
48
49 unsafe {
50 match c_gen_salt(cost, random.as_ptr(), salt.as_mut_ptr()) {
51 0 => Ok(salt),
52 errno => Err(CryptError::new(errno, None)),
53 }
54 }
55}
56
57pub fn hash(password: &str, salt: &[u8]) -> Result<[u8; 61], CryptError> {
62 if password.len() == 0 {
63 return Err(CryptError::invalid_arg("password cannot be empty".into()));
64 }
65 if password.len() > 72 {
66 return Err(CryptError::invalid_arg("password length must not exceed 72".into()));
67 }
68 if salt.len() < 30 {
69 return Err(CryptError::invalid_arg("salt must be at least 30 bytes long".into()));
70 }
71
72 let c_password = if let Ok(c) = CString::new(password) {
73 c
74 } else {
75 return Err(CryptError::invalid_arg("password must not contain NULL characters".into()));
76 };
77
78 unsafe {
79 let mut result: [u8; 61] = [0; 61];
80 match c_hash(c_password.as_bytes().as_ptr(),
81 salt.as_ptr(),
82 result.as_mut_ptr()) {
83 0 => Ok(result),
84 errno => Err(CryptError::new(errno, None)),
85 }
86 }
87}
88
89pub fn to_str<'a>(bytes: &'a [u8]) -> Result<&'a str, CryptError> {
91 match CStr::from_bytes_with_nul(bytes) {
92 Ok(c_str) => {
93 match c_str.to_str() {
94 Ok(s) => Ok(s),
95 Err(e) => Err(CryptError::invalid_arg(e.to_string())),
96 }
97 }
98 Err(e) => Err(CryptError::invalid_arg(e.to_string())),
99 }
100}
101
102
103#[derive(Debug)]
107pub struct CryptError {
108 errno: c_int,
110
111 desc: Option<String>,
113}
114
115impl CryptError {
116 pub fn new(errno: c_int, desc: Option<String>) -> CryptError {
117 CryptError { errno, desc }
118 }
119
120 pub fn invalid_arg(desc: String) -> CryptError {
121 CryptError {
122 errno: libc::EINVAL,
123 desc: Some(desc),
124 }
125 }
126
127 pub fn errno(&self) -> c_int {
128 self.errno
129 }
130}
131
132impl Error for CryptError {
133 fn description(&self) -> &str {
134 "bcrypt error"
135 }
136}
137
138impl fmt::Display for CryptError {
139 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
140 let desc = if let Some(ref desc) = self.desc {
141 desc.clone()
142 } else {
143 unsafe { errno_to_string(self.errno) }
144 };
145
146 write!(f, "bcrypt error: {} ({})", desc, self.errno)
147 }
148}
149
150fn fill_random(random: &mut [u8]) {
151 let mut rng = OsRng::new().unwrap();
152 rng.fill_bytes(random);
153}
154
155unsafe fn errno_to_string(errno: c_int) -> String {
156 from_utf8_unchecked(CStr::from_ptr(strerror(errno)).to_bytes()).into()
157}
158
159
160#[cfg(test)]
161mod tests {
162 use super::{to_str, gen_salt, hash};
163
164 #[test]
165 fn test_to_str() {
166 assert_eq!(to_str(&[114, 117, 115, 116, 097, 099, 101, 097, 110, 0]).unwrap(),
167 "rustacean");
168 assert!(to_str(&[114, 117, 115, 116, 097, 099, 101, 097, 110]).is_err());
169 assert!(to_str(&[114, 117, 115, 116, 097, 099, 101, 097, 0, 110, 0]).is_err());
170 }
171
172 #[test]
173 fn test_gen_salt() {
174 let salt = gen_salt(4).unwrap();
175 assert_eq!(to_str(&salt).unwrap().len(), 29);
176 }
177
178 #[test]
179 fn test_hash() {
180 let pwhash = hash("Password", &gen_salt(4).unwrap()).unwrap();
181 assert_eq!(to_str(&pwhash).unwrap().len(), 60);
182
183 assert!(hash("Password", &[0; 15]).is_err());
184 assert!(hash("", &gen_salt(4).unwrap()).is_err());
185 }
186}