]> git.saurik.com Git - apple/libsecurity_codesigning.git/blame - lib/reqinterp.cpp
libsecurity_codesigning-55037.15.tar.gz
[apple/libsecurity_codesigning.git] / lib / reqinterp.cpp
CommitLineData
7d31e928
A
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"
d1c1ab47 28#include "codesigning_dtrace.h"
7d31e928
A
29#include <Security/SecTrustSettingsPriv.h>
30#include <Security/SecCertificatePriv.h>
31#include <security_utilities/memutils.h>
f60086fc 32#include <security_utilities/logging.h>
7d31e928
A
33#include "csutilities.h"
34
35namespace Security {
36namespace CodeSigning {
37
38
f60086fc
A
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//
49class Fragments {
50public:
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
58private:
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
64private:
65 CFBundleRef mMyBundle; // Security.framework bundle
66 Mutex mLock; // lock for all of the below...
67 FragMap mFragments; // cached fragments
68};
69
70static ModuleNexus<Fragments> fragments;
f6842776
A
71
72
7d31e928 73//
f60086fc 74// Magic certificate features
7d31e928 75//
f60086fc
A
76static CFStringRef appleIntermediateCN = CFSTR("Apple Code Signing Certification Authority");
77static CFStringRef appleIntermediateO = CFSTR("Apple Inc.");
7d31e928
A
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//
86bool Requirement::Interpreter::evaluate()
87{
88 ExprOp op = ExprOp(get<uint32_t>());
d1c1ab47 89 CODESIGN_EVAL_REQINT_OP(op, this->pc() - sizeof(uint32_t));
7d31e928
A
90 switch (op & ~opFlagMask) {
91 case opFalse:
92 return false;
93 case opTrue:
94 return true;
95 case opIdent:
f60086fc 96 return mContext->directory && getString() == mContext->directory->identifier();
7d31e928 97 case opAppleAnchor:
f6842776 98 return appleSigned();
516ae477
A
99 case opAppleGenericAnchor:
100 return appleAnchored();
7d31e928
A
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:
516ae477 112 return evaluate() & evaluate();
7d31e928 113 case opOr:
516ae477 114 return evaluate() | evaluate();
7d31e928 115 case opCDHash:
f60086fc 116 if (mContext->directory) {
7d31e928
A
117 SHA1 hash;
118 hash(mContext->directory, mContext->directory->length());
119 return hash.verify(getHash());
f60086fc
A
120 } else
121 return false;
7d31e928
A
122 case opNot:
123 return !evaluate();
124 case opInfoKeyField:
125 {
126 string key = getString();
127 Match match(*this);
128 return infoKeyValue(key, match);
129 }
516ae477
A
130 case opEntitlementField:
131 {
132 string key = getString();
133 Match match(*this);
134 return entitlementValue(key, match);
135 }
7d31e928
A
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 }
516ae477
A
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 }
f60086fc
A
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 }
7d31e928
A
157 case opTrustedCert:
158 return trustedCert(get<int32_t>());
159 case opTrustedCerts:
160 return trustedCerts();
f60086fc
A
161 case opNamedAnchor:
162 return fragments().namedAnchor(getString(), *mContext);
163 case opNamedCode:
164 return fragments().named(getString(), *mContext);
7d31e928
A
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) {
d1c1ab47 171 CODESIGN_EVAL_REQINT_UNKNOWN_FALSE(op);
7d31e928
A
172 return false;
173 } else {
d1c1ab47 174 CODESIGN_EVAL_REQINT_UNKNOWN_SKIPPED(op);
7d31e928
A
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//
188bool 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
516ae477
A
197//
198// Evaluate an entitlement condition
199//
200bool 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
7d31e928
A
209bool 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 },
f60086fc 224// { "subject.C-L", &CSSMOID_CollectiveLocalityName }, // missing from Security.framework headers
7d31e928 225 { "subject.O", &CSSMOID_OrganizationName },
f60086fc 226 { "subject.C-O", &CSSMOID_CollectiveOrganizationName },
7d31e928 227 { "subject.OU", &CSSMOID_OrganizationalUnitName },
f60086fc 228 { "subject.C-OU", &CSSMOID_CollectiveOrganizationalUnitName },
7d31e928 229 { "subject.ST", &CSSMOID_StateProvinceName },
f60086fc 230 { "subject.C-ST", &CSSMOID_CollectiveStateProvinceName },
7d31e928 231 { "subject.STREET", &CSSMOID_StreetAddress },
f60086fc
A
232 { "subject.C-STREET", &CSSMOID_CollectiveStreetAddress },
233 { "subject.UID", &CSSMOID_UserID },
7d31e928
A
234 { NULL, NULL }
235 };
7d31e928
A
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;
d1c1ab47 241 if (OSStatus rc = SecCertificateCopySubjectComponent(cert, cf->oid, &value.aref())) {
62e4ed3d 242 secdebug("csinterp", "cert %p lookup for DN.%s failed rc=%d", cert, key.c_str(), (int)rc);
7d31e928
A
243 return false;
244 }
245 return match(value);
246 }
247
516ae477
A
248 // email multi-valued match (any of...)
249 if (key == "email") {
250 CFRef<CFArrayRef> value;
251 if (OSStatus rc = SecCertificateCopyEmailAddresses(cert, &value.aref())) {
62e4ed3d 252 secdebug("csinterp", "cert %p lookup for email failed rc=%d", cert, (int)rc);
516ae477
A
253 return false;
254 }
255 return match(value);
256 }
257
7d31e928
A
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
f60086fc 263
516ae477
A
264bool 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
271bool Requirement::Interpreter::certFieldGeneric(const CssmOid &oid, const Match &match, SecCertificateRef cert)
272{
273 return cert && certificateHasField(cert, oid) && match(kCFBooleanTrue);
274}
275
f60086fc
A
276bool 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
283bool Requirement::Interpreter::certFieldPolicy(const CssmOid &oid, const Match &match, SecCertificateRef cert)
284{
285 return cert && certificateHasPolicy(cert, oid) && match(kCFBooleanTrue);
286}
287
516ae477 288
f6842776
A
289//
290// Check the Apple-signed condition
291//
516ae477 292bool Requirement::Interpreter::appleAnchored()
f6842776
A
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 )
516ae477
A
300 return true;
301 return false;
302}
303
304bool Requirement::Interpreter::appleSigned()
305{
306 if (appleAnchored())
d1c1ab47
A
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)
f6842776 310 && certFieldValue("subject.O", Match(appleIntermediateO, matchEqual), intermed))
d1c1ab47 311 return true;
f6842776
A
312 return false;
313}
314
315
7d31e928
A
316//
317// Verify an anchor requirement against the context
318//
319bool 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//
339bool 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
361bool 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//
385SecTrustSettingsResult 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//
430Requirement::Interpreter::Match::Match(Interpreter &interp)
431{
432 switch (mOp = interp.get<MatchOperation>()) {
433 case matchExists:
434 break;
435 case matchEqual:
436 case matchContains:
516ae477
A
437 case matchBeginsWith:
438 case matchEndsWith:
439 case matchLessThan:
440 case matchGreaterThan:
441 case matchLessEqual:
442 case matchGreaterEqual:
f60086fc 443 mValue.take(makeCFString(interp.getString()));
7d31e928
A
444 break;
445 default:
516ae477
A
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
7d31e928
A
449 break;
450 }
451}
452
453
454//
455// Execute a match against a candidate value
456//
457bool 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;
516ae477
A
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);
7d31e928 509 default:
516ae477 510 // unrecognized match types can never match
7d31e928
A
511 return false;
512 }
513}
514
515
516ae477
A
516bool 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
f60086fc
A
528//
529// External fragments
530//
531Fragments::Fragments()
532{
533 mMyBundle = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.security"));
534}
535
536
537bool 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
547CFDataRef 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
7d31e928
A
572} // CodeSigning
573} // Security