sos_core/crypto/
key_derivation.rs1use crate::{
3 crypto::{csprng, DerivedPrivateKey},
4 Error, Result,
5};
6use argon2::{
7 password_hash::{PasswordHash, PasswordHasher, SaltString},
8 Argon2,
9};
10use balloon_hash::Balloon;
11use rand::Rng;
12use secrecy::{ExposeSecret, SecretString};
13use serde::{Deserialize, Serialize};
14use serde_with::{base64::Base64, serde_as};
15use sha2::{Digest, Sha256};
16use std::{convert::AsRef, fmt, str::FromStr};
17
18pub(crate) const ARGON_2_ID: u8 = 1;
20pub(crate) const BALLOON_HASH: u8 = 2;
22
23#[serde_as]
25#[derive(Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq)]
26pub struct Seed(#[serde_as(as = "Base64")] pub [u8; Seed::SIZE]);
27
28impl Seed {
29 pub const SIZE: usize = 32;
31}
32
33impl AsRef<[u8]> for Seed {
34 fn as_ref(&self) -> &[u8] {
35 &self.0
36 }
37}
38
39#[derive(Debug, Hash, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)]
41pub enum KeyDerivation {
42 Argon2Id,
44 BalloonHash,
46}
47
48impl KeyDerivation {
49 pub fn deriver(&self) -> Box<dyn Deriver<Sha256> + Send + 'static> {
51 match self {
52 KeyDerivation::Argon2Id => Box::new(Argon2IdDeriver),
53 KeyDerivation::BalloonHash => Box::new(BalloonHashDeriver),
54 }
55 }
56
57 pub fn generate_salt() -> SaltString {
59 SaltString::generate(&mut csprng())
60 }
61
62 pub fn parse_salt<S: AsRef<str>>(salt: S) -> Result<SaltString> {
64 Ok(SaltString::from_b64(salt.as_ref())?)
65 }
66
67 #[deprecated]
69 pub fn generate_seed() -> Seed {
70 let bytes: [u8; Seed::SIZE] = csprng().gen();
71 Seed(bytes)
72 }
73}
74
75impl Default for KeyDerivation {
76 fn default() -> Self {
77 Self::Argon2Id
78 }
79}
80
81impl fmt::Display for KeyDerivation {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 write!(f, "{}", {
84 match self {
85 Self::Argon2Id => "argon_2_id",
86 Self::BalloonHash => "balloon_hash",
87 }
88 })
89 }
90}
91
92impl FromStr for KeyDerivation {
93 type Err = Error;
94
95 fn from_str(s: &str) -> Result<Self> {
96 match s {
97 "argon_2_id" => Ok(Self::Argon2Id),
98 "balloon_hash" => Ok(Self::BalloonHash),
99 _ => Err(Error::InvalidKeyDerivation(s.to_string())),
100 }
101 }
102}
103
104impl From<&KeyDerivation> for u8 {
105 fn from(value: &KeyDerivation) -> Self {
106 match value {
107 KeyDerivation::Argon2Id => ARGON_2_ID,
108 KeyDerivation::BalloonHash => BALLOON_HASH,
109 }
110 }
111}
112
113impl TryFrom<u8> for KeyDerivation {
114 type Error = Error;
115 fn try_from(value: u8) -> std::result::Result<Self, Self::Error> {
116 Ok(match value {
117 ARGON_2_ID => KeyDerivation::Argon2Id,
118 BALLOON_HASH => KeyDerivation::BalloonHash,
119 _ => return Err(Error::InvalidKeyDerivation(value.to_string())),
120 })
121 }
122}
123
124pub trait Deriver<D: Digest> {
126 fn hash_password<'a>(
128 &self,
129 password: &[u8],
130 salt: &'a SaltString,
131 ) -> Result<PasswordHash<'a>>;
132
133 fn derive(
136 &self,
137 password: &SecretString,
138 salt: &SaltString,
139 seed: Option<&Seed>,
140 ) -> Result<DerivedPrivateKey> {
141 let buffer = if let Some(seed) = seed {
142 let mut buffer = password.expose_secret().as_bytes().to_vec();
143 buffer.extend_from_slice(seed.as_ref());
144 buffer
145 } else {
146 password.expose_secret().as_bytes().to_vec()
147 };
148
149 let password_hash = self.hash_password(buffer.as_slice(), salt)?;
150 let password_hash_string = password_hash.serialize();
151 let hash = D::digest(password_hash_string.as_bytes());
152 Ok(DerivedPrivateKey::new(secrecy::SecretBox::new(
153 hash.as_slice().to_vec().into(),
154 )))
155 }
156}
157
158pub struct Argon2IdDeriver;
161
162impl Deriver<Sha256> for Argon2IdDeriver {
163 fn hash_password<'a>(
164 &self,
165 password: &[u8],
166 salt: &'a SaltString,
167 ) -> Result<PasswordHash<'a>> {
168 let argon2 = Argon2::default();
169 Ok(argon2.hash_password(password, salt)?)
170 }
171}
172
173pub struct BalloonHashDeriver;
176
177impl Deriver<Sha256> for BalloonHashDeriver {
178 fn hash_password<'a>(
179 &self,
180 password: &[u8],
181 salt: &'a SaltString,
182 ) -> Result<PasswordHash<'a>> {
183 let balloon = Balloon::<Sha256>::default();
184 Ok(balloon.hash_password(password, salt)?)
185 }
186}