use aes::Aes256;
use base64::{engine::general_purpose::STANDARD as B64, Engine as _};
use cfb_mode::cipher::{AsyncStreamCipher, KeyIvInit};
use serde::Deserialize;
use tracing::debug;
use super::client::{Interaction, InteractionProtocol, InteractshError};
type Aes256CfbDec = cfb_mode::Decryptor<Aes256>;
#[derive(Deserialize, Default)]
#[serde(default)]
struct InteractionRaw {
protocol: String,
#[serde(rename = "unique-id")]
unique_id: String,
#[serde(rename = "full-id")]
full_id: String,
#[serde(rename = "remote-address")]
remote_address: String,
timestamp: String,
#[serde(rename = "raw-request")]
raw_request: String,
#[serde(rename = "raw-response")]
raw_response: String,
#[serde(rename = "q-type")]
q_type: String,
}
const MAX_RAW_PAYLOAD: usize = 16 * 1024;
pub(super) fn decrypt_entry(
aes_key: &[u8],
b64: &str,
) -> Result<Option<Interaction>, InteractshError> {
let bytes = B64
.decode(b64.as_bytes())
.map_err(|e| InteractshError::Decrypt(format!("base64: {e}")))?;
if bytes.len() < 16 {
return Err(InteractshError::Decrypt(format!(
"ciphertext too short ({} < 16)",
bytes.len()
)));
}
let (iv, ct) = bytes.split_at(16);
let mut buf = ct.to_vec();
Aes256CfbDec::new_from_slices(aes_key, iv)
.map_err(|e| InteractshError::Decrypt(format!("cfb init: {e}")))?
.decrypt(&mut buf);
let json = match std::str::from_utf8(&buf) {
Ok(s) => s,
Err(_) => return Ok(None), };
let raw: InteractionRaw = match serde_json::from_str(json) {
Ok(v) => v,
Err(e) => {
debug!(target: "keyhog::oob", error = %e, "interactsh JSON parse failed; skipping entry");
return Ok(None);
}
};
let unique_id = if !raw.full_id.is_empty() {
raw.full_id
} else {
raw.unique_id
};
if unique_id.is_empty() {
return Ok(None);
}
let raw_payload = if !raw.raw_request.is_empty() {
raw.raw_request
} else if !raw.raw_response.is_empty() {
raw.raw_response
} else {
raw.q_type
};
let raw_payload: String = raw_payload.chars().take(MAX_RAW_PAYLOAD).collect();
Ok(Some(Interaction {
unique_id,
protocol: InteractionProtocol::parse(&raw.protocol),
remote_address: raw.remote_address,
timestamp: raw.timestamp,
raw_payload,
}))
}