feat(license): add gyandoot-license crate + CLI subcommand
- New crate: gyandoot-license with: - Machine fingerprinting (CPU, MAC, Disk, hostname) - Ed25519 JWT signing and verification - Agent counting with RAII guards - AES-256-GCM encrypted license cache - Anti-tamper binary integrity checks - Debugger detection - Added to workspace Cargo.toml - Added 'gyandoot license' CLI subcommand (activate, status, deactivate, fingerprint)
This commit is contained in:
118
crates/gyandoot-license/src/anti_tamper.rs
Normal file
118
crates/gyandoot-license/src/anti_tamper.rs
Normal file
@@ -0,0 +1,118 @@
|
||||
//! Anti-Tamper & Debugger Detection
|
||||
//!
|
||||
//! Prevents the binary from being modified or debugged.
|
||||
//! - Binary integrity hash verification
|
||||
//! - Debugger detection (ptrace)
|
||||
//! - String obfuscation helpers
|
||||
|
||||
use crate::LicenseError;
|
||||
use sha2::{Sha256, Digest};
|
||||
|
||||
/// Compute SHA-256 hash of the current binary
|
||||
pub fn current_binary_hash() -> String {
|
||||
let exe_path = std::env::current_exe().unwrap_or_else(|_| std::path::PathBuf::from("unknown"));
|
||||
if let Ok(data) = std::fs::read(&exe_path) {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(&data);
|
||||
hex::encode(hasher.finalize())
|
||||
} else {
|
||||
"unknown_binary".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify binary integrity against an expected hash
|
||||
pub fn verify_binary(expected_hash: &str) -> Result<(), LicenseError> {
|
||||
let actual = current_binary_hash();
|
||||
if actual != expected_hash {
|
||||
return Err(LicenseError::BinaryTampered);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Detect if a debugger is attached
|
||||
pub fn detect_debugger() -> bool {
|
||||
// Linux: check if being ptraced
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
// Try ptrace(PTRACE_TRACEME, ...) — if it fails, we're being debugged
|
||||
let result = unsafe { libc::ptrace(libc::PTRACE_TRACEME, 0, 0, 0) };
|
||||
if result == -1 {
|
||||
return true; // Being traced
|
||||
}
|
||||
// Detach
|
||||
unsafe { libc::ptrace(libc::PTRACE_DETACH, 0, 0, 0) };
|
||||
}
|
||||
|
||||
// Check for common debugger environment variables
|
||||
let debug_env_vars = [
|
||||
"DEBUG", "DEBUGGER", "JAVA_DEBUG", "PYTHONDEBUG",
|
||||
"TRACE", "TRACEME", "LD_PRELOAD",
|
||||
];
|
||||
for var in &debug_env_vars {
|
||||
if std::env::var(var).is_ok() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for /proc/self/status TracerPid
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if let Ok(content) = std::fs::read_to_string("/proc/self/status") {
|
||||
for line in content.lines() {
|
||||
if line.starts_with("TracerPid:") {
|
||||
let pid = line.trim_start_matches("TracerPid:").trim();
|
||||
if pid != "0" {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Run all anti-tamper checks at startup
|
||||
pub fn run_startup_checks(expected_binary_hash: &str) -> Result<(), LicenseError> {
|
||||
// Check 1: Debugger
|
||||
if detect_debugger() {
|
||||
return Err(LicenseError::DebuggerDetected);
|
||||
}
|
||||
|
||||
// Check 2: Binary integrity
|
||||
verify_binary(expected_binary_hash)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Obfuscate a string literal (compile-time)
|
||||
/// Usage: obfstr!("sensitive_string")
|
||||
/// In production, use the `obfstr` crate for true compile-time obfuscation
|
||||
#[macro_export]
|
||||
macro_rules! obf {
|
||||
($s:expr) => {{
|
||||
let bytes: &[u8] = $s.as_bytes();
|
||||
let mut obfuscated: Vec<u8> = bytes.iter().map(|b| b ^ 0xAA).collect();
|
||||
let deobf: Vec<u8> = obfuscated.iter().map(|b| b ^ 0xAA).collect();
|
||||
String::from_utf8(deobf).unwrap_or_default()
|
||||
}};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_binary_hash_not_empty() {
|
||||
let hash = current_binary_hash();
|
||||
assert!(!hash.is_empty());
|
||||
assert_eq!(hash.len(), 64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_obf_macro() {
|
||||
let original = "license.gyannetra.com";
|
||||
let result = obf!(original);
|
||||
assert_eq!(result, original);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user