csutilities.cpp
1 /* 2 * Copyright (c) 2006-2013 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 // csutilities - miscellaneous utilities for the code signing implementation 26 // 27 28 #include "csutilities.h" 29 #include <libDER/DER_Encode.h> 30 #include <libDER/DER_Keys.h> 31 #include <libDER/asn1Types.h> 32 #include <libDER/oids.h> 33 #include <security_asn1/SecAsn1Coder.h> 34 #include <security_asn1/SecAsn1Templates.h> 35 #include <Security/SecCertificatePriv.h> 36 #include <Security/SecCertificate.h> 37 #include <Security/SecPolicyPriv.h> 38 #include <utilities/SecAppleAnchorPriv.h> 39 #include <utilities/SecInternalReleasePriv.h> 40 #include "requirement.h" 41 #include <security_utilities/hashing.h> 42 #include <security_utilities/debugging.h> 43 #include <security_utilities/errors.h> 44 #include <sys/mount.h> 45 #include <sys/utsname.h> 46 #include <errno.h> 47 #include <sys/attr.h> 48 #include <sys/xattr.h> 49 #include <libgen.h> 50 #include "debugging.h" 51 52 extern "C" { 53 54 /* Decode a choice of UTCTime or GeneralizedTime to a CFAbsoluteTime. Return 55 an absoluteTime if the date was valid and properly decoded. Return 56 NULL_TIME otherwise. */ 57 CFAbsoluteTime SecAbsoluteTimeFromDateContent(DERTag tag, const uint8_t *bytes, 58 size_t length); 59 60 } 61 62 namespace Security { 63 namespace CodeSigning { 64 65 66 // 67 // Test for the canonical Apple CA certificate 68 // 69 bool isAppleCA(SecCertificateRef cert) 70 { 71 SecAppleTrustAnchorFlags flags = 0; 72 if (SecIsInternalRelease()) 73 flags |= kSecAppleTrustAnchorFlagsIncludeTestAnchors; 74 return SecIsAppleTrustAnchor(cert, flags); 75 } 76 77 78 // 79 // Calculate the canonical hash of a certificate, given its raw (DER) data. 80 // 81 void hashOfCertificate(const void *certData, size_t certLength, SHA1::Digest digest) 82 { 83 SHA1 hasher; 84 hasher(certData, certLength); 85 hasher.finish(digest); 86 } 87 88 89 // 90 // Ditto, given a SecCertificateRef 91 // 92 void hashOfCertificate(SecCertificateRef cert, SHA1::Digest digest) 93 { 94 assert(cert); 95 hashOfCertificate(SecCertificateGetBytePtr(cert), SecCertificateGetLength(cert), digest); 96 } 97 98 99 // 100 // One-stop hash-certificate-and-compare 101 // 102 bool verifyHash(SecCertificateRef cert, const Hashing::Byte *digest) 103 { 104 SHA1::Digest dig; 105 hashOfCertificate(cert, dig); 106 return !memcmp(dig, digest, SHA1::digestLength); 107 } 108 109 #if TARGET_OS_OSX 110 // 111 // Check to see if a certificate contains a particular field, by OID. This works for extensions, 112 // even ones not recognized by the local CL. It does not return any value, only presence. 113 // 114 bool certificateHasField(SecCertificateRef cert, const CSSM_OID &oid) 115 { 116 CFDataRef oidData = NULL; 117 CFDataRef data = NULL; 118 bool isCritical = false; 119 bool matched = false; 120 121 oidData = CFDataCreateWithBytesNoCopy(NULL, oid.Data, oid.Length, 122 kCFAllocatorNull); 123 if (!(cert && oidData)) { 124 goto out; 125 } 126 data = SecCertificateCopyExtensionValue(cert, oidData, &isCritical); 127 if (data == NULL) { 128 goto out; 129 } 130 matched = true; 131 out: 132 if (data) { 133 CFRelease(data); 134 } 135 if (oidData) { 136 CFRelease(oidData); 137 } 138 return matched; 139 } 140 141 142 // 143 // Retrieve X.509 policy extension OIDs, if any. 144 // This currently ignores policy qualifiers. 145 // 146 bool certificateHasPolicy(SecCertificateRef cert, const CSSM_OID &policyOid) 147 { 148 bool matched = false; 149 CFDataRef oidData = CFDataCreateWithBytesNoCopy(NULL, policyOid.Data, policyOid.Length, 150 kCFAllocatorNull); 151 if (!(cert && oidData)) { 152 goto out; 153 } 154 matched = SecPolicyCheckCertCertificatePolicy(cert, oidData); 155 out: 156 if (oidData) { 157 CFRelease(oidData); 158 } 159 return matched; 160 } 161 162 163 CFDateRef certificateCopyFieldDate(SecCertificateRef cert, const CSSM_OID &policyOid) 164 { 165 CFDataRef oidData = NULL; 166 CFDateRef value = NULL; 167 CFDataRef data = NULL; 168 SecAsn1CoderRef coder = NULL; 169 CSSM_DATA str = { 0 }; 170 CFAbsoluteTime time = 0.0; 171 OSStatus status = 0; 172 bool isCritical; 173 174 oidData = CFDataCreateWithBytesNoCopy(NULL, policyOid.Data, policyOid.Length, 175 kCFAllocatorNull); 176 177 if (oidData == NULL) { 178 goto out; 179 } 180 181 data = SecCertificateCopyExtensionValue(cert, oidData, &isCritical); 182 183 if (data == NULL) { 184 goto out; 185 } 186 187 status = SecAsn1CoderCreate(&coder); 188 if (status != 0) { 189 goto out; 190 } 191 192 // We currently only support UTF8 strings. 193 status = SecAsn1Decode(coder, CFDataGetBytePtr(data), CFDataGetLength(data), 194 kSecAsn1UTF8StringTemplate, &str); 195 if (status != 0) { 196 goto out; 197 } 198 199 time = SecAbsoluteTimeFromDateContent(ASN1_GENERALIZED_TIME, 200 str.Data, str.Length); 201 202 if (time == 0.0) { 203 goto out; 204 } 205 206 value = CFDateCreate(NULL, time); 207 out: 208 if (coder) { 209 SecAsn1CoderRelease(coder); 210 } 211 if (data) { 212 CFRelease(data); 213 } 214 if (oidData) { 215 CFRelease(oidData); 216 } 217 218 return value; 219 } 220 #endif 221 222 // 223 // Copyfile 224 // 225 Copyfile::Copyfile() 226 { 227 if (!(mState = copyfile_state_alloc())) 228 UnixError::throwMe(); 229 } 230 231 void Copyfile::set(uint32_t flag, const void *value) 232 { 233 check(::copyfile_state_set(mState, flag, value)); 234 } 235 236 void Copyfile::get(uint32_t flag, void *value) 237 { 238 check(::copyfile_state_set(mState, flag, value)); 239 } 240 241 void Copyfile::operator () (const char *src, const char *dst, copyfile_flags_t flags) 242 { 243 check(::copyfile(src, dst, mState, flags)); 244 } 245 246 void Copyfile::check(int rc) 247 { 248 if (rc < 0) 249 UnixError::throwMe(); 250 } 251 252 253 // 254 // MessageTracer support 255 // 256 MessageTrace::MessageTrace(const char *domain, const char *signature) 257 { 258 mAsl = asl_new(ASL_TYPE_MSG); 259 if (domain) 260 asl_set(mAsl, "com.apple.message.domain", domain); 261 if (signature) 262 asl_set(mAsl, "com.apple.message.signature", signature); 263 } 264 265 void MessageTrace::add(const char *key, const char *format, ...) 266 { 267 va_list args; 268 va_start(args, format); 269 char value[200]; 270 vsnprintf(value, sizeof(value), format, args); 271 va_end(args); 272 asl_set(mAsl, (string("com.apple.message.") + key).c_str(), value); 273 } 274 275 void MessageTrace::send(const char *format, ...) 276 { 277 va_list args; 278 va_start(args, format); 279 asl_vlog(NULL, mAsl, ASL_LEVEL_NOTICE, format, args); 280 va_end(args); 281 } 282 283 284 285 // Resource limited async workers for doing work on nested bundles 286 LimitedAsync::LimitedAsync(bool async) 287 { 288 // validate multiple resources concurrently if bundle resides on solid-state media 289 290 // How many async workers to spin off. If zero, validating only happens synchronously. 291 long async_workers = 0; 292 293 long ncpu = sysconf(_SC_NPROCESSORS_ONLN); 294 295 if (async && ncpu > 0) 296 async_workers = ncpu - 1; // one less because this thread also validates 297 298 mResourceSemaphore = new Dispatch::Semaphore(async_workers); 299 } 300 301 LimitedAsync::LimitedAsync(LimitedAsync &limitedAsync) 302 { 303 mResourceSemaphore = new Dispatch::Semaphore(*limitedAsync.mResourceSemaphore); 304 } 305 306 LimitedAsync::~LimitedAsync() 307 { 308 delete mResourceSemaphore; 309 } 310 311 bool LimitedAsync::perform(Dispatch::Group &groupRef, void (^block)()) { 312 __block Dispatch::SemaphoreWait wait(*mResourceSemaphore, DISPATCH_TIME_NOW); 313 314 if (wait.acquired()) { 315 dispatch_queue_t defaultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 316 317 groupRef.enqueue(defaultQueue, ^{ 318 // Hold the semaphore count until the worker is done validating. 319 Dispatch::SemaphoreWait innerWait(wait); 320 block(); 321 }); 322 return true; 323 } else { 324 block(); 325 return false; 326 } 327 } 328 329 bool isOnRootFilesystem(const char *path) 330 { 331 int rc = 0; 332 struct statfs sfb; 333 334 rc = statfs(path, &sfb); 335 if (rc != 0) { 336 secerror("Unable to check if path is on rootfs: %d, %s", errno, path); 337 return false; 338 } 339 return ((sfb.f_flags & MNT_ROOTFS) == MNT_ROOTFS); 340 } 341 342 bool pathExists(const char *path) 343 { 344 int rc; 345 346 if (!path) { 347 secerror("path is NULL"); 348 return false; 349 } 350 351 rc = access(path, F_OK); 352 if (rc != 0) { 353 if (errno != ENOENT) { 354 secerror("Unable to check if path exists: %d, %s", errno, path); 355 } 356 return false; 357 } 358 359 return true; 360 } 361 362 bool pathMatchesXattrFilenameSpec(const char *path) 363 { 364 char *baseName = NULL; 365 bool ret = false; 366 367 if (!path) { 368 secerror("path is NULL"); 369 goto done; 370 } 371 372 // Extra byte for NULL storage. 373 baseName = (char *)malloc(strlen(path) + 1); 374 if (!baseName) { 375 secerror("Unable to allocate space for storing basename: %d [%s]", errno, strerror(errno)); 376 goto done; 377 } 378 379 // basename_r will return a "/" if path is only slashes. It will return 380 // a "." for a NULL/empty path. Both of these cases are handled by the logic 381 // later. The only situation where basename_r will return a NULL is when path 382 // is longer than MAXPATHLEN. 383 384 if (basename_r(path, baseName) == NULL) { 385 secerror("Could not get basename of %s: %d [%s]", path, errno, strerror(errno)); 386 goto done; 387 } 388 389 // The file name must start with "._", followed by the name 390 // of the file for which it stores the xattrs. Hence, its length 391 // must be at least three --> 2 for "._" and 1 for a non-empty file 392 // name. 393 if (strlen(baseName) < 3) { 394 goto done; 395 } 396 397 if (baseName[0] != '.' || baseName[1] != '_') { 398 goto done; 399 } 400 401 ret = true; 402 403 done: 404 if (baseName) { 405 free(baseName); 406 } 407 408 return ret; 409 } 410 411 bool pathIsRegularFile(const char *path) 412 { 413 if (!path) { 414 secerror("path is NULL"); 415 return false; 416 } 417 418 struct stat sb; 419 if (stat(path, &sb)) { 420 secerror("Unable to stat %s: %d [%s]", path, errno, strerror(errno)); 421 return false; 422 } 423 424 return (sb.st_mode & S_IFREG) == S_IFREG; 425 } 426 427 bool pathHasXattrs(const char *path) 428 { 429 if (!path) { 430 secerror("path is NULL"); 431 return false; 432 } 433 434 ssize_t xattrSize = listxattr(path, NULL, 0, 0); 435 if (xattrSize == -1) { 436 secerror("Unable to acquire the xattr list from %s", path); 437 return false; 438 } 439 440 return (xattrSize > 0); 441 } 442 443 bool pathFileSystemUsesXattrFiles(const char *path) 444 { 445 struct _VolumeCapabilitiesWrapped { 446 uint32_t length; 447 vol_capabilities_attr_t volume_capabilities; 448 } __attribute__((aligned(4), packed)); 449 450 struct attrlist attr_list; 451 struct _VolumeCapabilitiesWrapped volume_cap_wrapped; 452 struct statfs sfb; 453 454 if (!path) { 455 secerror("path is NULL"); 456 return false; 457 } 458 459 int ret = statfs(path, &sfb); 460 if (ret != 0) { 461 secerror("Unable to convert %s to its filesystem mount [statfs failed]: %d [%s]", path, errno, strerror(errno)); 462 return false; 463 } 464 path = sfb.f_mntonname; 465 466 memset(&attr_list, 0, sizeof(attr_list)); 467 attr_list.bitmapcount = ATTR_BIT_MAP_COUNT; 468 attr_list.volattr = ATTR_VOL_INFO | ATTR_VOL_CAPABILITIES; 469 470 ret = getattrlist(path, &attr_list, &volume_cap_wrapped, sizeof(volume_cap_wrapped), 0); 471 if (ret) { 472 secerror("Unable to get volume capabilities from %s: %d [%s]", path, errno, strerror(errno)); 473 return false; 474 } 475 476 if (volume_cap_wrapped.length != sizeof(volume_cap_wrapped)) { 477 secerror("getattrlist return length incorrect, expected %lu, got %u", sizeof(volume_cap_wrapped), volume_cap_wrapped.length); 478 return false; 479 } 480 481 // The valid bit tells us whether the corresponding bit in capabilities is valid 482 // or not. For file systems where the valid bit isn't set, we can safely assume that 483 // extended attributes aren't supported natively. 484 485 bool xattr_valid = (volume_cap_wrapped.volume_capabilities.valid[VOL_CAPABILITIES_INTERFACES] & VOL_CAP_INT_EXTENDED_ATTR) == VOL_CAP_INT_EXTENDED_ATTR; 486 if (!xattr_valid) { 487 return true; 488 } 489 490 bool xattr_capability = (volume_cap_wrapped.volume_capabilities.capabilities[VOL_CAPABILITIES_INTERFACES] & VOL_CAP_INT_EXTENDED_ATTR) == VOL_CAP_INT_EXTENDED_ATTR; 491 if (!xattr_capability) { 492 return true; 493 } 494 495 return false; 496 } 497 498 bool pathIsValidXattrFile(const string fullPath, const char *scope) 499 { 500 // Confirm that fullPath begins from root. 501 if (fullPath[0] != '/') { 502 secinfo(scope, "%s isn't a full path, but a relative path", fullPath.c_str()); 503 return false; 504 } 505 506 // Confirm that fullPath is a regular file. 507 if (!pathIsRegularFile(fullPath.c_str())) { 508 secinfo(scope, "%s isn't a regular file", fullPath.c_str()); 509 return false; 510 } 511 512 // Check that the file name matches the Xattr file spec. 513 if (!pathMatchesXattrFilenameSpec(fullPath.c_str())) { 514 secinfo(scope, "%s doesn't match Xattr file path spec", fullPath.c_str()); 515 return false; 516 } 517 518 // We are guaranteed to have at least one "/" by virtue of fullPath 519 // being a path from the root of the filesystem hierarchy. 520 // 521 // We construct the real file name by copying everything up to 522 // the last "/", adding the "/" back in, then skipping 523 // over the backslash (+1) and the "._" (+2) in the rest of the 524 // string. 525 526 size_t lastBackSlash = fullPath.find_last_of("/"); 527 const string realFilePath = fullPath.substr(0, lastBackSlash) + "/" + fullPath.substr(lastBackSlash + 1 + 2); 528 529 if (!pathExists(realFilePath.c_str())) { 530 secinfo(scope, "%s does not exist, forcing resource validation on %s", realFilePath.c_str(), fullPath.c_str()); 531 return false; 532 } 533 534 // Lastly, we need to confirm that the real file contains some xattrs. If not, 535 // then the file represented by fullPath isn't an xattr file. 536 if (!pathHasXattrs(realFilePath.c_str())) { 537 secinfo(scope, "%s does not contain xattrs, forcing resource validation on %s", realFilePath.c_str(), fullPath.c_str()); 538 return false; 539 } 540 541 return true; 542 } 543 544 string pathRemaining(string fullPath, string prefix) 545 { 546 if ((fullPath.length() < prefix.length()) || 547 (prefix.length() == 0) || 548 (fullPath.length() == 0) || 549 !isPathPrefix(prefix, fullPath)) { 550 return ""; 551 } 552 553 size_t currentPosition = prefix.length(); 554 if (prefix[currentPosition-1] != '/') { 555 // If the prefix doesn't already end with a /, add one to the position so the remaining 556 // doesn't start with one. 557 currentPosition += 1; 558 } 559 560 // Ensure we're not indexing outside the bounds of fullPath. 561 if (currentPosition >= fullPath.length()) { 562 return ""; 563 } 564 565 return fullPath.substr(currentPosition, string::npos); 566 } 567 568 bool isPathPrefix(string prefixPath, string fullPath) 569 { 570 size_t pos = fullPath.find(prefixPath); 571 if (pos == 0) { 572 // If they're a perfect match, its not really a path prefix. 573 if (prefixPath.length() == fullPath.length()) { 574 return false; 575 } 576 577 // Ensure the prefix starts a relative path under the prefix. 578 if (prefixPath.back() == '/') { 579 // If the prefix ends with a delimeter, we're good. 580 return true; 581 } else { 582 // Otherwise, the next character in the fullPath needs to be a delimeter. 583 return fullPath.at(prefixPath.length()) == '/'; 584 } 585 } 586 return false; 587 } 588 589 bool iterateLargestSubpaths(string path, bool (^pathHandler)(string)) 590 { 591 size_t lastPossibleSlash = path.length(); 592 size_t currentPosition = 0; 593 bool stopped = false; 594 595 while (!stopped) { 596 currentPosition = path.find_last_of("/", lastPossibleSlash); 597 if (currentPosition == string::npos || currentPosition == 0) { 598 break; 599 } 600 601 // Erase from the current position to the end of the string. 602 path.erase(currentPosition, string::npos); 603 stopped = pathHandler(path); 604 if (!stopped) { 605 if (currentPosition == 0) { 606 break; 607 } 608 lastPossibleSlash = currentPosition - 1; 609 } 610 } 611 return stopped; 612 } 613 614 615 } // end namespace CodeSigning 616 } // end namespace Security