1/* 2 * Copyright (c) 2003-2009,2012 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// 26// codesigdb - code-hash equivalence database 27// 28#include "codesigdb.h" 29#include "process.h" 30#include "server.h" 31#include "agentquery.h" 32#include <security_utilities/memutils.h> 33#include <security_utilities/logging.h> 34#include <Security/SecRequirementPriv.h> 35 36 37// 38// A self-constructing database key class. 39// Key format is <t><uid|S><key data> 40// where 41// <t> single ASCII character type code ('H' for hash links) 42// <uid|S> decimal userid of owning user, or 'S' for system entries. Followed by null byte. 43// <key data> variable length key value (binary). 44// 45class DbKey : public CssmAutoData { 46public: 47 DbKey(char type, const CssmData &key, bool perUser = false, uid_t user = 0); 48}; 49 50DbKey::DbKey(char type, const CssmData &key, bool perUser, uid_t user) 51 : CssmAutoData(Allocator::standard()) 52{ 53 using namespace LowLevelMemoryUtilities; 54 char header[20]; 55 size_t headerLength; 56 if (perUser) 57 headerLength = 1 + sprintf(header, "%c%d", type, user); 58 else 59 headerLength = 1 + sprintf(header, "%cS", type); 60 malloc(headerLength + key.length()); 61 memcpy(this->data(), header, headerLength); 62 memcpy(get().at(headerLength), key.data(), key.length()); 63} 64 65 66// 67// A subclass of Identity made of whole cloth (from a raw CodeSignature ACL information) 68// 69struct AclIdentity : public CodeSignatures::Identity { 70 AclIdentity(const CssmData hash, string path) : mHash(hash), mPath(path) { } 71 72 string getPath() const { return mPath; } 73 const CssmData getHash() const { return mHash; } 74 75private: 76 const CssmData mHash; 77 const string mPath; 78}; 79 80 81// 82// Construct a CodeSignatures objects 83// 84CodeSignatures::CodeSignatures(const char *path) 85{ 86 try { 87 mDb.open(path, O_RDWR | O_CREAT, 0644); 88 } catch (const CommonError &err) { 89 try { 90 mDb.open(path, O_RDONLY, 0644); 91 Syslog::warning("database %s opened READONLY (R/W failed errno=%d)", path, err.unixError()); 92 secdebug("codesign", "database %s opened READONLY (R/W failed errno=%d)", path, err.unixError()); 93 } catch (...) { 94 Syslog::warning("cannot open %s; using no code equivalents", path); 95 secdebug("codesign", "unable to open %s; using no code equivalents", path); 96 } 97 } 98 if (mDb) 99 mDb.flush(); // in case we just created it 100 IFDUMPING("equiv", debugDump("open")); 101} 102 103CodeSignatures::~CodeSignatures() 104{ 105} 106 107 108// 109// (Re)open the equivalence database. 110// This is useful to switch to database in another volume. 111// 112void CodeSignatures::open(const char *path) 113{ 114 mDb.open(path, O_RDWR | O_CREAT, 0644); 115 mDb.flush(); 116 IFDUMPING("equiv", debugDump("reopen")); 117} 118 119 120// 121// Basic Identity objects 122// 123CodeSignatures::Identity::Identity() : mState(untried) 124{ } 125 126CodeSignatures::Identity::~Identity() 127{ } 128 129string CodeSignatures::Identity::canonicalName(const string &path) 130{ 131 string::size_type slash = path.rfind('/'); 132 if (slash == string::npos) // bloody unlikely, but whatever... 133 return path; 134 return path.substr(slash+1); 135} 136 137 138// 139// Find and store database objects (primitive layer) 140// 141bool CodeSignatures::find(Identity &id, uid_t user) 142{ 143 if (id.mState != Identity::untried) 144 return id.mState == Identity::valid; 145 try { 146 DbKey userKey('H', id.getHash(), true, user); 147 CssmData linkValue; 148 if (mDb.get(userKey, linkValue)) { 149 id.mName = string(linkValue.interpretedAs<const char>(), linkValue.length()); 150 IFDUMPING("equiv", id.debugDump("found/user")); 151 id.mState = Identity::valid; 152 return true; 153 } 154 DbKey sysKey('H', id.getHash()); 155 if (mDb.get(sysKey, linkValue)) { 156 id.mName = string(linkValue.interpretedAs<const char>(), linkValue.length()); 157 IFDUMPING("equiv", id.debugDump("found/system")); 158 id.mState = Identity::valid; 159 return true; 160 } 161 } catch (...) { 162 secdebug("codesign", "exception validating identity for %s - marking failed", id.path().c_str()); 163 id.mState = Identity::invalid; 164 } 165 return id.mState == Identity::valid; 166} 167 168void CodeSignatures::makeLink(Identity &id, const string &ident, bool forUser, uid_t user) 169{ 170 DbKey key('H', id.getHash(), forUser, user); 171 if (!mDb.put(key, StringData(ident))) 172 UnixError::throwMe(); 173} 174 175 176// 177// Administrative manipulation calls 178// 179void CodeSignatures::addLink(const CssmData &oldHash, const CssmData &newHash, 180 const char *inName, bool forSystem) 181{ 182 string name = Identity::canonicalName(inName); 183 uid_t user = Server::process().uid(); 184 if (forSystem && user) // only root user can establish forSystem links 185 UnixError::throwMe(EACCES); 186 if (!forSystem) // in fact, for now we don't allow per-user calls at all 187 UnixError::throwMe(EACCES); 188 AclIdentity oldCode(oldHash, name); 189 AclIdentity newCode(newHash, name); 190 secdebug("codesign", "addlink for name %s", name.c_str()); 191 StLock<Mutex> _(mDatabaseLock); 192 if (oldCode) { 193 if (oldCode.trustedName() != name) { 194 secdebug("codesign", "addlink does not match existing name %s", 195 oldCode.trustedName().c_str()); 196 MacOSError::throwMe(CSSMERR_CSP_VERIFY_FAILED); 197 } 198 } else { 199 makeLink(oldCode, name, !forSystem, user); 200 } 201 if (!newCode) 202 makeLink(newCode, name, !forSystem, user); 203 mDb.flush(); 204} 205 206void CodeSignatures::removeLink(const CssmData &hash, const char *name, bool forSystem) 207{ 208 AclIdentity code(hash, name); 209 uid_t user = Server::process().uid(); 210 if (forSystem && user) // only root user can remove forSystem links 211 UnixError::throwMe(EACCES); 212 DbKey key('H', hash, !forSystem, user); 213 StLock<Mutex> _(mDatabaseLock); 214 mDb.erase(key); 215 mDb.flush(); 216} 217 218 219// 220// Verify signature matches. 221// This ends up getting called when a CodeSignatureAclSubject is validated. 222// The OSXVerifier describes what we require of the client code; the process represents 223// the requesting client; and the context gives us access to the ACL and its environment 224// in case we want to, well, creatively rewrite it for some reason. 225// 226bool CodeSignatures::verify(Process &process, 227 const OSXVerifier &verifier, const AclValidationContext &context) 228{ 229 secdebug("codesign", "start verify"); 230 231 StLock<Mutex> _(process); 232 SecCodeRef code = process.currentGuest(); 233 if (!code) { 234 secdebug("codesign", "no code base: fail"); 235 return false; 236 } 237 if (SecRequirementRef requirement = verifier.requirement()) { 238 // If the ACL contains a code signature (requirement), we won't match against unsigned code at all. 239 // The legacy hash is ignored (it's for use by pre-Leopard systems). 240 secdebug("codesign", "CS requirement present; ignoring legacy hashes"); 241 Server::active().longTermActivity(); 242 switch (OSStatus rc = SecCodeCheckValidity(code, kSecCSDefaultFlags, requirement)) { 243 case noErr: 244 secdebug("codesign", "CS verify passed"); 245 return true; 246 case errSecCSUnsigned: 247 secdebug("codesign", "CS verify against unsigned binary failed"); 248 return false; 249 default: 250 secdebug("codesign", "CS verify failed OSStatus=%d", int32_t(rc)); 251 return false; 252 } 253 } 254 switch (matchSignedClientToLegacyACL(process, code, verifier, context)) { 255 case noErr: // handled, allow access 256 return true; 257 case errSecCSUnsigned: // unsigned client, complete legacy case 258 secdebug("codesign", "no CS requirement - using legacy hash"); 259 return verifyLegacy(process, 260 CssmData::wrap(verifier.legacyHash(), SHA1::digestLength), 261 verifier.path()); 262 default: // client unsuitable, reject this match 263 return false; 264 } 265} 266 267 268// 269// See if we can rewrite the ACL from legacy to Code Signing form without losing too much security. 270// Returns true if the present validation should succeed (we probably rewrote the ACL). 271// Returns false if the present validation shouldn't succeed based on what we did here (we may still 272// have rewritten the ACL, in principle). 273// 274// Note that these checks add nontrivial overhead to ACL processing. We want to eventually phase 275// this out, or at least make it an option that doesn't run all the time - perhaps an "extra legacy 276// effort" per-client mode bit. 277// 278static string trim(string s, char delimiter) 279{ 280 string::size_type p = s.rfind(delimiter); 281 if (p != string::npos) 282 s = s.substr(p + 1); 283 return s; 284} 285 286static string trim(string s, char delimiter, string suffix) 287{ 288 s = trim(s, delimiter); 289 int preLength = s.length() - suffix.length(); 290 if (preLength > 0 && s.substr(preLength) == suffix) 291 s = s.substr(0, preLength); 292 return s; 293} 294 295OSStatus CodeSignatures::matchSignedClientToLegacyACL(Process &process, 296 SecCodeRef code, const OSXVerifier &verifier, const AclValidationContext &context) 297{ 298 // 299 // Check whether we seem to be matching a legacy .Mac ACL against a member of the .Mac group 300 // 301 if (SecurityServerAcl::looksLikeLegacyDotMac(context)) { 302 Server::active().longTermActivity(); 303 CFRef<SecRequirementRef> dotmac; 304 MacOSError::check(SecRequirementCreateGroup(CFSTR("dot-mac"), NULL, kSecCSDefaultFlags, &dotmac.aref())); 305 if (SecCodeCheckValidity(code, kSecCSDefaultFlags, dotmac) == noErr) { 306 secdebug("codesign", "client is a dot-mac application; update the ACL accordingly"); 307 308 // create a suitable AclSubject (this is the above-the-API-line way) 309 CFRef<CFDataRef> reqdata; 310 MacOSError::check(SecRequirementCopyData(dotmac, kSecCSDefaultFlags, &reqdata.aref())); 311 RefPointer<CodeSignatureAclSubject> subject = new CodeSignatureAclSubject(NULL, "group://dot-mac"); 312 subject->add((const BlobCore *)CFDataGetBytePtr(reqdata)); 313 314 // add it to the ACL and pass the access check (we just quite literally did it above) 315 SecurityServerAcl::addToStandardACL(context, subject); 316 return noErr; 317 } 318 } 319 320 // 321 // Get best names for the ACL (legacy) subject and the (signed) client 322 // 323 CFRef<CFDictionaryRef> info; 324 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref())); 325 CFStringRef signingIdentity = CFStringRef(CFDictionaryGetValue(info, kSecCodeInfoIdentifier)); 326 if (!signingIdentity) // unsigned 327 return errSecCSUnsigned; 328 329 string bundleName; // client 330 if (CFDictionaryRef infoList = CFDictionaryRef(CFDictionaryGetValue(info, kSecCodeInfoPList))) 331 if (CFStringRef name = CFStringRef(CFDictionaryGetValue(infoList, kCFBundleNameKey))) 332 bundleName = trim(cfString(name), '.'); 333 if (bundleName.empty()) // fall back to signing identifier 334 bundleName = trim(cfString(signingIdentity), '.'); 335 336 string aclName = trim(verifier.path(), '/', ".app"); // ACL 337 338 secdebug("codesign", "matching signed client \"%s\" against legacy ACL \"%s\"", 339 bundleName.c_str(), aclName.c_str()); 340 341 // 342 // Check whether we're matching a signed APPLE application against a legacy ACL by the same name 343 // 344 if (bundleName == aclName) { 345 const unsigned char reqData[] = { // "anchor apple", version 1 blob, embedded here 346 0xfa, 0xde, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x10, 347 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03 348 }; 349 CFRef<SecRequirementRef> apple; 350 MacOSError::check(SecRequirementCreateWithData(CFTempData(reqData, sizeof(reqData)), 351 kSecCSDefaultFlags, &apple.aref())); 352 Server::active().longTermActivity(); 353 switch (OSStatus rc = SecCodeCheckValidity(code, kSecCSDefaultFlags, apple)) { 354 case noErr: 355 { 356 secdebug("codesign", "withstands strict scrutiny; quietly adding new ACL"); 357 RefPointer<OSXCode> wrap = new OSXCodeWrap(code); 358 RefPointer<AclSubject> subject = new CodeSignatureAclSubject(OSXVerifier(wrap)); 359 SecurityServerAcl::addToStandardACL(context, subject); 360 return noErr; 361 } 362 default: 363 secdebug("codesign", "validation fails with rc=%d, rejecting", int32_t(rc)); 364 return rc; 365 } 366 secdebug("codesign", "does not withstand strict scrutiny; ask the user"); 367 QueryCodeCheck query; 368 query.inferHints(process); 369 if (!query(verifier.path().c_str())) { 370 secdebug("codesign", "user declined equivalence: cancel the access"); 371 CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED); 372 } 373 RefPointer<OSXCode> wrap = new OSXCodeWrap(code); 374 RefPointer<AclSubject> subject = new CodeSignatureAclSubject(OSXVerifier(wrap)); 375 SecurityServerAcl::addToStandardACL(context, subject); 376 return noErr; 377 } 378 379 // not close enough to even ask - this can't match 380 return errSecCSReqFailed; 381} 382 383 384// 385// Perform legacy hash verification. 386// This is the pre-Leopard (Tiger, Panther) code path. Here we only have legacy hashes 387// (called, confusingly, "signatures"), which we're matching against suitably computed 388// "signatures" (hashes) on the requesting application. We consult the CodeEquivalenceDatabase 389// in a doomed attempt to track changes made to applications through updates, and issue 390// equivalence dialogs to users if we have a name match (but hash mismatch). That's all 391// there was before Code Signing; and that's what you'll continue to get if the requesting 392// application is unsigned. Until we throw the whole mess out altogether, hopefully by 393// the Next Big Cat After Leopard. 394// 395bool CodeSignatures::verifyLegacy(Process &process, const CssmData &signature, string path) 396{ 397 // First of all, if the signature directly matches the client's code, we're obviously fine 398 // we don't even need the database for that... 399 Identity &clientIdentity = process; 400 try { 401 if (clientIdentity.getHash() == signature) { 402 secdebug("codesign", "direct match: pass"); 403 return true; 404 } 405 } catch (...) { 406 secdebug("codesign", "exception getting client code hash: fail"); 407 return false; 408 } 409 410#if CONSULT_LEGACY_CODE_EQUIVALENCE_DATABASE 411 412 // Ah well. Establish mediator objects for database signature links 413 AclIdentity aclIdentity(signature, path); 414 415 uid_t user = process.uid(); 416 { 417 StLock<Mutex> _(mDatabaseLock); 418 find(aclIdentity, user); 419 find(clientIdentity, user); 420 } 421 422 // if both links exist, we can decide this right now 423 if (aclIdentity && clientIdentity) { 424 if (aclIdentity.trustedName() == clientIdentity.trustedName()) { 425 secdebug("codesign", "app references match: pass"); 426 return true; 427 } else { 428 secdebug("codesign", "client/acl links exist but are unequal: fail"); 429 return false; 430 } 431 } 432 433 // check for name equality 434 secdebug("codesign", "matching client %s against acl %s", 435 clientIdentity.name().c_str(), aclIdentity.name().c_str()); 436 if (aclIdentity.name() != clientIdentity.name()) { 437 secdebug("codesign", "name/path mismatch: fail"); 438 return false; 439 } 440 441 // The names match - we have a possible update. 442 443 // Take the UI lock now to serialize "update rushes". 444 LongtermStLock uiLocker(mUILock); 445 446 // re-read the database in case some other thread beat us to the update 447 { 448 StLock<Mutex> _(mDatabaseLock); 449 find(aclIdentity, user); 450 find(clientIdentity, user); 451 } 452 if (aclIdentity && clientIdentity) { 453 if (aclIdentity.trustedName() == clientIdentity.trustedName()) { 454 secdebug("codesign", "app references match: pass (on the rematch)"); 455 return true; 456 } else { 457 secdebug("codesign", "client/acl links exist but are unequal: fail (on the rematch)"); 458 return false; 459 } 460 } 461 462 // ask the user 463 QueryCodeCheck query; 464 query.inferHints(process); 465 if (!query(aclIdentity.path().c_str())) 466 { 467 secdebug("codesign", "user declined equivalence: cancel the access"); 468 CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED); 469 } 470 471 // take the database lock back for real 472 StLock<Mutex> _(mDatabaseLock); 473 474 // user wants us to go ahead and establish trust (if possible) 475 if (aclIdentity) { 476 // acl is linked but new client: link the client to this application 477 makeLink(clientIdentity, aclIdentity.trustedName(), true, user); 478 mDb.flush(); 479 secdebug("codesign", "client %s linked to application %s: pass", 480 clientIdentity.path().c_str(), aclIdentity.trustedName().c_str()); 481 return true; 482 } 483 484 if (clientIdentity) { // code link exists, acl link missing 485 // client is linked but ACL (hash) never seen: link the ACL to this app 486 makeLink(aclIdentity, clientIdentity.trustedName(), true, user); 487 mDb.flush(); 488 secdebug("codesign", "acl %s linked to client %s: pass", 489 aclIdentity.path().c_str(), clientIdentity.trustedName().c_str()); 490 return true; 491 } 492 493 // the De Novo case: no links, must create everything 494 string ident = clientIdentity.name(); 495 makeLink(clientIdentity, ident, true, user); 496 makeLink(aclIdentity, ident, true, user); 497 mDb.flush(); 498 secdebug("codesign", "new linkages established: pass"); 499 return true; 500 501#else /* ignore Code Equivalence Database */ 502 503 return false; 504 505#endif 506} 507 508 509// 510// Debug dumping support 511// 512#if defined(DEBUGDUMP) 513 514void CodeSignatures::debugDump(const char *how) const 515{ 516 using namespace Debug; 517 using namespace LowLevelMemoryUtilities; 518 if (!how) 519 how = "dump"; 520 CssmData key, value; 521 if (!mDb.first(key, value)) { 522 dump("CODE EQUIVALENTS DATABASE IS EMPTY (%s)\n", how); 523 } else { 524 dump("CODE EQUIVALENTS DATABASE DUMP (%s)\n", how); 525 do { 526 const char *header = key.interpretedAs<const char>(); 527 size_t headerLength = strlen(header) + 1; 528 dump("%s:", header); 529 dumpData(key.at(headerLength), key.length() - headerLength); 530 dump(" => "); 531 dumpData(value); 532 dump("\n"); 533 } while (mDb.next(key, value)); 534 dump("END DUMP\n"); 535 } 536} 537 538void CodeSignatures::Identity::debugDump(const char *how) const 539{ 540 using namespace Debug; 541 if (!how) 542 how = "dump"; 543 dump("IDENTITY (%s) path=%s", how, getPath().c_str()); 544 dump(" name=%s hash=", mName.empty() ? "(unset)" : mName.c_str()); 545 dumpData(getHash()); 546 dump("\n"); 547} 548 549#endif //DEBUGDUMP 550