/* * Copyright (c) 2006-2008 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ // // Requirements Language Grammar // // This file describes two distinct (related) grammars: // Requirement => single requirement (Requirement *) // RequirementSet => set of labeled requirements (Requirements *) // The grammar can "autosense" - i.e. recognize which one it's fed and // return appropriate semantic data. // // The semantic data compiled is a malloc'ed BlobCore * - a Requirement // object or a SuperBlob containing multiple Requirements. // // Errors are indicated to the caller by accumulating error message strings // in the errors member variable. Any non-empty error value indicates failure. // Presence of semantic data is not a reliable indication of success. // header "post_include_hpp" { #include "requirement.h" using namespace CodeSigning; typedef Requirement::Maker Maker; } header "post_include_cpp" { #include "requirement.h" #include "reqmaker.h" #include "csutilities.h" #include #include #include // OID coding using namespace CodeSigning; typedef Requirement::Maker Maker; } options { language="Cpp"; namespace="Security_CodeSigning"; namespaceStd="std"; namespaceAntlr="antlr"; genHashLines=false; } { // // Collect error messages. // Note that the immediate caller takes the absence of collected error messages // to indicate compilation success. // void RequirementParser::reportError(const antlr::RecognitionException &ex) { errors += ex.toString() + "\n"; } void RequirementParser::reportError(const std::string &s) { errors += s + "\n"; } // // Parser helper functions // string RequirementParser::hexString(const string &s) { if (s.size() % 2) throw antlr::SemanticException("odd number of digits"); const char *p = s.data(); string result; for (unsigned n = 0; n < s.length(); n += 2) { char c; sscanf(p+n, "%2hhx", &c); result.push_back(c); } return result; } void RequirementParser::hashString(const string &s, SHA1::Digest hash) { if (s.size() != 2 * SHA1::digestLength) throw antlr::SemanticException("invalid hash length"); memcpy(hash, hexString(s).data(), SHA1::digestLength); } static const char *matchPrefix(const string &key, const char *prefix) { size_t pLength = strlen(prefix); if (!key.compare(0, pLength, prefix, 0, pLength)) return key.c_str() + pLength; else return NULL; } void RequirementParser::certMatchOperation(Maker &maker, int32_t slot, string key) { if (matchPrefix(key, "subject.")) { maker.put(opCertField); maker.put(slot); maker.put(key); } else if (const char *oids = matchPrefix(key, "field.")) { maker.put(opCertGeneric); maker.put(slot); CssmAutoData oid(Allocator::standard()); oid.fromOid(oids); maker.putData(oid.data(), oid.length()); } else if (const char *oids = matchPrefix(key, "extension.")) { maker.put(opCertGeneric); maker.put(slot); CssmAutoData oid(Allocator::standard()); oid.fromOid(oids); maker.putData(oid.data(), oid.length()); } else if (const char *oids = matchPrefix(key, "policy.")) { maker.put(opCertPolicy); maker.put(slot); CssmAutoData oid(Allocator::standard()); oid.fromOid(oids); maker.putData(oid.data(), oid.length()); } else { throw antlr::SemanticException(key + ": unrecognized certificate field"); } } } class RequirementParser extends Parser; options { k=2; } { public: std::string errors; void reportError(const antlr::RecognitionException &ex); void reportError(const std::string &s); private: static string hexString(const string &s); static void hashString(const string &s, SHA1::Digest hash); void certMatchOperation(Maker &maker, int32_t slot, string key); } // // Compound target; compiles single requirements or requirement sets // and returns them as a BlobCore. // autosense returns [BlobCore *result = NULL] : result=requirement | result=requirementSet ; // // A Requirements Set. // requirementSet returns [Requirements *result = NULL] { Requirements::Maker maker; } : ( { uint32_t t; Requirement *req; } t=requirementType ARROW req=requirementElement { maker.add(t, req); } )+ { result = errors.empty() ? maker() : NULL; } EOF ; requirementType returns [uint32_t type = kSecInvalidRequirementType] : "guest" { type = kSecGuestRequirementType; } | "host" { type = kSecHostRequirementType; } | "designated" { type = kSecDesignatedRequirementType; } | "library" { type = kSecLibraryRequirementType; } | "plugin" { type = kSecPluginRequirementType; } | type=integer ; // // A single Requirement (untyped) // requirement returns [Requirement *result = NULL] : result = requirementElement EOF ; requirementElement returns [Requirement *result = NULL] { Requirement::Maker maker; } : expr[maker] { result = maker(); } ( fluff )* ; // // Classic recursive expressions // expr[Maker &maker] { Maker::Label label(maker); } : term[maker] ( "or" { maker.insert(label) = opOr; } term[maker] )* ; term[Maker &maker] { Maker::Label label(maker); } : primary[maker] ( "and" { maker.insert(label) = opAnd; } primary[maker] )* ; primary[Maker &maker] : LPAREN expr[maker] RPAREN | NOT { maker.put(opNot); } primary[maker] | ( "always" | "true" ) { maker.put(opTrue); } | ( "never" | "false" ) { maker.put(opFalse); } | certspec[maker] | infospec[maker] | entitlementspec[maker] | "identifier" { string code; } eql code=identifierString { maker.ident(code); } | "cdhash" { SHA1::Digest digest; } eql hash[digest] { maker.cdhash(digest); } | "platform" { int32_t ident; } eql ident=integer { maker.platform(ident); } | "notarized" { maker.put(opNotarized); } | LPAREN { string name; } name=identifierString RPAREN { maker.put(opNamedCode); maker.put(name); } ; // // Certificate specifications restrict certificates in the signing chain // certspec[Maker &maker] : "anchor" "apple" appleanchor[maker] | "anchor" "generic" "apple" // alternate form { maker.put(opAppleGenericAnchor); } | ( "certificate" | "cert" | "anchor" ) "trusted" { maker.trustedAnchor(); } | ( "certificate" | "cert" ) { int32_t slot; } slot=certSlot ( certslotspec[maker, slot] | "trusted" { maker.trustedAnchor(slot); } ) | "anchor" certslotspec[maker, Requirement::anchorCert] ; appleanchor[Maker &maker] : empty { maker.put(opAppleAnchor); } | "generic" { maker.put(opAppleGenericAnchor); } | { string name; } name=identifierString { maker.put(opNamedAnchor); maker.put(name); } ; certslotspec[Maker &maker, int32_t slot] { string key; } : eql { SHA1::Digest digest; } certificateDigest[digest] { maker.anchor(slot, digest); } | key=bracketKey { certMatchOperation(maker, slot, key); } match_suffix[maker] ; // // Info specifications place conditions on entries in the Info.plist // infospec[Maker &maker] { string key; } : "info" key=bracketKey { maker.put(opInfoKeyField); maker.put(key); } match_suffix[maker] ; // // Entitlement specifications place conditions on embedded entitlement entries // entitlementspec[Maker &maker] { string key; } : "entitlement" key=bracketKey { maker.put(opEntitlementField); maker.put(key); } match_suffix[maker] ; // // Common match operations, written as a syntactic suffix (the operand precedes this) // match_suffix[Maker &maker] : empty ( "exists" ) ? { maker.put(matchExists); } | ( EQL | EQQL ) { MatchOperation mop = matchEqual; string value; } ( STAR { mop = matchEndsWith; } ) ? value=datavalue ( STAR { mop = (mop == matchEndsWith) ? matchContains : matchBeginsWith; } ) ? { maker.put(mop); maker.put(value); } | SUBS { string value; } value=datavalue { maker.put(matchContains); maker.put(value); } | LESS { string value; } value=datavalue { maker.put(matchLessThan); maker.put(value); } | GT { string value; } value=datavalue { maker.put(matchGreaterThan); maker.put(value); } | LE { string value; } value=datavalue { maker.put(matchLessEqual); maker.put(value); } | GE { string value; } value=datavalue { maker.put(matchGreaterEqual); maker.put(value); } ; bracketKey returns [string key] : LBRACK key=stringvalue RBRACK ; // // A certSlot identifies one certificate from the certificate chain // certSlot returns [int32_t slot = 0] : slot=integer // counting from the anchor up | NEG slot=integer // counting from the leaf down { slot = -slot; } | "leaf" // the leaf ( == -1) { slot = Requirement::leafCert; } | "root" // the root ( == 0) { slot = Requirement::anchorCert; } ; // an arbitrary digest value hash[SHA1::Digest digest] : hash:HASHCONSTANT { hashString(hash->getText(), digest); } ; // various forms to specify a certificate hash certificateDigest[SHA1::Digest digest] : hash[digest] | { string path; } path=pathstring { if (CFRef certData = cfLoadFile(path)) hashOfCertificate(CFDataGetBytePtr(certData), CFDataGetLength(certData), digest); else throw antlr::SemanticException(path + ": not found"); } ; // generic data - can be simple string, quoted string, or 0x-style hex datavalue returns [string result] : result=stringvalue | hex:HEXCONSTANT { result = hexString(hex->getText()); } ; // strings can always be quoted, but DOTKEYs don't need to be stringvalue returns [string result] : dk:DOTKEY { result = dk->getText(); } | s:STRING { result = s->getText(); } ; // pathstrings are like strings, but PATHNAMEs don't need to be quoted either pathstring returns [string result] : dk:DOTKEY { result = dk->getText(); } | s:STRING { result = s->getText(); } | pn:PATHNAME { result = pn->getText(); } ; // unique identifier value identifierString returns [string result] : dk:DOTKEY { result = dk->getText(); } | s:STRING { result = s->getText(); } ; // 32-bit integer integer returns [int32_t result] : s:INTEGER { result = int32_t(atol(s->getText().c_str())); } ; // syntactic cavity generators fluff : SEMI ; eql : EQL | EQQL | empty ; empty : ; // // The lexer for the Requirement language. // Really straightforward and conventional. // A subset of strings don't need to be quoted (DOTKEYs). Neither do some simple // pathnames starting with "/". // Hash values have a special syntax H"abcd" (abcd in straight hex). // Hex constants of the form 0xabcd can have any length; they are carried // around as strings (which are in turn stored as data in the language binary). // class RequirementLexer extends Lexer; options { k=2; testLiterals=false; // Pass through valid UTF-8 (which excludes hex C0-C1 and F5-FF), // but also exclude ASCII control characters below 0x20 (space). // Byte ranges according to Unicode 11.0, paragraph 3.9 D92. charVocabulary='\000'..'\277' | '\302'..'\364'; } protected IDENT options { testLiterals=true; } : ( 'A' .. 'Z' | 'a' .. 'z' ) ( 'A' .. 'Z' | 'a' .. 'z' | '0' .. '9' )* ; DOTKEY options { testLiterals=true; } : IDENT ( "." ( IDENT | INTEGER ) )* ; PATHNAME : "/" IDENT ( "/" IDENT )+ ; HASHCONSTANT : 'H'! '"'! ( HEX )+ '"'! ; HEXCONSTANT : '0'! 'x'! ( HEX )+ ; STRING : '"'! ( ( '\\'! '"' ) | ( ~ ( '"' | '\\' ) ) )* '"'! ; INTEGER : ( '0' .. '9' ) + ; protected HEX : '0' .. '9' | 'a' .. 'f' | 'A' .. 'F' ; // operator tokens ARROW : "=>" ; SEMI : ';' ; LPAREN : '(' ; RPAREN : ')' ; LBRACK : '[' ; RBRACK : ']' ; LESS : '<' ; GT : '>' ; LE : "<=" ; GE : ">=" ; COMMA : ',' ; EQL : '=' ; EQQL : "==" ; SUBS : '~' ; NEG : '-' ; NOT : '!' ; STAR : '*' ; // // White spaces // WS : ( ' ' | '\n' { newline(); } | '\t' )+ { $setType(antlr::Token::SKIP); } ; SHELLCOMMENT : '#' ( ~ '\n' )* { $setType(antlr::Token::SKIP); } ; C_COMMENT : "/*" ( (~'*')|('*'(~'/')) )* "*/" { $setType(antlr::Token::SKIP); } ; CPP_COMMENT : "//" ( ~ '\n' )* { $setType(antlr::Token::SKIP); } ;