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