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
)
206 // this might take a while...
207 Server::active().longTermActivity();
209 // take Token lock and hold throughout insertion
210 StLock
<Mutex
> _(*this);
212 Syslog::debug("token inserted into reader %s", slot
.name().c_str());
213 secdebug("token", "%p begin insertion into slot %p (reader %s)",
214 this, &slot
, slot
.name().c_str());
216 mState
= slot
.pcscState();
218 RefPointer
<TokenDaemon
> tokend
= chooseTokend();
220 secdebug("token", "%p no token daemons available - faulting this card", this);
224 // tell the tokend object to relay faults to us
225 tokend
->faultRelay(this);
227 // locate or establish cache directories
228 if (tokend
->hasTokenUid()) {
229 secdebug("token", "%p CHOOSING %s (score=%d, uid=\"%s\")",
230 this, tokend
->bundlePath().c_str(), tokend
->score(), tokend
->tokenUid().c_str());
231 mCache
= new TokenCache::Token(reader().cache
,
232 tokend
->bundleIdentifier() + ":" + tokend
->tokenUid());
234 secdebug("token", "%p CHOOSING %s (score=%d, temporary)",
235 this, tokend
->bundlePath().c_str(), tokend
->score());
236 mCache
= new TokenCache::Token(reader().cache
);
238 secdebug("token", "%p token cache at %s", this, mCache
->root().c_str());
240 // here's the primary parameters of the new subservice
241 mGuid
= gGuidAppleSdCSPDL
;
242 mSubservice
= mCache
->subservice();
244 // establish work areas with tokend
245 char mdsDirectory
[PATH_MAX
];
246 char printName
[PATH_MAX
];
247 tokend
->establish(mGuid
, mSubservice
,
248 (mCache
->type() != TokenCache::Token::existing
? kSecTokendEstablishNewCache
: 0) | kSecTokendEstablishMakeMDS
,
249 mCache
->cachePath().c_str(), mCache
->workPath().c_str(),
250 mdsDirectory
, printName
);
252 // establish print name
253 if (mCache
->type() == TokenCache::Token::existing
) {
254 mPrintName
= mCache
->printName();
255 if (mPrintName
.empty())
256 mPrintName
= printName
;
258 mPrintName
= printName
;
259 if (mPrintName
.empty()) {
260 // last resort - new card and tokend didn't give us one
261 snprintf(printName
, sizeof(printName
), "smart card #%d", mSubservice
);
262 mPrintName
= printName
;
264 if (mCache
->type() != TokenCache::Token::existing
)
265 mCache
->printName(mPrintName
); // store in cache
268 secdebug("token", "%p installing MDS from %s(%s)", this,
269 tokend
->bundlePath().c_str(),
270 mdsDirectory
[0] ? mdsDirectory
: "ALL");
271 string holdGuid
= mGuid
.toString(); // extend lifetime of .toString()
272 MDS_InstallDefaults mdsDefaults
= {
275 tokend
->hasTokenUid() ? tokend
->tokenUid().c_str() : "",
276 this->printName().c_str()
278 mds().install(&mdsDefaults
,
279 tokend
->bundlePath().c_str(),
280 mdsDirectory
[0] ? mdsDirectory
: NULL
,
284 // commit to insertion
285 StLock
<Mutex
> _(mSSIDLock
);
286 assert(mSubservices
.find(mSubservice
) == mSubservices
.end());
287 mSubservices
.insert(make_pair(mSubservice
, this));
290 // assign mTokend right before notification - mustn't be set if
291 // anything goes wrong during insertion
294 notify(kNotificationCDSAInsertion
);
296 Syslog::notice("reader %s inserted token \"%s\" (%s) subservice %ld using driver %s",
297 slot
.name().c_str(), mPrintName
.c_str(),
298 mTokend
->hasTokenUid() ? mTokend
->tokenUid().c_str() : "NO UID",
299 mSubservice
, mTokend
->bundleIdentifier().c_str());
300 secdebug("token", "%p inserted as %s:%d", this, mGuid
.toString().c_str(), mSubservice
);
301 } catch (const CommonError
&err
) {
302 Syslog::notice("token in reader %s cannot be used (error %ld)", slot
.name().c_str(), err
.osStatus());
303 secdebug("token", "exception during insertion processing");
306 // exception thrown during insertion processing. Mark faulted
307 Syslog::notice("token in reader %s cannot be used", slot
.name().c_str());
308 secdebug("token", "exception during insertion processing");
315 // Process the logical removal of a Token from a Reader.
316 // Most of the time, this is asynchronous - someone has yanked the physical
317 // token out of a physical slot, and we're left with changing our universe
318 // to conform to the new realities. Reality #1 is that we can't talk to the
319 // physical token anymore.
321 // Note that if we're in FAULT mode, there really isn't a TokenDaemon around
322 // to kick. We're just holding on to represent the fact that there *is* a (useless)
323 // token in the slot, and now it's been finally yanked. Good riddance.
327 StLock
<Mutex
> _(*this);
328 Syslog::notice("reader %s removed token \"%s\" (%s) subservice %ld",
329 reader().name().c_str(), mPrintName
.c_str(),
331 ? (mTokend
->hasTokenUid() ? mTokend
->tokenUid().c_str() : "NO UID")
334 secdebug("token", "%p begin removal from slot %p (reader %s)",
335 this, &reader(), reader().name().c_str());
337 mTokend
->faultRelay(NULL
); // unregister (no more faults, please)
338 mds().uninstall(mGuid
.toString().c_str(), mSubservice
);
339 secdebug("token", "%p mds uninstall complete", this);
341 secdebug("token", "%p kill complete", this);
342 notify(kNotificationCDSARemoval
);
343 secdebug("token", "%p removal complete", this);
348 // Set the token to fault state.
349 // This essentially "cuts off" all operations on an inserted token and makes
350 // them fail. It also sends a FAULT notification via CSSM to any clients.
351 // Only one fault is actually processed; multiple calls are ignored.
353 // Note that a faulted token is not REMOVED; it's still physically present.
354 // No fault is declared when a token is actually removed.
356 void Token::fault(bool async
)
358 StLock
<Mutex
> _(*this);
359 if (!mFaulted
) { // first one
360 secdebug("token", "%p %s FAULT", this, async
? "ASYNCHRONOUS" : "SYNCHRONOUS");
365 // send CDSA notification
366 notify(kNotificationCDSAFailure
);
368 // cast off our TokenDaemon for good
369 //>>> mTokend = NULL;
372 // if this is a synchronous fault, abort this operation now
374 CssmError::throwMe(CSSM_ERRCODE_DEVICE_FAILED
);
378 void Token::relayFault(bool async
)
380 secdebug("token", "%p fault relayed from tokend", this);
386 // This is the "kill" hook for Token as a Node<> object.
390 // Avoid holding the lock across call to resetAcls
391 // This can cause deadlock on card removal
393 StLock
<Mutex
> _(*this);
396 mTokend
= NULL
; // cast loose our tokend (if any)
397 // Take us out of the map
398 StLock
<Mutex
> _(mSSIDLock
);
399 SSIDMap::iterator it
= mSubservices
.find(mSubservice
);
400 assert(it
!= mSubservices
.end() && it
->second
== this);
401 if (it
!= mSubservices
.end() && it
->second
== this)
402 mSubservices
.erase(it
);
406 resetAcls(); // release our TokenDbCommons
407 PerGlobal::kill(); // generic action
413 // Send CDSA-layer notifications for this token.
414 // These events are usually received by CDSA plugins working with securityd.
416 void Token::notify(NotificationEvent event
)
418 NameValueDictionary nvd
;
419 CssmSubserviceUid
ssuid(mGuid
, NULL
, h2n (mSubservice
),
420 h2n(CSSM_SERVICE_DL
| CSSM_SERVICE_CSP
));
421 nvd
.Insert(new NameValuePair(SSUID_KEY
, CssmData::wrap(ssuid
)));
425 // inject notification into Security event system
426 Listener::notify(kNotificationDomainCDSA
, event
, data
);
434 // Choose a token daemon for our card.
436 // Right now, we probe tokends sequentially. If there are many tokends, it would be
437 // faster to launch them in parallel (relying on PCSC transactions to separate them);
438 // but it's not altogether clear whether this would slow things down on low-memory
439 // systems by forcing (excessive) swapping. There is room for future experimentation.
441 RefPointer
<TokenDaemon
> Token::chooseTokend()
443 //@@@ CodeRepository should learn to update from disk changes to be re-usable
444 CodeRepository
<Bundle
> candidates("Security/tokend", ".tokend", "TOKENDAEMONPATH", false);
446 //@@@ we could sort by reverse "maxScore" and avoid launching those who won't cut it anyway...
448 RefPointer
<TokenDaemon
> leader
;
449 for (CodeRepository
<Bundle
>::const_iterator it
= candidates
.begin();
450 it
!= candidates
.end(); it
++) {
452 // any pre-launch screening of candidate *it goes here
454 RefPointer
<TokenDaemon
> tokend
= new TokenDaemon(*it
,
455 reader().name(), reader().pcscState(), reader().cache
);
457 if (tokend
->state() == ServerChild::dead
) // ah well, this one's no good
460 // probe the (single) tokend
461 if (!tokend
->probe()) // non comprende...
464 // we got a contender!
465 if (!leader
|| tokend
->score() > leader
->score())
466 leader
= tokend
; // a new front runner, he is...
468 secdebug("token", "exception setting up %s (moving on)", (*it
)->canonicalPath().c_str());
476 // Token::Access mediates calls through TokenDaemon to the actual daemon out there.
478 Token::Access::Access(Token
&myToken
)
481 mTokend
= &token
.tokend(); // throws if faulted or otherwise inappropriate
484 Token::Access::~Access()
490 // Debug dump support
492 #if defined(DEBUGDUMP)
494 void Token::dumpNode()
496 PerGlobal::dumpNode();
497 Debug::dump(" %s[%d] tokend=%p",
498 mGuid
.toString().c_str(), mSubservice
, mTokend
.get());