1use crate::Result;
4use crate::error::{Error, SecurityError, SidParseError};
5use std::fmt::Write;
6
7#[derive(Debug, Clone, PartialEq, Eq, Hash)]
9pub struct Sid {
10 raw: Vec<u8>,
11 string: String,
12}
13
14impl Sid {
15 pub fn from_bytes(data: &[u8]) -> Result<Self> {
17 let (sid_string, consumed) = sid_to_string_with_size(data).ok_or_else(|| {
18 Error::Security(SecurityError::SidParse(SidParseError::new(
19 "binary_sid",
20 "SID data is malformed or truncated",
21 )))
22 })?;
23
24 if consumed != data.len() {
25 return Err(Error::Security(SecurityError::SidParse(
26 SidParseError::new("binary_sid", "SID buffer contains trailing bytes"),
27 )));
28 }
29
30 Ok(Self {
31 raw: data.to_vec(),
32 string: sid_string,
33 })
34 }
35
36 pub fn parse(value: &str) -> Result<Self> {
38 let raw = sid_string_to_bytes(value)?;
39 Ok(Self {
40 raw,
41 string: value.to_string(),
42 })
43 }
44
45 pub fn from_sddl_trustee(value: &str) -> Result<Self> {
47 if value.starts_with("S-") {
48 return Self::parse(value);
49 }
50
51 let sid_string = sddl_alias_to_sid(value).ok_or_else(|| {
52 Error::Security(SecurityError::SidParse(SidParseError::new(
53 value.to_string(),
54 "unrecognized SDDL trustee alias",
55 )))
56 })?;
57
58 Self::parse(sid_string)
59 }
60
61 pub fn as_str(&self) -> &str {
63 &self.string
64 }
65
66 pub fn as_bytes(&self) -> &[u8] {
68 &self.raw
69 }
70
71 pub fn to_sddl_alias(&self) -> Option<&'static str> {
73 sid_to_sddl_alias(self.as_str())
74 }
75
76 pub fn eq_case_insensitive(&self, other: &str) -> bool {
78 self.string.eq_ignore_ascii_case(other)
79 }
80}
81
82fn sddl_alias_to_sid(value: &str) -> Option<&'static str> {
83 match value {
84 "SY" => Some("S-1-5-18"), "BA" => Some("S-1-5-32-544"), "BU" => Some("S-1-5-32-545"), "BG" => Some("S-1-5-32-546"), "PU" => Some("S-1-5-32-547"), "AO" => Some("S-1-5-32-548"), "SO" => Some("S-1-5-32-549"), "PO" => Some("S-1-5-32-550"), "BO" => Some("S-1-5-32-551"), "RE" => Some("S-1-5-32-552"), "WD" => Some("S-1-1-0"), "AU" => Some("S-1-5-11"), "AN" => Some("S-1-5-7"), "NU" => Some("S-1-5-2"), "IU" => Some("S-1-5-4"), "SU" => Some("S-1-5-6"), "LS" => Some("S-1-5-19"), "NS" => Some("S-1-5-20"), "CO" => Some("S-1-3-0"), "CG" => Some("S-1-3-1"), "OW" => Some("S-1-3-4"), "AC" => Some("S-1-15-2-1"), "S-1-5-80-0" => Some("S-1-5-80-0"),
107 _ => None,
108 }
109}
110
111fn sid_to_sddl_alias(value: &str) -> Option<&'static str> {
112 match value {
113 "S-1-5-18" => Some("SY"),
114 "S-1-5-32-544" => Some("BA"),
115 "S-1-5-32-545" => Some("BU"),
116 "S-1-5-32-546" => Some("BG"),
117 "S-1-5-32-547" => Some("PU"),
118 "S-1-5-32-548" => Some("AO"),
119 "S-1-5-32-549" => Some("SO"),
120 "S-1-5-32-550" => Some("PO"),
121 "S-1-5-32-551" => Some("BO"),
122 "S-1-5-32-552" => Some("RE"),
123 "S-1-1-0" => Some("WD"),
124 "S-1-5-11" => Some("AU"),
125 "S-1-5-7" => Some("AN"),
126 "S-1-5-2" => Some("NU"),
127 "S-1-5-4" => Some("IU"),
128 "S-1-5-6" => Some("SU"),
129 "S-1-5-19" => Some("LS"),
130 "S-1-5-20" => Some("NS"),
131 "S-1-3-0" => Some("CO"),
132 "S-1-3-1" => Some("CG"),
133 "S-1-3-4" => Some("OW"),
134 "S-1-15-2-1" => Some("AC"),
135 _ => None,
136 }
137}
138
139impl std::fmt::Display for Sid {
140 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141 write!(f, "{}", self.string)
142 }
143}
144
145fn sid_string_to_bytes(value: &str) -> Result<Vec<u8>> {
146 let parts: Vec<&str> = value.split('-').collect();
147 if parts.len() < 3 || !parts[0].eq_ignore_ascii_case("S") {
148 return Err(Error::Security(SecurityError::SidParse(
149 SidParseError::new(
150 value.to_string(),
151 "expected SID format S-Revision-IdentifierAuthority-SubAuthority...",
152 ),
153 )));
154 }
155
156 let revision = parts[1].parse::<u8>().map_err(|_| {
157 Error::Security(SecurityError::SidParse(SidParseError::new(
158 value.to_string(),
159 "invalid SID revision",
160 )))
161 })?;
162
163 let identifier_authority = parts[2].parse::<u64>().map_err(|_| {
164 Error::Security(SecurityError::SidParse(SidParseError::new(
165 value.to_string(),
166 "invalid identifier authority",
167 )))
168 })?;
169
170 if identifier_authority > 0x0000_FFFF_FFFF {
171 return Err(Error::Security(SecurityError::SidParse(
172 SidParseError::new(
173 value.to_string(),
174 "identifier authority must fit in 48 bits",
175 ),
176 )));
177 }
178
179 let sub_authority_count = parts.len().saturating_sub(3);
180 if sub_authority_count > u8::MAX as usize {
181 return Err(Error::Security(SecurityError::SidParse(
182 SidParseError::new(value.to_string(), "too many sub-authorities"),
183 )));
184 }
185
186 let mut out = Vec::with_capacity(8 + sub_authority_count * 4);
187 out.push(revision);
188 out.push(sub_authority_count as u8);
189
190 let authority_be = identifier_authority.to_be_bytes();
191 out.extend_from_slice(&authority_be[2..]);
192
193 for part in parts.iter().skip(3) {
194 let sub = part.parse::<u32>().map_err(|_| {
195 Error::Security(SecurityError::SidParse(SidParseError::new(
196 value.to_string(),
197 "invalid sub-authority value",
198 )))
199 })?;
200 out.extend_from_slice(&sub.to_le_bytes());
201 }
202
203 Ok(out)
204}
205
206fn sid_to_string_with_size(sid: &[u8]) -> Option<(String, usize)> {
207 if sid.len() < 8 {
208 return None;
209 }
210
211 let mut id = String::with_capacity(32);
212 let subauthority_count = sid[1] as usize;
213
214 let mut identifier_authority = (u16::from_be_bytes([sid[2], sid[3]]) as u64) << 32;
215 identifier_authority |= u32::from_be_bytes([sid[4], sid[5], sid[6], sid[7]]) as u64;
216
217 let _ = write!(&mut id, "S-{}-{}", sid[0], identifier_authority);
218
219 let mut start = 8usize;
220 for _ in 0..subauthority_count {
221 if start + 4 > sid.len() {
222 return None;
223 }
224
225 let authority =
226 u32::from_le_bytes([sid[start], sid[start + 1], sid[start + 2], sid[start + 3]]);
227 let _ = write!(&mut id, "-{}", authority);
228 start += 4;
229 }
230
231 Some((id, start))
232}
233
234#[cfg(test)]
235mod tests {
236 use super::Sid;
237
238 #[test]
239 fn sid_round_trip_string_binary() {
240 let sid = Sid::parse("S-1-5-32-544").expect("parse should succeed");
241 let sid2 = Sid::from_bytes(sid.as_bytes()).expect("binary parse should succeed");
242 assert_eq!(sid.as_str(), sid2.as_str());
243 assert_eq!(sid.as_bytes(), sid2.as_bytes());
244 }
245
246 #[test]
247 fn sid_parse_invalid_format() {
248 let err = Sid::parse("not-a-sid").expect_err("expected parse error");
249 assert!(err.to_string().contains("expected SID format"));
250 }
251
252 #[test]
253 fn sid_parse_from_sddl_alias() {
254 let sid = Sid::from_sddl_trustee("BA").expect("alias parse should succeed");
255 assert_eq!(sid.as_str(), "S-1-5-32-544");
256 }
257
258 #[test]
259 fn sid_to_sddl_alias_round_trip() {
260 let sid = Sid::parse("S-1-5-32-545").expect("parse should succeed");
261 assert_eq!(sid.to_sddl_alias(), Some("BU"));
262 }
263}