]> git.saurik.com Git - apple/securityd.git/blob - src/token.cpp
securityd-26692.tar.gz
[apple/securityd.git] / src / token.cpp
1 /*
2 * Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
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
11 * file.
12 *
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.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24
25 //
26 // token - internal representation of a (single distinct) hardware token
27 //
28 #include "token.h"
29 #include "tokendatabase.h"
30 #include "reader.h"
31 #include "notifications.h"
32 #include "child.h"
33 #include "server.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>
39
40 #include <sys/types.h>
41 #include <sys/wait.h>
42 #include <grp.h>
43 #include <pwd.h>
44
45 using namespace MDSClient;
46
47
48 //
49 // SSID -> Token map
50 //
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;
55
56
57 //
58 // Token construction and destruction is trivial; the good stuff
59 // happens in insert() and remove() below.
60 //
61 Token::Token()
62 : mFaulted(false), mTokend(NULL), mResetLevel(1)
63 {
64 secdebug("token", "%p created", this);
65 }
66
67
68 Token::~Token()
69 {
70 secdebug("token", "%p (%s:%ld) destroyed",
71 this, mGuid.toString().c_str(), mSubservice);
72 }
73
74
75 Reader &Token::reader() const
76 {
77 return referent< ::Reader>();
78 }
79
80 TokenDaemon &Token::tokend()
81 {
82 StLock<Mutex> _(*this);
83 if (mFaulted)
84 CssmError::throwMe(CSSM_ERRCODE_DEVICE_FAILED);
85 if (mTokend)
86 return *mTokend;
87 else
88 CssmError::throwMe(CSSM_ERRCODE_DEVICE_FAILED);
89 }
90
91
92 //
93 // We don't currently use a database handle to tokend.
94 // This is just to satisfy the TokenAcl.
95 //
96 GenericHandle Token::tokenHandle() const
97 {
98 return noDb; // we don't currently use tokend-side DbHandles
99 }
100
101
102 //
103 // Token is the SecurityServerAcl for the token
104 //
105 AclKind Token::aclKind() const
106 {
107 return dbAcl;
108 }
109
110 Token &Token::token()
111 {
112 return *this;
113 }
114
115
116 //
117 // Find Token by subservice id.
118 // Throws if ssid is invalid (i.e. always returns non-NULL)
119 //
120 RefPointer<Token> Token::find(uint32 ssid)
121 {
122 StLock<Mutex> _(mSSIDLock);
123 SSIDMap::const_iterator it = mSubservices.find(ssid);
124 if (it == mSubservices.end())
125 CssmError::throwMe(CSSMERR_CSSM_INVALID_SUBSERVICEID);
126 else
127 return it->second;
128 }
129
130
131 //
132 // Reset management.
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.
140 //
141 Token::ResetGeneration Token::resetGeneration() const
142 {
143 return mResetLevel;
144 }
145
146 void Token::resetAcls()
147 {
148 StLock<Mutex> _(*this);
149 mResetLevel++;
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();
154 }
155
156 void Token::addCommon(TokenDbCommon &dbc)
157 {
158 mCommons.insert(&dbc);
159 }
160
161 void Token::removeCommon(TokenDbCommon &dbc)
162 {
163 assert(mCommons.find(&dbc) != mCommons.end());
164 mCommons.erase(&dbc);
165 }
166
167
168 //
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
173 // use it.
174 //
175 void Token::insert(::Reader &slot)
176 {
177 try {
178 // this might take a while...
179 Server::active().longTermActivity();
180
181 // take Token lock and hold throughout insertion
182 StLock<Mutex> _(*this);
183
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());
187 referent(slot);
188 mState = slot.pcscState();
189
190 RefPointer<TokenDaemon> tokend = chooseTokend();
191 if (!tokend) {
192 secdebug("token", "%p no token daemons available - faulting this card", this);
193 fault(false);
194 }
195
196 // tell the tokend object to relay faults to us
197 tokend->faultRelay(this);
198
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());
205 } else {
206 secdebug("token", "%p CHOOSING %s (score=%ld, temporary)",
207 this, tokend->bundlePath().c_str(), tokend->score());
208 mCache = new TokenCache::Token(reader().cache);
209 }
210 secdebug("token", "%p token cache at %s", this, mCache->root().c_str());
211
212 // here's the primary parameters of the new subservice
213 mGuid = gGuidAppleSdCSPDL;
214 mSubservice = mCache->subservice();
215
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);
223
224 // establish print name
225 if (mCache->type() == TokenCache::Token::existing) {
226 mPrintName = mCache->printName();
227 if (mPrintName.empty())
228 mPrintName = printName;
229 } else
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;
235 }
236 if (mCache->type() != TokenCache::Token::existing)
237 mCache->printName(mPrintName); // store in cache
238
239 // install MDS
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 = {
245 holdGuid.c_str(),
246 mSubservice,
247 tokend->hasTokenUid() ? tokend->tokenUid().c_str() : "",
248 this->printName().c_str()
249 };
250 mds().install(&mdsDefaults,
251 tokend->bundlePath().c_str(),
252 mdsDirectory[0] ? mdsDirectory : NULL,
253 NULL);
254
255 {
256 // commit to insertion
257 StLock<Mutex> _(mSSIDLock);
258 assert(mSubservices.find(mSubservice) == mSubservices.end());
259 mSubservices.insert(make_pair(mSubservice, this));
260 }
261
262 // assign mTokend right before notification - mustn't be set if
263 // anything goes wrong during insertion
264 mTokend = tokend;
265
266 notify(kNotificationCDSAInsertion);
267
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");
276 fault(false);
277 } catch (...) {
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");
281 fault(false);
282 }
283 }
284
285
286 //
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.
292 //
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.
296 //
297 void Token::remove()
298 {
299 StLock<Mutex> _(*this);
300 Syslog::notice("reader %s removed token \"%s\" (%s) subservice %ld",
301 reader().name().c_str(), mPrintName.c_str(),
302 mTokend
303 ? (mTokend->hasTokenUid() ? mTokend->tokenUid().c_str() : "NO UID")
304 : "NO tokend",
305 mSubservice);
306 secdebug("token", "%p begin removal from slot %p (reader %s)",
307 this, &reader(), reader().name().c_str());
308 if (mTokend)
309 mTokend->faultRelay(NULL); // unregister (no more faults, please)
310 notify(kNotificationCDSARemoval);
311 mds().uninstall(mGuid.toString().c_str(), mSubservice);
312 this->kill();
313 secdebug("token", "%p removal complete", this);
314 }
315
316
317 //
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.
322 //
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.
325 //
326 void Token::fault(bool async)
327 {
328 StLock<Mutex> _(*this);
329 if (!mFaulted) { // first one
330 secdebug("token", "%p %s FAULT", this, async ? "ASYNCHRONOUS" : "SYNCHRONOUS");
331
332 // mark faulted
333 mFaulted = true;
334
335 // send CDSA notification
336 notify(kNotificationCDSAFailure);
337
338 // cast off our TokenDaemon for good
339 mTokend = NULL;
340 }
341
342 // if this is a synchronous fault, abort this operation now
343 if (!async)
344 CssmError::throwMe(CSSM_ERRCODE_DEVICE_FAILED);
345 }
346
347
348 void Token::relayFault(bool async)
349 {
350 secdebug("token", "%p fault relayed from tokend", this);
351 this->fault(async);
352 }
353
354
355 //
356 // This is the "kill" hook for Token as a Node<> object.
357 //
358 void Token::kill()
359 {
360 StLock<Mutex> _(*this);
361 if (mTokend)
362 {
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);
370 }
371
372 resetAcls(); // release our TokenDbCommons
373 PerGlobal::kill(); // generic action
374
375 }
376
377
378 //
379 // Send CDSA-layer notifications for this token.
380 // These events are usually received by CDSA plugins working with securityd.
381 //
382 void Token::notify(NotificationEvent event)
383 {
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)));
388 CssmData data;
389 nvd.Export(data);
390
391 // inject notification into Security event system
392 Listener::notify(kNotificationDomainCDSA, event, data);
393
394 // clean up
395 free (data.data());
396 }
397
398
399 //
400 // Choose a token daemon for our card.
401 //
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.
406 //
407 RefPointer<TokenDaemon> Token::chooseTokend()
408 {
409 //@@@ CodeRepository should learn to update from disk changes to be re-usable
410 CodeRepository<GenericBundle> candidates("Security/tokend", ".tokend", "TOKENDAEMONPATH", false);
411 candidates.update();
412 //@@@ we could sort by reverse "maxScore" and avoid launching those who won't cut it anyway...
413
414 RefPointer<TokenDaemon> leader;
415 for (CodeRepository<GenericBundle>::const_iterator it = candidates.begin();
416 it != candidates.end(); it++) {
417 try {
418 // any pre-launch screening of candidate *it goes here
419
420 RefPointer<TokenDaemon> tokend = new TokenDaemon(*it,
421 reader().name(), reader().pcscState(), reader().cache);
422
423 if (tokend->state() == ServerChild::dead) // ah well, this one's no good
424 continue;
425
426 // probe the (single) tokend
427 if (!tokend->probe()) // non comprende...
428 continue;
429
430 // we got a contender!
431 if (!leader || tokend->score() > leader->score())
432 leader = tokend; // a new front runner, he is...
433 } catch (...) {
434 secdebug("token", "exception setting up %s (moving on)", (*it)->canonicalPath().c_str());
435 }
436 }
437 return leader;
438 }
439
440
441 //
442 // Token::Access mediates calls through TokenDaemon to the actual daemon out there.
443 //
444 Token::Access::Access(Token &myToken)
445 : token(myToken)
446 {
447 mTokend = &token.tokend(); // throws if faulted or otherwise inappropriate
448 }
449
450 Token::Access::~Access()
451 {
452 }
453
454
455 //
456 // Debug dump support
457 //
458 #if defined(DEBUGDUMP)
459
460 void Token::dumpNode()
461 {
462 PerGlobal::dumpNode();
463 Debug::dump(" %s[%ld] tokend=%p",
464 mGuid.toString().c_str(), mSubservice, mTokend.get());
465 }
466
467 #endif //DEBUGDUMP