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());