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