1/* 2 * Copyright (c) 2006 Apple Computer, 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// reqinterp - Requirement language (exprOp) interpreter 26// 27#include "reqinterp.h" 28#include "codesigning_dtrace.h" 29#include <Security/SecTrustSettingsPriv.h> 30#include <Security/SecCertificatePriv.h> 31#include <security_utilities/memutils.h> 32#include <security_utilities/logging.h> 33#include "csutilities.h" 34 35namespace Security { 36namespace CodeSigning { 37 38 39// 40// Fragment fetching, caching, and evaluation. 41// 42// Several language elements allow "calling" of separate requirement programs 43// stored on disk as (binary) requirement blobs. The Fragments class takes care 44// of finding, loading, caching, and evaluating them. 45// 46// This is a singleton for (process global) caching. It works fine as multiple instances, 47// at a loss of caching effectiveness. 48// 49class Fragments { 50public: 51 Fragments(); 52 53 bool named(const std::string &name, const Requirement::Context &ctx) 54 { return evalNamed("subreq", name, ctx); } 55 bool namedAnchor(const std::string &name, const Requirement::Context &ctx) 56 { return evalNamed("anchorreq", name, ctx); } 57 58private: 59 bool evalNamed(const char *type, const std::string &name, const Requirement::Context &ctx); 60 CFDataRef fragment(const char *type, const std::string &name); 61 62 typedef std::map<std::string, CFRef<CFDataRef> > FragMap; 63 64private: 65 CFBundleRef mMyBundle; // Security.framework bundle 66 Mutex mLock; // lock for all of the below... 67 FragMap mFragments; // cached fragments 68}; 69 70static ModuleNexus<Fragments> fragments; 71 72 73// 74// Magic certificate features 75// 76static CFStringRef appleIntermediateCN = CFSTR("Apple Code Signing Certification Authority"); 77static CFStringRef appleIntermediateO = CFSTR("Apple Inc."); 78 79 80// 81// Main interpreter function. 82// 83// ExprOp code is in Polish Notation (operator followed by operands), 84// and this engine uses opportunistic evaluation. 85// 86bool Requirement::Interpreter::evaluate() 87{ 88 ExprOp op = ExprOp(get<uint32_t>()); 89 CODESIGN_EVAL_REQINT_OP(op, this->pc() - sizeof(uint32_t)); 90 switch (op & ~opFlagMask) { 91 case opFalse: 92 return false; 93 case opTrue: 94 return true; 95 case opIdent: 96 return mContext->directory && getString() == mContext->directory->identifier(); 97 case opAppleAnchor: 98 return appleSigned(); 99 case opAppleGenericAnchor: 100 return appleAnchored(); 101 case opAnchorHash: 102 { 103 SecCertificateRef cert = mContext->cert(get<int32_t>()); 104 return verifyAnchor(cert, getSHA1()); 105 } 106 case opInfoKeyValue: // [legacy; use opInfoKeyField] 107 { 108 string key = getString(); 109 return infoKeyValue(key, Match(CFTempString(getString()), matchEqual)); 110 } 111 case opAnd: 112 return evaluate() & evaluate(); 113 case opOr: 114 return evaluate() | evaluate(); 115 case opCDHash: 116 if (mContext->directory) { 117 SHA1 hash; 118 hash(mContext->directory, mContext->directory->length()); 119 return hash.verify(getHash()); 120 } else 121 return false; 122 case opNot: 123 return !evaluate(); 124 case opInfoKeyField: 125 { 126 string key = getString(); 127 Match match(*this); 128 return infoKeyValue(key, match); 129 } 130 case opEntitlementField: 131 { 132 string key = getString(); 133 Match match(*this); 134 return entitlementValue(key, match); 135 } 136 case opCertField: 137 { 138 SecCertificateRef cert = mContext->cert(get<int32_t>()); 139 string key = getString(); 140 Match match(*this); 141 return certFieldValue(key, match, cert); 142 } 143 case opCertGeneric: 144 { 145 SecCertificateRef cert = mContext->cert(get<int32_t>()); 146 string key = getString(); 147 Match match(*this); 148 return certFieldGeneric(key, match, cert); 149 } 150 case opCertPolicy: 151 { 152 SecCertificateRef cert = mContext->cert(get<int32_t>()); 153 string key = getString(); 154 Match match(*this); 155 return certFieldPolicy(key, match, cert); 156 } 157 case opTrustedCert: 158 return trustedCert(get<int32_t>()); 159 case opTrustedCerts: 160 return trustedCerts(); 161 case opNamedAnchor: 162 return fragments().namedAnchor(getString(), *mContext); 163 case opNamedCode: 164 return fragments().named(getString(), *mContext); 165 default: 166 // opcode not recognized - handle generically if possible, fail otherwise 167 if (op & (opGenericFalse | opGenericSkip)) { 168 // unknown opcode, but it has a size field and can be safely bypassed 169 skip(get<uint32_t>()); 170 if (op & opGenericFalse) { 171 CODESIGN_EVAL_REQINT_UNKNOWN_FALSE(op); 172 return false; 173 } else { 174 CODESIGN_EVAL_REQINT_UNKNOWN_SKIPPED(op); 175 return evaluate(); 176 } 177 } 178 // unrecognized opcode and no way to interpret it 179 secdebug("csinterp", "opcode 0x%x cannot be handled; aborting", op); 180 MacOSError::throwMe(errSecCSUnimplemented); 181 } 182} 183 184 185// 186// Evaluate an Info.plist key condition 187// 188bool Requirement::Interpreter::infoKeyValue(const string &key, const Match &match) 189{ 190 if (mContext->info) // we have an Info.plist 191 if (CFTypeRef value = CFDictionaryGetValue(mContext->info, CFTempString(key))) 192 return match(value); 193 return false; 194} 195 196 197// 198// Evaluate an entitlement condition 199// 200bool Requirement::Interpreter::entitlementValue(const string &key, const Match &match) 201{ 202 if (mContext->entitlements) // we have an Info.plist 203 if (CFTypeRef value = CFDictionaryGetValue(mContext->entitlements, CFTempString(key))) 204 return match(value); 205 return false; 206} 207 208 209bool Requirement::Interpreter::certFieldValue(const string &key, const Match &match, SecCertificateRef cert) 210{ 211 // no cert, no chance 212 if (cert == NULL) 213 return false; 214 215 // a table of recognized keys for the "certificate[foo]" syntax 216 static const struct CertField { 217 const char *name; 218 const CSSM_OID *oid; 219 } certFields[] = { 220 { "subject.C", &CSSMOID_CountryName }, 221 { "subject.CN", &CSSMOID_CommonName }, 222 { "subject.D", &CSSMOID_Description }, 223 { "subject.L", &CSSMOID_LocalityName }, 224// { "subject.C-L", &CSSMOID_CollectiveLocalityName }, // missing from Security.framework headers 225 { "subject.O", &CSSMOID_OrganizationName }, 226 { "subject.C-O", &CSSMOID_CollectiveOrganizationName }, 227 { "subject.OU", &CSSMOID_OrganizationalUnitName }, 228 { "subject.C-OU", &CSSMOID_CollectiveOrganizationalUnitName }, 229 { "subject.ST", &CSSMOID_StateProvinceName }, 230 { "subject.C-ST", &CSSMOID_CollectiveStateProvinceName }, 231 { "subject.STREET", &CSSMOID_StreetAddress }, 232 { "subject.C-STREET", &CSSMOID_CollectiveStreetAddress }, 233 { "subject.UID", &CSSMOID_UserID }, 234 { NULL, NULL } 235 }; 236 237 // DN-component single-value match 238 for (const CertField *cf = certFields; cf->name; cf++) 239 if (cf->name == key) { 240 CFRef<CFStringRef> value; 241 if (OSStatus rc = SecCertificateCopySubjectComponent(cert, cf->oid, &value.aref())) { 242 secdebug("csinterp", "cert %p lookup for DN.%s failed rc=%d", cert, key.c_str(), (int)rc); 243 return false; 244 } 245 return match(value); 246 } 247 248 // email multi-valued match (any of...) 249 if (key == "email") { 250 CFRef<CFArrayRef> value; 251 if (OSStatus rc = SecCertificateCopyEmailAddresses(cert, &value.aref())) { 252 secdebug("csinterp", "cert %p lookup for email failed rc=%d", cert, (int)rc); 253 return false; 254 } 255 return match(value); 256 } 257 258 // unrecognized key. Fail but do not abort to promote backward compatibility down the road 259 secdebug("csinterp", "cert field notation \"%s\" not understood", key.c_str()); 260 return false; 261} 262 263 264bool Requirement::Interpreter::certFieldGeneric(const string &key, const Match &match, SecCertificateRef cert) 265{ 266 // the key is actually a (binary) OID value 267 CssmOid oid((char *)key.data(), key.length()); 268 return certFieldGeneric(oid, match, cert); 269} 270 271bool Requirement::Interpreter::certFieldGeneric(const CssmOid &oid, const Match &match, SecCertificateRef cert) 272{ 273 return cert && certificateHasField(cert, oid) && match(kCFBooleanTrue); 274} 275 276bool Requirement::Interpreter::certFieldPolicy(const string &key, const Match &match, SecCertificateRef cert) 277{ 278 // the key is actually a (binary) OID value 279 CssmOid oid((char *)key.data(), key.length()); 280 return certFieldPolicy(oid, match, cert); 281} 282 283bool Requirement::Interpreter::certFieldPolicy(const CssmOid &oid, const Match &match, SecCertificateRef cert) 284{ 285 return cert && certificateHasPolicy(cert, oid) && match(kCFBooleanTrue); 286} 287 288 289// 290// Check the Apple-signed condition 291// 292bool Requirement::Interpreter::appleAnchored() 293{ 294 if (SecCertificateRef cert = mContext->cert(anchorCert)) 295 if (isAppleCA(cert) 296#if defined(TEST_APPLE_ANCHOR) 297 || verifyAnchor(cert, testAppleAnchorHash()) 298#endif 299 ) 300 return true; 301 return false; 302} 303 304bool Requirement::Interpreter::appleSigned() 305{ 306 if (appleAnchored()) 307 if (SecCertificateRef intermed = mContext->cert(-2)) // first intermediate 308 // first intermediate common name match (exact) 309 if (certFieldValue("subject.CN", Match(appleIntermediateCN, matchEqual), intermed) 310 && certFieldValue("subject.O", Match(appleIntermediateO, matchEqual), intermed)) 311 return true; 312 return false; 313} 314 315 316// 317// Verify an anchor requirement against the context 318// 319bool Requirement::Interpreter::verifyAnchor(SecCertificateRef cert, const unsigned char *digest) 320{ 321 // get certificate bytes 322 if (cert) { 323 CSSM_DATA certData; 324 MacOSError::check(SecCertificateGetData(cert, &certData)); 325 326 // verify hash 327 //@@@ should get SHA1(cert(-1).data) precalculated during chain verification 328 SHA1 hasher; 329 hasher(certData.Data, certData.Length); 330 return hasher.verify(digest); 331 } 332 return false; 333} 334 335 336// 337// Check one or all certificate(s) in the cert chain against the Trust Settings database. 338// 339bool Requirement::Interpreter::trustedCerts() 340{ 341 int anchor = mContext->certCount() - 1; 342 for (int slot = 0; slot <= anchor; slot++) 343 if (SecCertificateRef cert = mContext->cert(slot)) 344 switch (trustSetting(cert, slot == anchor)) { 345 case kSecTrustSettingsResultTrustRoot: 346 case kSecTrustSettingsResultTrustAsRoot: 347 return true; 348 case kSecTrustSettingsResultDeny: 349 return false; 350 case kSecTrustSettingsResultUnspecified: 351 break; 352 default: 353 assert(false); 354 return false; 355 } 356 else 357 return false; 358 return false; 359} 360 361bool Requirement::Interpreter::trustedCert(int slot) 362{ 363 if (SecCertificateRef cert = mContext->cert(slot)) { 364 int anchorSlot = mContext->certCount() - 1; 365 switch (trustSetting(cert, slot == anchorCert || slot == anchorSlot)) { 366 case kSecTrustSettingsResultTrustRoot: 367 case kSecTrustSettingsResultTrustAsRoot: 368 return true; 369 case kSecTrustSettingsResultDeny: 370 case kSecTrustSettingsResultUnspecified: 371 return false; 372 default: 373 assert(false); 374 return false; 375 } 376 } else 377 return false; 378} 379 380 381// 382// Explicitly check one certificate against the Trust Settings database and report 383// the findings. This is a helper for the various Trust Settings evaluators. 384// 385SecTrustSettingsResult Requirement::Interpreter::trustSetting(SecCertificateRef cert, bool isAnchor) 386{ 387 // the SPI input is the uppercase hex form of the SHA-1 of the certificate... 388 assert(cert); 389 SHA1::Digest digest; 390 hashOfCertificate(cert, digest); 391 string Certhex = CssmData(digest, sizeof(digest)).toHex(); 392 for (string::iterator it = Certhex.begin(); it != Certhex.end(); ++it) 393 if (islower(*it)) 394 *it = toupper(*it); 395 396 // call Trust Settings and see what it finds 397 SecTrustSettingsDomain domain; 398 SecTrustSettingsResult result; 399 CSSM_RETURN *errors = NULL; 400 uint32 errorCount = 0; 401 bool foundMatch, foundAny; 402 switch (OSStatus rc = SecTrustSettingsEvaluateCert( 403 CFTempString(Certhex), // settings index 404 &CSSMOID_APPLE_TP_CODE_SIGNING, // standard code signing policy 405 NULL, 0, // policy string (unused) 406 kSecTrustSettingsKeyUseAny, // no restriction on key usage @@@ 407 isAnchor, // consult system default anchor set 408 409 &domain, // domain of found setting 410 &errors, &errorCount, // error set and maximum count 411 &result, // the actual setting 412 &foundMatch, &foundAny // optimization hints (not used) 413 )) { 414 case errSecSuccess: 415 ::free(errors); 416 if (foundMatch) 417 return result; 418 else 419 return kSecTrustSettingsResultUnspecified; 420 default: 421 ::free(errors); 422 MacOSError::throwMe(rc); 423 } 424} 425 426 427// 428// Create a Match object from the interpreter stream 429// 430Requirement::Interpreter::Match::Match(Interpreter &interp) 431{ 432 switch (mOp = interp.get<MatchOperation>()) { 433 case matchExists: 434 break; 435 case matchEqual: 436 case matchContains: 437 case matchBeginsWith: 438 case matchEndsWith: 439 case matchLessThan: 440 case matchGreaterThan: 441 case matchLessEqual: 442 case matchGreaterEqual: 443 mValue.take(makeCFString(interp.getString())); 444 break; 445 default: 446 // Assume this (unknown) match type has a single data argument. 447 // This gives us a chance to keep the instruction stream aligned. 448 interp.getString(); // discard 449 break; 450 } 451} 452 453 454// 455// Execute a match against a candidate value 456// 457bool Requirement::Interpreter::Match::operator () (CFTypeRef candidate) const 458{ 459 // null candidates always fail 460 if (!candidate) 461 return false; 462 463 // interpret an array as matching alternatives (any one succeeds) 464 if (CFGetTypeID(candidate) == CFArrayGetTypeID()) { 465 CFArrayRef array = CFArrayRef(candidate); 466 CFIndex count = CFArrayGetCount(array); 467 for (CFIndex n = 0; n < count; n++) 468 if ((*this)(CFArrayGetValueAtIndex(array, n))) // yes, it's recursive 469 return true; 470 } 471 472 switch (mOp) { 473 case matchExists: // anything but NULL and boolean false "exists" 474 return !CFEqual(candidate, kCFBooleanFalse); 475 case matchEqual: // equality works for all CF types 476 return CFEqual(candidate, mValue); 477 case matchContains: 478 if (CFGetTypeID(candidate) == CFStringGetTypeID()) { 479 CFStringRef value = CFStringRef(candidate); 480 if (CFStringFindWithOptions(value, mValue, CFRangeMake(0, CFStringGetLength(value)), 0, NULL)) 481 return true; 482 } 483 return false; 484 case matchBeginsWith: 485 if (CFGetTypeID(candidate) == CFStringGetTypeID()) { 486 CFStringRef value = CFStringRef(candidate); 487 if (CFStringFindWithOptions(value, mValue, CFRangeMake(0, CFStringGetLength(mValue)), 0, NULL)) 488 return true; 489 } 490 return false; 491 case matchEndsWith: 492 if (CFGetTypeID(candidate) == CFStringGetTypeID()) { 493 CFStringRef value = CFStringRef(candidate); 494 CFIndex matchLength = CFStringGetLength(mValue); 495 CFIndex start = CFStringGetLength(value) - matchLength; 496 if (start >= 0) 497 if (CFStringFindWithOptions(value, mValue, CFRangeMake(start, matchLength), 0, NULL)) 498 return true; 499 } 500 return false; 501 case matchLessThan: 502 return inequality(candidate, kCFCompareNumerically, kCFCompareLessThan, true); 503 case matchGreaterThan: 504 return inequality(candidate, kCFCompareNumerically, kCFCompareGreaterThan, true); 505 case matchLessEqual: 506 return inequality(candidate, kCFCompareNumerically, kCFCompareGreaterThan, false); 507 case matchGreaterEqual: 508 return inequality(candidate, kCFCompareNumerically, kCFCompareLessThan, false); 509 default: 510 // unrecognized match types can never match 511 return false; 512 } 513} 514 515 516bool Requirement::Interpreter::Match::inequality(CFTypeRef candidate, CFStringCompareFlags flags, 517 CFComparisonResult outcome, bool negate) const 518{ 519 if (CFGetTypeID(candidate) == CFStringGetTypeID()) { 520 CFStringRef value = CFStringRef(candidate); 521 if ((CFStringCompare(value, mValue, flags) == outcome) == negate) 522 return true; 523 } 524 return false; 525} 526 527 528// 529// External fragments 530// 531Fragments::Fragments() 532{ 533 mMyBundle = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.security")); 534} 535 536 537bool Fragments::evalNamed(const char *type, const std::string &name, const Requirement::Context &ctx) 538{ 539 if (CFDataRef fragData = fragment(type, name)) { 540 const Requirement *req = (const Requirement *)CFDataGetBytePtr(fragData); // was prevalidated as Requirement 541 return req->validates(ctx); 542 } 543 return false; 544} 545 546 547CFDataRef Fragments::fragment(const char *type, const std::string &name) 548{ 549 string key = name + "!!" + type; // compound key 550 StLock<Mutex> _(mLock); // lock for cache access 551 FragMap::const_iterator it = mFragments.find(key); 552 if (it == mFragments.end()) { 553 CFRef<CFDataRef> fragData; // will always be set (NULL on any errors) 554 if (CFRef<CFURLRef> fragURL = CFBundleCopyResourceURL(mMyBundle, CFTempString(name), CFSTR("csreq"), CFTempString(type))) 555 if (CFRef<CFDataRef> data = cfLoadFile(fragURL)) { // got data 556 const Requirement *req = (const Requirement *)CFDataGetBytePtr(data); 557 if (req->validateBlob(CFDataGetLength(data))) // looks like a Requirement... 558 fragData = data; // ... so accept it 559 else 560 Syslog::warning("Invalid sub-requirement at %s", cfString(fragURL).c_str()); 561 } 562 if (CODESIGN_EVAL_REQINT_FRAGMENT_LOAD_ENABLED()) 563 CODESIGN_EVAL_REQINT_FRAGMENT_LOAD(type, name.c_str(), fragData ? CFDataGetBytePtr(fragData) : NULL); 564 mFragments[key] = fragData; // cache it, success or failure 565 return fragData; 566 } 567 CODESIGN_EVAL_REQINT_FRAGMENT_HIT(type, name.c_str()); 568 return it->second; 569} 570 571 572} // CodeSigning 573} // Security 574