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