Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Non-sensitive info
DTIM__DEFAULT__ADDRESS="0.0.0.0"
DTIM__DEFAULT__PORT=3030
DTIM__DEFAULT__LOG_LEVEL="info"

# Sensitive info (.env only)
DTIM__DEFAULT__STORAGE__DATABASE_URL="postgres://user:pass@localhost/db"
DTIM__DEFAULT__WATCHERS__VIRUSTOTAL_API_KEY="your_api_key"
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,6 @@ ed25519-dalek = { version = "2.1", features = ["rand_core"] }
sha2 = "0.10"
hex = "0.4"
async-trait = "0.1"
http-body-util = "0.1"
http-body-util = "0.1"
dotenvy = "0.15"
once_cell = "1.21"
24 changes: 6 additions & 18 deletions src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ use ed25519_dalek::VerifyingKey;
use http_body_util::BodyExt as _;
use rustls::ServerConfig;
use serde::Serialize;
use std::sync::Arc;
use std::{io, str::FromStr};
use std::{str::FromStr as _, sync::Arc};
use tokio::sync::Mutex;

use crate::{
Expand All @@ -31,7 +30,6 @@ use crate::{
#[derive(Clone)]
pub struct AppState {
pub node: Arc<Mutex<Node>>,
pub mesh_identity: MeshIdentity,
pub key_mgr: SymmetricKeyManager,
}

Expand Down Expand Up @@ -83,7 +81,7 @@ async fn authorize_client_node(
if valid {
Some(MeshIdentity::Remote {
id: node_id,
verifying_key,
verifying_key: Box::new(verifying_key),
})
} else {
None
Expand All @@ -94,10 +92,9 @@ pub async fn start_server(
node: Arc<Mutex<Node>>,
key_mgr: SymmetricKeyManager,
config: Arc<ServerConfig>,
address: String,
port: u16,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let mesh_identity = node.lock().await.identity().clone();

let app = Router::new()
// Peer registry endpoints
.route("/api/v1/echo", post(echo_handler))
Expand Down Expand Up @@ -128,24 +125,15 @@ pub async fn start_server(
"/taxii2/root/collections/{id}/objects/",
post(taxii_post_objects_handler),
)
.with_state(Arc::new(AppState {
node,
mesh_identity,
key_mgr,
}))
.with_state(Arc::new(AppState { node, key_mgr }))
.layer(middleware::from_fn(auth));

let addr = format!("0.0.0.0:{}", port).parse()?;
let addr = format!("{}:{}", address, port).parse()?;
let tls_config = RustlsConfig::from_config(config);
axum_server::bind_rustls(addr, tls_config)
.serve(app.into_make_service())
.await
.map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
format!("Failed to start server: {}", e),
)
})?;
.map_err(|e| std::io::Error::other(format!("Failed to start server: {}", e)))?;
Ok(())
}

Expand Down
12 changes: 6 additions & 6 deletions src/crypto/mesh_identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ pub const PUBLIC_KEY_PATH: &str = "data/keys/mesh.pub";
pub enum MeshIdentity {
Local {
id: String,
verifying_key: VerifyingKey,
signing_key: SigningKey,
verifying_key: Box<VerifyingKey>,
signing_key: Box<SigningKey>,
},
Remote {
id: String,
verifying_key: VerifyingKey,
verifying_key: Box<VerifyingKey>,
},
}

Expand All @@ -36,8 +36,8 @@ impl MeshIdentity {
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
Ok(Self::Local {
id: MeshIdentity::derive_hex_id(&pub_bytes),
signing_key,
verifying_key,
signing_key: Box::new(signing_key),
verifying_key: Box::new(verifying_key),
})
}

Expand Down Expand Up @@ -73,7 +73,7 @@ impl MeshIdentity {

pub fn derive_hex_id(pubkey_bytes: &[u8; 32]) -> String {
let hash = Sha256::digest(pubkey_bytes);
hex::encode(&hash)
hex::encode(hash)
}

pub fn id(&self) -> &String {
Expand Down
4 changes: 2 additions & 2 deletions src/crypto/symmetric_key_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ impl SymmetricKeyManager {
fn generate_key() -> Key<Aes256Gcm> {
let mut key_bytes = [0u8; 32];
OsRng.fill_bytes(&mut key_bytes);
Key::<Aes256Gcm>::from_slice(&key_bytes).clone()
Key::<Aes256Gcm>::from_slice(&key_bytes).to_owned()
}

pub fn rotate_key(&mut self) {
Expand All @@ -39,7 +39,7 @@ impl SymmetricKeyManager {
.unwrap()
.as_secs();
if now - self.key_rotation_time >= self.rotation_interval {
self.previous_key = Some(self.current_key.clone());
self.previous_key = Some(self.current_key);
self.current_key = Self::generate_key();
self.key_rotation_time = now;
}
Expand Down
33 changes: 17 additions & 16 deletions src/logging.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use chrono::Utc;
use log::{error, Level, LevelFilter, Metadata, Record};
use std::fs::{self, OpenOptions};
use std::io::{self, Write};
use std::io::Write as _;
use std::path::PathBuf;

use crate::crypto::SymmetricKeyManager;
Expand All @@ -18,7 +18,7 @@ impl EncryptedLogger {
log_path: PathBuf,
key_mgr: SymmetricKeyManager,
level: LevelFilter,
) -> io::Result<Self> {
) -> std::io::Result<Self> {
if let Some(parent) = log_path.parent() {
fs::create_dir_all(parent)?;
}
Expand All @@ -29,13 +29,14 @@ impl EncryptedLogger {
})
}

pub fn write_log(&mut self, level: Level, message: &str) -> io::Result<()> {
pub fn write_log(&mut self, level: Level, message: &str) -> std::io::Result<()> {
let timestamp = Utc::now().to_rfc3339();
let log_entry = format!("[{}] [{}] {}\n", timestamp, level, message);

let (ciphertext, nonce, mac) = self.key_mgr.encrypt(log_entry.as_bytes()).map_err(|e| {
io::Error::new(io::ErrorKind::Other, format!("Encryption failed: {}", e))
})?;
let (ciphertext, nonce, mac) = self
.key_mgr
.encrypt(log_entry.as_bytes())
.map_err(|e| std::io::Error::other(format!("Encryption failed: {}", e)))?;
let encrypted_entry = format!("{}\n{}\n{}\n", ciphertext, nonce, mac);

let filename = format!("{}.log", Utc::now().format("%Y-%m-%d"));
Expand All @@ -52,7 +53,7 @@ impl EncryptedLogger {
Ok(())
}

pub fn read_logs(&self, date: &str) -> io::Result<Vec<String>> {
pub fn read_logs(&self, date: &str) -> std::io::Result<Vec<String>> {
let filename = format!("{}.log", date);
let log_file = self.log_path.join(filename);

Expand All @@ -65,15 +66,15 @@ impl EncryptedLogger {

let mut lines = content.lines().peekable();
while lines.peek().is_some() {
let ciphertext = lines
.next()
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Invalid log format"))?;
let nonce = lines
.next()
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Invalid log format"))?;
let mac = lines
.next()
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Invalid log format"))?;
let ciphertext = lines.next().ok_or_else(|| {
std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid log format")
})?;
let nonce = lines.next().ok_or_else(|| {
std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid log format")
})?;
let mac = lines.next().ok_or_else(|| {
std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid log format")
})?;

match self.key_mgr.decrypt(ciphertext, nonce, mac) {
Ok(decrypted) => {
Expand Down
26 changes: 18 additions & 8 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod uuid;
use axum::body::Body;
use base64::prelude::BASE64_STANDARD;
use base64::Engine;
use crypto::MeshIdentity;
use http_body_util::BodyExt as _;
use log::LevelFilter;
use models::{IndicatorType, ThreatIndicator};
Expand Down Expand Up @@ -75,22 +76,27 @@ fn make_server_config(settings: &settings::Settings) -> Arc<rustls::ServerConfig
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
init_logging();
let settings = Settings::new()?;
let mesh_identity = crypto::MeshIdentity::load_or_generate()?;
let mut key_mgr = crypto::SymmetricKeyManager::new(settings.tls.key_rotation_days);
let tls_config = make_server_config(&settings);

let logger = logging::EncryptedLogger::new(
settings.storage.encrypted_logs_path.clone(),
key_mgr.clone(),
LevelFilter::from_str(&settings.log_level).unwrap(),
LevelFilter::from_str(&settings.log_level).unwrap_or_else(|_| {
log::warn!(
"Invalid log level: {}, defaulting to Info",
settings.log_level
);
LevelFilter::Info
}),
)?;

let node = node::Node::new(mesh_identity.clone(), logger, settings.privacy);
let node = node::Node::new(logger, settings.privacy)?;

let id = node.get_id();
println!("Node ID: {:?}", id);

let base64_pubkey = BASE64_STANDARD.encode(mesh_identity.verifying_key().to_bytes());
let base64_pubkey = BASE64_STANDARD.encode(node.identity().verifying_key().to_bytes());

let mut data = NodePeer {
id: id.to_string(),
Expand All @@ -107,8 +113,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
.expect("Failed to collect body")
.to_bytes();

let signature =
crypto::MeshIdentity::sign(mesh_identity.signing_key().unwrap().clone(), &bytes);
let signing_key = node
.identity()
.signing_key()
.ok_or_else(|| std::io::Error::other("Failed to get signing key".to_string()))?;
let signature = MeshIdentity::sign(signing_key.clone(), &bytes);

data.set_signature(signature);

Expand Down Expand Up @@ -137,8 +146,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
node.bootstrap_peers(settings.network.init_peers.clone());
}

let server_handle =
tokio::spawn(async move { api::start_server(node, key_mgr, tls_config, 3030).await });
let server_handle = tokio::spawn(async move {
api::start_server(node, key_mgr, tls_config, settings.address, settings.port).await
});

server_handle.await??;

Expand Down
11 changes: 6 additions & 5 deletions src/models.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{
collections::{BTreeSet, HashMap},
fmt, io,
fmt,
net::IpAddr,
};

Expand Down Expand Up @@ -69,11 +69,11 @@ impl ThreatIndicator {
pub fn encrypt(
&self,
key_mgr: &mut SymmetricKeyManager,
) -> Result<EncryptedThreatIndicator, io::Error> {
) -> Result<EncryptedThreatIndicator, std::io::Error> {
let serialized = serde_json::to_vec(self).expect("Failed to serialize ThreatIndicator");
let (ciphertext, nonce, mac) = key_mgr.encrypt(&serialized).map_err(|e| {
io::Error::new(io::ErrorKind::Other, format!("Encryption failed: {}", e))
})?;
let (ciphertext, nonce, mac) = key_mgr
.encrypt(&serialized)
.map_err(|e| std::io::Error::other(format!("Encryption failed: {}", e)))?;

Ok(EncryptedThreatIndicator {
ciphertext,
Expand All @@ -94,6 +94,7 @@ impl ThreatIndicator {
.map_err(|e| format!("Failed to deserialize ThreatIndicator: {}", e))
}

#[allow(unused)] // TODO: implement in watchers
pub fn infer_type(value: &str) -> IndicatorType {
if let Ok(ip) = value.parse::<IpAddr>() {
return match ip {
Expand Down
22 changes: 7 additions & 15 deletions src/node.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::{
settings::PrivacyConfig,
crypto::MeshIdentity,
crypto::{self, MeshIdentity},
logging::EncryptedLogger,
models::{PrivacyLevel, ThreatIndicator, TlpLevel},
settings::PrivacyConfig,
uuid::Uuid,
};
use chrono::Utc;
Expand All @@ -20,8 +20,9 @@ pub struct Node {
}

impl Node {
pub fn new(identity: MeshIdentity, logger: EncryptedLogger, privacy: PrivacyConfig) -> Self {
Node {
pub fn new(logger: EncryptedLogger, privacy: PrivacyConfig) -> Result<Self, std::io::Error> {
let identity = crypto::MeshIdentity::load_or_generate()?;
Ok(Node {
identity,
indicators: HashMap::new(),
peers: HashMap::new(),
Expand All @@ -32,7 +33,7 @@ impl Node {
_ => PrivacyLevel::Moderate,
},
allow_custom_fields: privacy.allow_custom_fields,
}
})
}

pub fn identity(&self) -> &MeshIdentity {
Expand Down Expand Up @@ -107,8 +108,7 @@ impl Node {

let mut stix_indicators: Vec<serde_json::Value> = indicators
.iter()
.map(|i| i.to_stix(self.privacy_level, self.allow_custom_fields))
.filter_map(|i| i)
.filter_map(|i| i.to_stix(self.privacy_level, self.allow_custom_fields))
.collect();

let stix_mds: Vec<serde_json::Value> = indicators
Expand Down Expand Up @@ -147,14 +147,6 @@ impl NodePeer {
&self.endpoint
}

pub fn get_public_key(&self) -> &str {
&self.public_key
}

pub fn get_signature(&self) -> Option<&str> {
self.signature.as_deref()
}

pub fn set_signature(&mut self, signature: String) {
self.signature = Some(signature);
}
Expand Down
Loading
Loading