use crate::Result;
use crate::error::{Error, SecurityError, SidParseError};
use std::fmt::Write;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Sid {
raw: Vec<u8>,
string: String,
}
impl Sid {
pub fn from_bytes(data: &[u8]) -> Result<Self> {
let (sid_string, consumed) = sid_to_string_with_size(data).ok_or_else(|| {
Error::Security(SecurityError::SidParse(SidParseError::new(
"binary_sid",
"SID data is malformed or truncated",
)))
})?;
if consumed != data.len() {
return Err(Error::Security(SecurityError::SidParse(
SidParseError::new("binary_sid", "SID buffer contains trailing bytes"),
)));
}
Ok(Self {
raw: data.to_vec(),
string: sid_string,
})
}
pub fn parse(value: &str) -> Result<Self> {
let raw = sid_string_to_bytes(value)?;
Ok(Self {
raw,
string: value.to_string(),
})
}
pub fn from_sddl_trustee(value: &str) -> Result<Self> {
if value.starts_with("S-") {
return Self::parse(value);
}
let sid_string = sddl_alias_to_sid(value).ok_or_else(|| {
Error::Security(SecurityError::SidParse(SidParseError::new(
value.to_string(),
"unrecognized SDDL trustee alias",
)))
})?;
Self::parse(sid_string)
}
pub fn as_str(&self) -> &str {
&self.string
}
pub fn as_bytes(&self) -> &[u8] {
&self.raw
}
pub fn to_sddl_alias(&self) -> Option<&'static str> {
sid_to_sddl_alias(self.as_str())
}
pub fn eq_case_insensitive(&self, other: &str) -> bool {
self.string.eq_ignore_ascii_case(other)
}
}
fn sddl_alias_to_sid(value: &str) -> Option<&'static str> {
match value {
"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"),
_ => None,
}
}
fn sid_to_sddl_alias(value: &str) -> Option<&'static str> {
match value {
"S-1-5-18" => Some("SY"),
"S-1-5-32-544" => Some("BA"),
"S-1-5-32-545" => Some("BU"),
"S-1-5-32-546" => Some("BG"),
"S-1-5-32-547" => Some("PU"),
"S-1-5-32-548" => Some("AO"),
"S-1-5-32-549" => Some("SO"),
"S-1-5-32-550" => Some("PO"),
"S-1-5-32-551" => Some("BO"),
"S-1-5-32-552" => Some("RE"),
"S-1-1-0" => Some("WD"),
"S-1-5-11" => Some("AU"),
"S-1-5-7" => Some("AN"),
"S-1-5-2" => Some("NU"),
"S-1-5-4" => Some("IU"),
"S-1-5-6" => Some("SU"),
"S-1-5-19" => Some("LS"),
"S-1-5-20" => Some("NS"),
"S-1-3-0" => Some("CO"),
"S-1-3-1" => Some("CG"),
"S-1-3-4" => Some("OW"),
"S-1-15-2-1" => Some("AC"),
_ => None,
}
}
impl std::fmt::Display for Sid {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.string)
}
}
fn sid_string_to_bytes(value: &str) -> Result<Vec<u8>> {
let parts: Vec<&str> = value.split('-').collect();
if parts.len() < 3 || !parts[0].eq_ignore_ascii_case("S") {
return Err(Error::Security(SecurityError::SidParse(
SidParseError::new(
value.to_string(),
"expected SID format S-Revision-IdentifierAuthority-SubAuthority...",
),
)));
}
let revision = parts[1].parse::<u8>().map_err(|_| {
Error::Security(SecurityError::SidParse(SidParseError::new(
value.to_string(),
"invalid SID revision",
)))
})?;
let identifier_authority = parts[2].parse::<u64>().map_err(|_| {
Error::Security(SecurityError::SidParse(SidParseError::new(
value.to_string(),
"invalid identifier authority",
)))
})?;
if identifier_authority > 0x0000_FFFF_FFFF {
return Err(Error::Security(SecurityError::SidParse(
SidParseError::new(
value.to_string(),
"identifier authority must fit in 48 bits",
),
)));
}
let sub_authority_count = parts.len().saturating_sub(3);
if sub_authority_count > u8::MAX as usize {
return Err(Error::Security(SecurityError::SidParse(
SidParseError::new(value.to_string(), "too many sub-authorities"),
)));
}
let mut out = Vec::with_capacity(8 + sub_authority_count * 4);
out.push(revision);
out.push(sub_authority_count as u8);
let authority_be = identifier_authority.to_be_bytes();
out.extend_from_slice(&authority_be[2..]);
for part in parts.iter().skip(3) {
let sub = part.parse::<u32>().map_err(|_| {
Error::Security(SecurityError::SidParse(SidParseError::new(
value.to_string(),
"invalid sub-authority value",
)))
})?;
out.extend_from_slice(&sub.to_le_bytes());
}
Ok(out)
}
fn sid_to_string_with_size(sid: &[u8]) -> Option<(String, usize)> {
if sid.len() < 8 {
return None;
}
let mut id = String::with_capacity(32);
let subauthority_count = sid[1] as usize;
let mut identifier_authority = (u16::from_be_bytes([sid[2], sid[3]]) as u64) << 32;
identifier_authority |= u32::from_be_bytes([sid[4], sid[5], sid[6], sid[7]]) as u64;
let _ = write!(&mut id, "S-{}-{}", sid[0], identifier_authority);
let mut start = 8usize;
for _ in 0..subauthority_count {
if start + 4 > sid.len() {
return None;
}
let authority =
u32::from_le_bytes([sid[start], sid[start + 1], sid[start + 2], sid[start + 3]]);
let _ = write!(&mut id, "-{}", authority);
start += 4;
}
Some((id, start))
}
#[cfg(test)]
mod tests {
use super::Sid;
#[test]
fn sid_round_trip_string_binary() {
let sid = Sid::parse("S-1-5-32-544").expect("parse should succeed");
let sid2 = Sid::from_bytes(sid.as_bytes()).expect("binary parse should succeed");
assert_eq!(sid.as_str(), sid2.as_str());
assert_eq!(sid.as_bytes(), sid2.as_bytes());
}
#[test]
fn sid_parse_invalid_format() {
let err = Sid::parse("not-a-sid").expect_err("expected parse error");
assert!(err.to_string().contains("expected SID format"));
}
#[test]
fn sid_parse_from_sddl_alias() {
let sid = Sid::from_sddl_trustee("BA").expect("alias parse should succeed");
assert_eq!(sid.as_str(), "S-1-5-32-544");
}
#[test]
fn sid_to_sddl_alias_round_trip() {
let sid = Sid::parse("S-1-5-32-545").expect("parse should succeed");
assert_eq!(sid.to_sddl_alias(), Some("BU"));
}
}