]>
Commit | Line | Data |
---|---|---|
b1ab9ed8 | 1 | /* |
d8f41ccd | 2 | * Copyright (c) 2006,2011-2014 Apple Inc. All Rights Reserved. |
b1ab9ed8 A |
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 | // reqinterp - Requirement language (exprOp) interpreter | |
26 | // | |
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> | |
e3d460c9 A |
33 | #include <sys/csr.h> |
34 | #include <IOKit/IOKitLib.h> | |
35 | #include <IOKit/IOCFUnserialize.h> | |
b1ab9ed8 A |
36 | #include "csutilities.h" |
37 | ||
38 | namespace Security { | |
39 | namespace CodeSigning { | |
40 | ||
41 | ||
42 | // | |
43 | // Fragment fetching, caching, and evaluation. | |
44 | // | |
45 | // Several language elements allow "calling" of separate requirement programs | |
46 | // stored on disk as (binary) requirement blobs. The Fragments class takes care | |
47 | // of finding, loading, caching, and evaluating them. | |
48 | // | |
49 | // This is a singleton for (process global) caching. It works fine as multiple instances, | |
50 | // at a loss of caching effectiveness. | |
51 | // | |
52 | class Fragments { | |
53 | public: | |
54 | Fragments(); | |
55 | ||
56 | bool named(const std::string &name, const Requirement::Context &ctx) | |
57 | { return evalNamed("subreq", name, ctx); } | |
58 | bool namedAnchor(const std::string &name, const Requirement::Context &ctx) | |
59 | { return evalNamed("anchorreq", name, ctx); } | |
60 | ||
61 | private: | |
62 | bool evalNamed(const char *type, const std::string &name, const Requirement::Context &ctx); | |
63 | CFDataRef fragment(const char *type, const std::string &name); | |
64 | ||
65 | typedef std::map<std::string, CFRef<CFDataRef> > FragMap; | |
66 | ||
67 | private: | |
68 | CFBundleRef mMyBundle; // Security.framework bundle | |
69 | Mutex mLock; // lock for all of the below... | |
70 | FragMap mFragments; // cached fragments | |
71 | }; | |
72 | ||
73 | static ModuleNexus<Fragments> fragments; | |
74 | ||
75 | ||
76 | // | |
77 | // Magic certificate features | |
78 | // | |
79 | static CFStringRef appleIntermediateCN = CFSTR("Apple Code Signing Certification Authority"); | |
80 | static CFStringRef appleIntermediateO = CFSTR("Apple Inc."); | |
81 | ||
82 | ||
83 | // | |
84 | // Main interpreter function. | |
85 | // | |
86 | // ExprOp code is in Polish Notation (operator followed by operands), | |
87 | // and this engine uses opportunistic evaluation. | |
88 | // | |
89 | bool Requirement::Interpreter::evaluate() | |
5c19dc3a A |
90 | { return eval(stackLimit); } |
91 | ||
92 | bool Requirement::Interpreter::eval(int depth) | |
b1ab9ed8 | 93 | { |
5c19dc3a A |
94 | if (--depth <= 0) // nested too deeply - protect the stack |
95 | MacOSError::throwMe(errSecCSReqInvalid); | |
96 | ||
b1ab9ed8 A |
97 | ExprOp op = ExprOp(get<uint32_t>()); |
98 | CODESIGN_EVAL_REQINT_OP(op, this->pc() - sizeof(uint32_t)); | |
99 | switch (op & ~opFlagMask) { | |
100 | case opFalse: | |
101 | return false; | |
102 | case opTrue: | |
103 | return true; | |
104 | case opIdent: | |
105 | return mContext->directory && getString() == mContext->directory->identifier(); | |
106 | case opAppleAnchor: | |
107 | return appleSigned(); | |
108 | case opAppleGenericAnchor: | |
109 | return appleAnchored(); | |
110 | case opAnchorHash: | |
111 | { | |
112 | SecCertificateRef cert = mContext->cert(get<int32_t>()); | |
113 | return verifyAnchor(cert, getSHA1()); | |
114 | } | |
115 | case opInfoKeyValue: // [legacy; use opInfoKeyField] | |
116 | { | |
117 | string key = getString(); | |
118 | return infoKeyValue(key, Match(CFTempString(getString()), matchEqual)); | |
119 | } | |
120 | case opAnd: | |
5c19dc3a | 121 | return eval(depth) & eval(depth); |
b1ab9ed8 | 122 | case opOr: |
5c19dc3a | 123 | return eval(depth) | eval(depth); |
b1ab9ed8 A |
124 | case opCDHash: |
125 | if (mContext->directory) { | |
5c19dc3a A |
126 | CFRef<CFDataRef> cdhash = mContext->directory->cdhash(); |
127 | CFRef<CFDataRef> required = getHash(); | |
128 | return CFEqual(cdhash, required); | |
b1ab9ed8 A |
129 | } else |
130 | return false; | |
131 | case opNot: | |
5c19dc3a | 132 | return !eval(depth); |
b1ab9ed8 A |
133 | case opInfoKeyField: |
134 | { | |
135 | string key = getString(); | |
136 | Match match(*this); | |
137 | return infoKeyValue(key, match); | |
138 | } | |
139 | case opEntitlementField: | |
140 | { | |
141 | string key = getString(); | |
142 | Match match(*this); | |
143 | return entitlementValue(key, match); | |
144 | } | |
145 | case opCertField: | |
146 | { | |
147 | SecCertificateRef cert = mContext->cert(get<int32_t>()); | |
148 | string key = getString(); | |
149 | Match match(*this); | |
150 | return certFieldValue(key, match, cert); | |
151 | } | |
866f8763 | 152 | #if TARGET_OS_OSX |
b1ab9ed8 A |
153 | case opCertGeneric: |
154 | { | |
155 | SecCertificateRef cert = mContext->cert(get<int32_t>()); | |
156 | string key = getString(); | |
157 | Match match(*this); | |
158 | return certFieldGeneric(key, match, cert); | |
159 | } | |
160 | case opCertPolicy: | |
161 | { | |
162 | SecCertificateRef cert = mContext->cert(get<int32_t>()); | |
163 | string key = getString(); | |
164 | Match match(*this); | |
165 | return certFieldPolicy(key, match, cert); | |
166 | } | |
866f8763 | 167 | #endif |
b1ab9ed8 A |
168 | case opTrustedCert: |
169 | return trustedCert(get<int32_t>()); | |
170 | case opTrustedCerts: | |
171 | return trustedCerts(); | |
172 | case opNamedAnchor: | |
173 | return fragments().namedAnchor(getString(), *mContext); | |
174 | case opNamedCode: | |
175 | return fragments().named(getString(), *mContext); | |
5c19dc3a A |
176 | case opPlatform: |
177 | { | |
178 | int32_t targetPlatform = get<int32_t>(); | |
179 | return mContext->directory && mContext->directory->platform == targetPlatform; | |
180 | } | |
b1ab9ed8 A |
181 | default: |
182 | // opcode not recognized - handle generically if possible, fail otherwise | |
183 | if (op & (opGenericFalse | opGenericSkip)) { | |
184 | // unknown opcode, but it has a size field and can be safely bypassed | |
185 | skip(get<uint32_t>()); | |
186 | if (op & opGenericFalse) { | |
187 | CODESIGN_EVAL_REQINT_UNKNOWN_FALSE(op); | |
188 | return false; | |
189 | } else { | |
190 | CODESIGN_EVAL_REQINT_UNKNOWN_SKIPPED(op); | |
5c19dc3a | 191 | return eval(depth); |
b1ab9ed8 A |
192 | } |
193 | } | |
194 | // unrecognized opcode and no way to interpret it | |
fa7225c8 | 195 | secinfo("csinterp", "opcode 0x%x cannot be handled; aborting", op); |
b1ab9ed8 A |
196 | MacOSError::throwMe(errSecCSUnimplemented); |
197 | } | |
198 | } | |
199 | ||
200 | ||
201 | // | |
202 | // Evaluate an Info.plist key condition | |
203 | // | |
204 | bool Requirement::Interpreter::infoKeyValue(const string &key, const Match &match) | |
205 | { | |
206 | if (mContext->info) // we have an Info.plist | |
207 | if (CFTypeRef value = CFDictionaryGetValue(mContext->info, CFTempString(key))) | |
208 | return match(value); | |
209 | return false; | |
210 | } | |
211 | ||
212 | ||
213 | // | |
214 | // Evaluate an entitlement condition | |
215 | // | |
216 | bool Requirement::Interpreter::entitlementValue(const string &key, const Match &match) | |
217 | { | |
218 | if (mContext->entitlements) // we have an Info.plist | |
219 | if (CFTypeRef value = CFDictionaryGetValue(mContext->entitlements, CFTempString(key))) | |
220 | return match(value); | |
221 | return false; | |
222 | } | |
223 | ||
224 | ||
225 | bool Requirement::Interpreter::certFieldValue(const string &key, const Match &match, SecCertificateRef cert) | |
226 | { | |
866f8763 A |
227 | // XXX: Not supported on embedded yet due to lack of supporting API |
228 | #if TARGET_OS_OSX | |
b1ab9ed8 A |
229 | // no cert, no chance |
230 | if (cert == NULL) | |
231 | return false; | |
232 | ||
233 | // a table of recognized keys for the "certificate[foo]" syntax | |
234 | static const struct CertField { | |
235 | const char *name; | |
236 | const CSSM_OID *oid; | |
237 | } certFields[] = { | |
238 | { "subject.C", &CSSMOID_CountryName }, | |
239 | { "subject.CN", &CSSMOID_CommonName }, | |
240 | { "subject.D", &CSSMOID_Description }, | |
241 | { "subject.L", &CSSMOID_LocalityName }, | |
242 | // { "subject.C-L", &CSSMOID_CollectiveLocalityName }, // missing from Security.framework headers | |
243 | { "subject.O", &CSSMOID_OrganizationName }, | |
244 | { "subject.C-O", &CSSMOID_CollectiveOrganizationName }, | |
245 | { "subject.OU", &CSSMOID_OrganizationalUnitName }, | |
246 | { "subject.C-OU", &CSSMOID_CollectiveOrganizationalUnitName }, | |
247 | { "subject.ST", &CSSMOID_StateProvinceName }, | |
248 | { "subject.C-ST", &CSSMOID_CollectiveStateProvinceName }, | |
249 | { "subject.STREET", &CSSMOID_StreetAddress }, | |
250 | { "subject.C-STREET", &CSSMOID_CollectiveStreetAddress }, | |
251 | { "subject.UID", &CSSMOID_UserID }, | |
252 | { NULL, NULL } | |
253 | }; | |
fa7225c8 | 254 | |
b1ab9ed8 A |
255 | // DN-component single-value match |
256 | for (const CertField *cf = certFields; cf->name; cf++) | |
257 | if (cf->name == key) { | |
258 | CFRef<CFStringRef> value; | |
fa7225c8 A |
259 | OSStatus rc = SecCertificateCopySubjectComponent(cert, cf->oid, &value.aref()); |
260 | if (rc) { | |
261 | secinfo("csinterp", "cert %p lookup for DN.%s failed rc=%d", cert, key.c_str(), (int)rc); | |
b1ab9ed8 A |
262 | return false; |
263 | } | |
264 | return match(value); | |
265 | } | |
266 | ||
267 | // email multi-valued match (any of...) | |
268 | if (key == "email") { | |
269 | CFRef<CFArrayRef> value; | |
fa7225c8 A |
270 | OSStatus rc = SecCertificateCopyEmailAddresses(cert, &value.aref()); |
271 | if (rc) { | |
272 | secinfo("csinterp", "cert %p lookup for email failed rc=%d", cert, (int)rc); | |
b1ab9ed8 A |
273 | return false; |
274 | } | |
275 | return match(value); | |
276 | } | |
277 | ||
278 | // unrecognized key. Fail but do not abort to promote backward compatibility down the road | |
fa7225c8 | 279 | secinfo("csinterp", "cert field notation \"%s\" not understood", key.c_str()); |
866f8763 | 280 | #endif |
b1ab9ed8 A |
281 | return false; |
282 | } | |
283 | ||
866f8763 | 284 | #if TARGET_OS_OSX |
b1ab9ed8 A |
285 | bool Requirement::Interpreter::certFieldGeneric(const string &key, const Match &match, SecCertificateRef cert) |
286 | { | |
287 | // the key is actually a (binary) OID value | |
288 | CssmOid oid((char *)key.data(), key.length()); | |
289 | return certFieldGeneric(oid, match, cert); | |
290 | } | |
291 | ||
292 | bool Requirement::Interpreter::certFieldGeneric(const CssmOid &oid, const Match &match, SecCertificateRef cert) | |
293 | { | |
294 | return cert && certificateHasField(cert, oid) && match(kCFBooleanTrue); | |
295 | } | |
296 | ||
297 | bool Requirement::Interpreter::certFieldPolicy(const string &key, const Match &match, SecCertificateRef cert) | |
298 | { | |
299 | // the key is actually a (binary) OID value | |
300 | CssmOid oid((char *)key.data(), key.length()); | |
301 | return certFieldPolicy(oid, match, cert); | |
302 | } | |
303 | ||
304 | bool Requirement::Interpreter::certFieldPolicy(const CssmOid &oid, const Match &match, SecCertificateRef cert) | |
305 | { | |
306 | return cert && certificateHasPolicy(cert, oid) && match(kCFBooleanTrue); | |
307 | } | |
866f8763 | 308 | #endif |
b1ab9ed8 A |
309 | |
310 | // | |
311 | // Check the Apple-signed condition | |
312 | // | |
313 | bool Requirement::Interpreter::appleAnchored() | |
314 | { | |
315 | if (SecCertificateRef cert = mContext->cert(anchorCert)) | |
e3d460c9 | 316 | if (isAppleCA(cert)) |
b1ab9ed8 A |
317 | return true; |
318 | return false; | |
319 | } | |
320 | ||
e3d460c9 A |
321 | static CFStringRef kAMFINVRAMTrustedKeys = CFSTR("AMFITrustedKeys"); |
322 | ||
323 | CFArrayRef Requirement::Interpreter::getAdditionalTrustedAnchors() | |
324 | { | |
325 | __block CFRef<CFMutableArrayRef> keys = makeCFMutableArray(0); | |
326 | ||
327 | try { | |
328 | io_registry_entry_t entry = IORegistryEntryFromPath(kIOMasterPortDefault, "IODeviceTree:/options"); | |
329 | if (entry == IO_OBJECT_NULL) | |
330 | return NULL; | |
331 | ||
332 | CFRef<CFDataRef> configData = (CFDataRef)IORegistryEntryCreateCFProperty(entry, kAMFINVRAMTrustedKeys, kCFAllocatorDefault, 0); | |
333 | IOObjectRelease(entry); | |
334 | if (!configData) | |
335 | return NULL; | |
336 | ||
fa7225c8 A |
337 | CFRef<CFDictionaryRef> configDict = CFDictionaryRef(IOCFUnserializeWithSize((const char *)CFDataGetBytePtr(configData), |
338 | (size_t)CFDataGetLength(configData), | |
339 | kCFAllocatorDefault, 0, NULL)); | |
e3d460c9 A |
340 | if (!configDict) |
341 | return NULL; | |
342 | ||
343 | CFArrayRef trustedKeys = CFArrayRef(CFDictionaryGetValue(configDict, CFSTR("trustedKeys"))); | |
344 | if (!trustedKeys && CFGetTypeID(trustedKeys) != CFArrayGetTypeID()) | |
345 | return NULL; | |
346 | ||
347 | cfArrayApplyBlock(trustedKeys, ^(const void *value) { | |
348 | CFDictionaryRef key = CFDictionaryRef(value); | |
349 | if (!key && CFGetTypeID(key) != CFDictionaryGetTypeID()) | |
350 | return; | |
351 | ||
352 | CFDataRef hash = CFDataRef(CFDictionaryGetValue(key, CFSTR("certDigest"))); | |
353 | if (!hash && CFGetTypeID(hash) != CFDataGetTypeID()) | |
354 | return; | |
355 | CFArrayAppendValue(keys, hash); | |
356 | }); | |
357 | ||
358 | } catch (...) { | |
359 | } | |
360 | ||
361 | if (CFArrayGetCount(keys) == 0) | |
362 | return NULL; | |
363 | ||
364 | return keys.yield(); | |
365 | } | |
366 | ||
367 | bool Requirement::Interpreter::appleLocalAnchored() | |
368 | { | |
369 | static CFArrayRef additionalTrustedCertificates = NULL; | |
370 | ||
371 | if (csr_check(CSR_ALLOW_APPLE_INTERNAL)) | |
372 | return false; | |
373 | ||
374 | static dispatch_once_t onceToken; | |
375 | dispatch_once(&onceToken, ^{ | |
376 | additionalTrustedCertificates = getAdditionalTrustedAnchors(); | |
377 | }); | |
378 | ||
379 | if (additionalTrustedCertificates == NULL) | |
380 | return false; | |
381 | ||
382 | CFRef<CFDataRef> hash = SecCertificateCopySHA256Digest(mContext->cert(leafCert)); | |
383 | if (!hash) | |
384 | return false; | |
385 | ||
386 | if (CFArrayContainsValue(additionalTrustedCertificates, CFRangeMake(0, CFArrayGetCount(additionalTrustedCertificates)), hash)) | |
387 | return true; | |
388 | ||
389 | return false; | |
390 | } | |
391 | ||
b1ab9ed8 A |
392 | bool Requirement::Interpreter::appleSigned() |
393 | { | |
e3d460c9 | 394 | if (appleAnchored()) { |
b1ab9ed8 A |
395 | if (SecCertificateRef intermed = mContext->cert(-2)) // first intermediate |
396 | // first intermediate common name match (exact) | |
397 | if (certFieldValue("subject.CN", Match(appleIntermediateCN, matchEqual), intermed) | |
398 | && certFieldValue("subject.O", Match(appleIntermediateO, matchEqual), intermed)) | |
399 | return true; | |
e3d460c9 A |
400 | } else if (appleLocalAnchored()) { |
401 | return true; | |
402 | } | |
b1ab9ed8 A |
403 | return false; |
404 | } | |
405 | ||
406 | ||
407 | // | |
408 | // Verify an anchor requirement against the context | |
409 | // | |
410 | bool Requirement::Interpreter::verifyAnchor(SecCertificateRef cert, const unsigned char *digest) | |
411 | { | |
412 | // get certificate bytes | |
413 | if (cert) { | |
866f8763 A |
414 | SHA1 hasher; |
415 | #if TARGET_OS_OSX | |
b1ab9ed8 A |
416 | CSSM_DATA certData; |
417 | MacOSError::check(SecCertificateGetData(cert, &certData)); | |
418 | ||
419 | // verify hash | |
b1ab9ed8 | 420 | hasher(certData.Data, certData.Length); |
866f8763 A |
421 | #else |
422 | hasher(SecCertificateGetBytePtr(cert), SecCertificateGetLength(cert)); | |
423 | #endif | |
b1ab9ed8 A |
424 | return hasher.verify(digest); |
425 | } | |
426 | return false; | |
427 | } | |
428 | ||
429 | ||
430 | // | |
431 | // Check one or all certificate(s) in the cert chain against the Trust Settings database. | |
432 | // | |
433 | bool Requirement::Interpreter::trustedCerts() | |
434 | { | |
435 | int anchor = mContext->certCount() - 1; | |
436 | for (int slot = 0; slot <= anchor; slot++) | |
437 | if (SecCertificateRef cert = mContext->cert(slot)) | |
438 | switch (trustSetting(cert, slot == anchor)) { | |
439 | case kSecTrustSettingsResultTrustRoot: | |
440 | case kSecTrustSettingsResultTrustAsRoot: | |
441 | return true; | |
442 | case kSecTrustSettingsResultDeny: | |
443 | return false; | |
444 | case kSecTrustSettingsResultUnspecified: | |
445 | break; | |
446 | default: | |
447 | assert(false); | |
448 | return false; | |
449 | } | |
450 | else | |
451 | return false; | |
452 | return false; | |
453 | } | |
454 | ||
455 | bool Requirement::Interpreter::trustedCert(int slot) | |
456 | { | |
457 | if (SecCertificateRef cert = mContext->cert(slot)) { | |
458 | int anchorSlot = mContext->certCount() - 1; | |
459 | switch (trustSetting(cert, slot == anchorCert || slot == anchorSlot)) { | |
460 | case kSecTrustSettingsResultTrustRoot: | |
461 | case kSecTrustSettingsResultTrustAsRoot: | |
462 | return true; | |
463 | case kSecTrustSettingsResultDeny: | |
464 | case kSecTrustSettingsResultUnspecified: | |
465 | return false; | |
466 | default: | |
467 | assert(false); | |
468 | return false; | |
469 | } | |
470 | } else | |
471 | return false; | |
472 | } | |
473 | ||
474 | ||
475 | // | |
476 | // Explicitly check one certificate against the Trust Settings database and report | |
477 | // the findings. This is a helper for the various Trust Settings evaluators. | |
478 | // | |
479 | SecTrustSettingsResult Requirement::Interpreter::trustSetting(SecCertificateRef cert, bool isAnchor) | |
480 | { | |
866f8763 A |
481 | // XXX: Not supported on embedded yet due to lack of supporting API |
482 | #if TARGET_OS_OSX | |
b1ab9ed8 A |
483 | // the SPI input is the uppercase hex form of the SHA-1 of the certificate... |
484 | assert(cert); | |
485 | SHA1::Digest digest; | |
486 | hashOfCertificate(cert, digest); | |
487 | string Certhex = CssmData(digest, sizeof(digest)).toHex(); | |
488 | for (string::iterator it = Certhex.begin(); it != Certhex.end(); ++it) | |
489 | if (islower(*it)) | |
490 | *it = toupper(*it); | |
491 | ||
492 | // call Trust Settings and see what it finds | |
493 | SecTrustSettingsDomain domain; | |
494 | SecTrustSettingsResult result; | |
495 | CSSM_RETURN *errors = NULL; | |
496 | uint32 errorCount = 0; | |
497 | bool foundMatch, foundAny; | |
498 | switch (OSStatus rc = SecTrustSettingsEvaluateCert( | |
499 | CFTempString(Certhex), // settings index | |
500 | &CSSMOID_APPLE_TP_CODE_SIGNING, // standard code signing policy | |
501 | NULL, 0, // policy string (unused) | |
502 | kSecTrustSettingsKeyUseAny, // no restriction on key usage @@@ | |
503 | isAnchor, // consult system default anchor set | |
504 | ||
505 | &domain, // domain of found setting | |
506 | &errors, &errorCount, // error set and maximum count | |
507 | &result, // the actual setting | |
508 | &foundMatch, &foundAny // optimization hints (not used) | |
509 | )) { | |
427c49bc | 510 | case errSecSuccess: |
b1ab9ed8 A |
511 | ::free(errors); |
512 | if (foundMatch) | |
513 | return result; | |
514 | else | |
515 | return kSecTrustSettingsResultUnspecified; | |
516 | default: | |
517 | ::free(errors); | |
518 | MacOSError::throwMe(rc); | |
519 | } | |
866f8763 A |
520 | #else |
521 | return kSecTrustSettingsResultUnspecified; | |
522 | #endif | |
b1ab9ed8 A |
523 | } |
524 | ||
525 | ||
526 | // | |
527 | // Create a Match object from the interpreter stream | |
528 | // | |
529 | Requirement::Interpreter::Match::Match(Interpreter &interp) | |
530 | { | |
531 | switch (mOp = interp.get<MatchOperation>()) { | |
532 | case matchExists: | |
533 | break; | |
534 | case matchEqual: | |
535 | case matchContains: | |
536 | case matchBeginsWith: | |
537 | case matchEndsWith: | |
538 | case matchLessThan: | |
539 | case matchGreaterThan: | |
540 | case matchLessEqual: | |
541 | case matchGreaterEqual: | |
542 | mValue.take(makeCFString(interp.getString())); | |
543 | break; | |
544 | default: | |
545 | // Assume this (unknown) match type has a single data argument. | |
546 | // This gives us a chance to keep the instruction stream aligned. | |
547 | interp.getString(); // discard | |
548 | break; | |
549 | } | |
550 | } | |
551 | ||
552 | ||
553 | // | |
554 | // Execute a match against a candidate value | |
555 | // | |
556 | bool Requirement::Interpreter::Match::operator () (CFTypeRef candidate) const | |
557 | { | |
558 | // null candidates always fail | |
559 | if (!candidate) | |
560 | return false; | |
561 | ||
562 | // interpret an array as matching alternatives (any one succeeds) | |
563 | if (CFGetTypeID(candidate) == CFArrayGetTypeID()) { | |
564 | CFArrayRef array = CFArrayRef(candidate); | |
565 | CFIndex count = CFArrayGetCount(array); | |
566 | for (CFIndex n = 0; n < count; n++) | |
567 | if ((*this)(CFArrayGetValueAtIndex(array, n))) // yes, it's recursive | |
568 | return true; | |
569 | } | |
570 | ||
571 | switch (mOp) { | |
572 | case matchExists: // anything but NULL and boolean false "exists" | |
573 | return !CFEqual(candidate, kCFBooleanFalse); | |
574 | case matchEqual: // equality works for all CF types | |
575 | return CFEqual(candidate, mValue); | |
576 | case matchContains: | |
577 | if (CFGetTypeID(candidate) == CFStringGetTypeID()) { | |
578 | CFStringRef value = CFStringRef(candidate); | |
579 | if (CFStringFindWithOptions(value, mValue, CFRangeMake(0, CFStringGetLength(value)), 0, NULL)) | |
580 | return true; | |
581 | } | |
582 | return false; | |
583 | case matchBeginsWith: | |
584 | if (CFGetTypeID(candidate) == CFStringGetTypeID()) { | |
585 | CFStringRef value = CFStringRef(candidate); | |
586 | if (CFStringFindWithOptions(value, mValue, CFRangeMake(0, CFStringGetLength(mValue)), 0, NULL)) | |
587 | return true; | |
588 | } | |
589 | return false; | |
590 | case matchEndsWith: | |
591 | if (CFGetTypeID(candidate) == CFStringGetTypeID()) { | |
592 | CFStringRef value = CFStringRef(candidate); | |
593 | CFIndex matchLength = CFStringGetLength(mValue); | |
594 | CFIndex start = CFStringGetLength(value) - matchLength; | |
595 | if (start >= 0) | |
596 | if (CFStringFindWithOptions(value, mValue, CFRangeMake(start, matchLength), 0, NULL)) | |
597 | return true; | |
598 | } | |
599 | return false; | |
600 | case matchLessThan: | |
601 | return inequality(candidate, kCFCompareNumerically, kCFCompareLessThan, true); | |
602 | case matchGreaterThan: | |
603 | return inequality(candidate, kCFCompareNumerically, kCFCompareGreaterThan, true); | |
604 | case matchLessEqual: | |
605 | return inequality(candidate, kCFCompareNumerically, kCFCompareGreaterThan, false); | |
606 | case matchGreaterEqual: | |
607 | return inequality(candidate, kCFCompareNumerically, kCFCompareLessThan, false); | |
608 | default: | |
609 | // unrecognized match types can never match | |
610 | return false; | |
611 | } | |
612 | } | |
613 | ||
614 | ||
615 | bool Requirement::Interpreter::Match::inequality(CFTypeRef candidate, CFStringCompareFlags flags, | |
616 | CFComparisonResult outcome, bool negate) const | |
617 | { | |
618 | if (CFGetTypeID(candidate) == CFStringGetTypeID()) { | |
619 | CFStringRef value = CFStringRef(candidate); | |
620 | if ((CFStringCompare(value, mValue, flags) == outcome) == negate) | |
621 | return true; | |
622 | } | |
623 | return false; | |
624 | } | |
625 | ||
626 | ||
627 | // | |
628 | // External fragments | |
629 | // | |
630 | Fragments::Fragments() | |
631 | { | |
632 | mMyBundle = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.security")); | |
633 | } | |
634 | ||
635 | ||
636 | bool Fragments::evalNamed(const char *type, const std::string &name, const Requirement::Context &ctx) | |
637 | { | |
638 | if (CFDataRef fragData = fragment(type, name)) { | |
639 | const Requirement *req = (const Requirement *)CFDataGetBytePtr(fragData); // was prevalidated as Requirement | |
640 | return req->validates(ctx); | |
641 | } | |
642 | return false; | |
643 | } | |
644 | ||
645 | ||
646 | CFDataRef Fragments::fragment(const char *type, const std::string &name) | |
647 | { | |
648 | string key = name + "!!" + type; // compound key | |
649 | StLock<Mutex> _(mLock); // lock for cache access | |
650 | FragMap::const_iterator it = mFragments.find(key); | |
651 | if (it == mFragments.end()) { | |
652 | CFRef<CFDataRef> fragData; // will always be set (NULL on any errors) | |
653 | if (CFRef<CFURLRef> fragURL = CFBundleCopyResourceURL(mMyBundle, CFTempString(name), CFSTR("csreq"), CFTempString(type))) | |
654 | if (CFRef<CFDataRef> data = cfLoadFile(fragURL)) { // got data | |
655 | const Requirement *req = (const Requirement *)CFDataGetBytePtr(data); | |
656 | if (req->validateBlob(CFDataGetLength(data))) // looks like a Requirement... | |
657 | fragData = data; // ... so accept it | |
658 | else | |
659 | Syslog::warning("Invalid sub-requirement at %s", cfString(fragURL).c_str()); | |
660 | } | |
661 | if (CODESIGN_EVAL_REQINT_FRAGMENT_LOAD_ENABLED()) | |
662 | CODESIGN_EVAL_REQINT_FRAGMENT_LOAD(type, name.c_str(), fragData ? CFDataGetBytePtr(fragData) : NULL); | |
663 | mFragments[key] = fragData; // cache it, success or failure | |
664 | return fragData; | |
665 | } | |
666 | CODESIGN_EVAL_REQINT_FRAGMENT_HIT(type, name.c_str()); | |
667 | return it->second; | |
668 | } | |
669 | ||
670 | ||
671 | } // CodeSigning | |
672 | } // Security |