Skip to content

Commit ac402a2

Browse files
committed
Support conditional-ACE expressions in SDDL conversion (Fixes #106)
1 parent 1190d94 commit ac402a2

8 files changed

Lines changed: 1503 additions & 1 deletion

File tree

ace/condition/condition.go

Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
// Package condition implements the conditional-expression codec used by
2+
// conditional (callback) ACEs, as specified in MS-DTYP 2.4.4.17 and its
3+
// subsections (2.4.4.17.1 Conditional ACE Expressions, and the Literal /
4+
// Attribute / Relational / Logical operator token tables).
5+
//
6+
// A conditional expression lives in the ApplicationData member of a callback
7+
// ACE, prefixed by the 4-byte ACE_CONDITION_SIGNATURE "artx" (0x61 0x72 0x74
8+
// 0x78). The remainder is a series of tokens in postfix (reverse Polish) order.
9+
// This package converts between the SDDL textual form of a conditional
10+
// expression (e.g. `(@User.Title=="PM" && @User.Division=="Finance")`) and that
11+
// binary form.
12+
//
13+
// text -> Marshal -> ApplicationData bytes ("artx" + tokens + DWORD padding)
14+
// bytes -> Unmarshal -> text
15+
//
16+
// All multibyte integers (including UTF-16 characters) are stored
17+
// least-significant byte first, and the encoded expression is padded with 0x00
18+
// to a DWORD (4-byte) boundary.
19+
package condition
20+
21+
import (
22+
"fmt"
23+
)
24+
25+
// ACE_CONDITION_SIGNATURE marks a callback ACE's ApplicationData as a
26+
// conditional expression (MS-DTYP 2.4.4.17.1).
27+
var ACE_CONDITION_SIGNATURE = []byte{0x61, 0x72, 0x74, 0x78}
28+
29+
// Literal, attribute, and operator token byte-codes (MS-DTYP 2.4.4.17.1.x).
30+
const (
31+
tokenPadding byte = 0x00
32+
33+
// Literal tokens.
34+
tokenInt8 byte = 0x01
35+
tokenInt16 byte = 0x02
36+
tokenInt32 byte = 0x03
37+
tokenInt64 byte = 0x04
38+
tokenUnicode byte = 0x10
39+
tokenOctet byte = 0x18
40+
tokenComposite byte = 0x50
41+
tokenSID byte = 0x51
42+
43+
// Attribute-name tokens (encoding identical to a Unicode string).
44+
tokenLocalAttr byte = 0xf8
45+
tokenUserAttr byte = 0xf9
46+
tokenResourceAttr byte = 0xfa
47+
tokenDeviceAttr byte = 0xfb
48+
49+
// Binary relational operators.
50+
tokenEqual byte = 0x80
51+
tokenNotEqual byte = 0x81
52+
tokenLessThan byte = 0x82
53+
tokenLessOrEqual byte = 0x83
54+
tokenGreaterThan byte = 0x84
55+
tokenGreaterOrEqual byte = 0x85
56+
tokenContains byte = 0x86
57+
tokenAnyOf byte = 0x88
58+
tokenNotContains byte = 0x8e
59+
tokenNotAnyOf byte = 0x8f
60+
61+
// Unary relational operators (operand is a SID literal or a composite of SIDs).
62+
tokenMemberOf byte = 0x89
63+
tokenDeviceMemberOf byte = 0x8a
64+
tokenMemberOfAny byte = 0x8b
65+
tokenDeviceMemberOfAny byte = 0x8c
66+
tokenNotMemberOf byte = 0x90
67+
tokenNotDeviceMemberOf byte = 0x91
68+
tokenNotMemberOfAny byte = 0x92
69+
tokenNotDeviceMemberOfAny byte = 0x93
70+
71+
// Unary logical operators.
72+
tokenExists byte = 0x87
73+
tokenNotExists byte = 0x8d
74+
tokenNot byte = 0xa2
75+
76+
// Binary logical operators.
77+
tokenAnd byte = 0xa0
78+
tokenOr byte = 0xa1
79+
)
80+
81+
// Base codes for integer literals (MS-DTYP 2.4.4.17.5).
82+
const (
83+
baseOctal byte = 0x01
84+
baseDecimal byte = 0x02
85+
baseHex byte = 0x03
86+
)
87+
88+
// Sign codes for integer literals (MS-DTYP 2.4.4.17.5).
89+
const (
90+
signPositive byte = 0x01
91+
signNegative byte = 0x02
92+
signNone byte = 0x03
93+
)
94+
95+
// wordOperators maps the SDDL keyword operators to their token byte-codes.
96+
var wordOperators = map[string]byte{
97+
"Contains": tokenContains,
98+
"Not_Contains": tokenNotContains,
99+
"Any_of": tokenAnyOf,
100+
"Not_Any_of": tokenNotAnyOf,
101+
"Member_of": tokenMemberOf,
102+
"Device_Member_of": tokenDeviceMemberOf,
103+
"Member_of_Any": tokenMemberOfAny,
104+
"Device_Member_of_Any": tokenDeviceMemberOfAny,
105+
"Not_Member_of": tokenNotMemberOf,
106+
"Not_Device_Member_of": tokenNotDeviceMemberOf,
107+
"Not_Member_of_Any": tokenNotMemberOfAny,
108+
"Not_Device_Member_of_Any": tokenNotDeviceMemberOfAny,
109+
"Exists": tokenExists,
110+
"Not_Exists": tokenNotExists,
111+
}
112+
113+
// operatorNames is the reverse of wordOperators plus the symbolic operators,
114+
// used when serializing an AST back to SDDL text.
115+
var operatorNames = map[byte]string{
116+
tokenAnd: "&&",
117+
tokenOr: "||",
118+
tokenNot: "!",
119+
tokenEqual: "==",
120+
tokenNotEqual: "!=",
121+
tokenLessThan: "<",
122+
tokenLessOrEqual: "<=",
123+
tokenGreaterThan: ">",
124+
tokenGreaterOrEqual: ">=",
125+
tokenContains: "Contains",
126+
tokenNotContains: "Not_Contains",
127+
tokenAnyOf: "Any_of",
128+
tokenNotAnyOf: "Not_Any_of",
129+
tokenMemberOf: "Member_of",
130+
tokenDeviceMemberOf: "Device_Member_of",
131+
tokenMemberOfAny: "Member_of_Any",
132+
tokenDeviceMemberOfAny: "Device_Member_of_Any",
133+
tokenNotMemberOf: "Not_Member_of",
134+
tokenNotDeviceMemberOf: "Not_Device_Member_of",
135+
tokenNotMemberOfAny: "Not_Member_of_Any",
136+
tokenNotDeviceMemberOfAny: "Not_Device_Member_of_Any",
137+
tokenExists: "Exists",
138+
tokenNotExists: "Not_Exists",
139+
}
140+
141+
func isMemberOfOperator(tok byte) bool {
142+
switch tok {
143+
case tokenMemberOf, tokenDeviceMemberOf, tokenMemberOfAny, tokenDeviceMemberOfAny,
144+
tokenNotMemberOf, tokenNotDeviceMemberOf, tokenNotMemberOfAny, tokenNotDeviceMemberOfAny:
145+
return true
146+
}
147+
return false
148+
}
149+
150+
// ---------------------------------------------------------------------------
151+
// Abstract syntax tree
152+
// ---------------------------------------------------------------------------
153+
154+
// Node is a node in a conditional-expression AST.
155+
type Node interface {
156+
isNode()
157+
}
158+
159+
// Attribute is an attribute-name operand (local or @User./@Resource./@Device.).
160+
type Attribute struct {
161+
Token byte // one of tokenLocalAttr/tokenUserAttr/tokenResourceAttr/tokenDeviceAttr
162+
Name string
163+
}
164+
165+
// IntLiteral is an integer literal. Width is the token byte-code (int8..int64),
166+
// Base and Sign preserve the textual representation for lossless round-tripping.
167+
type IntLiteral struct {
168+
Value int64
169+
Width byte
170+
Base byte
171+
Sign byte
172+
}
173+
174+
// StringLiteral is a Unicode string literal.
175+
type StringLiteral struct{ Value string }
176+
177+
// OctetLiteral is an octet (byte) string literal (SDDL "#hex" form).
178+
type OctetLiteral struct{ Value []byte }
179+
180+
// SIDLiteral is a SID literal (SDDL "SID(...)" form).
181+
type SIDLiteral struct{ SID string }
182+
183+
// Composite is a braced set of literals ("{a,b,c}").
184+
type Composite struct{ Items []Node }
185+
186+
// UnaryOp applies a unary operator (logical !, Exists/Not_Exists, or a
187+
// Member_of-family operator) to a single operand.
188+
type UnaryOp struct {
189+
Op byte
190+
Operand Node
191+
}
192+
193+
// BinaryOp applies a binary operator (relational or logical) to two operands.
194+
type BinaryOp struct {
195+
Op byte
196+
Left, Right Node
197+
}
198+
199+
func (*Attribute) isNode() {}
200+
func (*IntLiteral) isNode() {}
201+
func (*StringLiteral) isNode() {}
202+
func (*OctetLiteral) isNode() {}
203+
func (*SIDLiteral) isNode() {}
204+
func (*Composite) isNode() {}
205+
func (*UnaryOp) isNode() {}
206+
func (*BinaryOp) isNode() {}
207+
208+
// ---------------------------------------------------------------------------
209+
// Public API
210+
// ---------------------------------------------------------------------------
211+
212+
// IsConditional reports whether applicationData carries a conditional
213+
// expression, i.e. whether it begins with the ACE_CONDITION_SIGNATURE.
214+
func IsConditional(applicationData []byte) bool {
215+
return len(applicationData) >= 4 &&
216+
applicationData[0] == ACE_CONDITION_SIGNATURE[0] &&
217+
applicationData[1] == ACE_CONDITION_SIGNATURE[1] &&
218+
applicationData[2] == ACE_CONDITION_SIGNATURE[2] &&
219+
applicationData[3] == ACE_CONDITION_SIGNATURE[3]
220+
}
221+
222+
// Marshal parses an SDDL conditional-expression string and returns the binary
223+
// ApplicationData encoding (signature + postfix token stream + DWORD padding).
224+
// The input may optionally be wrapped in a single outer pair of parentheses, as
225+
// it appears in an ACE string.
226+
func Marshal(expr string) ([]byte, error) {
227+
root, err := Parse(expr)
228+
if err != nil {
229+
return nil, err
230+
}
231+
return Encode(root)
232+
}
233+
234+
// Unmarshal decodes binary conditional-expression ApplicationData into its SDDL
235+
// textual form (without an outer pair of parentheses).
236+
func Unmarshal(applicationData []byte) (string, error) {
237+
root, err := Decode(applicationData)
238+
if err != nil {
239+
return "", err
240+
}
241+
return Serialize(root), nil
242+
}
243+
244+
// Parse converts an SDDL conditional-expression string into an AST.
245+
func Parse(expr string) (Node, error) {
246+
toks, err := lex(expr)
247+
if err != nil {
248+
return nil, err
249+
}
250+
p := &parser{tokens: toks}
251+
node, err := p.parseExpr()
252+
if err != nil {
253+
return nil, err
254+
}
255+
if !p.atEnd() {
256+
return nil, fmt.Errorf("unexpected trailing input at token %q", p.peek().text)
257+
}
258+
return node, nil
259+
}
260+
261+
// Encode serializes an AST into binary ApplicationData.
262+
func Encode(root Node) ([]byte, error) {
263+
out := make([]byte, 0, 32)
264+
out = append(out, ACE_CONDITION_SIGNATURE...)
265+
body, err := encodeNode(root)
266+
if err != nil {
267+
return nil, err
268+
}
269+
out = append(out, body...)
270+
// Pad to a DWORD boundary with 0x00.
271+
for len(out)%4 != 0 {
272+
out = append(out, 0x00)
273+
}
274+
return out, nil
275+
}
276+
277+
// Decode parses binary ApplicationData into an AST.
278+
func Decode(data []byte) (Node, error) {
279+
if !IsConditional(data) {
280+
return nil, fmt.Errorf("application data is not a conditional expression (missing artx signature)")
281+
}
282+
return decodeTokens(data[4:])
283+
}
284+
285+
// Serialize converts an AST back into an SDDL conditional-expression string
286+
// (without an outer pair of parentheses).
287+
func Serialize(root Node) string {
288+
return serializeNode(root, 0)
289+
}

0 commit comments

Comments
 (0)