Skip to main content

dolang_winterop/security/
sid.rs

1use std::{error, fmt, str::FromStr};
2
3use serde::{
4    Deserialize, Deserializer, Serialize, Serializer,
5    de::{self, SeqAccess, Visitor},
6    ser::SerializeSeq,
7};
8
9const REVISION: u8 = SidRevision::One as u8;
10const MIN_SUB_AUTHORITIES: usize = 1;
11const MAX_SUB_AUTHORITIES: usize = 15;
12const IDENTIFIER_AUTHORITY_MAX: u64 = (1 << 48) - 1;
13
14/// A Windows security identifier (SID).
15///
16/// The text form is canonical `S-1-...` notation and the binary helpers use
17/// the native Windows packet layout.
18#[derive(Debug, Clone, PartialEq, Eq, Hash)]
19pub struct Sid {
20    identifier_authority: SidIdentifierAuthority,
21    sub_authorities: Box<[u32]>,
22}
23
24impl Sid {
25    /// Creates a revision-1 SID from its identifier authority and sub-authorities.
26    ///
27    /// The authority must fit in 48 bits and a SID must have one through 15
28    /// sub-authorities.
29    pub fn new(
30        identifier_authority: impl Into<SidIdentifierAuthority>,
31        sub_authorities: impl IntoIterator<Item = u32>,
32    ) -> Result<Self, SidError> {
33        let identifier_authority = identifier_authority.into();
34        let identifier_authority_value = u64::from(identifier_authority);
35        if identifier_authority_value > IDENTIFIER_AUTHORITY_MAX {
36            return Err(SidError::IdentifierAuthority);
37        }
38        let sub_authorities = sub_authorities.into_iter().collect::<Box<[_]>>();
39        if !(MIN_SUB_AUTHORITIES..=MAX_SUB_AUTHORITIES).contains(&sub_authorities.len()) {
40            return Err(SidError::SubAuthorityCount(sub_authorities.len()));
41        }
42        Ok(Self {
43            identifier_authority,
44            sub_authorities,
45        })
46    }
47
48    /// Returns the SID revision.
49    pub const fn revision(&self) -> SidRevision {
50        SidRevision::One
51    }
52
53    /// Returns the identifier authority
54    pub const fn identifier_authority(&self) -> SidIdentifierAuthority {
55        self.identifier_authority
56    }
57
58    /// Returns the SID sub-authorities.
59    pub fn sub_authorities(&self) -> &[u32] {
60        &self.sub_authorities
61    }
62
63    /// Parses a Windows-native SID packet.
64    pub fn from_bytes(bytes: &[u8]) -> Result<Self, SidError> {
65        if bytes.len() < 8 {
66            return Err(SidError::PacketLength);
67        }
68        if bytes[0] != REVISION {
69            return Err(SidError::Revision(bytes[0]));
70        }
71        let count = usize::from(bytes[1]);
72        if !(MIN_SUB_AUTHORITIES..=MAX_SUB_AUTHORITIES).contains(&count) {
73            return Err(SidError::SubAuthorityCount(count));
74        }
75        let expected = 8 + count * 4;
76        if bytes.len() != expected {
77            return Err(SidError::PacketLength);
78        }
79
80        let mut identifier_authority_bytes = [0; 8];
81        identifier_authority_bytes[2..].copy_from_slice(&bytes[2..8]);
82        let identifier_authority = u64::from_be_bytes(identifier_authority_bytes);
83        let sub_authorities = bytes[8..]
84            .as_chunks::<4>()
85            .0
86            .iter()
87            .copied()
88            .map(u32::from_le_bytes);
89        Self::new(identifier_authority, sub_authorities)
90    }
91
92    /// Converts this SID to the Windows-native SID packet.
93    pub fn to_bytes(&self) -> Vec<u8> {
94        let mut bytes = Vec::with_capacity(8 + self.sub_authorities.len() * 4);
95        bytes.push(REVISION);
96        bytes.push(u8::try_from(self.sub_authorities.len()).unwrap());
97        bytes.extend_from_slice(&u64::from(self.identifier_authority).to_be_bytes()[2..]);
98        for sub_authority in &self.sub_authorities {
99            bytes.extend_from_slice(&sub_authority.to_le_bytes());
100        }
101        bytes
102    }
103
104    fn identifier_authority_bytes(&self) -> [u8; 6] {
105        u64::from(self.identifier_authority).to_be_bytes()[2..]
106            .try_into()
107            .unwrap()
108    }
109}
110
111impl TryFrom<&[u8]> for Sid {
112    type Error = SidError;
113
114    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
115        Self::from_bytes(value)
116    }
117}
118
119impl fmt::Display for Sid {
120    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121        write!(f, "S-{REVISION}-")?;
122        let identifier_authority = u64::from(self.identifier_authority);
123        if identifier_authority < (1 << 32) {
124            write!(f, "{identifier_authority}")?;
125        } else {
126            write!(f, "0x{identifier_authority:012X}")?;
127        }
128        for sub_authority in &self.sub_authorities {
129            write!(f, "-{sub_authority}")?;
130        }
131        Ok(())
132    }
133}
134
135/// SID packet format revision.
136#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
137#[repr(u8)]
138pub enum SidRevision {
139    /// Revision 1, the only SID format revision.
140    One = 1,
141}
142
143impl Serialize for SidRevision {
144    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
145    where
146        S: Serializer,
147    {
148        serializer.serialize_u8(*self as u8)
149    }
150}
151
152impl<'de> Deserialize<'de> for SidRevision {
153    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
154    where
155        D: Deserializer<'de>,
156    {
157        match u8::deserialize(deserializer)? {
158            REVISION => Ok(Self::One),
159            revision => Err(de::Error::custom(SidError::Revision(revision))),
160        }
161    }
162}
163
164/// Authority responsible for issuing a SID.
165#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
166#[non_exhaustive]
167pub enum SidIdentifierAuthority {
168    /// Null authority.
169    Null,
170    /// World authority.
171    World,
172    /// Local authority.
173    Local,
174    /// Creator authority.
175    Creator,
176    /// Non-unique authority.
177    NonUnique,
178    /// NT authority.
179    Nt,
180    /// Resource manager authority.
181    ResourceManager,
182    /// Application package authority.
183    AppPackage,
184    /// Mandatory label authority.
185    MandatoryLabel,
186    /// Scoped policy identifier authority.
187    ScopedPolicy,
188    /// Authentication authority.
189    Authentication,
190    /// Process trust authority.
191    ProcessTrust,
192    /// An authority not known by this version of the crate.
193    Unknown(u64),
194}
195
196impl From<u64> for SidIdentifierAuthority {
197    fn from(value: u64) -> Self {
198        match value {
199            0 => Self::Null,
200            1 => Self::World,
201            2 => Self::Local,
202            3 => Self::Creator,
203            4 => Self::NonUnique,
204            5 => Self::Nt,
205            9 => Self::ResourceManager,
206            15 => Self::AppPackage,
207            16 => Self::MandatoryLabel,
208            17 => Self::ScopedPolicy,
209            18 => Self::Authentication,
210            19 => Self::ProcessTrust,
211            value => Self::Unknown(value),
212        }
213    }
214}
215
216impl From<SidIdentifierAuthority> for u64 {
217    fn from(authority: SidIdentifierAuthority) -> Self {
218        match authority {
219            SidIdentifierAuthority::Null => 0,
220            SidIdentifierAuthority::World => 1,
221            SidIdentifierAuthority::Local => 2,
222            SidIdentifierAuthority::Creator => 3,
223            SidIdentifierAuthority::NonUnique => 4,
224            SidIdentifierAuthority::Nt => 5,
225            SidIdentifierAuthority::ResourceManager => 9,
226            SidIdentifierAuthority::AppPackage => 15,
227            SidIdentifierAuthority::MandatoryLabel => 16,
228            SidIdentifierAuthority::ScopedPolicy => 17,
229            SidIdentifierAuthority::Authentication => 18,
230            SidIdentifierAuthority::ProcessTrust => 19,
231            SidIdentifierAuthority::Unknown(value) => value,
232        }
233    }
234}
235
236impl Serialize for SidIdentifierAuthority {
237    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
238    where
239        S: Serializer,
240    {
241        serializer.serialize_u64((*self).into())
242    }
243}
244
245impl<'de> Deserialize<'de> for SidIdentifierAuthority {
246    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
247    where
248        D: Deserializer<'de>,
249    {
250        Ok(u64::deserialize(deserializer)?.into())
251    }
252}
253
254/// A SID whose full value is a constant.
255///
256/// Every variant names a SID that is identical on every Windows installation,
257/// so converting one to a [`Sid`] never consults the environment. SIDs that
258/// are relative to a domain or a machine — `Domain Admins`
259/// (`S-1-5-21-<domain>-512`), the local `Administrator` account
260/// (`S-1-5-21-<machine>-500`) — are deliberately absent and belong to a name
261/// lookup instead. Keep it that way: the point of this enum is that it cannot
262/// fail.
263///
264/// This crate does not name these; text spellings belong to whatever exposes
265/// them.
266#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
267#[non_exhaustive]
268pub enum WellKnownSid {
269    /// `S-1-0-0`, the null SID.
270    Null,
271    /// `S-1-1-0`, the world (everyone).
272    Everyone,
273    /// `S-1-2-0`, users who log on to local terminals.
274    Local,
275    /// `S-1-2-1`, users who log on to the physical console.
276    ConsoleLogon,
277    /// `S-1-3-0`, a placeholder replaced by the creating user's SID.
278    CreatorOwner,
279    /// `S-1-3-1`, a placeholder replaced by the creating user's primary group.
280    CreatorGroup,
281    /// `S-1-3-4`, the rights granted to an object's current owner.
282    OwnerRights,
283    /// `S-1-5-1`, users logged on through a dial-up connection.
284    Dialup,
285    /// `S-1-5-2`, users logged on through a network connection.
286    Network,
287    /// `S-1-5-3`, users logged on through a batch queue facility.
288    Batch,
289    /// `S-1-5-4`, users logged on interactively.
290    Interactive,
291    /// `S-1-5-6`, accounts logged on as a service.
292    Service,
293    /// `S-1-5-7`, anonymous logon.
294    Anonymous,
295    /// `S-1-5-10`, a placeholder replaced by the SID of the object itself.
296    PrincipalSelf,
297    /// `S-1-5-11`, users authenticated by the system.
298    AuthenticatedUsers,
299    /// `S-1-5-12`, code running in a restricted security context.
300    RestrictedCode,
301    /// `S-1-5-14`, users logged on through a remote interactive session.
302    RemoteInteractiveLogon,
303    /// `S-1-5-15`, users of the same organization as the account.
304    ThisOrganization,
305    /// `S-1-5-18`, the local system account.
306    LocalSystem,
307    /// `S-1-5-19`, the local service account.
308    LocalService,
309    /// `S-1-5-20`, the network service account.
310    NetworkService,
311    /// `S-1-5-113`, any local account.
312    LocalAccount,
313    /// `S-1-5-114`, any local account that is a member of Administrators.
314    LocalAccountAdministrator,
315    /// `S-1-5-32-544`, the builtin Administrators group.
316    BuiltinAdministrators,
317    /// `S-1-5-32-545`, the builtin Users group.
318    BuiltinUsers,
319    /// `S-1-5-32-546`, the builtin Guests group.
320    BuiltinGuests,
321    /// `S-1-5-32-547`, the builtin Power Users group.
322    BuiltinPowerUsers,
323    /// `S-1-5-32-551`, the builtin Backup Operators group.
324    BuiltinBackupOperators,
325    /// `S-1-5-32-555`, the builtin Remote Desktop Users group.
326    BuiltinRemoteDesktopUsers,
327    /// `S-1-5-32-580`, the builtin Remote Management Users group.
328    BuiltinRemoteManagementUsers,
329    /// `S-1-15-2-1`, all application packages.
330    AllApplicationPackages,
331    /// `S-1-15-2-2`, all restricted application packages.
332    AllRestrictedApplicationPackages,
333    /// `S-1-16-0`, the untrusted integrity level.
334    UntrustedLabel,
335    /// `S-1-16-4096`, the low integrity level.
336    LowLabel,
337    /// `S-1-16-8192`, the medium integrity level.
338    MediumLabel,
339    /// `S-1-16-8448`, the medium-plus integrity level.
340    MediumPlusLabel,
341    /// `S-1-16-12288`, the high integrity level.
342    HighLabel,
343    /// `S-1-16-16384`, the system integrity level.
344    SystemLabel,
345}
346
347impl WellKnownSid {
348    /// Identifier authority and sub-authorities of this SID.
349    const fn parts(self) -> (SidIdentifierAuthority, &'static [u32]) {
350        use SidIdentifierAuthority::{AppPackage, Creator, Local, MandatoryLabel, Nt, Null, World};
351        match self {
352            Self::Null => (Null, &[0]),
353            Self::Everyone => (World, &[0]),
354            Self::Local => (Local, &[0]),
355            Self::ConsoleLogon => (Local, &[1]),
356            Self::CreatorOwner => (Creator, &[0]),
357            Self::CreatorGroup => (Creator, &[1]),
358            Self::OwnerRights => (Creator, &[4]),
359            Self::Dialup => (Nt, &[1]),
360            Self::Network => (Nt, &[2]),
361            Self::Batch => (Nt, &[3]),
362            Self::Interactive => (Nt, &[4]),
363            Self::Service => (Nt, &[6]),
364            Self::Anonymous => (Nt, &[7]),
365            Self::PrincipalSelf => (Nt, &[10]),
366            Self::AuthenticatedUsers => (Nt, &[11]),
367            Self::RestrictedCode => (Nt, &[12]),
368            Self::RemoteInteractiveLogon => (Nt, &[14]),
369            Self::ThisOrganization => (Nt, &[15]),
370            Self::LocalSystem => (Nt, &[18]),
371            Self::LocalService => (Nt, &[19]),
372            Self::NetworkService => (Nt, &[20]),
373            Self::LocalAccount => (Nt, &[113]),
374            Self::LocalAccountAdministrator => (Nt, &[114]),
375            Self::BuiltinAdministrators => (Nt, &[32, 544]),
376            Self::BuiltinUsers => (Nt, &[32, 545]),
377            Self::BuiltinGuests => (Nt, &[32, 546]),
378            Self::BuiltinPowerUsers => (Nt, &[32, 547]),
379            Self::BuiltinBackupOperators => (Nt, &[32, 551]),
380            Self::BuiltinRemoteDesktopUsers => (Nt, &[32, 555]),
381            Self::BuiltinRemoteManagementUsers => (Nt, &[32, 580]),
382            Self::AllApplicationPackages => (AppPackage, &[2, 1]),
383            Self::AllRestrictedApplicationPackages => (AppPackage, &[2, 2]),
384            Self::UntrustedLabel => (MandatoryLabel, &[0]),
385            Self::LowLabel => (MandatoryLabel, &[4096]),
386            Self::MediumLabel => (MandatoryLabel, &[8192]),
387            Self::MediumPlusLabel => (MandatoryLabel, &[8448]),
388            Self::HighLabel => (MandatoryLabel, &[12288]),
389            Self::SystemLabel => (MandatoryLabel, &[16384]),
390        }
391    }
392}
393
394impl From<WellKnownSid> for Sid {
395    fn from(value: WellKnownSid) -> Self {
396        let (identifier_authority, sub_authorities) = value.parts();
397        Self {
398            identifier_authority,
399            sub_authorities: sub_authorities.into(),
400        }
401    }
402}
403
404impl FromStr for Sid {
405    type Err = SidError;
406
407    fn from_str(value: &str) -> Result<Self, Self::Err> {
408        let mut parts = value.split('-');
409        if parts.next() != Some("S") || parts.next() != Some("1") {
410            return Err(SidError::StringSyntax);
411        }
412        let authority = parts.next().ok_or(SidError::StringSyntax)?;
413        let identifier_authority = if let Some(hex) = authority.strip_prefix("0x") {
414            if hex.len() != 12 || !hex.bytes().all(|byte| byte.is_ascii_hexdigit()) {
415                return Err(SidError::StringSyntax);
416            }
417            let authority = u64::from_str_radix(hex, 16).map_err(|_| SidError::StringSyntax)?;
418            if authority < (1 << 32) {
419                return Err(SidError::StringSyntax);
420            }
421            authority
422        } else {
423            if authority.is_empty()
424                || (authority.len() > 1 && authority.starts_with('0'))
425                || !authority.bytes().all(|byte| byte.is_ascii_digit())
426            {
427                return Err(SidError::StringSyntax);
428            }
429            let authority = authority
430                .parse::<u64>()
431                .map_err(|_| SidError::IdentifierAuthority)?;
432            if authority >= (1 << 32) {
433                return Err(SidError::StringSyntax);
434            }
435            authority
436        };
437
438        let sub_authorities = parts
439            .map(|part| {
440                if part.is_empty()
441                    || (part.len() > 1 && part.starts_with('0'))
442                    || !part.bytes().all(|byte| byte.is_ascii_digit())
443                {
444                    return Err(SidError::StringSyntax);
445                }
446                part.parse::<u32>().map_err(|_| SidError::StringSyntax)
447            })
448            .collect::<Result<Vec<_>, _>>()?;
449        Self::new(identifier_authority, sub_authorities)
450    }
451}
452
453impl Serialize for Sid {
454    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
455    where
456        S: Serializer,
457    {
458        let mut seq = serializer.serialize_seq(Some(3 + self.sub_authorities.len()))?;
459        seq.serialize_element(&REVISION)?;
460        seq.serialize_element(&u8::try_from(self.sub_authorities.len()).unwrap())?;
461        seq.serialize_element(&self.identifier_authority_bytes())?;
462        for sub_authority in &self.sub_authorities {
463            seq.serialize_element(sub_authority)?;
464        }
465        seq.end()
466    }
467}
468
469impl<'de> Deserialize<'de> for Sid {
470    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
471    where
472        D: Deserializer<'de>,
473    {
474        struct SidVisitor;
475
476        impl<'de> Visitor<'de> for SidVisitor {
477            type Value = Sid;
478
479            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
480                formatter.write_str("a structurally encoded Windows SID")
481            }
482
483            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
484            where
485                A: SeqAccess<'de>,
486            {
487                let revision: u8 = seq
488                    .next_element()?
489                    .ok_or_else(|| de::Error::invalid_length(0, &self))?;
490                if revision != REVISION {
491                    return Err(de::Error::custom(SidError::Revision(revision)));
492                }
493                let count: u8 = seq
494                    .next_element()?
495                    .ok_or_else(|| de::Error::invalid_length(1, &self))?;
496                let count = usize::from(count);
497                if !(MIN_SUB_AUTHORITIES..=MAX_SUB_AUTHORITIES).contains(&count) {
498                    return Err(de::Error::custom(SidError::SubAuthorityCount(count)));
499                }
500                let authority: [u8; 6] = seq
501                    .next_element()?
502                    .ok_or_else(|| de::Error::invalid_length(2, &self))?;
503                let mut authority_bytes = [0; 8];
504                authority_bytes[2..].copy_from_slice(&authority);
505                let authority = u64::from_be_bytes(authority_bytes);
506                let mut sub_authorities = Vec::with_capacity(count);
507                for index in 0..count {
508                    sub_authorities.push(
509                        seq.next_element()?
510                            .ok_or_else(|| de::Error::invalid_length(3 + index, &self))?,
511                    );
512                }
513                if seq.next_element::<de::IgnoredAny>()?.is_some() {
514                    return Err(de::Error::invalid_length(4 + count, &self));
515                }
516                Sid::new(authority, sub_authorities).map_err(de::Error::custom)
517            }
518        }
519
520        deserializer.deserialize_seq(SidVisitor)
521    }
522}
523
524/// Error returned when constructing or parsing a SID.
525#[derive(Debug, Clone, PartialEq, Eq)]
526pub enum SidError {
527    /// The SID uses a revision other than revision 1.
528    Revision(u8),
529    /// The SID has fewer than one or more than 15 sub-authorities.
530    SubAuthorityCount(usize),
531    /// The identifier authority exceeds the 48-bit Windows field.
532    IdentifierAuthority,
533    /// The binary packet length does not match its declared structure.
534    PacketLength,
535    /// Text was not canonical SID notation.
536    StringSyntax,
537}
538
539impl fmt::Display for SidError {
540    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
541        match self {
542            Self::Revision(revision) => write!(f, "unsupported SID revision {revision}"),
543            Self::SubAuthorityCount(count) => {
544                write!(
545                    f,
546                    "SID must contain between 1 and 15 sub-authorities, got {count}"
547                )
548            }
549            Self::IdentifierAuthority => f.write_str("SID identifier authority exceeds 48 bits"),
550            Self::PacketLength => f.write_str("SID packet length does not match its structure"),
551            Self::StringSyntax => f.write_str("invalid canonical SID string"),
552        }
553    }
554}
555
556impl error::Error for SidError {}
557
558#[cfg(test)]
559mod tests {
560    use super::*;
561
562    #[test]
563    fn string_and_packet_round_trip() {
564        let sid: Sid = "S-1-5-21-287454020-2864434397".parse().unwrap();
565        let bytes = [
566            1, 3, 0, 0, 0, 0, 0, 5, 21, 0, 0, 0, 0x44, 0x33, 0x22, 0x11, 0xDD, 0xCC, 0xBB, 0xAA,
567        ];
568        assert_eq!(sid.to_bytes(), bytes);
569        assert_eq!(Sid::from_bytes(&bytes).unwrap(), sid);
570        assert_eq!(sid.to_string(), "S-1-5-21-287454020-2864434397");
571        assert_eq!(Sid::new(5, [32, 544]).unwrap().to_string(), "S-1-5-32-544");
572    }
573
574    #[test]
575    fn well_known_sids_have_their_canonical_values() {
576        // `canonical` is an exhaustive match, so a new variant does not
577        // compile until it is spelled here; add it to `ALL` as well.
578        const ALL: &[WellKnownSid] = &[
579            WellKnownSid::Null,
580            WellKnownSid::Everyone,
581            WellKnownSid::Local,
582            WellKnownSid::ConsoleLogon,
583            WellKnownSid::CreatorOwner,
584            WellKnownSid::CreatorGroup,
585            WellKnownSid::OwnerRights,
586            WellKnownSid::Dialup,
587            WellKnownSid::Network,
588            WellKnownSid::Batch,
589            WellKnownSid::Interactive,
590            WellKnownSid::Service,
591            WellKnownSid::Anonymous,
592            WellKnownSid::PrincipalSelf,
593            WellKnownSid::AuthenticatedUsers,
594            WellKnownSid::RestrictedCode,
595            WellKnownSid::RemoteInteractiveLogon,
596            WellKnownSid::ThisOrganization,
597            WellKnownSid::LocalSystem,
598            WellKnownSid::LocalService,
599            WellKnownSid::NetworkService,
600            WellKnownSid::LocalAccount,
601            WellKnownSid::LocalAccountAdministrator,
602            WellKnownSid::BuiltinAdministrators,
603            WellKnownSid::BuiltinUsers,
604            WellKnownSid::BuiltinGuests,
605            WellKnownSid::BuiltinPowerUsers,
606            WellKnownSid::BuiltinBackupOperators,
607            WellKnownSid::BuiltinRemoteDesktopUsers,
608            WellKnownSid::BuiltinRemoteManagementUsers,
609            WellKnownSid::AllApplicationPackages,
610            WellKnownSid::AllRestrictedApplicationPackages,
611            WellKnownSid::UntrustedLabel,
612            WellKnownSid::LowLabel,
613            WellKnownSid::MediumLabel,
614            WellKnownSid::MediumPlusLabel,
615            WellKnownSid::HighLabel,
616            WellKnownSid::SystemLabel,
617        ];
618
619        fn canonical(sid: WellKnownSid) -> &'static str {
620            match sid {
621                WellKnownSid::Null => "S-1-0-0",
622                WellKnownSid::Everyone => "S-1-1-0",
623                WellKnownSid::Local => "S-1-2-0",
624                WellKnownSid::ConsoleLogon => "S-1-2-1",
625                WellKnownSid::CreatorOwner => "S-1-3-0",
626                WellKnownSid::CreatorGroup => "S-1-3-1",
627                WellKnownSid::OwnerRights => "S-1-3-4",
628                WellKnownSid::Dialup => "S-1-5-1",
629                WellKnownSid::Network => "S-1-5-2",
630                WellKnownSid::Batch => "S-1-5-3",
631                WellKnownSid::Interactive => "S-1-5-4",
632                WellKnownSid::Service => "S-1-5-6",
633                WellKnownSid::Anonymous => "S-1-5-7",
634                WellKnownSid::PrincipalSelf => "S-1-5-10",
635                WellKnownSid::AuthenticatedUsers => "S-1-5-11",
636                WellKnownSid::RestrictedCode => "S-1-5-12",
637                WellKnownSid::RemoteInteractiveLogon => "S-1-5-14",
638                WellKnownSid::ThisOrganization => "S-1-5-15",
639                WellKnownSid::LocalSystem => "S-1-5-18",
640                WellKnownSid::LocalService => "S-1-5-19",
641                WellKnownSid::NetworkService => "S-1-5-20",
642                WellKnownSid::LocalAccount => "S-1-5-113",
643                WellKnownSid::LocalAccountAdministrator => "S-1-5-114",
644                WellKnownSid::BuiltinAdministrators => "S-1-5-32-544",
645                WellKnownSid::BuiltinUsers => "S-1-5-32-545",
646                WellKnownSid::BuiltinGuests => "S-1-5-32-546",
647                WellKnownSid::BuiltinPowerUsers => "S-1-5-32-547",
648                WellKnownSid::BuiltinBackupOperators => "S-1-5-32-551",
649                WellKnownSid::BuiltinRemoteDesktopUsers => "S-1-5-32-555",
650                WellKnownSid::BuiltinRemoteManagementUsers => "S-1-5-32-580",
651                WellKnownSid::AllApplicationPackages => "S-1-15-2-1",
652                WellKnownSid::AllRestrictedApplicationPackages => "S-1-15-2-2",
653                WellKnownSid::UntrustedLabel => "S-1-16-0",
654                WellKnownSid::LowLabel => "S-1-16-4096",
655                WellKnownSid::MediumLabel => "S-1-16-8192",
656                WellKnownSid::MediumPlusLabel => "S-1-16-8448",
657                WellKnownSid::HighLabel => "S-1-16-12288",
658                WellKnownSid::SystemLabel => "S-1-16-16384",
659            }
660        }
661
662        for well_known in ALL {
663            let expected = canonical(*well_known);
664            let sid = Sid::from(*well_known);
665            assert_eq!(sid.to_string(), expected, "{well_known:?}");
666            // Every constant is a SID the parser accepts, unchanged.
667            assert_eq!(expected.parse::<Sid>().unwrap(), sid, "{well_known:?}");
668        }
669    }
670
671    #[test]
672    fn high_identifier_authority_uses_hexadecimal() {
673        let sid: Sid = "S-1-0x010203040506-7".parse().unwrap();
674        assert_eq!(
675            sid.identifier_authority(),
676            SidIdentifierAuthority::Unknown(0x0102_0304_0506)
677        );
678        assert_eq!(sid.to_string(), "S-1-0x010203040506-7");
679        assert_eq!(&sid.to_bytes()[2..8], &[1, 2, 3, 4, 5, 6]);
680    }
681
682    #[test]
683    fn rejects_noncanonical_or_malformed_values() {
684        assert!("S-1-5".parse::<Sid>().is_err());
685        assert!("S-1-05-1".parse::<Sid>().is_err());
686        assert!("S-1-5-01".parse::<Sid>().is_err());
687        assert!("S-1-0x000000000005-1".parse::<Sid>().is_err());
688        assert!(Sid::from_bytes(&[2, 1, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0]).is_err());
689        assert!(Sid::from_bytes(&[1, 1, 0, 0, 0, 0, 0, 5]).is_err());
690    }
691
692    #[test]
693    fn serde_is_structural() {
694        let sid: Sid = "S-1-5-32-544".parse().unwrap();
695        let encoded = postcard::to_stdvec(&sid).unwrap();
696        let expected =
697            postcard::to_stdvec(&(1u8, 2u8, [0u8, 0, 0, 0, 0, 5], 32u32, 544u32)).unwrap();
698        assert_eq!(encoded, [vec![5], expected].concat());
699        assert_eq!(postcard::from_bytes::<Sid>(&encoded).unwrap(), sid);
700    }
701
702    #[test]
703    fn revisions_and_authorities_use_native_values() {
704        assert_eq!(
705            Sid::new(SidIdentifierAuthority::Nt, [32])
706                .unwrap()
707                .revision(),
708            SidRevision::One
709        );
710        assert_eq!(SidIdentifierAuthority::from(5), SidIdentifierAuthority::Nt);
711        assert_eq!(
712            SidIdentifierAuthority::from(42),
713            SidIdentifierAuthority::Unknown(42)
714        );
715        assert_eq!(postcard::to_stdvec(&SidRevision::One).unwrap(), vec![1]);
716        assert!(postcard::from_bytes::<SidRevision>(&[2]).is_err());
717        assert_eq!(
718            postcard::to_stdvec(&SidIdentifierAuthority::Nt).unwrap(),
719            postcard::to_stdvec(&5u64).unwrap()
720        );
721    }
722}