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 CommonSet tmpCommons
;
150 StLock
<Mutex
> _(*this);
152 secdebug("token", "%p reset (level=%d, propagating to %ld common(s)",
153 this, mResetLevel
, mCommons
.size());
154 // Make a copy to avoid deadlock with TokenDbCommon lock
155 tmpCommons
= mCommons
;
157 for (CommonSet::const_iterator it
= tmpCommons
.begin(); it
!= tmpCommons
.end(); it
++)
158 RefPointer
<TokenDbCommon
>(*it
)->resetAcls();
161 void Token::addCommon(TokenDbCommon
&dbc
)
163 secdebug("token", "%p addCommon TokenDbCommon %p", this, &dbc
);
164 mCommons
.insert(&dbc
);
167 void Token::removeCommon(TokenDbCommon
&dbc
)
169 secdebug("token", "%p removeCommon TokenDbCommon %p", this, &dbc
);
170 if (mCommons
.find(&dbc
) != mCommons
.end())
171 mCommons
.erase(&dbc
);
176 // Process the logical insertion of a Token into a Reader.
177 // From the client's point of view, this is where the CSSM subservice is created,
178 // characterized, and activated. From tokend's point of view, this is where
179 // we're analyzing the token, determine its characteristics, and get ready to
182 void Token::insert(::Reader
&slot
)
185 // this might take a while...
186 Server::active().longTermActivity();
188 // take Token lock and hold throughout insertion
189 StLock
<Mutex
> _(*this);
191 Syslog::debug("token inserted into reader %s", slot
.name().c_str());
192 secdebug("token", "%p begin insertion into slot %p (reader %s)",
193 this, &slot
, slot
.name().c_str());
195 mState
= slot
.pcscState();
197 RefPointer
<TokenDaemon
> tokend
= chooseTokend();
199 secdebug("token", "%p no token daemons available - faulting this card", this);
203 // tell the tokend object to relay faults to us
204 tokend
->faultRelay(this);
206 // locate or establish cache directories
207 if (tokend
->hasTokenUid()) {
208 secdebug("token", "%p CHOOSING %s (score=%ld, uid=\"%s\")",
209 this, tokend
->bundlePath().c_str(), tokend
->score(), tokend
->tokenUid().c_str());
210 mCache
= new TokenCache::Token(reader().cache
,
211 tokend
->bundleIdentifier() + ":" + tokend
->tokenUid());
213 secdebug("token", "%p CHOOSING %s (score=%ld, temporary)",
214 this, tokend
->bundlePath().c_str(), tokend
->score());
215 mCache
= new TokenCache::Token(reader().cache
);
217 secdebug("token", "%p token cache at %s", this, mCache
->root().c_str());
219 // here's the primary parameters of the new subservice
220 mGuid
= gGuidAppleSdCSPDL
;
221 mSubservice
= mCache
->subservice();
223 // establish work areas with tokend
224 char mdsDirectory
[PATH_MAX
];
225 char printName
[PATH_MAX
];
226 tokend
->establish(mGuid
, mSubservice
,
227 (mCache
->type() != TokenCache::Token::existing
? kSecTokendEstablishNewCache
: 0) | kSecTokendEstablishMakeMDS
,
228 mCache
->cachePath().c_str(), mCache
->workPath().c_str(),
229 mdsDirectory
, printName
);
231 // establish print name
232 if (mCache
->type() == TokenCache::Token::existing
) {
233 mPrintName
= mCache
->printName();
234 if (mPrintName
.empty())
235 mPrintName
= printName
;
237 mPrintName
= printName
;
238 if (mPrintName
.empty()) {
239 // last resort - new card and tokend didn't give us one
240 snprintf(printName
, sizeof(printName
), "smart card #%ld", mSubservice
);
241 mPrintName
= printName
;
243 if (mCache
->type() != TokenCache::Token::existing
)
244 mCache
->printName(mPrintName
); // store in cache
247 secdebug("token", "%p installing MDS from %s(%s)", this,
248 tokend
->bundlePath().c_str(),
249 mdsDirectory
[0] ? mdsDirectory
: "ALL");
250 string holdGuid
= mGuid
.toString(); // extend lifetime of .toString()
251 MDS_InstallDefaults mdsDefaults
= {
254 tokend
->hasTokenUid() ? tokend
->tokenUid().c_str() : "",
255 this->printName().c_str()
257 mds().install(&mdsDefaults
,
258 tokend
->bundlePath().c_str(),
259 mdsDirectory
[0] ? mdsDirectory
: NULL
,
263 // commit to insertion
264 StLock
<Mutex
> _(mSSIDLock
);
265 assert(mSubservices
.find(mSubservice
) == mSubservices
.end());
266 mSubservices
.insert(make_pair(mSubservice
, this));
269 // assign mTokend right before notification - mustn't be set if
270 // anything goes wrong during insertion
273 notify(kNotificationCDSAInsertion
);
275 Syslog::notice("reader %s inserted token \"%s\" (%s) subservice %ld using driver %s",
276 slot
.name().c_str(), mPrintName
.c_str(),
277 mTokend
->hasTokenUid() ? mTokend
->tokenUid().c_str() : "NO UID",
278 mSubservice
, mTokend
->bundleIdentifier().c_str());
279 secdebug("token", "%p inserted as %s:%ld", this, mGuid
.toString().c_str(), mSubservice
);
280 } catch (const CommonError
&err
) {
281 Syslog::notice("token in reader %s cannot be used (error %ld)", slot
.name().c_str(), err
.osStatus());
282 secdebug("token", "exception during insertion processing");
285 // exception thrown during insertion processing. Mark faulted
286 Syslog::notice("token in reader %s cannot be used", slot
.name().c_str());
287 secdebug("token", "exception during insertion processing");
294 // Process the logical removal of a Token from a Reader.
295 // Most of the time, this is asynchronous - someone has yanked the physical
296 // token out of a physical slot, and we're left with changing our universe
297 // to conform to the new realities. Reality #1 is that we can't talk to the
298 // physical token anymore.
300 // Note that if we're in FAULT mode, there really isn't a TokenDaemon around
301 // to kick. We're just holding on to represent the fact that there *is* a (useless)
302 // token in the slot, and now it's been finally yanked. Good riddance.
306 StLock
<Mutex
> _(*this);
307 Syslog::notice("reader %s removed token \"%s\" (%s) subservice %ld",
308 reader().name().c_str(), mPrintName
.c_str(),
310 ? (mTokend
->hasTokenUid() ? mTokend
->tokenUid().c_str() : "NO UID")
313 secdebug("token", "%p begin removal from slot %p (reader %s)",
314 this, &reader(), reader().name().c_str());
316 mTokend
->faultRelay(NULL
); // unregister (no more faults, please)
317 mds().uninstall(mGuid
.toString().c_str(), mSubservice
);
318 secdebug("token", "%p mds uninstall complete", this);
320 secdebug("token", "%p kill complete", this);
321 notify(kNotificationCDSARemoval
);
322 secdebug("token", "%p removal complete", this);
327 // Set the token to fault state.
328 // This essentially "cuts off" all operations on an inserted token and makes
329 // them fail. It also sends a FAULT notification via CSSM to any clients.
330 // Only one fault is actually processed; multiple calls are ignored.
332 // Note that a faulted token is not REMOVED; it's still physically present.
333 // No fault is declared when a token is actually removed.
335 void Token::fault(bool async
)
337 StLock
<Mutex
> _(*this);
338 if (!mFaulted
) { // first one
339 secdebug("token", "%p %s FAULT", this, async
? "ASYNCHRONOUS" : "SYNCHRONOUS");
344 // send CDSA notification
345 notify(kNotificationCDSAFailure
);
347 // cast off our TokenDaemon for good
351 // if this is a synchronous fault, abort this operation now
353 CssmError::throwMe(CSSM_ERRCODE_DEVICE_FAILED
);
357 void Token::relayFault(bool async
)
359 secdebug("token", "%p fault relayed from tokend", this);
365 // This is the "kill" hook for Token as a Node<> object.
369 // Avoid holding the lock across call to resetAcls
370 // This can cause deadlock on card removal
372 StLock
<Mutex
> _(*this);
375 mTokend
= NULL
; // cast loose our tokend (if any)
376 // Take us out of the map
377 StLock
<Mutex
> _(mSSIDLock
);
378 SSIDMap::iterator it
= mSubservices
.find(mSubservice
);
379 assert(it
!= mSubservices
.end() && it
->second
== this);
380 if (it
!= mSubservices
.end() && it
->second
== this)
381 mSubservices
.erase(it
);
385 resetAcls(); // release our TokenDbCommons
386 PerGlobal::kill(); // generic action
392 // Send CDSA-layer notifications for this token.
393 // These events are usually received by CDSA plugins working with securityd.
395 void Token::notify(NotificationEvent event
)
397 NameValueDictionary nvd
;
398 CssmSubserviceUid
ssuid(mGuid
, NULL
, mSubservice
,
399 CSSM_SERVICE_DL
| CSSM_SERVICE_CSP
);
400 nvd
.Insert(new NameValuePair(SSUID_KEY
, CssmData::wrap(ssuid
)));
404 // inject notification into Security event system
405 Listener::notify(kNotificationDomainCDSA
, event
, data
);
413 // Choose a token daemon for our card.
415 // Right now, we probe tokends sequentially. If there are many tokends, it would be
416 // faster to launch them in parallel (relying on PCSC transactions to separate them);
417 // but it's not altogether clear whether this would slow things down on low-memory
418 // systems by forcing (excessive) swapping. There is room for future experimentation.
420 RefPointer
<TokenDaemon
> Token::chooseTokend()
422 //@@@ CodeRepository should learn to update from disk changes to be re-usable
423 CodeRepository
<GenericBundle
> candidates("Security/tokend", ".tokend", "TOKENDAEMONPATH", false);
425 //@@@ we could sort by reverse "maxScore" and avoid launching those who won't cut it anyway...
427 RefPointer
<TokenDaemon
> leader
;
428 for (CodeRepository
<GenericBundle
>::const_iterator it
= candidates
.begin();
429 it
!= candidates
.end(); it
++) {
431 // any pre-launch screening of candidate *it goes here
433 RefPointer
<TokenDaemon
> tokend
= new TokenDaemon(*it
,
434 reader().name(), reader().pcscState(), reader().cache
);
436 if (tokend
->state() == ServerChild::dead
) // ah well, this one's no good
439 // probe the (single) tokend
440 if (!tokend
->probe()) // non comprende...
443 // we got a contender!
444 if (!leader
|| tokend
->score() > leader
->score())
445 leader
= tokend
; // a new front runner, he is...
447 secdebug("token", "exception setting up %s (moving on)", (*it
)->canonicalPath().c_str());
455 // Token::Access mediates calls through TokenDaemon to the actual daemon out there.
457 Token::Access::Access(Token
&myToken
)
460 mTokend
= &token
.tokend(); // throws if faulted or otherwise inappropriate
463 Token::Access::~Access()
469 // Debug dump support
471 #if defined(DEBUGDUMP)
473 void Token::dumpNode()
475 PerGlobal::dumpNode();
476 Debug::dump(" %s[%ld] tokend=%p",
477 mGuid
.toString().c_str(), mSubservice
, mTokend
.get());