| // Copyright (c) 2017 Martijn Rijkeboer <[email protected]> |
| // |
| // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
| // option. This file may not be copied, modified, or distributed |
| // except according to those terms. |
| |
| use base64; |
| use crate::context::Context; |
| use crate::decoded::Decoded; |
| use crate::error::Error; |
| use crate::result::Result; |
| use crate::variant::Variant; |
| use crate::version::Version; |
| |
| /// Structure containing the options. |
| struct Options { |
| mem_cost: u32, |
| time_cost: u32, |
| parallelism: u32, |
| } |
| |
| /// Gets the base64 encoded length of a byte slice with the specified length. |
| pub fn base64_len(length: u32) -> u32 { |
| let olen = (length / 3) << 2; |
| match length % 3 { |
| 2 => olen + 3, |
| 1 => olen + 2, |
| _ => olen, |
| } |
| } |
| |
| /// Attempts to decode the encoded string slice. |
| pub fn decode_string(encoded: &str) -> Result<Decoded> { |
| let items: Vec<&str> = encoded.split('$').collect(); |
| if items.len() == 6 { |
| decode_empty(items[0])?; |
| let variant = decode_variant(items[1])?; |
| let version = decode_version(items[2])?; |
| let options = decode_options(items[3])?; |
| let salt = base64::decode(items[4])?; |
| let hash = base64::decode(items[5])?; |
| |
| Ok(Decoded { |
| variant, |
| version, |
| mem_cost: options.mem_cost, |
| time_cost: options.time_cost, |
| parallelism: options.parallelism, |
| salt, |
| hash, |
| }) |
| } else if items.len() == 5 { |
| decode_empty(items[0])?; |
| let variant = decode_variant(items[1])?; |
| let options = decode_options(items[2])?; |
| let salt = base64::decode(items[3])?; |
| let hash = base64::decode(items[4])?; |
| |
| Ok(Decoded { |
| variant, |
| version: Version::Version10, |
| mem_cost: options.mem_cost, |
| time_cost: options.time_cost, |
| parallelism: options.parallelism, |
| salt, |
| hash, |
| }) |
| } else { |
| Err(Error::DecodingFail) |
| } |
| } |
| |
| fn decode_empty(str: &str) -> Result<()> { |
| if str == "" { |
| Ok(()) |
| } else { |
| Err(Error::DecodingFail) |
| } |
| } |
| |
| fn decode_options(str: &str) -> Result<Options> { |
| let items: Vec<&str> = str.split(',').collect(); |
| if items.len() == 3 { |
| Ok(Options { |
| mem_cost: decode_option(items[0], "m")?, |
| time_cost: decode_option(items[1], "t")?, |
| parallelism: decode_option(items[2], "p")?, |
| }) |
| } else { |
| Err(Error::DecodingFail) |
| } |
| } |
| |
| fn decode_option(str: &str, name: &str) -> Result<u32> { |
| let items: Vec<&str> = str.split('=').collect(); |
| if items.len() == 2 { |
| if items[0] == name { |
| decode_u32(items[1]) |
| } else { |
| Err(Error::DecodingFail) |
| } |
| } else { |
| Err(Error::DecodingFail) |
| } |
| } |
| |
| fn decode_u32(str: &str) -> Result<u32> { |
| match str.parse() { |
| Ok(i) => Ok(i), |
| Err(_) => Err(Error::DecodingFail), |
| } |
| } |
| |
| fn decode_variant(str: &str) -> Result<Variant> { |
| Variant::from_str(str) |
| } |
| |
| fn decode_version(str: &str) -> Result<Version> { |
| let items: Vec<&str> = str.split('=').collect(); |
| if items.len() == 2 { |
| if items[0] == "v" { |
| Version::from_str(items[1]) |
| } else { |
| Err(Error::DecodingFail) |
| } |
| } else { |
| Err(Error::DecodingFail) |
| } |
| } |
| |
| /// Encodes the hash and context. |
| pub fn encode_string(context: &Context, hash: &[u8]) -> String { |
| format!( |
| "${}$v={}$m={},t={},p={}${}${}", |
| context.config.variant, |
| context.config.version, |
| context.config.mem_cost, |
| context.config.time_cost, |
| context.config.lanes, |
| base64::encode_config(context.salt, base64::STANDARD_NO_PAD), |
| base64::encode_config(hash, base64::STANDARD_NO_PAD), |
| ) |
| } |
| |
| /// Gets the string length of the specified number. |
| pub fn num_len(number: u32) -> u32 { |
| let mut len = 1; |
| let mut num = number; |
| while num >= 10 { |
| len += 1; |
| num /= 10; |
| } |
| len |
| } |
| |
| |
| #[cfg(test)] |
| mod tests { |
| |
| use crate::config::Config; |
| use crate::context::Context; |
| use crate::decoded::Decoded; |
| use crate::encoding::{base64_len, decode_string, encode_string, num_len}; |
| use crate::error::Error; |
| use crate::thread_mode::ThreadMode; |
| use crate::variant::Variant; |
| use crate::version::Version; |
| |
| #[test] |
| fn base64_len_returns_correct_length() { |
| let tests = vec![ |
| (1, 2), |
| (2, 3), |
| (3, 4), |
| (4, 6), |
| (5, 7), |
| (6, 8), |
| (7, 10), |
| (8, 11), |
| (9, 12), |
| (10, 14), |
| ]; |
| for (len, expected) in tests { |
| let actual = base64_len(len); |
| assert_eq!(actual, expected); |
| } |
| } |
| |
| #[test] |
| fn decode_string_with_version10_returns_correct_result() { |
| let encoded = "$argon2i$v=16$m=4096,t=3,p=1\ |
| $c2FsdDEyMzQ=$MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI="; |
| let expected = Decoded { |
| variant: Variant::Argon2i, |
| version: Version::Version10, |
| mem_cost: 4096, |
| time_cost: 3, |
| parallelism: 1, |
| salt: b"salt1234".to_vec(), |
| hash: b"12345678901234567890123456789012".to_vec(), |
| }; |
| let actual = decode_string(encoded).unwrap(); |
| assert_eq!(actual, expected); |
| } |
| |
| #[test] |
| fn decode_string_with_version13_returns_correct_result() { |
| let encoded = "$argon2i$v=19$m=4096,t=3,p=1\ |
| $c2FsdDEyMzQ=$MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI="; |
| let expected = Decoded { |
| variant: Variant::Argon2i, |
| version: Version::Version13, |
| mem_cost: 4096, |
| time_cost: 3, |
| parallelism: 1, |
| salt: b"salt1234".to_vec(), |
| hash: b"12345678901234567890123456789012".to_vec(), |
| }; |
| let actual = decode_string(encoded).unwrap(); |
| assert_eq!(actual, expected); |
| } |
| |
| #[test] |
| fn decode_string_without_version_returns_correct_result() { |
| let encoded = "$argon2i$m=4096,t=3,p=1\ |
| $c2FsdDEyMzQ=$MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI="; |
| let expected = Decoded { |
| variant: Variant::Argon2i, |
| version: Version::Version10, |
| mem_cost: 4096, |
| time_cost: 3, |
| parallelism: 1, |
| salt: b"salt1234".to_vec(), |
| hash: b"12345678901234567890123456789012".to_vec(), |
| }; |
| let actual = decode_string(encoded).unwrap(); |
| assert_eq!(actual, expected); |
| } |
| |
| #[test] |
| fn decode_string_without_variant_returns_error_result() { |
| let encoded = "$m=4096,t=3,p=1\ |
| $c2FsdDEyMzQ=$MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI="; |
| let result = decode_string(encoded); |
| assert_eq!(result, Err(Error::DecodingFail)); |
| } |
| |
| #[test] |
| fn decode_string_with_empty_variant_returns_error_result() { |
| let encoded = "$$m=4096,t=3,p=1\ |
| $c2FsdDEyMzQ=$MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI="; |
| let result = decode_string(encoded); |
| assert_eq!(result, Err(Error::DecodingFail)); |
| } |
| |
| #[test] |
| fn decode_string_with_invalid_variant_returns_error_result() { |
| let encoded = "$argon$m=4096,t=3,p=1\ |
| $c2FsdDEyMzQ=$MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI="; |
| let result = decode_string(encoded); |
| assert_eq!(result, Err(Error::DecodingFail)); |
| } |
| |
| #[test] |
| fn decode_string_without_mem_cost_returns_error_result() { |
| let encoded = "$argon2i$t=3,p=1\ |
| $c2FsdDEyMzQ=$MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI="; |
| let result = decode_string(encoded); |
| assert_eq!(result, Err(Error::DecodingFail)); |
| } |
| |
| #[test] |
| fn decode_string_with_empty_mem_cost_returns_error_result() { |
| let encoded = "$argon2i$m=,t=3,p=1\ |
| $c2FsdDEyMzQ=$MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI="; |
| let result = decode_string(encoded); |
| assert_eq!(result, Err(Error::DecodingFail)); |
| } |
| |
| #[test] |
| fn decode_string_with_non_numeric_mem_cost_returns_error_result() { |
| let encoded = "$argon2i$m=a,t=3,p=1\ |
| $c2FsdDEyMzQ=$MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI="; |
| let result = decode_string(encoded); |
| assert_eq!(result, Err(Error::DecodingFail)); |
| } |
| |
| #[test] |
| fn decode_string_without_time_cost_returns_error_result() { |
| let encoded = "$argon2i$m=4096,p=1\ |
| $c2FsdDEyMzQ=$MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI="; |
| let result = decode_string(encoded); |
| assert_eq!(result, Err(Error::DecodingFail)); |
| } |
| |
| #[test] |
| fn decode_string_with_empty_time_cost_returns_error_result() { |
| let encoded = "$argon2i$m=4096,t=,p=1\ |
| $c2FsdDEyMzQ=$MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI="; |
| let result = decode_string(encoded); |
| assert_eq!(result, Err(Error::DecodingFail)); |
| } |
| |
| #[test] |
| fn decode_string_with_non_numeric_time_cost_returns_error_result() { |
| let encoded = "$argon2i$m=4096,t=a,p=1\ |
| $c2FsdDEyMzQ=$MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI="; |
| let result = decode_string(encoded); |
| assert_eq!(result, Err(Error::DecodingFail)); |
| } |
| |
| #[test] |
| fn decode_string_without_parallelism_returns_error_result() { |
| let encoded = "$argon2i$m=4096,t=3\ |
| $c2FsdDEyMzQ=$MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI="; |
| let result = decode_string(encoded); |
| assert_eq!(result, Err(Error::DecodingFail)); |
| } |
| |
| #[test] |
| fn decode_string_with_empty_parallelism_returns_error_result() { |
| let encoded = "$argon2i$m=4096,t=3,p=\ |
| $c2FsdDEyMzQ=$MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI="; |
| let result = decode_string(encoded); |
| assert_eq!(result, Err(Error::DecodingFail)); |
| } |
| |
| #[test] |
| fn decode_string_with_non_numeric_parallelism_returns_error_result() { |
| let encoded = "$argon2i$m=4096,t=3,p=a\ |
| $c2FsdDEyMzQ=$MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI="; |
| let result = decode_string(encoded); |
| assert_eq!(result, Err(Error::DecodingFail)); |
| } |
| |
| #[test] |
| fn decode_string_without_salt_returns_error_result() { |
| let encoded = "$argon2i$m=4096,t=3,p=1\ |
| $MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI="; |
| let result = decode_string(encoded); |
| assert_eq!(result, Err(Error::DecodingFail)); |
| } |
| |
| #[test] |
| fn decode_string_without_hash_returns_error_result() { |
| let encoded = "$argon2i$m=4096,t=3,p=a\ |
| $c2FsdDEyMzQ="; |
| let result = decode_string(encoded); |
| assert_eq!(result, Err(Error::DecodingFail)); |
| } |
| |
| #[test] |
| fn decode_string_with_empty_hash_returns_error_result() { |
| let encoded = "$argon2i$m=4096,t=3,p=a\ |
| $c2FsdDEyMzQ=$"; |
| let result = decode_string(encoded); |
| assert_eq!(result, Err(Error::DecodingFail)); |
| } |
| |
| #[test] |
| fn encode_string_returns_correct_string() { |
| let hash = b"12345678901234567890123456789012".to_vec(); |
| let config = Config { |
| ad: &[], |
| hash_length: hash.len() as u32, |
| lanes: 1, |
| mem_cost: 4096, |
| secret: &[], |
| thread_mode: ThreadMode::Parallel, |
| time_cost: 3, |
| variant: Variant::Argon2i, |
| version: Version::Version13, |
| }; |
| let pwd = b"password".to_vec(); |
| let salt = b"salt1234".to_vec(); |
| let context = Context::new(config, &pwd, &salt).unwrap(); |
| let expected = "$argon2i$v=19$m=4096,t=3,p=1\ |
| $c2FsdDEyMzQ$MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI"; |
| let actual = encode_string(&context, &hash); |
| assert_eq!(actual, expected); |
| } |
| |
| #[test] |
| fn num_len_returns_correct_length() { |
| let tests = vec![ |
| (1, 1), |
| (10, 2), |
| (110, 3), |
| (1230, 4), |
| (12340, 5), |
| (123457, 6), |
| ]; |
| for (num, expected) in tests { |
| let actual = num_len(num); |
| assert_eq!(actual, expected); |
| } |
| } |
| } |