]>
Commit | Line | Data |
---|---|---|
7d31e928 | 1 | /* |
d1c1ab47 | 2 | * Copyright (c) 2006-2008 Apple Inc. All Rights Reserved. |
7d31e928 A |
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. | |
516ae477 | 32 | // |
7d31e928 A |
33 | // The semantic data compiled is a malloc'ed BlobCore * - a Requirement |
34 | // object or a SuperBlob containing multiple Requirements. | |
35 | // | |
516ae477 A |
36 | // Errors are indicated to the caller by accumulating error message strings |
37 | // in the errors member variable. Any non-empty error value indicates failure. | |
38 | // Presence of semantic data is not a reliable indication of success. | |
39 | // | |
7d31e928 A |
40 | header "post_include_hpp" { |
41 | #include "requirement.h" | |
42 | using namespace CodeSigning; | |
43 | typedef Requirement::Maker Maker; | |
44 | } | |
45 | ||
46 | header "post_include_cpp" { | |
47 | #include "requirement.h" | |
48 | #include "reqmaker.h" | |
49 | #include "csutilities.h" | |
50 | #include <security_utilities/cfutilities.h> | |
51 | #include <security_utilities/hashing.h> | |
516ae477 | 52 | #include <security_cdsa_utilities/cssmdata.h> // OID coding |
7d31e928 A |
53 | using namespace CodeSigning; |
54 | typedef Requirement::Maker Maker; | |
55 | } | |
56 | ||
57 | options { | |
58 | language="Cpp"; | |
59 | namespace="Security_CodeSigning"; | |
60 | namespaceStd="std"; | |
61 | namespaceAntlr="antlr"; | |
62 | genHashLines=false; | |
63 | } | |
64 | ||
65 | ||
66 | { | |
67 | // | |
516ae477 A |
68 | // Collect error messages. |
69 | // Note that the immediate caller takes the absence of collected error messages | |
70 | // to indicate compilation success. | |
7d31e928 A |
71 | // |
72 | void RequirementParser::reportError(const antlr::RecognitionException &ex) | |
73 | { | |
74 | errors += ex.toString() + "\n"; | |
75 | } | |
76 | ||
77 | void RequirementParser::reportError(const std::string &s) | |
78 | { | |
79 | errors += s + "\n"; | |
80 | } | |
81 | ||
82 | ||
83 | // | |
84 | // Parser helper functions | |
85 | // | |
86 | string RequirementParser::hexString(const string &s) | |
87 | { | |
88 | if (s.size() % 2) | |
89 | throw antlr::SemanticException("odd number of digits"); | |
90 | const char *p = s.data(); | |
91 | string result; | |
92 | for (unsigned n = 0; n < s.length(); n += 2) { | |
93 | char c; | |
94 | sscanf(p+n, "%2hhx", &c); | |
95 | result.push_back(c); | |
96 | } | |
97 | return result; | |
98 | } | |
99 | ||
100 | void RequirementParser::hashString(const string &s, SHA1::Digest hash) | |
101 | { | |
102 | if (s.size() != 2 * SHA1::digestLength) | |
103 | throw antlr::SemanticException("invalid hash length"); | |
104 | memcpy(hash, hexString(s).data(), SHA1::digestLength); | |
105 | } | |
516ae477 | 106 | |
d1c1ab47 | 107 | static const char *matchPrefix(const string &key, const char *prefix) |
516ae477 | 108 | { |
d1c1ab47 A |
109 | unsigned pLength = strlen(prefix); |
110 | if (!key.compare(0, pLength, prefix, 0, pLength)) | |
111 | return key.c_str() + pLength; | |
112 | else | |
113 | return NULL; | |
114 | } | |
115 | ||
116 | void RequirementParser::certMatchOperation(Maker &maker, int32_t slot, string key) | |
117 | { | |
118 | if (matchPrefix(key, "subject.")) { | |
516ae477 A |
119 | maker.put(opCertField); |
120 | maker.put(slot); | |
121 | maker.put(key); | |
d1c1ab47 A |
122 | } else if (const char *oids = matchPrefix(key, "field.")) { |
123 | maker.put(opCertGeneric); | |
124 | maker.put(slot); | |
125 | CssmAutoData oid(Allocator::standard()); oid.fromOid(oids); | |
126 | maker.putData(oid.data(), oid.length()); | |
127 | } else if (const char *oids = matchPrefix(key, "extension.")) { | |
516ae477 A |
128 | maker.put(opCertGeneric); |
129 | maker.put(slot); | |
d1c1ab47 A |
130 | CssmAutoData oid(Allocator::standard()); oid.fromOid(oids); |
131 | maker.putData(oid.data(), oid.length()); | |
516ae477 A |
132 | } else { |
133 | throw antlr::SemanticException(key + ": unrecognized certificate field"); | |
134 | } | |
135 | } | |
7d31e928 A |
136 | } |
137 | ||
516ae477 | 138 | |
7d31e928 A |
139 | class RequirementParser extends Parser; |
140 | ||
141 | options { | |
142 | k=2; | |
143 | } | |
144 | ||
145 | { | |
146 | public: | |
147 | std::string errors; | |
148 | void reportError(const antlr::RecognitionException &ex); | |
149 | void reportError(const std::string &s); | |
150 | ||
151 | private: | |
152 | static string hexString(const string &s); | |
153 | static void hashString(const string &s, SHA1::Digest hash); | |
d1c1ab47 | 154 | void certMatchOperation(Maker &maker, int32_t slot, string key); |
7d31e928 A |
155 | } |
156 | ||
157 | ||
158 | // | |
159 | // Compound target; compiles single requirements or requirement sets | |
160 | // and returns them as a BlobCore. | |
161 | // | |
162 | autosense returns [BlobCore *result = NULL] | |
163 | : result=requirement | |
164 | | result=requirementSet | |
165 | ; | |
166 | ||
167 | ||
168 | // | |
169 | // A Requirements Set. | |
170 | // | |
171 | requirementSet returns [Requirements *result = NULL] | |
172 | { Requirements::Maker maker; } | |
173 | : ( { uint32_t t; Requirement *req; } | |
174 | t=requirementType ARROW req=requirementElement | |
175 | { maker.add(t, req); } | |
176 | )+ | |
177 | { result = errors.empty() ? maker() : NULL; } | |
178 | EOF | |
179 | ; | |
180 | ||
181 | requirementType returns [uint32_t type = kSecInvalidRequirementType] | |
182 | : "guest" | |
183 | { type = kSecGuestRequirementType; } | |
184 | | "host" | |
185 | { type = kSecHostRequirementType; } | |
186 | | "designated" | |
187 | { type = kSecDesignatedRequirementType; } | |
188 | | "library" | |
189 | { type = kSecLibraryRequirementType; } | |
190 | | stype:INTEGER | |
191 | { type = atol(stype->getText().c_str()); } | |
192 | ; | |
193 | ||
194 | ||
195 | // | |
196 | // A single Requirement (untyped) | |
197 | // | |
198 | requirement returns [Requirement *result = NULL] | |
199 | : result = requirementElement | |
200 | EOF | |
201 | ; | |
202 | ||
203 | requirementElement returns [Requirement *result = NULL] | |
204 | { Requirement::Maker maker; } | |
205 | : expr[maker] | |
206 | { result = maker(); } | |
207 | ( fluff )* | |
208 | ; | |
209 | ||
210 | ||
211 | // | |
212 | // Classic recursive expressions | |
213 | // | |
214 | expr[Maker &maker] | |
215 | { Maker::Label label(maker); } | |
d1c1ab47 | 216 | : term[maker] ( "or" { maker.insert<ExprOp>(label) = opOr; } term[maker] )* |
7d31e928 A |
217 | ; |
218 | ||
219 | term[Maker &maker] | |
220 | { Maker::Label label(maker); } | |
d1c1ab47 | 221 | : primary[maker] ( "and" { maker.insert<ExprOp>(label) = opAnd; } primary[maker] )* |
7d31e928 A |
222 | ; |
223 | ||
224 | primary[Maker &maker] | |
225 | : LPAREN expr[maker] RPAREN | |
226 | | NOT { maker.put(opNot); } primary[maker] | |
227 | | ( "always" | "true" ) | |
228 | { maker.put(opTrue); } | |
229 | | ( "never" | "false" ) | |
230 | { maker.put(opFalse); } | |
231 | | certspec[maker] | |
232 | | infospec[maker] | |
516ae477 | 233 | | entitlementspec[maker] |
7d31e928 A |
234 | | "identifier" { string code; } eql code=identifierString |
235 | { maker.ident(code); } | |
516ae477 | 236 | | "cdhash" { SHA1::Digest digest; } eql hash[digest] |
7d31e928 A |
237 | { maker.cdhash(digest); } |
238 | ; | |
239 | ||
240 | ||
241 | // | |
242 | // Certificate specifications restrict certificates in the signing chain | |
243 | // | |
244 | certspec[Maker &maker] | |
516ae477 A |
245 | : "anchor" "apple" appleanchor[maker] |
246 | | "anchor" "generic" "apple" // alternate form | |
247 | { maker.put(opAppleGenericAnchor); } | |
7d31e928 A |
248 | | ( "certificate" | "cert" | "anchor" ) "trusted" |
249 | { maker.trustedAnchor(); } | |
d1c1ab47 | 250 | | ( "certificate" | "cert" ) { int32_t slot; } slot=certSlot |
7d31e928 A |
251 | ( certslotspec[maker, slot] | "trusted" { maker.trustedAnchor(slot); } ) |
252 | | "anchor" certslotspec[maker, Requirement::anchorCert] | |
253 | ; | |
254 | ||
516ae477 A |
255 | appleanchor[Maker &maker] |
256 | : empty | |
257 | { maker.put(opAppleAnchor); } | |
258 | | "generic" | |
259 | { maker.put(opAppleGenericAnchor); } | |
260 | ; | |
261 | ||
d1c1ab47 | 262 | certslotspec[Maker &maker, int32_t slot] { string key; } |
7d31e928 A |
263 | : eql { SHA1::Digest digest; } certificateDigest[digest] |
264 | { maker.anchor(slot, digest); } | |
265 | | key=bracketKey | |
516ae477 | 266 | { certMatchOperation(maker, slot, key); } |
7d31e928 A |
267 | match_suffix[maker] |
268 | ; | |
269 | ||
270 | ||
271 | // | |
272 | // Info specifications place conditions on entries in the Info.plist | |
273 | // | |
274 | infospec[Maker &maker] { string key; } | |
275 | : "info" key=bracketKey | |
276 | { maker.put(opInfoKeyField); maker.put(key); } | |
277 | match_suffix[maker] | |
278 | ; | |
279 | ||
516ae477 A |
280 | |
281 | // | |
282 | // Entitlement specifications place conditions on embedded entitlement entries | |
283 | // | |
284 | entitlementspec[Maker &maker] { string key; } | |
285 | : "entitlement" key=bracketKey | |
286 | { maker.put(opEntitlementField); maker.put(key); } | |
287 | match_suffix[maker] | |
288 | ; | |
289 | ||
290 | ||
291 | // | |
292 | // Common match operations, written as a syntactic suffix (the operand precedes this) | |
293 | // | |
7d31e928 | 294 | match_suffix[Maker &maker] |
516ae477 | 295 | : empty ( "exists" ) ? |
7d31e928 | 296 | { maker.put(matchExists); } |
516ae477 A |
297 | | ( EQL | EQQL ) |
298 | { MatchOperation mop = matchEqual; string value; } | |
299 | ( STAR { mop = matchEndsWith; } ) ? | |
300 | value=datavalue | |
301 | ( STAR { mop = (mop == matchEndsWith) ? matchContains : matchBeginsWith; } ) ? | |
302 | { maker.put(mop); maker.put(value); } | |
7d31e928 A |
303 | | SUBS { string value; } value=datavalue |
304 | { maker.put(matchContains); maker.put(value); } | |
516ae477 A |
305 | | LESS { string value; } value=datavalue |
306 | { maker.put(matchLessThan); maker.put(value); } | |
307 | | GT { string value; } value=datavalue | |
308 | { maker.put(matchGreaterThan); maker.put(value); } | |
309 | | LE { string value; } value=datavalue | |
310 | { maker.put(matchLessEqual); maker.put(value); } | |
311 | | GE { string value; } value=datavalue | |
312 | { maker.put(matchGreaterEqual); maker.put(value); } | |
7d31e928 A |
313 | ; |
314 | ||
315 | bracketKey returns [string key] | |
316 | : LBRACK key=stringvalue RBRACK | |
317 | ; | |
318 | ||
319 | // | |
d1c1ab47 | 320 | // A certSlot identifies one certificate from the certificate chain |
7d31e928 | 321 | // |
d1c1ab47 | 322 | certSlot returns [int32_t slot = 0] |
7d31e928 A |
323 | : s:INTEGER // counting from the anchor up |
324 | { slot = atol(s->getText().c_str()); } | |
325 | | NEG ss:INTEGER // counting from the leaf down | |
326 | { slot = -atol(ss->getText().c_str()); } | |
327 | | "leaf" // the leaf ( == -1) | |
328 | { slot = Requirement::leafCert; } | |
329 | | "root" // the root ( == 0) | |
330 | { slot = Requirement::anchorCert; } | |
331 | ; | |
332 | ||
7d31e928 A |
333 | // an arbitrary digest value |
334 | hash[SHA1::Digest digest] | |
335 | : hash:HASHCONSTANT | |
336 | { hashString(hash->getText(), digest); } | |
337 | ; | |
338 | ||
339 | // various forms to specify a certificate hash | |
340 | certificateDigest[SHA1::Digest digest] | |
341 | : hash[digest] | |
342 | | { string path; } path=pathstring | |
343 | { if (CFRef<CFDataRef> certData = cfLoadFile(path)) | |
344 | hashOfCertificate(CFDataGetBytePtr(certData), CFDataGetLength(certData), digest); | |
345 | else | |
346 | throw antlr::SemanticException(path + ": not found"); | |
347 | } | |
348 | ; | |
349 | ||
350 | // generic data - can be simple string, quoted string, or 0x-style hex | |
351 | datavalue returns [string result] | |
352 | : result=stringvalue | |
353 | | hex:HEXCONSTANT { result = hexString(hex->getText()); } | |
354 | ; | |
355 | ||
356 | // strings can always be quoted, but DOTKEYs don't need to be | |
357 | stringvalue returns [string result] | |
358 | : dk:DOTKEY { result = dk->getText(); } | |
359 | | s:STRING { result = s->getText(); } | |
360 | ; | |
361 | ||
362 | // pathstrings are like strings, but PATHNAMEs don't need to be quoted either | |
363 | pathstring returns [string result] | |
364 | : dk:DOTKEY { result = dk->getText(); } | |
365 | | s:STRING { result = s->getText(); } | |
366 | | pn:PATHNAME { result = pn->getText(); } | |
367 | ; | |
368 | ||
369 | // unique identifier value | |
370 | identifierString returns [string result] | |
371 | : dk:DOTKEY { result = dk->getText(); } | |
372 | | s:STRING { result = s->getText(); } | |
373 | ; | |
374 | ||
375 | // syntactic cavity generators | |
376 | fluff | |
377 | : SEMI | |
378 | ; | |
379 | ||
380 | eql | |
381 | : EQL | |
516ae477 | 382 | | EQQL |
7d31e928 A |
383 | | empty |
384 | ; | |
385 | ||
386 | empty : ; | |
387 | ||
388 | ||
389 | // | |
390 | // The lexer for the Requirement language. | |
391 | // Really straightforward and conventional. | |
516ae477 A |
392 | // A subset of strings don't need to be quoted (DOTKEYs). Neither do some simple |
393 | // pathnames starting with "/". | |
394 | // Hash values have a special syntax H"abcd" (abcd in straight hex). | |
395 | // Hex constants of the form 0xabcd can have any length; they are carried | |
396 | // around as strings (which are in turn stored as data in the language binary). | |
7d31e928 A |
397 | // |
398 | class RequirementLexer extends Lexer; | |
399 | ||
400 | options { | |
401 | k=2; | |
402 | testLiterals=false; | |
403 | } | |
404 | ||
405 | protected | |
406 | IDENT options { testLiterals=true; } | |
407 | : ( 'A' .. 'Z' | 'a' .. 'z' ) ( 'A' .. 'Z' | 'a' .. 'z' | '0' .. '9' )* | |
408 | ; | |
409 | ||
410 | DOTKEY options { testLiterals=true; } | |
516ae477 | 411 | : IDENT ( "." ( IDENT | INTEGER ) )* |
7d31e928 A |
412 | ; |
413 | ||
414 | PATHNAME | |
415 | : "/" IDENT ( "/" IDENT )+ | |
416 | ; | |
417 | ||
418 | HASHCONSTANT | |
419 | : 'H'! '"'! ( HEX )+ '"'! | |
420 | ; | |
421 | ||
422 | HEXCONSTANT | |
423 | : '0'! 'x'! ( HEX )+ | |
424 | ; | |
425 | ||
426 | STRING | |
427 | : '"'! ( ( '\\'! '"' ) | ( ~ ( '"' | '\\' ) ) )* '"'! | |
428 | ; | |
429 | ||
430 | INTEGER | |
431 | : ( '0' .. '9' ) + | |
432 | ; | |
433 | ||
434 | protected | |
435 | HEX : '0' .. '9' | 'a' .. 'f' | 'A' .. 'F' ; | |
436 | ||
437 | // operator tokens | |
438 | ARROW : "=>" ; | |
439 | SEMI : ';' ; | |
440 | LPAREN : '(' ; | |
441 | RPAREN : ')' ; | |
442 | LBRACK : '[' ; | |
443 | RBRACK : ']' ; | |
516ae477 A |
444 | LESS : '<' ; |
445 | GT : '>' ; | |
446 | LE : "<=" ; | |
447 | GE : ">=" ; | |
7d31e928 A |
448 | COMMA : ',' ; |
449 | EQL : '=' ; | |
516ae477 | 450 | EQQL : "==" ; |
7d31e928 A |
451 | SUBS : '~' ; |
452 | NEG : '-' ; | |
453 | NOT : '!' ; | |
516ae477 | 454 | STAR : '*' ; |
7d31e928 A |
455 | |
456 | ||
457 | // | |
458 | // White spaces | |
459 | // | |
460 | WS : ( ' ' | '\n' { newline(); } | '\t' )+ | |
461 | { $setType(antlr::Token::SKIP); } | |
462 | ; | |
463 | ||
464 | SHELLCOMMENT | |
465 | : '#' ( ~ '\n' )* | |
466 | { $setType(antlr::Token::SKIP); } | |
467 | ; | |
468 | ||
469 | C_COMMENT | |
470 | : "/*" ( (~'*')|('*'(~'/')) )* "*/" | |
471 | { $setType(antlr::Token::SKIP); } | |
472 | ; | |
473 | ||
474 | CPP_COMMENT | |
475 | : "//" ( ~ '\n' )* | |
476 | { $setType(antlr::Token::SKIP); } | |
477 | ; |