]> git.saurik.com Git - apple/security.git/blob - SecurityServer/xdatabase.cpp
Security-54.1.tar.gz
[apple/security.git] / SecurityServer / xdatabase.cpp
1 /*
2 * Copyright (c) 2000-2001 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 // database - database session management
21 //
22 #include "xdatabase.h"
23 #include "agentquery.h"
24 #include "key.h"
25 #include "server.h"
26 #include "cfnotifier.h" // legacy
27 #include "notifications.h"
28 #include "SecurityAgentClient.h"
29 #include <Security/acl_any.h> // for default owner ACLs
30
31
32 //
33 // The map of database common segments
34 //
35 Mutex Database::commonLock;
36 Database::CommonMap Database::commons;
37
38
39 //
40 // Create a Database object from initial parameters (create operation)
41 //
42 Database::Database(const DLDbIdentifier &id, const DBParameters &params, Process &proc,
43 const AccessCredentials *cred, const AclEntryPrototype *owner)
44 : SecurityServerAcl(dbAcl, CssmAllocator::standard()), process(proc),
45 mValidData(false), version(0), mBlob(NULL)
46 {
47 // save a copy of the credentials for later access control
48 mCred = DataWalkers::copy(cred, CssmAllocator::standard());
49
50 // create a new random signature to complete the DLDbIdentifier
51 Signature newSig;
52 Server::active().random(newSig.bytes);
53 DbIdentifier ident(id, newSig);
54
55 // create common block and initialize
56 common = new Common(ident);
57 StLock<Mutex> _(*common);
58 { StLock<Mutex> _(commonLock);
59 assert(commons.find(ident) == commons.end()); // better be new!
60 commons[ident] = common = new Common(ident);
61 common->useCount++;
62 }
63 // new common is now visible but we hold its lock
64
65 // obtain initial passphrase and generate keys
66 common->mParams = params;
67 common->setupKeys(cred);
68
69 // establish initial ACL
70 if (owner)
71 cssmSetInitial(*owner);
72 else
73 cssmSetInitial(new AnyAclSubject());
74 mValidData = true;
75
76 // for now, create the blob immediately
77 //@@@ this could be deferred, at the cost of some additional
78 //@@@ state monitoring. What happens if it locks before we have a blob?
79 encode();
80
81 // register with process
82 process.addDatabase(this);
83
84 IFDEBUG(debug("SSdb", "database %s(%p) created, common at %p",
85 common->dbName(), this, common));
86 IFDUMPING("SSdb", debugDump("creation complete"));
87 }
88
89
90 //
91 // Create a Database object from a database blob (decoding)
92 //
93 Database::Database(const DLDbIdentifier &id, const DbBlob *blob, Process &proc,
94 const AccessCredentials *cred)
95 : SecurityServerAcl(dbAcl, CssmAllocator::standard()), process(proc),
96 mValidData(false), version(0)
97 {
98 // perform basic validation on the incoming blob
99 assert(blob);
100 blob->validate(CSSMERR_APPLEDL_INVALID_DATABASE_BLOB);
101 switch (blob->version) {
102 #if defined(COMPAT_OSX_10_0)
103 case blob->version_MacOS_10_0:
104 break;
105 #endif
106 case blob->version_MacOS_10_1:
107 break;
108 default:
109 CssmError::throwMe(CSSMERR_APPLEDL_INCOMPATIBLE_DATABASE_BLOB);
110 }
111
112 // save a copy of the credentials for later access control
113 mCred = DataWalkers::copy(cred, CssmAllocator::standard());
114
115 // check to see if we already know about this database
116 DbIdentifier ident(id, blob->randomSignature);
117 StLock<Mutex> mapLock(commonLock);
118 CommonMap::iterator it = commons.find(ident);
119 if (it != commons.end()) {
120 // already there
121 common = it->second; // reuse common component
122 //@@@ arbitrate sequence number here, perhaps update common->mParams
123 StLock<Mutex> _(*common); // lock common against other users
124 common->useCount++;
125 IFDEBUG(debug("SSdb",
126 "open database %s(%p) version %lx at known common %p(%d)",
127 common->dbName(), this, blob->version, common, int(common->useCount)));
128 } else {
129 // newly introduced
130 commons[ident] = common = new Common(ident);
131 common->mParams = blob->params;
132 common->useCount++;
133 IFDEBUG(debug("SSdb", "open database %s(%p) version %lx with new common %p",
134 common->dbName(), this, blob->version, common));
135 }
136
137 // register with process
138 process.addDatabase(this);
139
140 mBlob = blob->copy();
141 IFDUMPING("SSdb", debugDump("end of decode"));
142 }
143
144
145 //
146 // Destroy a Database
147 //
148 Database::~Database()
149 {
150 IFDEBUG(debug("SSdb", "deleting database %s(%p) common %p (%d refs)",
151 common->dbName(), this, common, int(common->useCount)));
152 IFDUMPING("SSdb", debugDump("deleting database instance"));
153 process.removeDatabase(this);
154 CssmAllocator::standard().free(mCred);
155
156 // take the commonLock to avoid races against re-use of the common
157 StLock<Mutex> __(commonLock);
158 if (--common->useCount == 0 && common->isLocked()) {
159 // last use of this database, and it's locked - discard
160 IFDUMPING("SSdb", debugDump("discarding common"));
161 discard(common);
162 } else if (common->useCount == 0)
163 IFDUMPING("SSdb", debugDump("retained because it's unlocked"));
164 }
165
166
167 //
168 // (Re-)Authenticate the database. This changes the stored credentials.
169 //
170 void Database::authenticate(const AccessCredentials *cred)
171 {
172 StLock<Mutex> _(*common);
173 CssmAllocator::standard().free(mCred);
174 mCred = DataWalkers::copy(cred, CssmAllocator::standard());
175 }
176
177
178 //
179 // Encode the current database as a blob.
180 // Note that this returns memory we own and keep.
181 //
182 DbBlob *Database::encode()
183 {
184 StLock<Mutex> _(*common);
185 if (!validBlob()) {
186 // unlock the database
187 makeUnlocked();
188
189 // create new up-to-date blob
190 DbBlob *blob = common->encode(*this);
191 CssmAllocator::standard().free(mBlob);
192 mBlob = blob;
193 version = common->version;
194 debug("SSdb", "encoded database %p(%s) version %ld", this, dbName(), version);
195 }
196 activity();
197 assert(mBlob);
198 return mBlob;
199 }
200
201
202 //
203 // Change the passphrase on a database
204 //
205 void Database::changePassphrase(const AccessCredentials *cred)
206 {
207 StLock<Mutex> _(*common);
208 if (isLocked()) {
209 CssmAutoData passphrase(CssmAllocator::standard(CssmAllocator::sensitive));
210 if (getBatchPassphrase(cred, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK, passphrase)) {
211 // incoming sample contained data for unlock
212 makeUnlocked(passphrase);
213 } else {
214 // perform standard unlock
215 makeUnlocked();
216 }
217 } else if (!mValidData) // need to decode to get our ACLs, passphrase available
218 decode(common->passphrase);
219
220 // get the new passphrase
221 // @@@ unstaged version -- revise to filter passphrases
222 Process &cltProc = Server::active().connection().process;
223 IFDEBUG(debug("SSdb", "New passphrase query from PID %d (UID %d)", cltProc.pid(), cltProc.uid()));
224 QueryNewPassphrase query(cltProc.uid(), cltProc.session, *common, SecurityAgent::changePassphrase);
225 query(cred, common->passphrase);
226 common->version++; // blob state changed
227 IFDEBUG(debug("SSdb", "Database %s(%p) passphrase changed", common->dbName(), this));
228
229 // send out a notification
230 KeychainNotifier::passphraseChanged(identifier());
231 notify(passphraseChangedEvent);
232
233 // I guess this counts as an activity
234 activity();
235 }
236
237
238 //
239 // Unlock this database (if needed) by obtaining the passphrase in some
240 // suitable way and then proceeding to unlock with it. Performs retries
241 // where appropriate. Does absolutely nothing if the database is already unlocked.
242 //
243 void Database::unlock()
244 {
245 StLock<Mutex> _(*common);
246 makeUnlocked();
247 }
248
249 void Database::makeUnlocked()
250 {
251 IFDUMPING("SSdb", debugDump("default procedures unlock"));
252 if (isLocked()) {
253 assert(mBlob || (mValidData && common->passphrase));
254
255 Process &cltProc = Server::active().connection().process;
256 IFDEBUG(debug("SSdb", "Unlock query from process %d (UID %d)", cltProc.pid(), cltProc.uid()));
257 QueryUnlock query(cltProc.uid(), cltProc.session, *this);
258 query(mCred);
259 if (isLocked()) // still locked, unlock failed
260 CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED);
261
262 // successfully unlocked
263 activity(); // set timeout timer
264 } else if (!mValidData) // need to decode to get our ACLs, passphrase available
265 decode(common->passphrase);
266 }
267
268
269 //
270 // Perform programmatic unlock of a database, given a passphrase.
271 //
272 void Database::unlock(const CssmData &passphrase)
273 {
274 StLock<Mutex> _(*common);
275 makeUnlocked(passphrase);
276 }
277
278 void Database::makeUnlocked(const CssmData &passphrase)
279 {
280 if (isLocked()) {
281 if (decode(passphrase))
282 return;
283 else
284 CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED);
285 } else if (!mValidData)
286 decode(common->passphrase);
287 }
288
289
290 //
291 // Perform an actual unlock operation given a passphrase.
292 // Caller must hold common lock.
293 //
294 bool Database::decode(const CssmData &passphrase)
295 {
296 if (mValidData && common->passphrase) { // just check
297 return common->unlock(passphrase);
298 } else { // decode our blob
299 assert(mBlob);
300 void *privateAclBlob;
301 if (common->unlock(mBlob, passphrase, &privateAclBlob)) {
302 if (!mValidData) {
303 importBlob(mBlob->publicAclBlob(), privateAclBlob);
304 mValidData = true;
305 }
306 CssmAllocator::standard().free(privateAclBlob);
307 return true;
308 }
309 }
310 return false;
311 }
312
313
314 //
315 // Verify a putative database passphrase.
316 // This requires that the database be already unlocked;
317 // it will not unlock the database (and will not lock it
318 // if the proffered phrase is wrong).
319 //
320 bool Database::validatePassphrase(const CssmData &passphrase) const
321 {
322 assert(!isLocked());
323 return passphrase == common->passphrase;
324 }
325
326
327 //
328 // Lock this database
329 //
330 void Database::lock()
331 {
332 common->lock();
333 }
334
335
336 //
337 // Lock all databases we know of.
338 // This is an interim stop-gap measure, until we can work out how database
339 // state should interact with true multi-session operation.
340 //
341 void Database::lockAllDatabases(bool forSleep)
342 {
343 StLock<Mutex> _(commonLock); // hold all changes to Common map
344 debug("SSdb", "locking all %d known databases", int(commons.size()));
345 for (CommonMap::iterator it = commons.begin(); it != commons.end(); it++)
346 it->second->lock(true, forSleep); // lock, already holding commonLock
347 }
348
349
350 //
351 // Given a Key for this database, encode it into a blob and return it.
352 //
353 KeyBlob *Database::encodeKey(const CssmKey &key, const CssmData &pubAcl, const CssmData &privAcl)
354 {
355 makeUnlocked();
356
357 // tell the cryptocore to form the key blob
358 return common->encodeKeyCore(key, pubAcl, privAcl);
359 }
360
361
362 //
363 // Given a "blobbed" key for this database, decode it into its real
364 // key object and (re)populate its ACL.
365 //
366 void Database::decodeKey(KeyBlob *blob, CssmKey &key,
367 void * &pubAcl, void * &privAcl)
368 {
369 makeUnlocked(); // we need our keys
370
371 common->decodeKeyCore(blob, key, pubAcl, privAcl);
372 // memory protocol: pubAcl points into blob; privAcl was allocated
373
374 activity();
375 }
376
377
378 //
379 // Modify database parameters
380 //
381 void Database::setParameters(const DBParameters &params)
382 {
383 StLock<Mutex> _(*common);
384 makeUnlocked();
385 common->mParams = params;
386 common->version++; // invalidate old blobs
387 activity();
388 }
389
390
391 //
392 // Retrieve database parameters
393 //
394 void Database::getParameters(DBParameters &params)
395 {
396 StLock<Mutex> _(*common);
397 makeUnlocked();
398 params = common->mParams;
399 //activity(); // getting parameters does not reset the idle timer
400 }
401
402
403 //
404 // Intercept ACL change requests and reset blob validity
405 //
406 void Database::instantiateAcl()
407 {
408 StLock<Mutex> _(*common);
409 makeUnlocked();
410 }
411
412 void Database::noticeAclChange()
413 {
414 StLock<Mutex> _(*common);
415 version = 0;
416 }
417
418 const Database *Database::relatedDatabase() const
419 { return this; }
420
421
422 //
423 // Debugging support
424 //
425 #if defined(DEBUGDUMP)
426
427 void Database::debugDump(const char *msg)
428 {
429 assert(common);
430 const Signature &sig = common->identifier();
431 uint32 sig4; memcpy(&sig4, sig.bytes, sizeof(sig4));
432 Debug::dump("** %s(%8.8lx) common=%p(%ld) %s\n",
433 common->dbName(), sig4, common, common->useCount, msg);
434 if (isLocked())
435 Debug::dump(" locked");
436 else {
437 Time::Absolute when = common->when();
438 time_t whenTime = time_t(when);
439 Debug::dump(" UNLOCKED(%24.24s/%.2g)", ctime(&whenTime),
440 (when - Time::now()).seconds());
441 }
442 Debug::dump(" %s blobversion=%ld/%ld %svalidData",
443 (common->isValid() ? "validkeys" : "!validkeys"),
444 version, common->version,
445 (mValidData ? "" : "!"));
446 Debug::dump(" Params=(%ld %d)\n",
447 common->mParams.idleTimeout, common->mParams.lockOnSleep);
448 }
449
450 #endif //DEBUGDUMP
451
452
453 //
454 // Database::Common basic features
455 //
456 Database::Common::Common(const DbIdentifier &id)
457 : mIdentifier(id), sequence(0), passphrase(CssmAllocator::standard(CssmAllocator::sensitive)),
458 useCount(0), version(1),
459 mIsLocked(true)
460 { }
461
462 Database::Common::~Common()
463 {
464 // explicitly unschedule ourselves
465 Server::active().clearTimer(this);
466 }
467
468
469 void Database::discard(Common *common)
470 {
471 // LOCKING: commonLock held, *common NOT held
472 debug("SSdb", "discarding dbcommon %p (no users, locked)", common);
473 commons.erase(common->identifier());
474 delete common;
475 }
476
477 bool Database::Common::unlock(DbBlob *blob, const CssmData &passphrase,
478 void **privateAclBlob)
479 {
480 try {
481 // Tell the cryptocore to (try to) decode itself. This will fail
482 // in an astonishing variety of ways if the passphrase is wrong.
483 decodeCore(blob, passphrase, privateAclBlob);
484 } catch (...) {
485 //@@@ which errors should we let through? Any?
486 return false;
487 }
488
489 // save the passphrase (we'll need it for database encoding)
490 this->passphrase = passphrase;
491
492 // retrieve some public arguments
493 mParams = blob->params;
494
495 // now successfully unlocked
496 mIsLocked = false;
497
498 // set timeout
499 activity();
500
501 // broadcast unlock notification
502 KeychainNotifier::unlock(identifier());
503 notify(unlockedEvent);
504 return true;
505 }
506
507
508 //
509 // Fast-path unlock: secrets already valid; just check passphrase and approve.
510 //
511 bool Database::Common::unlock(const CssmData &passphrase)
512 {
513 assert(isValid());
514 if (isLocked()) {
515 if (passphrase == this->passphrase) {
516 mIsLocked = false;
517 KeychainNotifier::unlock(identifier());
518 notify(unlockedEvent);
519 return true; // okay
520 } else
521 return false; // failed
522 } else
523 return true; // was unlocked; no problem
524 }
525
526 void Database::Common::lock(bool holdingCommonLock, bool forSleep)
527 {
528 StLock<Mutex> locker(*this);
529 if (!isLocked()) {
530 if (forSleep && !mParams.lockOnSleep)
531 return; // it doesn't want to
532
533 //@@@ discard secrets here? That would make fast-path impossible.
534 mIsLocked = true;
535 KeychainNotifier::lock(identifier());
536 notify(lockedEvent);
537
538 // if no database refers to us now, we're history
539 StLock<Mutex> _(commonLock, false);
540 if (!holdingCommonLock)
541 _.lock();
542 if (useCount == 0) {
543 locker.unlock(); // release object lock
544 discard(this);
545 }
546 }
547 }
548
549 DbBlob *Database::Common::encode(Database &db)
550 {
551 assert(!isLocked()); // must have been unlocked by caller
552
553 // export database ACL to blob form
554 CssmData pubAcl, privAcl;
555 db.exportBlob(pubAcl, privAcl);
556
557 // tell the cryptocore to form the blob
558 DbBlob form;
559 form.randomSignature = identifier();
560 form.sequence = sequence;
561 form.params = mParams;
562 DbBlob *blob = encodeCore(form, passphrase, pubAcl, privAcl);
563
564 // clean up and go
565 db.allocator.free(pubAcl);
566 db.allocator.free(privAcl);
567 return blob;
568 }
569
570
571 //
572 // Send out database-related notifications
573 //
574 void Database::Common::notify(Listener::Event event)
575 {
576 IFDEBUG(debug("SSdb", "common %s(%p) sending event %ld", dbName(), this, event));
577 DLDbFlatIdentifier flatId(mIdentifier); // walkable form of DLDbIdentifier
578 CssmAutoData data(CssmAllocator::standard());
579 copy(&flatId, CssmAllocator::standard(), data.get());
580 Listener::notify(Listener::databaseNotifications, event, data);
581 }
582
583
584 //
585 // Initialize a (new) database's key information.
586 // This acquires the passphrase in the appropriate way.
587 // When (successfully) done, the database is in the unlocked state.
588 //
589 void Database::Common::setupKeys(const AccessCredentials *cred)
590 {
591 // get the new passphrase
592 // @@@ Un-staged version of the API - revise with acceptability tests
593 Process &cltProc = Server::active().connection().process;
594 IFDEBUG(debug("SSdb", "New passphrase request from process %d (UID %d)", cltProc.pid(), cltProc.uid()));
595 QueryNewPassphrase query(cltProc.uid(), cltProc.session, *this, SecurityAgent::newDatabase);
596 query(cred, passphrase);
597
598 // we have the passphrase now
599 generateNewSecrets();
600
601 // we're unlocked now
602 mIsLocked = false;
603 activity();
604 }
605
606
607 //
608 // Perform deferred lock processing for a database.
609 //
610 void Database::Common::action()
611 {
612 IFDEBUG(debug("SSdb", "common %s(%p) locked by timer (%d refs)",
613 dbName(), this, int(useCount)));
614 lock();
615 }
616
617 void Database::Common::activity()
618 {
619 if (!isLocked())
620 Server::active().setTimer(this, int(mParams.idleTimeout));
621 }