Skip to main content

adhammer_core/
sid.rs

1//! SID / GUID parsing per MS-DTYP. No FFI — pure binary + string handling.
2
3use std::fmt;
4
5/// Windows Security Identifier. Stored canonically; `Display` yields `S-1-5-...`.
6#[derive(Clone, PartialEq, Eq, Hash)]
7pub struct Sid {
8    pub revision: u8,
9    pub identifier_authority: u64, // 6 bytes, big-endian in the wire format
10    pub sub_authorities: Vec<u32>,
11}
12
13impl Sid {
14    /// Parse the binary (SID_AND_ATTRIBUTES / objectSid) representation.
15    pub fn from_bytes(b: &[u8]) -> Option<Sid> {
16        if b.len() < 8 {
17            return None;
18        }
19        let revision = b[0];
20        let count = b[1] as usize;
21        let mut authority: u64 = 0;
22        for &byte in &b[2..8] {
23            authority = (authority << 8) | byte as u64; // big-endian
24        }
25        if b.len() < 8 + count * 4 {
26            return None;
27        }
28        let mut subs = Vec::with_capacity(count);
29        for i in 0..count {
30            let off = 8 + i * 4;
31            subs.push(u32::from_le_bytes([b[off], b[off + 1], b[off + 2], b[off + 3]]));
32        }
33        Some(Sid { revision, identifier_authority: authority, sub_authorities: subs })
34    }
35
36    /// Parse the string form `S-1-5-21-...-513`.
37    pub fn parse(s: &str) -> Option<Sid> {
38        let mut it = s.split('-');
39        if it.next()? != "S" {
40            return None;
41        }
42        let revision = it.next()?.parse().ok()?;
43        let identifier_authority = it.next()?.parse().ok()?;
44        let sub_authorities = it.map(|p| p.parse().ok()).collect::<Option<Vec<u32>>>()?;
45        Some(Sid { revision, identifier_authority, sub_authorities })
46    }
47
48    /// Last sub-authority — the RID.
49    pub fn rid(&self) -> Option<u32> {
50        self.sub_authorities.last().copied()
51    }
52
53    /// True for the built-in / well-known SIDs that are never domain-specific
54    /// (S-1-5-32-544 etc.). Used to skip noise when scoring ACLs.
55    pub fn is_well_known(&self) -> bool {
56        matches!(self.identifier_authority, 1 | 3) // WORLD, CREATOR
57            || (self.identifier_authority == 5 && self.sub_authorities.first() == Some(&32))
58    }
59}
60
61impl fmt::Display for Sid {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        write!(f, "S-{}-{}", self.revision, self.identifier_authority)?;
64        for s in &self.sub_authorities {
65            write!(f, "-{s}")?;
66        }
67        Ok(())
68    }
69}
70
71impl fmt::Debug for Sid {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        write!(f, "{self}")
74    }
75}
76
77/// Well-known RIDs (relative to the domain SID unless noted).
78pub mod rid {
79    pub const ADMINISTRATOR: u32 = 500;
80    pub const GUEST: u32 = 501;
81    pub const KRBTGT: u32 = 502;
82    pub const DOMAIN_ADMINS: u32 = 512;
83    pub const DOMAIN_CONTROLLERS: u32 = 516;
84    pub const SCHEMA_ADMINS: u32 = 518;
85    pub const ENTERPRISE_ADMINS: u32 = 519;
86    pub const ADMINISTRATORS_BUILTIN: u32 = 544; // under S-1-5-32
87}
88
89/// 16-byte GUID, stored in the mixed-endian on-wire layout already normalized
90/// to a comparable byte array. Rendered `{aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee}`.
91#[derive(Clone, Copy, PartialEq, Eq, Hash)]
92pub struct Guid(pub [u8; 16]);
93
94impl Guid {
95    pub fn from_bytes(b: &[u8]) -> Option<Guid> {
96        Some(Guid(b.get(..16)?.try_into().ok()?))
97    }
98
99    /// Parse `1131f6aa-9c07-11d1-f79f-00c04fc2dcd2` (braces optional).
100    pub fn parse(s: &str) -> Option<Guid> {
101        let s = s.trim_matches(|c| c == '{' || c == '}');
102        let hex: String = s.chars().filter(|c| *c != '-').collect();
103        if hex.len() != 32 {
104            return None;
105        }
106        let raw: Vec<u8> = (0..16)
107            .map(|i| u8::from_str_radix(&hex[i * 2..i * 2 + 2], 16).ok())
108            .collect::<Option<_>>()?;
109        // string form is big-endian for first 3 groups; store as-is for compare.
110        let mut b = [0u8; 16];
111        b[0..4].copy_from_slice(&[raw[3], raw[2], raw[1], raw[0]]);
112        b[4..6].copy_from_slice(&[raw[5], raw[4]]);
113        b[6..8].copy_from_slice(&[raw[7], raw[6]]);
114        b[8..16].copy_from_slice(&raw[8..16]);
115        Some(Guid(b))
116    }
117}
118
119impl fmt::Display for Guid {
120    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121        let b = &self.0;
122        write!(
123            f,
124            "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
125            b[3], b[2], b[1], b[0], b[5], b[4], b[7], b[6],
126            b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]
127        )
128    }
129}
130
131impl fmt::Debug for Guid {
132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133        write!(f, "{self}")
134    }
135}