stalkerware_indicators/
lib.rs

1//! Parse a stalkerware-indicators yaml into a list of [`Rule`](struct.Rule.html)s.
2//!
3//! ## Example
4//!
5//! ```
6//! use anyhow::Context;
7//! use std::fs;
8//!
9//! fn main() -> anyhow::Result<()> {
10//!     let buf = fs::read("test_data/ioc-2022-12-15.yaml")
11//!         .context("Failed to read ioc yaml file")?;
12//!
13//!     let rules = stalkerware_indicators::parse_from_buf(&buf);
14//!     for rule in rules {
15//!         println!("Rule: {:?}", rule);
16//!     }
17//!
18//!     Ok(())
19//! }
20//! ```
21
22pub mod errors;
23mod structs;
24
25use crate::errors::*;
26pub use crate::structs::*;
27use std::fmt;
28use std::fs;
29use std::path::Path;
30
31/// Load a yaml ioc.yaml from a byte slice
32pub fn parse_from_buf(buf: &[u8]) -> Result<Vec<Rule>> {
33    let data =
34        serde_yaml::from_slice(buf).context("Failed to parse stalkerware-indicators rules")?;
35    Ok(data)
36}
37
38/// Load a yaml ioc.yaml from the file system
39pub fn parse_from_file<T: AsRef<Path> + fmt::Debug>(path: T) -> Result<Vec<Rule>> {
40    let buf = fs::read(&path).with_context(|| anyhow!("Failed to read file: {:?}", path))?;
41    parse_from_buf(&buf)
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47
48    #[test]
49    fn test_load_2022_09_14() {
50        let rules = parse_from_file("test_data/ioc-2022-09-14.yaml").unwrap();
51        assert_eq!(rules.len(), 117);
52    }
53
54    #[test]
55    fn test_load_2022_12_15() {
56        let rules = parse_from_file("test_data/ioc-2022-12-15.yaml").unwrap();
57        assert_eq!(rules.len(), 146);
58    }
59
60    #[test]
61    fn parse_minimal() {
62        let buf = r#"
63- name: Minimal
64  type: stalkerware
65        "#;
66
67        let rules = parse_from_buf(buf.as_bytes()).unwrap();
68        assert_eq!(
69            rules,
70            vec![Rule {
71                name: "Minimal".to_string(),
72                names: Vec::new(),
73                r#type: "stalkerware".to_string(),
74                packages: Vec::new(),
75                distribution: Vec::new(),
76                certificates: Vec::new(),
77                websites: Vec::new(),
78                c2: C2Rule {
79                    ips: Vec::new(),
80                    domains: Vec::new(),
81                },
82            },]
83        );
84    }
85
86    #[test]
87    fn parse_empty_c2() {
88        let buf = r#"
89- name: Minimal
90  type: stalkerware
91  c2: {}
92        "#;
93
94        let rules = parse_from_buf(buf.as_bytes()).unwrap();
95        assert_eq!(
96            rules,
97            vec![Rule {
98                name: "Minimal".to_string(),
99                names: Vec::new(),
100                r#type: "stalkerware".to_string(),
101                packages: Vec::new(),
102                distribution: Vec::new(),
103                certificates: Vec::new(),
104                websites: Vec::new(),
105                c2: C2Rule {
106                    ips: Vec::new(),
107                    domains: Vec::new(),
108                },
109            },]
110        );
111    }
112}