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
use super::store_key::{StoreKey, PREFIX_KDF};
use crate::{
    crypto::{buffer::ArrayKey, generic_array::ArrayLength},
    error::Error,
    storage::Options,
};

mod argon2;
pub use self::argon2::Level as Argon2Level;
use self::argon2::SaltSize as Argon2Salt;

pub const METHOD_ARGON2I: &str = "argon2i";

/// Supported KDF methods for generating or referencing a store key
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum KdfMethod {
    /// Argon2i derivation method
    Argon2i(Argon2Level),
}

impl KdfMethod {
    pub(crate) fn decode(method: &str) -> Result<(Self, String), Error> {
        let mut method_and_detail = method.splitn(3, ':');
        let prefix = method_and_detail.next();
        if prefix == Some(PREFIX_KDF) {
            let method = method_and_detail.next().unwrap_or_default();
            let mut level_and_detail = method_and_detail.next().unwrap_or_default().splitn(2, '?');
            let level = level_and_detail.next().unwrap_or_default();
            let detail = level_and_detail.next().unwrap_or_default();
            if method == METHOD_ARGON2I {
                if let Some(level) = Argon2Level::from_str(level) {
                    return Ok((
                        Self::Argon2i(level),
                        if detail.is_empty() {
                            "".to_owned()
                        } else {
                            format!("?{}", detail)
                        },
                    ));
                }
            }
        }
        Err(err_msg!(Unsupported, "Invalid key derivation method"))
    }

    pub(crate) fn encode(&self, detail: Option<&str>) -> String {
        match self {
            Self::Argon2i(level) => format!(
                "{}:{}:{}{}",
                PREFIX_KDF,
                METHOD_ARGON2I,
                level.as_str(),
                detail.unwrap_or_default()
            ),
        }
    }

    pub(crate) fn derive_new_key(&self, password: &str) -> Result<(StoreKey, String), Error> {
        match self {
            Self::Argon2i(level) => {
                let salt = level.generate_salt();
                let key = level.derive_key(password.as_bytes(), salt.as_ref())?;
                let detail = format!("?salt={}", salt.as_hex());
                Ok((key, detail))
            }
        }
    }

    pub(crate) fn derive_key(&self, password: &str, detail: &str) -> Result<StoreKey, Error> {
        match self {
            Self::Argon2i(level) => {
                let salt = parse_salt::<Argon2Salt>(detail)?;
                let key = level.derive_key(password.as_bytes(), salt.as_ref())?;
                Ok(key)
            }
        }
    }
}

fn parse_salt<L: ArrayLength<u8>>(detail: &str) -> Result<ArrayKey<L>, Error> {
    let opts = Options::parse_uri(detail)?;
    if let Some(salt) = opts.query.get("salt") {
        ArrayKey::<L>::try_new_with(|arr| {
            hex::decode_to_slice(salt, arr).map_err(|_| err_msg!(Input, "Invalid salt"))
        })
    } else {
        Err(err_msg!(Input, "Missing salt"))
    }
}