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