libpasta 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.gitmodules +3 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +41 -0
- data/Rakefile +37 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/ext/Rakefile +9 -0
- data/ext/pasta-bindings/.gitmodules +3 -0
- data/ext/pasta-bindings/Makefile +84 -0
- data/ext/pasta-bindings/README.md +54 -0
- data/ext/pasta-bindings/libpasta/.gitignore +2 -0
- data/ext/pasta-bindings/libpasta/.travis.yml +60 -0
- data/ext/pasta-bindings/libpasta/Cargo.toml +42 -0
- data/ext/pasta-bindings/libpasta/LICENSE.md +21 -0
- data/ext/pasta-bindings/libpasta/Makefile +27 -0
- data/ext/pasta-bindings/libpasta/README.md +78 -0
- data/ext/pasta-bindings/libpasta/benches/bench.rs +125 -0
- data/ext/pasta-bindings/libpasta/examples/alternate_key_source.rs +33 -0
- data/ext/pasta-bindings/libpasta/examples/config.rs +10 -0
- data/ext/pasta-bindings/libpasta/examples/config_hmac.rs +25 -0
- data/ext/pasta-bindings/libpasta/examples/hash_password.rs +10 -0
- data/ext/pasta-bindings/libpasta/examples/migrate_password.rs +47 -0
- data/ext/pasta-bindings/libpasta/examples/verify_password.rs +24 -0
- data/ext/pasta-bindings/libpasta/libpasta-capi/Cargo.toml +12 -0
- data/ext/pasta-bindings/libpasta/libpasta-capi/ctest/compile +6 -0
- data/ext/pasta-bindings/libpasta/libpasta-capi/ctest/test +0 -0
- data/ext/pasta-bindings/libpasta/libpasta-capi/ctest/test.c +12 -0
- data/ext/pasta-bindings/libpasta/libpasta-capi/include/pasta.h +6 -0
- data/ext/pasta-bindings/libpasta/libpasta-capi/src/lib.rs +80 -0
- data/ext/pasta-bindings/libpasta/src/bench.rs +39 -0
- data/ext/pasta-bindings/libpasta/src/config.rs +358 -0
- data/ext/pasta-bindings/libpasta/src/hashing/de.rs +284 -0
- data/ext/pasta-bindings/libpasta/src/hashing/mod.rs +161 -0
- data/ext/pasta-bindings/libpasta/src/hashing/ser.rs +67 -0
- data/ext/pasta-bindings/libpasta/src/key/mod.rs +66 -0
- data/ext/pasta-bindings/libpasta/src/lib.rs +468 -0
- data/ext/pasta-bindings/libpasta/src/primitives/argon2.rs +180 -0
- data/ext/pasta-bindings/libpasta/src/primitives/bcrypt.rs +165 -0
- data/ext/pasta-bindings/libpasta/src/primitives/hmac.rs +134 -0
- data/ext/pasta-bindings/libpasta/src/primitives/mod.rs +312 -0
- data/ext/pasta-bindings/libpasta/src/primitives/pbkdf2.rs +272 -0
- data/ext/pasta-bindings/libpasta/src/primitives/scrypt.rs +144 -0
- data/ext/pasta-bindings/libpasta/src/sod.rs +46 -0
- data/ext/pasta-bindings/libpasta/tests/.libpasta.yaml +8 -0
- data/ext/pasta-bindings/libpasta/tests/common/mod.rs +37 -0
- data/ext/pasta-bindings/libpasta/tests/test_argon2.rs +8 -0
- data/ext/pasta-bindings/libpasta/tests/test_bcrypt.rs +8 -0
- data/ext/pasta-bindings/libpasta/tests/test_from_file.rs +13 -0
- data/ext/pasta-bindings/libpasta/tests/test_hmac.rs +26 -0
- data/ext/pasta-bindings/libpasta/tests/test_pbkdf2.rs +8 -0
- data/ext/pasta-bindings/libpasta/tests/test_ringpbkdf2.rs +8 -0
- data/ext/pasta-bindings/libpasta/tests/test_scrypt.rs +8 -0
- data/ext/pasta-bindings/pasta.i +31 -0
- data/ext/pasta-bindings/tests/Makefile +38 -0
- data/ext/pasta-bindings/tests/pasta_test.go +12 -0
- data/ext/pasta-bindings/tests/test.php +7 -0
- data/ext/pasta-bindings/tests/test.py +7 -0
- data/ext/pasta-bindings/tests/test.rb +5 -0
- data/lib/libpasta.rb +2 -0
- data/lib/libpasta/version.rb +3 -0
- data/libpasta.gemspec +52 -0
- data/spec/libpasta_spec.rb +11 -0
- data/spec/spec_helper.rb +11 -0
- metadata +183 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
//! The `key` module is for managing key sources.
|
2
|
+
//!
|
3
|
+
//! The idea is that a running application can dynamically insert keys into
|
4
|
+
//! the key store, which are used for producing and verifying hashes.
|
5
|
+
#![allow(dead_code)]
|
6
|
+
|
7
|
+
use data_encoding::BASE64_NOPAD;
|
8
|
+
use ring::digest;
|
9
|
+
|
10
|
+
use std::collections::HashMap;
|
11
|
+
use std::fmt;
|
12
|
+
use std::sync::RwLock;
|
13
|
+
|
14
|
+
lazy_static!{
|
15
|
+
static ref LOCAL_STORE: LocalStore = LocalStore::new();
|
16
|
+
}
|
17
|
+
|
18
|
+
pub(crate) fn get_global() -> &'static LocalStore {
|
19
|
+
&*LOCAL_STORE
|
20
|
+
}
|
21
|
+
|
22
|
+
/// Structure used as a global store for keys.
|
23
|
+
#[derive(Debug)]
|
24
|
+
pub struct LocalStore {
|
25
|
+
store: RwLock<HashMap<String, Vec<u8>>>,
|
26
|
+
}
|
27
|
+
|
28
|
+
|
29
|
+
/// A key storage source. Permits retrieving and storing keys.
|
30
|
+
///
|
31
|
+
/// Keys are indexed by a `String` id, and are stored as Vec<u8>.
|
32
|
+
pub trait Store: fmt::Debug + Send + Sync {
|
33
|
+
/// Insert a new key into the `Store`.
|
34
|
+
fn insert(&self, key: &[u8]) -> String;
|
35
|
+
|
36
|
+
/// Get a key from the `Store`.
|
37
|
+
fn get_key(&self, id: &str) -> Option<Vec<u8>>;
|
38
|
+
}
|
39
|
+
|
40
|
+
impl LocalStore {
|
41
|
+
fn new() -> Self {
|
42
|
+
Self { store: RwLock::new(HashMap::new()) }
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
impl Store for LocalStore {
|
47
|
+
/// Insert a new key into the `KeyStore`.
|
48
|
+
fn insert(&self, key: &[u8]) -> String {
|
49
|
+
let digest = digest::digest(&digest::SHA512_256, key);
|
50
|
+
let key_id = BASE64_NOPAD.encode(digest.as_ref());
|
51
|
+
let _ = self.store
|
52
|
+
.write()
|
53
|
+
.expect("could not get write on key store")
|
54
|
+
.insert(key_id.clone(), key.to_vec());
|
55
|
+
key_id
|
56
|
+
}
|
57
|
+
|
58
|
+
/// Get a key from the `KeyStore`.
|
59
|
+
fn get_key(&self, id: &str) -> Option<Vec<u8>> {
|
60
|
+
if let Some(v) = self.store.read().expect("could not get read lock on key store").get(id) {
|
61
|
+
Some(v.clone())
|
62
|
+
} else {
|
63
|
+
None
|
64
|
+
}
|
65
|
+
}
|
66
|
+
}
|
@@ -0,0 +1,468 @@
|
|
1
|
+
// Copyright (c) 2017, Sam Scott
|
2
|
+
|
3
|
+
// Permission to use, copy, modify, and/or distribute this software for any
|
4
|
+
// purpose with or without fee is hereby granted, provided that the above
|
5
|
+
// copyright notice and this permission notice appear in all copies.
|
6
|
+
|
7
|
+
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
8
|
+
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
9
|
+
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
10
|
+
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
11
|
+
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
12
|
+
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
13
|
+
// PERFORMANCE OF THIS SOFTWARE.
|
14
|
+
|
15
|
+
//! # Pasta - Password Storage
|
16
|
+
//! _Making passwords painless_
|
17
|
+
//!
|
18
|
+
//! This is a library designed to make secure password storage easy.
|
19
|
+
//!
|
20
|
+
//! For a more comprehensive introduction, see [the homepage](https://libpasta.github.io/)
|
21
|
+
//!
|
22
|
+
//!
|
23
|
+
//! ## Examples
|
24
|
+
//!
|
25
|
+
//! The basic functionality for computing password hashes is:
|
26
|
+
//!
|
27
|
+
//! ```
|
28
|
+
//! extern crate libpasta;
|
29
|
+
//! // We re-export the rpassword crate for CLI password input.
|
30
|
+
//! use libpasta::rpassword::*;
|
31
|
+
//!
|
32
|
+
//! fn main() {
|
33
|
+
//! # if false {
|
34
|
+
//! let password = prompt_password_stdout("Please enter your password:").unwrap();
|
35
|
+
//! # }
|
36
|
+
//! # let password = "hunter2";
|
37
|
+
//! let password_hash = libpasta::hash_password(password);
|
38
|
+
//! println!("The stored password is: '{}'", password_hash);
|
39
|
+
//! }
|
40
|
+
//! ```
|
41
|
+
//! ## Supported formats
|
42
|
+
//!
|
43
|
+
//! `libpasta` attempts to support some legacy formats. For example, the `bcrypt`
|
44
|
+
//! format `$2y$...`.
|
45
|
+
|
46
|
+
#![cfg_attr(all(feature="bench", test), feature(test))]
|
47
|
+
|
48
|
+
|
49
|
+
#![allow(unknown_lints)]
|
50
|
+
#![deny(clippy_pedantic)]
|
51
|
+
#![allow(
|
52
|
+
missing_docs_in_private_items,
|
53
|
+
// we use fn new() -> Primitive for convenience
|
54
|
+
new_ret_no_self,
|
55
|
+
range_plus_one, // `..=end` not yet stable
|
56
|
+
use_debug,
|
57
|
+
)]
|
58
|
+
#![deny(
|
59
|
+
const_err,
|
60
|
+
dead_code,
|
61
|
+
deprecated,
|
62
|
+
exceeding_bitshifts,
|
63
|
+
improper_ctypes,
|
64
|
+
missing_docs,
|
65
|
+
mutable_transmutes,
|
66
|
+
no_mangle_const_items,
|
67
|
+
non_camel_case_types,
|
68
|
+
non_shorthand_field_patterns,
|
69
|
+
non_snake_case,
|
70
|
+
non_upper_case_globals,
|
71
|
+
overflowing_literals,
|
72
|
+
path_statements,
|
73
|
+
plugin_as_library,
|
74
|
+
private_no_mangle_fns,
|
75
|
+
private_no_mangle_statics,
|
76
|
+
stable_features,
|
77
|
+
trivial_casts,
|
78
|
+
trivial_numeric_casts,
|
79
|
+
unconditional_recursion,
|
80
|
+
unknown_crate_types,
|
81
|
+
unreachable_code,
|
82
|
+
unsafe_code,
|
83
|
+
unstable_features,
|
84
|
+
unused_allocation,
|
85
|
+
unused_assignments,
|
86
|
+
unused_attributes,
|
87
|
+
unused_comparisons,
|
88
|
+
unused_extern_crates,
|
89
|
+
unused_features,
|
90
|
+
unused_imports,
|
91
|
+
unused_import_braces,
|
92
|
+
unused_must_use,
|
93
|
+
unused_mut,
|
94
|
+
unused_parens,
|
95
|
+
unused_qualifications,
|
96
|
+
unused_results,
|
97
|
+
unused_unsafe,
|
98
|
+
unused_variables,
|
99
|
+
variant_size_differences,
|
100
|
+
warnings,
|
101
|
+
while_true,
|
102
|
+
)]
|
103
|
+
#![cfg_attr(all(feature="bench", test), allow(unstable_features))]
|
104
|
+
|
105
|
+
extern crate data_encoding;
|
106
|
+
#[macro_use]
|
107
|
+
extern crate error_chain;
|
108
|
+
extern crate itertools;
|
109
|
+
#[macro_use]
|
110
|
+
extern crate lazy_static;
|
111
|
+
#[macro_use]
|
112
|
+
extern crate log;
|
113
|
+
extern crate num_traits;
|
114
|
+
extern crate ring;
|
115
|
+
extern crate ring_pwhash;
|
116
|
+
extern crate serde;
|
117
|
+
#[macro_use]
|
118
|
+
extern crate serde_derive;
|
119
|
+
extern crate serde_mcf;
|
120
|
+
extern crate serde_yaml;
|
121
|
+
|
122
|
+
/// Re-export rpassword for convenience.
|
123
|
+
pub mod rpassword {
|
124
|
+
extern crate rpassword;
|
125
|
+
pub use self::rpassword::*;
|
126
|
+
}
|
127
|
+
|
128
|
+
/// `libpasta` errors.
|
129
|
+
pub mod errors {
|
130
|
+
use ring;
|
131
|
+
use serde_mcf;
|
132
|
+
use std::{fmt, result};
|
133
|
+
// Create the Error, ErrorKind, ResultExt, and Result types
|
134
|
+
error_chain!{
|
135
|
+
foreign_links {
|
136
|
+
Deserialize(serde_mcf::errors::Error) #[doc = "Errors from de/serializing MCF password hashes."] ;
|
137
|
+
Ring(ring::error::Unspecified) #[doc = "Errors originating from `ring`"] ;
|
138
|
+
}
|
139
|
+
}
|
140
|
+
|
141
|
+
/// Convenience trait for producing detailed error messages on `expect`.
|
142
|
+
pub trait ExpectReport {
|
143
|
+
/// Return type on successful `expect`
|
144
|
+
type Inner;
|
145
|
+
/// Wraps `Result::expect` to produce a longer error message with
|
146
|
+
/// instructions for submitting a bug report.
|
147
|
+
fn expect_report(self, msg: &str) -> Self::Inner;
|
148
|
+
}
|
149
|
+
|
150
|
+
impl<T, E: fmt::Debug> ExpectReport for result::Result<T, E> {
|
151
|
+
type Inner = T;
|
152
|
+
fn expect_report(self, msg: &str) -> T {
|
153
|
+
self.expect(&format!("{}\nIf you are seeing this message, you have encountered \
|
154
|
+
a situation we did not think was possible. Please submit a bug \
|
155
|
+
report at https://github.com/libpasta/libpasta/issues with this message.\n", msg))
|
156
|
+
}
|
157
|
+
}
|
158
|
+
|
159
|
+
impl<T> ExpectReport for Option<T> {
|
160
|
+
type Inner = T;
|
161
|
+
fn expect_report(self, msg: &str) -> T {
|
162
|
+
self.expect(&format!("{}\nIf you are seeing this message, you have encountered\
|
163
|
+
a situation we did not think was possible. Please submit a bug\
|
164
|
+
report at https://github.com/libpasta/libpasta/issues with this message.\n", msg))
|
165
|
+
}
|
166
|
+
}
|
167
|
+
}
|
168
|
+
|
169
|
+
use errors::*;
|
170
|
+
use ring::rand::SecureRandom;
|
171
|
+
|
172
|
+
#[macro_use]
|
173
|
+
mod bench;
|
174
|
+
|
175
|
+
pub mod config;
|
176
|
+
pub use config::Config;
|
177
|
+
pub mod key;
|
178
|
+
pub mod hashing;
|
179
|
+
use hashing::Output;
|
180
|
+
|
181
|
+
pub mod primitives;
|
182
|
+
|
183
|
+
/// Module to define the Static or Dynamic `Sod` enum.
|
184
|
+
pub mod sod;
|
185
|
+
|
186
|
+
/// Generates a default hash for a given password.
|
187
|
+
///
|
188
|
+
/// Will automatically generate a random salt. In the extreme case that the
|
189
|
+
/// default source of randomness is unavailable, this will fallback to a seed
|
190
|
+
/// generated when the library is initialised. An error will be logged when this
|
191
|
+
/// happens.
|
192
|
+
///
|
193
|
+
/// This is the simplest way to use libpasta, and uses sane defaults.
|
194
|
+
/// ## Panics
|
195
|
+
/// A panic indicates a problem with the serialization mechanisms, and should
|
196
|
+
/// be reported.
|
197
|
+
pub fn hash_password(password: &str) -> String {
|
198
|
+
config::DEFAULT_CONFIG.hash_password(password)
|
199
|
+
}
|
200
|
+
|
201
|
+
/// Same as `hash_password` but returns `Result` to allow error handling.
|
202
|
+
/// TODO: decide on which API is best to use.
|
203
|
+
#[doc(hidden)]
|
204
|
+
pub fn hash_password_safe(password: &str) -> Result<String> {
|
205
|
+
config::DEFAULT_CONFIG.hash_password_safe(password)
|
206
|
+
|
207
|
+
}
|
208
|
+
|
209
|
+
/// Verifies the provided password matches the inputted hash string.
|
210
|
+
///
|
211
|
+
/// If there is any error in processing the hash or password, this
|
212
|
+
/// will simply return `false`.
|
213
|
+
pub fn verify_password(hash: &str, password: &str) -> bool {
|
214
|
+
verify_password_safe(hash, password).unwrap_or(false)
|
215
|
+
}
|
216
|
+
|
217
|
+
/// Same as `verify_password` but returns `Result` to allow error handling.
|
218
|
+
/// TODO: decide on which API is best to use.
|
219
|
+
#[doc(hidden)]
|
220
|
+
pub fn verify_password_safe(hash: &str, password: &str) -> Result<bool> {
|
221
|
+
let pwd_hash: Output = serde_mcf::from_str(hash)?;
|
222
|
+
Ok(pwd_hash.verify(password))
|
223
|
+
}
|
224
|
+
|
225
|
+
/// Verifies a supplied password against a previously computed password hash,
|
226
|
+
/// and performs an in-place update of the hash value if the password verifies.
|
227
|
+
/// Hence this needs to take a mutable `String` reference.
|
228
|
+
pub fn verify_password_update_hash(hash: &mut String, password: &str) -> bool {
|
229
|
+
config::DEFAULT_CONFIG.verify_password_update_hash(hash, password)
|
230
|
+
}
|
231
|
+
|
232
|
+
/// Same as `verify_password_update_hash`, but returns `Result` to allow error handling.
|
233
|
+
#[doc(hidden)]
|
234
|
+
pub fn verify_password_update_hash_safe(hash: &mut String, password: &str) -> Result<bool> {
|
235
|
+
config::DEFAULT_CONFIG.verify_password_update_hash_safe(hash, password)
|
236
|
+
|
237
|
+
}
|
238
|
+
|
239
|
+
|
240
|
+
/// Migrate the input hash to the current recommended hash.
|
241
|
+
///
|
242
|
+
/// Note that this does *not* require the password. This is for batch updating
|
243
|
+
/// of hashes, where the password is not available. This performs an onion
|
244
|
+
/// approach, returning `new_hash(old_hash)`.
|
245
|
+
///
|
246
|
+
/// If the password is also available, the `verify_password_update_hash` should
|
247
|
+
/// instead be used.
|
248
|
+
pub fn migrate_hash(hash: &mut String) {
|
249
|
+
config::DEFAULT_CONFIG.migrate_hash(hash)
|
250
|
+
}
|
251
|
+
|
252
|
+
/// Same as `migrate_hash` but returns `Result` to allow error handling.
|
253
|
+
#[doc(hidden)]
|
254
|
+
pub fn migrate_hash_safe(hash: &mut String) -> Result<()> {
|
255
|
+
config::DEFAULT_CONFIG.migrate_hash_safe(hash)
|
256
|
+
|
257
|
+
}
|
258
|
+
|
259
|
+
fn gen_salt(rng: &SecureRandom) -> Vec<u8> {
|
260
|
+
let mut salt = vec![0_u8; 16];
|
261
|
+
if rng.fill(&mut salt).is_ok() {
|
262
|
+
salt
|
263
|
+
} else {
|
264
|
+
error!("failed to get fresh randomness, relying on backup seed to generate pseudoranom output");
|
265
|
+
config::backup_gen_salt()
|
266
|
+
}
|
267
|
+
}
|
268
|
+
|
269
|
+
#[cfg(test)]
|
270
|
+
use ring::rand::SystemRandom;
|
271
|
+
|
272
|
+
#[cfg(test)]
|
273
|
+
fn get_salt() -> Vec<u8> {
|
274
|
+
gen_salt(&SystemRandom)
|
275
|
+
}
|
276
|
+
|
277
|
+
#[cfg(test)]
|
278
|
+
mod api_tests {
|
279
|
+
use super::*;
|
280
|
+
use config::DEFAULT_PRIM;
|
281
|
+
use hashing::{Algorithm, Output};
|
282
|
+
use primitives::{Bcrypt, Hmac};
|
283
|
+
use sod::Sod;
|
284
|
+
|
285
|
+
#[test]
|
286
|
+
fn sanity_check() {
|
287
|
+
let password = "";
|
288
|
+
let hash = hash_password(password);
|
289
|
+
println!("Hash: {:?}", hash);
|
290
|
+
|
291
|
+
// can't use password again
|
292
|
+
let password = "";
|
293
|
+
assert!(verify_password(&hash, password));
|
294
|
+
assert!(!verify_password(&hash, "wrong password"));
|
295
|
+
|
296
|
+
let password = "hunter2";
|
297
|
+
let hash = hash_password(password);
|
298
|
+
|
299
|
+
// can't use password again
|
300
|
+
let password = "hunter2";
|
301
|
+
assert!(verify_password(&hash, password));
|
302
|
+
assert!(!verify_password(&hash, "wrong password"));
|
303
|
+
}
|
304
|
+
|
305
|
+
#[test]
|
306
|
+
fn external_check() {
|
307
|
+
let password = "hunter2";
|
308
|
+
let hash = "$2a$10$u.Fhlm/a1DpHr/z5KrsLG.iZ7iM9r8DInJvZ57VArRKuhlHAoVZOi";
|
309
|
+
let pwd_hash: Output = serde_mcf::from_str(hash).unwrap();
|
310
|
+
println!("{:?}", pwd_hash);
|
311
|
+
|
312
|
+
let expected_hash = pwd_hash.alg.hash_with_salt(password.as_bytes(), &pwd_hash.salt);
|
313
|
+
assert_eq!(pwd_hash.hash, &expected_hash[..]);
|
314
|
+
assert!(verify_password(&hash, password));
|
315
|
+
}
|
316
|
+
|
317
|
+
#[test]
|
318
|
+
fn emoji_password() {
|
319
|
+
let password = "emojisaregreat💖💖💖";
|
320
|
+
let hash = hash_password(password);
|
321
|
+
assert!(verify_password(&hash, password));
|
322
|
+
}
|
323
|
+
|
324
|
+
#[test]
|
325
|
+
fn nested_hash() {
|
326
|
+
let password = "hunter2";
|
327
|
+
|
328
|
+
let params = Algorithm::Nested {
|
329
|
+
inner: Box::new(Algorithm::default()),
|
330
|
+
outer: DEFAULT_PRIM.clone(),
|
331
|
+
};
|
332
|
+
let hash = params.hash(&password);
|
333
|
+
|
334
|
+
let password = "hunter2";
|
335
|
+
println!("{:?}", hash);
|
336
|
+
assert!(hash.verify(&password));
|
337
|
+
|
338
|
+
let password = "hunter2";
|
339
|
+
let hash = serde_mcf::to_string(&hash).unwrap();
|
340
|
+
println!("{:?}", hash);
|
341
|
+
let _hash: Output = serde_mcf::from_str(&hash).unwrap();
|
342
|
+
println!("{:?}", _hash);
|
343
|
+
assert!(verify_password(&hash, password));
|
344
|
+
}
|
345
|
+
|
346
|
+
#[test]
|
347
|
+
fn verify_update() {
|
348
|
+
let password = "hunter2";
|
349
|
+
|
350
|
+
let params = Algorithm::Nested {
|
351
|
+
inner: Box::new(Algorithm::default()),
|
352
|
+
outer: DEFAULT_PRIM.clone(),
|
353
|
+
};
|
354
|
+
let hash = params.hash(&password);
|
355
|
+
|
356
|
+
let password = "hunter2";
|
357
|
+
assert!(hash.verify(&password));
|
358
|
+
|
359
|
+
let password = "hunter2";
|
360
|
+
let hash = serde_mcf::to_string(&hash).unwrap();
|
361
|
+
assert!(verify_password(&hash, password));
|
362
|
+
}
|
363
|
+
|
364
|
+
#[test]
|
365
|
+
fn migrate() {
|
366
|
+
let password = "hunter2";
|
367
|
+
|
368
|
+
let params = Algorithm::Single(Bcrypt::default());
|
369
|
+
let mut hash = serde_mcf::to_string(¶ms.hash(&password)).unwrap();
|
370
|
+
println!("Original: {:?}", hash);
|
371
|
+
migrate_hash(&mut hash);
|
372
|
+
println!("Migrated: {:?}", hash);
|
373
|
+
|
374
|
+
let password = "hunter2";
|
375
|
+
assert!(verify_password(&hash, password));
|
376
|
+
|
377
|
+
let password = "hunter2";
|
378
|
+
assert!(verify_password_update_hash(&mut hash, password));
|
379
|
+
println!("Updated: {:?}", hash);
|
380
|
+
|
381
|
+
|
382
|
+
let password = "hunter2";
|
383
|
+
let mut pwd_hash: Output = serde_mcf::from_str(&hash).unwrap();
|
384
|
+
pwd_hash.alg = Algorithm::default();
|
385
|
+
assert!(pwd_hash.verify(&password));
|
386
|
+
}
|
387
|
+
|
388
|
+
#[test]
|
389
|
+
fn handles_broken_hashes() {
|
390
|
+
// base hash: $$scrypt$ln=14,r=8,p=1$Yw/fI4D7b2PNqpUCg5UzKA$kp6humqf/GUV+6HQ/jND3gd8Zoz4VyBgGqk4DHt+k5c
|
391
|
+
let password = "hunter2";
|
392
|
+
|
393
|
+
// Missing param
|
394
|
+
let hash = "$$scrypt$ln=14p=1$Yw/fI4D7b2PNqpUCg5UzKA$kp6humqf/GUV+6HQ/jND3gd8Zoz4VyBgGqk4DHt+k5c";
|
395
|
+
assert!(!verify_password(&hash, password));
|
396
|
+
|
397
|
+
// Incorrect hash-id
|
398
|
+
let hash = "$$nocrypt$ln=14p=1$Yw/fI4D7b2PNqpUCg5UzKA$kp6humqf/GUV+6HQ/jND3gd8Zoz4VyBgGqk4DHt+k5c";
|
399
|
+
assert!(!verify_password(&hash, password));
|
400
|
+
|
401
|
+
// Missing salt
|
402
|
+
let hash = "$$scrypt$ln=14p=1$$kp6humqf/GUV+6HQ/jND3gd8Zoz4VyBgGqk4DHt+k5c";
|
403
|
+
assert!(!verify_password(&hash, password));
|
404
|
+
|
405
|
+
// Incorrect number of fields
|
406
|
+
let hash = "$$scrypt$ln=14p=1$kp6humqf/GUV+6HQ/jND3gd8Zoz4VyBgGqk4DHt+k5c";
|
407
|
+
assert!(!verify_password(&hash, password));
|
408
|
+
|
409
|
+
// Truncated hash
|
410
|
+
let hash = "$$scrypt$ln=14,r=8,\
|
411
|
+
p=1$Yw/fI4D7b2PNqpUCg5UzKA$kp6humqf/GUV+6HQ/jND3gd8Zoz4VyBgGqk4DHt";
|
412
|
+
assert!(!verify_password(&hash, password));
|
413
|
+
|
414
|
+
// Extended hash
|
415
|
+
let hash = "$$scrypt$ln=14,r=8,\
|
416
|
+
p=1$Yw/fI4D7b2PNqpUCg5UzKA$kp6humqf/GUV+6HQ/jND3gd8Zoz4VyBgGqk4DHt+k5cAAAA";
|
417
|
+
assert!(!verify_password(&hash, password));
|
418
|
+
}
|
419
|
+
|
420
|
+
#[test]
|
421
|
+
fn migrate_hash_ok() {
|
422
|
+
let mut hash = "$2a$10$175ikf/E6E.73e83.fJRbODnYWBwmfS0ENdzUBZbedUNGO.99wJfa".to_owned();
|
423
|
+
migrate_hash(&mut hash);
|
424
|
+
}
|
425
|
+
|
426
|
+
#[test]
|
427
|
+
fn hash_and_key() {
|
428
|
+
let password = "hunter2";
|
429
|
+
|
430
|
+
let alg = Algorithm::Single(Bcrypt::default()).into_wrapped(Hmac::default().into());
|
431
|
+
let hash = serde_mcf::to_string(&alg.hash(&password)).unwrap();
|
432
|
+
assert!(verify_password(&hash, password));
|
433
|
+
}
|
434
|
+
|
435
|
+
use std::result;
|
436
|
+
use std::marker::{Send, Sync};
|
437
|
+
|
438
|
+
struct NoRandomness;
|
439
|
+
static NO_RAND_REF: &'static (SecureRandom + Send + Sync) = &NoRandomness;
|
440
|
+
impl SecureRandom for NoRandomness {
|
441
|
+
fn fill(&self, _dest: &mut [u8]) -> result::Result<(), ring::error::Unspecified> {
|
442
|
+
Err(ring::error::Unspecified)
|
443
|
+
}
|
444
|
+
}
|
445
|
+
|
446
|
+
#[test]
|
447
|
+
fn no_randomness_ok() {
|
448
|
+
use std::mem;
|
449
|
+
|
450
|
+
// Using a broken PRNG still results in distinct salts
|
451
|
+
let salt1 = ::gen_salt(&NoRandomness);
|
452
|
+
let salt2 = ::gen_salt(&NoRandomness);
|
453
|
+
assert!(salt1 != salt2);
|
454
|
+
|
455
|
+
|
456
|
+
#[allow(unsafe_code)]
|
457
|
+
unsafe {
|
458
|
+
// We break the PRNG by replacing it with one which always fails!
|
459
|
+
let rng = mem::transmute::<*const Sod<SecureRandom + Send + Sync>, *mut Sod<SecureRandom + Send + Sync>>(&*config::RANDOMNESS_SOURCE);
|
460
|
+
*rng = Sod::Static(NO_RAND_REF);
|
461
|
+
}
|
462
|
+
|
463
|
+
// Yet two passwords differ
|
464
|
+
let hash1 = hash_password("hunter2");
|
465
|
+
let hash2 = hash_password("hunter2");
|
466
|
+
assert!(hash1 != hash2);
|
467
|
+
}
|
468
|
+
}
|