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