2 * Copyright (c) 2004,2007 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>
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:%d) 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
);
132 // We override getAcl to provide PIN state feedback
134 void Token::getAcl(const char *tag
, uint32
&count
, AclEntryInfo
*&acls
)
136 if (pinFromAclTag(tag
, "?")) { // read from tokend - do not cache
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
++)
148 TokenAcl::cssmGetAcl(tag
, count
, acls
);
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.
162 Token::ResetGeneration
Token::resetGeneration() const
167 void Token::resetAcls()
169 CommonSet tmpCommons
;
171 StLock
<Mutex
> _(*this);
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
;
178 for (CommonSet::const_iterator it
= tmpCommons
.begin(); it
!= tmpCommons
.end();)
179 RefPointer
<TokenDbCommon
>(*it
++)->resetAcls();
182 void Token::addCommon(TokenDbCommon
&dbc
)
184 secdebug("token", "%p addCommon TokenDbCommon %p", this, &dbc
);
185 mCommons
.insert(&dbc
);
188 void Token::removeCommon(TokenDbCommon
&dbc
)
190 secdebug("token", "%p removeCommon TokenDbCommon %p", this, &dbc
);
191 if (mCommons
.find(&dbc
) != mCommons
.end())
192 mCommons
.erase(&dbc
);
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
203 void Token::insert(::Reader
&slot
, RefPointer
<TokenDaemon
> tokend
)
206 // this might take a while...
207 Server::active().longTermActivity();
209 mState
= slot
.pcscState();
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
219 // take Token lock and hold throughout insertion
220 StLock
<Mutex
> _(*this);
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());
226 // tell the tokend object to relay faults to us
227 tokend
->faultRelay(this);
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());
236 secdebug("token", "%p using %s (score=%d, temporary)",
237 this, tokend
->bundlePath().c_str(), tokend
->score());
238 mCache
= new TokenCache::Token(reader().cache
);
240 secdebug("token", "%p token cache at %s", this, mCache
->root().c_str());
242 // here's the primary parameters of the new subservice
243 mGuid
= gGuidAppleSdCSPDL
;
244 mSubservice
= mCache
->subservice();
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
);
254 // establish print name
255 if (mCache
->type() == TokenCache::Token::existing
) {
256 mPrintName
= mCache
->printName();
257 if (mPrintName
.empty())
258 mPrintName
= printName
;
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
;
266 if (mCache
->type() != TokenCache::Token::existing
)
267 mCache
->printName(mPrintName
); // store in cache
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
275 if (tokend
->hasTokenUid())
276 holdTokenUid
= tokend
->tokenUid();
277 string holdPrintName
= this->printName();
278 MDS_InstallDefaults mdsDefaults
= {
281 holdTokenUid
.c_str(),
282 holdPrintName
.c_str()
284 mds().install(&mdsDefaults
,
285 tokend
->bundlePath().c_str(),
286 mdsDirectory
[0] ? mdsDirectory
: NULL
,
290 // commit to insertion
291 StLock
<Mutex
> _(mSSIDLock
);
292 assert(mSubservices
.find(mSubservice
) == mSubservices
.end());
293 mSubservices
.insert(make_pair(mSubservice
, this));
296 // assign mTokend right before notification - mustn't be set if
297 // anything goes wrong during insertion
300 notify(kNotificationCDSAInsertion
);
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");
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");
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.
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.
333 StLock
<Mutex
> _(*this);
334 Syslog::notice("reader %s removed token \"%s\" (%s) subservice %ld",
335 reader().name().c_str(), mPrintName
.c_str(),
337 ? (mTokend
->hasTokenUid() ? mTokend
->tokenUid().c_str() : "NO UID")
340 secdebug("token", "%p begin removal from slot %p (reader %s)",
341 this, &reader(), reader().name().c_str());
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);
347 secdebug("token", "%p kill complete", this);
348 notify(kNotificationCDSARemoval
);
349 secdebug("token", "%p removal complete", this);
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.
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.
362 void Token::fault(bool async
)
364 StLock
<Mutex
> _(*this);
365 if (!mFaulted
) { // first one
366 secdebug("token", "%p %s FAULT", this, async
? "ASYNCHRONOUS" : "SYNCHRONOUS");
371 // send CDSA notification
372 notify(kNotificationCDSAFailure
);
374 // cast off our TokenDaemon for good
375 //>>> mTokend = NULL;
378 // if this is a synchronous fault, abort this operation now
380 CssmError::throwMe(CSSM_ERRCODE_DEVICE_FAILED
);
384 void Token::relayFault(bool async
)
386 secdebug("token", "%p fault relayed from tokend", this);
392 // This is the "kill" hook for Token as a Node<> object.
396 // Avoid holding the lock across call to resetAcls
397 // This can cause deadlock on card removal
399 StLock
<Mutex
> _(*this);
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
);
412 resetAcls(); // release our TokenDbCommons
413 PerGlobal::kill(); // generic action
419 // Send CDSA-layer notifications for this token.
420 // These events are usually received by CDSA plugins working with securityd.
422 void Token::notify(NotificationEvent event
)
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
)));
431 // inject notification into Security event system
432 Listener::notify(kNotificationDomainCDSA
, event
, data
);
440 // Choose a token daemon for our card.
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.
447 RefPointer
<TokenDaemon
> Token::chooseTokend()
449 //@@@ CodeRepository should learn to update from disk changes to be re-usable
450 CodeRepository
<Bundle
> candidates("Security/tokend", ".tokend", "TOKENDAEMONPATH", false);
452 //@@@ we could sort by reverse "maxScore" and avoid launching those who won't cut it anyway...
454 RefPointer
<TokenDaemon
> leader
;
455 for (CodeRepository
<Bundle
>::const_iterator it
= candidates
.begin();
456 it
!= candidates
.end(); it
++) {
457 RefPointer
<Bundle
> candidate
= *it
;
459 // skip software token daemons - ineligible for automatic choosing
460 if (CFTypeRef type
= (*it
)->infoPlistItem("TokendType"))
461 if (CFEqual(type
, CFSTR("software")))
464 // okay, launch it and let it try
465 RefPointer
<TokenDaemon
> tokend
= new TokenDaemon(candidate
,
466 reader().name(), reader().pcscState(), reader().cache
);
468 if (tokend
->state() == ServerChild::dead
) // ah well, this one's no good
471 // probe the (single) tokend
472 if (!tokend
->probe()) // non comprende...
475 // we got a contender!
476 if (!leader
|| tokend
->score() > leader
->score())
477 leader
= tokend
; // a new front runner, he is...
479 secdebug("token", "exception setting up %s (moving on)", candidate
->canonicalPath().c_str());
487 // Token::Access mediates calls through TokenDaemon to the actual daemon out there.
489 Token::Access::Access(Token
&myToken
)
492 mTokend
= &token
.tokend(); // throws if faulted or otherwise inappropriate
495 Token::Access::~Access()
501 // Debug dump support
503 #if defined(DEBUGDUMP)
505 void Token::dumpNode()
507 PerGlobal::dumpNode();
508 Debug::dump(" %s[%d] tokend=%p",
509 mGuid
.toString().c_str(), mSubservice
, mTokend
.get());