]>
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()); | |
f60086fc A |
132 | } else if (const char *oids = matchPrefix(key, "policy.")) { |
133 | maker.put(opCertPolicy); | |
134 | maker.put(slot); | |
135 | CssmAutoData oid(Allocator::standard()); oid.fromOid(oids); | |
136 | maker.putData(oid.data(), oid.length()); | |
516ae477 A |
137 | } else { |
138 | throw antlr::SemanticException(key + ": unrecognized certificate field"); | |
139 | } | |
140 | } | |
7d31e928 A |
141 | } |
142 | ||
516ae477 | 143 | |
7d31e928 A |
144 | class RequirementParser extends Parser; |
145 | ||
146 | options { | |
147 | k=2; | |
148 | } | |
149 | ||
150 | { | |
151 | public: | |
152 | std::string errors; | |
153 | void reportError(const antlr::RecognitionException &ex); | |
154 | void reportError(const std::string &s); | |
155 | ||
156 | private: | |
157 | static string hexString(const string &s); | |
158 | static void hashString(const string &s, SHA1::Digest hash); | |
d1c1ab47 | 159 | void certMatchOperation(Maker &maker, int32_t slot, string key); |
7d31e928 A |
160 | } |
161 | ||
162 | ||
163 | // | |
164 | // Compound target; compiles single requirements or requirement sets | |
165 | // and returns them as a BlobCore. | |
166 | // | |
167 | autosense returns [BlobCore *result = NULL] | |
168 | : result=requirement | |
169 | | result=requirementSet | |
170 | ; | |
171 | ||
172 | ||
173 | // | |
174 | // A Requirements Set. | |
175 | // | |
176 | requirementSet returns [Requirements *result = NULL] | |
177 | { Requirements::Maker maker; } | |
178 | : ( { uint32_t t; Requirement *req; } | |
179 | t=requirementType ARROW req=requirementElement | |
180 | { maker.add(t, req); } | |
181 | )+ | |
182 | { result = errors.empty() ? maker() : NULL; } | |
183 | EOF | |
184 | ; | |
185 | ||
186 | requirementType returns [uint32_t type = kSecInvalidRequirementType] | |
187 | : "guest" | |
188 | { type = kSecGuestRequirementType; } | |
189 | | "host" | |
190 | { type = kSecHostRequirementType; } | |
191 | | "designated" | |
192 | { type = kSecDesignatedRequirementType; } | |
193 | | "library" | |
194 | { type = kSecLibraryRequirementType; } | |
f60086fc A |
195 | | "plugin" |
196 | { type = kSecPluginRequirementType; } | |
7d31e928 A |
197 | | stype:INTEGER |
198 | { type = atol(stype->getText().c_str()); } | |
199 | ; | |
200 | ||
201 | ||
202 | // | |
203 | // A single Requirement (untyped) | |
204 | // | |
205 | requirement returns [Requirement *result = NULL] | |
206 | : result = requirementElement | |
207 | EOF | |
208 | ; | |
209 | ||
210 | requirementElement returns [Requirement *result = NULL] | |
211 | { Requirement::Maker maker; } | |
212 | : expr[maker] | |
213 | { result = maker(); } | |
214 | ( fluff )* | |
215 | ; | |
216 | ||
217 | ||
218 | // | |
219 | // Classic recursive expressions | |
220 | // | |
221 | expr[Maker &maker] | |
222 | { Maker::Label label(maker); } | |
d1c1ab47 | 223 | : term[maker] ( "or" { maker.insert<ExprOp>(label) = opOr; } term[maker] )* |
7d31e928 A |
224 | ; |
225 | ||
226 | term[Maker &maker] | |
227 | { Maker::Label label(maker); } | |
d1c1ab47 | 228 | : primary[maker] ( "and" { maker.insert<ExprOp>(label) = opAnd; } primary[maker] )* |
7d31e928 A |
229 | ; |
230 | ||
231 | primary[Maker &maker] | |
232 | : LPAREN expr[maker] RPAREN | |
233 | | NOT { maker.put(opNot); } primary[maker] | |
234 | | ( "always" | "true" ) | |
235 | { maker.put(opTrue); } | |
236 | | ( "never" | "false" ) | |
237 | { maker.put(opFalse); } | |
238 | | certspec[maker] | |
239 | | infospec[maker] | |
516ae477 | 240 | | entitlementspec[maker] |
7d31e928 A |
241 | | "identifier" { string code; } eql code=identifierString |
242 | { maker.ident(code); } | |
516ae477 | 243 | | "cdhash" { SHA1::Digest digest; } eql hash[digest] |
7d31e928 | 244 | { maker.cdhash(digest); } |
f60086fc A |
245 | | LPAREN { string name; } name=identifierString RPAREN |
246 | { maker.put(opNamedCode); maker.put(name); } | |
7d31e928 A |
247 | ; |
248 | ||
249 | ||
250 | // | |
251 | // Certificate specifications restrict certificates in the signing chain | |
252 | // | |
253 | certspec[Maker &maker] | |
516ae477 A |
254 | : "anchor" "apple" appleanchor[maker] |
255 | | "anchor" "generic" "apple" // alternate form | |
256 | { maker.put(opAppleGenericAnchor); } | |
7d31e928 A |
257 | | ( "certificate" | "cert" | "anchor" ) "trusted" |
258 | { maker.trustedAnchor(); } | |
d1c1ab47 | 259 | | ( "certificate" | "cert" ) { int32_t slot; } slot=certSlot |
7d31e928 A |
260 | ( certslotspec[maker, slot] | "trusted" { maker.trustedAnchor(slot); } ) |
261 | | "anchor" certslotspec[maker, Requirement::anchorCert] | |
262 | ; | |
263 | ||
516ae477 A |
264 | appleanchor[Maker &maker] |
265 | : empty | |
266 | { maker.put(opAppleAnchor); } | |
267 | | "generic" | |
268 | { maker.put(opAppleGenericAnchor); } | |
935e6928 | 269 | | { string name; } name=identifierString |
f60086fc | 270 | { maker.put(opNamedAnchor); maker.put(name); } |
516ae477 A |
271 | ; |
272 | ||
d1c1ab47 | 273 | certslotspec[Maker &maker, int32_t slot] { string key; } |
7d31e928 A |
274 | : eql { SHA1::Digest digest; } certificateDigest[digest] |
275 | { maker.anchor(slot, digest); } | |
276 | | key=bracketKey | |
516ae477 | 277 | { certMatchOperation(maker, slot, key); } |
7d31e928 A |
278 | match_suffix[maker] |
279 | ; | |
280 | ||
281 | ||
282 | // | |
283 | // Info specifications place conditions on entries in the Info.plist | |
284 | // | |
285 | infospec[Maker &maker] { string key; } | |
286 | : "info" key=bracketKey | |
287 | { maker.put(opInfoKeyField); maker.put(key); } | |
288 | match_suffix[maker] | |
289 | ; | |
290 | ||
516ae477 A |
291 | |
292 | // | |
293 | // Entitlement specifications place conditions on embedded entitlement entries | |
294 | // | |
295 | entitlementspec[Maker &maker] { string key; } | |
296 | : "entitlement" key=bracketKey | |
297 | { maker.put(opEntitlementField); maker.put(key); } | |
298 | match_suffix[maker] | |
299 | ; | |
300 | ||
301 | ||
302 | // | |
303 | // Common match operations, written as a syntactic suffix (the operand precedes this) | |
304 | // | |
7d31e928 | 305 | match_suffix[Maker &maker] |
516ae477 | 306 | : empty ( "exists" ) ? |
7d31e928 | 307 | { maker.put(matchExists); } |
516ae477 A |
308 | | ( EQL | EQQL ) |
309 | { MatchOperation mop = matchEqual; string value; } | |
310 | ( STAR { mop = matchEndsWith; } ) ? | |
311 | value=datavalue | |
312 | ( STAR { mop = (mop == matchEndsWith) ? matchContains : matchBeginsWith; } ) ? | |
313 | { maker.put(mop); maker.put(value); } | |
7d31e928 A |
314 | | SUBS { string value; } value=datavalue |
315 | { maker.put(matchContains); maker.put(value); } | |
516ae477 A |
316 | | LESS { string value; } value=datavalue |
317 | { maker.put(matchLessThan); maker.put(value); } | |
318 | | GT { string value; } value=datavalue | |
319 | { maker.put(matchGreaterThan); maker.put(value); } | |
320 | | LE { string value; } value=datavalue | |
321 | { maker.put(matchLessEqual); maker.put(value); } | |
322 | | GE { string value; } value=datavalue | |
323 | { maker.put(matchGreaterEqual); maker.put(value); } | |
7d31e928 A |
324 | ; |
325 | ||
326 | bracketKey returns [string key] | |
327 | : LBRACK key=stringvalue RBRACK | |
328 | ; | |
329 | ||
330 | // | |
d1c1ab47 | 331 | // A certSlot identifies one certificate from the certificate chain |
7d31e928 | 332 | // |
d1c1ab47 | 333 | certSlot returns [int32_t slot = 0] |
7d31e928 A |
334 | : s:INTEGER // counting from the anchor up |
335 | { slot = atol(s->getText().c_str()); } | |
336 | | NEG ss:INTEGER // counting from the leaf down | |
337 | { slot = -atol(ss->getText().c_str()); } | |
338 | | "leaf" // the leaf ( == -1) | |
339 | { slot = Requirement::leafCert; } | |
340 | | "root" // the root ( == 0) | |
341 | { slot = Requirement::anchorCert; } | |
342 | ; | |
343 | ||
7d31e928 A |
344 | // an arbitrary digest value |
345 | hash[SHA1::Digest digest] | |
346 | : hash:HASHCONSTANT | |
347 | { hashString(hash->getText(), digest); } | |
348 | ; | |
349 | ||
350 | // various forms to specify a certificate hash | |
351 | certificateDigest[SHA1::Digest digest] | |
352 | : hash[digest] | |
353 | | { string path; } path=pathstring | |
354 | { if (CFRef<CFDataRef> certData = cfLoadFile(path)) | |
355 | hashOfCertificate(CFDataGetBytePtr(certData), CFDataGetLength(certData), digest); | |
356 | else | |
357 | throw antlr::SemanticException(path + ": not found"); | |
358 | } | |
359 | ; | |
360 | ||
361 | // generic data - can be simple string, quoted string, or 0x-style hex | |
362 | datavalue returns [string result] | |
363 | : result=stringvalue | |
364 | | hex:HEXCONSTANT { result = hexString(hex->getText()); } | |
365 | ; | |
366 | ||
367 | // strings can always be quoted, but DOTKEYs don't need to be | |
368 | stringvalue returns [string result] | |
369 | : dk:DOTKEY { result = dk->getText(); } | |
370 | | s:STRING { result = s->getText(); } | |
371 | ; | |
372 | ||
373 | // pathstrings are like strings, but PATHNAMEs don't need to be quoted either | |
374 | pathstring returns [string result] | |
375 | : dk:DOTKEY { result = dk->getText(); } | |
376 | | s:STRING { result = s->getText(); } | |
377 | | pn:PATHNAME { result = pn->getText(); } | |
378 | ; | |
379 | ||
380 | // unique identifier value | |
381 | identifierString returns [string result] | |
382 | : dk:DOTKEY { result = dk->getText(); } | |
383 | | s:STRING { result = s->getText(); } | |
384 | ; | |
385 | ||
386 | // syntactic cavity generators | |
387 | fluff | |
388 | : SEMI | |
389 | ; | |
390 | ||
391 | eql | |
392 | : EQL | |
516ae477 | 393 | | EQQL |
7d31e928 A |
394 | | empty |
395 | ; | |
396 | ||
397 | empty : ; | |
398 | ||
399 | ||
400 | // | |
401 | // The lexer for the Requirement language. | |
402 | // Really straightforward and conventional. | |
516ae477 A |
403 | // A subset of strings don't need to be quoted (DOTKEYs). Neither do some simple |
404 | // pathnames starting with "/". | |
405 | // Hash values have a special syntax H"abcd" (abcd in straight hex). | |
406 | // Hex constants of the form 0xabcd can have any length; they are carried | |
407 | // around as strings (which are in turn stored as data in the language binary). | |
7d31e928 A |
408 | // |
409 | class RequirementLexer extends Lexer; | |
410 | ||
411 | options { | |
412 | k=2; | |
413 | testLiterals=false; | |
414 | } | |
415 | ||
416 | protected | |
417 | IDENT options { testLiterals=true; } | |
418 | : ( 'A' .. 'Z' | 'a' .. 'z' ) ( 'A' .. 'Z' | 'a' .. 'z' | '0' .. '9' )* | |
419 | ; | |
420 | ||
421 | DOTKEY options { testLiterals=true; } | |
516ae477 | 422 | : IDENT ( "." ( IDENT | INTEGER ) )* |
7d31e928 A |
423 | ; |
424 | ||
425 | PATHNAME | |
426 | : "/" IDENT ( "/" IDENT )+ | |
427 | ; | |
428 | ||
429 | HASHCONSTANT | |
430 | : 'H'! '"'! ( HEX )+ '"'! | |
431 | ; | |
432 | ||
433 | HEXCONSTANT | |
434 | : '0'! 'x'! ( HEX )+ | |
435 | ; | |
436 | ||
437 | STRING | |
438 | : '"'! ( ( '\\'! '"' ) | ( ~ ( '"' | '\\' ) ) )* '"'! | |
439 | ; | |
440 | ||
441 | INTEGER | |
442 | : ( '0' .. '9' ) + | |
443 | ; | |
444 | ||
445 | protected | |
446 | HEX : '0' .. '9' | 'a' .. 'f' | 'A' .. 'F' ; | |
447 | ||
448 | // operator tokens | |
449 | ARROW : "=>" ; | |
450 | SEMI : ';' ; | |
451 | LPAREN : '(' ; | |
452 | RPAREN : ')' ; | |
453 | LBRACK : '[' ; | |
454 | RBRACK : ']' ; | |
516ae477 A |
455 | LESS : '<' ; |
456 | GT : '>' ; | |
457 | LE : "<=" ; | |
458 | GE : ">=" ; | |
7d31e928 A |
459 | COMMA : ',' ; |
460 | EQL : '=' ; | |
516ae477 | 461 | EQQL : "==" ; |
7d31e928 A |
462 | SUBS : '~' ; |
463 | NEG : '-' ; | |
464 | NOT : '!' ; | |
516ae477 | 465 | STAR : '*' ; |
7d31e928 A |
466 | |
467 | ||
468 | // | |
469 | // White spaces | |
470 | // | |
471 | WS : ( ' ' | '\n' { newline(); } | '\t' )+ | |
472 | { $setType(antlr::Token::SKIP); } | |
473 | ; | |
474 | ||
475 | SHELLCOMMENT | |
476 | : '#' ( ~ '\n' )* | |
477 | { $setType(antlr::Token::SKIP); } | |
478 | ; | |
479 | ||
480 | C_COMMENT | |
481 | : "/*" ( (~'*')|('*'(~'/')) )* "*/" | |
482 | { $setType(antlr::Token::SKIP); } | |
483 | ; | |
484 | ||
485 | CPP_COMMENT | |
486 | : "//" ( ~ '\n' )* | |
487 | { $setType(antlr::Token::SKIP); } | |
488 | ; |