]>
Commit | Line | Data |
---|---|---|
7d31e928 A |
1 | /* |
2 | * Copyright (c) 2006-2007 Apple Inc. All Rights Reserved. | |
3 | * | |
4 | * @APPLE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * This file contains Original Code and/or Modifications of Original Code | |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. Please obtain a copy of the License at | |
10 | * http://www.opensource.apple.com/apsl/ and read it before using this | |
11 | * file. | |
12 | * | |
13 | * The Original Code and all software distributed under the License are | |
14 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
15 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
16 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
18 | * Please see the License for the specific language governing rights and | |
19 | * limitations under the License. | |
20 | * | |
21 | * @APPLE_LICENSE_HEADER_END@ | |
22 | */ | |
23 | ||
24 | // | |
25 | // Requirements Language Grammar | |
26 | // | |
27 | // This file describes two distinct (related) grammars: | |
28 | // Requirement => single requirement (Requirement *) | |
29 | // RequirementSet => set of labeled requirements (Requirements *) | |
30 | // The grammar can "autosense" - i.e. recognize which one it's fed and | |
31 | // return appropriate semantic data. | |
32 | // The semantic data compiled is a malloc'ed BlobCore * - a Requirement | |
33 | // object or a SuperBlob containing multiple Requirements. | |
34 | // | |
35 | header "post_include_hpp" { | |
36 | #include "requirement.h" | |
37 | using namespace CodeSigning; | |
38 | typedef Requirement::Maker Maker; | |
39 | } | |
40 | ||
41 | header "post_include_cpp" { | |
42 | #include "requirement.h" | |
43 | #include "reqmaker.h" | |
44 | #include "csutilities.h" | |
45 | #include <security_utilities/cfutilities.h> | |
46 | #include <security_utilities/hashing.h> | |
47 | using namespace CodeSigning; | |
48 | typedef Requirement::Maker Maker; | |
49 | } | |
50 | ||
51 | options { | |
52 | language="Cpp"; | |
53 | namespace="Security_CodeSigning"; | |
54 | namespaceStd="std"; | |
55 | namespaceAntlr="antlr"; | |
56 | genHashLines=false; | |
57 | } | |
58 | ||
59 | ||
60 | { | |
61 | // | |
62 | // Collect error messages | |
63 | // | |
64 | void RequirementParser::reportError(const antlr::RecognitionException &ex) | |
65 | { | |
66 | errors += ex.toString() + "\n"; | |
67 | } | |
68 | ||
69 | void RequirementParser::reportError(const std::string &s) | |
70 | { | |
71 | errors += s + "\n"; | |
72 | } | |
73 | ||
74 | ||
75 | // | |
76 | // Parser helper functions | |
77 | // | |
78 | string RequirementParser::hexString(const string &s) | |
79 | { | |
80 | if (s.size() % 2) | |
81 | throw antlr::SemanticException("odd number of digits"); | |
82 | const char *p = s.data(); | |
83 | string result; | |
84 | for (unsigned n = 0; n < s.length(); n += 2) { | |
85 | char c; | |
86 | sscanf(p+n, "%2hhx", &c); | |
87 | result.push_back(c); | |
88 | } | |
89 | return result; | |
90 | } | |
91 | ||
92 | void RequirementParser::hashString(const string &s, SHA1::Digest hash) | |
93 | { | |
94 | if (s.size() != 2 * SHA1::digestLength) | |
95 | throw antlr::SemanticException("invalid hash length"); | |
96 | memcpy(hash, hexString(s).data(), SHA1::digestLength); | |
97 | } | |
98 | } | |
99 | ||
100 | class RequirementParser extends Parser; | |
101 | ||
102 | options { | |
103 | k=2; | |
104 | } | |
105 | ||
106 | { | |
107 | public: | |
108 | std::string errors; | |
109 | void reportError(const antlr::RecognitionException &ex); | |
110 | void reportError(const std::string &s); | |
111 | ||
112 | private: | |
113 | static string hexString(const string &s); | |
114 | static void hashString(const string &s, SHA1::Digest hash); | |
115 | } | |
116 | ||
117 | ||
118 | // | |
119 | // Compound target; compiles single requirements or requirement sets | |
120 | // and returns them as a BlobCore. | |
121 | // | |
122 | autosense returns [BlobCore *result = NULL] | |
123 | : result=requirement | |
124 | | result=requirementSet | |
125 | ; | |
126 | ||
127 | ||
128 | // | |
129 | // A Requirements Set. | |
130 | // | |
131 | requirementSet returns [Requirements *result = NULL] | |
132 | { Requirements::Maker maker; } | |
133 | : ( { uint32_t t; Requirement *req; } | |
134 | t=requirementType ARROW req=requirementElement | |
135 | { maker.add(t, req); } | |
136 | )+ | |
137 | { result = errors.empty() ? maker() : NULL; } | |
138 | EOF | |
139 | ; | |
140 | ||
141 | requirementType returns [uint32_t type = kSecInvalidRequirementType] | |
142 | : "guest" | |
143 | { type = kSecGuestRequirementType; } | |
144 | | "host" | |
145 | { type = kSecHostRequirementType; } | |
146 | | "designated" | |
147 | { type = kSecDesignatedRequirementType; } | |
148 | | "library" | |
149 | { type = kSecLibraryRequirementType; } | |
150 | | stype:INTEGER | |
151 | { type = atol(stype->getText().c_str()); } | |
152 | ; | |
153 | ||
154 | ||
155 | // | |
156 | // A single Requirement (untyped) | |
157 | // | |
158 | requirement returns [Requirement *result = NULL] | |
159 | : result = requirementElement | |
160 | EOF | |
161 | ; | |
162 | ||
163 | requirementElement returns [Requirement *result = NULL] | |
164 | { Requirement::Maker maker; } | |
165 | : expr[maker] | |
166 | { result = maker(); } | |
167 | ( fluff )* | |
168 | ; | |
169 | ||
170 | ||
171 | // | |
172 | // Classic recursive expressions | |
173 | // | |
174 | expr[Maker &maker] | |
175 | { Maker::Label label(maker); } | |
176 | : term[maker] ( "and" { maker.insert<ExprOp>(label) = opAnd; } term[maker] )* | |
177 | ; | |
178 | ||
179 | term[Maker &maker] | |
180 | { Maker::Label label(maker); } | |
181 | : primary[maker] ( "or" { maker.insert<ExprOp>(label) = opOr; } primary[maker] )* | |
182 | ; | |
183 | ||
184 | primary[Maker &maker] | |
185 | : LPAREN expr[maker] RPAREN | |
186 | | NOT { maker.put(opNot); } primary[maker] | |
187 | | ( "always" | "true" ) | |
188 | { maker.put(opTrue); } | |
189 | | ( "never" | "false" ) | |
190 | { maker.put(opFalse); } | |
191 | | certspec[maker] | |
192 | | infospec[maker] | |
193 | | "identifier" { string code; } eql code=identifierString | |
194 | { maker.ident(code); } | |
195 | | "cdhash" { SHA1::Digest digest; } hash[digest] | |
196 | { maker.cdhash(digest); } | |
197 | ; | |
198 | ||
199 | ||
200 | // | |
201 | // Certificate specifications restrict certificates in the signing chain | |
202 | // | |
203 | certspec[Maker &maker] | |
204 | : "anchor" "apple" | |
205 | { maker.put(opAppleAnchor); } | |
206 | | ( "certificate" | "cert" | "anchor" ) "trusted" | |
207 | { maker.trustedAnchor(); } | |
208 | | ( "certificate" | "cert" ) { int slot; } slot=certSlot | |
209 | ( certslotspec[maker, slot] | "trusted" { maker.trustedAnchor(slot); } ) | |
210 | | "anchor" certslotspec[maker, Requirement::anchorCert] | |
211 | ; | |
212 | ||
213 | certslotspec[Maker &maker, int slot] { string key; } | |
214 | : eql { SHA1::Digest digest; } certificateDigest[digest] | |
215 | { maker.anchor(slot, digest); } | |
216 | | key=bracketKey | |
217 | { maker.put(opCertField); maker.put(slot); maker.put(key); } | |
218 | match_suffix[maker] | |
219 | ; | |
220 | ||
221 | ||
222 | // | |
223 | // Info specifications place conditions on entries in the Info.plist | |
224 | // | |
225 | infospec[Maker &maker] { string key; } | |
226 | : "info" key=bracketKey | |
227 | { maker.put(opInfoKeyField); maker.put(key); } | |
228 | match_suffix[maker] | |
229 | ; | |
230 | ||
231 | match_suffix[Maker &maker] | |
232 | : empty | |
233 | { maker.put(matchExists); } | |
234 | | EQL { string value; } value=datavalue | |
235 | { maker.put(matchEqual); maker.put(value); } | |
236 | | SUBS { string value; } value=datavalue | |
237 | { maker.put(matchContains); maker.put(value); } | |
238 | ; | |
239 | ||
240 | bracketKey returns [string key] | |
241 | : LBRACK key=stringvalue RBRACK | |
242 | ; | |
243 | ||
244 | // | |
245 | // A certSlot identifiers one certificate from the certificate chain | |
246 | // | |
247 | certSlot returns [int slot] | |
248 | : s:INTEGER // counting from the anchor up | |
249 | { slot = atol(s->getText().c_str()); } | |
250 | | NEG ss:INTEGER // counting from the leaf down | |
251 | { slot = -atol(ss->getText().c_str()); } | |
252 | | "leaf" // the leaf ( == -1) | |
253 | { slot = Requirement::leafCert; } | |
254 | | "root" // the root ( == 0) | |
255 | { slot = Requirement::anchorCert; } | |
256 | ; | |
257 | ||
258 | certSlotAnchor returns [int slot] | |
259 | : slot=certSlot | |
260 | | empty // defaults to anchor ( == 0) | |
261 | { slot = Requirement::anchorCert; } | |
262 | ; | |
263 | ||
264 | // an arbitrary digest value | |
265 | hash[SHA1::Digest digest] | |
266 | : hash:HASHCONSTANT | |
267 | { hashString(hash->getText(), digest); } | |
268 | ; | |
269 | ||
270 | // various forms to specify a certificate hash | |
271 | certificateDigest[SHA1::Digest digest] | |
272 | : hash[digest] | |
273 | | { string path; } path=pathstring | |
274 | { if (CFRef<CFDataRef> certData = cfLoadFile(path)) | |
275 | hashOfCertificate(CFDataGetBytePtr(certData), CFDataGetLength(certData), digest); | |
276 | else | |
277 | throw antlr::SemanticException(path + ": not found"); | |
278 | } | |
279 | ; | |
280 | ||
281 | // generic data - can be simple string, quoted string, or 0x-style hex | |
282 | datavalue returns [string result] | |
283 | : result=stringvalue | |
284 | | hex:HEXCONSTANT { result = hexString(hex->getText()); } | |
285 | ; | |
286 | ||
287 | // strings can always be quoted, but DOTKEYs don't need to be | |
288 | stringvalue returns [string result] | |
289 | : dk:DOTKEY { result = dk->getText(); } | |
290 | | s:STRING { result = s->getText(); } | |
291 | ; | |
292 | ||
293 | // pathstrings are like strings, but PATHNAMEs don't need to be quoted either | |
294 | pathstring returns [string result] | |
295 | : dk:DOTKEY { result = dk->getText(); } | |
296 | | s:STRING { result = s->getText(); } | |
297 | | pn:PATHNAME { result = pn->getText(); } | |
298 | ; | |
299 | ||
300 | // unique identifier value | |
301 | identifierString returns [string result] | |
302 | : dk:DOTKEY { result = dk->getText(); } | |
303 | | s:STRING { result = s->getText(); } | |
304 | ; | |
305 | ||
306 | // syntactic cavity generators | |
307 | fluff | |
308 | : SEMI | |
309 | ; | |
310 | ||
311 | eql | |
312 | : EQL | |
313 | | empty | |
314 | ; | |
315 | ||
316 | empty : ; | |
317 | ||
318 | ||
319 | // | |
320 | // The lexer for the Requirement language. | |
321 | // Really straightforward and conventional. | |
322 | // A subset of strings don't need to be quoted (DOTKEYs), though the disassembler | |
323 | // will always quote them anyway. | |
324 | // There's a syntax H"abcd" to denote hash values (in hex). | |
325 | // | |
326 | class RequirementLexer extends Lexer; | |
327 | ||
328 | options { | |
329 | k=2; | |
330 | testLiterals=false; | |
331 | } | |
332 | ||
333 | protected | |
334 | IDENT options { testLiterals=true; } | |
335 | : ( 'A' .. 'Z' | 'a' .. 'z' ) ( 'A' .. 'Z' | 'a' .. 'z' | '0' .. '9' )* | |
336 | ; | |
337 | ||
338 | DOTKEY options { testLiterals=true; } | |
339 | : IDENT ( "." IDENT )* | |
340 | ; | |
341 | ||
342 | PATHNAME | |
343 | : "/" IDENT ( "/" IDENT )+ | |
344 | ; | |
345 | ||
346 | HASHCONSTANT | |
347 | : 'H'! '"'! ( HEX )+ '"'! | |
348 | ; | |
349 | ||
350 | HEXCONSTANT | |
351 | : '0'! 'x'! ( HEX )+ | |
352 | ; | |
353 | ||
354 | STRING | |
355 | : '"'! ( ( '\\'! '"' ) | ( ~ ( '"' | '\\' ) ) )* '"'! | |
356 | ; | |
357 | ||
358 | INTEGER | |
359 | : ( '0' .. '9' ) + | |
360 | ; | |
361 | ||
362 | protected | |
363 | HEX : '0' .. '9' | 'a' .. 'f' | 'A' .. 'F' ; | |
364 | ||
365 | // operator tokens | |
366 | ARROW : "=>" ; | |
367 | SEMI : ';' ; | |
368 | LPAREN : '(' ; | |
369 | RPAREN : ')' ; | |
370 | LBRACK : '[' ; | |
371 | RBRACK : ']' ; | |
372 | COMMA : ',' ; | |
373 | EQL : '=' ; | |
374 | SUBS : '~' ; | |
375 | NEG : '-' ; | |
376 | NOT : '!' ; | |
377 | ||
378 | ||
379 | // | |
380 | // White spaces | |
381 | // | |
382 | WS : ( ' ' | '\n' { newline(); } | '\t' )+ | |
383 | { $setType(antlr::Token::SKIP); } | |
384 | ; | |
385 | ||
386 | SHELLCOMMENT | |
387 | : '#' ( ~ '\n' )* | |
388 | { $setType(antlr::Token::SKIP); } | |
389 | ; | |
390 | ||
391 | C_COMMENT | |
392 | : "/*" ( (~'*')|('*'(~'/')) )* "*/" | |
393 | { $setType(antlr::Token::SKIP); } | |
394 | ; | |
395 | ||
396 | CPP_COMMENT | |
397 | : "//" ( ~ '\n' )* | |
398 | { $setType(antlr::Token::SKIP); } | |
399 | ; |