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 StLock
<Mutex
> _(*this);
150 secdebug("token", "%p reset (level=%d, propagating to %ld common(s)",
151 this, mResetLevel
, mCommons
.size());
152 for (CommonSet::const_iterator it
= mCommons
.begin(); it
!= mCommons
.end(); it
++)
153 RefPointer
<TokenDbCommon
>(*it
)->resetAcls();
156 void Token::addCommon(TokenDbCommon
&dbc
)
158 mCommons
.insert(&dbc
);
161 void Token::removeCommon(TokenDbCommon
&dbc
)
163 assert(mCommons
.find(&dbc
) != mCommons
.end());
164 mCommons
.erase(&dbc
);
169 // Process the logical insertion of a Token into a Reader.
170 // From the client's point of view, this is where the CSSM subservice is created,
171 // characterized, and activated. From tokend's point of view, this is where
172 // we're analyzing the token, determine its characteristics, and get ready to
175 void Token::insert(::Reader
&slot
)
178 // this might take a while...
179 Server::active().longTermActivity();
181 // take Token lock and hold throughout insertion
182 StLock
<Mutex
> _(*this);
184 Syslog::debug("token inserted into reader %s", slot
.name().c_str());
185 secdebug("token", "%p begin insertion into slot %p (reader %s)",
186 this, &slot
, slot
.name().c_str());
188 mState
= slot
.pcscState();
190 RefPointer
<TokenDaemon
> tokend
= chooseTokend();
192 secdebug("token", "%p no token daemons available - faulting this card", this);
196 // tell the tokend object to relay faults to us
197 tokend
->faultRelay(this);
199 // locate or establish cache directories
200 if (tokend
->hasTokenUid()) {
201 secdebug("token", "%p CHOOSING %s (score=%ld, uid=\"%s\")",
202 this, tokend
->bundlePath().c_str(), tokend
->score(), tokend
->tokenUid().c_str());
203 mCache
= new TokenCache::Token(reader().cache
,
204 tokend
->bundleIdentifier() + ":" + tokend
->tokenUid());
206 secdebug("token", "%p CHOOSING %s (score=%ld, temporary)",
207 this, tokend
->bundlePath().c_str(), tokend
->score());
208 mCache
= new TokenCache::Token(reader().cache
);
210 secdebug("token", "%p token cache at %s", this, mCache
->root().c_str());
212 // here's the primary parameters of the new subservice
213 mGuid
= gGuidAppleSdCSPDL
;
214 mSubservice
= mCache
->subservice();
216 // establish work areas with tokend
217 char mdsDirectory
[PATH_MAX
];
218 char printName
[PATH_MAX
];
219 tokend
->establish(mGuid
, mSubservice
,
220 (mCache
->type() != TokenCache::Token::existing
? kSecTokendEstablishNewCache
: 0) | kSecTokendEstablishMakeMDS
,
221 mCache
->cachePath().c_str(), mCache
->workPath().c_str(),
222 mdsDirectory
, printName
);
224 // establish print name
225 if (mCache
->type() == TokenCache::Token::existing
) {
226 mPrintName
= mCache
->printName();
227 if (mPrintName
.empty())
228 mPrintName
= printName
;
230 mPrintName
= printName
;
231 if (mPrintName
.empty()) {
232 // last resort - new card and tokend didn't give us one
233 snprintf(printName
, sizeof(printName
), "smart card #%ld", mSubservice
);
234 mPrintName
= printName
;
236 if (mCache
->type() != TokenCache::Token::existing
)
237 mCache
->printName(mPrintName
); // store in cache
240 secdebug("token", "%p installing MDS from %s(%s)", this,
241 tokend
->bundlePath().c_str(),
242 mdsDirectory
[0] ? mdsDirectory
: "ALL");
243 string holdGuid
= mGuid
.toString(); // extend lifetime of .toString()
244 MDS_InstallDefaults mdsDefaults
= {
247 tokend
->hasTokenUid() ? tokend
->tokenUid().c_str() : "",
248 this->printName().c_str()
250 mds().install(&mdsDefaults
,
251 tokend
->bundlePath().c_str(),
252 mdsDirectory
[0] ? mdsDirectory
: NULL
,
256 // commit to insertion
257 StLock
<Mutex
> _(mSSIDLock
);
258 assert(mSubservices
.find(mSubservice
) == mSubservices
.end());
259 mSubservices
.insert(make_pair(mSubservice
, this));
262 // assign mTokend right before notification - mustn't be set if
263 // anything goes wrong during insertion
266 notify(kNotificationCDSAInsertion
);
268 Syslog::notice("reader %s inserted token \"%s\" (%s) subservice %ld using driver %s",
269 slot
.name().c_str(), mPrintName
.c_str(),
270 mTokend
->hasTokenUid() ? mTokend
->tokenUid().c_str() : "NO UID",
271 mSubservice
, mTokend
->bundleIdentifier().c_str());
272 secdebug("token", "%p inserted as %s:%ld", this, mGuid
.toString().c_str(), mSubservice
);
273 } catch (const CommonError
&err
) {
274 Syslog::notice("token in reader %s cannot be used (error %ld)", slot
.name().c_str(), err
.osStatus());
275 secdebug("token", "exception during insertion processing");
278 // exception thrown during insertion processing. Mark faulted
279 Syslog::notice("token in reader %s cannot be used", slot
.name().c_str());
280 secdebug("token", "exception during insertion processing");
287 // Process the logical removal of a Token from a Reader.
288 // Most of the time, this is asynchronous - someone has yanked the physical
289 // token out of a physical slot, and we're left with changing our universe
290 // to conform to the new realities. Reality #1 is that we can't talk to the
291 // physical token anymore.
293 // Note that if we're in FAULT mode, there really isn't a TokenDaemon around
294 // to kick. We're just holding on to represent the fact that there *is* a (useless)
295 // token in the slot, and now it's been finally yanked. Good riddance.
299 StLock
<Mutex
> _(*this);
300 Syslog::notice("reader %s removed token \"%s\" (%s) subservice %ld",
301 reader().name().c_str(), mPrintName
.c_str(),
303 ? (mTokend
->hasTokenUid() ? mTokend
->tokenUid().c_str() : "NO UID")
306 secdebug("token", "%p begin removal from slot %p (reader %s)",
307 this, &reader(), reader().name().c_str());
309 mTokend
->faultRelay(NULL
); // unregister (no more faults, please)
310 notify(kNotificationCDSARemoval
);
311 mds().uninstall(mGuid
.toString().c_str(), mSubservice
);
313 secdebug("token", "%p removal complete", this);
318 // Set the token to fault state.
319 // This essentially "cuts off" all operations on an inserted token and makes
320 // them fail. It also sends a FAULT notification via CSSM to any clients.
321 // Only one fault is actually processed; multiple calls are ignored.
323 // Note that a faulted token is not REMOVED; it's still physically present.
324 // No fault is declared when a token is actually removed.
326 void Token::fault(bool async
)
328 StLock
<Mutex
> _(*this);
329 if (!mFaulted
) { // first one
330 secdebug("token", "%p %s FAULT", this, async
? "ASYNCHRONOUS" : "SYNCHRONOUS");
335 // send CDSA notification
336 notify(kNotificationCDSAFailure
);
338 // cast off our TokenDaemon for good
342 // if this is a synchronous fault, abort this operation now
344 CssmError::throwMe(CSSM_ERRCODE_DEVICE_FAILED
);
348 void Token::relayFault(bool async
)
350 secdebug("token", "%p fault relayed from tokend", this);
356 // This is the "kill" hook for Token as a Node<> object.
360 StLock
<Mutex
> _(*this);
363 mTokend
= NULL
; // cast loose our tokend (if any)
364 // Take us out of the map
365 StLock
<Mutex
> _(mSSIDLock
);
366 SSIDMap::iterator it
= mSubservices
.find(mSubservice
);
367 assert(it
!= mSubservices
.end() && it
->second
== this);
368 if (it
!= mSubservices
.end() && it
->second
== this)
369 mSubservices
.erase(it
);
372 resetAcls(); // release our TokenDbCommons
373 PerGlobal::kill(); // generic action
379 // Send CDSA-layer notifications for this token.
380 // These events are usually received by CDSA plugins working with securityd.
382 void Token::notify(NotificationEvent event
)
384 NameValueDictionary nvd
;
385 CssmSubserviceUid
ssuid(mGuid
, NULL
, mSubservice
,
386 CSSM_SERVICE_DL
| CSSM_SERVICE_CSP
);
387 nvd
.Insert(new NameValuePair(SSUID_KEY
, CssmData::wrap(ssuid
)));
391 // inject notification into Security event system
392 Listener::notify(kNotificationDomainCDSA
, event
, data
);
400 // Choose a token daemon for our card.
402 // Right now, we probe tokends sequentially. If there are many tokends, it would be
403 // faster to launch them in parallel (relying on PCSC transactions to separate them);
404 // but it's not altogether clear whether this would slow things down on low-memory
405 // systems by forcing (excessive) swapping. There is room for future experimentation.
407 RefPointer
<TokenDaemon
> Token::chooseTokend()
409 //@@@ CodeRepository should learn to update from disk changes to be re-usable
410 CodeRepository
<GenericBundle
> candidates("Security/tokend", ".tokend", "TOKENDAEMONPATH", false);
412 //@@@ we could sort by reverse "maxScore" and avoid launching those who won't cut it anyway...
414 RefPointer
<TokenDaemon
> leader
;
415 for (CodeRepository
<GenericBundle
>::const_iterator it
= candidates
.begin();
416 it
!= candidates
.end(); it
++) {
418 // any pre-launch screening of candidate *it goes here
420 RefPointer
<TokenDaemon
> tokend
= new TokenDaemon(*it
,
421 reader().name(), reader().pcscState(), reader().cache
);
423 if (tokend
->state() == ServerChild::dead
) // ah well, this one's no good
426 // probe the (single) tokend
427 if (!tokend
->probe()) // non comprende...
430 // we got a contender!
431 if (!leader
|| tokend
->score() > leader
->score())
432 leader
= tokend
; // a new front runner, he is...
434 secdebug("token", "exception setting up %s (moving on)", (*it
)->canonicalPath().c_str());
442 // Token::Access mediates calls through TokenDaemon to the actual daemon out there.
444 Token::Access::Access(Token
&myToken
)
447 mTokend
= &token
.tokend(); // throws if faulted or otherwise inappropriate
450 Token::Access::~Access()
456 // Debug dump support
458 #if defined(DEBUGDUMP)
460 void Token::dumpNode()
462 PerGlobal::dumpNode();
463 Debug::dump(" %s[%ld] tokend=%p",
464 mGuid
.toString().c_str(), mSubservice
, mTokend
.get());