diff --git a/rust/src/lib.rs b/rust/src/lib.rs index dc136a06..3211a747 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -111,5 +111,7 @@ pub use crate::vaas::Vaas; pub use builder::Builder; pub use cancellation::CancellationToken; pub use connection::Connection; +pub use message::Detection; +pub use message::LibMagic; pub use sha256::Sha256; pub use vaas_verdict::VaasVerdict; diff --git a/rust/src/message/detection.rs b/rust/src/message/detection.rs new file mode 100644 index 00000000..331348a9 --- /dev/null +++ b/rust/src/message/detection.rs @@ -0,0 +1,12 @@ +use serde::{Deserialize, Serialize}; + +/// Scan engine detection +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct Detection { + /// Engine ID + pub engine: i32, + /// File name + pub file_name: String, + /// Virus signature name + pub virus: String, +} diff --git a/rust/src/message/lib_magic.rs b/rust/src/message/lib_magic.rs new file mode 100644 index 00000000..4f890337 --- /dev/null +++ b/rust/src/message/lib_magic.rs @@ -0,0 +1,10 @@ +use serde::{Deserialize, Serialize}; + +/// File and mime type as classified by https://www.darwinsys.com/file/ +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct LibMagic { + /// The file type + pub file_type: String, + /// The mime type + pub mime_type: String, +} diff --git a/rust/src/message/mod.rs b/rust/src/message/mod.rs index b834ff2d..680441c6 100644 --- a/rust/src/message/mod.rs +++ b/rust/src/message/mod.rs @@ -2,8 +2,10 @@ mod auth_request; mod auth_response; +mod detection; mod error; mod kind; +mod lib_magic; mod message_type; mod open_id_connect_token_response; mod upload_url; @@ -15,7 +17,9 @@ mod verdict_response; pub(super) use auth_request::AuthRequest; pub(super) use auth_response::AuthResponse; +pub use detection::Detection; pub(super) use error::ErrorResponse; +pub use lib_magic::LibMagic; pub(super) use message_type::MessageType; pub(super) use open_id_connect_token_response::OpenIdConnectTokenResponse; pub(super) use upload_url::UploadUrl; diff --git a/rust/src/message/verdict_response.rs b/rust/src/message/verdict_response.rs index 9ccfab3d..e6a04e26 100644 --- a/rust/src/message/verdict_response.rs +++ b/rust/src/message/verdict_response.rs @@ -1,14 +1,17 @@ +use super::{Detection, LibMagic}; use crate::error::Error; use serde::{Deserialize, Serialize}; use std::convert::TryFrom; -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct VerdictResponse { pub sha256: String, pub guid: String, pub verdict: String, pub url: Option, pub upload_token: Option, + pub detections: Option>, + pub lib_magic: Option, } impl TryFrom<&String> for VerdictResponse { @@ -17,3 +20,42 @@ impl TryFrom<&String> for VerdictResponse { serde_json::from_str(value).map_err(|e| e.into()) } } + +#[cfg(test)] +mod tests { + use crate::message::{detection::Detection, lib_magic::LibMagic, VerdictResponse}; + + #[test] + fn deserialize() { + let json = r#"{"sha256":"275a021bbfb6489e54d471899f7db9d1663fc695ec2fe2a2c4538aabf651fd0f","file_name":null,"verdict":"Malicious","upload_token":null,"url":null,"detections":[{"engine":2,"file_name":"/tmp/scan/051f699f-b21f-4d33-9cdd-d8b2f01e6118","virus":"EICAR-Test-File"},{"engine":3,"file_name":"/tmp/scan/051f699f-b21f-4d33-9cdd-d8b2f01e6118","virus":"EICAR_TEST_FILE"}],"lib_magic":{"file_type":"EICAR virus test files","mime_type":"text/plain"},"kind":"VerdictResponse","request_id":"ed7207a5-d65a-4400-b91c-673ff39cfd8b","guid":"ed7207a5-d65a-4400-b91c-673ff39cfd8b"}"#; + let verdict_response: VerdictResponse = serde_json::from_str(json).unwrap(); + + assert_eq!( + VerdictResponse { + sha256: "275a021bbfb6489e54d471899f7db9d1663fc695ec2fe2a2c4538aabf651fd0f" + .to_string(), + guid: "ed7207a5-d65a-4400-b91c-673ff39cfd8b".to_string(), + verdict: "Malicious".to_string(), + url: None, + upload_token: None, + detections: Some(vec![ + Detection { + engine: 2, + file_name: "/tmp/scan/051f699f-b21f-4d33-9cdd-d8b2f01e6118".to_string(), + virus: "EICAR-Test-File".to_string() + }, + Detection { + engine: 3, + file_name: "/tmp/scan/051f699f-b21f-4d33-9cdd-d8b2f01e6118".to_string(), + virus: "EICAR_TEST_FILE".to_string() + } + ]), + lib_magic: Some(LibMagic { + file_type: "EICAR virus test files".to_string(), + mime_type: "text/plain".to_string() + }) + }, + verdict_response + ); + } +} diff --git a/rust/src/vaas_verdict.rs b/rust/src/vaas_verdict.rs index 5ceebf46..bebec602 100644 --- a/rust/src/vaas_verdict.rs +++ b/rust/src/vaas_verdict.rs @@ -3,7 +3,7 @@ //! The `VaaSVerdict` is the result of a request for a verdict. It contains the verdict itself and the SHA256 hash of the requested file. use crate::error::Error; -use crate::message::{Verdict, VerdictResponse}; +use crate::message::{Detection, LibMagic, Verdict, VerdictResponse}; use crate::sha256::Sha256; use std::convert::TryFrom; @@ -15,6 +15,10 @@ pub struct VaasVerdict { pub sha256: Sha256, /// Verdict for the file pub verdict: Verdict, + /// Detections + pub detections: Option>, + /// File and mime type as classified by https://www.darwinsys.com/file/ + pub lib_magic: Option, } impl TryFrom for VaasVerdict { @@ -23,6 +27,8 @@ impl TryFrom for VaasVerdict { Ok(Self { sha256: Sha256::try_from(verdict_response.sha256.as_str())?, verdict: Verdict::try_from(&verdict_response)?, + detections: verdict_response.detections, + lib_magic: verdict_response.lib_magic, }) } } diff --git a/rust/tests/real_api_integration_tests.rs b/rust/tests/real_api_integration_tests.rs index f7f5b8ac..73d8afc0 100644 --- a/rust/tests/real_api_integration_tests.rs +++ b/rust/tests/real_api_integration_tests.rs @@ -4,7 +4,7 @@ use reqwest::Url; use std::convert::TryFrom; use std::ops::Deref; use vaas::auth::authenticators::{ClientCredentials, Password}; -use vaas::{message::Verdict, CancellationToken, Connection, Sha256, Vaas}; +use vaas::{message::Verdict, CancellationToken, Connection, LibMagic, Sha256, Vaas}; async fn get_vaas_with_flags(use_cache: bool, use_hash_lookup: bool) -> Connection { let token_url: Url = dotenv::var("TOKEN_URL") @@ -20,7 +20,7 @@ async fn get_vaas_with_flags(use_cache: bool, use_hash_lookup: bool) -> Connecti .expect("No VAAS_URL environment variable set to be used in the integration tests"); Vaas::builder(authenticator) .use_cache(use_cache) - .use_cache(use_hash_lookup) + .use_hash_lookup(use_hash_lookup) .url(Url::parse(&vaas_url).unwrap()) .build() .unwrap() @@ -330,20 +330,29 @@ async fn from_sha256_multiple_unknown_hash() { } #[tokio::test] -async fn from_file_single_malicious_file() { +async fn for_file_single_malicious_file() { let eicar = "X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*"; let tmp_file = std::env::temp_dir().join("eicar.txt"); std::fs::write(&tmp_file, eicar.as_bytes()).unwrap(); - let vaas = get_vaas().await; + let vaas = get_vaas_with_flags(false, false).await; let ct = CancellationToken::from_seconds(30); - let verdict = vaas.for_file(&tmp_file, &ct).await; + let verdict = vaas.for_file(&tmp_file, &ct).await.unwrap(); - assert_eq!(Verdict::Malicious, verdict.as_ref().unwrap().verdict); + assert_eq!(Verdict::Malicious, verdict.verdict); + assert_eq!(Sha256::try_from(&tmp_file).unwrap(), verdict.sha256); + let detections = verdict.detections.unwrap(); + assert_eq!(2, detections[0].engine); + assert_eq!("EICAR-Test-File".to_string(), detections[0].virus); + assert_eq!(3, detections[1].engine); + assert_eq!("EICAR_TEST_FILE".to_string(), detections[1].virus); assert_eq!( - Sha256::try_from(&tmp_file).unwrap(), - verdict.unwrap().sha256 + Some(LibMagic { + file_type: "EICAR virus test files".to_string(), + mime_type: "text/plain".to_string() + }), + verdict.lib_magic ); std::fs::remove_file(&tmp_file).unwrap(); }