Skip to main content

oo7/crypto/
native.rs

1use std::{
2    ops::{Mul, Rem, Shr},
3    sync::LazyLock,
4};
5
6use cbc::cipher::{
7    BlockModeDecrypt, BlockModeEncrypt, BlockSizeUser, IvSizeUser, KeyInit, KeyIvInit, KeySizeUser,
8    block_padding::{NoPadding, Pkcs7},
9};
10use hkdf::Hkdf;
11use md5::Md5;
12use num::{FromPrimitive, Integer, One, Zero};
13use num_bigint_dig::BigUint;
14use pbkdf2::hmac::{
15    Mac,
16    digest::{Digest, FixedOutput, Output, OutputSizeUser},
17};
18use sha2::Sha256;
19use subtle::ConstantTimeEq;
20use zeroize::{Zeroize, Zeroizing};
21
22use crate::{Key, file};
23
24type EncAlg = cbc::Encryptor<aes::Aes128>;
25type DecAlg = cbc::Decryptor<aes::Aes128>;
26type MacAlg = pbkdf2::hmac::Hmac<sha2::Sha256>;
27
28pub fn encrypt(
29    data: impl AsRef<[u8]>,
30    key: &Key,
31    iv: impl AsRef<[u8]>,
32) -> Result<Vec<u8>, super::Error> {
33    let mut blob = vec![0; data.as_ref().len() + EncAlg::block_size()];
34
35    // Unwrapping since adding `CIPHER_BLOCK_SIZE` to array is enough space for
36    // PKCS7
37    let encrypted = EncAlg::new_from_slices(key.as_ref(), iv.as_ref())
38        .expect("Invalid key length")
39        .encrypt_padded_b2b::<Pkcs7>(data.as_ref(), &mut blob)?;
40
41    Ok(encrypted.to_vec())
42}
43
44pub fn decrypt(
45    blob: impl AsRef<[u8]>,
46    key: &Key,
47    iv: impl AsRef<[u8]>,
48) -> Result<Zeroizing<Vec<u8>>, super::Error> {
49    let mut data = blob.as_ref().to_vec();
50
51    let decrypted = DecAlg::new_from_slices(key.as_ref(), iv.as_ref())
52        .expect("Invalid key length")
53        .decrypt_padded::<Pkcs7>(&mut data)?;
54
55    Ok(decrypted.to_vec().into())
56}
57
58pub(crate) fn decrypt_no_padding(
59    blob: impl AsRef<[u8]>,
60    key: &Key,
61    iv: impl AsRef<[u8]>,
62) -> Result<Zeroizing<Vec<u8>>, super::Error> {
63    let mut data = blob.as_ref().to_vec();
64
65    let decrypted = DecAlg::new_from_slices(key.as_ref(), iv.as_ref())
66        .expect("Invalid key length")
67        .decrypt_padded::<NoPadding>(&mut data)?;
68
69    Ok(decrypted.to_vec().into())
70}
71
72pub(crate) fn iv_len() -> usize {
73    DecAlg::iv_size()
74}
75
76pub(crate) fn generate_private_key() -> Result<Zeroizing<Vec<u8>>, super::Error> {
77    let mut key = vec![0u8; EncAlg::key_size()];
78    getrandom::fill(&mut key)?;
79    Ok(Zeroizing::new(key))
80}
81
82pub(crate) fn generate_public_key(private_key: impl AsRef<[u8]>) -> Result<Vec<u8>, super::Error> {
83    let private_key_uint = BigUint::from_bytes_be(private_key.as_ref());
84    static DH_GENERATOR: LazyLock<BigUint> = LazyLock::new(|| BigUint::from_u64(0x2).unwrap());
85    let public_key_uint = powm(&DH_GENERATOR, private_key_uint);
86
87    Ok(public_key_uint.to_bytes_be())
88}
89
90pub(crate) fn generate_aes_key(
91    private_key: impl AsRef<[u8]>,
92    server_public_key: impl AsRef<[u8]>,
93) -> Result<Zeroizing<Vec<u8>>, super::Error> {
94    let server_public_key_uint = BigUint::from_bytes_be(server_public_key.as_ref());
95    let private_key_uint = BigUint::from_bytes_be(private_key.as_ref());
96    let common_secret = powm(&server_public_key_uint, private_key_uint);
97
98    let mut common_secret_bytes = common_secret.to_bytes_be();
99    let mut common_secret_padded = vec![0; 128 - common_secret_bytes.len()];
100    // inefficient, but ok for now
101    common_secret_padded.append(&mut common_secret_bytes);
102
103    // hkdf
104    // input_keying_material
105    let ikm = common_secret_padded;
106    let salt = None;
107    let info = [];
108
109    // output keying material
110    let mut okm = Zeroizing::new(vec![0; 16]);
111
112    let (_, hk) = Hkdf::<Sha256>::extract(salt, &ikm);
113    hk.expand(&info, okm.as_mut())
114        .expect("hkdf expand should never fail");
115
116    Ok(okm)
117}
118
119pub fn generate_iv() -> Result<Vec<u8>, super::Error> {
120    let mut iv = vec![0u8; EncAlg::iv_size()];
121    getrandom::fill(&mut iv)?;
122    Ok(iv)
123}
124
125pub(crate) fn mac_len() -> usize {
126    MacAlg::output_size()
127}
128
129pub(crate) fn compute_mac(data: impl AsRef<[u8]>, key: &Key) -> Result<crate::Mac, super::Error> {
130    let mut mac = MacAlg::new_from_slice(key.as_ref()).unwrap();
131    mac.update(data.as_ref());
132    Ok(crate::Mac::new(mac.finalize().into_bytes().to_vec()))
133}
134
135pub(crate) fn verify_mac(
136    data: impl AsRef<[u8]>,
137    key: &Key,
138    expected_mac: impl AsRef<[u8]>,
139) -> Result<bool, super::Error> {
140    let mut mac = MacAlg::new_from_slice(key.as_ref()).unwrap();
141    mac.update(data.as_ref());
142    Ok(mac.verify_slice(expected_mac.as_ref()).is_ok())
143}
144
145pub(crate) fn verify_checksum_md5(digest: impl AsRef<[u8]>, content: impl AsRef<[u8]>) -> bool {
146    let mut hasher = Md5::new();
147    hasher.update(content.as_ref());
148    hasher.finalize_fixed().ct_eq(digest.as_ref()).into()
149}
150
151pub(crate) fn derive_key(
152    secret: impl AsRef<[u8]>,
153    key_strength: Result<(), file::WeakKeyError>,
154    salt: impl AsRef<[u8]>,
155    iteration_count: usize,
156) -> Result<Key, super::Error> {
157    let mut key = Key::new_with_strength(vec![0; EncAlg::block_size()], key_strength);
158
159    pbkdf2::pbkdf2::<pbkdf2::hmac::Hmac<sha2::Sha256>>(
160        secret.as_ref(),
161        salt.as_ref(),
162        iteration_count.try_into().unwrap(),
163        key.as_mut(),
164    )
165    .expect("HMAC can be initialized with any key length");
166
167    Ok(key)
168}
169
170pub(crate) fn legacy_derive_key_and_iv(
171    secret: impl AsRef<[u8]>,
172    key_strength: Result<(), file::WeakKeyError>,
173    salt: impl AsRef<[u8]>,
174    iteration_count: usize,
175) -> Result<(Key, Vec<u8>), super::Error> {
176    let mut buffer = vec![0; EncAlg::key_size() + EncAlg::iv_size()];
177    let mut hasher = Sha256::new();
178    let mut digest_buffer = vec![0; <Sha256 as Digest>::output_size()];
179    #[allow(deprecated)]
180    let digest = Output::<Sha256>::from_mut_slice(digest_buffer.as_mut_slice());
181
182    let mut pos = 0usize;
183
184    loop {
185        hasher.update(secret.as_ref());
186        hasher.update(salt.as_ref());
187        hasher.finalize_into_reset(digest);
188
189        for _ in 1..iteration_count {
190            // We can't pass an instance, the borrow checker
191            // would complain about digest being dropped at the end of
192            // for block
193            #[allow(clippy::needless_borrows_for_generic_args)]
194            hasher.update(&digest);
195            hasher.finalize_into_reset(digest);
196        }
197
198        let to_read = usize::min(digest.len(), buffer.len() - pos);
199        buffer[pos..].copy_from_slice(&digest[..to_read]);
200        pos += to_read;
201
202        if pos == buffer.len() {
203            break;
204        }
205
206        // We can't pass an instance, the borrow checker
207        // would complain about digest being dropped at the end of
208        // loop block
209        #[allow(clippy::needless_borrows_for_generic_args)]
210        hasher.update(&digest);
211    }
212
213    let iv = buffer.split_off(EncAlg::key_size());
214    Ok((Key::new_with_strength(buffer, key_strength), iv))
215}
216
217/// from https://github.com/plietar/librespot/blob/master/core/src/util/mod.rs#L53
218fn powm(base: &BigUint, mut exp: BigUint) -> BigUint {
219    // for key exchange
220    static DH_PRIME: LazyLock<BigUint> = LazyLock::new(|| {
221        BigUint::from_bytes_be(&[
222            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68,
223            0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, 0x29, 0x02, 0x4E, 0x08,
224            0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A,
225            0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B,
226            0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51,
227            0xC2, 0x45, 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9,
228            0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, 0xEE, 0x38,
229            0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6,
230            0x49, 0x28, 0x66, 0x51, 0xEC, 0xE6, 0x53, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
231            0xFF, 0xFF,
232        ])
233    });
234
235    let mut base = base.clone();
236    let mut result: BigUint = One::one();
237
238    while !exp.is_zero() {
239        if exp.is_odd() {
240            result = result.mul(&base).rem(&*DH_PRIME);
241        }
242        exp = exp.shr(1);
243        base = (&base).mul(&base).rem(&*DH_PRIME);
244    }
245    exp.zeroize();
246
247    result
248}