2 * Copyright (c) 2006,2011-2014 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
25 // reqinterp - Requirement language (exprOp) interpreter
27 #include "reqinterp.h"
28 #include "codesigning_dtrace.h"
29 #include <Security/SecTrustSettingsPriv.h>
30 #include <Security/SecCertificatePriv.h>
31 #include <security_utilities/memutils.h>
32 #include <security_utilities/logging.h>
34 #include <IOKit/IOKitLib.h>
35 #include <IOKit/IOCFUnserialize.h>
36 #include "csutilities.h"
37 #include "notarization.h"
40 namespace CodeSigning
{
44 // Fragment fetching, caching, and evaluation.
46 // Several language elements allow "calling" of separate requirement programs
47 // stored on disk as (binary) requirement blobs. The Fragments class takes care
48 // of finding, loading, caching, and evaluating them.
50 // This is a singleton for (process global) caching. It works fine as multiple instances,
51 // at a loss of caching effectiveness.
57 bool named(const std::string
&name
, const Requirement::Context
&ctx
)
58 { return evalNamed("subreq", name
, ctx
); }
59 bool namedAnchor(const std::string
&name
, const Requirement::Context
&ctx
)
60 { return evalNamed("anchorreq", name
, ctx
); }
63 bool evalNamed(const char *type
, const std::string
&name
, const Requirement::Context
&ctx
);
64 CFDataRef
fragment(const char *type
, const std::string
&name
);
66 typedef std::map
<std::string
, CFRef
<CFDataRef
> > FragMap
;
69 CFBundleRef mMyBundle
; // Security.framework bundle
70 Mutex mLock
; // lock for all of the below...
71 FragMap mFragments
; // cached fragments
74 static ModuleNexus
<Fragments
> fragments
;
78 // Magic certificate features
80 static CFStringRef appleIntermediateCN
= CFSTR("Apple Code Signing Certification Authority");
81 static CFStringRef appleIntermediateO
= CFSTR("Apple Inc.");
85 // Main interpreter function.
87 // ExprOp code is in Polish Notation (operator followed by operands),
88 // and this engine uses opportunistic evaluation.
90 bool Requirement::Interpreter::evaluate()
91 { return eval(stackLimit
); }
93 bool Requirement::Interpreter::eval(int depth
)
95 if (--depth
<= 0) // nested too deeply - protect the stack
96 MacOSError::throwMe(errSecCSReqInvalid
);
98 ExprOp op
= ExprOp(get
<uint32_t>());
99 CODESIGN_EVAL_REQINT_OP(op
, this->pc() - sizeof(uint32_t));
100 switch (op
& ~opFlagMask
) {
106 return mContext
->directory
&& getString() == mContext
->directory
->identifier();
108 return appleSigned();
109 case opAppleGenericAnchor
:
110 return appleAnchored();
113 SecCertificateRef cert
= mContext
->cert(get
<int32_t>());
114 return verifyAnchor(cert
, getSHA1());
116 case opInfoKeyValue
: // [legacy; use opInfoKeyField]
118 string key
= getString();
119 return infoKeyValue(key
, Match(CFTempString(getString()), matchEqual
));
122 return eval(depth
) & eval(depth
);
124 return eval(depth
) | eval(depth
);
126 if (mContext
->directory
) {
127 CFRef
<CFDataRef
> cdhash
= mContext
->directory
->cdhash();
128 CFRef
<CFDataRef
> required
= getHash();
129 return CFEqual(cdhash
, required
);
136 string key
= getString();
138 return infoKeyValue(key
, match
);
140 case opEntitlementField
:
142 string key
= getString();
144 return entitlementValue(key
, match
);
148 SecCertificateRef cert
= mContext
->cert(get
<int32_t>());
149 string key
= getString();
151 return certFieldValue(key
, match
, cert
);
156 SecCertificateRef cert
= mContext
->cert(get
<int32_t>());
157 string key
= getString();
159 return certFieldGeneric(key
, match
, cert
);
163 SecCertificateRef cert
= mContext
->cert(get
<int32_t>());
164 string key
= getString();
166 return certFieldPolicy(key
, match
, cert
);
170 return trustedCert(get
<int32_t>());
172 return trustedCerts();
174 return fragments().namedAnchor(getString(), *mContext
);
176 return fragments().named(getString(), *mContext
);
179 int32_t targetPlatform
= get
<int32_t>();
180 return mContext
->directory
&& mContext
->directory
->platform
== targetPlatform
;
184 return isNotarized(mContext
);
187 // opcode not recognized - handle generically if possible, fail otherwise
188 if (op
& (opGenericFalse
| opGenericSkip
)) {
189 // unknown opcode, but it has a size field and can be safely bypassed
190 skip(get
<uint32_t>());
191 if (op
& opGenericFalse
) {
192 CODESIGN_EVAL_REQINT_UNKNOWN_FALSE(op
);
195 CODESIGN_EVAL_REQINT_UNKNOWN_SKIPPED(op
);
199 // unrecognized opcode and no way to interpret it
200 secinfo("csinterp", "opcode 0x%x cannot be handled; aborting", op
);
201 MacOSError::throwMe(errSecCSUnimplemented
);
207 // Evaluate an Info.plist key condition
209 bool Requirement::Interpreter::infoKeyValue(const string
&key
, const Match
&match
)
211 if (mContext
->info
) // we have an Info.plist
212 if (CFTypeRef value
= CFDictionaryGetValue(mContext
->info
, CFTempString(key
)))
219 // Evaluate an entitlement condition
221 bool Requirement::Interpreter::entitlementValue(const string
&key
, const Match
&match
)
223 if (mContext
->entitlements
) // we have an Info.plist
224 if (CFTypeRef value
= CFDictionaryGetValue(mContext
->entitlements
, CFTempString(key
)))
230 bool Requirement::Interpreter::certFieldValue(const string
&key
, const Match
&match
, SecCertificateRef cert
)
232 // XXX: Not supported on embedded yet due to lack of supporting API
234 // no cert, no chance
238 // a table of recognized keys for the "certificate[foo]" syntax
239 static const struct CertField
{
243 { "subject.C", &CSSMOID_CountryName
},
244 { "subject.CN", &CSSMOID_CommonName
},
245 { "subject.D", &CSSMOID_Description
},
246 { "subject.L", &CSSMOID_LocalityName
},
247 // { "subject.C-L", &CSSMOID_CollectiveLocalityName }, // missing from Security.framework headers
248 { "subject.O", &CSSMOID_OrganizationName
},
249 { "subject.C-O", &CSSMOID_CollectiveOrganizationName
},
250 { "subject.OU", &CSSMOID_OrganizationalUnitName
},
251 { "subject.C-OU", &CSSMOID_CollectiveOrganizationalUnitName
},
252 { "subject.ST", &CSSMOID_StateProvinceName
},
253 { "subject.C-ST", &CSSMOID_CollectiveStateProvinceName
},
254 { "subject.STREET", &CSSMOID_StreetAddress
},
255 { "subject.C-STREET", &CSSMOID_CollectiveStreetAddress
},
256 { "subject.UID", &CSSMOID_UserID
},
260 // DN-component single-value match
261 for (const CertField
*cf
= certFields
; cf
->name
; cf
++)
262 if (cf
->name
== key
) {
263 CFRef
<CFStringRef
> value
;
264 OSStatus rc
= SecCertificateCopySubjectComponent(cert
, cf
->oid
, &value
.aref());
266 secinfo("csinterp", "cert %p lookup for DN.%s failed rc=%d", cert
, key
.c_str(), (int)rc
);
272 // email multi-valued match (any of...)
273 if (key
== "email") {
274 CFRef
<CFArrayRef
> value
;
275 OSStatus rc
= SecCertificateCopyEmailAddresses(cert
, &value
.aref());
277 secinfo("csinterp", "cert %p lookup for email failed rc=%d", cert
, (int)rc
);
283 // unrecognized key. Fail but do not abort to promote backward compatibility down the road
284 secinfo("csinterp", "cert field notation \"%s\" not understood", key
.c_str());
290 bool Requirement::Interpreter::certFieldGeneric(const string
&key
, const Match
&match
, SecCertificateRef cert
)
292 // the key is actually a (binary) OID value
293 CssmOid
oid((char *)key
.data(), key
.length());
294 return certFieldGeneric(oid
, match
, cert
);
297 bool Requirement::Interpreter::certFieldGeneric(const CssmOid
&oid
, const Match
&match
, SecCertificateRef cert
)
299 return cert
&& certificateHasField(cert
, oid
) && match(kCFBooleanTrue
);
302 bool Requirement::Interpreter::certFieldPolicy(const string
&key
, const Match
&match
, SecCertificateRef cert
)
304 // the key is actually a (binary) OID value
305 CssmOid
oid((char *)key
.data(), key
.length());
306 return certFieldPolicy(oid
, match
, cert
);
309 bool Requirement::Interpreter::certFieldPolicy(const CssmOid
&oid
, const Match
&match
, SecCertificateRef cert
)
311 return cert
&& certificateHasPolicy(cert
, oid
) && match(kCFBooleanTrue
);
316 // Check the Apple-signed condition
318 bool Requirement::Interpreter::appleAnchored()
320 if (SecCertificateRef cert
= mContext
->cert(anchorCert
))
326 static CFStringRef kAMFINVRAMTrustedKeys
= CFSTR("AMFITrustedKeys");
328 CFArrayRef
Requirement::Interpreter::getAdditionalTrustedAnchors()
330 __block CFRef
<CFMutableArrayRef
> keys
= makeCFMutableArray(0);
333 io_registry_entry_t entry
= IORegistryEntryFromPath(kIOMasterPortDefault
, "IODeviceTree:/options");
334 if (entry
== IO_OBJECT_NULL
)
337 CFRef
<CFDataRef
> configData
= (CFDataRef
)IORegistryEntryCreateCFProperty(entry
, kAMFINVRAMTrustedKeys
, kCFAllocatorDefault
, 0);
338 IOObjectRelease(entry
);
342 CFRef
<CFDictionaryRef
> configDict
= CFDictionaryRef(IOCFUnserializeWithSize((const char *)CFDataGetBytePtr(configData
),
343 (size_t)CFDataGetLength(configData
),
344 kCFAllocatorDefault
, 0, NULL
));
348 CFArrayRef trustedKeys
= CFArrayRef(CFDictionaryGetValue(configDict
, CFSTR("trustedKeys")));
349 if (!trustedKeys
&& CFGetTypeID(trustedKeys
) != CFArrayGetTypeID())
352 cfArrayApplyBlock(trustedKeys
, ^(const void *value
) {
353 CFDictionaryRef key
= CFDictionaryRef(value
);
354 if (!key
&& CFGetTypeID(key
) != CFDictionaryGetTypeID())
357 CFDataRef hash
= CFDataRef(CFDictionaryGetValue(key
, CFSTR("certDigest")));
358 if (!hash
&& CFGetTypeID(hash
) != CFDataGetTypeID())
360 CFArrayAppendValue(keys
, hash
);
366 if (CFArrayGetCount(keys
) == 0)
372 bool Requirement::Interpreter::appleLocalAnchored()
374 static CFArrayRef additionalTrustedCertificates
= NULL
;
376 if (csr_check(CSR_ALLOW_APPLE_INTERNAL
))
379 if (mContext
->forcePlatform
) {
383 static dispatch_once_t onceToken
;
384 dispatch_once(&onceToken
, ^{
385 additionalTrustedCertificates
= getAdditionalTrustedAnchors();
388 if (additionalTrustedCertificates
== NULL
)
391 CFRef
<CFDataRef
> hash
= SecCertificateCopySHA256Digest(mContext
->cert(leafCert
));
395 if (CFArrayContainsValue(additionalTrustedCertificates
, CFRangeMake(0, CFArrayGetCount(additionalTrustedCertificates
)), hash
))
401 bool Requirement::Interpreter::appleSigned()
403 if (appleAnchored()) {
404 if (SecCertificateRef intermed
= mContext
->cert(-2)) // first intermediate
405 // first intermediate common name match (exact)
406 if (certFieldValue("subject.CN", Match(appleIntermediateCN
, matchEqual
), intermed
)
407 && certFieldValue("subject.O", Match(appleIntermediateO
, matchEqual
), intermed
))
409 } else if (appleLocalAnchored()) {
417 // Verify an anchor requirement against the context
419 bool Requirement::Interpreter::verifyAnchor(SecCertificateRef cert
, const unsigned char *digest
)
421 // get certificate bytes
426 MacOSError::check(SecCertificateGetData(cert
, &certData
));
429 hasher(certData
.Data
, certData
.Length
);
431 hasher(SecCertificateGetBytePtr(cert
), SecCertificateGetLength(cert
));
433 return hasher
.verify(digest
);
440 // Check one or all certificate(s) in the cert chain against the Trust Settings database.
442 bool Requirement::Interpreter::trustedCerts()
444 int anchor
= mContext
->certCount() - 1;
445 for (int slot
= 0; slot
<= anchor
; slot
++)
446 if (SecCertificateRef cert
= mContext
->cert(slot
))
447 switch (trustSetting(cert
, slot
== anchor
)) {
448 case kSecTrustSettingsResultTrustRoot
:
449 case kSecTrustSettingsResultTrustAsRoot
:
451 case kSecTrustSettingsResultDeny
:
453 case kSecTrustSettingsResultUnspecified
:
464 bool Requirement::Interpreter::trustedCert(int slot
)
466 if (SecCertificateRef cert
= mContext
->cert(slot
)) {
467 int anchorSlot
= mContext
->certCount() - 1;
468 switch (trustSetting(cert
, slot
== anchorCert
|| slot
== anchorSlot
)) {
469 case kSecTrustSettingsResultTrustRoot
:
470 case kSecTrustSettingsResultTrustAsRoot
:
472 case kSecTrustSettingsResultDeny
:
473 case kSecTrustSettingsResultUnspecified
:
485 // Explicitly check one certificate against the Trust Settings database and report
486 // the findings. This is a helper for the various Trust Settings evaluators.
488 SecTrustSettingsResult
Requirement::Interpreter::trustSetting(SecCertificateRef cert
, bool isAnchor
)
490 // XXX: Not supported on embedded yet due to lack of supporting API
492 // the SPI input is the uppercase hex form of the SHA-1 of the certificate...
495 hashOfCertificate(cert
, digest
);
496 string Certhex
= CssmData(digest
, sizeof(digest
)).toHex();
497 for (string::iterator it
= Certhex
.begin(); it
!= Certhex
.end(); ++it
)
501 // call Trust Settings and see what it finds
502 SecTrustSettingsDomain domain
;
503 SecTrustSettingsResult result
;
504 CSSM_RETURN
*errors
= NULL
;
505 uint32 errorCount
= 0;
506 bool foundMatch
, foundAny
;
507 switch (OSStatus rc
= SecTrustSettingsEvaluateCert(
508 CFTempString(Certhex
), // settings index
509 &CSSMOID_APPLE_TP_CODE_SIGNING
, // standard code signing policy
510 NULL
, 0, // policy string (unused)
511 kSecTrustSettingsKeyUseAny
, // no restriction on key usage @@@
512 isAnchor
, // consult system default anchor set
514 &domain
, // domain of found setting
515 &errors
, &errorCount
, // error set and maximum count
516 &result
, // the actual setting
517 &foundMatch
, &foundAny
// optimization hints (not used)
524 return kSecTrustSettingsResultUnspecified
;
527 MacOSError::throwMe(rc
);
530 return kSecTrustSettingsResultUnspecified
;
536 // Create a Match object from the interpreter stream
538 Requirement::Interpreter::Match::Match(Interpreter
&interp
)
540 switch (mOp
= interp
.get
<MatchOperation
>()) {
545 case matchBeginsWith
:
548 case matchGreaterThan
:
550 case matchGreaterEqual
:
551 mValue
.take(makeCFString(interp
.getString()));
554 // Assume this (unknown) match type has a single data argument.
555 // This gives us a chance to keep the instruction stream aligned.
556 interp
.getString(); // discard
563 // Execute a match against a candidate value
565 bool Requirement::Interpreter::Match::operator () (CFTypeRef candidate
) const
567 // null candidates always fail
571 // interpret an array as matching alternatives (any one succeeds)
572 if (CFGetTypeID(candidate
) == CFArrayGetTypeID()) {
573 CFArrayRef array
= CFArrayRef(candidate
);
574 CFIndex count
= CFArrayGetCount(array
);
575 for (CFIndex n
= 0; n
< count
; n
++)
576 if ((*this)(CFArrayGetValueAtIndex(array
, n
))) // yes, it's recursive
581 case matchExists
: // anything but NULL and boolean false "exists"
582 return !CFEqual(candidate
, kCFBooleanFalse
);
583 case matchEqual
: // equality works for all CF types
584 return CFEqual(candidate
, mValue
);
586 if (CFGetTypeID(candidate
) == CFStringGetTypeID()) {
587 CFStringRef value
= CFStringRef(candidate
);
588 if (CFStringFindWithOptions(value
, mValue
, CFRangeMake(0, CFStringGetLength(value
)), 0, NULL
))
592 case matchBeginsWith
:
593 if (CFGetTypeID(candidate
) == CFStringGetTypeID()) {
594 CFStringRef value
= CFStringRef(candidate
);
595 if (CFStringFindWithOptions(value
, mValue
, CFRangeMake(0, CFStringGetLength(mValue
)), 0, NULL
))
600 if (CFGetTypeID(candidate
) == CFStringGetTypeID()) {
601 CFStringRef value
= CFStringRef(candidate
);
602 CFIndex matchLength
= CFStringGetLength(mValue
);
603 CFIndex start
= CFStringGetLength(value
) - matchLength
;
605 if (CFStringFindWithOptions(value
, mValue
, CFRangeMake(start
, matchLength
), 0, NULL
))
610 return inequality(candidate
, kCFCompareNumerically
, kCFCompareLessThan
, true);
611 case matchGreaterThan
:
612 return inequality(candidate
, kCFCompareNumerically
, kCFCompareGreaterThan
, true);
614 return inequality(candidate
, kCFCompareNumerically
, kCFCompareGreaterThan
, false);
615 case matchGreaterEqual
:
616 return inequality(candidate
, kCFCompareNumerically
, kCFCompareLessThan
, false);
618 // unrecognized match types can never match
624 bool Requirement::Interpreter::Match::inequality(CFTypeRef candidate
, CFStringCompareFlags flags
,
625 CFComparisonResult outcome
, bool negate
) const
627 if (CFGetTypeID(candidate
) == CFStringGetTypeID()) {
628 CFStringRef value
= CFStringRef(candidate
);
629 if ((CFStringCompare(value
, mValue
, flags
) == outcome
) == negate
)
637 // External fragments
639 Fragments::Fragments()
641 mMyBundle
= CFBundleGetBundleWithIdentifier(CFSTR("com.apple.security"));
645 bool Fragments::evalNamed(const char *type
, const std::string
&name
, const Requirement::Context
&ctx
)
647 if (CFDataRef fragData
= fragment(type
, name
)) {
648 const Requirement
*req
= (const Requirement
*)CFDataGetBytePtr(fragData
); // was prevalidated as Requirement
649 return req
->validates(ctx
);
655 CFDataRef
Fragments::fragment(const char *type
, const std::string
&name
)
657 string key
= name
+ "!!" + type
; // compound key
658 StLock
<Mutex
> _(mLock
); // lock for cache access
659 FragMap::const_iterator it
= mFragments
.find(key
);
660 if (it
== mFragments
.end()) {
661 CFRef
<CFDataRef
> fragData
; // will always be set (NULL on any errors)
662 if (CFRef
<CFURLRef
> fragURL
= CFBundleCopyResourceURL(mMyBundle
, CFTempString(name
), CFSTR("csreq"), CFTempString(type
)))
663 if (CFRef
<CFDataRef
> data
= cfLoadFile(fragURL
)) { // got data
664 const Requirement
*req
= (const Requirement
*)CFDataGetBytePtr(data
);
665 if (req
->validateBlob(CFDataGetLength(data
))) // looks like a Requirement...
666 fragData
= data
; // ... so accept it
668 Syslog::warning("Invalid sub-requirement at %s", cfString(fragURL
).c_str());
670 if (CODESIGN_EVAL_REQINT_FRAGMENT_LOAD_ENABLED())
671 CODESIGN_EVAL_REQINT_FRAGMENT_LOAD(type
, name
.c_str(), fragData
? CFDataGetBytePtr(fragData
) : NULL
);
672 mFragments
[key
] = fragData
; // cache it, success or failure
675 CODESIGN_EVAL_REQINT_FRAGMENT_HIT(type
, name
.c_str());