2  * Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved. 
   4  * @APPLE_LICENSE_HEADER_START@ 
   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 
  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. 
  21  * @APPLE_LICENSE_HEADER_END@ 
  26 // token - internal representation of a (single distinct) hardware token 
  29 #include "tokendatabase.h" 
  31 #include "notifications.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> 
  40 #include <sys/types.h> 
  45 using namespace MDSClient
; 
  51 Token::SSIDMap 
Token::mSubservices
; 
  52 // Make sure to always take mSSIDLock after we take the Token lock 
  53 // itself or own it's own. 
  54 Mutex 
Token::mSSIDLock
; 
  58 // Token construction and destruction is trivial; the good stuff 
  59 // happens in insert() and remove() below. 
  62         : mFaulted(false), mTokend(NULL
), mResetLevel(1) 
  64         secdebug("token", "%p created", this); 
  70         secdebug("token", "%p (%s:%ld) destroyed", 
  71                 this, mGuid
.toString().c_str(), mSubservice
); 
  75 Reader 
&Token::reader() const 
  77         return referent
< ::Reader
>(); 
  80 TokenDaemon 
&Token::tokend() 
  82         StLock
<Mutex
> _(*this); 
  84                 CssmError::throwMe(CSSM_ERRCODE_DEVICE_FAILED
); 
  88                 CssmError::throwMe(CSSM_ERRCODE_DEVICE_FAILED
); 
  93 // We don't currently use a database handle to tokend. 
  94 // This is just to satisfy the TokenAcl. 
  96 GenericHandle 
Token::tokenHandle() const 
  98         return noDb
;    // we don't currently use tokend-side DbHandles 
 103 // Token is the SecurityServerAcl for the token 
 105 AclKind 
Token::aclKind() const 
 110 Token 
&Token::token() 
 117 // Find Token by subservice id. 
 118 // Throws if ssid is invalid (i.e. always returns non-NULL) 
 120 RefPointer
<Token
> Token::find(uint32 ssid
) 
 122         StLock
<Mutex
> _(mSSIDLock
); 
 123         SSIDMap::const_iterator it 
= mSubservices
.find(ssid
); 
 124         if (it 
== mSubservices
.end()) 
 125                 CssmError::throwMe(CSSMERR_CSSM_INVALID_SUBSERVICEID
); 
 133 // A Token has a "reset level", a number that is incremented whenever a token 
 134 // (hardware) reset is reported (as an error) by tokend. TokenAcls have their 
 135 // own matching level, which is that of the Token's when the ACL was last synchronized 
 136 // with tokend. Thus, incrementing the reset level invalidates all TokenAcls 
 137 // (without the need to enumerate them all). 
 138 // Note that a Token starts with a level of 1, while ACLs start at zero. This forces 
 139 // them to initially load their state from tokend. 
 141 Token::ResetGeneration 
Token::resetGeneration() const 
 146 void Token::resetAcls() 
 148         StLock
<Mutex
> _(*this); 
 150         secdebug("token", "%p reset (level=%d, propagating to %ld common(s)", 
 151                 this, mResetLevel
, mCommons
.size()); 
 152         for (CommonSet::const_iterator it 
= mCommons
.begin(); it 
!= mCommons
.end(); ) 
 153                 RefPointer
<TokenDbCommon
>(*it
++)->resetAcls(); 
 156 void Token::addCommon(TokenDbCommon 
&dbc
) 
 158         mCommons
.insert(&dbc
); 
 161 void Token::removeCommon(TokenDbCommon 
&dbc
) 
 163         assert(mCommons
.find(&dbc
) != mCommons
.end()); 
 164         mCommons
.erase(&dbc
); 
 169 // Process the logical insertion of a Token into a Reader. 
 170 // From the client's point of view, this is where the CSSM subservice is created, 
 171 // characterized, and activated. From tokend's point of view, this is where 
 172 // we're analyzing the token, determine its characteristics, and get ready to 
 175 void Token::insert(::Reader 
&slot
) 
 178                 // this might take a while... 
 179                 Server::active().longTermActivity(); 
 181                 // take Token lock and hold throughout insertion 
 182                 StLock
<Mutex
> _(*this); 
 184                 Syslog::debug("token inserted into reader %s", slot
.name().c_str()); 
 185                 secdebug("token", "%p begin insertion into slot %p (reader %s)", 
 186                         this, &slot
, slot
.name().c_str()); 
 188                 mState 
= slot
.pcscState(); 
 190                 RefPointer
<TokenDaemon
> tokend 
= chooseTokend(); 
 192                         secdebug("token", "%p no token daemons available - faulting this card", this); 
 196                 // tell the tokend object to relay faults to us 
 197                 tokend
->faultRelay(this); 
 199                 // locate or establish cache directories 
 200                 if (tokend
->hasTokenUid()) { 
 201                         secdebug("token", "%p CHOOSING %s (score=%ld, uid=\"%s\")", 
 202                                 this, tokend
->bundlePath().c_str(), tokend
->score(), tokend
->tokenUid().c_str()); 
 203                         mCache 
= new TokenCache::Token(reader().cache
, 
 204                                 tokend
->bundleIdentifier() + ":" + tokend
->tokenUid()); 
 206                         secdebug("token", "%p CHOOSING %s (score=%ld, temporary)", 
 207                                 this, tokend
->bundlePath().c_str(), tokend
->score()); 
 208                         mCache 
= new TokenCache::Token(reader().cache
); 
 210                 secdebug("token", "%p token cache at %s", this, mCache
->root().c_str()); 
 212                 // here's the primary parameters of the new subservice 
 213                 mGuid 
= gGuidAppleSdCSPDL
; 
 214                 mSubservice 
= mCache
->subservice(); 
 216                 // establish work areas with tokend 
 217                 char mdsDirectory
[PATH_MAX
]; 
 218                 char printName
[PATH_MAX
]; 
 219                 tokend
->establish(mGuid
, mSubservice
, 
 220                         (mCache
->type() != TokenCache::Token::existing 
? kSecTokendEstablishNewCache 
: 0) | kSecTokendEstablishMakeMDS
, 
 221                         mCache
->cachePath().c_str(), mCache
->workPath().c_str(), 
 222                         mdsDirectory
, printName
); 
 224                 // establish print name 
 225                 if (mCache
->type() == TokenCache::Token::existing
) { 
 226                         mPrintName 
= mCache
->printName(); 
 227                         if (mPrintName
.empty()) 
 228                                 mPrintName 
= printName
; 
 230                         mPrintName 
= printName
; 
 231                 if (mPrintName
.empty()) { 
 232                         // last resort - new card and tokend didn't give us one 
 233                         snprintf(printName
, sizeof(printName
), "smart card #%ld", mSubservice
); 
 234                         mPrintName 
= printName
; 
 236                 if (mCache
->type() != TokenCache::Token::existing
) 
 237                         mCache
->printName(mPrintName
);          // store in cache 
 240                 secdebug("token", "%p installing MDS from %s(%s)", this, 
 241                         tokend
->bundlePath().c_str(), 
 242                         mdsDirectory
[0] ? mdsDirectory 
: "ALL"); 
 243                 string holdGuid 
= mGuid
.toString();     // extend lifetime of .toString() 
 244                 MDS_InstallDefaults mdsDefaults 
= { 
 247                         tokend
->hasTokenUid() ? tokend
->tokenUid().c_str() : "", 
 248                         this->printName().c_str() 
 250                 mds().install(&mdsDefaults
, 
 251                         tokend
->bundlePath().c_str(), 
 252                         mdsDirectory
[0] ? mdsDirectory 
: NULL
, 
 256                         // commit to insertion 
 257                         StLock
<Mutex
> _(mSSIDLock
); 
 258                         assert(mSubservices
.find(mSubservice
) == mSubservices
.end()); 
 259                         mSubservices
.insert(make_pair(mSubservice
, this)); 
 262                 // assign mTokend right before notification - mustn't be set if 
 263                 // anything goes wrong during insertion 
 266                 notify(kNotificationCDSAInsertion
); 
 268                 Syslog::notice("reader %s inserted token \"%s\" (%s) subservice %ld using driver %s", 
 269                         slot
.name().c_str(), mPrintName
.c_str(), 
 270                         mTokend
->hasTokenUid() ? mTokend
->tokenUid().c_str() : "NO UID", 
 271                         mSubservice
, mTokend
->bundleIdentifier().c_str()); 
 272                 secdebug("token", "%p inserted as %s:%ld", this, mGuid
.toString().c_str(), mSubservice
); 
 273         } catch (const CommonError 
&err
) { 
 274                 Syslog::notice("token in reader %s cannot be used (error %ld)", slot
.name().c_str(), err
.osStatus()); 
 275                 secdebug("token", "exception during insertion processing"); 
 278                 // exception thrown during insertion processing. Mark faulted 
 279                 Syslog::notice("token in reader %s cannot be used", slot
.name().c_str()); 
 280                 secdebug("token", "exception during insertion processing"); 
 287 // Process the logical removal of a Token from a Reader. 
 288 // Most of the time, this is asynchronous - someone has yanked the physical 
 289 // token out of a physical slot, and we're left with changing our universe 
 290 // to conform to the new realities. Reality #1 is that we can't talk to the 
 291 // physical token anymore. 
 293 // Note that if we're in FAULT mode, there really isn't a TokenDaemon around 
 294 // to kick. We're just holding on to represent the fact that there *is* a (useless) 
 295 // token in the slot, and now it's been finally yanked. Good riddance. 
 299         StLock
<Mutex
> _(*this); 
 300         Syslog::notice("reader %s removed token \"%s\" (%s) subservice %ld", 
 301                         reader().name().c_str(), mPrintName
.c_str(), 
 303                                 ? (mTokend
->hasTokenUid() ? mTokend
->tokenUid().c_str() : "NO UID") 
 306         secdebug("token", "%p begin removal from slot %p (reader %s)", 
 307                 this, &reader(), reader().name().c_str()); 
 309                 mTokend
->faultRelay(NULL
);              // unregister (no more faults, please) 
 310         notify(kNotificationCDSARemoval
); 
 311         mds().uninstall(mGuid
.toString().c_str(), mSubservice
); 
 313         secdebug("token", "%p removal complete", this); 
 318 // Set the token to fault state. 
 319 // This essentially "cuts off" all operations on an inserted token and makes 
 320 // them fail. It also sends a FAULT notification via CSSM to any clients. 
 321 // Only one fault is actually processed; multiple calls are ignored. 
 323 // Note that a faulted token is not REMOVED; it's still physically present. 
 324 // No fault is declared when a token is actually removed. 
 326 void Token::fault(bool async
) 
 328         StLock
<Mutex
> _(*this); 
 329         if (!mFaulted
) {        // first one 
 330                 secdebug("token", "%p %s FAULT", this, async 
? "ASYNCHRONOUS" : "SYNCHRONOUS"); 
 335                 // send CDSA notification 
 336                 notify(kNotificationCDSAFailure
); 
 338                 // cast off our TokenDaemon for good 
 342         // if this is a synchronous fault, abort this operation now 
 344                 CssmError::throwMe(CSSM_ERRCODE_DEVICE_FAILED
); 
 348 void Token::relayFault(bool async
) 
 350         secdebug("token", "%p fault relayed from tokend", this); 
 356 // This is the "kill" hook for Token as a Node<> object. 
 360         StLock
<Mutex
> _(*this); 
 363                 mTokend 
= NULL
;                                 // cast loose our tokend (if any) 
 364                 // Take us out of the map 
 365                 StLock
<Mutex
> _(mSSIDLock
); 
 366                 SSIDMap::iterator it 
= mSubservices
.find(mSubservice
); 
 367                 assert(it 
!= mSubservices
.end() && it
->second 
== this); 
 368                 if (it 
!= mSubservices
.end() && it
->second 
== this) 
 369                         mSubservices
.erase(it
); 
 372         resetAcls();                                    // release our TokenDbCommons 
 373         PerGlobal::kill();                              // generic action 
 379 // Send CDSA-layer notifications for this token. 
 380 // These events are usually received by CDSA plugins working with securityd. 
 382 void Token::notify(NotificationEvent event
) 
 384     NameValueDictionary nvd
; 
 385         CssmSubserviceUid 
ssuid(mGuid
, NULL
, h2n (mSubservice
), 
 386                 h2n(CSSM_SERVICE_DL 
| CSSM_SERVICE_CSP
)); 
 387         nvd
.Insert(new NameValuePair(SSUID_KEY
, CssmData::wrap(ssuid
))); 
 391         // inject notification into Security event system 
 392     Listener::notify(kNotificationDomainCDSA
, event
, data
); 
 400 // Choose a token daemon for our card. 
 402 // Right now, we probe tokends sequentially. If there are many tokends, it would be 
 403 // faster to launch them in parallel (relying on PCSC transactions to separate them); 
 404 // but it's not altogether clear whether this would slow things down on low-memory 
 405 // systems by forcing (excessive) swapping. There is room for future experimentation. 
 407 RefPointer
<TokenDaemon
> Token::chooseTokend() 
 409         //@@@ CodeRepository should learn to update from disk changes to be re-usable 
 410         CodeRepository
<GenericBundle
> candidates("Security/tokend", ".tokend", "TOKENDAEMONPATH", false); 
 412         //@@@ we could sort by reverse "maxScore" and avoid launching those who won't cut it anyway... 
 414         RefPointer
<TokenDaemon
> leader
; 
 415         for (CodeRepository
<GenericBundle
>::const_iterator it 
= candidates
.begin(); 
 416                         it 
!= candidates
.end(); it
++) { 
 418                         // any pre-launch screening of candidate *it goes here 
 420                         RefPointer
<TokenDaemon
> tokend 
= new TokenDaemon(*it
, 
 421                                 reader().name(), reader().pcscState(), reader().cache
); 
 423                         if (tokend
->state() == ServerChild::dead
)       // ah well, this one's no good 
 426                         // probe the (single) tokend 
 427                         if (!tokend
->probe())           // non comprende... 
 430                         // we got a contender! 
 431                         if (!leader 
|| tokend
->score() > leader
->score()) 
 432                                 leader 
= tokend
;                // a new front runner, he is... 
 434                         secdebug("token", "exception setting up %s (moving on)", (*it
)->canonicalPath().c_str()); 
 442 // Token::Access mediates calls through TokenDaemon to the actual daemon out there. 
 444 Token::Access::Access(Token 
&myToken
) 
 447         mTokend 
= &token
.tokend();      // throws if faulted or otherwise inappropriate 
 450 Token::Access::~Access() 
 456 // Debug dump support 
 458 #if defined(DEBUGDUMP) 
 460 void Token::dumpNode() 
 462         PerGlobal::dumpNode(); 
 463         Debug::dump(" %s[%ld] tokend=%p", 
 464                 mGuid
.toString().c_str(), mSubservice
, mTokend
.get());