sqlx_core_oldapi/mysql/connection/
auth.rs

1use bytes::buf::Chain;
2use bytes::Bytes;
3use digest::Digest;
4use rsa::{pkcs8::DecodePublicKey, Oaep, RsaPublicKey};
5use sha1::Sha1;
6use sha2::Sha256;
7
8use crate::error::Error;
9use crate::mysql::connection::stream::MySqlStream;
10use crate::mysql::protocol::auth::AuthPlugin;
11use crate::mysql::protocol::Packet;
12
13impl AuthPlugin {
14    pub(super) async fn scramble(
15        self,
16        stream: &mut MySqlStream,
17        password: &str,
18        nonce: &Chain<Bytes, Bytes>,
19    ) -> Result<Vec<u8>, Error> {
20        match self {
21            // https://mariadb.com/kb/en/caching_sha2_password-authentication-plugin/
22            AuthPlugin::CachingSha2Password => Ok(scramble_sha256(password, nonce)),
23
24            AuthPlugin::MySqlNativePassword => Ok(scramble_sha1(password, nonce)),
25
26            // https://mariadb.com/kb/en/sha256_password-plugin/
27            AuthPlugin::Sha256Password => encrypt_rsa(stream, 0x01, password, nonce).await,
28        }
29    }
30
31    pub(super) async fn handle(
32        self,
33        stream: &mut MySqlStream,
34        packet: Packet<Bytes>,
35        password: &str,
36        nonce: &Chain<Bytes, Bytes>,
37    ) -> Result<bool, Error> {
38        match self {
39            AuthPlugin::CachingSha2Password if packet[0] == 0x01 => {
40                match packet[1] {
41                    // AUTH_OK
42                    0x03 => Ok(true),
43
44                    // AUTH_CONTINUE
45                    0x04 => {
46                        let payload = encrypt_rsa(stream, 0x02, password, nonce).await?;
47
48                        stream.write_packet(&*payload);
49                        stream.flush().await?;
50
51                        Ok(false)
52                    }
53
54                    v => {
55                        Err(err_protocol!("unexpected result from fast authentication 0x{:x} when expecting 0x03 (AUTH_OK) or 0x04 (AUTH_CONTINUE)", v))
56                    }
57                }
58            }
59
60            _ => Err(err_protocol!(
61                "unexpected packet 0x{:02x} for auth plugin '{}' during authentication",
62                packet[0],
63                self.name()
64            )),
65        }
66    }
67}
68
69fn scramble_sha1(password: &str, nonce: &Chain<Bytes, Bytes>) -> Vec<u8> {
70    // SHA1( password ) ^ SHA1( seed + SHA1( SHA1( password ) ) )
71    // https://mariadb.com/kb/en/connection/#mysql_native_password-plugin
72
73    let mut ctx = Sha1::new();
74
75    ctx.update(password);
76
77    let mut pw_hash = ctx.finalize_reset();
78
79    ctx.update(&pw_hash);
80
81    let pw_hash_hash = ctx.finalize_reset();
82
83    ctx.update(nonce.first_ref());
84    ctx.update(nonce.last_ref());
85    ctx.update(pw_hash_hash);
86
87    let pw_seed_hash_hash = ctx.finalize();
88
89    xor_eq(&mut pw_hash, &pw_seed_hash_hash);
90
91    pw_hash.to_vec()
92}
93
94fn scramble_sha256(password: &str, nonce: &Chain<Bytes, Bytes>) -> Vec<u8> {
95    // XOR(SHA256(password), SHA256(seed, SHA256(SHA256(password))))
96    // https://mariadb.com/kb/en/caching_sha2_password-authentication-plugin/#sha-2-encrypted-password
97    let mut ctx = Sha256::new();
98
99    ctx.update(password);
100
101    let mut pw_hash = ctx.finalize_reset();
102
103    ctx.update(&pw_hash);
104
105    let pw_hash_hash = ctx.finalize_reset();
106
107    ctx.update(nonce.first_ref());
108    ctx.update(nonce.last_ref());
109    ctx.update(pw_hash_hash);
110
111    let pw_seed_hash_hash = ctx.finalize();
112
113    xor_eq(&mut pw_hash, &pw_seed_hash_hash);
114
115    pw_hash.to_vec()
116}
117
118async fn encrypt_rsa<'s>(
119    stream: &'s mut MySqlStream,
120    public_key_request_id: u8,
121    password: &'s str,
122    nonce: &'s Chain<Bytes, Bytes>,
123) -> Result<Vec<u8>, Error> {
124    // https://mariadb.com/kb/en/caching_sha2_password-authentication-plugin/
125
126    if stream.is_tls() {
127        // If in a TLS stream, send the password directly in clear text
128        return Ok(to_asciz(password));
129    }
130
131    // client sends a public key request
132    stream.write_packet(&[public_key_request_id][..]);
133    stream.flush().await?;
134
135    // server sends a public key response
136    let packet = stream.recv_packet().await?;
137    let rsa_pub_key = &packet[1..];
138
139    // xor the password with the given nonce
140    let mut pass = to_asciz(password);
141
142    let (a, b) = (nonce.first_ref(), nonce.last_ref());
143    let mut nonce = Vec::with_capacity(a.len() + b.len());
144    nonce.extend_from_slice(&*a);
145    nonce.extend_from_slice(&*b);
146
147    xor_eq(&mut pass, &*nonce);
148
149    // client sends an RSA encrypted password
150    let pkey = parse_rsa_pub_key(rsa_pub_key)?;
151    let padding = Oaep::new::<sha1::Sha1>();
152    pkey.encrypt(&mut rand::thread_rng(), padding, &pass[..])
153        .map_err(Error::protocol)
154}
155
156// XOR(x, y)
157// If len(y) < len(x), wrap around inside y
158fn xor_eq(x: &mut [u8], y: &[u8]) {
159    let y_len = y.len();
160
161    for i in 0..x.len() {
162        x[i] ^= y[i % y_len];
163    }
164}
165
166fn to_asciz(s: &str) -> Vec<u8> {
167    let mut z = String::with_capacity(s.len() + 1);
168    z.push_str(s);
169    z.push('\0');
170
171    z.into_bytes()
172}
173
174// https://docs.rs/rsa/0.3.0/rsa/struct.RSAPublicKey.html?search=#example-1
175fn parse_rsa_pub_key(key: &[u8]) -> Result<RsaPublicKey, Error> {
176    let pem = std::str::from_utf8(key).map_err(Error::protocol)?;
177
178    // This takes advantage of the knowledge that we know
179    // we are receiving a PKCS#8 RSA Public Key at all
180    // times from MySQL
181
182    RsaPublicKey::from_public_key_pem(&pem).map_err(Error::protocol)
183}