mirror of
https://github.com/hpd840321/craftlabs-authorization-sdk.git
synced 2026-06-09 10:00:30 +08:00
155 lines
4.2 KiB
Rust
155 lines
4.2 KiB
Rust
use sha2::{Digest, Sha256};
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct FingerprintLayer {
|
|
pub source: &'static str,
|
|
pub value: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct DeviceFingerprint {
|
|
pub composite_hash: String,
|
|
pub stability_score: u8,
|
|
pub layers: Vec<FingerprintLayer>,
|
|
}
|
|
|
|
pub fn collect() -> DeviceFingerprint {
|
|
let mut layers = Vec::new();
|
|
|
|
let l1 = try_dmi_uuid();
|
|
layers.push(FingerprintLayer { source: "hw_uuid", value: l1 });
|
|
|
|
let l2 = try_machine_id();
|
|
layers.push(FingerprintLayer { source: "os_id", value: l2 });
|
|
|
|
let l3 = try_rootfs_uuid();
|
|
layers.push(FingerprintLayer { source: "fs_uuid", value: l3 });
|
|
|
|
let l4 = Some(try_physical_mac());
|
|
layers.push(FingerprintLayer { source: "mac", value: l4 });
|
|
|
|
let mut hasher = Sha256::new();
|
|
for layer in &layers {
|
|
if let Some(ref v) = layer.value {
|
|
hasher.update(v.as_bytes());
|
|
}
|
|
hasher.update(b"|");
|
|
}
|
|
let hash = format!("HC-{:x}", hasher.finalize());
|
|
|
|
let stability = compute_stability(&layers);
|
|
|
|
DeviceFingerprint {
|
|
composite_hash: hash,
|
|
stability_score: stability,
|
|
layers,
|
|
}
|
|
}
|
|
|
|
fn compute_stability(layers: &[FingerprintLayer]) -> u8 {
|
|
let mut score: u8 = 0;
|
|
if layers[0].value.is_some() { score += 40; }
|
|
if layers[1].value.is_some() { score += 30; }
|
|
if layers[2].value.is_some() { score += 20; }
|
|
if layers[3].value.is_some() { score += 10; }
|
|
score
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
fn try_dmi_uuid() -> Option<String> {
|
|
std::fs::read_to_string("/sys/class/dmi/id/product_uuid")
|
|
.map(|s| s.trim().to_string())
|
|
.ok()
|
|
.filter(|s| !s.is_empty() && s != "00000000-0000-0000-0000-000000000000")
|
|
}
|
|
|
|
#[cfg(not(target_os = "linux"))]
|
|
fn try_dmi_uuid() -> Option<String> {
|
|
None
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
fn try_machine_id() -> Option<String> {
|
|
std::fs::read_to_string("/etc/machine-id")
|
|
.or_else(|_| std::fs::read_to_string("/var/lib/dbus/machine-id"))
|
|
.map(|s| s.trim().to_string())
|
|
.ok()
|
|
.filter(|s| !s.is_empty())
|
|
}
|
|
|
|
#[cfg(not(target_os = "linux"))]
|
|
fn try_machine_id() -> Option<String> {
|
|
None
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
fn try_rootfs_uuid() -> Option<String> {
|
|
std::fs::read_to_string("/proc/cmdline")
|
|
.ok()
|
|
.and_then(|cmdline| {
|
|
for part in cmdline.split_whitespace() {
|
|
if part.starts_with("root=") {
|
|
return Some(part.trim_start_matches("root=").to_string());
|
|
}
|
|
}
|
|
None
|
|
})
|
|
}
|
|
|
|
#[cfg(not(target_os = "linux"))]
|
|
fn try_rootfs_uuid() -> Option<String> {
|
|
None
|
|
}
|
|
|
|
fn try_physical_mac() -> String {
|
|
#[cfg(target_os = "linux")]
|
|
{
|
|
let mut macs: Vec<String> = Vec::new();
|
|
if let Ok(entries) = std::fs::read_dir("/sys/class/net") {
|
|
for entry in entries.flatten() {
|
|
let iface = entry.file_name().to_string_lossy().to_string();
|
|
if iface == "lo" || iface.starts_with("docker") || iface.starts_with("veth") || iface.starts_with("tap") {
|
|
continue;
|
|
}
|
|
if let Ok(addr) = std::fs::read_to_string(entry.path().join("address")) {
|
|
let a = addr.trim().to_string();
|
|
if !a.is_empty() && a != "00:00:00:00:00:00" {
|
|
macs.push(a);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if macs.is_empty() { "unknown-mac".to_string() } else { macs.sort(); macs.join(",") }
|
|
}
|
|
#[cfg(not(target_os = "linux"))]
|
|
{ "unknown-mac".to_string() }
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_collect_returns_valid_structure() {
|
|
let fp = collect();
|
|
assert!(fp.composite_hash.starts_with("HC-"));
|
|
assert_eq!(fp.layers.len(), 4);
|
|
for layer in &fp.layers {
|
|
assert!(matches!(layer.source, "hw_uuid" | "os_id" | "fs_uuid" | "mac"));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_stability_score_never_exceeds_100() {
|
|
let fp = collect();
|
|
assert!(fp.stability_score <= 100);
|
|
}
|
|
|
|
#[test]
|
|
fn test_composite_hash_deterministic() {
|
|
let fp1 = collect();
|
|
let fp2 = collect();
|
|
assert_eq!(fp1.composite_hash, fp2.composite_hash);
|
|
}
|
|
}
|