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