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); }
	;