2 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
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
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.
20 // database - database session management
22 #include "xdatabase.h"
23 #include "agentquery.h"
26 #include "cfnotifier.h"
27 #include "SecurityAgentClient.h"
28 #include <Security/acl_any.h> // for default owner ACLs
32 // The map of database common segments
34 Mutex
Database::commonLock
;
35 Database::CommonMap
Database::commons
;
39 // Create a Database object from initial parameters (create operation)
41 Database::Database(const DLDbIdentifier
&id
, const DBParameters
¶ms
, Process
&proc
,
42 const AccessCredentials
*cred
, const AclEntryPrototype
*owner
)
43 : SecurityServerAcl(dbAcl
, CssmAllocator::standard()), process(proc
),
44 mValidData(false), version(0), mBlob(NULL
)
46 // save a copy of the credentials for later access control
47 mCred
= DataWalkers::copy(cred
, CssmAllocator::standard());
49 // create a new random signature to complete the DLDbIdentifier
51 Server::active().random(newSig
.bytes
);
52 DbIdentifier
ident(id
, newSig
);
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
);
62 // new common is now visible but we hold its lock
64 // obtain initial passphrase and generate keys
65 common
->mParams
= params
;
66 common
->setupKeys(cred
);
68 // establish initial ACL
70 cssmSetInitial(*owner
);
72 cssmSetInitial(new AnyAclSubject());
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?
80 // register with process
81 process
.addDatabase(this);
83 IFDEBUG(debug("SSdb", "database %s(%p) created, common at %p",
84 common
->dbName(), this, common
));
85 IFDUMPING("SSdb", debugDump("creation complete"));
90 // Create a Database object from a database blob (decoding)
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)
97 // perform basic validation on the incoming 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
:
105 case blob
->version_MacOS_10_1
:
108 CssmError::throwMe(CSSMERR_APPLEDL_INCOMPATIBLE_DATABASE_BLOB
);
111 // save a copy of the credentials for later access control
112 mCred
= DataWalkers::copy(cred
, CssmAllocator::standard());
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()) {
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
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
)));
129 commons
[ident
] = common
= new Common(ident
);
130 common
->mParams
= blob
->params
;
132 IFDEBUG(debug("SSdb", "open database %s(%p) version %lx with new common %p",
133 common
->dbName(), this, blob
->version
, common
));
136 // register with process
137 process
.addDatabase(this);
139 mBlob
= blob
->copy();
140 IFDUMPING("SSdb", debugDump("end of decode"));
145 // Destroy a Database
147 Database::~Database()
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
);
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"));
161 } else if (common
->useCount
== 0)
162 IFDUMPING("SSdb", debugDump("retained because it's unlocked"));
167 // (Re-)Authenticate the database. This changes the stored credentials.
169 void Database::authenticate(const AccessCredentials
*cred
)
171 StLock
<Mutex
> _(*common
);
172 CssmAllocator::standard().free(mCred
);
173 mCred
= DataWalkers::copy(cred
, CssmAllocator::standard());
178 // Encode the current database as a blob.
179 // Note that this returns memory we own and keep.
181 DbBlob
*Database::encode()
183 StLock
<Mutex
> _(*common
);
185 // unlock the database
188 // create new up-to-date blob
189 DbBlob
*blob
= common
->encode(*this);
190 CssmAllocator::standard().free(mBlob
);
192 version
= common
->version
;
193 debug("SSdb", "encoded database %p(%s) version %ld", this, dbName(), version
);
202 // Change the passphrase on a database
204 void Database::changePassphrase(const AccessCredentials
*cred
)
206 StLock
<Mutex
> _(*common
);
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
);
213 // perform standard unlock
216 } else if (!mValidData
) // need to decode to get our ACLs, passphrase available
217 decode(common
->passphrase
);
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));
226 // send out a notification
227 KeychainNotifier::passphraseChanged(identifier());
229 // I guess this counts as an activity
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.
239 void Database::unlock()
241 StLock
<Mutex
> _(*common
);
245 void Database::makeUnlocked()
247 IFDUMPING("SSdb", debugDump("default procedures unlock"));
249 assert(mBlob
|| (mValidData
&& common
->passphrase
));
251 QueryUnlock
query(*this);
253 if (isLocked()) // still locked, unlock failed
254 CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED
);
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
);
264 // Perform programmatic unlock of a database, given a passphrase.
266 void Database::unlock(const CssmData
&passphrase
)
268 StLock
<Mutex
> _(*common
);
269 makeUnlocked(passphrase
);
272 void Database::makeUnlocked(const CssmData
&passphrase
)
275 if (decode(passphrase
))
278 CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED
);
279 } else if (!mValidData
)
280 decode(common
->passphrase
);
285 // Perform an actual unlock operation given a passphrase.
286 // Caller must hold common lock.
288 bool Database::decode(const CssmData
&passphrase
)
290 if (mValidData
&& common
->passphrase
) { // just check
291 return common
->unlock(passphrase
);
292 } else { // decode our blob
294 void *privateAclBlob
;
295 if (common
->unlock(mBlob
, passphrase
, &privateAclBlob
)) {
297 importBlob(mBlob
->publicAclBlob(), privateAclBlob
);
300 CssmAllocator::standard().free(privateAclBlob
);
309 // Lock this database
311 void Database::lock()
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.
322 void Database::lockAllDatabases(bool forSleep
)
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
332 // Given a Key for this database, encode it into a blob and return it.
334 KeyBlob
*Database::encodeKey(const CssmKey
&key
, const CssmData
&pubAcl
, const CssmData
&privAcl
)
338 // tell the cryptocore to form the key blob
339 return common
->encodeKeyCore(key
, pubAcl
, privAcl
);
344 // Given a "blobbed" key for this database, decode it into its real
345 // key object and (re)populate its ACL.
347 void Database::decodeKey(KeyBlob
*blob
, CssmKey
&key
,
348 void * &pubAcl
, void * &privAcl
)
350 makeUnlocked(); // we need our keys
352 common
->decodeKeyCore(blob
, key
, pubAcl
, privAcl
);
353 // memory protocol: pubAcl points into blob; privAcl was allocated
360 // Modify database parameters
362 void Database::setParameters(const DBParameters
¶ms
)
364 StLock
<Mutex
> _(*common
);
366 common
->mParams
= params
;
367 common
->version
++; // invalidate old blobs
373 // Retrieve database parameters
375 void Database::getParameters(DBParameters
¶ms
)
377 StLock
<Mutex
> _(*common
);
379 params
= common
->mParams
;
380 //activity(); // getting parameters does not reset the idle timer
385 // Intercept ACL change requests and reset blob validity
387 void Database::instantiateAcl()
389 StLock
<Mutex
> _(*common
);
393 void Database::noticeAclChange()
395 StLock
<Mutex
> _(*common
);
399 const Database
*Database::relatedDatabase() const
406 #if defined(DEBUGDUMP)
408 void Database::debugDump(const char *msg
)
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
);
416 Debug::dump(" locked");
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());
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
);
435 // Database::Common basic features
437 Database::Common::Common(const DbIdentifier
&id
)
438 : mIdentifier(id
), sequence(0), passphrase(CssmAllocator::standard(CssmAllocator::sensitive
)),
439 useCount(0), version(1),
443 Database::Common::~Common()
445 // explicitly unschedule ourselves
446 Server::active().clearTimer(this);
450 void Database::discard(Common
*common
)
452 // LOCKING: commonLock held, *common NOT held
453 debug("SSdb", "discarding dbcommon %p (no users, locked)", common
);
454 commons
.erase(common
->identifier());
458 bool Database::Common::unlock(DbBlob
*blob
, const CssmData
&passphrase
,
459 void **privateAclBlob
)
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
);
466 //@@@ which errors should we let through? Any?
470 // save the passphrase (we'll need it for database encoding)
471 this->passphrase
= passphrase
;
473 // retrieve some public arguments
474 mParams
= blob
->params
;
476 // now successfully unlocked
482 // broadcast unlock notification
483 KeychainNotifier::unlock(identifier());
489 // Fast-path unlock: secrets already valid; just check passphrase and approve.
491 bool Database::Common::unlock(const CssmData
&passphrase
)
495 if (passphrase
== this->passphrase
) {
497 KeychainNotifier::unlock(identifier());
500 return false; // failed
502 return true; // was unlocked; no problem
505 void Database::Common::lock(bool holdingCommonLock
, bool forSleep
)
507 StLock
<Mutex
> locker(*this);
509 if (forSleep
&& !mParams
.lockOnSleep
)
510 return; // it doesn't want to
512 //@@@ discard secrets here? That would make fast-path impossible.
514 KeychainNotifier::lock(identifier());
516 // if no database refers to us now, we're history
517 StLock
<Mutex
> _(commonLock
, false);
518 if (!holdingCommonLock
)
521 locker
.unlock(); // release object lock
527 DbBlob
*Database::Common::encode(Database
&db
)
529 assert(!isLocked()); // must have been unlocked by caller
531 // export database ACL to blob form
532 CssmData pubAcl
, privAcl
;
533 db
.exportBlob(pubAcl
, privAcl
);
535 // tell the cryptocore to form the blob
537 form
.randomSignature
= identifier();
538 form
.sequence
= sequence
;
539 form
.params
= mParams
;
540 DbBlob
*blob
= encodeCore(form
, passphrase
, pubAcl
, privAcl
);
543 db
.allocator
.free(pubAcl
);
544 db
.allocator
.free(privAcl
);
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.
554 void Database::Common::setupKeys(const AccessCredentials
*cred
)
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
);
561 // we have the passphrase now
562 generateNewSecrets();
564 // we're unlocked now
571 // Perform deferred lock processing for a database.
573 void Database::Common::action()
575 IFDEBUG(debug("SSdb", "common %s(%p) locked by timer (%d refs)",
576 dbName(), this, int(useCount
)));
580 void Database::Common::activity()
583 Server::active().setTimer(this, int(mParams
.idleTimeout
));