interactsh_rs/client/
registered.rs

1use base64::engine::general_purpose;
2use base64::Engine as _;
3use secrecy::{ExposeSecret, Secret};
4use smallvec::SmallVec;
5use snafu::ResultExt;
6
7use super::errors::{
8    client_poll_error,
9    client_registration_error,
10    ClientPollError,
11    ClientRegistrationError,
12};
13use super::http_utils::{self, Client, DeregisterData, HttpRequest, PollResponse};
14use crate::crypto::aes;
15use crate::crypto::rsa::RSAPrivKey;
16use crate::interaction_log::LogEntry;
17
18/// The client type returned when an [UnregisteredClient](crate::client::UnregisteredClient)
19/// successfully registers with its configured Interactsh server.
20#[derive(Debug, Clone)]
21pub struct RegisteredClient {
22    pub(crate) rsa_key: RSAPrivKey,
23    pub(crate) server: String,
24    pub(crate) sub_domain: String,
25    pub(crate) correlation_id: String,
26    pub(crate) auth_token: Option<Secret<String>>,
27    pub(crate) secret_key: Secret<String>,
28    pub(crate) reqwest_client: reqwest::Client,
29    pub(crate) parse_logs: bool,
30}
31
32impl RegisteredClient {
33    #[deprecated(
34        since = "0.2.0",
35        note = "Renaming for accuracy. Use get_interaction_fqdn() instead."
36    )]
37    /// Gets the interaction FQDN for the current
38    /// registered session
39    ///
40    /// For naming accuracy, this function has been replaced by [get_interaction_fqdn()](RegisteredClient::get_interaction_fqdn()).
41    pub fn get_interaction_url(&self) -> String {
42        self.get_interaction_fqdn()
43    }
44
45    /// Gets the interaction FQDN for the current
46    /// registered session
47    pub fn get_interaction_fqdn(&self) -> String {
48        format!("{}.{}", self.sub_domain, self.server)
49    }
50
51    /// Deregisters the [RegisteredClient] with the Interactsh server.
52    ///
53    /// If the deregistration fails, this returns a
54    /// [ClientRegistrationError](super::errors::ClientRegistrationError),
55    /// which contains a clone of this client if another try is needed.
56    pub async fn deregister(self) -> Result<(), ClientRegistrationError<RegisteredClient>> {
57        let post_data = DeregisterData {
58            correlation_id: self.correlation_id.clone(),
59            secret_key: self.secret_key.expose_secret().clone(),
60        };
61
62        self.do_registration_request(post_data)
63            .await
64            .context(client_registration_error::ClientRegistration { client: self })?;
65
66        Ok(())
67    }
68
69    /// Polls the Interactsh server for any new logs.
70    pub async fn poll(&self) -> Result<Option<Vec<LogEntry>>, ClientPollError> {
71        let poll_url = format!("https://{}/poll", self.server);
72
73        let mut query_params = SmallVec::<[(String, String); 2]>::new();
74        query_params.push(("id".into(), self.correlation_id.clone()));
75        query_params.push(("secret".into(), self.secret_key.expose_secret().clone()));
76
77        let request_info = HttpRequest::new_get_request(poll_url, query_params);
78
79        let get_response = http_utils::make_http_request(
80            &self.reqwest_client,
81            self.auth_token.as_ref(),
82            request_info,
83        )
84        .await
85        .context(client_poll_error::PollFailure)?;
86
87        let status = &get_response.status();
88
89        if !status.is_success() {
90            let server_msg = get_response
91                .text()
92                .await
93                .unwrap_or_else(|_| "Unknown error".to_string());
94            let status_code = status.as_u16();
95            let error = client_poll_error::PollErrorStatus {
96                server_msg,
97                status_code,
98            };
99
100            return error.fail();
101        }
102
103        let response_body = get_response
104            .json::<PollResponse>()
105            .await
106            .context(client_poll_error::ResponseJsonParseFailed)?;
107
108        let response_body_data = match response_body.data_list {
109            Some(data) => {
110                if data.is_empty() {
111                    return Ok(None);
112                } else {
113                    data
114                }
115            }
116            None => return Ok(None),
117        };
118        let aes_key_decoded = general_purpose::STANDARD
119            .decode(&response_body.aes_key)
120            .context(client_poll_error::Base64DecodeFailed)?;
121
122        let mut results = Vec::new();
123        for data in response_body_data.iter() {
124            let data_decoded = general_purpose::STANDARD
125                .decode(data)
126                .context(client_poll_error::Base64DecodeFailed)?;
127            let decrypted_data = self.decrypt_data(&aes_key_decoded, &data_decoded)?;
128
129            let log_entry = if self.parse_logs {
130                LogEntry::try_parse_log(decrypted_data.as_str())
131            } else {
132                LogEntry::return_raw_log(decrypted_data.as_str())
133            };
134
135            results.push(log_entry);
136        }
137
138        Ok(Some(results))
139    }
140
141    fn decrypt_data(
142        &self,
143        aes_key: &[u8],
144        encrypted_data: &[u8],
145    ) -> Result<String, ClientPollError> {
146        let aes_plain_key = self
147            .rsa_key
148            .decrypt_data(aes_key)
149            .context(client_poll_error::AesKeyDecryptFailed)?;
150
151        let decrypted_data = aes::decrypt_data(&aes_plain_key, encrypted_data)
152            .context(client_poll_error::DataDecryptFailed)?;
153
154        let decrypted_string = String::from_utf8_lossy(&decrypted_data);
155
156        Ok(decrypted_string.into())
157    }
158}
159
160impl Client for RegisteredClient {
161    fn get_registration_url(&self) -> String {
162        format!("https://{}/deregister", &self.server)
163    }
164
165    fn get_reqwest_client(&self) -> &reqwest::Client {
166        &self.reqwest_client
167    }
168
169    fn get_auth_token(&self) -> Option<&Secret<String>> {
170        self.auth_token.as_ref()
171    }
172}