]> git.saurik.com Git - apple/security.git/blob - securityd/src/token.cpp
Security-59754.80.3.tar.gz
[apple/security.git] / securityd / src / token.cpp
1 /*
2 * Copyright (c) 2004-2008,2013 Apple 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 secinfo("token", "%p created", this);
65 }
66
67
68 Token::~Token()
69 {
70 secinfo("token", "%p (%s:%d) 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 // We override getAcl to provide PIN state feedback
133 //
134 void Token::getAcl(const char *tag, uint32 &count, AclEntryInfo *&acls)
135 {
136 if (pinFromAclTag(tag, "?")) { // read from tokend - do not cache
137 AclEntryInfo *racls;
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++)
144 walk(copy, acls[n]);
145 return;
146 }
147
148 TokenAcl::cssmGetAcl(tag, count, acls);
149 }
150
151
152 //
153 // Reset management.
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.
161 //
162 Token::ResetGeneration Token::resetGeneration() const
163 {
164 return mResetLevel;
165 }
166
167 void Token::resetAcls()
168 {
169 CommonSet tmpCommons;
170 {
171 StLock<Mutex> _(*this);
172 mResetLevel++;
173 secinfo("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;
177 }
178 for (CommonSet::const_iterator it = tmpCommons.begin(); it != tmpCommons.end();)
179 RefPointer<TokenDbCommon>(*it++)->resetAcls();
180 }
181
182 void Token::addCommon(TokenDbCommon &dbc)
183 {
184 secinfo("token", "%p addCommon TokenDbCommon %p", this, &dbc);
185 mCommons.insert(&dbc);
186 }
187
188 void Token::removeCommon(TokenDbCommon &dbc)
189 {
190 secinfo("token", "%p removeCommon TokenDbCommon %p", this, &dbc);
191 if (mCommons.find(&dbc) != mCommons.end())
192 mCommons.erase(&dbc);
193 }
194
195
196 //
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
201 // use it.
202 //
203 void Token::insert(::Reader &slot, RefPointer<TokenDaemon> tokend)
204 {
205 try {
206 // this might take a while...
207 Server::active().longTermActivity();
208 referent(slot);
209 mState = slot.pcscState();
210
211 if (tokend == NULL) {
212 // no pre-determined Tokend - search for one
213 if (!(tokend = chooseTokend())) {
214 secinfo("token", "%p no token daemons available - faulting this card", this);
215 fault(false); // throws
216 }
217 }
218
219 // take Token lock and hold throughout insertion
220 StLock<Mutex> _(*this);
221
222 Syslog::debug("token inserted into reader %s", slot.name().c_str());
223 secinfo("token", "%p begin insertion into slot %p (reader %s)",
224 this, &slot, slot.name().c_str());
225
226 // tell the tokend object to relay faults to us
227 tokend->faultRelay(this);
228
229 // locate or establish cache directories
230 if (tokend->hasTokenUid()) {
231 secinfo("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());
235 } else {
236 secinfo("token", "%p using %s (score=%d, temporary)",
237 this, tokend->bundlePath().c_str(), tokend->score());
238 mCache = new TokenCache::Token(reader().cache);
239 }
240 secinfo("token", "%p token cache at %s", this, mCache->root().c_str());
241
242 // here's the primary parameters of the new subservice
243 mGuid = gGuidAppleSdCSPDL;
244 mSubservice = mCache->subservice();
245
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);
253
254 // establish print name
255 if (mCache->type() == TokenCache::Token::existing) {
256 mPrintName = mCache->printName();
257 if (mPrintName.empty())
258 mPrintName = printName;
259 } else
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;
265 }
266 if (mCache->type() != TokenCache::Token::existing)
267 mCache->printName(mPrintName); // store in cache
268
269 // install MDS
270 secinfo("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
274 string holdTokenUid;
275 if (tokend->hasTokenUid())
276 holdTokenUid = tokend->tokenUid();
277 string holdPrintName = this->printName();
278 MDS_InstallDefaults mdsDefaults = {
279 holdGuid.c_str(),
280 mSubservice,
281 holdTokenUid.c_str(),
282 holdPrintName.c_str()
283 };
284 mds().install(&mdsDefaults,
285 tokend->bundlePath().c_str(),
286 mdsDirectory[0] ? mdsDirectory : NULL,
287 NULL);
288
289 {
290 // commit to insertion
291 StLock<Mutex> _(mSSIDLock);
292 assert(mSubservices.find(mSubservice) == mSubservices.end());
293 mSubservices.insert(make_pair(mSubservice, this));
294 }
295
296 // assign mTokend right before notification - mustn't be set if
297 // anything goes wrong during insertion
298 mTokend = tokend;
299
300 notify(kNotificationCDSAInsertion);
301
302 Syslog::notice("reader %s inserted token \"%s\" (%s) subservice %d 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 secinfo("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 %d)", slot.name().c_str(), err.osStatus());
309 secinfo("token", "exception during insertion processing");
310 fault(false);
311 } catch (...) {
312 // exception thrown during insertion processing. Mark faulted
313 Syslog::notice("token in reader %s cannot be used", slot.name().c_str());
314 secinfo("token", "exception during insertion processing");
315 fault(false);
316 }
317 }
318
319
320 //
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.
326 //
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.
330 //
331 void Token::remove()
332 {
333 StLock<Mutex> _(*this);
334 Syslog::notice("reader %s removed token \"%s\" (%s) subservice %d",
335 reader().name().c_str(), mPrintName.c_str(),
336 mTokend
337 ? (mTokend->hasTokenUid() ? mTokend->tokenUid().c_str() : "NO UID")
338 : "NO tokend",
339 mSubservice);
340 secinfo("token", "%p begin removal from slot %p (reader %s)",
341 this, &reader(), reader().name().c_str());
342 if (mTokend)
343 mTokend->faultRelay(NULL); // unregister (no more faults, please)
344 mds().uninstall(mGuid.toString().c_str(), mSubservice);
345 secinfo("token", "%p mds uninstall complete", this);
346 this->kill();
347 secinfo("token", "%p kill complete", this);
348 notify(kNotificationCDSARemoval);
349 secinfo("token", "%p removal complete", this);
350 }
351
352
353 //
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.
358 //
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.
361 //
362 void Token::fault(bool async)
363 {
364 StLock<Mutex> _(*this);
365 if (!mFaulted) { // first one
366 secinfo("token", "%p %s FAULT", this, async ? "ASYNCHRONOUS" : "SYNCHRONOUS");
367
368 // mark faulted
369 mFaulted = true;
370
371 // send CDSA notification
372 notify(kNotificationCDSAFailure);
373
374 // cast off our TokenDaemon for good
375 //>>> mTokend = NULL;
376 }
377
378 // if this is a synchronous fault, abort this operation now
379 if (!async)
380 CssmError::throwMe(CSSM_ERRCODE_DEVICE_FAILED);
381 }
382
383
384 void Token::relayFault(bool async)
385 {
386 secinfo("token", "%p fault relayed from tokend", this);
387 this->fault(async);
388 }
389
390
391 //
392 // This is the "kill" hook for Token as a Node<> object.
393 //
394 void Token::kill()
395 {
396 // Avoid holding the lock across call to resetAcls
397 // This can cause deadlock on card removal
398 {
399 StLock<Mutex> _(*this);
400 if (mTokend)
401 {
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);
409 }
410 }
411
412 resetAcls(); // release our TokenDbCommons
413 PerGlobal::kill(); // generic action
414
415 }
416
417
418 //
419 // Send CDSA-layer notifications for this token.
420 // These events are usually received by CDSA plugins working with securityd.
421 //
422 void Token::notify(NotificationEvent event)
423 {
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)));
428 CssmData data;
429 nvd.Export(data);
430
431 // inject notification into Security event system
432 Listener::notify(kNotificationDomainCDSA, event, data);
433
434 // clean up
435 free (data.data());
436 }
437
438 //
439 // Choose a token daemon for our card.
440 //
441 // Right now, we probe tokends sequentially. If there are many tokends, it would be
442 // faster to launch them in parallel (relying on PCSC transactions to separate them);
443 // but it's not altogether clear whether this would slow things down on low-memory
444 // systems by forcing (excessive) swapping. There is room for future experimentation.
445 //
446 RefPointer<TokenDaemon> Token::chooseTokend()
447 {
448 //@@@ CodeRepository should learn to update from disk changes to be re-usable
449 CodeRepository<Bundle> candidates("Security/tokend", ".tokend", "TOKENDAEMONPATH", false);
450 candidates.update();
451 //@@@ we could sort by reverse "maxScore" and avoid launching those who won't cut it anyway...
452
453 string chosenIdentifier;
454 set<string> candidateIdentifiers;
455 RefPointer<TokenDaemon> leader;
456 for (CodeRepository<Bundle>::const_iterator it = candidates.begin();
457 it != candidates.end(); it++) {
458 RefPointer<Bundle> candidate = *it;
459 try {
460 // skip software token daemons - ineligible for automatic choosing
461 if (CFTypeRef type = (*it)->infoPlistItem("TokendType"))
462 if (CFEqual(type, CFSTR("software")))
463 continue;
464
465 // okay, launch it and let it try
466 RefPointer<TokenDaemon> tokend = new TokenDaemon(candidate,
467 reader().name(), reader().pcscState(), reader().cache);
468
469 // add identifier to candidate names set
470 candidateIdentifiers.insert(tokend->bundleIdentifier());
471
472 if (tokend->state() == ServerChild::dead) // ah well, this one's no good
473 continue;
474
475 // probe the (single) tokend
476 if (!tokend->probe()) // non comprende...
477 continue;
478
479 // we got a contender!
480 if (!leader || tokend->score() > leader->score()) {
481 leader = tokend; // a new front runner, he is...
482 chosenIdentifier = leader->bundleIdentifier();
483 }
484 } catch (...) {
485 secinfo("token", "exception setting up %s (moving on)", candidate->canonicalPath().c_str());
486 }
487 }
488
489 // concatenate all candidate identifiers (sorted internally inside std::set)
490 string identifiers;
491 for (set<string>::const_iterator i = candidateIdentifiers.begin(), e = candidateIdentifiers.end(); i != e; ++i) {
492 if (i != candidateIdentifiers.begin())
493 identifiers.append(";");
494 identifiers.append(*i);
495 }
496
497 return leader;
498 }
499
500
501 //
502 // Token::Access mediates calls through TokenDaemon to the actual daemon out there.
503 //
504 Token::Access::Access(Token &myToken)
505 : token(myToken)
506 {
507 mTokend = &token.tokend(); // throws if faulted or otherwise inappropriate
508 }
509
510 Token::Access::~Access()
511 {
512 }
513
514
515 //
516 // Debug dump support
517 //
518 #if defined(DEBUGDUMP)
519
520 void Token::dumpNode()
521 {
522 PerGlobal::dumpNode();
523 Debug::dump(" %s[%d] tokend=%p",
524 mGuid.toString().c_str(), mSubservice, mTokend.get());
525 }
526
527 #endif //DEBUGDUMP