]> git.saurik.com Git - apple/securityd.git/blob - src/codesigdb.cpp
securityd-29035.tar.gz
[apple/securityd.git] / src / codesigdb.cpp
1 /*
2 * Copyright (c) 2003-2004 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 //
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
35
36 //
37 // A self-constructing database key class.
38 // Key format is <t><uid|S><key data>
39 // where
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).
43 //
44 class DbKey : public CssmAutoData {
45 public:
46 DbKey(char type, const CssmData &key, bool perUser = false, uid_t user = 0);
47 };
48
49 DbKey::DbKey(char type, const CssmData &key, bool perUser, uid_t user)
50 : CssmAutoData(Allocator::standard())
51 {
52 using namespace LowLevelMemoryUtilities;
53 char header[20];
54 size_t headerLength;
55 if (perUser)
56 headerLength = 1 + sprintf(header, "%c%d", type, user);
57 else
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());
62 }
63
64
65 //
66 // A subclass of Identity made of whole cloth (from a raw CodeSignature ACL information)
67 //
68 class AclIdentity : public CodeSignatures::Identity {
69 public:
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 : "") { }
74
75 protected:
76 std::string getPath() const { return mPath; }
77 const CssmData getHash(CodeSigning::OSXSigner &) const { return mHash; }
78
79 private:
80 const CssmData mHash;
81 std::string mPath;
82 };
83
84
85 //
86 // Construct a CodeSignatures objects
87 //
88 CodeSignatures::CodeSignatures(const char *path)
89 {
90 try {
91 mDb.open(path, O_RDWR | O_CREAT, 0644);
92 } catch (const CommonError &err) {
93 try {
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());
97 } catch (...) {
98 Syslog::warning("cannot open %s; using no code equivalents", path);
99 secdebug("codesign", "unable to open %s; using no code equivalents", path);
100 }
101 }
102 if (mDb)
103 mDb.flush(); // in case we just created it
104 IFDUMPING("equiv", debugDump("open"));
105 }
106
107 CodeSignatures::~CodeSignatures()
108 {
109 }
110
111
112 //
113 // (Re)open the equivalence database.
114 // This is useful to switch to database in another volume.
115 //
116 void CodeSignatures::open(const char *path)
117 {
118 mDb.open(path, O_RDWR | O_CREAT, 0644);
119 if (mDb)
120 mDb.flush();
121 IFDUMPING("equiv", debugDump("reopen"));
122 }
123
124
125 //
126 // Basic Identity objects
127 //
128 CodeSignatures::Identity::Identity() : mState(untried)
129 { }
130
131 CodeSignatures::Identity::~Identity()
132 { }
133
134 string CodeSignatures::Identity::canonicalName(const string &path)
135 {
136 string::size_type slash = path.rfind('/');
137 if (slash == string::npos) // bloody unlikely, but whatever...
138 return path;
139 return path.substr(slash+1);
140 }
141
142
143 //
144 // Find and store database objects (primitive layer)
145 //
146 bool CodeSignatures::find(Identity &id, uid_t user)
147 {
148 if (!mDb)
149 return false;
150 if (id.mState != Identity::untried)
151 return id.mState == Identity::valid;
152 try {
153 DbKey userKey('H', id.getHash(mSigner), true, user);
154 CssmData linkValue;
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;
159 return true;
160 }
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;
166 return true;
167 }
168 } catch (...) {
169 secdebug("codesign", "exception validating identity for %s - marking failed", id.path().c_str());
170 id.mState = Identity::invalid;
171 }
172 return id.mState == Identity::valid;
173 }
174
175 void CodeSignatures::makeLink(Identity &id, const string &ident, bool forUser, uid_t user)
176 {
177 if (!mDb)
178 UnixError::throwMe(ENOENT);
179 DbKey key('H', id.getHash(mSigner), forUser, user);
180 if (!mDb.put(key, StringData(ident)))
181 UnixError::throwMe();
182 }
183
184 void CodeSignatures::makeApplication(const std::string &name, const std::string &path)
185 {
186 //@@@ create app record and fill (later)
187 }
188
189
190 //
191 // Administrative manipulation calls
192 //
193 void CodeSignatures::addLink(const CssmData &oldHash, const CssmData &newHash,
194 const char *inName, bool forSystem)
195 {
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);
206 if (oldCode) {
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);
211 }
212 } else {
213 makeLink(oldCode, name, !forSystem, user);
214 }
215 if (!newCode)
216 makeLink(newCode, name, !forSystem, user);
217 mDb.flush();
218 }
219
220 void CodeSignatures::removeLink(const CssmData &hash, const char *name, bool forSystem)
221 {
222 if (!mDb)
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);
230 mDb.erase(key);
231 mDb.flush();
232 }
233
234
235 //
236 // Verify signature matches
237 //
238 bool CodeSignatures::verify(Process &process,
239 const CodeSigning::Signature *trustedSignature, const CssmData *comment)
240 {
241 secdebug("codesign", "start verify");
242
243 // if we have no client code, we cannot possibly match this
244 if (!process.clientCode()) {
245 secdebug("codesign", "no code base: fail");
246 return false;
247 }
248
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;
252 try {
253 if (clientIdentity.getHash(mSigner) == CssmData(*trustedSignature)) {
254 secdebug("codesign", "direct match: pass");
255 return true;
256 }
257 } catch (...) {
258 secdebug("codesign", "exception getting client code hash: fail");
259 return false;
260 }
261
262 // don't bother the user if the db is MIA
263 if (!mDb) {
264 secdebug("codesign", "database not open; cannot verify");
265 return false;
266 }
267
268 // ah well. Establish mediator objects for database signature links
269 AclIdentity aclIdentity(trustedSignature, comment ? comment->interpretedAs<const char>() : NULL);
270
271 uid_t user = process.uid();
272 {
273 StLock<Mutex> _(mDatabaseLock);
274 find(aclIdentity, user);
275 find(clientIdentity, user);
276 }
277
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");
282 return true;
283 } else {
284 secdebug("codesign", "client/acl links exist but are unequal: fail");
285 return false;
286 }
287 }
288
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");
294 return false;
295 }
296
297 // The names match - we have a possible update.
298
299 // Take the UI lock now to serialize "update rushes".
300 Server::active().longTermActivity();
301 StLock<Mutex> uiLocker(mUILock);
302
303 // re-read the database in case some other thread beat us to the update
304 {
305 StLock<Mutex> _(mDatabaseLock);
306 find(aclIdentity, user);
307 find(clientIdentity, user);
308 }
309 if (aclIdentity && clientIdentity) {
310 if (aclIdentity.trustedName() == clientIdentity.trustedName()) {
311 secdebug("codesign", "app references match: pass (on the rematch)");
312 return true;
313 } else {
314 secdebug("codesign", "client/acl links exist but are unequal: fail (on the rematch)");
315 return false;
316 }
317 }
318
319 // ask the user
320 QueryCodeCheck query;
321 query.inferHints(process);
322 if (!query(aclIdentity.path().c_str()))
323 {
324 secdebug("codesign", "user declined equivalence: fail");
325 return false;
326 }
327
328 // take the database lock back for real
329 StLock<Mutex> _(mDatabaseLock);
330
331 // user wants us to go ahead and establish trust (if possible)
332 if (aclIdentity) {
333 // acl is linked but new client: link the client to this application
334 makeLink(clientIdentity, aclIdentity.trustedName(), true, user);
335 mDb.flush();
336 secdebug("codesign", "client %s linked to application %s: pass",
337 clientIdentity.path().c_str(), aclIdentity.trustedName().c_str());
338 return true;
339 }
340
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);
344 mDb.flush();
345 secdebug("codesign", "acl %s linked to client %s: pass",
346 aclIdentity.path().c_str(), clientIdentity.trustedName().c_str());
347 return true;
348 }
349
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);
355 mDb.flush();
356 secdebug("codesign", "new linkages established: pass");
357 return true;
358 }
359
360
361 //
362 // Debug dumping support
363 //
364 #if defined(DEBUGDUMP)
365
366 void CodeSignatures::debugDump(const char *how) const
367 {
368 using namespace Debug;
369 using namespace LowLevelMemoryUtilities;
370 if (!how)
371 how = "dump";
372 CssmData key, value;
373 if (!mDb) {
374 dump("CODE EQUIVALENTS DATABASE IS NOT OPEN (%s)", how);
375 } else {
376 if (!mDb.first(key, value)) {
377 dump("CODE EQUIVALENTS DATABASE IS EMPTY (%s)\n", how);
378 } else {
379 dump("CODE EQUIVALENTS DATABASE DUMP (%s)\n", how);
380 do {
381 const char *header = key.interpretedAs<const char>();
382 size_t headerLength = strlen(header) + 1;
383 dump("%s:", header);
384 dumpData(key.at(headerLength), key.length() - headerLength);
385 dump(" => ");
386 dumpData(value);
387 dump("\n");
388 } while (mDb.next(key, value));
389 dump("END DUMP\n");
390 }
391 }
392 }
393
394 void CodeSignatures::Identity::debugDump(const char *how) const
395 {
396 using namespace Debug;
397 if (!how)
398 how = "dump";
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));
403 dump("\n");
404 }
405
406 #endif //DEBUGDUMP