pub mod envelope;
pub mod stix;
#[derive(Debug, thiserror::Error)]
pub enum ThreatModelError {
#[error("threat-model: sqlite: {0}")]
Sqlite(#[from] rusqlite::Error),
#[error("threat-model: json: {0}")]
Json(#[from] serde_json::Error),
#[error("threat-model: io: {0}")]
Io(#[from] std::io::Error),
#[error("threat-model: STIX parse: {path}: {source}")]
StixParse {
path: std::path::PathBuf,
#[source]
source: serde_json::Error,
},
#[error("threat-model: findings-db build failed — {0:#}")]
DbBuildFailed(#[source] anyhow::Error),
#[error("threat-model: invalid value for {field}: {value}")]
InvalidValue {
field: &'static str,
value: String,
},
}
pub type Result<T> = std::result::Result<T, ThreatModelError>;
pub fn parse_acquisition_metadata(
acquired_from: Option<&str>,
operator: Option<&str>,
case_ref: Option<&str>,
acquired_at: Option<&str>,
) -> Result<droidsaw_common::threat_model::AcquisitionMetadata> {
use droidsaw_common::threat_model::{AcquisitionMetadata, AcquisitionSource};
let source_kind = match acquired_from {
None => AcquisitionSource::Unknown,
Some("adb_pull") => AcquisitionSource::AdbPull,
Some("file_upload") => AcquisitionSource::FileUpload,
Some("download_url") => AcquisitionSource::DownloadUrl,
Some("device_image") => AcquisitionSource::DeviceImage,
Some("unknown") => AcquisitionSource::Unknown,
Some(other) => {
return Err(ThreatModelError::InvalidValue {
field: "acquired-from",
value: other.to_string(),
})
}
};
let acquired_at_parsed = match acquired_at {
None => None,
Some(s) => Some(s.parse::<chrono::DateTime<chrono::Utc>>().map_err(|e| {
ThreatModelError::InvalidValue {
field: "acquired-at",
value: format!("{s} ({e})"),
}
})?),
};
Ok(AcquisitionMetadata {
source_kind,
operator: operator.map(str::to_string),
authority_ref: case_ref.map(str::to_string),
acquired_at: acquired_at_parsed,
pre_analysis_hash: None,
})
}
pub fn write_unsigned_evidence(
findings: &[droidsaw_common::Finding],
acquisition: &droidsaw_common::threat_model::AcquisitionMetadata,
tool_version: &str,
out_dir: &std::path::Path,
) -> Result<UnsignedEvidencePaths> {
std::fs::create_dir_all(out_dir)?;
let db_path = out_dir.join("findings.db");
drop(std::fs::remove_file(&db_path));
crate::commands::write_findings_db(findings, &db_path)
.map_err(ThreatModelError::DbBuildFailed)?;
let conn = rusqlite::Connection::open(&db_path)?;
let env = envelope::produce_unsigned_envelope(&conn, acquisition, tool_version)?;
let envelope_json_path = out_dir.join("envelope.json");
let findings_ndjson_path = out_dir.join("findings.ndjson");
std::fs::write(&envelope_json_path, serde_json::to_vec_pretty(&env)?)?;
std::fs::write(&findings_ndjson_path, &env.findings_ndjson)?;
Ok(UnsignedEvidencePaths {
envelope_json: envelope_json_path,
findings_ndjson: findings_ndjson_path,
findings_db: db_path,
finding_count: env.finding_count,
finding_set_hash: env.finding_set_hash,
})
}
#[derive(Debug, Clone)]
pub struct UnsignedEvidencePaths {
pub envelope_json: std::path::PathBuf,
pub findings_ndjson: std::path::PathBuf,
pub findings_db: std::path::PathBuf,
pub finding_count: u64,
pub finding_set_hash: String,
}