1use crate::control_connection::TorControlConnection;
2use crate::error::TorError;
3use hex;
4use hmac::{Hmac, Mac};
5use lazy_static::lazy_static;
6use rand;
7use regex::Regex;
8use serde::{Deserialize, Serialize};
9use sha2::Sha256;
10
11type HmacSha256 = Hmac<Sha256>;
13
14fn parse_authchallenge_response(response: &str) -> Result<(Vec<u8>, Vec<u8>), TorError> {
15 lazy_static! {
17 static ref RE: Regex =
18 Regex::new(r"^AUTHCHALLENGE SERVERHASH=(?P<server_hash>[0-9A-F]*) SERVERNONCE=(?P<server_nonce>[0-9A-F]*)")
19 .unwrap();
20 }
21 match RE.captures(response) {
22 Some(captures) => {
23 let server_hash_hex = &captures["server_hash"];
24 let server_nonce_hex = &captures["server_nonce"];
25 let server_hash = match hex::decode(server_hash_hex) {
26 Ok(hash) => hash,
27 Err(error) => {
28 return Err(TorError::protocol_error(&format!(
29 "Error decoding base64 server hash from server: {}",
30 error
31 )))
32 }
33 };
34 let server_nonce = match hex::decode(server_nonce_hex) {
35 Ok(nonce) => nonce,
36 Err(error) => {
37 return Err(TorError::protocol_error(&format!(
38 "Error decoding base64 server nonce from server: {}",
39 error
40 )))
41 }
42 };
43 Ok((server_hash, server_nonce))
44 }
45 None => Err(TorError::protocol_error(&format!(
46 "Unexpected response from AUTHCHALLENGE: {}",
47 response
48 ))),
49 }
50}
51
52fn generate_hmac(key: &[u8], message: &[u8]) -> Vec<u8> {
53 let mut hmac = HmacSha256::new_from_slice(key).unwrap();
54 hmac.update(message);
55 hmac.finalize().into_bytes().to_vec()
56}
57
58fn validate_server_hash(
59 safe_cookie_value: &[u8],
60 client_nonce: &mut Vec<u8>,
61 server_nonce: &mut Vec<u8>,
62 server_hash: &[u8],
63) -> Result<Vec<u8>, TorError> {
64 let mut message = safe_cookie_value.to_vec();
66 message.append(client_nonce);
67 message.append(server_nonce);
68 let hash = generate_hmac(
69 b"Tor safe cookie authentication server-to-controller hash",
70 &message,
71 );
72
73 if hash.as_slice() == server_hash {
74 Ok(message)
75 } else {
76 Err(TorError::authentication_error(
77 "Server hash didn't validate",
78 ))
79 }
80}
81
82async fn safe_cookie_authentication(
83 cookie: &[u8],
84 connection: &mut TorControlConnection,
85) -> Result<(), TorError> {
86 let mut client_nonce = vec![rand::random::<u8>(), rand::random::<u8>()];
87 let client_nonce_hex = hex::encode(&client_nonce);
88 let response = connection
89 .send_command(
90 "AUTHCHALLENGE",
91 Some(&format!("SAFECOOKIE {}", client_nonce_hex)),
92 )
93 .await?;
94
95 if response.status_code != 250 {
97 return Err(TorError::protocol_error(&format!(
98 "Expected status code 250, got {}",
99 response.status_code
100 )));
101 }
102 let (server_hash, mut server_nonce) = parse_authchallenge_response(&response.reply)?;
103
104 let message = validate_server_hash(cookie, &mut client_nonce, &mut server_nonce, &server_hash)?;
107
108 let auth_string = hex::encode(generate_hmac(
110 b"Tor safe cookie authentication controller-to-server hash",
111 &message,
112 ));
113
114 let response = connection
116 .send_command("AUTHENTICATE", Some(&auth_string))
117 .await?;
118 if response.status_code != 250 {
119 return Err(TorError::protocol_error(&format!(
120 "Expected status code 250, got {}",
121 response.status_code
122 )));
123 }
124 Ok(())
125}
126
127#[derive(Clone, Debug, Default, Serialize, Deserialize, strum_macros::Display)]
139pub enum TorAuthentication {
140 #[default]
141 Null,
142 SafeCookie(Option<Vec<u8>>), HashedPassword(String), }
145
146lazy_static! {
147 static ref NULL_AUTH_NAME: String = "NULL".to_string();
148 static ref HASHED_PASSWORD_NAME: String = "HASHEDPASSWORD".to_string();
149 static ref SAFE_COOKIE_NAME: String = "SAFECOOKIE".to_string();
150}
151
152impl TorAuthentication {
153 pub async fn authenticate(
155 &self,
156 connection: &mut TorControlConnection,
157 ) -> Result<(), TorError> {
158 let protocol_info = connection.get_protocol_info().await?;
159 match self {
160 TorAuthentication::Null => {
161 if !protocol_info.auth_methods.contains(&*NULL_AUTH_NAME) {
162 return Err(TorError::authentication_error(
163 "Null auth not allowed by this server",
164 ));
165 }
166 match connection.send_command("AUTHENTICATE", None).await {
167 Ok(_) => Ok(()),
168 Err(TorError::ProtocolError(error)) => {
169 Err(TorError::AuthenticationError(error))
170 }
171 Err(error) => Err(error),
172 }
173 }
174 TorAuthentication::HashedPassword(password) => {
175 if !protocol_info.auth_methods.contains(&*HASHED_PASSWORD_NAME) {
176 return Err(TorError::authentication_error(
177 "HashedPassword auth not allowed by this server",
178 ));
179 }
180 match connection
181 .send_command("AUTHENTICATE", Some(&format!("\"{}\"", password)))
182 .await
183 {
184 Ok(_) => Ok(()),
185 Err(TorError::ProtocolError(error)) => {
186 Err(TorError::AuthenticationError(error))
187 }
188 Err(error) => Err(error),
189 }
190 }
191 TorAuthentication::SafeCookie(cookie) => {
192 if !protocol_info.auth_methods.contains(&*SAFE_COOKIE_NAME) {
193 return Err(TorError::authentication_error(
194 "SafeCookie auth not allowed by this server",
195 ));
196 }
197 match cookie {
198 Some(cookie) => safe_cookie_authentication(cookie, connection).await,
200
201 None => match protocol_info.cookie_file {
204 Some(cookie_file) => match std::fs::read(cookie_file.clone()) {
205 Ok(cookie) => safe_cookie_authentication(&cookie, connection).await,
206 Err(error) => Err(TorError::authentication_error(&format!(
207 "Error reading cookie file {}: {}",
208 cookie_file, error
209 ))),
210 },
211 None => Err(TorError::authentication_error(
212 "No cookie file provided in tor protocol info",
213 )),
214 },
215 }
216 }
217 }
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 #[test]
226 fn test_auth_display() {
227 assert_eq!("Null", format!("{}", TorAuthentication::Null));
228 assert_eq!(
229 "HashedPassword",
230 format!("{}", TorAuthentication::HashedPassword("foobar".into()))
231 );
232 assert_eq!(
233 "SafeCookie",
234 format!(
235 "{}",
236 TorAuthentication::SafeCookie(Some("foobar".as_bytes().to_vec()))
237 )
238 );
239 }
240}