]> git.saurik.com Git - apple/securityd.git/blob - src/codesigdb.cpp
c1482601af70b222c6b7ba74630776d922c87c58
[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 * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved.
7 *
8 * This file contains Original Code and/or Modifications of Original Code
9 * as defined in and that are subject to the Apple Public Source License
10 * Version 2.0 (the 'License'). You may not use this file except in
11 * compliance with the License. Please obtain a copy of the License at
12 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * file.
14 *
15 * The Original Code and all software distributed under the License are
16 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
17 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
18 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
20 * Please see the License for the specific language governing rights and
21 * limitations under the License.
22 *
23 * @APPLE_LICENSE_HEADER_END@
24 */
25
26
27 //
28 // codesigdb - code-hash equivalence database
29 //
30 #include "codesigdb.h"
31 #include "process.h"
32 #include "server.h"
33 #include "agentquery.h"
34 #include <security_utilities/memutils.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 class AclIdentity : public CodeSignatures::Identity {
70 public:
71 AclIdentity(const CodeSigning::Signature *sig, const char *comment)
72 : mHash(*sig), mPath(comment ? comment : "") { }
73 AclIdentity(const CssmData &hash, const char *comment)
74 : mHash(hash), mPath(comment ? comment : "") { }
75
76 protected:
77 std::string getPath() const { return mPath; }
78 const CssmData getHash(CodeSigning::OSXSigner &) const { return mHash; }
79
80 private:
81 const CssmData mHash;
82 std::string mPath;
83 };
84
85
86 //
87 // Construct a CodeSignatures objects
88 //
89 CodeSignatures::CodeSignatures(const char *path)
90 {
91 try {
92 mDb.open(path, O_RDWR | O_CREAT, 0644);
93 } catch (const CommonError &err) {
94 try {
95 mDb.open(path, O_RDONLY, 0644);
96 Syslog::warning("database %s opened READONLY (R/W failed errno=%d)", path, err.unixError());
97 secdebug("codesign", "database %s opened READONLY (R/W failed errno=%d)", path, err.unixError());
98 } catch (...) {
99 Syslog::warning("cannot open %s; using no code equivalents", path);
100 secdebug("codesign", "unable to open %s; using no code equivalents", path);
101 }
102 }
103 if (mDb)
104 mDb.flush(); // in case we just created it
105 IFDUMPING("equiv", debugDump("open"));
106 }
107
108 CodeSignatures::~CodeSignatures()
109 {
110 }
111
112
113 //
114 // (Re)open the equivalence database.
115 // This is useful to switch to database in another volume.
116 //
117 void CodeSignatures::open(const char *path)
118 {
119 mDb.open(path, O_RDWR | O_CREAT, 0644);
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 (id.mState != Identity::untried)
149 return id.mState == Identity::valid;
150 try {
151 DbKey userKey('H', id.getHash(mSigner), true, user);
152 CssmData linkValue;
153 if (mDb.get(userKey, linkValue)) {
154 id.mName = string(linkValue.interpretedAs<const char>(), linkValue.length());
155 IFDUMPING("equiv", id.debugDump("found/user"));
156 id.mState = Identity::valid;
157 return true;
158 }
159 DbKey sysKey('H', id.getHash(mSigner));
160 if (mDb.get(sysKey, linkValue)) {
161 id.mName = string(linkValue.interpretedAs<const char>(), linkValue.length());
162 IFDUMPING("equiv", id.debugDump("found/system"));
163 id.mState = Identity::valid;
164 return true;
165 }
166 } catch (...) {
167 secdebug("codesign", "exception validating identity for %s - marking failed", id.path().c_str());
168 id.mState = Identity::invalid;
169 }
170 return id.mState == Identity::valid;
171 }
172
173 void CodeSignatures::makeLink(Identity &id, const string &ident, bool forUser, uid_t user)
174 {
175 DbKey key('H', id.getHash(mSigner), forUser, user);
176 if (!mDb.put(key, StringData(ident)))
177 UnixError::throwMe();
178 }
179
180 void CodeSignatures::makeApplication(const std::string &name, const std::string &path)
181 {
182 //@@@ create app record and fill (later)
183 }
184
185
186 //
187 // Administrative manipulation calls
188 //
189 void CodeSignatures::addLink(const CssmData &oldHash, const CssmData &newHash,
190 const char *inName, bool forSystem)
191 {
192 string name = Identity::canonicalName(inName);
193 uid_t user = Server::process().uid();
194 if (forSystem && user) // only root user can establish forSystem links
195 UnixError::throwMe(EACCES);
196 if (!forSystem) // in fact, for now we don't allow per-user calls at all
197 UnixError::throwMe(EACCES);
198 AclIdentity oldCode(oldHash, name.c_str());
199 AclIdentity newCode(newHash, name.c_str());
200 secdebug("codesign", "addlink for name %s", name.c_str());
201 StLock<Mutex> _(mDatabaseLock);
202 if (oldCode) {
203 if (oldCode.trustedName() != name) {
204 secdebug("codesign", "addlink does not match existing name %s",
205 oldCode.trustedName().c_str());
206 MacOSError::throwMe(CSSMERR_CSP_VERIFY_FAILED);
207 }
208 } else {
209 makeLink(oldCode, name, !forSystem, user);
210 }
211 if (!newCode)
212 makeLink(newCode, name, !forSystem, user);
213 mDb.flush();
214 }
215
216 void CodeSignatures::removeLink(const CssmData &hash, const char *name, bool forSystem)
217 {
218 AclIdentity code(hash, name);
219 uid_t user = Server::process().uid();
220 if (forSystem && user) // only root user can remove forSystem links
221 UnixError::throwMe(EACCES);
222 DbKey key('H', hash, !forSystem, user);
223 StLock<Mutex> _(mDatabaseLock);
224 mDb.erase(key);
225 mDb.flush();
226 }
227
228
229 //
230 // Verify signature matches
231 //
232 bool CodeSignatures::verify(Process &process,
233 const CodeSigning::Signature *trustedSignature, const CssmData *comment)
234 {
235 secdebug("codesign", "start verify");
236
237 // if we have no client code, we cannot possibly match this
238 if (!process.clientCode()) {
239 secdebug("codesign", "no code base: fail");
240 return false;
241 }
242
243 // first of all, if the signature directly matches the client's code, we're obviously fine
244 // we don't even need the database for that...
245 Identity &clientIdentity = process;
246 try {
247 if (clientIdentity.getHash(mSigner) == CssmData(*trustedSignature)) {
248 secdebug("codesign", "direct match: pass");
249 return true;
250 }
251 } catch (...) {
252 secdebug("codesign", "exception getting client code hash: fail");
253 return false;
254 }
255
256 // ah well. Establish mediator objects for database signature links
257 AclIdentity aclIdentity(trustedSignature, comment ? comment->interpretedAs<const char>() : NULL);
258
259 uid_t user = process.uid();
260 {
261 StLock<Mutex> _(mDatabaseLock);
262 find(aclIdentity, user);
263 find(clientIdentity, user);
264 }
265
266 // if both links exist, we can decide this right now
267 if (aclIdentity && clientIdentity) {
268 if (aclIdentity.trustedName() == clientIdentity.trustedName()) {
269 secdebug("codesign", "app references match: pass");
270 return true;
271 } else {
272 secdebug("codesign", "client/acl links exist but are unequal: fail");
273 return false;
274 }
275 }
276
277 // check for name equality
278 secdebug("codesign", "matching client %s against acl %s",
279 clientIdentity.name().c_str(), aclIdentity.name().c_str());
280 if (aclIdentity.name() != clientIdentity.name()) {
281 secdebug("codesign", "name/path mismatch: fail");
282 return false;
283 }
284
285 // The names match - we have a possible update.
286
287 // Take the UI lock now to serialize "update rushes".
288 Server::active().longTermActivity();
289 StLock<Mutex> uiLocker(mUILock);
290
291 // re-read the database in case some other thread beat us to the update
292 {
293 StLock<Mutex> _(mDatabaseLock);
294 find(aclIdentity, user);
295 find(clientIdentity, user);
296 }
297 if (aclIdentity && clientIdentity) {
298 if (aclIdentity.trustedName() == clientIdentity.trustedName()) {
299 secdebug("codesign", "app references match: pass (on the rematch)");
300 return true;
301 } else {
302 secdebug("codesign", "client/acl links exist but are unequal: fail (on the rematch)");
303 return false;
304 }
305 }
306
307 // ask the user
308 QueryCodeCheck query;
309 query.inferHints(process);
310 if (!query(aclIdentity.path().c_str()))
311 {
312 secdebug("codesign", "user declined equivalence: fail");
313 return false;
314 }
315
316 // take the database lock back for real
317 StLock<Mutex> _(mDatabaseLock);
318
319 // user wants us to go ahead and establish trust (if possible)
320 if (aclIdentity) {
321 // acl is linked but new client: link the client to this application
322 makeLink(clientIdentity, aclIdentity.trustedName(), true, user);
323 mDb.flush();
324 secdebug("codesign", "client %s linked to application %s: pass",
325 clientIdentity.path().c_str(), aclIdentity.trustedName().c_str());
326 return true;
327 }
328
329 if (clientIdentity) { // code link exists, acl link missing
330 // client is linked but ACL (hash) never seen: link the ACL to this app
331 makeLink(aclIdentity, clientIdentity.trustedName(), true, user);
332 mDb.flush();
333 secdebug("codesign", "acl %s linked to client %s: pass",
334 aclIdentity.path().c_str(), clientIdentity.trustedName().c_str());
335 return true;
336 }
337
338 // the De Novo case: no links, must create everything
339 string ident = clientIdentity.name();
340 makeApplication(ident, clientIdentity.path());
341 makeLink(clientIdentity, ident, true, user);
342 makeLink(aclIdentity, ident, true, user);
343 mDb.flush();
344 secdebug("codesign", "new linkages established: pass");
345 return true;
346 }
347
348
349 //
350 // Debug dumping support
351 //
352 #if defined(DEBUGDUMP)
353
354 void CodeSignatures::debugDump(const char *how) const
355 {
356 using namespace Debug;
357 using namespace LowLevelMemoryUtilities;
358 if (!how)
359 how = "dump";
360 CssmData key, value;
361 if (!mDb.first(key, value)) {
362 dump("CODE EQUIVALENTS DATABASE IS EMPTY (%s)\n", how);
363 } else {
364 dump("CODE EQUIVALENTS DATABASE DUMP (%s)\n", how);
365 do {
366 const char *header = key.interpretedAs<const char>();
367 size_t headerLength = strlen(header) + 1;
368 dump("%s:", header);
369 dumpData(key.at(headerLength), key.length() - headerLength);
370 dump(" => ");
371 dumpData(value);
372 dump("\n");
373 } while (mDb.next(key, value));
374 dump("END DUMP\n");
375 }
376 }
377
378 void CodeSignatures::Identity::debugDump(const char *how) const
379 {
380 using namespace Debug;
381 if (!how)
382 how = "dump";
383 dump("IDENTITY (%s) path=%s", how, getPath().c_str());
384 dump(" name=%s hash=", mName.empty() ? "(unset)" : mName.c_str());
385 CodeSigning::OSXSigner signer;
386 dumpData(getHash(signer));
387 dump("\n");
388 }
389
390 #endif //DEBUGDUMP