libpasta 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,284 @@
|
|
1
|
+
//! Serde deserialize for password hashes
|
2
|
+
//!
|
3
|
+
//! The output from a hashing algorithm typically includes: the hash itself,
|
4
|
+
//! the salt, and the parameters used in the hashing.
|
5
|
+
//! This allows us to store the output in an unambigous fashion.
|
6
|
+
//!
|
7
|
+
//! However, not all algorithms had this foresight, and many instead wrote
|
8
|
+
//! simple formats which simply included the hash output and salt concatenated.
|
9
|
+
//!
|
10
|
+
//! This module attempts to deserialize various formats into the `libpasta`
|
11
|
+
//! supported form.
|
12
|
+
|
13
|
+
use hashing::{Algorithm, Output};
|
14
|
+
use primitives::Primitive;
|
15
|
+
|
16
|
+
use serde::{Deserialize, Deserializer};
|
17
|
+
use serde::de::{self, Visitor};
|
18
|
+
use serde::de::Error;
|
19
|
+
use serde_mcf;
|
20
|
+
use serde_mcf::{base64, base64bcrypt, Hashes};
|
21
|
+
|
22
|
+
use std::fmt;
|
23
|
+
|
24
|
+
|
25
|
+
/// Currently supported hashing variants.
|
26
|
+
///
|
27
|
+
/// `Bcrypt`: `$(2a|2b|2x|2y)$<cost>$<salthash>`
|
28
|
+
/// where salthash is a non-standard base64 encoding.
|
29
|
+
/// `Mcf`: `$<alg-id>$<params map>$<salt>$<hash>`
|
30
|
+
/// `Pasta`: `$<MCF-hash>` or `$!<Pasta-hash>` (recursively)
|
31
|
+
#[derive(Debug, PartialEq)]
|
32
|
+
enum SupportedVariants {
|
33
|
+
Bcrypt(Hashes),
|
34
|
+
Mcf(Hashes),
|
35
|
+
Pasta(PastaVariants),
|
36
|
+
}
|
37
|
+
|
38
|
+
/// A Pasta hash is either a sing hash parameterisation, or a recursive
|
39
|
+
/// structure, containing many hash parameters.
|
40
|
+
#[derive(Debug, PartialEq)]
|
41
|
+
enum PastaVariants {
|
42
|
+
Single,
|
43
|
+
Nested,
|
44
|
+
}
|
45
|
+
|
46
|
+
static VAR_STRUCT: [&'static str; 2] = ["variant", "remaining"];
|
47
|
+
|
48
|
+
// The *Fields structs define the layout of the various supported variants,
|
49
|
+
// as detailed above. After parsing the algorithm identifier, one of these
|
50
|
+
// structs are used to attempt to deserialize.
|
51
|
+
|
52
|
+
#[derive(Deserialize)]
|
53
|
+
struct BcryptFields {
|
54
|
+
cost: u32,
|
55
|
+
#[serde(with = "base64bcrypt")]
|
56
|
+
salthash: (Vec<u8>, Vec<u8>),
|
57
|
+
}
|
58
|
+
|
59
|
+
#[derive(Deserialize)]
|
60
|
+
struct McfFields {
|
61
|
+
params: serde_mcf::Map<String, serde_mcf::Value>,
|
62
|
+
#[serde(with = "base64")]
|
63
|
+
pub salt: Vec<u8>,
|
64
|
+
#[serde(with = "base64")]
|
65
|
+
pub hash: Vec<u8>,
|
66
|
+
}
|
67
|
+
|
68
|
+
/// The nested Pasta format is specified by a $<id>$<params> parameterising the
|
69
|
+
/// outer layer hash algorithm, followed by another set of algorithm parameters.
|
70
|
+
/// This inner hash may also a further layer of nested params.
|
71
|
+
#[derive(Deserialize)]
|
72
|
+
struct PastaNest {
|
73
|
+
outer_id: Hashes,
|
74
|
+
outer_params: serde_mcf::Map<String, serde_mcf::Value>,
|
75
|
+
inner: Output,
|
76
|
+
}
|
77
|
+
|
78
|
+
// Deserialize Output using OutputVisitor
|
79
|
+
impl<'de> Deserialize<'de> for Output {
|
80
|
+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
81
|
+
where D: Deserializer<'de>
|
82
|
+
{
|
83
|
+
deserializer.deserialize_struct("var_container", &VAR_STRUCT, OutputVisitor)
|
84
|
+
}
|
85
|
+
}
|
86
|
+
|
87
|
+
/// `OutputVisitor` does most of the heavy lifting of the deserializing.
|
88
|
+
/// First, we use the `SupportedVariants` enum to identify which type of hash
|
89
|
+
/// we are dealing with.
|
90
|
+
/// Second, the remaining values are deserialized into the suitable *Field struct.
|
91
|
+
/// Finally, the fields are unified into the `Output` struct and returned.
|
92
|
+
struct OutputVisitor;
|
93
|
+
impl<'de> Visitor<'de> for OutputVisitor {
|
94
|
+
type Value = Output;
|
95
|
+
|
96
|
+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
97
|
+
formatter.write_str("an identifier")
|
98
|
+
}
|
99
|
+
|
100
|
+
fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
|
101
|
+
where V: de::MapAccess<'de>
|
102
|
+
{
|
103
|
+
|
104
|
+
// The first step is to detect which variant we are dealing with.
|
105
|
+
let _: Option<String> = map.next_key()?;
|
106
|
+
let var: SupportedVariants = map.next_value()?;
|
107
|
+
|
108
|
+
match var {
|
109
|
+
// Deserialize each variant using specific format.
|
110
|
+
// Note that let fields: SomeFields = map.next_value()?;
|
111
|
+
// is automatically calling the deserializer for SomeFields.
|
112
|
+
SupportedVariants::Bcrypt(_) => {
|
113
|
+
let _: Option<String> = map.next_key()?;
|
114
|
+
let fields: BcryptFields = map.next_value()?;
|
115
|
+
let prim = ::primitives::Bcrypt::new(fields.cost);
|
116
|
+
if prim == ::primitives::Poisoned.into() {
|
117
|
+
#[allow(use_debug)]
|
118
|
+
return Err(V::Error::custom(format!("failed to deserialize as {:?}", var)));
|
119
|
+
}
|
120
|
+
Ok(Output {
|
121
|
+
alg: Algorithm::Single(prim),
|
122
|
+
salt: fields.salthash.0,
|
123
|
+
hash: fields.salthash.1,
|
124
|
+
})
|
125
|
+
}
|
126
|
+
SupportedVariants::Mcf(var) => {
|
127
|
+
let _: Option<String> = map.next_key()?;
|
128
|
+
let fields: McfFields = map.next_value()?;
|
129
|
+
let prim = ::primitives::Primitive::from((&var, &fields.params));
|
130
|
+
if prim == ::primitives::Poisoned.into() {
|
131
|
+
#[allow(use_debug)]
|
132
|
+
return Err(V::Error::custom(format!("failed to deserialize as {:?}", var)));
|
133
|
+
}
|
134
|
+
Ok(Output {
|
135
|
+
alg: Algorithm::Single(prim),
|
136
|
+
salt: fields.salt,
|
137
|
+
hash: fields.hash,
|
138
|
+
})
|
139
|
+
}
|
140
|
+
SupportedVariants::Pasta(var) => {
|
141
|
+
match var {
|
142
|
+
PastaVariants::Single => {
|
143
|
+
let _: Option<String> = map.next_key()?;
|
144
|
+
let output: serde_mcf::McfHash = map.next_value()?;
|
145
|
+
let prim = ::primitives::Primitive::from((&output.algorithm,
|
146
|
+
&output.parameters));
|
147
|
+
if prim == ::primitives::Poisoned.into() {
|
148
|
+
#[allow(use_debug)]
|
149
|
+
return Err(V::Error::custom(format!("failed to deserialize as {:?}", var)));
|
150
|
+
}
|
151
|
+
Ok(Output {
|
152
|
+
alg: Algorithm::Single(prim),
|
153
|
+
salt: output.salt,
|
154
|
+
hash: output.hash,
|
155
|
+
})
|
156
|
+
}
|
157
|
+
PastaVariants::Nested => {
|
158
|
+
let _: Option<String> = map.next_key()?;
|
159
|
+
// Note that in this case, PastaNest deserializer is
|
160
|
+
// recursively deserializing PastaVariants until
|
161
|
+
// reaching the end.
|
162
|
+
let fields: PastaNest = map.next_value()?;
|
163
|
+
let prim = ::primitives::Primitive::from((&fields.outer_id,
|
164
|
+
&fields.outer_params));
|
165
|
+
if prim == ::primitives::Poisoned.into() {
|
166
|
+
#[allow(use_debug)]
|
167
|
+
return Err(V::Error::custom(format!("failed to deserialize as {:?}", var)));
|
168
|
+
}
|
169
|
+
Ok(Output {
|
170
|
+
alg: Algorithm::Nested {
|
171
|
+
outer: prim,
|
172
|
+
inner: Box::new(fields.inner.alg.clone()),
|
173
|
+
},
|
174
|
+
salt: fields.inner.salt,
|
175
|
+
hash: fields.inner.hash,
|
176
|
+
})
|
177
|
+
}
|
178
|
+
}
|
179
|
+
}
|
180
|
+
}
|
181
|
+
}
|
182
|
+
}
|
183
|
+
|
184
|
+
impl<'de> Deserialize<'de> for SupportedVariants {
|
185
|
+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
186
|
+
where D: Deserializer<'de>
|
187
|
+
{
|
188
|
+
deserializer.deserialize_identifier(VariantVisitor)
|
189
|
+
|
190
|
+
}
|
191
|
+
}
|
192
|
+
|
193
|
+
/// Visitor to deserialize the `SupportedVariants` enum.
|
194
|
+
/// Currently is able to detect the variant by how the string starts.
|
195
|
+
/// For example, `$$` or `$!$` indicates a Pasta variant, whereas `$2a` would
|
196
|
+
/// be a regular `Bcrypt` hash.
|
197
|
+
struct VariantVisitor;
|
198
|
+
impl<'de> Visitor<'de> for VariantVisitor {
|
199
|
+
type Value = SupportedVariants;
|
200
|
+
|
201
|
+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
202
|
+
formatter.write_str("an identifier")
|
203
|
+
}
|
204
|
+
|
205
|
+
fn visit_borrowed_str<E>(self, val: &str) -> Result<Self::Value, E>
|
206
|
+
where E: Error
|
207
|
+
{
|
208
|
+
let var = match val {
|
209
|
+
"" => SupportedVariants::Pasta(PastaVariants::Single),
|
210
|
+
"!" => SupportedVariants::Pasta(PastaVariants::Nested),
|
211
|
+
var => {
|
212
|
+
let variant = Hashes::from_id(var).ok_or_else(|| {
|
213
|
+
E::custom(format!("unknown MCF variant: {}", var))
|
214
|
+
})?;
|
215
|
+
|
216
|
+
match variant {
|
217
|
+
Hashes::Bcrypt |
|
218
|
+
Hashes::Bcrypta |
|
219
|
+
Hashes::Bcryptx |
|
220
|
+
Hashes::Bcrypty |
|
221
|
+
Hashes::Bcryptb => {
|
222
|
+
SupportedVariants::Bcrypt(variant)
|
223
|
+
},
|
224
|
+
_ => SupportedVariants::Mcf(variant),
|
225
|
+
}
|
226
|
+
}
|
227
|
+
};
|
228
|
+
Ok(var)
|
229
|
+
}
|
230
|
+
}
|
231
|
+
|
232
|
+
// The deserializing of a `Primitive` is used in the nested `Pasta` variants.
|
233
|
+
impl<'de> Deserialize<'de> for Primitive {
|
234
|
+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
235
|
+
where D: Deserializer<'de>
|
236
|
+
{
|
237
|
+
#[derive(Deserialize)]
|
238
|
+
struct PrimitiveStruct {
|
239
|
+
id: Hashes,
|
240
|
+
params: serde_mcf::Map<String, serde_mcf::Value>,
|
241
|
+
}
|
242
|
+
let prim = PrimitiveStruct::deserialize(deserializer)?;
|
243
|
+
let prim = (&prim.id, &prim.params).into();
|
244
|
+
if prim == ::primitives::Poisoned.into() {
|
245
|
+
#[allow(use_debug)]
|
246
|
+
return Err(D::Error::custom("failed to deserialize"));
|
247
|
+
}
|
248
|
+
Ok(prim)
|
249
|
+
}
|
250
|
+
}
|
251
|
+
|
252
|
+
#[cfg(test)]
|
253
|
+
mod test {
|
254
|
+
use serde_mcf;
|
255
|
+
use serde_yaml;
|
256
|
+
use super::*;
|
257
|
+
|
258
|
+
#[test]
|
259
|
+
fn variant_tests() {
|
260
|
+
let variant = "$argon2i";
|
261
|
+
assert_eq!(serde_mcf::from_str::<SupportedVariants>(variant).unwrap(),
|
262
|
+
SupportedVariants::Mcf(Hashes::Argon2i));
|
263
|
+
|
264
|
+
let not_a_variant = "12";
|
265
|
+
assert!(serde_yaml::from_str::<SupportedVariants>(not_a_variant).is_err());
|
266
|
+
}
|
267
|
+
|
268
|
+
#[test]
|
269
|
+
fn hash_tests() {
|
270
|
+
let hash = "$$non-existant$$$";
|
271
|
+
assert!(serde_mcf::from_str::<Output>(hash).is_err());
|
272
|
+
|
273
|
+
let hash = "$argon2i$fake_map=12$salt$hash";
|
274
|
+
assert!(serde_mcf::from_str::<Output>(hash).is_err());
|
275
|
+
}
|
276
|
+
|
277
|
+
#[test]
|
278
|
+
fn de_bcrypt() {
|
279
|
+
let hash = "$2a$10$175ikf/E6E.73e83.fJRbODnYWBwmfS0ENdzUBZbedUNGO.99wJfa";
|
280
|
+
assert!(serde_mcf::from_str::<Output>(hash).is_ok());
|
281
|
+
let broken_hash = "$2a$purple$175ikf/E6E.73e83.fJRbODnYWBwmfS0ENdzUBZbedUNGO.99wJfa";
|
282
|
+
assert!(serde_mcf::from_str::<Output>(broken_hash).is_err());
|
283
|
+
}
|
284
|
+
}
|
@@ -0,0 +1,161 @@
|
|
1
|
+
//! Password hashing functionality
|
2
|
+
//!
|
3
|
+
//! While the `primitives` module handles the raw implementations of hashing
|
4
|
+
//! algorithms, this module contains the `libpasta` hashing functionality
|
5
|
+
//! itself. In particular, a `libpasta` hashing `Algorithm` is defined as a
|
6
|
+
//! recursive structure, containing either a single `Primitive`, or a
|
7
|
+
//! `Primitive` and a further layer of `Algorithm`. This is the hashing onion.
|
8
|
+
|
9
|
+
use std::default::Default;
|
10
|
+
|
11
|
+
use config;
|
12
|
+
use primitives::Primitive;
|
13
|
+
|
14
|
+
mod de;
|
15
|
+
mod ser;
|
16
|
+
|
17
|
+
#[derive(Clone, Debug, PartialEq)]
|
18
|
+
/// `libpasta` password hashing algorithms can be nested, which is captured
|
19
|
+
/// by this recursive enum.
|
20
|
+
pub enum Algorithm {
|
21
|
+
/// A single instance of a password-hashing primitive.
|
22
|
+
Single(Primitive),
|
23
|
+
/// The password-hashing algorithm is composed of nested primitives.
|
24
|
+
Nested {
|
25
|
+
/// The outermost layer of the algorithm is a single primitive
|
26
|
+
outer: Primitive,
|
27
|
+
/// The rest of the layers
|
28
|
+
inner: Box<Algorithm>,
|
29
|
+
},
|
30
|
+
}
|
31
|
+
|
32
|
+
|
33
|
+
#[derive(Debug)]
|
34
|
+
/// Represents the output of a password hashing algorithm.
|
35
|
+
pub struct Output {
|
36
|
+
/// The algorithm used
|
37
|
+
pub alg: Algorithm,
|
38
|
+
/// The salt
|
39
|
+
pub salt: Vec<u8>,
|
40
|
+
/// The hash output
|
41
|
+
pub hash: Vec<u8>,
|
42
|
+
}
|
43
|
+
|
44
|
+
|
45
|
+
impl Default for Algorithm {
|
46
|
+
fn default() -> Self {
|
47
|
+
config::DEFAULT_ALG.clone()
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
impl Output {
|
52
|
+
/// Verifies that the supplied password matches the hashed value.
|
53
|
+
pub fn verify(&self, password: &str) -> bool {
|
54
|
+
self.alg.verify(password.as_bytes(), &self.salt, &self.hash)
|
55
|
+
}
|
56
|
+
|
57
|
+
pub(crate) fn check_keys(&mut self, config: &config::Config) {
|
58
|
+
self.alg.update_key(config);
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
impl Algorithm {
|
63
|
+
/// Type-safe function to compute the hash of a password.
|
64
|
+
pub fn hash(&self, password: &str) -> Output {
|
65
|
+
let salt = super::gen_salt(&**config::RANDOMNESS_SOURCE);
|
66
|
+
let output = self.hash_with_salt(password.as_bytes(), &salt);
|
67
|
+
Output {
|
68
|
+
hash: output,
|
69
|
+
salt: salt,
|
70
|
+
alg: self.clone(),
|
71
|
+
}
|
72
|
+
}
|
73
|
+
|
74
|
+
/// Computes the hash output for given password and salt.
|
75
|
+
pub fn hash_with_salt(&self, password: &[u8], salt: &[u8]) -> Vec<u8> {
|
76
|
+
match *self {
|
77
|
+
Algorithm::Single(ref p) => p.compute(password, salt),
|
78
|
+
Algorithm::Nested { ref inner, ref outer } => {
|
79
|
+
let innerput = inner.hash_with_salt(password, salt);
|
80
|
+
outer.compute(&innerput, salt)
|
81
|
+
}
|
82
|
+
}
|
83
|
+
}
|
84
|
+
|
85
|
+
/// Verifies the password, salt and hash are matching by recursively
|
86
|
+
/// re-computing the hash and verifying the final value.
|
87
|
+
pub fn verify(&self, password: &[u8], salt: &[u8], hash: &[u8]) -> bool {
|
88
|
+
match *self {
|
89
|
+
Algorithm::Single(ref p) => p.verify(password, salt, hash),
|
90
|
+
Algorithm::Nested { ref inner, ref outer } => {
|
91
|
+
let innerput = inner.hash_with_salt(password, salt);
|
92
|
+
outer.verify(&innerput, salt, hash)
|
93
|
+
}
|
94
|
+
}
|
95
|
+
}
|
96
|
+
|
97
|
+
/// Test whether the current 'Algorithm` is sufficiently secure.
|
98
|
+
pub fn needs_migrating(&self) -> bool {
|
99
|
+
let default: &Primitive = &*config::DEFAULT_PRIM;
|
100
|
+
|
101
|
+
match *self {
|
102
|
+
Algorithm::Single(ref a2) |
|
103
|
+
Algorithm::Nested { outer: ref a2, .. } => a2.ge(default),
|
104
|
+
}
|
105
|
+
}
|
106
|
+
|
107
|
+
/// Copies `self` into a new `Algorithm` wrapped by `outer`
|
108
|
+
pub fn to_wrapped(&self, outer: Primitive) -> Self {
|
109
|
+
Algorithm::Nested {
|
110
|
+
outer: outer,
|
111
|
+
inner: Box::new(self.clone()),
|
112
|
+
}
|
113
|
+
}
|
114
|
+
|
115
|
+
/// Moves `self` into a new `Algorithm` wrapped by `outer`
|
116
|
+
pub fn into_wrapped(self, outer: Primitive) -> Self {
|
117
|
+
Algorithm::Nested {
|
118
|
+
outer: outer,
|
119
|
+
inner: Box::new(self),
|
120
|
+
}
|
121
|
+
}
|
122
|
+
|
123
|
+
pub(crate) fn update_key(&mut self, config: &config::Config) {
|
124
|
+
match *self {
|
125
|
+
Algorithm::Single(ref mut p) => {
|
126
|
+
if let Some(newp) = p.update_key(config) {
|
127
|
+
*p = newp;
|
128
|
+
}
|
129
|
+
},
|
130
|
+
Algorithm::Nested { ref mut inner, ref mut outer } => {
|
131
|
+
inner.update_key(config);
|
132
|
+
// outer.update_key(config).and_then(|new_outer| *outer = new_outer);
|
133
|
+
if let Some(newp) = outer.update_key(config) {
|
134
|
+
*outer = newp;
|
135
|
+
}
|
136
|
+
|
137
|
+
}
|
138
|
+
}
|
139
|
+
}
|
140
|
+
}
|
141
|
+
|
142
|
+
#[cfg(test)]
|
143
|
+
mod test {
|
144
|
+
use super::*;
|
145
|
+
use serde_mcf;
|
146
|
+
|
147
|
+
#[test]
|
148
|
+
fn test_hash() {
|
149
|
+
let alg = Algorithm::default();
|
150
|
+
let output = alg.hash(&"hunter2");
|
151
|
+
println!("{:?}", serde_mcf::to_string(&output).unwrap());
|
152
|
+
}
|
153
|
+
|
154
|
+
#[test]
|
155
|
+
fn test_wrapped() {
|
156
|
+
let alg = Algorithm::default();
|
157
|
+
let prim = &*config::DEFAULT_PRIM;
|
158
|
+
let _alg1 = alg.to_wrapped(prim.clone());
|
159
|
+
let _alg = alg.into_wrapped(prim.clone());
|
160
|
+
}
|
161
|
+
}
|
@@ -0,0 +1,67 @@
|
|
1
|
+
/// Serialization of password hashes using serde.
|
2
|
+
///
|
3
|
+
/// Compared to the complext deserialization logic, this is comparatively
|
4
|
+
/// simpler, since we only support serializing to our own standardised format.
|
5
|
+
///
|
6
|
+
/// In practice, `serde_mcf` will be used to produce the serialized output.
|
7
|
+
/// However, this same structure can help to produce configuration files.
|
8
|
+
|
9
|
+
use hashing::{Algorithm, Output};
|
10
|
+
use primitives::Primitive;
|
11
|
+
|
12
|
+
use serde::{Serialize, Serializer};
|
13
|
+
use serde::ser::{SerializeStruct, SerializeStructVariant};
|
14
|
+
use serde_mcf;
|
15
|
+
use serde_mcf::{Hashes, Map, Value};
|
16
|
+
|
17
|
+
#[derive(Serialize)]
|
18
|
+
struct Base64Encoded<'a>(#[serde(with="serde_mcf::base64")]
|
19
|
+
&'a [u8]);
|
20
|
+
|
21
|
+
impl Serialize for Output {
|
22
|
+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
23
|
+
where S: Serializer
|
24
|
+
{
|
25
|
+
let mut state = serializer.serialize_struct("Output", 2)?;
|
26
|
+
state.serialize_field("algorithm", &self.alg)?;
|
27
|
+
state.serialize_field("salt", &Base64Encoded(&self.salt))?;
|
28
|
+
state.serialize_field("hash", &Base64Encoded(&self.hash))?;
|
29
|
+
state.end()
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
impl<'a> Serialize for Algorithm {
|
34
|
+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
35
|
+
where S: Serializer
|
36
|
+
{
|
37
|
+
match *self {
|
38
|
+
Algorithm::Single(ref alg) => {
|
39
|
+
let mut state = serializer.serialize_struct_variant("algorithm", 0, "", 2)?;
|
40
|
+
let (algorithm, params): (Hashes, Map<String, Value>) = alg.into();
|
41
|
+
state.serialize_field("id", &algorithm)?;
|
42
|
+
state.serialize_field("params", ¶ms)?;
|
43
|
+
state.end()
|
44
|
+
}
|
45
|
+
Algorithm::Nested { ref outer, ref inner } => {
|
46
|
+
let mut state = serializer.serialize_struct_variant("algorithm", 1, "!", 3)?;
|
47
|
+
let (algorithm, params): (Hashes, Map<String, Value>) = outer.into();
|
48
|
+
state.serialize_field("outer_id", &algorithm)?;
|
49
|
+
state.serialize_field("outer_params", ¶ms)?;
|
50
|
+
state.serialize_field("inner", &inner)?;
|
51
|
+
state.end()
|
52
|
+
}
|
53
|
+
}
|
54
|
+
}
|
55
|
+
}
|
56
|
+
|
57
|
+
impl<'a> Serialize for Primitive {
|
58
|
+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
59
|
+
where S: Serializer
|
60
|
+
{
|
61
|
+
let mut state = serializer.serialize_struct("primitive", 2)?;
|
62
|
+
let (algorithm, params): (Hashes, Map<String, Value>) = self.into();
|
63
|
+
state.serialize_field("id", &algorithm)?;
|
64
|
+
state.serialize_field("params", ¶ms)?;
|
65
|
+
state.end()
|
66
|
+
}
|
67
|
+
}
|