Skip to main content

oracledb_protocol/
crypto.rs

1#![forbid(unsafe_code)]
2
3use aes::cipher::{BlockDecrypt, BlockEncrypt, KeyInit};
4use aes::Aes256;
5use hex::FromHex;
6use md5::Md5;
7use pbkdf2::pbkdf2_hmac;
8use rand::rngs::OsRng;
9use rand::RngCore;
10use sha1::Sha1;
11use sha2::{Digest, Sha512};
12
13use crate::thin::{TNS_VERIFIER_TYPE_11G_1, TNS_VERIFIER_TYPE_11G_2, TNS_VERIFIER_TYPE_12C};
14use crate::{ProtocolError, Result};
15
16#[derive(Clone, Debug, Eq, PartialEq)]
17pub struct EncryptedPassword {
18    pub session_key: String,
19    pub speedy_key: Option<String>,
20    pub password: String,
21    pub combo_key: Vec<u8>,
22}
23
24pub fn generate_verifier(
25    password: &[u8],
26    session_data: &std::collections::BTreeMap<String, String>,
27    verifier_type: u32,
28) -> Result<EncryptedPassword> {
29    let verifier_data = decode_session_hex(session_data, "AUTH_VFR_DATA")?;
30    let (key_len, password_hash, password_key) = match verifier_type {
31        TNS_VERIFIER_TYPE_12C => {
32            let iterations = parse_session_u32(session_data, "AUTH_PBKDF2_VGEN_COUNT")?;
33            let mut salt = verifier_data.clone();
34            salt.extend_from_slice(b"AUTH_PBKDF2_SPEEDY_KEY");
35            let mut password_key = [0u8; 64];
36            pbkdf2_hmac::<Sha512>(password, &salt, iterations, &mut password_key);
37            let mut hasher = Sha512::new();
38            hasher.update(password_key);
39            hasher.update(&verifier_data);
40            (
41                32usize,
42                hasher.finalize()[..32].to_vec(),
43                Some(password_key.to_vec()),
44            )
45        }
46        TNS_VERIFIER_TYPE_11G_1 | TNS_VERIFIER_TYPE_11G_2 => {
47            let mut hasher = Sha1::new();
48            hasher.update(password);
49            hasher.update(&verifier_data);
50            let mut hash = hasher.finalize().to_vec();
51            hash.extend_from_slice(&[0, 0, 0, 0]);
52            (24usize, hash, None)
53        }
54        other => {
55            return Err(ProtocolError::UnsupportedVerifier {
56                verifier_type: other,
57            })
58        }
59    };
60
61    let encoded_server_key = decode_session_hex(session_data, "AUTH_SESSKEY")?;
62    let session_key_part_a = decrypt_cbc_raw(&password_hash, &encoded_server_key)?;
63    let mut session_key_part_b = vec![0u8; session_key_part_a.len()];
64    OsRng.fill_bytes(&mut session_key_part_b);
65    let encoded_client_key = encrypt_cbc(&password_hash, &session_key_part_b, false)?;
66
67    let (session_key, combo_key) = if session_key_part_a.len() == 48 {
68        let session_key = hex_upper_truncated(&encoded_client_key, 96);
69        let mut mixed = [0u8; 24];
70        for ix in 16..40 {
71            mixed[ix - 16] = session_key_part_a[ix] ^ session_key_part_b[ix];
72        }
73        let mut first = Md5::new();
74        first.update(&mixed[..16]);
75        let mut second = Md5::new();
76        second.update(&mixed[16..]);
77        let mut combo = first.finalize().to_vec();
78        combo.extend_from_slice(&second.finalize());
79        combo.truncate(key_len);
80        (session_key, combo)
81    } else {
82        let session_key = hex_upper_truncated(&encoded_client_key, 64);
83        let salt = decode_session_hex(session_data, "AUTH_PBKDF2_CSK_SALT")?;
84        let iterations = parse_session_u32(session_data, "AUTH_PBKDF2_SDER_COUNT")?;
85        if session_key_part_a.len() < key_len || session_key_part_b.len() < key_len {
86            return Err(ProtocolError::TtcDecode("session key too short"));
87        }
88        let mut temp_key = session_key_part_b[..key_len].to_vec();
89        temp_key.extend_from_slice(&session_key_part_a[..key_len]);
90        let temp_key_hex = hex_upper(&temp_key);
91        let mut combo_key = vec![0u8; key_len];
92        pbkdf2_hmac::<Sha512>(temp_key_hex.as_bytes(), &salt, iterations, &mut combo_key);
93        (session_key, combo_key)
94    };
95
96    let speedy_key = if matches!(verifier_type, TNS_VERIFIER_TYPE_12C) {
97        let password_key =
98            password_key.ok_or(ProtocolError::TtcDecode("missing 12c password key"))?;
99        let mut salt = [0u8; 16];
100        OsRng.fill_bytes(&mut salt);
101        let mut plain = salt.to_vec();
102        plain.extend_from_slice(&password_key);
103        let encrypted = encrypt_cbc(&combo_key, &plain, false)?;
104        Some(hex_upper_truncated(&encrypted, 160))
105    } else {
106        None
107    };
108
109    let mut salt = [0u8; 16];
110    OsRng.fill_bytes(&mut salt);
111    let mut password_with_salt = salt.to_vec();
112    password_with_salt.extend_from_slice(password);
113    let encrypted_password = encrypt_cbc(&combo_key, &password_with_salt, false)?;
114
115    Ok(EncryptedPassword {
116        session_key,
117        speedy_key,
118        password: hex_upper(&encrypted_password),
119        combo_key,
120    })
121}
122
123/// Encrypt the old/new password pair with the session combo key for the
124/// change-password call (reference messages/auth.pyx `_encrypt_passwords`:
125/// one shared random salt, AES-CBC under `_combo_key`, upper-hex encoding).
126pub fn encrypt_change_password_pair(
127    combo_key: &[u8],
128    old_password: &[u8],
129    new_password: &[u8],
130) -> Result<(String, String)> {
131    let mut salt = [0u8; 16];
132    OsRng.fill_bytes(&mut salt);
133    let mut old_with_salt = salt.to_vec();
134    old_with_salt.extend_from_slice(old_password);
135    let encrypted_old = encrypt_cbc(combo_key, &old_with_salt, false)?;
136    let mut new_with_salt = salt.to_vec();
137    new_with_salt.extend_from_slice(new_password);
138    let encrypted_new = encrypt_cbc(combo_key, &new_with_salt, false)?;
139    Ok((hex_upper(&encrypted_old), hex_upper(&encrypted_new)))
140}
141
142pub fn verify_server_response(
143    combo_key: &[u8],
144    session_data: &std::collections::BTreeMap<String, String>,
145) -> Result<()> {
146    let Some(response_hex) = session_data.get("AUTH_SVR_RESPONSE") else {
147        return Err(ProtocolError::InvalidServerResponse);
148    };
149    let encoded_response =
150        Vec::from_hex(response_hex).map_err(|_| ProtocolError::InvalidServerResponse)?;
151    let response = decrypt_cbc_raw(combo_key, &encoded_response)?;
152    if response.get(16..32) == Some(b"SERVER_TO_CLIENT") {
153        Ok(())
154    } else {
155        Err(ProtocolError::InvalidServerResponse)
156    }
157}
158
159fn decode_session_hex(
160    session_data: &std::collections::BTreeMap<String, String>,
161    key: &'static str,
162) -> Result<Vec<u8>> {
163    let value = session_data
164        .get(key)
165        .ok_or(ProtocolError::MissingAuthParameter { key })?;
166    Vec::from_hex(value).map_err(|_| ProtocolError::MissingAuthParameter { key })
167}
168
169fn parse_session_u32(
170    session_data: &std::collections::BTreeMap<String, String>,
171    key: &'static str,
172) -> Result<u32> {
173    session_data
174        .get(key)
175        .ok_or(ProtocolError::MissingAuthParameter { key })?
176        .parse()
177        .map_err(|_| ProtocolError::MissingAuthParameter { key })
178}
179
180fn encrypt_cbc(key: &[u8], plain_text: &[u8], zeros: bool) -> Result<Vec<u8>> {
181    let mut data = plain_text.to_vec();
182    let pad_len = 16 - (data.len() % 16);
183    if zeros {
184        data.extend(std::iter::repeat_n(0, pad_len));
185    } else {
186        data.extend(std::iter::repeat_n(pad_len as u8, pad_len));
187    }
188
189    let cipher = Aes256::new_from_slice(key).map_err(|_| ProtocolError::InvalidAesKey)?;
190    let mut previous = [0u8; 16];
191    for block in data.chunks_mut(16) {
192        for (byte, prev) in block.iter_mut().zip(previous) {
193            *byte ^= prev;
194        }
195        let block_ref = aes::cipher::generic_array::GenericArray::from_mut_slice(block);
196        cipher.encrypt_block(block_ref);
197        previous.copy_from_slice(block_ref);
198    }
199    Ok(data)
200}
201
202fn decrypt_cbc_raw(key: &[u8], encrypted_text: &[u8]) -> Result<Vec<u8>> {
203    if !encrypted_text.len().is_multiple_of(16) {
204        return Err(ProtocolError::TtcDecode(
205            "AES-CBC ciphertext length is not block aligned",
206        ));
207    }
208    let cipher = Aes256::new_from_slice(key).map_err(|_| ProtocolError::InvalidAesKey)?;
209    let mut out = encrypted_text.to_vec();
210    let mut previous = [0u8; 16];
211    for block in out.chunks_mut(16) {
212        let current: [u8; 16] = block
213            .try_into()
214            .map_err(|_| ProtocolError::TtcDecode("invalid AES block"))?;
215        let block_ref = aes::cipher::generic_array::GenericArray::from_mut_slice(block);
216        cipher.decrypt_block(block_ref);
217        for (byte, prev) in block.iter_mut().zip(previous) {
218            *byte ^= prev;
219        }
220        previous = current;
221    }
222    Ok(out)
223}
224
225fn hex_upper(bytes: &[u8]) -> String {
226    hex::encode_upper(bytes)
227}
228
229fn hex_upper_truncated(bytes: &[u8], chars: usize) -> String {
230    let mut text = hex_upper(bytes);
231    text.truncate(chars);
232    text
233}