2  * Copyright (c) 2004-2008,2013 Apple 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> 
  44 #include <msgtracer_client.h> 
  46 using namespace MDSClient
; 
  52 Token::SSIDMap 
Token::mSubservices
; 
  53 // Make sure to always take mSSIDLock after we take the Token lock 
  54 // itself or own it's own. 
  55 Mutex 
Token::mSSIDLock
; 
  59 // Token construction and destruction is trivial; the good stuff 
  60 // happens in insert() and remove() below. 
  63         : mFaulted(false), mTokend(NULL
), mResetLevel(1) 
  65         secdebug("token", "%p created", this); 
  71         secdebug("token", "%p (%s:%d) destroyed", 
  72                 this, mGuid
.toString().c_str(), mSubservice
); 
  76 Reader 
&Token::reader() const 
  78         return referent
< ::Reader
>(); 
  81 TokenDaemon 
&Token::tokend() 
  83         StLock
<Mutex
> _(*this); 
  85                 CssmError::throwMe(CSSM_ERRCODE_DEVICE_FAILED
); 
  89                 CssmError::throwMe(CSSM_ERRCODE_DEVICE_FAILED
); 
  94 // We don't currently use a database handle to tokend. 
  95 // This is just to satisfy the TokenAcl. 
  97 GenericHandle 
Token::tokenHandle() const 
  99         return noDb
;    // we don't currently use tokend-side DbHandles 
 104 // Token is the SecurityServerAcl for the token 
 106 AclKind 
Token::aclKind() const 
 111 Token 
&Token::token() 
 118 // Find Token by subservice id. 
 119 // Throws if ssid is invalid (i.e. always returns non-NULL) 
 121 RefPointer
<Token
> Token::find(uint32 ssid
) 
 123         StLock
<Mutex
> _(mSSIDLock
); 
 124         SSIDMap::const_iterator it 
= mSubservices
.find(ssid
); 
 125         if (it 
== mSubservices
.end()) 
 126                 CssmError::throwMe(CSSMERR_CSSM_INVALID_SUBSERVICEID
); 
 133 // We override getAcl to provide PIN state feedback 
 135 void Token::getAcl(const char *tag
, uint32 
&count
, AclEntryInfo 
*&acls
) 
 137         if (pinFromAclTag(tag
, "?")) {  // read from tokend - do not cache 
 139                 token().tokend().getAcl(aclKind(), tokenHandle(), tag
, count
, racls
); 
 140                 // make a chunk-copy because that's the contract we have with the caller 
 141                 acls 
= Allocator::standard().alloc
<AclEntryInfo
>(count 
* sizeof(AclEntryInfo
)); 
 142                 memcpy(acls
, racls
, count 
* sizeof(AclEntryInfo
)); 
 143                 ChunkCopyWalker copy
; 
 144                 for (uint32 n 
= 0; n 
< count
; n
++) 
 149         TokenAcl::cssmGetAcl(tag
, count
, acls
); 
 155 // A Token has a "reset level", a number that is incremented whenever a token 
 156 // (hardware) reset is reported (as an error) by tokend. TokenAcls have their 
 157 // own matching level, which is that of the Token's when the ACL was last synchronized 
 158 // with tokend. Thus, incrementing the reset level invalidates all TokenAcls 
 159 // (without the need to enumerate them all). 
 160 // Note that a Token starts with a level of 1, while ACLs start at zero. This forces 
 161 // them to initially load their state from tokend. 
 163 Token::ResetGeneration 
Token::resetGeneration() const 
 168 void Token::resetAcls() 
 170         CommonSet tmpCommons
; 
 172                 StLock
<Mutex
> _(*this); 
 174                 secdebug("token", "%p reset (level=%d, propagating to %ld common(s)", 
 175                         this, mResetLevel
, mCommons
.size()); 
 176                 // Make a copy to avoid deadlock with TokenDbCommon lock 
 177                 tmpCommons 
= mCommons
; 
 179         for (CommonSet::const_iterator it 
= tmpCommons
.begin(); it 
!= tmpCommons
.end();) 
 180                 RefPointer
<TokenDbCommon
>(*it
++)->resetAcls(); 
 183 void Token::addCommon(TokenDbCommon 
&dbc
) 
 185         secdebug("token", "%p addCommon TokenDbCommon %p", this, &dbc
); 
 186         mCommons
.insert(&dbc
); 
 189 void Token::removeCommon(TokenDbCommon 
&dbc
) 
 191         secdebug("token", "%p removeCommon TokenDbCommon %p", this, &dbc
); 
 192         if (mCommons
.find(&dbc
) != mCommons
.end()) 
 193                 mCommons
.erase(&dbc
); 
 198 // Process the logical insertion of a Token into a Reader. 
 199 // From the client's point of view, this is where the CSSM subservice is created, 
 200 // characterized, and activated. From tokend's point of view, this is where 
 201 // we're analyzing the token, determine its characteristics, and get ready to 
 204 void Token::insert(::Reader 
&slot
, RefPointer
<TokenDaemon
> tokend
) 
 207                 // this might take a while... 
 208                 Server::active().longTermActivity(); 
 210                 mState 
= slot
.pcscState(); 
 212                 if (tokend 
== NULL
) { 
 213                         // no pre-determined Tokend - search for one 
 214                         if (!(tokend 
= chooseTokend())) { 
 215                                 secdebug("token", "%p no token daemons available - faulting this card", this); 
 216                                 fault(false);   // throws 
 220                 // take Token lock and hold throughout insertion 
 221                 StLock
<Mutex
> _(*this); 
 223                 Syslog::debug("token inserted into reader %s", slot
.name().c_str()); 
 224                 secdebug("token", "%p begin insertion into slot %p (reader %s)", 
 225                         this, &slot
, slot
.name().c_str()); 
 227                 // tell the tokend object to relay faults to us 
 228                 tokend
->faultRelay(this); 
 230                 // locate or establish cache directories 
 231                 if (tokend
->hasTokenUid()) { 
 232                         secdebug("token", "%p using %s (score=%d, uid=\"%s\")", 
 233                                 this, tokend
->bundlePath().c_str(), tokend
->score(), tokend
->tokenUid().c_str()); 
 234                         mCache 
= new TokenCache::Token(reader().cache
, 
 235                                 tokend
->bundleIdentifier() + ":" + tokend
->tokenUid()); 
 237                         secdebug("token", "%p using %s (score=%d, temporary)", 
 238                                 this, tokend
->bundlePath().c_str(), tokend
->score()); 
 239                         mCache 
= new TokenCache::Token(reader().cache
); 
 241                 secdebug("token", "%p token cache at %s", this, mCache
->root().c_str()); 
 243                 // here's the primary parameters of the new subservice 
 244                 mGuid 
= gGuidAppleSdCSPDL
; 
 245                 mSubservice 
= mCache
->subservice(); 
 247                 // establish work areas with tokend 
 248                 char mdsDirectory
[PATH_MAX
]; 
 249                 char printName
[PATH_MAX
]; 
 250                 tokend
->establish(mGuid
, mSubservice
, 
 251                         (mCache
->type() != TokenCache::Token::existing 
? kSecTokendEstablishNewCache 
: 0) | kSecTokendEstablishMakeMDS
, 
 252                         mCache
->cachePath().c_str(), mCache
->workPath().c_str(), 
 253                         mdsDirectory
, printName
); 
 255                 // establish print name 
 256                 if (mCache
->type() == TokenCache::Token::existing
) { 
 257                         mPrintName 
= mCache
->printName(); 
 258                         if (mPrintName
.empty()) 
 259                                 mPrintName 
= printName
; 
 261                         mPrintName 
= printName
; 
 262                 if (mPrintName
.empty()) { 
 263                         // last resort - new card and tokend didn't give us one 
 264                         snprintf(printName
, sizeof(printName
), "smart card #%d", mSubservice
); 
 265                         mPrintName 
= printName
; 
 267                 if (mCache
->type() != TokenCache::Token::existing
) 
 268                         mCache
->printName(mPrintName
);          // store in cache 
 271                 secdebug("token", "%p installing MDS from %s(%s)", this, 
 272                         tokend
->bundlePath().c_str(), 
 273                         mdsDirectory
[0] ? mdsDirectory 
: "ALL"); 
 274                 string holdGuid 
= mGuid
.toString();     // extend lifetime of std::string 
 276                 if (tokend
->hasTokenUid()) 
 277                         holdTokenUid 
= tokend
->tokenUid(); 
 278                 string holdPrintName 
= this->printName(); 
 279                 MDS_InstallDefaults mdsDefaults 
= { 
 282                         holdTokenUid
.c_str(), 
 283                         holdPrintName
.c_str() 
 285                 mds().install(&mdsDefaults
, 
 286                         tokend
->bundlePath().c_str(), 
 287                         mdsDirectory
[0] ? mdsDirectory 
: NULL
, 
 291                         // commit to insertion 
 292                         StLock
<Mutex
> _(mSSIDLock
); 
 293                         assert(mSubservices
.find(mSubservice
) == mSubservices
.end()); 
 294                         mSubservices
.insert(make_pair(mSubservice
, this)); 
 297                 // assign mTokend right before notification - mustn't be set if 
 298                 // anything goes wrong during insertion 
 301                 notify(kNotificationCDSAInsertion
); 
 303                 Syslog::notice("reader %s inserted token \"%s\" (%s) subservice %ld using driver %s", 
 304                         slot
.name().c_str(), mPrintName
.c_str(), 
 305                         mTokend
->hasTokenUid() ? mTokend
->tokenUid().c_str() : "NO UID", 
 306                         mSubservice
, mTokend
->bundleIdentifier().c_str()); 
 307                 secdebug("token", "%p inserted as %s:%d", this, mGuid
.toString().c_str(), mSubservice
); 
 308         } catch (const CommonError 
&err
) { 
 309                 Syslog::notice("token in reader %s cannot be used (error %ld)", slot
.name().c_str(), err
.osStatus()); 
 310                 secdebug("token", "exception during insertion processing"); 
 313                 // exception thrown during insertion processing. Mark faulted 
 314                 Syslog::notice("token in reader %s cannot be used", slot
.name().c_str()); 
 315                 secdebug("token", "exception during insertion processing"); 
 322 // Process the logical removal of a Token from a Reader. 
 323 // Most of the time, this is asynchronous - someone has yanked the physical 
 324 // token out of a physical slot, and we're left with changing our universe 
 325 // to conform to the new realities. Reality #1 is that we can't talk to the 
 326 // physical token anymore. 
 328 // Note that if we're in FAULT mode, there really isn't a TokenDaemon around 
 329 // to kick. We're just holding on to represent the fact that there *is* a (useless) 
 330 // token in the slot, and now it's been finally yanked. Good riddance. 
 334         StLock
<Mutex
> _(*this); 
 335         Syslog::notice("reader %s removed token \"%s\" (%s) subservice %ld", 
 336                         reader().name().c_str(), mPrintName
.c_str(), 
 338                                 ? (mTokend
->hasTokenUid() ? mTokend
->tokenUid().c_str() : "NO UID") 
 341         secdebug("token", "%p begin removal from slot %p (reader %s)", 
 342                 this, &reader(), reader().name().c_str()); 
 344                 mTokend
->faultRelay(NULL
);              // unregister (no more faults, please) 
 345         mds().uninstall(mGuid
.toString().c_str(), mSubservice
); 
 346         secdebug("token", "%p mds uninstall complete", this); 
 348         secdebug("token", "%p kill complete", this); 
 349         notify(kNotificationCDSARemoval
); 
 350         secdebug("token", "%p removal complete", this); 
 355 // Set the token to fault state. 
 356 // This essentially "cuts off" all operations on an inserted token and makes 
 357 // them fail. It also sends a FAULT notification via CSSM to any clients. 
 358 // Only one fault is actually processed; multiple calls are ignored. 
 360 // Note that a faulted token is not REMOVED; it's still physically present. 
 361 // No fault is declared when a token is actually removed. 
 363 void Token::fault(bool async
) 
 365         StLock
<Mutex
> _(*this); 
 366         if (!mFaulted
) {        // first one 
 367                 secdebug("token", "%p %s FAULT", this, async 
? "ASYNCHRONOUS" : "SYNCHRONOUS"); 
 372                 // send CDSA notification 
 373                 notify(kNotificationCDSAFailure
); 
 375                 // cast off our TokenDaemon for good 
 376 //>>>           mTokend = NULL; 
 379         // if this is a synchronous fault, abort this operation now 
 381                 CssmError::throwMe(CSSM_ERRCODE_DEVICE_FAILED
); 
 385 void Token::relayFault(bool async
) 
 387         secdebug("token", "%p fault relayed from tokend", this); 
 393 // This is the "kill" hook for Token as a Node<> object. 
 397         // Avoid holding the lock across call to resetAcls 
 398         // This can cause deadlock on card removal 
 400                 StLock
<Mutex
> _(*this); 
 403                         mTokend 
= NULL
;                                 // cast loose our tokend (if any) 
 404                         // Take us out of the map 
 405                         StLock
<Mutex
> _(mSSIDLock
); 
 406                         SSIDMap::iterator it 
= mSubservices
.find(mSubservice
); 
 407                         assert(it 
!= mSubservices
.end() && it
->second 
== this); 
 408                         if (it 
!= mSubservices
.end() && it
->second 
== this) 
 409                                 mSubservices
.erase(it
); 
 413         resetAcls();                                    // release our TokenDbCommons 
 414         PerGlobal::kill();                              // generic action 
 420 // Send CDSA-layer notifications for this token. 
 421 // These events are usually received by CDSA plugins working with securityd. 
 423 void Token::notify(NotificationEvent event
) 
 425     NameValueDictionary nvd
; 
 426         CssmSubserviceUid 
ssuid(mGuid
, NULL
, h2n (mSubservice
), 
 427                 h2n(CSSM_SERVICE_DL 
| CSSM_SERVICE_CSP
)); 
 428         nvd
.Insert(new NameValuePair(SSUID_KEY
, CssmData::wrap(ssuid
))); 
 432         // inject notification into Security event system 
 433     Listener::notify(kNotificationDomainCDSA
, event
, data
); 
 439 static void mt_log_ctk_tokend(const char *signature
, const char *signature2
) 
 441     msgtracer_log_with_keys("com.apple.ctk.tokend", ASL_LEVEL_NOTICE
, 
 442                             "com.apple.message.signature", signature
, 
 443                             "com.apple.message.signature2", signature2
, 
 444                             "com.apple.message.summarize", "YES", 
 449 // Choose a token daemon for our card. 
 451 // Right now, we probe tokends sequentially. If there are many tokends, it would be 
 452 // faster to launch them in parallel (relying on PCSC transactions to separate them); 
 453 // but it's not altogether clear whether this would slow things down on low-memory 
 454 // systems by forcing (excessive) swapping. There is room for future experimentation. 
 456 RefPointer
<TokenDaemon
> Token::chooseTokend() 
 458         //@@@ CodeRepository should learn to update from disk changes to be re-usable 
 459         CodeRepository
<Bundle
> candidates("Security/tokend", ".tokend", "TOKENDAEMONPATH", false); 
 461         //@@@ we could sort by reverse "maxScore" and avoid launching those who won't cut it anyway... 
 463         string chosenIdentifier
; 
 464         set
<string
> candidateIdentifiers
; 
 465         RefPointer
<TokenDaemon
> leader
; 
 466         for (CodeRepository
<Bundle
>::const_iterator it 
= candidates
.begin(); 
 467                         it 
!= candidates
.end(); it
++) { 
 468                 RefPointer
<Bundle
> candidate 
= *it
; 
 470                         // skip software token daemons - ineligible for automatic choosing 
 471                         if (CFTypeRef type 
= (*it
)->infoPlistItem("TokendType")) 
 472                                 if (CFEqual(type
, CFSTR("software"))) 
 475                         // okay, launch it and let it try 
 476                         RefPointer
<TokenDaemon
> tokend 
= new TokenDaemon(candidate
, 
 477                                 reader().name(), reader().pcscState(), reader().cache
); 
 479                         // add identifier to candidate names set 
 480                         candidateIdentifiers
.insert(tokend
->bundleIdentifier()); 
 482                         if (tokend
->state() == ServerChild::dead
)       // ah well, this one's no good 
 485                         // probe the (single) tokend 
 486                         if (!tokend
->probe())           // non comprende... 
 489                         // we got a contender! 
 490                         if (!leader 
|| tokend
->score() > leader
->score()) { 
 491                                 leader 
= tokend
;                // a new front runner, he is... 
 492                                 chosenIdentifier 
= leader
->bundleIdentifier(); 
 495                         secdebug("token", "exception setting up %s (moving on)", candidate
->canonicalPath().c_str()); 
 499         // concatenate all candidate identifiers (sorted internally inside std::set) 
 501         for (set
<string
>::const_iterator i 
= candidateIdentifiers
.begin(), e 
= candidateIdentifiers
.end(); i 
!= e
; ++i
) { 
 502                 if (i 
!= candidateIdentifiers
.begin()) 
 503                         identifiers
.append(";"); 
 504                 identifiers
.append(*i
); 
 506         mt_log_ctk_tokend(identifiers
.c_str(), chosenIdentifier
.c_str()); 
 513 // Token::Access mediates calls through TokenDaemon to the actual daemon out there. 
 515 Token::Access::Access(Token 
&myToken
) 
 518         mTokend 
= &token
.tokend();      // throws if faulted or otherwise inappropriate 
 521 Token::Access::~Access() 
 527 // Debug dump support 
 529 #if defined(DEBUGDUMP) 
 531 void Token::dumpNode() 
 533         PerGlobal::dumpNode(); 
 534         Debug::dump(" %s[%d] tokend=%p", 
 535                 mGuid
.toString().c_str(), mSubservice
, mTokend
.get());