2 * Copyright (c) 2003-2004 Apple Computer, 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>
37 // A self-constructing database key class.
38 // Key format is <t><uid|S><key data>
40 // <t> single ASCII character type code ('H' for hash links)
41 // <uid|S> decimal userid of owning user, or 'S' for system entries. Followed by null byte.
42 // <key data> variable length key value (binary).
44 class DbKey
: public CssmAutoData
{
46 DbKey(char type
, const CssmData
&key
, bool perUser
= false, uid_t user
= 0);
49 DbKey::DbKey(char type
, const CssmData
&key
, bool perUser
, uid_t user
)
50 : CssmAutoData(Allocator::standard())
52 using namespace LowLevelMemoryUtilities
;
56 headerLength
= 1 + sprintf(header
, "%c%d", type
, user
);
58 headerLength
= 1 + sprintf(header
, "%cS", type
);
59 malloc(headerLength
+ key
.length());
60 memcpy(this->data(), header
, headerLength
);
61 memcpy(get().at(headerLength
), key
.data(), key
.length());
66 // A subclass of Identity made of whole cloth (from a raw CodeSignature ACL information)
68 class AclIdentity
: public CodeSignatures::Identity
{
70 AclIdentity(const CodeSigning::Signature
*sig
, const char *comment
)
71 : mHash(*sig
), mPath(comment
? comment
: "") { }
72 AclIdentity(const CssmData
&hash
, const char *comment
)
73 : mHash(hash
), mPath(comment
? comment
: "") { }
76 std::string
getPath() const { return mPath
; }
77 const CssmData
getHash(CodeSigning::OSXSigner
&) const { return mHash
; }
86 // Construct a CodeSignatures objects
88 CodeSignatures::CodeSignatures(const char *path
)
91 mDb
.open(path
, O_RDWR
| O_CREAT
, 0644);
92 } catch (const CommonError
&err
) {
94 mDb
.open(path
, O_RDONLY
, 0644);
95 Syslog::warning("database %s opened READONLY (R/W failed errno=%d)", path
, err
.unixError());
96 secdebug("codesign", "database %s opened READONLY (R/W failed errno=%d)", path
, err
.unixError());
98 Syslog::warning("cannot open %s; using no code equivalents", path
);
99 secdebug("codesign", "unable to open %s; using no code equivalents", path
);
103 mDb
.flush(); // in case we just created it
104 IFDUMPING("equiv", debugDump("open"));
107 CodeSignatures::~CodeSignatures()
113 // (Re)open the equivalence database.
114 // This is useful to switch to database in another volume.
116 void CodeSignatures::open(const char *path
)
118 mDb
.open(path
, O_RDWR
| O_CREAT
, 0644);
121 IFDUMPING("equiv", debugDump("reopen"));
126 // Basic Identity objects
128 CodeSignatures::Identity::Identity() : mState(untried
)
131 CodeSignatures::Identity::~Identity()
134 string
CodeSignatures::Identity::canonicalName(const string
&path
)
136 string::size_type slash
= path
.rfind('/');
137 if (slash
== string::npos
) // bloody unlikely, but whatever...
139 return path
.substr(slash
+1);
144 // Find and store database objects (primitive layer)
146 bool CodeSignatures::find(Identity
&id
, uid_t user
)
150 if (id
.mState
!= Identity::untried
)
151 return id
.mState
== Identity::valid
;
153 DbKey
userKey('H', id
.getHash(mSigner
), true, user
);
155 if (mDb
.get(userKey
, linkValue
)) {
156 id
.mName
= string(linkValue
.interpretedAs
<const char>(), linkValue
.length());
157 IFDUMPING("equiv", id
.debugDump("found/user"));
158 id
.mState
= Identity::valid
;
161 DbKey
sysKey('H', id
.getHash(mSigner
));
162 if (mDb
.get(sysKey
, linkValue
)) {
163 id
.mName
= string(linkValue
.interpretedAs
<const char>(), linkValue
.length());
164 IFDUMPING("equiv", id
.debugDump("found/system"));
165 id
.mState
= Identity::valid
;
169 secdebug("codesign", "exception validating identity for %s - marking failed", id
.path().c_str());
170 id
.mState
= Identity::invalid
;
172 return id
.mState
== Identity::valid
;
175 void CodeSignatures::makeLink(Identity
&id
, const string
&ident
, bool forUser
, uid_t user
)
178 UnixError::throwMe(ENOENT
);
179 DbKey
key('H', id
.getHash(mSigner
), forUser
, user
);
180 if (!mDb
.put(key
, StringData(ident
)))
181 UnixError::throwMe();
184 void CodeSignatures::makeApplication(const std::string
&name
, const std::string
&path
)
186 //@@@ create app record and fill (later)
191 // Administrative manipulation calls
193 void CodeSignatures::addLink(const CssmData
&oldHash
, const CssmData
&newHash
,
194 const char *inName
, bool forSystem
)
196 string name
= Identity::canonicalName(inName
);
197 uid_t user
= Server::process().uid();
198 if (forSystem
&& user
) // only root user can establish forSystem links
199 UnixError::throwMe(EACCES
);
200 if (!forSystem
) // in fact, for now we don't allow per-user calls at all
201 UnixError::throwMe(EACCES
);
202 AclIdentity
oldCode(oldHash
, name
.c_str());
203 AclIdentity
newCode(newHash
, name
.c_str());
204 secdebug("codesign", "addlink for name %s", name
.c_str());
205 StLock
<Mutex
> _(mDatabaseLock
);
207 if (oldCode
.trustedName() != name
) {
208 secdebug("codesign", "addlink does not match existing name %s",
209 oldCode
.trustedName().c_str());
210 MacOSError::throwMe(CSSMERR_CSP_VERIFY_FAILED
);
213 makeLink(oldCode
, name
, !forSystem
, user
);
216 makeLink(newCode
, name
, !forSystem
, user
);
220 void CodeSignatures::removeLink(const CssmData
&hash
, const char *name
, bool forSystem
)
223 UnixError::throwMe(ENOENT
);
224 AclIdentity
code(hash
, name
);
225 uid_t user
= Server::process().uid();
226 if (forSystem
&& user
) // only root user can remove forSystem links
227 UnixError::throwMe(EACCES
);
228 DbKey
key('H', hash
, !forSystem
, user
);
229 StLock
<Mutex
> _(mDatabaseLock
);
236 // Verify signature matches
238 bool CodeSignatures::verify(Process
&process
,
239 const CodeSigning::Signature
*trustedSignature
, const CssmData
*comment
)
241 secdebug("codesign", "start verify");
243 // if we have no client code, we cannot possibly match this
244 if (!process
.clientCode()) {
245 secdebug("codesign", "no code base: fail");
249 // first of all, if the signature directly matches the client's code, we're obviously fine
250 // we don't even need the database for that...
251 Identity
&clientIdentity
= process
;
253 if (clientIdentity
.getHash(mSigner
) == CssmData(*trustedSignature
)) {
254 secdebug("codesign", "direct match: pass");
258 secdebug("codesign", "exception getting client code hash: fail");
262 // don't bother the user if the db is MIA
264 secdebug("codesign", "database not open; cannot verify");
268 // ah well. Establish mediator objects for database signature links
269 AclIdentity
aclIdentity(trustedSignature
, comment
? comment
->interpretedAs
<const char>() : NULL
);
271 uid_t user
= process
.uid();
273 StLock
<Mutex
> _(mDatabaseLock
);
274 find(aclIdentity
, user
);
275 find(clientIdentity
, user
);
278 // if both links exist, we can decide this right now
279 if (aclIdentity
&& clientIdentity
) {
280 if (aclIdentity
.trustedName() == clientIdentity
.trustedName()) {
281 secdebug("codesign", "app references match: pass");
284 secdebug("codesign", "client/acl links exist but are unequal: fail");
289 // check for name equality
290 secdebug("codesign", "matching client %s against acl %s",
291 clientIdentity
.name().c_str(), aclIdentity
.name().c_str());
292 if (aclIdentity
.name() != clientIdentity
.name()) {
293 secdebug("codesign", "name/path mismatch: fail");
297 // The names match - we have a possible update.
299 // Take the UI lock now to serialize "update rushes".
300 Server::active().longTermActivity();
301 StLock
<Mutex
> uiLocker(mUILock
);
303 // re-read the database in case some other thread beat us to the update
305 StLock
<Mutex
> _(mDatabaseLock
);
306 find(aclIdentity
, user
);
307 find(clientIdentity
, user
);
309 if (aclIdentity
&& clientIdentity
) {
310 if (aclIdentity
.trustedName() == clientIdentity
.trustedName()) {
311 secdebug("codesign", "app references match: pass (on the rematch)");
314 secdebug("codesign", "client/acl links exist but are unequal: fail (on the rematch)");
320 QueryCodeCheck query
;
321 query
.inferHints(process
);
322 if (!query(aclIdentity
.path().c_str()))
324 secdebug("codesign", "user declined equivalence: fail");
328 // take the database lock back for real
329 StLock
<Mutex
> _(mDatabaseLock
);
331 // user wants us to go ahead and establish trust (if possible)
333 // acl is linked but new client: link the client to this application
334 makeLink(clientIdentity
, aclIdentity
.trustedName(), true, user
);
336 secdebug("codesign", "client %s linked to application %s: pass",
337 clientIdentity
.path().c_str(), aclIdentity
.trustedName().c_str());
341 if (clientIdentity
) { // code link exists, acl link missing
342 // client is linked but ACL (hash) never seen: link the ACL to this app
343 makeLink(aclIdentity
, clientIdentity
.trustedName(), true, user
);
345 secdebug("codesign", "acl %s linked to client %s: pass",
346 aclIdentity
.path().c_str(), clientIdentity
.trustedName().c_str());
350 // the De Novo case: no links, must create everything
351 string ident
= clientIdentity
.name();
352 makeApplication(ident
, clientIdentity
.path());
353 makeLink(clientIdentity
, ident
, true, user
);
354 makeLink(aclIdentity
, ident
, true, user
);
356 secdebug("codesign", "new linkages established: pass");
362 // Debug dumping support
364 #if defined(DEBUGDUMP)
366 void CodeSignatures::debugDump(const char *how
) const
368 using namespace Debug
;
369 using namespace LowLevelMemoryUtilities
;
374 dump("CODE EQUIVALENTS DATABASE IS NOT OPEN (%s)", how
);
376 if (!mDb
.first(key
, value
)) {
377 dump("CODE EQUIVALENTS DATABASE IS EMPTY (%s)\n", how
);
379 dump("CODE EQUIVALENTS DATABASE DUMP (%s)\n", how
);
381 const char *header
= key
.interpretedAs
<const char>();
382 size_t headerLength
= strlen(header
) + 1;
384 dumpData(key
.at(headerLength
), key
.length() - headerLength
);
388 } while (mDb
.next(key
, value
));
394 void CodeSignatures::Identity::debugDump(const char *how
) const
396 using namespace Debug
;
399 dump("IDENTITY (%s) path=%s", how
, getPath().c_str());
400 dump(" name=%s hash=", mName
.empty() ? "(unset)" : mName
.c_str());
401 CodeSigning::OSXSigner signer
;
402 dumpData(getHash(signer
));