1/* 2 * Copyright (c) 2004,2007 Apple Inc. All Rights Reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 6 * This file contains Original Code and/or Modifications of Original Code 7 * as defined in and that are subject to the Apple Public Source License 8 * Version 2.0 (the 'License'). You may not use this file except in 9 * compliance with the License. Please obtain a copy of the License at 10 * http://www.opensource.apple.com/apsl/ and read it before using this 11 * file. 12 * 13 * The Original Code and all software distributed under the License are 14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 18 * Please see the License for the specific language governing rights and 19 * limitations under the License. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23 24 25// 26// token - internal representation of a (single distinct) hardware token 27// 28#include "token.h" 29#include "tokendatabase.h" 30#include "reader.h" 31#include "notifications.h" 32#include "child.h" 33#include "server.h" 34#include <securityd_client/dictionary.h> 35#include <security_utilities/coderepository.h> 36#include <security_utilities/logging.h> 37#include <security_cdsa_client/mdsclient.h> 38#include <SecurityTokend/SecTokend.h> 39 40#include <sys/types.h> 41#include <sys/wait.h> 42#include <grp.h> 43#include <pwd.h> 44 45using namespace MDSClient; 46 47 48// 49// SSID -> Token map 50// 51Token::SSIDMap Token::mSubservices; 52// Make sure to always take mSSIDLock after we take the Token lock 53// itself or own it's own. 54Mutex Token::mSSIDLock; 55 56 57// 58// Token construction and destruction is trivial; the good stuff 59// happens in insert() and remove() below. 60// 61Token::Token() 62 : mFaulted(false), mTokend(NULL), mResetLevel(1) 63{ 64 secdebug("token", "%p created", this); 65} 66 67 68Token::~Token() 69{ 70 secdebug("token", "%p (%s:%d) destroyed", 71 this, mGuid.toString().c_str(), mSubservice); 72} 73 74 75Reader &Token::reader() const 76{ 77 return referent< ::Reader>(); 78} 79 80TokenDaemon &Token::tokend() 81{ 82 StLock<Mutex> _(*this); 83 if (mFaulted) 84 CssmError::throwMe(CSSM_ERRCODE_DEVICE_FAILED); 85 if (mTokend) 86 return *mTokend; 87 else 88 CssmError::throwMe(CSSM_ERRCODE_DEVICE_FAILED); 89} 90 91 92// 93// We don't currently use a database handle to tokend. 94// This is just to satisfy the TokenAcl. 95// 96GenericHandle Token::tokenHandle() const 97{ 98 return noDb; // we don't currently use tokend-side DbHandles 99} 100 101 102// 103// Token is the SecurityServerAcl for the token 104// 105AclKind Token::aclKind() const 106{ 107 return dbAcl; 108} 109 110Token &Token::token() 111{ 112 return *this; 113} 114 115 116// 117// Find Token by subservice id. 118// Throws if ssid is invalid (i.e. always returns non-NULL) 119// 120RefPointer<Token> Token::find(uint32 ssid) 121{ 122 StLock<Mutex> _(mSSIDLock); 123 SSIDMap::const_iterator it = mSubservices.find(ssid); 124 if (it == mSubservices.end()) 125 CssmError::throwMe(CSSMERR_CSSM_INVALID_SUBSERVICEID); 126 else 127 return it->second; 128} 129 130 131// 132// We override getAcl to provide PIN state feedback 133// 134void Token::getAcl(const char *tag, uint32 &count, AclEntryInfo *&acls) 135{ 136 if (pinFromAclTag(tag, "?")) { // read from tokend - do not cache 137 AclEntryInfo *racls; 138 token().tokend().getAcl(aclKind(), tokenHandle(), tag, count, racls); 139 // make a chunk-copy because that's the contract we have with the caller 140 acls = Allocator::standard().alloc<AclEntryInfo>(count * sizeof(AclEntryInfo)); 141 memcpy(acls, racls, count * sizeof(AclEntryInfo)); 142 ChunkCopyWalker copy; 143 for (uint32 n = 0; n < count; n++) 144 walk(copy, acls[n]); 145 return; 146 } 147 148 TokenAcl::cssmGetAcl(tag, count, acls); 149} 150 151 152// 153// Reset management. 154// A Token has a "reset level", a number that is incremented whenever a token 155// (hardware) reset is reported (as an error) by tokend. TokenAcls have their 156// own matching level, which is that of the Token's when the ACL was last synchronized 157// with tokend. Thus, incrementing the reset level invalidates all TokenAcls 158// (without the need to enumerate them all). 159// Note that a Token starts with a level of 1, while ACLs start at zero. This forces 160// them to initially load their state from tokend. 161// 162Token::ResetGeneration Token::resetGeneration() const 163{ 164 return mResetLevel; 165} 166 167void Token::resetAcls() 168{ 169 CommonSet tmpCommons; 170 { 171 StLock<Mutex> _(*this); 172 mResetLevel++; 173 secdebug("token", "%p reset (level=%d, propagating to %ld common(s)", 174 this, mResetLevel, mCommons.size()); 175 // Make a copy to avoid deadlock with TokenDbCommon lock 176 tmpCommons = mCommons; 177 } 178 for (CommonSet::const_iterator it = tmpCommons.begin(); it != tmpCommons.end();) 179 RefPointer<TokenDbCommon>(*it++)->resetAcls(); 180} 181 182void Token::addCommon(TokenDbCommon &dbc) 183{ 184 secdebug("token", "%p addCommon TokenDbCommon %p", this, &dbc); 185 mCommons.insert(&dbc); 186} 187 188void Token::removeCommon(TokenDbCommon &dbc) 189{ 190 secdebug("token", "%p removeCommon TokenDbCommon %p", this, &dbc); 191 if (mCommons.find(&dbc) != mCommons.end()) 192 mCommons.erase(&dbc); 193} 194 195 196// 197// Process the logical insertion of a Token into a Reader. 198// From the client's point of view, this is where the CSSM subservice is created, 199// characterized, and activated. From tokend's point of view, this is where 200// we're analyzing the token, determine its characteristics, and get ready to 201// use it. 202// 203void Token::insert(::Reader &slot, RefPointer<TokenDaemon> tokend) 204{ 205 try { 206 // this might take a while... 207 Server::active().longTermActivity(); 208 referent(slot); 209 mState = slot.pcscState(); 210 211 if (tokend == NULL) { 212 // no pre-determined Tokend - search for one 213 if (!(tokend = chooseTokend())) { 214 secdebug("token", "%p no token daemons available - faulting this card", this); 215 fault(false); // throws 216 } 217 } 218 219 // take Token lock and hold throughout insertion 220 StLock<Mutex> _(*this); 221 222 Syslog::debug("token inserted into reader %s", slot.name().c_str()); 223 secdebug("token", "%p begin insertion into slot %p (reader %s)", 224 this, &slot, slot.name().c_str()); 225 226 // tell the tokend object to relay faults to us 227 tokend->faultRelay(this); 228 229 // locate or establish cache directories 230 if (tokend->hasTokenUid()) { 231 secdebug("token", "%p using %s (score=%d, uid=\"%s\")", 232 this, tokend->bundlePath().c_str(), tokend->score(), tokend->tokenUid().c_str()); 233 mCache = new TokenCache::Token(reader().cache, 234 tokend->bundleIdentifier() + ":" + tokend->tokenUid()); 235 } else { 236 secdebug("token", "%p using %s (score=%d, temporary)", 237 this, tokend->bundlePath().c_str(), tokend->score()); 238 mCache = new TokenCache::Token(reader().cache); 239 } 240 secdebug("token", "%p token cache at %s", this, mCache->root().c_str()); 241 242 // here's the primary parameters of the new subservice 243 mGuid = gGuidAppleSdCSPDL; 244 mSubservice = mCache->subservice(); 245 246 // establish work areas with tokend 247 char mdsDirectory[PATH_MAX]; 248 char printName[PATH_MAX]; 249 tokend->establish(mGuid, mSubservice, 250 (mCache->type() != TokenCache::Token::existing ? kSecTokendEstablishNewCache : 0) | kSecTokendEstablishMakeMDS, 251 mCache->cachePath().c_str(), mCache->workPath().c_str(), 252 mdsDirectory, printName); 253 254 // establish print name 255 if (mCache->type() == TokenCache::Token::existing) { 256 mPrintName = mCache->printName(); 257 if (mPrintName.empty()) 258 mPrintName = printName; 259 } else 260 mPrintName = printName; 261 if (mPrintName.empty()) { 262 // last resort - new card and tokend didn't give us one 263 snprintf(printName, sizeof(printName), "smart card #%d", mSubservice); 264 mPrintName = printName; 265 } 266 if (mCache->type() != TokenCache::Token::existing) 267 mCache->printName(mPrintName); // store in cache 268 269 // install MDS 270 secdebug("token", "%p installing MDS from %s(%s)", this, 271 tokend->bundlePath().c_str(), 272 mdsDirectory[0] ? mdsDirectory : "ALL"); 273 string holdGuid = mGuid.toString(); // extend lifetime of std::string 274 string holdTokenUid; 275 if (tokend->hasTokenUid()) 276 holdTokenUid = tokend->tokenUid(); 277 string holdPrintName = this->printName(); 278 MDS_InstallDefaults mdsDefaults = { 279 holdGuid.c_str(), 280 mSubservice, 281 holdTokenUid.c_str(), 282 holdPrintName.c_str() 283 }; 284 mds().install(&mdsDefaults, 285 tokend->bundlePath().c_str(), 286 mdsDirectory[0] ? mdsDirectory : NULL, 287 NULL); 288 289 { 290 // commit to insertion 291 StLock<Mutex> _(mSSIDLock); 292 assert(mSubservices.find(mSubservice) == mSubservices.end()); 293 mSubservices.insert(make_pair(mSubservice, this)); 294 } 295 296 // assign mTokend right before notification - mustn't be set if 297 // anything goes wrong during insertion 298 mTokend = tokend; 299 300 notify(kNotificationCDSAInsertion); 301 302 Syslog::notice("reader %s inserted token \"%s\" (%s) subservice %ld using driver %s", 303 slot.name().c_str(), mPrintName.c_str(), 304 mTokend->hasTokenUid() ? mTokend->tokenUid().c_str() : "NO UID", 305 mSubservice, mTokend->bundleIdentifier().c_str()); 306 secdebug("token", "%p inserted as %s:%d", this, mGuid.toString().c_str(), mSubservice); 307 } catch (const CommonError &err) { 308 Syslog::notice("token in reader %s cannot be used (error %ld)", slot.name().c_str(), err.osStatus()); 309 secdebug("token", "exception during insertion processing"); 310 fault(false); 311 } catch (...) { 312 // exception thrown during insertion processing. Mark faulted 313 Syslog::notice("token in reader %s cannot be used", slot.name().c_str()); 314 secdebug("token", "exception during insertion processing"); 315 fault(false); 316 } 317} 318 319 320// 321// Process the logical removal of a Token from a Reader. 322// Most of the time, this is asynchronous - someone has yanked the physical 323// token out of a physical slot, and we're left with changing our universe 324// to conform to the new realities. Reality #1 is that we can't talk to the 325// physical token anymore. 326// 327// Note that if we're in FAULT mode, there really isn't a TokenDaemon around 328// to kick. We're just holding on to represent the fact that there *is* a (useless) 329// token in the slot, and now it's been finally yanked. Good riddance. 330// 331void Token::remove() 332{ 333 StLock<Mutex> _(*this); 334 Syslog::notice("reader %s removed token \"%s\" (%s) subservice %ld", 335 reader().name().c_str(), mPrintName.c_str(), 336 mTokend 337 ? (mTokend->hasTokenUid() ? mTokend->tokenUid().c_str() : "NO UID") 338 : "NO tokend", 339 mSubservice); 340 secdebug("token", "%p begin removal from slot %p (reader %s)", 341 this, &reader(), reader().name().c_str()); 342 if (mTokend) 343 mTokend->faultRelay(NULL); // unregister (no more faults, please) 344 mds().uninstall(mGuid.toString().c_str(), mSubservice); 345 secdebug("token", "%p mds uninstall complete", this); 346 this->kill(); 347 secdebug("token", "%p kill complete", this); 348 notify(kNotificationCDSARemoval); 349 secdebug("token", "%p removal complete", this); 350} 351 352 353// 354// Set the token to fault state. 355// This essentially "cuts off" all operations on an inserted token and makes 356// them fail. It also sends a FAULT notification via CSSM to any clients. 357// Only one fault is actually processed; multiple calls are ignored. 358// 359// Note that a faulted token is not REMOVED; it's still physically present. 360// No fault is declared when a token is actually removed. 361// 362void Token::fault(bool async) 363{ 364 StLock<Mutex> _(*this); 365 if (!mFaulted) { // first one 366 secdebug("token", "%p %s FAULT", this, async ? "ASYNCHRONOUS" : "SYNCHRONOUS"); 367 368 // mark faulted 369 mFaulted = true; 370 371 // send CDSA notification 372 notify(kNotificationCDSAFailure); 373 374 // cast off our TokenDaemon for good 375//>>> mTokend = NULL; 376 } 377 378 // if this is a synchronous fault, abort this operation now 379 if (!async) 380 CssmError::throwMe(CSSM_ERRCODE_DEVICE_FAILED); 381} 382 383 384void Token::relayFault(bool async) 385{ 386 secdebug("token", "%p fault relayed from tokend", this); 387 this->fault(async); 388} 389 390 391// 392// This is the "kill" hook for Token as a Node<> object. 393// 394void Token::kill() 395{ 396 // Avoid holding the lock across call to resetAcls 397 // This can cause deadlock on card removal 398 { 399 StLock<Mutex> _(*this); 400 if (mTokend) 401 { 402 mTokend = NULL; // cast loose our tokend (if any) 403 // Take us out of the map 404 StLock<Mutex> _(mSSIDLock); 405 SSIDMap::iterator it = mSubservices.find(mSubservice); 406 assert(it != mSubservices.end() && it->second == this); 407 if (it != mSubservices.end() && it->second == this) 408 mSubservices.erase(it); 409 } 410 } 411 412 resetAcls(); // release our TokenDbCommons 413 PerGlobal::kill(); // generic action 414 415} 416 417 418// 419// Send CDSA-layer notifications for this token. 420// These events are usually received by CDSA plugins working with securityd. 421// 422void Token::notify(NotificationEvent event) 423{ 424 NameValueDictionary nvd; 425 CssmSubserviceUid ssuid(mGuid, NULL, h2n (mSubservice), 426 h2n(CSSM_SERVICE_DL | CSSM_SERVICE_CSP)); 427 nvd.Insert(new NameValuePair(SSUID_KEY, CssmData::wrap(ssuid))); 428 CssmData data; 429 nvd.Export(data); 430 431 // inject notification into Security event system 432 Listener::notify(kNotificationDomainCDSA, event, data); 433 434 // clean up 435 free (data.data()); 436} 437 438 439// 440// Choose a token daemon for our card. 441// 442// Right now, we probe tokends sequentially. If there are many tokends, it would be 443// faster to launch them in parallel (relying on PCSC transactions to separate them); 444// but it's not altogether clear whether this would slow things down on low-memory 445// systems by forcing (excessive) swapping. There is room for future experimentation. 446// 447RefPointer<TokenDaemon> Token::chooseTokend() 448{ 449 //@@@ CodeRepository should learn to update from disk changes to be re-usable 450 CodeRepository<Bundle> candidates("Security/tokend", ".tokend", "TOKENDAEMONPATH", false); 451 candidates.update(); 452 //@@@ we could sort by reverse "maxScore" and avoid launching those who won't cut it anyway... 453 454 RefPointer<TokenDaemon> leader; 455 for (CodeRepository<Bundle>::const_iterator it = candidates.begin(); 456 it != candidates.end(); it++) { 457 RefPointer<Bundle> candidate = *it; 458 try { 459 // skip software token daemons - ineligible for automatic choosing 460 if (CFTypeRef type = (*it)->infoPlistItem("TokendType")) 461 if (CFEqual(type, CFSTR("software"))) 462 continue; 463 464 // okay, launch it and let it try 465 RefPointer<TokenDaemon> tokend = new TokenDaemon(candidate, 466 reader().name(), reader().pcscState(), reader().cache); 467 468 if (tokend->state() == ServerChild::dead) // ah well, this one's no good 469 continue; 470 471 // probe the (single) tokend 472 if (!tokend->probe()) // non comprende... 473 continue; 474 475 // we got a contender! 476 if (!leader || tokend->score() > leader->score()) 477 leader = tokend; // a new front runner, he is... 478 } catch (...) { 479 secdebug("token", "exception setting up %s (moving on)", candidate->canonicalPath().c_str()); 480 } 481 } 482 return leader; 483} 484 485 486// 487// Token::Access mediates calls through TokenDaemon to the actual daemon out there. 488// 489Token::Access::Access(Token &myToken) 490 : token(myToken) 491{ 492 mTokend = &token.tokend(); // throws if faulted or otherwise inappropriate 493} 494 495Token::Access::~Access() 496{ 497} 498 499 500// 501// Debug dump support 502// 503#if defined(DEBUGDUMP) 504 505void Token::dumpNode() 506{ 507 PerGlobal::dumpNode(); 508 Debug::dump(" %s[%d] tokend=%p", 509 mGuid.toString().c_str(), mSubservice, mTokend.get()); 510} 511 512#endif //DEBUGDUMP 513