requirements.grammar [plain text]
/*
* 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 <security_utilities/cfutilities.h>
#include <security_utilities/hashing.h>
#include <security_cdsa_utilities/cssmdata.h> // 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)
{
unsigned 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; }
| stype:INTEGER
{ type = atol(stype->getText().c_str()); }
;
//
// 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<ExprOp>(label) = opOr; } term[maker] )*
;
term[Maker &maker]
{ Maker::Label label(maker); }
: primary[maker] ( "and" { maker.insert<ExprOp>(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); }
| 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]
: s:INTEGER // counting from the anchor up
{ slot = atol(s->getText().c_str()); }
| NEG ss:INTEGER // counting from the leaf down
{ slot = -atol(ss->getText().c_str()); }
| "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<CFDataRef> 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(); }
;
// 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;
}
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); }
;