]> git.saurik.com Git - apple/security.git/blob - OSX/libsecurity_codesigning/lib/StaticCode.cpp
Security-58286.70.7.tar.gz
[apple/security.git] / OSX / libsecurity_codesigning / lib / StaticCode.cpp
1 /*
2 * Copyright (c) 2006-2015 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 // StaticCode - SecStaticCode API objects
26 //
27 #include "StaticCode.h"
28 #include "Code.h"
29 #include "reqmaker.h"
30 #if TARGET_OS_OSX
31 #include "drmaker.h"
32 #endif
33 #include "reqdumper.h"
34 #include "reqparser.h"
35 #include "sigblob.h"
36 #include "resources.h"
37 #include "detachedrep.h"
38 #include "signerutils.h"
39 #if TARGET_OS_OSX
40 #include "csdatabase.h"
41 #endif
42 #include "dirscanner.h"
43 #include <CoreFoundation/CFURLAccess.h>
44 #include <Security/SecPolicyPriv.h>
45 #include <Security/SecTrustPriv.h>
46 #include <Security/SecCertificatePriv.h>
47 #if TARGET_OS_OSX
48 #include <Security/CMSPrivate.h>
49 #endif
50 #import <Security/SecCMS.h>
51 #include <Security/SecCmsContentInfo.h>
52 #include <Security/SecCmsSignerInfo.h>
53 #include <Security/SecCmsSignedData.h>
54 #if TARGET_OS_OSX
55 #include <Security/cssmapplePriv.h>
56 #endif
57 #include <security_utilities/unix++.h>
58 #include <security_utilities/cfmunge.h>
59 #include <security_utilities/casts.h>
60 #include <Security/CMSDecoder.h>
61 #include <security_utilities/logging.h>
62 #include <dirent.h>
63 #include <sys/xattr.h>
64 #include <sstream>
65 #include <IOKit/storage/IOStorageDeviceCharacteristics.h>
66 #include <dispatch/private.h>
67 #include <os/assumes.h>
68 #include <regex.h>
69
70
71 namespace Security {
72 namespace CodeSigning {
73
74 using namespace UnixPlusPlus;
75
76 // A requirement representing a Mac or iOS dev cert, a Mac or iOS distribution cert, or a developer ID
77 static const char WWDRRequirement[] = "anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.2] exists";
78 static const char MACWWDRRequirement[] = "anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.12] exists";
79 static const char developerID[] = "anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] exists"
80 " and certificate leaf[field.1.2.840.113635.100.6.1.13] exists";
81 static const char distributionCertificate[] = "anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.7] exists";
82 static const char iPhoneDistributionCert[] = "anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.4] exists";
83
84 //
85 // Map a component slot number to a suitable error code for a failure
86 //
87 static inline OSStatus errorForSlot(CodeDirectory::SpecialSlot slot)
88 {
89 switch (slot) {
90 case cdInfoSlot:
91 return errSecCSInfoPlistFailed;
92 case cdResourceDirSlot:
93 return errSecCSResourceDirectoryFailed;
94 default:
95 return errSecCSSignatureFailed;
96 }
97 }
98
99
100 //
101 // Construct a SecStaticCode object given a disk representation object
102 //
103 SecStaticCode::SecStaticCode(DiskRep *rep)
104 : mCheckfix30814861builder1(NULL),
105 mRep(rep),
106 mValidated(false), mExecutableValidated(false), mResourcesValidated(false), mResourcesValidContext(NULL),
107 mProgressQueue("com.apple.security.validation-progress", false, QOS_CLASS_UNSPECIFIED),
108 mOuterScope(NULL), mResourceScope(NULL),
109 mDesignatedReq(NULL), mGotResourceBase(false), mMonitor(NULL), mLimitedAsync(NULL)
110 #if TARGET_OS_OSX
111 , mEvalDetails(NULL)
112 #else
113 , mTrustedSigningCertChain(false)
114 #endif
115
116 {
117 CODESIGN_STATIC_CREATE(this, rep);
118 #if TARGET_OS_OSX
119 checkForSystemSignature();
120 #endif
121 }
122
123
124 //
125 // Clean up a SecStaticCode object
126 //
127 SecStaticCode::~SecStaticCode() throw()
128 try {
129 ::free(const_cast<Requirement *>(mDesignatedReq));
130 delete mResourcesValidContext;
131 delete mLimitedAsync;
132 delete mCheckfix30814861builder1;
133 } catch (...) {
134 return;
135 }
136
137 //
138 // Initialize a nested SecStaticCode object from its parent
139 //
140 void SecStaticCode::initializeFromParent(const SecStaticCode& parent) {
141 mOuterScope = &parent;
142 setMonitor(parent.monitor());
143 if (parent.mLimitedAsync)
144 mLimitedAsync = new LimitedAsync(*parent.mLimitedAsync);
145 }
146
147 //
148 // CF-level comparison of SecStaticCode objects compares CodeDirectory hashes if signed,
149 // and falls back on comparing canonical paths if (both are) not.
150 //
151 bool SecStaticCode::equal(SecCFObject &secOther)
152 {
153 SecStaticCode *other = static_cast<SecStaticCode *>(&secOther);
154 CFDataRef mine = this->cdHash();
155 CFDataRef his = other->cdHash();
156 if (mine || his)
157 return mine && his && CFEqual(mine, his);
158 else
159 return CFEqual(CFRef<CFURLRef>(this->copyCanonicalPath()), CFRef<CFURLRef>(other->copyCanonicalPath()));
160 }
161
162 CFHashCode SecStaticCode::hash()
163 {
164 if (CFDataRef h = this->cdHash())
165 return CFHash(h);
166 else
167 return CFHash(CFRef<CFURLRef>(this->copyCanonicalPath()));
168 }
169
170
171 //
172 // Invoke a stage monitor if registered
173 //
174 CFTypeRef SecStaticCode::reportEvent(CFStringRef stage, CFDictionaryRef info)
175 {
176 if (mMonitor)
177 return mMonitor(this->handle(false), stage, info);
178 else
179 return NULL;
180 }
181
182 void SecStaticCode::prepareProgress(unsigned int workload)
183 {
184 dispatch_sync(mProgressQueue, ^{
185 mCancelPending = false; // not canceled
186 });
187 if (mValidationFlags & kSecCSReportProgress) {
188 mCurrentWork = 0; // nothing done yet
189 mTotalWork = workload; // totally fake - we don't know how many files we'll get to chew
190 }
191 }
192
193 void SecStaticCode::reportProgress(unsigned amount /* = 1 */)
194 {
195 if (mMonitor && (mValidationFlags & kSecCSReportProgress)) {
196 // update progress and report
197 __block bool cancel = false;
198 dispatch_sync(mProgressQueue, ^{
199 if (mCancelPending)
200 cancel = true;
201 mCurrentWork += amount;
202 mMonitor(this->handle(false), CFSTR("progress"), CFTemp<CFDictionaryRef>("{current=%d,total=%d}", mCurrentWork, mTotalWork));
203 });
204 // if cancellation is pending, abort now
205 if (cancel)
206 MacOSError::throwMe(errSecCSCancelled);
207 }
208 }
209
210
211 //
212 // Set validation conditions for fine-tuning legacy tolerance
213 //
214 static void addError(CFTypeRef cfError, void* context)
215 {
216 if (CFGetTypeID(cfError) == CFNumberGetTypeID()) {
217 int64_t error;
218 CFNumberGetValue(CFNumberRef(cfError), kCFNumberSInt64Type, (void*)&error);
219 MacOSErrorSet* errors = (MacOSErrorSet*)context;
220 errors->insert(OSStatus(error));
221 }
222 }
223
224 void SecStaticCode::setValidationModifiers(CFDictionaryRef conditions)
225 {
226 if (conditions) {
227 CFDictionary source(conditions, errSecCSDbCorrupt);
228 mAllowOmissions = source.get<CFArrayRef>("omissions");
229 if (CFArrayRef errors = source.get<CFArrayRef>("errors"))
230 CFArrayApplyFunction(errors, CFRangeMake(0, CFArrayGetCount(errors)), addError, &this->mTolerateErrors);
231 }
232 }
233
234
235 //
236 // Request cancellation of a validation in progress.
237 // We do this by posting an abort flag that is checked periodically.
238 //
239 void SecStaticCode::cancelValidation()
240 {
241 if (!(mValidationFlags & kSecCSReportProgress)) // not using progress reporting; cancel won't make it through
242 MacOSError::throwMe(errSecCSInvalidFlags);
243 dispatch_assert_queue(mProgressQueue);
244 mCancelPending = true;
245 }
246
247
248 //
249 // Attach a detached signature.
250 //
251 void SecStaticCode::detachedSignature(CFDataRef sigData)
252 {
253 if (sigData) {
254 mDetachedSig = sigData;
255 mRep = new DetachedRep(sigData, mRep->base(), "explicit detached");
256 CODESIGN_STATIC_ATTACH_EXPLICIT(this, mRep);
257 } else {
258 mDetachedSig = NULL;
259 mRep = mRep->base();
260 CODESIGN_STATIC_ATTACH_EXPLICIT(this, NULL);
261 }
262 }
263
264
265 //
266 // Consult the system detached signature database to see if it contains
267 // a detached signature for this StaticCode. If it does, fetch and attach it.
268 // We do this only if the code has no signature already attached.
269 //
270 void SecStaticCode::checkForSystemSignature()
271 {
272 #if TARGET_OS_OSX
273 if (!this->isSigned()) {
274 SignatureDatabase db;
275 if (db.isOpen())
276 try {
277 if (RefPointer<DiskRep> dsig = db.findCode(mRep)) {
278 CODESIGN_STATIC_ATTACH_SYSTEM(this, dsig);
279 mRep = dsig;
280 }
281 } catch (...) {
282 }
283 }
284 #else
285 MacOSError::throwMe(errSecUnimplemented);
286 #endif
287 }
288
289
290 //
291 // Return a descriptive string identifying the source of the code signature
292 //
293 string SecStaticCode::signatureSource()
294 {
295 if (!isSigned())
296 return "unsigned";
297 if (DetachedRep *rep = dynamic_cast<DetachedRep *>(mRep.get()))
298 return rep->source();
299 return "embedded";
300 }
301
302
303 //
304 // Do ::required, but convert incoming SecCodeRefs to their SecStaticCodeRefs
305 // (if possible).
306 //
307 SecStaticCode *SecStaticCode::requiredStatic(SecStaticCodeRef ref)
308 {
309 SecCFObject *object = SecCFObject::required(ref, errSecCSInvalidObjectRef);
310 if (SecStaticCode *scode = dynamic_cast<SecStaticCode *>(object))
311 return scode;
312 else if (SecCode *code = dynamic_cast<SecCode *>(object))
313 return code->staticCode();
314 else // neither (a SecSomethingElse)
315 MacOSError::throwMe(errSecCSInvalidObjectRef);
316 }
317
318 SecCode *SecStaticCode::optionalDynamic(SecStaticCodeRef ref)
319 {
320 SecCFObject *object = SecCFObject::required(ref, errSecCSInvalidObjectRef);
321 if (dynamic_cast<SecStaticCode *>(object))
322 return NULL;
323 else if (SecCode *code = dynamic_cast<SecCode *>(object))
324 return code;
325 else // neither (a SecSomethingElse)
326 MacOSError::throwMe(errSecCSInvalidObjectRef);
327 }
328
329
330 //
331 // Void all cached validity data.
332 //
333 // We also throw out cached components, because the new signature data may have
334 // a different idea of what components should be present. We could reconcile the
335 // cached data instead, if performance seems to be impacted.
336 //
337 void SecStaticCode::resetValidity()
338 {
339 CODESIGN_EVAL_STATIC_RESET(this);
340 mValidated = false;
341 mExecutableValidated = mResourcesValidated = false;
342 if (mResourcesValidContext) {
343 delete mResourcesValidContext;
344 mResourcesValidContext = NULL;
345 }
346 mDir = NULL;
347 mCodeDirectories.clear();
348 mSignature = NULL;
349 for (unsigned n = 0; n < cdSlotCount; n++)
350 mCache[n] = NULL;
351 mInfoDict = NULL;
352 mEntitlements = NULL;
353 mResourceDict = NULL;
354 mDesignatedReq = NULL;
355 mCDHash = NULL;
356 mGotResourceBase = false;
357 mTrust = NULL;
358 mCertChain = NULL;
359 #if TARGET_OS_OSX
360 mEvalDetails = NULL;
361 #endif
362 mRep->flush();
363
364 #if TARGET_OS_OSX
365 // we may just have updated the system database, so check again
366 checkForSystemSignature();
367 #endif
368 }
369
370
371 //
372 // Retrieve a sealed component by special slot index.
373 // If the CodeDirectory has already been validated, validate against that.
374 // Otherwise, retrieve the component without validation (but cache it). Validation
375 // will go through the cache and validate all cached components.
376 //
377 CFDataRef SecStaticCode::component(CodeDirectory::SpecialSlot slot, OSStatus fail /* = errSecCSSignatureFailed */)
378 {
379 assert(slot <= cdSlotMax);
380
381 CFRef<CFDataRef> &cache = mCache[slot];
382 if (!cache) {
383 if (CFRef<CFDataRef> data = mRep->component(slot)) {
384 if (validated()) { // if the directory has been validated...
385 if (!codeDirectory()->slotIsPresent(-slot))
386 return NULL;
387
388 if (!codeDirectory()->validateSlot(CFDataGetBytePtr(data), // ... and it's no good
389 CFDataGetLength(data), -slot, false))
390 MacOSError::throwMe(errorForSlot(slot)); // ... then bail
391 }
392 cache = data; // it's okay, cache it
393 } else { // absent, mark so
394 if (validated()) // if directory has been validated...
395 if (codeDirectory()->slotIsPresent(-slot)) // ... and the slot is NOT missing
396 MacOSError::throwMe(errorForSlot(slot)); // was supposed to be there
397 cache = CFDataRef(kCFNull); // white lie
398 }
399 }
400 return (cache == CFDataRef(kCFNull)) ? NULL : cache.get();
401 }
402
403
404 //
405 // Get the CodeDirectories.
406 // Throws (if check==true) or returns NULL (check==false) if there are none.
407 // Always throws if the CodeDirectories exist but are invalid.
408 // NEVER validates against the signature.
409 //
410 const SecStaticCode::CodeDirectoryMap *
411 SecStaticCode::codeDirectories(bool check /* = true */) const
412 {
413 if (mCodeDirectories.empty()) {
414 try {
415 loadCodeDirectories(mCodeDirectories);
416 } catch (...) {
417 if (check)
418 throw;
419 // We wanted a NON-checked peek and failed to safely decode the existing CodeDirectories.
420 // Pretend this is unsigned, but make sure we didn't somehow cache an invalid CodeDirectory.
421 if (!mCodeDirectories.empty()) {
422 assert(false);
423 Syslog::warning("code signing internal problem: mCodeDirectories set despite exception exit");
424 MacOSError::throwMe(errSecCSInternalError);
425 }
426 }
427 } else {
428 return &mCodeDirectories;
429 }
430 if (!mCodeDirectories.empty()) {
431 return &mCodeDirectories;
432 }
433 if (check) {
434 MacOSError::throwMe(errSecCSUnsigned);
435 }
436 return NULL;
437 }
438
439 //
440 // Get the CodeDirectory.
441 // Throws (if check==true) or returns NULL (check==false) if there is none.
442 // Always throws if the CodeDirectory exists but is invalid.
443 // NEVER validates against the signature.
444 //
445 const CodeDirectory *SecStaticCode::codeDirectory(bool check /* = true */) const
446 {
447 if (!mDir) {
448 // pick our favorite CodeDirectory from the choices we've got
449 try {
450 CodeDirectoryMap const *candidates = codeDirectories(check);
451 if (candidates != NULL) {
452 CodeDirectory::HashAlgorithm type = CodeDirectory::bestHashOf(mHashAlgorithms);
453 mDir = candidates->at(type); // and the winner is...
454 }
455 } catch (...) {
456 if (check)
457 throw;
458 // We wanted a NON-checked peek and failed to safely decode the existing CodeDirectory.
459 // Pretend this is unsigned, but make sure we didn't somehow cache an invalid CodeDirectory.
460 if (mDir) {
461 assert(false);
462 Syslog::warning("code signing internal problem: mDir set despite exception exit");
463 MacOSError::throwMe(errSecCSInternalError);
464 }
465 }
466 }
467 if (mDir)
468 return reinterpret_cast<const CodeDirectory *>(CFDataGetBytePtr(mDir));
469 if (check)
470 MacOSError::throwMe(errSecCSUnsigned);
471 return NULL;
472 }
473
474
475 //
476 // Fetch an array of all available CodeDirectories.
477 // Returns false if unsigned (no classic CD slot), true otherwise.
478 //
479 bool SecStaticCode::loadCodeDirectories(CodeDirectoryMap& cdMap) const
480 {
481 __block CodeDirectoryMap candidates;
482 __block CodeDirectory::HashAlgorithms hashAlgorithms;
483 __block CFRef<CFDataRef> baseDir;
484 auto add = ^bool (CodeDirectory::SpecialSlot slot){
485 CFRef<CFDataRef> cdData = diskRep()->component(slot);
486 if (!cdData)
487 return false;
488 const CodeDirectory* cd = reinterpret_cast<const CodeDirectory*>(CFDataGetBytePtr(cdData));
489 if (!cd->validateBlob(CFDataGetLength(cdData)))
490 MacOSError::throwMe(errSecCSSignatureFailed); // no recovery - any suspect CD fails
491 cd->checkIntegrity();
492 auto result = candidates.insert(make_pair(cd->hashType, cdData.get()));
493 if (!result.second)
494 MacOSError::throwMe(errSecCSSignatureInvalid); // duplicate hashType, go to heck
495 hashAlgorithms.insert(cd->hashType);
496 if (slot == cdCodeDirectorySlot)
497 baseDir = cdData;
498 return true;
499 };
500 if (!add(cdCodeDirectorySlot))
501 return false; // no classic slot CodeDirectory -> unsigned
502 for (CodeDirectory::SpecialSlot slot = cdAlternateCodeDirectorySlots; slot < cdAlternateCodeDirectoryLimit; slot++)
503 if (!add(slot)) // no CodeDirectory at this slot -> end of alternates
504 break;
505 if (candidates.empty())
506 MacOSError::throwMe(errSecCSSignatureFailed); // no viable CodeDirectory in sight
507 // commit to cached values
508 cdMap.swap(candidates);
509 mHashAlgorithms.swap(hashAlgorithms);
510 mBaseDir = baseDir;
511 return true;
512 }
513
514
515 //
516 // Get the hash of the CodeDirectory.
517 // Returns NULL if there is none.
518 //
519 CFDataRef SecStaticCode::cdHash()
520 {
521 if (!mCDHash) {
522 if (const CodeDirectory *cd = codeDirectory(false)) {
523 mCDHash.take(cd->cdhash());
524 CODESIGN_STATIC_CDHASH(this, CFDataGetBytePtr(mCDHash), (unsigned int)CFDataGetLength(mCDHash));
525 }
526 }
527 return mCDHash;
528 }
529
530
531 //
532 // Get an array of the cdhashes for all digest types in this signature
533 // The array is sorted by cd->hashType.
534 //
535 CFArrayRef SecStaticCode::cdHashes()
536 {
537 if (!mCDHashes) {
538 CFRef<CFMutableArrayRef> cdList = makeCFMutableArray(0);
539 for (auto it = mCodeDirectories.begin(); it != mCodeDirectories.end(); ++it) {
540 const CodeDirectory *cd = (const CodeDirectory *)CFDataGetBytePtr(it->second);
541 if (CFRef<CFDataRef> hash = cd->cdhash())
542 CFArrayAppendValue(cdList, hash);
543 }
544 mCDHashes = cdList.get();
545 }
546 return mCDHashes;
547 }
548
549
550 //
551 // Return the CMS signature blob; NULL if none found.
552 //
553 CFDataRef SecStaticCode::signature()
554 {
555 if (!mSignature)
556 mSignature.take(mRep->signature());
557 if (mSignature)
558 return mSignature;
559 MacOSError::throwMe(errSecCSUnsigned);
560 }
561
562
563 //
564 // Verify the signature on the CodeDirectory.
565 // If this succeeds (doesn't throw), the CodeDirectory is statically trustworthy.
566 // Any outcome (successful or not) is cached for the lifetime of the StaticCode.
567 //
568 void SecStaticCode::validateDirectory()
569 {
570 // echo previous outcome, if any
571 // track revocation separately, as it may not have been checked
572 // during the initial validation
573 if (!validated() || ((mValidationFlags & kSecCSEnforceRevocationChecks) && !revocationChecked()))
574 try {
575 // perform validation (or die trying)
576 CODESIGN_EVAL_STATIC_DIRECTORY(this);
577 mValidationExpired = verifySignature();
578 if (mValidationFlags & kSecCSEnforceRevocationChecks)
579 mRevocationChecked = true;
580
581 for (CodeDirectory::SpecialSlot slot = codeDirectory()->maxSpecialSlot(); slot >= 1; --slot)
582 if (mCache[slot]) // if we already loaded that resource...
583 validateComponent(slot, errorForSlot(slot)); // ... then check it now
584 mValidated = true; // we've done the deed...
585 mValidationResult = errSecSuccess; // ... and it was good
586 } catch (const CommonError &err) {
587 mValidated = true;
588 mValidationResult = err.osStatus();
589 throw;
590 } catch (...) {
591 secinfo("staticCode", "%p validation threw non-common exception", this);
592 mValidated = true;
593 Syslog::notice("code signing internal problem: unknown exception thrown by validation");
594 mValidationResult = errSecCSInternalError;
595 throw;
596 }
597 assert(validated());
598 // XXX: Embedded doesn't have CSSMERR_TP_CERT_EXPIRED so we can't throw it
599 // XXX: This should be implemented for embedded once we implement
600 // XXX: verifySignature and see how we're going to handle expired certs
601 #if TARGET_OS_OSX
602 if (mValidationResult == errSecSuccess) {
603 if (mValidationExpired)
604 if ((mValidationFlags & kSecCSConsiderExpiration)
605 || (codeDirectory()->flags & kSecCodeSignatureForceExpiration))
606 MacOSError::throwMe(CSSMERR_TP_CERT_EXPIRED);
607 } else
608 MacOSError::throwMe(mValidationResult);
609 #endif
610 }
611
612
613 //
614 // Load and validate the CodeDirectory and all components *except* those related to the resource envelope.
615 // Those latter components are checked by validateResources().
616 //
617 void SecStaticCode::validateNonResourceComponents()
618 {
619 this->validateDirectory();
620 for (CodeDirectory::SpecialSlot slot = codeDirectory()->maxSpecialSlot(); slot >= 1; --slot)
621 switch (slot) {
622 case cdResourceDirSlot: // validated by validateResources
623 break;
624 default:
625 this->component(slot); // loads and validates
626 break;
627 }
628 }
629
630
631 //
632 // Check that any "top index" sealed into the signature conforms to what's actually here.
633 //
634 void SecStaticCode::validateTopDirectory()
635 {
636 assert(mDir); // must already have loaded CodeDirectories
637 if (CFDataRef topDirectory = component(cdTopDirectorySlot)) {
638 const auto topData = (const Endian<uint32_t> *)CFDataGetBytePtr(topDirectory);
639 const auto topDataEnd = topData + CFDataGetLength(topDirectory) / sizeof(*topData);
640 std::vector<uint32_t> signedVector(topData, topDataEnd);
641
642 std::vector<uint32_t> foundVector;
643 foundVector.push_back(cdCodeDirectorySlot); // mandatory
644 for (CodeDirectory::Slot slot = 1; slot <= cdSlotMax; ++slot)
645 if (component(slot))
646 foundVector.push_back(slot);
647 int alternateCount = int(mCodeDirectories.size() - 1); // one will go into cdCodeDirectorySlot
648 for (int n = 0; n < alternateCount; n++)
649 foundVector.push_back(cdAlternateCodeDirectorySlots + n);
650 foundVector.push_back(cdSignatureSlot); // mandatory (may be empty)
651
652 if (signedVector != foundVector)
653 MacOSError::throwMe(errSecCSSignatureFailed);
654 }
655 }
656
657
658 //
659 // Get the (signed) signing date from the code signature.
660 // Sadly, we need to validate the signature to get the date (as a side benefit).
661 // This means that you can't get the signing time for invalidly signed code.
662 //
663 // We could run the decoder "almost to" verification to avoid this, but there seems
664 // little practical point to such a duplication of effort.
665 //
666 CFAbsoluteTime SecStaticCode::signingTime()
667 {
668 validateDirectory();
669 return mSigningTime;
670 }
671
672 CFAbsoluteTime SecStaticCode::signingTimestamp()
673 {
674 validateDirectory();
675 return mSigningTimestamp;
676 }
677
678
679 //
680 // Verify the CMS signature.
681 // This performs the cryptographic tango. It returns if the signature is valid,
682 // or throws if it is not. As a side effect, a successful return sets up the
683 // cached certificate chain for future use.
684 // Returns true if the signature is expired (the X.509 sense), false if it's not.
685 // Expiration is fatal (throws) if a secure timestamp is included, but not otherwise.
686 //
687 bool SecStaticCode::verifySignature()
688 {
689 // ad-hoc signed code is considered validly signed by definition
690 if (flag(kSecCodeSignatureAdhoc)) {
691 CODESIGN_EVAL_STATIC_SIGNATURE_ADHOC(this);
692 return false;
693 }
694
695 DTRACK(CODESIGN_EVAL_STATIC_SIGNATURE, this, (char*)this->mainExecutablePath().c_str());
696 #if TARGET_OS_OSX
697 // decode CMS and extract SecTrust for verification
698 CFRef<CMSDecoderRef> cms;
699 MacOSError::check(CMSDecoderCreate(&cms.aref())); // create decoder
700 CFDataRef sig = this->signature();
701 MacOSError::check(CMSDecoderUpdateMessage(cms, CFDataGetBytePtr(sig), CFDataGetLength(sig)));
702 this->codeDirectory(); // load CodeDirectory (sets mDir)
703 MacOSError::check(CMSDecoderSetDetachedContent(cms, mBaseDir));
704 MacOSError::check(CMSDecoderFinalizeMessage(cms));
705 MacOSError::check(CMSDecoderSetSearchKeychain(cms, cfEmptyArray()));
706 CFRef<CFArrayRef> vf_policies(createVerificationPolicies());
707 CFRef<CFArrayRef> ts_policies(createTimeStampingAndRevocationPolicies());
708
709 CMSSignerStatus status;
710 MacOSError::check(CMSDecoderCopySignerStatus(cms, 0, vf_policies,
711 false, &status, &mTrust.aref(), NULL));
712
713 if (status != kCMSSignerValid) {
714 const char *reason;
715 switch (status) {
716 case kCMSSignerUnsigned: reason="kCMSSignerUnsigned"; break;
717 case kCMSSignerNeedsDetachedContent: reason="kCMSSignerNeedsDetachedContent"; break;
718 case kCMSSignerInvalidSignature: reason="kCMSSignerInvalidSignature"; break;
719 case kCMSSignerInvalidCert: reason="kCMSSignerInvalidCert"; break;
720 case kCMSSignerInvalidIndex: reason="kCMSSignerInvalidIndex"; break;
721 default: reason="unknown"; break;
722 }
723 Security::Syslog::error("CMSDecoderCopySignerStatus failed with %s error (%d)",
724 reason, (int)status);
725 MacOSError::throwMe(errSecCSSignatureFailed);
726 }
727
728 // retrieve auxiliary v1 data bag and verify against current state
729 CFRef<CFDataRef> hashAgilityV1;
730 switch (OSStatus rc = CMSDecoderCopySignerAppleCodesigningHashAgility(cms, 0, &hashAgilityV1.aref())) {
731 case noErr:
732 if (hashAgilityV1) {
733 CFRef<CFDictionaryRef> hashDict = makeCFDictionaryFrom(hashAgilityV1);
734 CFArrayRef cdList = CFArrayRef(CFDictionaryGetValue(hashDict, CFSTR("cdhashes")));
735 CFArrayRef myCdList = this->cdHashes();
736
737 /* Note that this is not very "agile": There's no way to calculate the exact
738 * list for comparison if it contains hash algorithms we don't know yet... */
739 if (cdList == NULL || !CFEqual(cdList, myCdList))
740 MacOSError::throwMe(errSecCSSignatureFailed);
741 }
742 break;
743 case -1: /* CMS used to return this for "no attribute found", so tolerate it. Now returning noErr/NULL */
744 break;
745 default:
746 MacOSError::throwMe(rc);
747 }
748
749 // retrieve auxiliary v2 data bag and verify against current state
750 CFRef<CFDictionaryRef> hashAgilityV2;
751 switch (OSStatus rc = CMSDecoderCopySignerAppleCodesigningHashAgilityV2(cms, 0, &hashAgilityV2.aref())) {
752 case noErr:
753 if (hashAgilityV2) {
754 /* Require number of code directoris and entries in the hash agility
755 * dict to be the same size (no stripping out code directories).
756 */
757 if (CFDictionaryGetCount(hashAgilityV2) != mCodeDirectories.size()) {
758 MacOSError::throwMe(errSecCSSignatureFailed);
759 }
760
761 /* Require every cdhash of every code directory whose hash
762 * algorithm we know to be in the agility dictionary.
763 *
764 * We check untruncated cdhashes here because we can.
765 */
766 bool foundOurs = false;
767 for (auto& entry : mCodeDirectories) {
768 SECOidTag tag = CodeDirectorySet::SECOidTagForAlgorithm(entry.first);
769
770 if (tag == SEC_OID_UNKNOWN) {
771 // Unknown hash algorithm, ignore.
772 continue;
773 }
774
775 CFRef<CFNumberRef> key = makeCFNumber(int(tag));
776 CFRef<CFDataRef> entryCdhash;
777 entryCdhash = (CFDataRef)CFDictionaryGetValue(hashAgilityV2, (void*)key.get());
778
779 CodeDirectory const *cd = (CodeDirectory const*)CFDataGetBytePtr(entry.second);
780 CFRef<CFDataRef> ourCdhash = cd->cdhash(false); // Untruncated cdhash!
781 if (!CFEqual(entryCdhash, ourCdhash)) {
782 MacOSError::throwMe(errSecCSSignatureFailed);
783 }
784
785 if (entry.first == this->hashAlgorithm()) {
786 foundOurs = true;
787 }
788 }
789
790 /* Require the cdhash of our chosen code directory to be in the dictionary.
791 * In theory, the dictionary could be full of unsupported cdhashes, but we
792 * really want ours, which is bound to be supported, to be covered.
793 */
794 if (!foundOurs) {
795 MacOSError::throwMe(errSecCSSignatureFailed);
796 }
797 }
798 break;
799 case -1: /* CMS used to return this for "no attribute found", so tolerate it. Now returning noErr/NULL */
800 break;
801 default:
802 MacOSError::throwMe(rc);
803 }
804
805 // internal signing time (as specified by the signer; optional)
806 mSigningTime = 0; // "not present" marker (nobody could code sign on Jan 1, 2001 :-)
807 switch (OSStatus rc = CMSDecoderCopySignerSigningTime(cms, 0, &mSigningTime)) {
808 case errSecSuccess:
809 case errSecSigningTimeMissing:
810 break;
811 default:
812 Security::Syslog::error("Could not get signing time (error %d)", (int)rc);
813 MacOSError::throwMe(rc);
814 }
815
816 // certified signing time (as specified by a TSA; optional)
817 mSigningTimestamp = 0;
818 switch (OSStatus rc = CMSDecoderCopySignerTimestampWithPolicy(cms, ts_policies, 0, &mSigningTimestamp)) {
819 case errSecSuccess:
820 case errSecTimestampMissing:
821 break;
822 default:
823 Security::Syslog::error("Could not get timestamp (error %d)", (int)rc);
824 MacOSError::throwMe(rc);
825 }
826
827 // set up the environment for SecTrust
828 if (mValidationFlags & kSecCSNoNetworkAccess) {
829 MacOSError::check(SecTrustSetNetworkFetchAllowed(mTrust,false)); // no network?
830 }
831 MacOSError::check(SecTrustSetKeychainsAllowed(mTrust, false));
832
833 CSSM_APPLE_TP_ACTION_DATA actionData = {
834 CSSM_APPLE_TP_ACTION_VERSION, // version of data structure
835 0 // action flags
836 };
837
838 if (!(mValidationFlags & kSecCSCheckTrustedAnchors)) {
839 /* no need to evaluate anchor trust when building cert chain */
840 MacOSError::check(SecTrustSetAnchorCertificates(mTrust, cfEmptyArray())); // no anchors
841 actionData.ActionFlags |= CSSM_TP_ACTION_IMPLICIT_ANCHORS; // action flags
842 }
843
844 for (;;) { // at most twice
845 MacOSError::check(SecTrustSetParameters(mTrust,
846 CSSM_TP_ACTION_DEFAULT, CFTempData(&actionData, sizeof(actionData))));
847
848 // evaluate trust and extract results
849 SecTrustResultType trustResult;
850 MacOSError::check(SecTrustEvaluate(mTrust, &trustResult));
851 MacOSError::check(SecTrustGetResult(mTrust, &trustResult, &mCertChain.aref(), &mEvalDetails));
852
853 // if this is an Apple developer cert....
854 if (teamID() && SecStaticCode::isAppleDeveloperCert(mCertChain)) {
855 CFRef<CFStringRef> teamIDFromCert;
856 if (CFArrayGetCount(mCertChain) > 0) {
857 /* Note that SecCertificateCopySubjectComponent sets the out parameter to NULL if there is no field present */
858 MacOSError::check(SecCertificateCopySubjectComponent((SecCertificateRef)CFArrayGetValueAtIndex(mCertChain, Requirement::leafCert),
859 &CSSMOID_OrganizationalUnitName,
860 &teamIDFromCert.aref()));
861
862 if (teamIDFromCert) {
863 CFRef<CFStringRef> teamIDFromCD = CFStringCreateWithCString(NULL, teamID(), kCFStringEncodingUTF8);
864 if (!teamIDFromCD) {
865 Security::Syslog::error("Could not get team identifier (%s)", teamID());
866 MacOSError::throwMe(errSecCSInvalidTeamIdentifier);
867 }
868
869 if (CFStringCompare(teamIDFromCert, teamIDFromCD, 0) != kCFCompareEqualTo) {
870 Security::Syslog::error("Team identifier in the signing certificate (%s) does not match the team identifier (%s) in the code directory",
871 cfString(teamIDFromCert).c_str(), teamID());
872 MacOSError::throwMe(errSecCSBadTeamIdentifier);
873 }
874 }
875 }
876 }
877
878 CODESIGN_EVAL_STATIC_SIGNATURE_RESULT(this, trustResult, mCertChain ? (int)CFArrayGetCount(mCertChain) : 0);
879 switch (trustResult) {
880 case kSecTrustResultProceed:
881 case kSecTrustResultUnspecified:
882 break; // success
883 case kSecTrustResultDeny:
884 MacOSError::throwMe(CSSMERR_APPLETP_TRUST_SETTING_DENY); // user reject
885 case kSecTrustResultInvalid:
886 assert(false); // should never happen
887 MacOSError::throwMe(CSSMERR_TP_NOT_TRUSTED);
888 default:
889 {
890 OSStatus result;
891 MacOSError::check(SecTrustGetCssmResultCode(mTrust, &result));
892 // if we have a valid timestamp, CMS validates against (that) signing time and all is well.
893 // If we don't have one, may validate against *now*, and must be able to tolerate expiration.
894 if (mSigningTimestamp == 0) { // no timestamp available
895 if (((result == CSSMERR_TP_CERT_EXPIRED) || (result == CSSMERR_TP_CERT_NOT_VALID_YET))
896 && !(actionData.ActionFlags & CSSM_TP_ACTION_ALLOW_EXPIRED)) {
897 CODESIGN_EVAL_STATIC_SIGNATURE_EXPIRED(this);
898 actionData.ActionFlags |= CSSM_TP_ACTION_ALLOW_EXPIRED; // (this also allows postdated certs)
899 continue; // retry validation while tolerating expiration
900 }
901 }
902 Security::Syslog::error("SecStaticCode: verification failed (trust result %d, error %d)", trustResult, (int)result);
903 MacOSError::throwMe(result);
904 }
905 }
906
907 if (mSigningTimestamp) {
908 CFIndex rootix = CFArrayGetCount(mCertChain);
909 if (SecCertificateRef mainRoot = SecCertificateRef(CFArrayGetValueAtIndex(mCertChain, rootix-1)))
910 if (isAppleCA(mainRoot)) {
911 // impose policy: if the signature itself draws to Apple, then so must the timestamp signature
912 CFRef<CFArrayRef> tsCerts;
913 OSStatus result = CMSDecoderCopySignerTimestampCertificates(cms, 0, &tsCerts.aref());
914 if (result) {
915 Security::Syslog::error("SecStaticCode: could not get timestamp certificates (error %d)", (int)result);
916 MacOSError::check(result);
917 }
918 CFIndex tsn = CFArrayGetCount(tsCerts);
919 bool good = tsn > 0 && isAppleCA(SecCertificateRef(CFArrayGetValueAtIndex(tsCerts, tsn-1)));
920 if (!good) {
921 result = CSSMERR_TP_NOT_TRUSTED;
922 Security::Syslog::error("SecStaticCode: timestamp policy verification failed (error %d)", (int)result);
923 MacOSError::throwMe(result);
924 }
925 }
926 }
927
928 return actionData.ActionFlags & CSSM_TP_ACTION_ALLOW_EXPIRED;
929 }
930 #else
931 // Do some pre-verification initialization
932 CFDataRef sig = this->signature();
933 this->codeDirectory(); // load CodeDirectory (sets mDir)
934 mSigningTime = 0; // "not present" marker (nobody could code sign on Jan 1, 2001 :-)
935
936 CFRef<CFDictionaryRef> attrs;
937 CFRef<CFArrayRef> vf_policies(createVerificationPolicies());
938
939 // Verify the CMS signature against mBaseDir (SHA1)
940 MacOSError::check(SecCMSVerifyCopyDataAndAttributes(sig, mBaseDir, vf_policies, &mTrust.aref(), NULL, &attrs.aref()));
941
942 // Copy the signing time
943 mSigningTime = SecTrustGetVerifyTime(mTrust);
944
945 // Validate the cert chain
946 SecTrustResultType trustResult;
947 MacOSError::check(SecTrustEvaluate(mTrust, &trustResult));
948
949 // retrieve auxiliary data bag and verify against current state
950 CFRef<CFDataRef> hashBag;
951 hashBag = CFDataRef(CFDictionaryGetValue(attrs, kSecCMSHashAgility));
952 if (hashBag) {
953 CFRef<CFDictionaryRef> hashDict = makeCFDictionaryFrom(hashBag);
954 CFArrayRef cdList = CFArrayRef(CFDictionaryGetValue(hashDict, CFSTR("cdhashes")));
955 CFArrayRef myCdList = this->cdHashes();
956 if (cdList == NULL || !CFEqual(cdList, myCdList))
957 MacOSError::throwMe(errSecCSSignatureFailed);
958 }
959
960 /*
961 * Populate mCertChain with the certs. If we failed validation, the
962 * signer's cert will be checked installed provisioning profiles as an
963 * alternative to verification against the policy for store-signed binaries
964 */
965 SecCertificateRef leafCert = SecTrustGetCertificateAtIndex(mTrust, 0);
966 if (leafCert != NULL) {
967 CFIndex count = SecTrustGetCertificateCount(mTrust);
968
969 CFMutableArrayRef certs = CFArrayCreateMutable(kCFAllocatorDefault, count,
970 &kCFTypeArrayCallBacks);
971
972 CFArrayAppendValue(certs, leafCert);
973 for (CFIndex i = 1; i < count; ++i) {
974 CFArrayAppendValue(certs, SecTrustGetCertificateAtIndex(mTrust, i));
975 }
976
977 mCertChain.take((CFArrayRef)certs);
978 }
979
980 // Did we implicitly trust the signer?
981 mTrustedSigningCertChain = (trustResult == kSecTrustResultUnspecified || trustResult == kSecTrustResultProceed);
982
983 return false; // XXX: Not checking for expired certs
984 #endif
985 }
986
987 #if TARGET_OS_OSX
988 //
989 // Return the TP policy used for signature verification.
990 // This may be a simple SecPolicyRef or a CFArray of policies.
991 // The caller owns the return value.
992 //
993 static SecPolicyRef makeRevocationPolicy(CFOptionFlags flags)
994 {
995 CFRef<SecPolicyRef> policy(SecPolicyCreateRevocation(flags));
996 return policy.yield();
997 }
998 #endif
999
1000 CFArrayRef SecStaticCode::createVerificationPolicies()
1001 {
1002 if (mValidationFlags & kSecCSUseSoftwareSigningCert) {
1003 CFRef<SecPolicyRef> ssRef = SecPolicyCreateAppleSoftwareSigning();
1004 return makeCFArray(1, ssRef.get());
1005 }
1006 #if TARGET_OS_OSX
1007 CFRef<SecPolicyRef> core;
1008 MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3,
1009 &CSSMOID_APPLE_TP_CODE_SIGNING, &core.aref()));
1010 if (mValidationFlags & kSecCSNoNetworkAccess) {
1011 // Skips all revocation since they require network connectivity
1012 // therefore annihilates kSecCSEnforceRevocationChecks if present
1013 CFRef<SecPolicyRef> no_revoc = makeRevocationPolicy(kSecRevocationNetworkAccessDisabled);
1014 return makeCFArray(2, core.get(), no_revoc.get());
1015 }
1016 else if (mValidationFlags & kSecCSEnforceRevocationChecks) {
1017 // Add CRL and OCSP policies
1018 CFRef<SecPolicyRef> revoc = makeRevocationPolicy(kSecRevocationUseAnyAvailableMethod);
1019 return makeCFArray(2, core.get(), revoc.get());
1020 } else {
1021 return makeCFArray(1, core.get());
1022 }
1023 #elif TARGET_OS_TV
1024 CFRef<SecPolicyRef> tvOSRef = SecPolicyCreateAppleTVOSApplicationSigning();
1025 return makeCFArray(1, tvOSRef.get());
1026 #else
1027 CFRef<SecPolicyRef> iOSRef = SecPolicyCreateiPhoneApplicationSigning();
1028 return makeCFArray(1, iOSRef.get());
1029 #endif
1030
1031 }
1032
1033 CFArrayRef SecStaticCode::createTimeStampingAndRevocationPolicies()
1034 {
1035 CFRef<SecPolicyRef> tsPolicy = SecPolicyCreateAppleTimeStamping();
1036 #if TARGET_OS_OSX
1037 if (mValidationFlags & kSecCSNoNetworkAccess) {
1038 // Skips all revocation since they require network connectivity
1039 // therefore annihilates kSecCSEnforceRevocationChecks if present
1040 CFRef<SecPolicyRef> no_revoc = makeRevocationPolicy(kSecRevocationNetworkAccessDisabled);
1041 return makeCFArray(2, tsPolicy.get(), no_revoc.get());
1042 }
1043 else if (mValidationFlags & kSecCSEnforceRevocationChecks) {
1044 // Add CRL and OCSP policies
1045 CFRef<SecPolicyRef> revoc = makeRevocationPolicy(kSecRevocationUseAnyAvailableMethod);
1046 return makeCFArray(2, tsPolicy.get(), revoc.get());
1047 }
1048 else {
1049 return makeCFArray(1, tsPolicy.get());
1050 }
1051 #else
1052 return makeCFArray(1, tsPolicy.get());
1053 #endif
1054
1055 }
1056
1057
1058 //
1059 // Validate a particular sealed, cached resource against its (special) CodeDirectory slot.
1060 // The resource must already have been placed in the cache.
1061 // This does NOT perform basic validation.
1062 //
1063 void SecStaticCode::validateComponent(CodeDirectory::SpecialSlot slot, OSStatus fail /* = errSecCSSignatureFailed */)
1064 {
1065 assert(slot <= cdSlotMax);
1066 CFDataRef data = mCache[slot];
1067 assert(data); // must be cached
1068 if (data == CFDataRef(kCFNull)) {
1069 if (codeDirectory()->slotIsPresent(-slot)) // was supposed to be there...
1070 MacOSError::throwMe(fail); // ... and is missing
1071 } else {
1072 if (!codeDirectory()->validateSlot(CFDataGetBytePtr(data), CFDataGetLength(data), -slot, false))
1073 MacOSError::throwMe(fail);
1074 }
1075 }
1076
1077
1078 //
1079 // Perform static validation of the main executable.
1080 // This reads the main executable from disk and validates it against the
1081 // CodeDirectory code slot array.
1082 // Note that this is NOT an in-memory validation, and is thus potentially
1083 // subject to timing attacks.
1084 //
1085 void SecStaticCode::validateExecutable()
1086 {
1087 if (!validatedExecutable()) {
1088 try {
1089 DTRACK(CODESIGN_EVAL_STATIC_EXECUTABLE, this,
1090 (char*)this->mainExecutablePath().c_str(), codeDirectory()->nCodeSlots);
1091 const CodeDirectory *cd = this->codeDirectory();
1092 if (!cd)
1093 MacOSError::throwMe(errSecCSUnsigned);
1094 AutoFileDesc fd(mainExecutablePath(), O_RDONLY);
1095 fd.fcntl(F_NOCACHE, true); // turn off page caching (one-pass)
1096 if (Universal *fat = mRep->mainExecutableImage())
1097 fd.seek(fat->archOffset());
1098 size_t pageSize = cd->pageSize ? (1 << cd->pageSize) : 0;
1099 size_t remaining = cd->signingLimit();
1100 for (uint32_t slot = 0; slot < cd->nCodeSlots; ++slot) {
1101 size_t thisPage = remaining;
1102 if (pageSize)
1103 thisPage = min(thisPage, pageSize);
1104 __block bool good = true;
1105 CodeDirectory::multipleHashFileData(fd, thisPage, hashAlgorithms(), ^(CodeDirectory::HashAlgorithm type, Security::DynamicHash *hasher) {
1106 const CodeDirectory* cd = (const CodeDirectory*)CFDataGetBytePtr(mCodeDirectories[type]);
1107 if (!hasher->verify(cd->getSlot(slot,
1108 mValidationFlags & kSecCSValidatePEH)))
1109 good = false;
1110 });
1111 if (!good) {
1112 CODESIGN_EVAL_STATIC_EXECUTABLE_FAIL(this, (int)slot);
1113 MacOSError::throwMe(errSecCSSignatureFailed);
1114 }
1115 remaining -= thisPage;
1116 }
1117 assert(remaining == 0);
1118 mExecutableValidated = true;
1119 mExecutableValidResult = errSecSuccess;
1120 } catch (const CommonError &err) {
1121 mExecutableValidated = true;
1122 mExecutableValidResult = err.osStatus();
1123 throw;
1124 } catch (...) {
1125 secinfo("staticCode", "%p executable validation threw non-common exception", this);
1126 mExecutableValidated = true;
1127 mExecutableValidResult = errSecCSInternalError;
1128 Syslog::notice("code signing internal problem: unknown exception thrown by validation");
1129 throw;
1130 }
1131 }
1132 assert(validatedExecutable());
1133 if (mExecutableValidResult != errSecSuccess)
1134 MacOSError::throwMe(mExecutableValidResult);
1135 }
1136
1137
1138 //
1139 // Perform static validation of sealed resources and nested code.
1140 //
1141 // This performs a whole-code static resource scan and effectively
1142 // computes a concordance between what's on disk and what's in the ResourceDirectory.
1143 // Any unsanctioned difference causes an error.
1144 //
1145 unsigned SecStaticCode::estimateResourceWorkload()
1146 {
1147 // workload estimate = number of sealed files
1148 CFDictionaryRef sealedResources = resourceDictionary();
1149 CFDictionaryRef files = cfget<CFDictionaryRef>(sealedResources, "files2");
1150 if (files == NULL)
1151 files = cfget<CFDictionaryRef>(sealedResources, "files");
1152 return files ? unsigned(CFDictionaryGetCount(files)) : 0;
1153 }
1154
1155 void SecStaticCode::validateResources(SecCSFlags flags)
1156 {
1157 // do we have a superset of this requested validation cached?
1158 bool doit = true;
1159 if (mResourcesValidated) { // have cached outcome
1160 if (!(flags & kSecCSCheckNestedCode) || mResourcesDeep) // was deep or need no deep scan
1161 doit = false;
1162 }
1163
1164 if (doit) {
1165 if (mLimitedAsync == NULL) {
1166 mLimitedAsync = new LimitedAsync(diskRep()->fd().mediumType() == kIOPropertyMediumTypeSolidStateKey);
1167 }
1168
1169 try {
1170 CFDictionaryRef rules;
1171 CFDictionaryRef files;
1172 uint32_t version;
1173 if (!loadResources(rules, files, version))
1174 return; // validly no resources; nothing to do (ok)
1175
1176 // found resources, and they are sealed
1177 DTRACK(CODESIGN_EVAL_STATIC_RESOURCES, this,
1178 (char*)this->mainExecutablePath().c_str(), 0);
1179
1180 // scan through the resources on disk, checking each against the resourceDirectory
1181 mResourcesValidContext = new CollectingContext(*this); // collect all failures in here
1182
1183 // check for weak resource rules
1184 bool strict = flags & kSecCSStrictValidate;
1185 if (strict) {
1186 if (hasWeakResourceRules(rules, version, mAllowOmissions))
1187 if (mTolerateErrors.find(errSecCSWeakResourceRules) == mTolerateErrors.end())
1188 MacOSError::throwMe(errSecCSWeakResourceRules);
1189 if (version == 1)
1190 if (mTolerateErrors.find(errSecCSWeakResourceEnvelope) == mTolerateErrors.end())
1191 MacOSError::throwMe(errSecCSWeakResourceEnvelope);
1192 }
1193
1194 Dispatch::Group group;
1195 Dispatch::Group &groupRef = group; // (into block)
1196
1197 // scan through the resources on disk, checking each against the resourceDirectory
1198 __block CFRef<CFMutableDictionaryRef> resourceMap = makeCFMutableDictionary(files);
1199 string base = cfString(this->resourceBase());
1200 ResourceBuilder resources(base, base, rules, strict, mTolerateErrors);
1201 this->mResourceScope = &resources;
1202 diskRep()->adjustResources(resources);
1203
1204 resources.scan(^(FTSENT *ent, uint32_t ruleFlags, const string relpath, ResourceBuilder::Rule *rule) {
1205 CFDictionaryRemoveValue(resourceMap, CFTempString(relpath));
1206 bool isSymlink = (ent->fts_info == FTS_SL);
1207
1208 void (^validate)() = ^{
1209 validateResource(files, relpath, isSymlink, *mResourcesValidContext, flags, version);
1210 reportProgress();
1211 };
1212
1213 mLimitedAsync->perform(groupRef, validate);
1214 });
1215 group.wait(); // wait until all async resources have been validated as well
1216
1217 unsigned leftovers = unsigned(CFDictionaryGetCount(resourceMap));
1218 if (leftovers > 0) {
1219 secinfo("staticCode", "%d sealed resource(s) not found in code", int(leftovers));
1220 CFDictionaryApplyFunction(resourceMap, SecStaticCode::checkOptionalResource, mResourcesValidContext);
1221 }
1222
1223 // now check for any errors found in the reporting context
1224 mResourcesValidated = true;
1225 mResourcesDeep = flags & kSecCSCheckNestedCode;
1226 if (mResourcesValidContext->osStatus() != errSecSuccess)
1227 mResourcesValidContext->throwMe();
1228 } catch (const CommonError &err) {
1229 mResourcesValidated = true;
1230 mResourcesDeep = flags & kSecCSCheckNestedCode;
1231 mResourcesValidResult = err.osStatus();
1232 throw;
1233 } catch (...) {
1234 secinfo("staticCode", "%p executable validation threw non-common exception", this);
1235 mResourcesValidated = true;
1236 mResourcesDeep = flags & kSecCSCheckNestedCode;
1237 mResourcesValidResult = errSecCSInternalError;
1238 Syslog::notice("code signing internal problem: unknown exception thrown by validation");
1239 throw;
1240 }
1241 }
1242 assert(validatedResources());
1243 if (mResourcesValidResult)
1244 MacOSError::throwMe(mResourcesValidResult);
1245 if (mResourcesValidContext->osStatus() != errSecSuccess)
1246 mResourcesValidContext->throwMe();
1247 }
1248
1249
1250 bool SecStaticCode::loadResources(CFDictionaryRef& rules, CFDictionaryRef& files, uint32_t& version)
1251 {
1252 // sanity first
1253 CFDictionaryRef sealedResources = resourceDictionary();
1254 if (this->resourceBase()) { // disk has resources
1255 if (sealedResources)
1256 /* go to work below */;
1257 else
1258 MacOSError::throwMe(errSecCSResourcesNotFound);
1259 } else { // disk has no resources
1260 if (sealedResources)
1261 MacOSError::throwMe(errSecCSResourcesNotFound);
1262 else
1263 return false; // no resources, not sealed - fine (no work)
1264 }
1265
1266 // use V2 resource seal if available, otherwise fall back to V1
1267 if (CFDictionaryGetValue(sealedResources, CFSTR("files2"))) { // have V2 signature
1268 rules = cfget<CFDictionaryRef>(sealedResources, "rules2");
1269 files = cfget<CFDictionaryRef>(sealedResources, "files2");
1270 version = 2;
1271 } else { // only V1 available
1272 rules = cfget<CFDictionaryRef>(sealedResources, "rules");
1273 files = cfget<CFDictionaryRef>(sealedResources, "files");
1274 version = 1;
1275 }
1276 if (!rules || !files)
1277 MacOSError::throwMe(errSecCSResourcesInvalid);
1278 return true;
1279 }
1280
1281
1282 void SecStaticCode::checkOptionalResource(CFTypeRef key, CFTypeRef value, void *context)
1283 {
1284 ValidationContext *ctx = static_cast<ValidationContext *>(context);
1285 ResourceSeal seal(value);
1286 if (!seal.optional()) {
1287 if (key && CFGetTypeID(key) == CFStringGetTypeID()) {
1288 CFTempURL tempURL(CFStringRef(key), false, ctx->code.resourceBase());
1289 if (!tempURL.get()) {
1290 ctx->reportProblem(errSecCSBadDictionaryFormat, kSecCFErrorResourceSeal, key);
1291 } else {
1292 ctx->reportProblem(errSecCSBadResource, kSecCFErrorResourceMissing, tempURL);
1293 }
1294 } else {
1295 ctx->reportProblem(errSecCSBadResource, kSecCFErrorResourceSeal, key);
1296 }
1297 }
1298 }
1299
1300
1301 static bool isOmitRule(CFTypeRef value)
1302 {
1303 if (CFGetTypeID(value) == CFBooleanGetTypeID())
1304 return value == kCFBooleanFalse;
1305 CFDictionary rule(value, errSecCSResourceRulesInvalid);
1306 return rule.get<CFBooleanRef>("omit") == kCFBooleanTrue;
1307 }
1308
1309 bool SecStaticCode::hasWeakResourceRules(CFDictionaryRef rulesDict, uint32_t version, CFArrayRef allowedOmissions)
1310 {
1311 // compute allowed omissions
1312 CFRef<CFArrayRef> defaultOmissions = this->diskRep()->allowedResourceOmissions();
1313 if (!defaultOmissions) {
1314 Syslog::notice("code signing internal problem: diskRep returned no allowedResourceOmissions");
1315 MacOSError::throwMe(errSecCSInternalError);
1316 }
1317 CFRef<CFMutableArrayRef> allowed = CFArrayCreateMutableCopy(NULL, 0, defaultOmissions);
1318 if (allowedOmissions)
1319 CFArrayAppendArray(allowed, allowedOmissions, CFRangeMake(0, CFArrayGetCount(allowedOmissions)));
1320 CFRange range = CFRangeMake(0, CFArrayGetCount(allowed));
1321
1322 // check all resource rules for weakness
1323 string catchAllRule = (version == 1) ? "^Resources/" : "^.*";
1324 __block bool coversAll = false;
1325 __block bool forbiddenOmission = false;
1326 CFArrayRef allowedRef = allowed.get(); // (into block)
1327 CFDictionary rules(rulesDict, errSecCSResourceRulesInvalid);
1328 rules.apply(^(CFStringRef key, CFTypeRef value) {
1329 string pattern = cfString(key, errSecCSResourceRulesInvalid);
1330 if (pattern == catchAllRule && value == kCFBooleanTrue) {
1331 coversAll = true;
1332 return;
1333 }
1334 if (isOmitRule(value))
1335 forbiddenOmission |= !CFArrayContainsValue(allowedRef, range, key);
1336 });
1337
1338 return !coversAll || forbiddenOmission;
1339 }
1340
1341
1342 //
1343 // Load, validate, cache, and return CFDictionary forms of sealed resources.
1344 //
1345 CFDictionaryRef SecStaticCode::infoDictionary()
1346 {
1347 if (!mInfoDict) {
1348 mInfoDict.take(getDictionary(cdInfoSlot, errSecCSInfoPlistFailed));
1349 secinfo("staticCode", "%p loaded InfoDict %p", this, mInfoDict.get());
1350 }
1351 return mInfoDict;
1352 }
1353
1354 CFDictionaryRef SecStaticCode::entitlements()
1355 {
1356 if (!mEntitlements) {
1357 validateDirectory();
1358 if (CFDataRef entitlementData = component(cdEntitlementSlot)) {
1359 validateComponent(cdEntitlementSlot);
1360 const EntitlementBlob *blob = reinterpret_cast<const EntitlementBlob *>(CFDataGetBytePtr(entitlementData));
1361 if (blob->validateBlob()) {
1362 mEntitlements.take(blob->entitlements());
1363 secinfo("staticCode", "%p loaded Entitlements %p", this, mEntitlements.get());
1364 }
1365 // we do not consider a different blob type to be an error. We think it's a new format we don't understand
1366 }
1367 }
1368 return mEntitlements;
1369 }
1370
1371 CFDictionaryRef SecStaticCode::resourceDictionary(bool check /* = true */)
1372 {
1373 if (mResourceDict) // cached
1374 return mResourceDict;
1375 if (CFRef<CFDictionaryRef> dict = getDictionary(cdResourceDirSlot, check))
1376 if (cfscan(dict, "{rules=%Dn,files=%Dn}")) {
1377 secinfo("staticCode", "%p loaded ResourceDict %p",
1378 this, mResourceDict.get());
1379 return mResourceDict = dict;
1380 }
1381 // bad format
1382 return NULL;
1383 }
1384
1385
1386 CFDataRef SecStaticCode::copyComponent(CodeDirectory::SpecialSlot slot, CFDataRef hash)
1387 {
1388 const CodeDirectory* cd = this->codeDirectory();
1389 if (CFCopyRef<CFDataRef> component = this->component(slot)) {
1390 if (hash) {
1391 const void *slotHash = cd->getSlot(slot, false);
1392 if (cd->hashSize != CFDataGetLength(hash) || 0 != memcmp(slotHash, CFDataGetBytePtr(hash), cd->hashSize)) {
1393 Syslog::notice("copyComponent hash mismatch slot %d length %d", slot, int(CFDataGetLength(hash)));
1394 return NULL; // mismatch
1395 }
1396 }
1397 return component.yield();
1398 }
1399 return NULL;
1400 }
1401
1402
1403
1404 //
1405 // Load and cache the resource directory base.
1406 // Note that the base is optional for each DiskRep.
1407 //
1408 CFURLRef SecStaticCode::resourceBase()
1409 {
1410 if (!mGotResourceBase) {
1411 string base = mRep->resourcesRootPath();
1412 if (!base.empty())
1413 mResourceBase.take(makeCFURL(base, true));
1414 mGotResourceBase = true;
1415 }
1416 return mResourceBase;
1417 }
1418
1419
1420 //
1421 // Load a component, validate it, convert it to a CFDictionary, and return that.
1422 // This will force load and validation, which means that it will perform basic
1423 // validation if it hasn't been done yet.
1424 //
1425 CFDictionaryRef SecStaticCode::getDictionary(CodeDirectory::SpecialSlot slot, bool check /* = true */)
1426 {
1427 if (check)
1428 validateDirectory();
1429 if (CFDataRef infoData = component(slot)) {
1430 validateComponent(slot);
1431 if (CFDictionaryRef dict = makeCFDictionaryFrom(infoData))
1432 return dict;
1433 else
1434 MacOSError::throwMe(errSecCSBadDictionaryFormat);
1435 }
1436 return NULL;
1437 }
1438
1439 //
1440 //
1441 //
1442 CFDictionaryRef SecStaticCode::diskRepInformation()
1443 {
1444 return mRep->diskRepInformation();
1445 }
1446
1447 bool SecStaticCode::checkfix30814861(string path, bool addition) {
1448 // <rdar://problem/30814861> v2 resource rules don't match v1 resource rules
1449
1450 //// Condition 1: Is the app an iOS app that was built with an SDK lower than 9.0?
1451
1452 // We started signing correctly in 2014, 9.0 was first seeded mid-2016.
1453
1454 CFRef<CFDictionaryRef> inf = diskRepInformation();
1455 try {
1456 CFDictionary info(diskRepInformation(), errSecCSNotSupported);
1457 uint32_t platform =
1458 cfNumber(info.get<CFNumberRef>(kSecCodeInfoDiskRepVersionPlatform, errSecCSNotSupported), 0);
1459 uint32_t sdkVersion =
1460 cfNumber(info.get<CFNumberRef>(kSecCodeInfoDiskRepVersionSDK, errSecCSNotSupported), 0);
1461
1462 if (platform != PLATFORM_IOS || sdkVersion >= 0x00090000) {
1463 return false;
1464 }
1465 } catch (const MacOSError &error) {
1466 return false;
1467 }
1468
1469 //// Condition 2: Is it a .sinf/.supf/.supp file at the right location?
1470
1471 static regex_t pathre_sinf;
1472 static regex_t pathre_supp_supf;
1473 static dispatch_once_t once;
1474
1475 dispatch_once(&once, ^{
1476 os_assert_zero(regcomp(&pathre_sinf,
1477 "^(Frameworks/[^/]+\\.framework/|PlugIns/[^/]+\\.appex/|())SC_Info/[^/]+\\.sinf$",
1478 REG_EXTENDED | REG_NOSUB));
1479 os_assert_zero(regcomp(&pathre_supp_supf,
1480 "^(Frameworks/[^/]+\\.framework/|PlugIns/[^/]+\\.appex/|())SC_Info/[^/]+\\.(supf|supp)$",
1481 REG_EXTENDED | REG_NOSUB));
1482 });
1483
1484 // .sinf is added, .supf/.supp are modified.
1485 const regex_t &pathre = addition ? pathre_sinf : pathre_supp_supf;
1486
1487 const int result = regexec(&pathre, path.c_str(), 0, NULL, 0);
1488
1489 if (result == REG_NOMATCH) {
1490 return false;
1491 } else if (result != 0) {
1492 // Huh?
1493 secerror("unexpected regexec result %d for path '%s'", result, path.c_str());
1494 return false;
1495 }
1496
1497 //// Condition 3: Do the v1 rules actually exclude the file?
1498
1499 dispatch_once(&mCheckfix30814861builder1_once, ^{
1500 // Create the v1 resource builder lazily.
1501 CFDictionaryRef rules1 = cfget<CFDictionaryRef>(resourceDictionary(), "rules");
1502 const string base = cfString(resourceBase());
1503
1504 mCheckfix30814861builder1 = new ResourceBuilder(base, base, rules1, false, mTolerateErrors);
1505 });
1506
1507 ResourceBuilder::Rule const * const matchingRule = mCheckfix30814861builder1->findRule(path);
1508
1509 if (matchingRule == NULL || !(matchingRule->flags & ResourceBuilder::omitted)) {
1510 return false;
1511 }
1512
1513 //// All matched, this file is a check-fixed sinf/supf/supp.
1514
1515 return true;
1516
1517 }
1518
1519 void SecStaticCode::validateResource(CFDictionaryRef files, string path, bool isSymlink, ValidationContext &ctx, SecCSFlags flags, uint32_t version)
1520 {
1521 if (!resourceBase()) // no resources in DiskRep
1522 MacOSError::throwMe(errSecCSResourcesNotFound);
1523 CFRef<CFURLRef> fullpath = makeCFURL(path, false, resourceBase());
1524 if (version > 1 && ((flags & (kSecCSStrictValidate|kSecCSRestrictSidebandData)) == (kSecCSStrictValidate|kSecCSRestrictSidebandData))) {
1525 AutoFileDesc fd(cfString(fullpath));
1526 if (fd.hasExtendedAttribute(XATTR_RESOURCEFORK_NAME) || fd.hasExtendedAttribute(XATTR_FINDERINFO_NAME))
1527 ctx.reportProblem(errSecCSInvalidAssociatedFileData, kSecCFErrorResourceSideband, fullpath);
1528 }
1529 if (CFTypeRef file = CFDictionaryGetValue(files, CFTempString(path))) {
1530 ResourceSeal seal(file);
1531 const ResourceSeal& rseal = seal;
1532 if (seal.nested()) {
1533 if (isSymlink)
1534 return ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath); // changed type
1535 string suffix = ".framework";
1536 bool isFramework = (path.length() > suffix.length())
1537 && (path.compare(path.length()-suffix.length(), suffix.length(), suffix) == 0);
1538 validateNestedCode(fullpath, seal, flags, isFramework);
1539 } else if (seal.link()) {
1540 if (!isSymlink)
1541 return ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath); // changed type
1542 validateSymlinkResource(cfString(fullpath), cfString(seal.link()), ctx, flags);
1543 } else if (seal.hash(hashAlgorithm())) { // genuine file
1544 if (isSymlink)
1545 return ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath); // changed type
1546 AutoFileDesc fd(cfString(fullpath), O_RDONLY, FileDesc::modeMissingOk); // open optional file
1547 if (fd) {
1548 __block bool good = true;
1549 CodeDirectory::multipleHashFileData(fd, 0, hashAlgorithms(), ^(CodeDirectory::HashAlgorithm type, Security::DynamicHash *hasher) {
1550 if (!hasher->verify(rseal.hash(type)))
1551 good = false;
1552 });
1553 if (!good) {
1554 if (version == 2 && checkfix30814861(path, false)) {
1555 secinfo("validateResource", "%s check-fixed (altered).", path.c_str());
1556 } else {
1557 ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath); // altered
1558 }
1559 }
1560 } else {
1561 if (!seal.optional())
1562 ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceMissing, fullpath); // was sealed but is now missing
1563 else
1564 return; // validly missing
1565 }
1566 } else
1567 ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath); // changed type
1568 return;
1569 }
1570 if (version == 1) { // version 1 ignores symlinks altogether
1571 char target[PATH_MAX];
1572 if (::readlink(cfString(fullpath).c_str(), target, sizeof(target)) > 0)
1573 return;
1574 }
1575 if (version == 2 && checkfix30814861(path, true)) {
1576 secinfo("validateResource", "%s check-fixed (added).", path.c_str());
1577 } else {
1578 ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAdded, CFTempURL(path, false, resourceBase()));
1579 }
1580 }
1581
1582 void SecStaticCode::validatePlainMemoryResource(string path, CFDataRef fileData, SecCSFlags flags)
1583 {
1584 CFDictionaryRef rules;
1585 CFDictionaryRef files;
1586 uint32_t version;
1587 if (!loadResources(rules, files, version))
1588 MacOSError::throwMe(errSecCSResourcesNotFound); // no resources sealed; this can't be right
1589 if (CFTypeRef file = CFDictionaryGetValue(files, CFTempString(path))) {
1590 ResourceSeal seal(file);
1591 const Byte *sealHash = seal.hash(hashAlgorithm());
1592 if (sealHash) {
1593 if (codeDirectory()->verifyMemoryContent(fileData, sealHash))
1594 return; // success
1595 }
1596 }
1597 MacOSError::throwMe(errSecCSBadResource);
1598 }
1599
1600 void SecStaticCode::validateSymlinkResource(std::string fullpath, std::string seal, ValidationContext &ctx, SecCSFlags flags)
1601 {
1602 static const char* const allowedDestinations[] = {
1603 "/System/",
1604 "/Library/",
1605 NULL
1606 };
1607 char target[PATH_MAX];
1608 ssize_t len = ::readlink(fullpath.c_str(), target, sizeof(target)-1);
1609 if (len < 0)
1610 UnixError::check(-1);
1611 target[len] = '\0';
1612 std::string fulltarget = target;
1613 if (target[0] != '/') {
1614 size_t lastSlash = fullpath.rfind('/');
1615 fulltarget = fullpath.substr(0, lastSlash) + '/' + target;
1616 }
1617 if (seal != target) {
1618 ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, CFTempString(fullpath));
1619 return;
1620 }
1621 if ((mValidationFlags & (kSecCSStrictValidate|kSecCSRestrictSymlinks)) == (kSecCSStrictValidate|kSecCSRestrictSymlinks)) {
1622 char resolved[PATH_MAX];
1623 if (realpath(fulltarget.c_str(), resolved)) {
1624 assert(resolved[0] == '/');
1625 size_t rlen = strlen(resolved);
1626 if (target[0] == '/') {
1627 // absolute symlink; only allow absolute links to system locations
1628 for (const char* const* pathp = allowedDestinations; *pathp; pathp++) {
1629 size_t dlen = strlen(*pathp);
1630 if (rlen > dlen && strncmp(resolved, *pathp, dlen) == 0)
1631 return; // target inside /System, deemed okay
1632 }
1633 } else {
1634 // everything else must be inside the bundle(s)
1635 for (const SecStaticCode* code = this; code; code = code->mOuterScope) {
1636 string root = code->mResourceScope->root();
1637 if (strncmp(resolved, root.c_str(), root.size()) == 0) {
1638 if (code->mResourceScope->includes(resolved + root.length() + 1))
1639 return; // located in resource stack && included in envelope
1640 else
1641 break; // located but excluded from envelope (deny)
1642 }
1643 }
1644 }
1645 }
1646 // if we fell through, flag a symlink error
1647 if (mTolerateErrors.find(errSecCSInvalidSymlink) == mTolerateErrors.end())
1648 ctx.reportProblem(errSecCSInvalidSymlink, kSecCFErrorResourceAltered, CFTempString(fullpath));
1649 }
1650 }
1651
1652 void SecStaticCode::validateNestedCode(CFURLRef path, const ResourceSeal &seal, SecCSFlags flags, bool isFramework)
1653 {
1654 CFRef<SecRequirementRef> req;
1655 if (SecRequirementCreateWithString(seal.requirement(), kSecCSDefaultFlags, &req.aref()))
1656 MacOSError::throwMe(errSecCSResourcesInvalid);
1657
1658 // recursively verify this nested code
1659 try {
1660 if (!(flags & kSecCSCheckNestedCode))
1661 flags |= kSecCSBasicValidateOnly | kSecCSQuickCheck;
1662 SecPointer<SecStaticCode> code = new SecStaticCode(DiskRep::bestGuess(cfString(path)));
1663 code->initializeFromParent(*this);
1664 code->staticValidate(flags & (~kSecCSRestrictToAppLike), SecRequirement::required(req));
1665
1666 if (isFramework && (flags & kSecCSStrictValidate))
1667 try {
1668 validateOtherVersions(path, flags & (~kSecCSRestrictToAppLike), req, code);
1669 } catch (const CSError &err) {
1670 MacOSError::throwMe(errSecCSBadFrameworkVersion);
1671 } catch (const MacOSError &err) {
1672 MacOSError::throwMe(errSecCSBadFrameworkVersion);
1673 }
1674
1675 } catch (CSError &err) {
1676 if (err.error == errSecCSReqFailed) {
1677 mResourcesValidContext->reportProblem(errSecCSBadNestedCode, kSecCFErrorResourceAltered, path);
1678 return;
1679 }
1680 err.augment(kSecCFErrorPath, path);
1681 throw;
1682 } catch (const MacOSError &err) {
1683 if (err.error == errSecCSReqFailed) {
1684 mResourcesValidContext->reportProblem(errSecCSBadNestedCode, kSecCFErrorResourceAltered, path);
1685 return;
1686 }
1687 CSError::throwMe(err.error, kSecCFErrorPath, path);
1688 }
1689 }
1690
1691 void SecStaticCode::validateOtherVersions(CFURLRef path, SecCSFlags flags, SecRequirementRef req, SecStaticCode *code)
1692 {
1693 // Find out what current points to and do not revalidate
1694 std::string mainPath = cfStringRelease(code->diskRep()->copyCanonicalPath());
1695
1696 char main_path[PATH_MAX];
1697 bool foundTarget = false;
1698
1699 /* If it failed to get the target of the symlink, do not fail. It is a performance loss,
1700 not a security hole */
1701 if (realpath(mainPath.c_str(), main_path) != NULL)
1702 foundTarget = true;
1703
1704 std::ostringstream versionsPath;
1705 versionsPath << cfString(path) << "/Versions/";
1706
1707 DirScanner scanner(versionsPath.str());
1708
1709 if (scanner.initialized()) {
1710 struct dirent *entry = NULL;
1711 while ((entry = scanner.getNext()) != NULL) {
1712 std::ostringstream fullPath;
1713
1714 if (entry->d_type != DT_DIR || strcmp(entry->d_name, "Current") == 0)
1715 continue;
1716
1717 fullPath << versionsPath.str() << entry->d_name;
1718
1719 char real_full_path[PATH_MAX];
1720 if (realpath(fullPath.str().c_str(), real_full_path) == NULL)
1721 UnixError::check(-1);
1722
1723 // Do case insensitive comparions because realpath() was called for both paths
1724 if (foundTarget && strcmp(main_path, real_full_path) == 0)
1725 continue;
1726
1727 SecPointer<SecStaticCode> frameworkVersion = new SecStaticCode(DiskRep::bestGuess(real_full_path));
1728 frameworkVersion->initializeFromParent(*this);
1729 frameworkVersion->staticValidate(flags, SecRequirement::required(req));
1730 }
1731 }
1732 }
1733
1734
1735 //
1736 // Test a CodeDirectory flag.
1737 // Returns false if there is no CodeDirectory.
1738 // May throw if the CodeDirectory is present but somehow invalid.
1739 //
1740 bool SecStaticCode::flag(uint32_t tested)
1741 {
1742 if (const CodeDirectory *cd = this->codeDirectory(false))
1743 return cd->flags & tested;
1744 else
1745 return false;
1746 }
1747
1748
1749 //
1750 // Retrieve the full SuperBlob containing all internal requirements.
1751 //
1752 const Requirements *SecStaticCode::internalRequirements()
1753 {
1754 if (CFDataRef reqData = component(cdRequirementsSlot)) {
1755 const Requirements *req = (const Requirements *)CFDataGetBytePtr(reqData);
1756 if (!req->validateBlob())
1757 MacOSError::throwMe(errSecCSReqInvalid);
1758 return req;
1759 } else
1760 return NULL;
1761 }
1762
1763
1764 //
1765 // Retrieve a particular internal requirement by type.
1766 //
1767 const Requirement *SecStaticCode::internalRequirement(SecRequirementType type)
1768 {
1769 if (const Requirements *reqs = internalRequirements())
1770 return reqs->find<Requirement>(type);
1771 else
1772 return NULL;
1773 }
1774
1775
1776 //
1777 // Return the Designated Requirement (DR). This can be either explicit in the
1778 // Internal Requirements component, or implicitly generated on demand here.
1779 // Note that an explicit DR may have been implicitly generated at signing time;
1780 // we don't distinguish this case.
1781 //
1782 const Requirement *SecStaticCode::designatedRequirement()
1783 {
1784 if (const Requirement *req = internalRequirement(kSecDesignatedRequirementType)) {
1785 return req; // explicit in signing data
1786 } else {
1787 if (!mDesignatedReq)
1788 mDesignatedReq = defaultDesignatedRequirement();
1789 return mDesignatedReq;
1790 }
1791 }
1792
1793
1794 //
1795 // Generate the default Designated Requirement (DR) for this StaticCode.
1796 // Ignore any explicit DR it may contain.
1797 //
1798 const Requirement *SecStaticCode::defaultDesignatedRequirement()
1799 {
1800 if (flag(kSecCodeSignatureAdhoc)) {
1801 // adhoc signature: return a cdhash requirement for all architectures
1802 __block Requirement::Maker maker;
1803 Requirement::Maker::Chain chain(maker, opOr);
1804
1805 // insert cdhash requirement for all architectures
1806 __block CFRef<CFMutableArrayRef> allHashes = CFArrayCreateMutableCopy(NULL, 0, this->cdHashes());
1807 handleOtherArchitectures(^(SecStaticCode *other) {
1808 CFArrayRef hashes = other->cdHashes();
1809 CFArrayAppendArray(allHashes, hashes, CFRangeMake(0, CFArrayGetCount(hashes)));
1810 });
1811 CFIndex count = CFArrayGetCount(allHashes);
1812 for (CFIndex n = 0; n < count; ++n) {
1813 chain.add();
1814 maker.cdhash(CFDataRef(CFArrayGetValueAtIndex(allHashes, n)));
1815 }
1816 return maker.make();
1817 } else {
1818 #if TARGET_OS_OSX
1819 // full signature: Gin up full context and let DRMaker do its thing
1820 validateDirectory(); // need the cert chain
1821 Requirement::Context context(this->certificates(),
1822 this->infoDictionary(),
1823 this->entitlements(),
1824 this->identifier(),
1825 this->codeDirectory()
1826 );
1827 return DRMaker(context).make();
1828 #else
1829 MacOSError::throwMe(errSecCSUnimplemented);
1830 #endif
1831 }
1832 }
1833
1834
1835 //
1836 // Validate a SecStaticCode against the internal requirement of a particular type.
1837 //
1838 void SecStaticCode::validateRequirements(SecRequirementType type, SecStaticCode *target,
1839 OSStatus nullError /* = errSecSuccess */)
1840 {
1841 DTRACK(CODESIGN_EVAL_STATIC_INTREQ, this, type, target, nullError);
1842 if (const Requirement *req = internalRequirement(type))
1843 target->validateRequirement(req, nullError ? nullError : errSecCSReqFailed);
1844 else if (nullError)
1845 MacOSError::throwMe(nullError);
1846 else
1847 /* accept it */;
1848 }
1849
1850 //
1851 // Validate this StaticCode against an external Requirement
1852 //
1853 bool SecStaticCode::satisfiesRequirement(const Requirement *req, OSStatus failure)
1854 {
1855 bool result = false;
1856 assert(req);
1857 validateDirectory();
1858 result = req->validates(Requirement::Context(mCertChain, infoDictionary(), entitlements(), codeDirectory()->identifier(), codeDirectory()), failure);
1859 return result;
1860 }
1861
1862 void SecStaticCode::validateRequirement(const Requirement *req, OSStatus failure)
1863 {
1864 if (!this->satisfiesRequirement(req, failure))
1865 MacOSError::throwMe(failure);
1866 }
1867
1868 //
1869 // Retrieve one certificate from the cert chain.
1870 // Positive and negative indices can be used:
1871 // [ leaf, intermed-1, ..., intermed-n, anchor ]
1872 // 0 1 ... -2 -1
1873 // Returns NULL if unavailable for any reason.
1874 //
1875 SecCertificateRef SecStaticCode::cert(int ix)
1876 {
1877 validateDirectory(); // need cert chain
1878 if (mCertChain) {
1879 CFIndex length = CFArrayGetCount(mCertChain);
1880 if (ix < 0)
1881 ix += length;
1882 if (ix >= 0 && ix < length)
1883 return SecCertificateRef(CFArrayGetValueAtIndex(mCertChain, ix));
1884 }
1885 return NULL;
1886 }
1887
1888 CFArrayRef SecStaticCode::certificates()
1889 {
1890 validateDirectory(); // need cert chain
1891 return mCertChain;
1892 }
1893
1894
1895 //
1896 // Gather (mostly) API-official information about this StaticCode.
1897 //
1898 // This method lives in the twilight between the API and internal layers,
1899 // since it generates API objects (Sec*Refs) for return.
1900 //
1901 CFDictionaryRef SecStaticCode::signingInformation(SecCSFlags flags)
1902 {
1903 //
1904 // Start with the pieces that we return even for unsigned code.
1905 // This makes Sec[Static]CodeRefs useful as API-level replacements
1906 // of our internal OSXCode objects.
1907 //
1908 CFRef<CFMutableDictionaryRef> dict = makeCFMutableDictionary(1,
1909 kSecCodeInfoMainExecutable, CFTempURL(this->mainExecutablePath()).get()
1910 );
1911
1912 //
1913 // If we're not signed, this is all you get
1914 //
1915 if (!this->isSigned())
1916 return dict.yield();
1917
1918 //
1919 // Add the generic attributes that we always include
1920 //
1921 CFDictionaryAddValue(dict, kSecCodeInfoIdentifier, CFTempString(this->identifier()));
1922 CFDictionaryAddValue(dict, kSecCodeInfoFlags, CFTempNumber(this->codeDirectory(false)->flags.get()));
1923 CFDictionaryAddValue(dict, kSecCodeInfoFormat, CFTempString(this->format()));
1924 CFDictionaryAddValue(dict, kSecCodeInfoSource, CFTempString(this->signatureSource()));
1925 CFDictionaryAddValue(dict, kSecCodeInfoUnique, this->cdHash());
1926 CFDictionaryAddValue(dict, kSecCodeInfoCdHashes, this->cdHashes());
1927 const CodeDirectory* cd = this->codeDirectory(false);
1928 CFDictionaryAddValue(dict, kSecCodeInfoDigestAlgorithm, CFTempNumber(cd->hashType));
1929 CFRef<CFArrayRef> digests = makeCFArrayFrom(^CFTypeRef(CodeDirectory::HashAlgorithm type) { return CFTempNumber(type); }, hashAlgorithms());
1930 CFDictionaryAddValue(dict, kSecCodeInfoDigestAlgorithms, digests);
1931 if (cd->platform)
1932 CFDictionaryAddValue(dict, kSecCodeInfoPlatformIdentifier, CFTempNumber(cd->platform));
1933 if (cd->runtimeVersion()) {
1934 CFDictionaryAddValue(dict, kSecCodeInfoRuntimeVersion, CFTempNumber(cd->runtimeVersion()));
1935 }
1936
1937 //
1938 // Deliver any Info.plist only if it looks intact
1939 //
1940 try {
1941 if (CFDictionaryRef info = this->infoDictionary())
1942 CFDictionaryAddValue(dict, kSecCodeInfoPList, info);
1943 } catch (...) { } // don't deliver Info.plist if questionable
1944
1945 //
1946 // kSecCSSigningInformation adds information about signing certificates and chains
1947 //
1948 if (flags & kSecCSSigningInformation)
1949 try {
1950 if (CFDataRef sig = this->signature())
1951 CFDictionaryAddValue(dict, kSecCodeInfoCMS, sig);
1952 if (const char *teamID = this->teamID())
1953 CFDictionaryAddValue(dict, kSecCodeInfoTeamIdentifier, CFTempString(teamID));
1954 if (mTrust)
1955 CFDictionaryAddValue(dict, kSecCodeInfoTrust, mTrust);
1956 if (CFArrayRef certs = this->certificates())
1957 CFDictionaryAddValue(dict, kSecCodeInfoCertificates, certs);
1958 if (CFAbsoluteTime time = this->signingTime())
1959 if (CFRef<CFDateRef> date = CFDateCreate(NULL, time))
1960 CFDictionaryAddValue(dict, kSecCodeInfoTime, date);
1961 if (CFAbsoluteTime time = this->signingTimestamp())
1962 if (CFRef<CFDateRef> date = CFDateCreate(NULL, time))
1963 CFDictionaryAddValue(dict, kSecCodeInfoTimestamp, date);
1964 } catch (...) { }
1965
1966 //
1967 // kSecCSRequirementInformation adds information on requirements
1968 //
1969 if (flags & kSecCSRequirementInformation)
1970
1971 //DR not currently supported on iOS
1972 #if TARGET_OS_OSX
1973 try {
1974 if (const Requirements *reqs = this->internalRequirements()) {
1975 CFDictionaryAddValue(dict, kSecCodeInfoRequirements,
1976 CFTempString(Dumper::dump(reqs)));
1977 CFDictionaryAddValue(dict, kSecCodeInfoRequirementData, CFTempData(*reqs));
1978 }
1979
1980 const Requirement *dreq = this->designatedRequirement();
1981 CFRef<SecRequirementRef> dreqRef = (new SecRequirement(dreq))->handle();
1982 CFDictionaryAddValue(dict, kSecCodeInfoDesignatedRequirement, dreqRef);
1983 if (this->internalRequirement(kSecDesignatedRequirementType)) { // explicit
1984 CFRef<SecRequirementRef> ddreqRef = (new SecRequirement(this->defaultDesignatedRequirement(), true))->handle();
1985 CFDictionaryAddValue(dict, kSecCodeInfoImplicitDesignatedRequirement, ddreqRef);
1986 } else { // implicit
1987 CFDictionaryAddValue(dict, kSecCodeInfoImplicitDesignatedRequirement, dreqRef);
1988 }
1989 } catch (...) { }
1990 #endif
1991
1992 try {
1993 if (CFDataRef ent = this->component(cdEntitlementSlot)) {
1994 CFDictionaryAddValue(dict, kSecCodeInfoEntitlements, ent);
1995 if (CFDictionaryRef entdict = this->entitlements())
1996 CFDictionaryAddValue(dict, kSecCodeInfoEntitlementsDict, entdict);
1997 }
1998 } catch (...) { }
1999
2000 //
2001 // kSecCSInternalInformation adds internal information meant to be for Apple internal
2002 // use (SPI), and not guaranteed to be stable. Primarily, this is data we want
2003 // to reliably transmit through the API wall so that code outside the Security.framework
2004 // can use it without having to play nasty tricks to get it.
2005 //
2006 if (flags & kSecCSInternalInformation) {
2007 try {
2008 if (mDir)
2009 CFDictionaryAddValue(dict, kSecCodeInfoCodeDirectory, mDir);
2010 CFDictionaryAddValue(dict, kSecCodeInfoCodeOffset, CFTempNumber(mRep->signingBase()));
2011 if (!(flags & kSecCSSkipResourceDirectory)) {
2012 if (CFRef<CFDictionaryRef> rdict = getDictionary(cdResourceDirSlot, false)) // suppress validation
2013 CFDictionaryAddValue(dict, kSecCodeInfoResourceDirectory, rdict);
2014 }
2015 if (CFRef<CFDictionaryRef> ddict = diskRepInformation())
2016 CFDictionaryAddValue(dict, kSecCodeInfoDiskRepInfo, ddict);
2017 } catch (...) { }
2018 }
2019
2020
2021 //
2022 // kSecCSContentInformation adds more information about the physical layout
2023 // of the signed code. This is (only) useful for packaging or patching-oriented
2024 // applications.
2025 //
2026 if (flags & kSecCSContentInformation && !(flags & kSecCSSkipResourceDirectory))
2027 if (CFRef<CFArrayRef> files = mRep->modifiedFiles())
2028 CFDictionaryAddValue(dict, kSecCodeInfoChangedFiles, files);
2029
2030 return dict.yield();
2031 }
2032
2033
2034 //
2035 // Resource validation contexts.
2036 // The default context simply throws a CSError, rudely terminating the operation.
2037 //
2038 SecStaticCode::ValidationContext::~ValidationContext()
2039 { /* virtual */ }
2040
2041 void SecStaticCode::ValidationContext::reportProblem(OSStatus rc, CFStringRef type, CFTypeRef value)
2042 {
2043 CSError::throwMe(rc, type, value);
2044 }
2045
2046 void SecStaticCode::CollectingContext::reportProblem(OSStatus rc, CFStringRef type, CFTypeRef value)
2047 {
2048 StLock<Mutex> _(mLock);
2049 if (mStatus == errSecSuccess)
2050 mStatus = rc; // record first failure for eventual error return
2051 if (type) {
2052 if (!mCollection)
2053 mCollection.take(makeCFMutableDictionary());
2054 CFMutableArrayRef element = CFMutableArrayRef(CFDictionaryGetValue(mCollection, type));
2055 if (!element) {
2056 element = makeCFMutableArray(0);
2057 if (!element)
2058 CFError::throwMe();
2059 CFDictionaryAddValue(mCollection, type, element);
2060 CFRelease(element);
2061 }
2062 CFArrayAppendValue(element, value);
2063 }
2064 }
2065
2066 void SecStaticCode::CollectingContext::throwMe()
2067 {
2068 assert(mStatus != errSecSuccess);
2069 throw CSError(mStatus, mCollection.retain());
2070 }
2071
2072
2073 //
2074 // Master validation driver.
2075 // This is the static validation (only) driver for the API.
2076 //
2077 // SecStaticCode exposes an a la carte menu of topical validators applying
2078 // to a given object. The static validation API pulls them together reliably,
2079 // but it also adds three matrix dimensions: architecture (for "fat" Mach-O binaries),
2080 // nested code, and multiple digests. This function will crawl a suitable cross-section of this
2081 // validation matrix based on which options it is given, creating temporary
2082 // SecStaticCode objects on the fly to complete the task.
2083 // (The point, of course, is to do as little duplicate work as possible.)
2084 //
2085 void SecStaticCode::staticValidate(SecCSFlags flags, const SecRequirement *req)
2086 {
2087 setValidationFlags(flags);
2088
2089 // initialize progress/cancellation state
2090 if (flags & kSecCSReportProgress)
2091 prepareProgress(estimateResourceWorkload() + 2); // +1 head, +1 tail
2092
2093
2094 // core components: once per architecture (if any)
2095 this->staticValidateCore(flags, req);
2096 if (flags & kSecCSCheckAllArchitectures)
2097 handleOtherArchitectures(^(SecStaticCode* subcode) {
2098 if (flags & kSecCSCheckGatekeeperArchitectures) {
2099 Universal *fat = subcode->diskRep()->mainExecutableImage();
2100 assert(fat && fat->narrowed()); // handleOtherArchitectures gave us a focused architecture slice
2101 Architecture arch = fat->bestNativeArch(); // actually, the ONLY one
2102 if ((arch.cpuType() & ~CPU_ARCH_MASK) == CPU_TYPE_POWERPC)
2103 return; // irrelevant to Gatekeeper
2104 }
2105 subcode->detachedSignature(this->mDetachedSig); // carry over explicit (but not implicit) detached signature
2106 subcode->staticValidateCore(flags, req);
2107 });
2108 reportProgress();
2109
2110 // allow monitor intervention in source validation phase
2111 reportEvent(CFSTR("prepared"), NULL);
2112
2113 // resources: once for all architectures
2114 if (!(flags & kSecCSDoNotValidateResources))
2115 this->validateResources(flags);
2116
2117 // perform strict validation if desired
2118 if (flags & kSecCSStrictValidate)
2119 mRep->strictValidate(codeDirectory(), mTolerateErrors, mValidationFlags);
2120 reportProgress();
2121
2122 // allow monitor intervention
2123 if (CFRef<CFTypeRef> veto = reportEvent(CFSTR("validated"), NULL)) {
2124 if (CFGetTypeID(veto) == CFNumberGetTypeID())
2125 MacOSError::throwMe(cfNumber<OSStatus>(veto.as<CFNumberRef>()));
2126 else
2127 MacOSError::throwMe(errSecCSBadCallbackValue);
2128 }
2129 }
2130
2131 void SecStaticCode::staticValidateCore(SecCSFlags flags, const SecRequirement *req)
2132 {
2133 try {
2134 this->validateNonResourceComponents(); // also validates the CodeDirectory
2135 this->validateTopDirectory();
2136 if (!(flags & kSecCSDoNotValidateExecutable))
2137 this->validateExecutable();
2138 if (req)
2139 this->validateRequirement(req->requirement(), errSecCSReqFailed);
2140 } catch (CSError &err) {
2141 if (Universal *fat = this->diskRep()->mainExecutableImage()) // Mach-O
2142 if (MachO *mach = fat->architecture()) {
2143 err.augment(kSecCFErrorArchitecture, CFTempString(mach->architecture().displayName()));
2144 delete mach;
2145 }
2146 throw;
2147 } catch (const MacOSError &err) {
2148 // add architecture information if we can get it
2149 if (Universal *fat = this->diskRep()->mainExecutableImage())
2150 if (MachO *mach = fat->architecture()) {
2151 CFTempString arch(mach->architecture().displayName());
2152 delete mach;
2153 CSError::throwMe(err.error, kSecCFErrorArchitecture, arch);
2154 }
2155 throw;
2156 }
2157 }
2158
2159
2160 //
2161 // A helper that generates SecStaticCode objects for all but the primary architecture
2162 // of a fat binary and calls a block on them.
2163 // If there's only one architecture (or this is an architecture-agnostic code),
2164 // nothing happens quickly.
2165 //
2166 void SecStaticCode::handleOtherArchitectures(void (^handle)(SecStaticCode* other))
2167 {
2168 if (Universal *fat = this->diskRep()->mainExecutableImage()) {
2169 Universal::Architectures architectures;
2170 fat->architectures(architectures);
2171 if (architectures.size() > 1) {
2172 DiskRep::Context ctx;
2173 off_t activeOffset = fat->archOffset();
2174 for (Universal::Architectures::const_iterator arch = architectures.begin(); arch != architectures.end(); ++arch) {
2175 try {
2176 ctx.offset = int_cast<size_t, off_t>(fat->archOffset(*arch));
2177 ctx.size = fat->lengthOfSlice(int_cast<off_t,size_t>(ctx.offset));
2178 if (ctx.offset != activeOffset) { // inactive architecture; check it
2179 SecPointer<SecStaticCode> subcode = new SecStaticCode(DiskRep::bestGuess(this->mainExecutablePath(), &ctx));
2180 subcode->detachedSignature(this->mDetachedSig); // carry over explicit (but not implicit) detached signature
2181 if (this->teamID() == NULL || subcode->teamID() == NULL) {
2182 if (this->teamID() != subcode->teamID())
2183 MacOSError::throwMe(errSecCSSignatureInvalid);
2184 } else if (strcmp(this->teamID(), subcode->teamID()) != 0)
2185 MacOSError::throwMe(errSecCSSignatureInvalid);
2186 handle(subcode);
2187 }
2188 } catch(std::out_of_range e) {
2189 // some of our int_casts fell over.
2190 MacOSError::throwMe(errSecCSBadObjectFormat);
2191 }
2192 }
2193 }
2194 }
2195 }
2196
2197 //
2198 // A method that takes a certificate chain (certs) and evaluates
2199 // if it is a Mac or IPhone developer cert, an app store distribution cert,
2200 // or a developer ID
2201 //
2202 bool SecStaticCode::isAppleDeveloperCert(CFArrayRef certs)
2203 {
2204 static const std::string appleDeveloperRequirement = "(" + std::string(WWDRRequirement) + ") or (" + MACWWDRRequirement + ") or (" + developerID + ") or (" + distributionCertificate + ") or (" + iPhoneDistributionCert + ")";
2205 SecPointer<SecRequirement> req = new SecRequirement(parseRequirement(appleDeveloperRequirement), true);
2206 Requirement::Context ctx(certs, NULL, NULL, "", NULL);
2207
2208 return req->requirement()->validates(ctx);
2209 }
2210
2211 } // end namespace CodeSigning
2212 } // end namespace Security