2 * Copyright (c) 2003-2008 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@
26 // codesigdb - code-hash equivalence database
28 #include "codesigdb.h"
31 #include "agentquery.h"
32 #include <security_utilities/memutils.h>
33 #include <security_utilities/logging.h>
34 #include <Security/SecRequirementPriv.h>
38 // A self-constructing database key class.
39 // Key format is <t><uid|S><key data>
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).
45 class DbKey
: public CssmAutoData
{
47 DbKey(char type
, const CssmData
&key
, bool perUser
= false, uid_t user
= 0);
50 DbKey::DbKey(char type
, const CssmData
&key
, bool perUser
, uid_t user
)
51 : CssmAutoData(Allocator::standard())
53 using namespace LowLevelMemoryUtilities
;
57 headerLength
= 1 + sprintf(header
, "%c%d", type
, user
);
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());
67 // A subclass of Identity made of whole cloth (from a raw CodeSignature ACL information)
69 struct AclIdentity
: public CodeSignatures::Identity
{
70 AclIdentity(const CssmData hash
, string path
) : mHash(hash
), mPath(path
) { }
72 string
getPath() const { return mPath
; }
73 const CssmData
getHash() const { return mHash
; }
82 // Construct a CodeSignatures objects
84 CodeSignatures::CodeSignatures(const char *path
)
87 mDb
.open(path
, O_RDWR
| O_CREAT
, 0644);
88 } catch (const CommonError
&err
) {
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());
94 Syslog::warning("cannot open %s; using no code equivalents", path
);
95 secdebug("codesign", "unable to open %s; using no code equivalents", path
);
99 mDb
.flush(); // in case we just created it
100 IFDUMPING("equiv", debugDump("open"));
103 CodeSignatures::~CodeSignatures()
109 // (Re)open the equivalence database.
110 // This is useful to switch to database in another volume.
112 void CodeSignatures::open(const char *path
)
114 mDb
.open(path
, O_RDWR
| O_CREAT
, 0644);
116 IFDUMPING("equiv", debugDump("reopen"));
121 // Basic Identity objects
123 CodeSignatures::Identity::Identity() : mState(untried
)
126 CodeSignatures::Identity::~Identity()
129 string
CodeSignatures::Identity::canonicalName(const string
&path
)
131 string::size_type slash
= path
.rfind('/');
132 if (slash
== string::npos
) // bloody unlikely, but whatever...
134 return path
.substr(slash
+1);
139 // Find and store database objects (primitive layer)
141 bool CodeSignatures::find(Identity
&id
, uid_t user
)
143 if (id
.mState
!= Identity::untried
)
144 return id
.mState
== Identity::valid
;
146 DbKey
userKey('H', id
.getHash(), true, user
);
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
;
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
;
162 secdebug("codesign", "exception validating identity for %s - marking failed", id
.path().c_str());
163 id
.mState
= Identity::invalid
;
165 return id
.mState
== Identity::valid
;
168 void CodeSignatures::makeLink(Identity
&id
, const string
&ident
, bool forUser
, uid_t user
)
170 DbKey
key('H', id
.getHash(), forUser
, user
);
171 if (!mDb
.put(key
, StringData(ident
)))
172 UnixError::throwMe();
177 // Administrative manipulation calls
179 void CodeSignatures::addLink(const CssmData
&oldHash
, const CssmData
&newHash
,
180 const char *inName
, bool forSystem
)
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
);
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
);
199 makeLink(oldCode
, name
, !forSystem
, user
);
202 makeLink(newCode
, name
, !forSystem
, user
);
206 void CodeSignatures::removeLink(const CssmData
&hash
, const char *name
, bool forSystem
)
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
);
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.
226 bool CodeSignatures::verify(Process
&process
,
227 const OSXVerifier
&verifier
, const AclValidationContext
&context
)
229 secdebug("codesign", "start verify");
231 StLock
<Mutex
> _(process
);
232 SecCodeRef code
= process
.currentGuest();
234 secdebug("codesign", "no code base: fail");
237 if (SecRequirementRef requirement
= verifier
.requirement()) {
238 // If the ACL contains a code signature (requirement), we won't match against unsigned code at all.
239 // The legacy hash is ignored (it's for use by pre-Leopard systems).
240 secdebug("codesign", "CS requirement present; ignoring legacy hashes");
241 Server::active().longTermActivity();
242 switch (OSStatus rc
= SecCodeCheckValidity(code
, kSecCSDefaultFlags
, requirement
)) {
244 secdebug("codesign", "CS verify passed");
246 case errSecCSUnsigned
:
247 secdebug("codesign", "CS verify against unsigned binary failed");
250 secdebug("codesign", "CS verify failed OSStatus=%d", int32_t(rc
));
254 switch (matchSignedClientToLegacyACL(process
, code
, verifier
, context
)) {
255 case noErr
: // handled, allow access
257 case errSecCSUnsigned
: // unsigned client, complete legacy case
258 secdebug("codesign", "no CS requirement - using legacy hash");
259 return verifyLegacy(process
,
260 CssmData::wrap(verifier
.legacyHash(), SHA1::digestLength
),
262 default: // client unsuitable, reject this match
269 // See if we can rewrite the ACL from legacy to Code Signing form without losing too much security.
270 // Returns true if the present validation should succeed (we probably rewrote the ACL).
271 // Returns false if the present validation shouldn't succeed based on what we did here (we may still
272 // have rewritten the ACL, in principle).
274 // Note that these checks add nontrivial overhead to ACL processing. We want to eventually phase
275 // this out, or at least make it an option that doesn't run all the time - perhaps an "extra legacy
276 // effort" per-client mode bit.
278 static string
trim(string s
, char delimiter
)
280 string::size_type p
= s
.rfind(delimiter
);
281 if (p
!= string::npos
)
286 static string
trim(string s
, char delimiter
, string suffix
)
288 s
= trim(s
, delimiter
);
289 int preLength
= s
.length() - suffix
.length();
290 if (preLength
> 0 && s
.substr(preLength
) == suffix
)
291 s
= s
.substr(0, preLength
);
295 OSStatus
CodeSignatures::matchSignedClientToLegacyACL(Process
&process
,
296 SecCodeRef code
, const OSXVerifier
&verifier
, const AclValidationContext
&context
)
299 // Check whether we seem to be matching a legacy .Mac ACL against a member of the .Mac group
301 if (SecurityServerAcl::looksLikeLegacyDotMac(context
)) {
302 Server::active().longTermActivity();
303 CFRef
<SecRequirementRef
> dotmac
;
304 MacOSError::check(SecRequirementCreateGroup(CFSTR("dot-mac"), NULL
, kSecCSDefaultFlags
, &dotmac
.aref()));
305 if (SecCodeCheckValidity(code
, kSecCSDefaultFlags
, dotmac
) == noErr
) {
306 secdebug("codesign", "client is a dot-mac application; update the ACL accordingly");
308 // create a suitable AclSubject (this is the above-the-API-line way)
309 CFRef
<CFDataRef
> reqdata
;
310 MacOSError::check(SecRequirementCopyData(dotmac
, kSecCSDefaultFlags
, &reqdata
.aref()));
311 RefPointer
<CodeSignatureAclSubject
> subject
= new CodeSignatureAclSubject(NULL
, "group://dot-mac");
312 subject
->add((const BlobCore
*)CFDataGetBytePtr(reqdata
));
314 // add it to the ACL and pass the access check (we just quite literally did it above)
315 SecurityServerAcl::addToStandardACL(context
, subject
);
321 // Get best names for the ACL (legacy) subject and the (signed) client
323 CFRef
<CFDictionaryRef
> info
;
324 MacOSError::check(SecCodeCopySigningInformation(code
, kSecCSSigningInformation
, &info
.aref()));
325 CFStringRef signingIdentity
= CFStringRef(CFDictionaryGetValue(info
, kSecCodeInfoIdentifier
));
326 if (!signingIdentity
) // unsigned
327 return errSecCSUnsigned
;
329 string bundleName
; // client
330 if (CFDictionaryRef infoList
= CFDictionaryRef(CFDictionaryGetValue(info
, kSecCodeInfoPList
)))
331 if (CFStringRef name
= CFStringRef(CFDictionaryGetValue(infoList
, kCFBundleNameKey
)))
332 bundleName
= trim(cfString(name
), '.');
333 if (bundleName
.empty()) // fall back to signing identifier
334 bundleName
= trim(cfString(signingIdentity
), '.');
336 string aclName
= trim(verifier
.path(), '/', ".app"); // ACL
338 secdebug("codesign", "matching signed client \"%s\" against legacy ACL \"%s\"",
339 bundleName
.c_str(), aclName
.c_str());
342 // Check whether we're matching a signed APPLE application against a legacy ACL by the same name
344 if (bundleName
== aclName
) {
345 const unsigned char reqData
[] = { // "anchor apple", version 1 blob, embedded here
346 0xfa, 0xde, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x10,
347 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03
349 CFRef
<SecRequirementRef
> apple
;
350 MacOSError::check(SecRequirementCreateWithData(CFTempData(reqData
, sizeof(reqData
)),
351 kSecCSDefaultFlags
, &apple
.aref()));
352 Server::active().longTermActivity();
353 switch (OSStatus rc
= SecCodeCheckValidity(code
, kSecCSDefaultFlags
, apple
)) {
356 secdebug("codesign", "withstands strict scrutiny; quietly adding new ACL");
357 RefPointer
<OSXCode
> wrap
= new OSXCodeWrap(code
);
358 RefPointer
<AclSubject
> subject
= new CodeSignatureAclSubject(OSXVerifier(wrap
));
359 SecurityServerAcl::addToStandardACL(context
, subject
);
363 secdebug("codesign", "validation fails with rc=%d, rejecting", int32_t(rc
));
366 secdebug("codesign", "does not withstand strict scrutiny; ask the user");
367 QueryCodeCheck query
;
368 query
.inferHints(process
);
369 if (!query(verifier
.path().c_str())) {
370 secdebug("codesign", "user declined equivalence: cancel the access");
371 CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED
);
373 RefPointer
<OSXCode
> wrap
= new OSXCodeWrap(code
);
374 RefPointer
<AclSubject
> subject
= new CodeSignatureAclSubject(OSXVerifier(wrap
));
375 SecurityServerAcl::addToStandardACL(context
, subject
);
379 // not close enough to even ask - this can't match
380 return errSecCSReqFailed
;
385 // Perform legacy hash verification.
386 // This is the pre-Leopard (Tiger, Panther) code path. Here we only have legacy hashes
387 // (called, confusingly, "signatures"), which we're matching against suitably computed
388 // "signatures" (hashes) on the requesting application. We consult the CodeEquivalenceDatabase
389 // in a doomed attempt to track changes made to applications through updates, and issue
390 // equivalence dialogs to users if we have a name match (but hash mismatch). That's all
391 // there was before Code Signing; and that's what you'll continue to get if the requesting
392 // application is unsigned. Until we throw the whole mess out altogether, hopefully by
393 // the Next Big Cat After Leopard.
395 bool CodeSignatures::verifyLegacy(Process
&process
, const CssmData
&signature
, string path
)
397 // First of all, if the signature directly matches the client's code, we're obviously fine
398 // we don't even need the database for that...
399 Identity
&clientIdentity
= process
;
401 if (clientIdentity
.getHash() == signature
) {
402 secdebug("codesign", "direct match: pass");
406 secdebug("codesign", "exception getting client code hash: fail");
410 #if CONSULT_LEGACY_CODE_EQUIVALENCE_DATABASE
412 // Ah well. Establish mediator objects for database signature links
413 AclIdentity
aclIdentity(signature
, path
);
415 uid_t user
= process
.uid();
417 StLock
<Mutex
> _(mDatabaseLock
);
418 find(aclIdentity
, user
);
419 find(clientIdentity
, user
);
422 // if both links exist, we can decide this right now
423 if (aclIdentity
&& clientIdentity
) {
424 if (aclIdentity
.trustedName() == clientIdentity
.trustedName()) {
425 secdebug("codesign", "app references match: pass");
428 secdebug("codesign", "client/acl links exist but are unequal: fail");
433 // check for name equality
434 secdebug("codesign", "matching client %s against acl %s",
435 clientIdentity
.name().c_str(), aclIdentity
.name().c_str());
436 if (aclIdentity
.name() != clientIdentity
.name()) {
437 secdebug("codesign", "name/path mismatch: fail");
441 // The names match - we have a possible update.
443 // Take the UI lock now to serialize "update rushes".
444 LongtermStLock
uiLocker(mUILock
);
446 // re-read the database in case some other thread beat us to the update
448 StLock
<Mutex
> _(mDatabaseLock
);
449 find(aclIdentity
, user
);
450 find(clientIdentity
, user
);
452 if (aclIdentity
&& clientIdentity
) {
453 if (aclIdentity
.trustedName() == clientIdentity
.trustedName()) {
454 secdebug("codesign", "app references match: pass (on the rematch)");
457 secdebug("codesign", "client/acl links exist but are unequal: fail (on the rematch)");
463 QueryCodeCheck query
;
464 query
.inferHints(process
);
465 if (!query(aclIdentity
.path().c_str()))
467 secdebug("codesign", "user declined equivalence: cancel the access");
468 CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED
);
471 // take the database lock back for real
472 StLock
<Mutex
> _(mDatabaseLock
);
474 // user wants us to go ahead and establish trust (if possible)
476 // acl is linked but new client: link the client to this application
477 makeLink(clientIdentity
, aclIdentity
.trustedName(), true, user
);
479 secdebug("codesign", "client %s linked to application %s: pass",
480 clientIdentity
.path().c_str(), aclIdentity
.trustedName().c_str());
484 if (clientIdentity
) { // code link exists, acl link missing
485 // client is linked but ACL (hash) never seen: link the ACL to this app
486 makeLink(aclIdentity
, clientIdentity
.trustedName(), true, user
);
488 secdebug("codesign", "acl %s linked to client %s: pass",
489 aclIdentity
.path().c_str(), clientIdentity
.trustedName().c_str());
493 // the De Novo case: no links, must create everything
494 string ident
= clientIdentity
.name();
495 makeLink(clientIdentity
, ident
, true, user
);
496 makeLink(aclIdentity
, ident
, true, user
);
498 secdebug("codesign", "new linkages established: pass");
501 #else /* ignore Code Equivalence Database */
510 // Debug dumping support
512 #if defined(DEBUGDUMP)
514 void CodeSignatures::debugDump(const char *how
) const
516 using namespace Debug
;
517 using namespace LowLevelMemoryUtilities
;
521 if (!mDb
.first(key
, value
)) {
522 dump("CODE EQUIVALENTS DATABASE IS EMPTY (%s)\n", how
);
524 dump("CODE EQUIVALENTS DATABASE DUMP (%s)\n", how
);
526 const char *header
= key
.interpretedAs
<const char>();
527 size_t headerLength
= strlen(header
) + 1;
529 dumpData(key
.at(headerLength
), key
.length() - headerLength
);
533 } while (mDb
.next(key
, value
));
538 void CodeSignatures::Identity::debugDump(const char *how
) const
540 using namespace Debug
;
543 dump("IDENTITY (%s) path=%s", how
, getPath().c_str());
544 dump(" name=%s hash=", mName
.empty() ? "(unset)" : mName
.c_str());