ares/
lib.rs

1//! Ares is an automatic decoding and cracking tool. https://github.com/bee-san/ares
2// Warns in case we forget to include documentation
3#![warn(
4    missing_docs,
5    clippy::missing_docs_in_private_items,
6    clippy::missing_errors_doc,
7    clippy::missing_panics_doc
8)]
9
10/// The main crate for the Ares project.
11/// This provides the library API interface for Ares.
12mod api_library_input_struct;
13/// Checkers is a module that contains the functions that check if the input is plaintext
14pub mod checkers;
15/// CLI Arg Parsing library
16pub mod cli;
17/// CLI Input Parser parses the input from the CLI and returns a struct.
18mod cli_input_parser;
19/// CLI Pretty Printing module for consistent output formatting
20///
21/// # Examples
22/// ```
23/// use ares::cli_pretty_printing::{success, warning};
24///
25/// // Print a success message
26/// let success_msg = success("Operation completed successfully");
27/// assert!(!success_msg.is_empty());
28///
29/// // Print a warning message
30/// let warning_msg = warning("Please check your input");
31/// assert!(!warning_msg.is_empty());
32/// ```
33pub mod cli_pretty_printing;
34/// The Config module enables a configuration module
35/// Like a global API to access config details
36pub mod config;
37/// Decoders are the functions that actually perform the decodings.
38pub mod decoders;
39/// The filtration system builds what decoders to use at runtime
40/// By default it will use them all.
41mod filtration_system;
42/// The searcher is the thing which searches for the plaintext
43/// It is the core of the program.
44mod searchers;
45/// Storage module for dictionaries and invisible characters
46pub mod storage;
47/// Timer for internal use
48mod timer;
49
50use checkers::{
51    athena::Athena,
52    checker_result::CheckResult,
53    checker_type::{Check, Checker},
54    wait_athena::WaitAthena,
55};
56use log::debug;
57
58use crate::{
59    config::{get_config, Config},
60    decoders::interface::Decoder,
61};
62
63use self::decoders::crack_results::CrackResult;
64
65/// The main function to call which performs the cracking.
66/// ```rust
67/// use ares::perform_cracking;
68/// use ares::config::Config;
69/// let mut config = Config::default();
70/// // You can set the config to your liking using the Config struct
71/// // Just edit the data like below if you want:
72/// config.timeout = 5;
73/// config.human_checker_on = false;
74/// config.verbose = 0;
75/// let result = perform_cracking("VGhlIG1haW4gZnVuY3Rpb24gdG8gY2FsbCB3aGljaCBwZXJmb3JtcyB0aGUgY3JhY2tpbmcu", config);
76/// assert!(true);
77/// // The result is an Option<DecoderResult> so we need to unwrap it
78/// // The DecoderResult contains the text and the path
79/// // The path is a vector of CrackResults which contains the decoder used and the keys used
80/// // The text is a vector of strings because some decoders return more than 1 text (Caesar)
81/// // Becuase the program has returned True, the first result is the plaintext (and it will only have 1 result).
82/// // This is some tech debt we need to clean up https://github.com/bee-san/Ares/issues/130
83/// assert!(result.unwrap().text[0] == "The main function to call which performs the cracking.");
84/// ```
85/// The human checker defaults to off in the config, but it returns the first thing it finds currently.
86/// We have an issue for that here https://github.com/bee-san/Ares/issues/129
87/// ```rust
88/// use ares::perform_cracking;
89/// use ares::config::Config;
90/// let mut config = Config::default();
91/// // You can set the config to your liking using the Config struct
92/// // Just edit the data like below if you want:
93/// config.timeout = 0;
94/// let result = perform_cracking("VGhlIG1haW4gZnVuY3Rpb24gdG8gY2FsbCB3aGljaCBwZXJmb3JtcyB0aGUgY3JhY2tpbmcu", config);
95/// assert!(true);
96/// // If the program times out, or it cannot decode the text it will return None.
97/// assert!(result.is_none());
98/// ```
99pub fn perform_cracking(text: &str, config: Config) -> Option<DecoderResult> {
100    // If top_results is enabled, ensure human_checker_on is disabled
101    let mut modified_config = config;
102    if modified_config.top_results {
103        modified_config.human_checker_on = false;
104        // Clear any previous results when starting a new cracking session
105        storage::wait_athena_storage::clear_plaintext_results();
106    }
107
108    config::set_global_config(modified_config);
109    let text = text.to_string();
110
111    let initial_check_for_plaintext = check_if_input_text_is_plaintext(&text);
112    if initial_check_for_plaintext.is_identified {
113        debug!(
114            "The input text provided to the program {} is the plaintext. Returning early.",
115            text
116        );
117        cli_pretty_printing::return_early_because_input_text_is_plaintext();
118
119        let mut crack_result = CrackResult::new(&Decoder::default(), text.to_string());
120        crack_result.checker_name = initial_check_for_plaintext.checker_name;
121
122        let output = DecoderResult {
123            text: vec![text],
124            path: vec![crack_result],
125        };
126
127        return Some(output);
128    }
129
130    // Build a new search tree
131    // This starts us with a node with no parents
132    // let search_tree = searchers::Tree::new(text.to_string());
133    cli_pretty_printing::success(&format!(
134        "DEBUG: lib.rs - Calling search_for_plaintext with text: {}",
135        text
136    ));
137    // Perform the search algorithm
138    // It will either return a failure or success.
139    let result = searchers::search_for_plaintext(text);
140    cli_pretty_printing::success(&format!(
141        "DEBUG: lib.rs - Result from search_for_plaintext: {:?}",
142        result.is_some()
143    ));
144    if let Some(ref res) = result {
145        cli_pretty_printing::success(&format!(
146            "DEBUG: lib.rs - Result has {} decoders in path",
147            res.path.len()
148        ));
149    }
150    result
151}
152
153/// Checks if the given input is plaintext or not
154/// Used at the start of the program to not waste CPU cycles
155fn check_if_input_text_is_plaintext(text: &str) -> CheckResult {
156    let config = get_config();
157
158    if config.top_results {
159        let wait_athena_checker = Checker::<WaitAthena>::new();
160        wait_athena_checker.check(text)
161    } else {
162        let athena_checker = Checker::<Athena>::new();
163        athena_checker.check(text)
164    }
165}
166
167/// DecoderResult is the result of decoders
168#[derive(Debug)]
169pub struct DecoderResult {
170    /// The text we have from the decoder, as a vector
171    /// because the decoder might return more than 1 text (caesar)
172    pub text: Vec<String>,
173    /// The list of decoders we have so far
174    /// The CrackResult contains more than just each decoder, such as the keys used
175    /// or the checkers used.
176    pub path: Vec<CrackResult>,
177}
178
179/// Creates a default DecoderResult with Default as the text / path
180impl Default for DecoderResult {
181    fn default() -> Self {
182        DecoderResult {
183            text: vec!["Default".to_string()],
184            path: vec![CrackResult::new(&Decoder::default(), "Default".to_string())],
185        }
186    }
187}
188
189/// Lets us create a new decoderResult with given text
190impl DecoderResult {
191    /// It's only used in tests so it thinks its dead code
192    fn _new(text: &str) -> Self {
193        DecoderResult {
194            text: vec![text.to_string()],
195            path: vec![CrackResult::new(&Decoder::default(), "Default".to_string())],
196        }
197    }
198}
199
200#[cfg(test)]
201mod tests {
202    use super::perform_cracking;
203    use crate::config::Config;
204
205    #[test]
206    fn test_perform_cracking_returns() {
207        let config = Config::default();
208        perform_cracking("SGVscCBJIG5lZWQgc29tZWJvZHkh", config);
209    }
210
211    #[test]
212    fn test_perform_cracking_returns_failure() {
213        let config = Config::default();
214        let result = perform_cracking("", config);
215        assert!(result.is_none());
216    }
217
218    #[test]
219    fn test_perform_cracking_returns_successful_base64_reverse() {
220        let config = Config::default();
221        let result = perform_cracking("aGVsbG8gdGhlcmUgZ2VuZXJhbA==", config);
222        assert!(result.is_some());
223        assert!(result.unwrap().text[0] == "hello there general")
224    }
225
226    #[test]
227    fn test_early_exit_if_input_is_plaintext() {
228        let config = Config::default();
229        let result = perform_cracking("192.168.0.1", config);
230        // Since we are exiting early the path should be of length 1, which is 1 check (the Athena check)
231        assert!(result.unwrap().path.len() == 1);
232    }
233
234    #[ignore]
235    #[test]
236    // Previously this would decode to `Fchohs as 13 dzoqsg!` because the English checker wasn't that good
237    // This test makes sure we can decode it okay
238    // TODO: Skipping this test because the English checker still isn't good.
239    fn test_successfully_decode_caesar() {
240        let config = Config::default();
241        let result = perform_cracking("Ebgngr zr 13 cynprf!", config);
242        // We return None since the input is the plaintext
243        assert!(result.unwrap().text[0] == "Rotate me 13 places!");
244    }
245
246    #[test]
247    fn test_successfully_inputted_plaintext() {
248        let config = Config::default();
249        let result = perform_cracking("Hello, World!", config);
250        // We return None since the input is the plaintext
251        let res_unwrapped = result.unwrap();
252        assert!(&res_unwrapped.text[0] == "Hello, World!");
253        // Since our input is the plaintext we did not decode it
254        // Therefore we return with the default decoder
255        assert!(res_unwrapped.path[0].decoder == "Default decoder");
256    }
257}