askar_storage/protect/kdf/
mod.rs

1//! Key derivations
2
3use super::store_key::{StoreKey, PREFIX_KDF};
4use crate::{
5    crypto::{buffer::ArrayKey, generic_array::ArrayLength},
6    error::Error,
7    options::Options,
8};
9
10mod argon2;
11pub use self::argon2::Level as Argon2Level;
12use self::argon2::SaltSize as Argon2Salt;
13
14pub const METHOD_ARGON2I: &str = "argon2i";
15
16/// Supported KDF methods for generating or referencing a store key
17#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
18pub enum KdfMethod {
19    /// Argon2i derivation method
20    Argon2i(Argon2Level),
21}
22
23impl KdfMethod {
24    pub(crate) fn decode(method: &str) -> Result<(Self, String), Error> {
25        let mut method_and_detail = method.splitn(3, ':');
26        let prefix = method_and_detail.next();
27        if prefix == Some(PREFIX_KDF) {
28            let method = method_and_detail.next().unwrap_or_default();
29            let mut level_and_detail = method_and_detail.next().unwrap_or_default().splitn(2, '?');
30            let level = level_and_detail.next().unwrap_or_default();
31            let detail = level_and_detail.next().unwrap_or_default();
32            if method == METHOD_ARGON2I {
33                if let Some(level) = Argon2Level::from_str(level) {
34                    return Ok((
35                        Self::Argon2i(level),
36                        if detail.is_empty() {
37                            "".to_owned()
38                        } else {
39                            format!("?{}", detail)
40                        },
41                    ));
42                }
43            }
44        }
45        Err(err_msg!(Unsupported, "Invalid key derivation method"))
46    }
47
48    pub(crate) fn encode(&self, detail: Option<&str>) -> String {
49        match self {
50            Self::Argon2i(level) => format!(
51                "{}:{}:{}{}",
52                PREFIX_KDF,
53                METHOD_ARGON2I,
54                level.as_str(),
55                detail.unwrap_or_default()
56            ),
57        }
58    }
59
60    pub(crate) fn derive_new_key(&self, password: &str) -> Result<(StoreKey, String), Error> {
61        match self {
62            Self::Argon2i(level) => {
63                let salt = level.generate_salt();
64                let key = level.derive_key(password.as_bytes(), salt.as_ref())?;
65                let detail = format!("?salt={}", salt.as_hex());
66                Ok((key, detail))
67            }
68        }
69    }
70
71    pub(crate) fn derive_key(&self, password: &str, detail: &str) -> Result<StoreKey, Error> {
72        match self {
73            Self::Argon2i(level) => {
74                let salt = parse_salt::<Argon2Salt>(detail)?;
75                let key = level.derive_key(password.as_bytes(), salt.as_ref())?;
76                Ok(key)
77            }
78        }
79    }
80}
81
82fn parse_salt<L: ArrayLength<u8>>(detail: &str) -> Result<ArrayKey<L>, Error> {
83    let opts = Options::parse_uri(detail)?;
84    if let Some(salt) = opts.query.get("salt") {
85        ArrayKey::<L>::try_new_with(|arr| {
86            hex::decode_to_slice(salt, arr).map_err(|_| err_msg!(Input, "Invalid salt"))
87        })
88    } else {
89        Err(err_msg!(Input, "Missing salt"))
90    }
91}