]> git.saurik.com Git - apple/securityd.git/blob - src/codesigdb.cpp
securityd-55009.tar.gz
[apple/securityd.git] / src / codesigdb.cpp
1 /*
2 * Copyright (c) 2003-2008 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 //
26 // codesigdb - code-hash equivalence database
27 //
28 #include "codesigdb.h"
29 #include "process.h"
30 #include "server.h"
31 #include "agentquery.h"
32 #include <security_utilities/memutils.h>
33 #include <security_utilities/logging.h>
34 #include <Security/SecRequirementPriv.h>
35
36
37 //
38 // A self-constructing database key class.
39 // Key format is <t><uid|S><key data>
40 // where
41 // <t> single ASCII character type code ('H' for hash links)
42 // <uid|S> decimal userid of owning user, or 'S' for system entries. Followed by null byte.
43 // <key data> variable length key value (binary).
44 //
45 class DbKey : public CssmAutoData {
46 public:
47 DbKey(char type, const CssmData &key, bool perUser = false, uid_t user = 0);
48 };
49
50 DbKey::DbKey(char type, const CssmData &key, bool perUser, uid_t user)
51 : CssmAutoData(Allocator::standard())
52 {
53 using namespace LowLevelMemoryUtilities;
54 char header[20];
55 size_t headerLength;
56 if (perUser)
57 headerLength = 1 + sprintf(header, "%c%d", type, user);
58 else
59 headerLength = 1 + sprintf(header, "%cS", type);
60 malloc(headerLength + key.length());
61 memcpy(this->data(), header, headerLength);
62 memcpy(get().at(headerLength), key.data(), key.length());
63 }
64
65
66 //
67 // A subclass of Identity made of whole cloth (from a raw CodeSignature ACL information)
68 //
69 struct AclIdentity : public CodeSignatures::Identity {
70 AclIdentity(const CssmData hash, string path) : mHash(hash), mPath(path) { }
71
72 string getPath() const { return mPath; }
73 const CssmData getHash() const { return mHash; }
74
75 private:
76 const CssmData mHash;
77 const string mPath;
78 };
79
80
81 //
82 // Construct a CodeSignatures objects
83 //
84 CodeSignatures::CodeSignatures(const char *path)
85 {
86 try {
87 mDb.open(path, O_RDWR | O_CREAT, 0644);
88 } catch (const CommonError &err) {
89 try {
90 mDb.open(path, O_RDONLY, 0644);
91 Syslog::warning("database %s opened READONLY (R/W failed errno=%d)", path, err.unixError());
92 secdebug("codesign", "database %s opened READONLY (R/W failed errno=%d)", path, err.unixError());
93 } catch (...) {
94 Syslog::warning("cannot open %s; using no code equivalents", path);
95 secdebug("codesign", "unable to open %s; using no code equivalents", path);
96 }
97 }
98 if (mDb)
99 mDb.flush(); // in case we just created it
100 IFDUMPING("equiv", debugDump("open"));
101 }
102
103 CodeSignatures::~CodeSignatures()
104 {
105 }
106
107
108 //
109 // (Re)open the equivalence database.
110 // This is useful to switch to database in another volume.
111 //
112 void CodeSignatures::open(const char *path)
113 {
114 mDb.open(path, O_RDWR | O_CREAT, 0644);
115 mDb.flush();
116 IFDUMPING("equiv", debugDump("reopen"));
117 }
118
119
120 //
121 // Basic Identity objects
122 //
123 CodeSignatures::Identity::Identity() : mState(untried)
124 { }
125
126 CodeSignatures::Identity::~Identity()
127 { }
128
129 string CodeSignatures::Identity::canonicalName(const string &path)
130 {
131 string::size_type slash = path.rfind('/');
132 if (slash == string::npos) // bloody unlikely, but whatever...
133 return path;
134 return path.substr(slash+1);
135 }
136
137
138 //
139 // Find and store database objects (primitive layer)
140 //
141 bool CodeSignatures::find(Identity &id, uid_t user)
142 {
143 if (id.mState != Identity::untried)
144 return id.mState == Identity::valid;
145 try {
146 DbKey userKey('H', id.getHash(), true, user);
147 CssmData linkValue;
148 if (mDb.get(userKey, linkValue)) {
149 id.mName = string(linkValue.interpretedAs<const char>(), linkValue.length());
150 IFDUMPING("equiv", id.debugDump("found/user"));
151 id.mState = Identity::valid;
152 return true;
153 }
154 DbKey sysKey('H', id.getHash());
155 if (mDb.get(sysKey, linkValue)) {
156 id.mName = string(linkValue.interpretedAs<const char>(), linkValue.length());
157 IFDUMPING("equiv", id.debugDump("found/system"));
158 id.mState = Identity::valid;
159 return true;
160 }
161 } catch (...) {
162 secdebug("codesign", "exception validating identity for %s - marking failed", id.path().c_str());
163 id.mState = Identity::invalid;
164 }
165 return id.mState == Identity::valid;
166 }
167
168 void CodeSignatures::makeLink(Identity &id, const string &ident, bool forUser, uid_t user)
169 {
170 DbKey key('H', id.getHash(), forUser, user);
171 if (!mDb.put(key, StringData(ident)))
172 UnixError::throwMe();
173 }
174
175
176 //
177 // Administrative manipulation calls
178 //
179 void CodeSignatures::addLink(const CssmData &oldHash, const CssmData &newHash,
180 const char *inName, bool forSystem)
181 {
182 string name = Identity::canonicalName(inName);
183 uid_t user = Server::process().uid();
184 if (forSystem && user) // only root user can establish forSystem links
185 UnixError::throwMe(EACCES);
186 if (!forSystem) // in fact, for now we don't allow per-user calls at all
187 UnixError::throwMe(EACCES);
188 AclIdentity oldCode(oldHash, name);
189 AclIdentity newCode(newHash, name);
190 secdebug("codesign", "addlink for name %s", name.c_str());
191 StLock<Mutex> _(mDatabaseLock);
192 if (oldCode) {
193 if (oldCode.trustedName() != name) {
194 secdebug("codesign", "addlink does not match existing name %s",
195 oldCode.trustedName().c_str());
196 MacOSError::throwMe(CSSMERR_CSP_VERIFY_FAILED);
197 }
198 } else {
199 makeLink(oldCode, name, !forSystem, user);
200 }
201 if (!newCode)
202 makeLink(newCode, name, !forSystem, user);
203 mDb.flush();
204 }
205
206 void CodeSignatures::removeLink(const CssmData &hash, const char *name, bool forSystem)
207 {
208 AclIdentity code(hash, name);
209 uid_t user = Server::process().uid();
210 if (forSystem && user) // only root user can remove forSystem links
211 UnixError::throwMe(EACCES);
212 DbKey key('H', hash, !forSystem, user);
213 StLock<Mutex> _(mDatabaseLock);
214 mDb.erase(key);
215 mDb.flush();
216 }
217
218
219 //
220 // Verify signature matches.
221 // This ends up getting called when a CodeSignatureAclSubject is validated.
222 // The OSXVerifier describes what we require of the client code; the process represents
223 // the requesting client; and the context gives us access to the ACL and its environment
224 // in case we want to, well, creatively rewrite it for some reason.
225 //
226 bool CodeSignatures::verify(Process &process,
227 const OSXVerifier &verifier, const AclValidationContext &context)
228 {
229 secdebug("codesign", "start verify");
230
231 // if we have no client code, we cannot possibly match this
232 SecCodeRef code = process.currentGuest();
233 if (!code) {
234 secdebug("codesign", "no code base: fail");
235 return false;
236 }
237
238 if (SecRequirementRef requirement = verifier.requirement()) {
239 // If the ACL contains a code signature (requirement), we won't match against unsigned code at all.
240 // The legacy hash is ignored (it's for use by pre-Leopard systems).
241 secdebug("codesign", "CS requirement present; ignoring legacy hashes");
242 Server::active().longTermActivity();
243 switch (OSStatus rc = SecCodeCheckValidity(code, kSecCSDefaultFlags, requirement)) {
244 case noErr:
245 secdebug("codesign", "CS verify passed");
246 return true;
247 case errSecCSUnsigned:
248 secdebug("codesign", "CS verify against unsigned binary failed");
249 return false;
250 default:
251 secdebug("codesign", "CS verify failed OSStatus=%d", int32_t(rc));
252 return false;
253 }
254 }
255 switch (matchSignedClientToLegacyACL(process, code, verifier, context)) {
256 case noErr: // handled, allow access
257 return true;
258 case errSecCSUnsigned: // unsigned client, complete legacy case
259 secdebug("codesign", "no CS requirement - using legacy hash");
260 return verifyLegacy(process,
261 CssmData::wrap(verifier.legacyHash(), SHA1::digestLength),
262 verifier.path());
263 default: // client unsuitable, reject this match
264 return false;
265 }
266 }
267
268
269 //
270 // See if we can rewrite the ACL from legacy to Code Signing form without losing too much security.
271 // Returns true if the present validation should succeed (we probably rewrote the ACL).
272 // Returns false if the present validation shouldn't succeed based on what we did here (we may still
273 // have rewritten the ACL, in principle).
274 //
275 // Note that these checks add nontrivial overhead to ACL processing. We want to eventually phase
276 // this out, or at least make it an option that doesn't run all the time - perhaps an "extra legacy
277 // effort" per-client mode bit.
278 //
279 static string trim(string s, char delimiter)
280 {
281 string::size_type p = s.rfind(delimiter);
282 if (p != string::npos)
283 s = s.substr(p + 1);
284 return s;
285 }
286
287 static string trim(string s, char delimiter, string suffix)
288 {
289 s = trim(s, delimiter);
290 int preLength = s.length() - suffix.length();
291 if (preLength > 0 && s.substr(preLength) == suffix)
292 s = s.substr(0, preLength);
293 return s;
294 }
295
296 OSStatus CodeSignatures::matchSignedClientToLegacyACL(Process &process,
297 SecCodeRef code, const OSXVerifier &verifier, const AclValidationContext &context)
298 {
299 //
300 // Check whether we seem to be matching a legacy .Mac ACL against a member of the .Mac group
301 //
302 if (SecurityServerAcl::looksLikeLegacyDotMac(context)) {
303 Server::active().longTermActivity();
304 CFRef<SecRequirementRef> dotmac;
305 MacOSError::check(SecRequirementCreateGroup(CFSTR("dot-mac"), NULL, kSecCSDefaultFlags, &dotmac.aref()));
306 if (SecCodeCheckValidity(code, kSecCSDefaultFlags, dotmac) == noErr) {
307 secdebug("codesign", "client is a dot-mac application; update the ACL accordingly");
308
309 // create a suitable AclSubject (this is the above-the-API-line way)
310 CFRef<CFDataRef> reqdata;
311 MacOSError::check(SecRequirementCopyData(dotmac, kSecCSDefaultFlags, &reqdata.aref()));
312 RefPointer<CodeSignatureAclSubject> subject = new CodeSignatureAclSubject(NULL, "group://dot-mac");
313 subject->add((const BlobCore *)CFDataGetBytePtr(reqdata));
314
315 // add it to the ACL and pass the access check (we just quite literally did it above)
316 SecurityServerAcl::addToStandardACL(context, subject);
317 return noErr;
318 }
319 }
320
321 //
322 // Get best names for the ACL (legacy) subject and the (signed) client
323 //
324 CFRef<CFDictionaryRef> info;
325 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
326 CFStringRef signingIdentity = CFStringRef(CFDictionaryGetValue(info, kSecCodeInfoIdentifier));
327 if (!signingIdentity) // unsigned
328 return errSecCSUnsigned;
329
330 string bundleName; // client
331 if (CFDictionaryRef infoList = CFDictionaryRef(CFDictionaryGetValue(info, kSecCodeInfoPList)))
332 if (CFStringRef name = CFStringRef(CFDictionaryGetValue(infoList, kCFBundleNameKey)))
333 bundleName = trim(cfString(name), '.');
334 if (bundleName.empty()) // fall back to signing identifier
335 bundleName = trim(cfString(signingIdentity), '.');
336
337 string aclName = trim(verifier.path(), '/', ".app"); // ACL
338
339 secdebug("codesign", "matching signed client \"%s\" against legacy ACL \"%s\"",
340 bundleName.c_str(), aclName.c_str());
341
342 //
343 // Check whether we're matching a signed APPLE application against a legacy ACL by the same name
344 //
345 if (bundleName == aclName) {
346 const unsigned char reqData[] = { // "anchor apple", version 1 blob, embedded here
347 0xfa, 0xde, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x10,
348 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03
349 };
350 CFRef<SecRequirementRef> apple;
351 MacOSError::check(SecRequirementCreateWithData(CFTempData(reqData, sizeof(reqData)),
352 kSecCSDefaultFlags, &apple.aref()));
353 Server::active().longTermActivity();
354 switch (OSStatus rc = SecCodeCheckValidity(code, kSecCSDefaultFlags, apple)) {
355 case noErr:
356 {
357 secdebug("codesign", "withstands strict scrutiny; quietly adding new ACL");
358 RefPointer<OSXCode> wrap = new OSXCodeWrap(code);
359 RefPointer<AclSubject> subject = new CodeSignatureAclSubject(OSXVerifier(wrap));
360 SecurityServerAcl::addToStandardACL(context, subject);
361 return noErr;
362 }
363 default:
364 secdebug("codesign", "validation fails with rc=%d, rejecting", int32_t(rc));
365 return rc;
366 }
367 secdebug("codesign", "does not withstand strict scrutiny; ask the user");
368 QueryCodeCheck query;
369 query.inferHints(process);
370 if (!query(verifier.path().c_str())) {
371 secdebug("codesign", "user declined equivalence: cancel the access");
372 CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED);
373 }
374 RefPointer<OSXCode> wrap = new OSXCodeWrap(code);
375 RefPointer<AclSubject> subject = new CodeSignatureAclSubject(OSXVerifier(wrap));
376 SecurityServerAcl::addToStandardACL(context, subject);
377 return noErr;
378 }
379
380 // not close enough to even ask - this can't match
381 return errSecCSReqFailed;
382 }
383
384
385 //
386 // Perform legacy hash verification.
387 // This is the pre-Leopard (Tiger, Panther) code path. Here we only have legacy hashes
388 // (called, confusingly, "signatures"), which we're matching against suitably computed
389 // "signatures" (hashes) on the requesting application. We consult the CodeEquivalenceDatabase
390 // in a doomed attempt to track changes made to applications through updates, and issue
391 // equivalence dialogs to users if we have a name match (but hash mismatch). That's all
392 // there was before Code Signing; and that's what you'll continue to get if the requesting
393 // application is unsigned. Until we throw the whole mess out altogether, hopefully by
394 // the Next Big Cat After Leopard.
395 //
396 bool CodeSignatures::verifyLegacy(Process &process, const CssmData &signature, string path)
397 {
398 // First of all, if the signature directly matches the client's code, we're obviously fine
399 // we don't even need the database for that...
400 Identity &clientIdentity = process;
401 try {
402 if (clientIdentity.getHash() == signature) {
403 secdebug("codesign", "direct match: pass");
404 return true;
405 }
406 } catch (...) {
407 secdebug("codesign", "exception getting client code hash: fail");
408 return false;
409 }
410
411 #if CONSULT_LEGACY_CODE_EQUIVALENCE_DATABASE
412
413 // Ah well. Establish mediator objects for database signature links
414 AclIdentity aclIdentity(signature, path);
415
416 uid_t user = process.uid();
417 {
418 StLock<Mutex> _(mDatabaseLock);
419 find(aclIdentity, user);
420 find(clientIdentity, user);
421 }
422
423 // if both links exist, we can decide this right now
424 if (aclIdentity && clientIdentity) {
425 if (aclIdentity.trustedName() == clientIdentity.trustedName()) {
426 secdebug("codesign", "app references match: pass");
427 return true;
428 } else {
429 secdebug("codesign", "client/acl links exist but are unequal: fail");
430 return false;
431 }
432 }
433
434 // check for name equality
435 secdebug("codesign", "matching client %s against acl %s",
436 clientIdentity.name().c_str(), aclIdentity.name().c_str());
437 if (aclIdentity.name() != clientIdentity.name()) {
438 secdebug("codesign", "name/path mismatch: fail");
439 return false;
440 }
441
442 // The names match - we have a possible update.
443
444 // Take the UI lock now to serialize "update rushes".
445 LongtermStLock uiLocker(mUILock);
446
447 // re-read the database in case some other thread beat us to the update
448 {
449 StLock<Mutex> _(mDatabaseLock);
450 find(aclIdentity, user);
451 find(clientIdentity, user);
452 }
453 if (aclIdentity && clientIdentity) {
454 if (aclIdentity.trustedName() == clientIdentity.trustedName()) {
455 secdebug("codesign", "app references match: pass (on the rematch)");
456 return true;
457 } else {
458 secdebug("codesign", "client/acl links exist but are unequal: fail (on the rematch)");
459 return false;
460 }
461 }
462
463 // ask the user
464 QueryCodeCheck query;
465 query.inferHints(process);
466 if (!query(aclIdentity.path().c_str()))
467 {
468 secdebug("codesign", "user declined equivalence: cancel the access");
469 CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED);
470 }
471
472 // take the database lock back for real
473 StLock<Mutex> _(mDatabaseLock);
474
475 // user wants us to go ahead and establish trust (if possible)
476 if (aclIdentity) {
477 // acl is linked but new client: link the client to this application
478 makeLink(clientIdentity, aclIdentity.trustedName(), true, user);
479 mDb.flush();
480 secdebug("codesign", "client %s linked to application %s: pass",
481 clientIdentity.path().c_str(), aclIdentity.trustedName().c_str());
482 return true;
483 }
484
485 if (clientIdentity) { // code link exists, acl link missing
486 // client is linked but ACL (hash) never seen: link the ACL to this app
487 makeLink(aclIdentity, clientIdentity.trustedName(), true, user);
488 mDb.flush();
489 secdebug("codesign", "acl %s linked to client %s: pass",
490 aclIdentity.path().c_str(), clientIdentity.trustedName().c_str());
491 return true;
492 }
493
494 // the De Novo case: no links, must create everything
495 string ident = clientIdentity.name();
496 makeLink(clientIdentity, ident, true, user);
497 makeLink(aclIdentity, ident, true, user);
498 mDb.flush();
499 secdebug("codesign", "new linkages established: pass");
500 return true;
501
502 #else /* ignore Code Equivalence Database */
503
504 return false;
505
506 #endif
507 }
508
509
510 //
511 // Debug dumping support
512 //
513 #if defined(DEBUGDUMP)
514
515 void CodeSignatures::debugDump(const char *how) const
516 {
517 using namespace Debug;
518 using namespace LowLevelMemoryUtilities;
519 if (!how)
520 how = "dump";
521 CssmData key, value;
522 if (!mDb.first(key, value)) {
523 dump("CODE EQUIVALENTS DATABASE IS EMPTY (%s)\n", how);
524 } else {
525 dump("CODE EQUIVALENTS DATABASE DUMP (%s)\n", how);
526 do {
527 const char *header = key.interpretedAs<const char>();
528 size_t headerLength = strlen(header) + 1;
529 dump("%s:", header);
530 dumpData(key.at(headerLength), key.length() - headerLength);
531 dump(" => ");
532 dumpData(value);
533 dump("\n");
534 } while (mDb.next(key, value));
535 dump("END DUMP\n");
536 }
537 }
538
539 void CodeSignatures::Identity::debugDump(const char *how) const
540 {
541 using namespace Debug;
542 if (!how)
543 how = "dump";
544 dump("IDENTITY (%s) path=%s", how, getPath().c_str());
545 dump(" name=%s hash=", mName.empty() ? "(unset)" : mName.c_str());
546 dumpData(getHash());
547 dump("\n");
548 }
549
550 #endif //DEBUGDUMP