1"""
2This module implements security descriptors, and the partial structures
3used in them, as specified in [MS-DTYP].
4"""
5
6import struct
7
8
9# Security descriptor control flags
10# [MS-DTYP]: 2.4.6
11SECURITY_DESCRIPTOR_OWNER_DEFAULTED = 0x0001
12SECURITY_DESCRIPTOR_GROUP_DEFAULTED = 0x0002
13SECURITY_DESCRIPTOR_DACL_PRESENT = 0x0004
14SECURITY_DESCRIPTOR_DACL_DEFAULTED = 0x0008
15SECURITY_DESCRIPTOR_SACL_PRESENT = 0x0010
16SECURITY_DESCRIPTOR_SACL_DEFAULTED = 0x0020
17SECURITY_DESCRIPTOR_SERVER_SECURITY = 0x0040
18SECURITY_DESCRIPTOR_DACL_TRUSTED = 0x0080
19SECURITY_DESCRIPTOR_DACL_COMPUTED_INHERITANCE_REQUIRED = 0x0100
20SECURITY_DESCRIPTOR_SACL_COMPUTED_INHERITANCE_REQUIRED = 0x0200
21SECURITY_DESCRIPTOR_DACL_AUTO_INHERITED = 0x0400
22SECURITY_DESCRIPTOR_SACL_AUTO_INHERITED = 0x0800
23SECURITY_DESCRIPTOR_DACL_PROTECTED = 0x1000
24SECURITY_DESCRIPTOR_SACL_PROTECTED = 0x2000
25SECURITY_DESCRIPTOR_RM_CONTROL_VALID = 0x4000
26SECURITY_DESCRIPTOR_SELF_RELATIVE = 0x8000
27
28# ACE types
29# [MS-DTYP]: 2.4.4.1
30ACE_TYPE_ACCESS_ALLOWED = 0x00
31ACE_TYPE_ACCESS_DENIED = 0x01
32ACE_TYPE_SYSTEM_AUDIT = 0x02
33ACE_TYPE_SYSTEM_ALARM = 0x03
34ACE_TYPE_ACCESS_ALLOWED_COMPOUND = 0x04
35ACE_TYPE_ACCESS_ALLOWED_OBJECT = 0x05
36ACE_TYPE_ACCESS_DENIED_OBJECT = 0x06
37ACE_TYPE_SYSTEM_AUDIT_OBJECT = 0x07
38ACE_TYPE_SYSTEM_ALARM_OBJECT = 0x08
39ACE_TYPE_ACCESS_ALLOWED_CALLBACK = 0x09
40ACE_TYPE_ACCESS_DENIED_CALLBACK = 0x0A
41ACE_TYPE_ACCESS_ALLOWED_CALLBACK_OBJECT = 0x0B
42ACE_TYPE_ACCESS_DENIED_CALLBACK_OBJECT = 0x0C
43ACE_TYPE_SYSTEM_AUDIT_CALLBACK = 0x0D
44ACE_TYPE_SYSTEM_ALARM_CALLBACK = 0x0E
45ACE_TYPE_SYSTEM_AUDIT_CALLBACK_OBJECT = 0x0F
46ACE_TYPE_SYSTEM_ALARM_CALLBACK_OBJECT = 0x10
47ACE_TYPE_SYSTEM_MANDATORY_LABEL = 0x11
48ACE_TYPE_SYSTEM_RESOURCE_ATTRIBUTE = 0x12
49ACE_TYPE_SYSTEM_SCOPED_POLICY_ID = 0x13
50
51# ACE flags
52# [MS-DTYP]: 2.4.4.1
53ACE_FLAG_OBJECT_INHERIT = 0x01
54ACE_FLAG_CONTAINER_INHERIT = 0x02
55ACE_FLAG_NO_PROPAGATE_INHERIT = 0x04
56ACE_FLAG_INHERIT_ONLY = 0x08
57ACE_FLAG_INHERITED = 0x10
58ACE_FLAG_SUCCESSFUL_ACCESS = 0x40
59ACE_FLAG_FAILED_ACCESS = 0x80
60
61# Pre-defined well-known SIDs
62# [MS-DTYP]: 2.4.2.4
63SID_NULL = "S-1-0-0"
64SID_EVERYONE = "S-1-1-0"
65SID_LOCAL = "S-1-2-0"
66SID_CONSOLE_LOGON = "S-1-2-1"
67SID_CREATOR_OWNER = "S-1-3-0"
68SID_CREATOR_GROUP = "S-1-3-1"
69SID_OWNER_SERVER = "S-1-3-2"
70SID_GROUP_SERVER = "S-1-3-3"
71SID_OWNER_RIGHTS = "S-1-3-4"
72SID_NT_AUTHORITY = "S-1-5"
73SID_DIALUP = "S-1-5-1"
74SID_NETWORK = "S-1-5-2"
75SID_BATCH = "S-1-5-3"
76SID_INTERACTIVE = "S-1-5-4"
77SID_SERVICE = "S-1-5-6"
78SID_ANONYMOUS = "S-1-5-7"
79SID_PROXY = "S-1-5-8"
80SID_ENTERPRISE_DOMAIN_CONTROLLERS = "S-1-5-9"
81SID_PRINCIPAL_SELF = "S-1-5-10"
82SID_AUTHENTICATED_USERS = "S-1-5-11"
83SID_RESTRICTED_CODE = "S-1-5-12"
84SID_TERMINAL_SERVER_USER = "S-1-5-13"
85SID_REMOTE_INTERACTIVE_LOGON = "S-1-5-14"
86SID_THIS_ORGANIZATION = "S-1-5-15"
87SID_IUSR = "S-1-5-17"
88SID_LOCAL_SYSTEM = "S-1-5-18"
89SID_LOCAL_SERVICE = "S-1-5-19"
90SID_NETWORK_SERVICE = "S-1-5-20"
91SID_COMPOUNDED_AUTHENTICATION = "S-1-5-21-0-0-0-496"
92SID_CLAIMS_VALID = "S-1-5-21-0-0-0-497"
93SID_BUILTIN_ADMINISTRATORS = "S-1-5-32-544"
94SID_BUILTIN_USERS = "S-1-5-32-545"
95SID_BUILTIN_GUESTS = "S-1-5-32-546"
96SID_POWER_USERS = "S-1-5-32-547"
97SID_ACCOUNT_OPERATORS = "S-1-5-32-548"
98SID_SERVER_OPERATORS = "S-1-5-32-549"
99SID_PRINTER_OPERATORS = "S-1-5-32-550"
100SID_BACKUP_OPERATORS = "S-1-5-32-551"
101SID_REPLICATOR = "S-1-5-32-552"
102SID_ALIAS_PREW2KCOMPACC = "S-1-5-32-554"
103SID_REMOTE_DESKTOP = "S-1-5-32-555"
104SID_NETWORK_CONFIGURATION_OPS = "S-1-5-32-556"
105SID_INCOMING_FOREST_TRUST_BUILDERS = "S-1-5-32-557"
106SID_PERFMON_USERS = "S-1-5-32-558"
107SID_PERFLOG_USERS = "S-1-5-32-559"
108SID_WINDOWS_AUTHORIZATION_ACCESS_GROUP = "S-1-5-32-560"
109SID_TERMINAL_SERVER_LICENSE_SERVERS = "S-1-5-32-561"
110SID_DISTRIBUTED_COM_USERS = "S-1-5-32-562"
111SID_IIS_IUSRS = "S-1-5-32-568"
112SID_CRYPTOGRAPHIC_OPERATORS = "S-1-5-32-569"
113SID_EVENT_LOG_READERS = "S-1-5-32-573"
114SID_CERTIFICATE_SERVICE_DCOM_ACCESS = "S-1-5-32-574"
115SID_RDS_REMOTE_ACCESS_SERVERS = "S-1-5-32-575"
116SID_RDS_ENDPOINT_SERVERS = "S-1-5-32-576"
117SID_RDS_MANAGEMENT_SERVERS = "S-1-5-32-577"
118SID_HYPER_V_ADMINS = "S-1-5-32-578"
119SID_ACCESS_CONTROL_ASSISTANCE_OPS = "S-1-5-32-579"
120SID_REMOTE_MANAGEMENT_USERS = "S-1-5-32-580"
121SID_WRITE_RESTRICTED_CODE = "S-1-5-33"
122SID_NTLM_AUTHENTICATION = "S-1-5-64-10"
123SID_SCHANNEL_AUTHENTICATION = "S-1-5-64-14"
124SID_DIGEST_AUTHENTICATION = "S-1-5-64-21"
125SID_THIS_ORGANIZATION_CERTIFICATE = "S-1-5-65-1"
126SID_NT_SERVICE = "S-1-5-80"
127SID_USER_MODE_DRIVERS = "S-1-5-84-0-0-0-0-0"
128SID_LOCAL_ACCOUNT = "S-1-5-113"
129SID_LOCAL_ACCOUNT_AND_MEMBER_OF_ADMINISTRATORS_GROUP = "S-1-5-114"
130SID_OTHER_ORGANIZATION = "S-1-5-1000"
131SID_ALL_APP_PACKAGES = "S-1-15-2-1"
132SID_ML_UNTRUSTED = "S-1-16-0"
133SID_ML_LOW = "S-1-16-4096"
134SID_ML_MEDIUM = "S-1-16-8192"
135SID_ML_MEDIUM_PLUS = "S-1-16-8448"
136SID_ML_HIGH = "S-1-16-12288"
137SID_ML_SYSTEM = "S-1-16-16384"
138SID_ML_PROTECTED_PROCESS = "S-1-16-20480"
139SID_AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY = "S-1-18-1"
140SID_SERVICE_ASSERTED_IDENTITY = "S-1-18-2"
141SID_FRESH_PUBLIC_KEY_IDENTITY = "S-1-18-3"
142SID_KEY_TRUST_IDENTITY = "S-1-18-4"
143SID_KEY_PROPERTY_MFA = "S-1-18-5"
144SID_KEY_PROPERTY_ATTESTATION = "S-1-18-6"
145
146
147class SID(object):
148    """
149    A Windows security identifier. Represents a single principal, such a
150    user or a group, as a sequence of numbers consisting of the revision,
151    identifier authority, and a variable-length list of subauthorities.
152
153    See [MS-DTYP]: 2.4.2
154    """
155    def __init__(self, revision, identifier_authority, subauthorities):
156        #: Revision, should always be 1.
157        self.revision = revision
158        #: An integer representing the identifier authority.
159        self.identifier_authority = identifier_authority
160        #: A list of integers representing all subauthorities.
161        self.subauthorities = subauthorities
162
163    def __str__(self):
164        """
165        String representation, as specified in [MS-DTYP]: 2.4.2.1
166        """
167        if self.identifier_authority >= 2**32:
168            id_auth = '%#x' % (self.identifier_authority,)
169        else:
170            id_auth = self.identifier_authority
171        auths = [self.revision, id_auth] + self.subauthorities
172        return 'S-' + '-'.join(str(subauth) for subauth in auths)
173
174    def __repr__(self):
175        return 'SID(%r)' % (str(self),)
176
177    @classmethod
178    def from_bytes(cls, data, return_tail=False):
179        revision, subauth_count = struct.unpack('<BB', data[:2])
180        identifier_authority = struct.unpack('>Q', '\x00\x00' + data[2:8])[0]
181        subauth_data = data[8:]
182        subauthorities = [struct.unpack('<L', subauth_data[4 * i : 4 * (i+1)])[0]
183                          for i in range(subauth_count)]
184        sid = cls(revision, identifier_authority, subauthorities)
185        if return_tail:
186            return sid, subauth_data[4 * subauth_count :]
187        return sid
188
189
190class ACE(object):
191    """
192    Represents a single access control entry.
193
194    See [MS-DTYP]: 2.4.4
195    """
196    HEADER_FORMAT = '<BBH'
197
198    def __init__(self, type_, flags, mask, sid, additional_data):
199        #: An integer representing the type of the ACE. One of the
200        #: ``ACE_TYPE_*`` constants. Corresponds to the ``AceType`` field
201        #: from [MS-DTYP] 2.4.4.1.
202        self.type = type_
203        #: An integer bitmask with ACE flags, corresponds to the
204        #: ``AceFlags`` field.
205        self.flags = flags
206        #: An integer representing the ``ACCESS_MASK`` as specified in
207        #: [MS-DTYP] 2.4.3.
208        self.mask = mask
209        #: The :class:`SID` of a trustee.
210        self.sid = sid
211        #: A dictionary of additional fields present in the ACE, depending
212        #: on the type. The following fields can be present:
213        #:
214        #: * ``flags``
215        #: * ``object_type``
216        #: * ``inherited_object_type``
217        #: * ``application_data``
218        #: * ``attribute_data``
219        self.additional_data = additional_data
220
221    def __repr__(self):
222        return "ACE(type=%#04x, flags=%#04x, mask=%#010x, sid=%s)" % (
223            self.type, self.flags, self.mask, self.sid,
224        )
225
226    @property
227    def isInheritOnly(self):
228        """Convenience property which indicates if this ACE is inherit
229        only, meaning that it doesn't apply to the object itself."""
230        return bool(self.flags & ACE_FLAG_INHERIT_ONLY)
231
232    @classmethod
233    def from_bytes(cls, data):
234        header_size = struct.calcsize(cls.HEADER_FORMAT)
235        header = data[:header_size]
236        type_, flags, size = struct.unpack(cls.HEADER_FORMAT, header)
237
238        assert len(data) >= size
239
240        body = data[header_size:size]
241        additional_data = {}
242
243        # In all ACE types, the mask immediately follows the header.
244        mask = struct.unpack('<I', body[:4])[0]
245        body = body[4:]
246
247        # All OBJECT-type ACEs contain additional flags, and two GUIDs as
248        # the following fields.
249        if type_ in (ACE_TYPE_ACCESS_ALLOWED_OBJECT,
250                     ACE_TYPE_ACCESS_DENIED_OBJECT,
251                     ACE_TYPE_ACCESS_ALLOWED_CALLBACK_OBJECT,
252                     ACE_TYPE_ACCESS_DENIED_CALLBACK_OBJECT,
253                     ACE_TYPE_SYSTEM_AUDIT_OBJECT,
254                     ACE_TYPE_SYSTEM_AUDIT_CALLBACK_OBJECT):
255            additional_data['flags'] = struct.unpack('<I', body[:4])[0]
256            additional_data['object_type'] = body[4:20]
257            additional_data['inherited_object_type'] = body[20:36]
258            body = body[36:]
259
260        # Then the SID in all types.
261        sid, body = SID.from_bytes(body, return_tail=True)
262
263        # CALLBACK-type ACEs (and for some obscure reason,
264        # SYSTEM_AUDIT_OBJECT) have a final tail of application data.
265        if type_ in (ACE_TYPE_ACCESS_ALLOWED_CALLBACK,
266                     ACE_TYPE_ACCESS_DENIED_CALLBACK,
267                     ACE_TYPE_ACCESS_ALLOWED_CALLBACK_OBJECT,
268                     ACE_TYPE_ACCESS_DENIED_CALLBACK_OBJECT,
269                     ACE_TYPE_SYSTEM_AUDIT_OBJECT,
270                     ACE_TYPE_SYSTEM_AUDIT_CALLBACK,
271                     ACE_TYPE_SYSTEM_AUDIT_CALLBACK_OBJECT):
272            additional_data['application_data'] = body
273
274        # SYSTEM_RESOURCE_ATTRIBUTE ACEs have a tail of attribute data.
275        if type_ == ACE_TYPE_SYSTEM_RESOURCE_ATTRIBUTE:
276            additional_data['attribute_data'] = body
277
278        return cls(type_, flags, mask, sid, additional_data)
279
280
281class ACL(object):
282    """
283    Access control list, encapsulating a sequence of access control
284    entries.
285
286    See [MS-DTYP]: 2.4.5
287    """
288    HEADER_FORMAT = '<BBHHH'
289
290    def __init__(self, revision, aces):
291        #: Integer value of the revision.
292        self.revision = revision
293        #: List of :class:`ACE` instances.
294        self.aces = aces
295
296    def __repr__(self):
297        return "ACL(%r)" % (self.aces,)
298
299    @classmethod
300    def from_bytes(cls, data):
301        revision = None
302        aces = []
303
304        header_size = struct.calcsize(cls.HEADER_FORMAT)
305        header, remaining = data[:header_size], data[header_size:]
306        revision, sbz1, size, count, sbz2 = struct.unpack(cls.HEADER_FORMAT, header)
307
308        assert len(data) >= size
309
310        for i in range(count):
311            ace_size = struct.unpack('<H', remaining[2:4])[0]
312            ace_data, remaining = remaining[:ace_size], remaining[ace_size:]
313            aces.append(ACE.from_bytes(ace_data))
314
315        return cls(revision, aces)
316
317
318class SecurityDescriptor(object):
319    """
320    Represents a security descriptor.
321
322    See [MS-DTYP]: 2.4.6
323    """
324
325    HEADER_FORMAT = '<BBHIIII'
326
327    def __init__(self, flags, owner, group, dacl, sacl):
328        #: Integer bitmask of control flags. Corresponds to the
329        #: ``Control`` field in [MS-DTYP] 2.4.6.
330        self.flags = flags
331        #: Instance of :class:`SID` representing the owner user.
332        self.owner = owner
333        #: Instance of :class:`SID` representing the owner group.
334        self.group = group
335        #: Instance of :class:`ACL` representing the discretionary access
336        #: control list, which specifies access restrictions of an object.
337        self.dacl = dacl
338        #: Instance of :class:`ACL` representing the system access control
339        #: list, which specifies audit logging of an object.
340        self.sacl = sacl
341
342    @classmethod
343    def from_bytes(cls, data):
344        owner = None
345        group = None
346        dacl = None
347        sacl = None
348
349        header = data[:struct.calcsize(cls.HEADER_FORMAT)]
350        (revision, sbz1, flags, owner_offset, group_offset, sacl_offset,
351         dacl_offset) = struct.unpack(cls.HEADER_FORMAT, header)
352
353        assert revision == 1
354        assert flags & SECURITY_DESCRIPTOR_SELF_RELATIVE
355        for offset in (owner_offset, group_offset, sacl_offset, dacl_offset):
356            assert 0 <= offset < len(data)
357
358        if owner_offset:
359            owner = SID.from_bytes(data[owner_offset:])
360        if group_offset:
361            group = SID.from_bytes(data[group_offset:])
362        if dacl_offset:
363            dacl = ACL.from_bytes(data[dacl_offset:])
364        if sacl_offset:
365            sacl = ACL.from_bytes(data[sacl_offset:])
366
367        return cls(flags, owner, group, dacl, sacl)
368