]>
Commit | Line | Data |
---|---|---|
d8f41ccd A |
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> | |
822b670c | 44 | #include <msgtracer_client.h> |
d8f41ccd A |
45 | |
46 | using namespace MDSClient; | |
47 | ||
48 | ||
49 | // | |
50 | // SSID -> Token map | |
51 | // | |
52 | Token::SSIDMap Token::mSubservices; | |
53 | // Make sure to always take mSSIDLock after we take the Token lock | |
54 | // itself or own it's own. | |
55 | Mutex Token::mSSIDLock; | |
56 | ||
57 | ||
58 | // | |
59 | // Token construction and destruction is trivial; the good stuff | |
60 | // happens in insert() and remove() below. | |
61 | // | |
62 | Token::Token() | |
63 | : mFaulted(false), mTokend(NULL), mResetLevel(1) | |
64 | { | |
fa7225c8 | 65 | secinfo("token", "%p created", this); |
d8f41ccd A |
66 | } |
67 | ||
68 | ||
69 | Token::~Token() | |
70 | { | |
fa7225c8 | 71 | secinfo("token", "%p (%s:%d) destroyed", |
d8f41ccd A |
72 | this, mGuid.toString().c_str(), mSubservice); |
73 | } | |
74 | ||
75 | ||
76 | Reader &Token::reader() const | |
77 | { | |
78 | return referent< ::Reader>(); | |
79 | } | |
80 | ||
81 | TokenDaemon &Token::tokend() | |
82 | { | |
83 | StLock<Mutex> _(*this); | |
84 | if (mFaulted) | |
85 | CssmError::throwMe(CSSM_ERRCODE_DEVICE_FAILED); | |
86 | if (mTokend) | |
87 | return *mTokend; | |
88 | else | |
89 | CssmError::throwMe(CSSM_ERRCODE_DEVICE_FAILED); | |
90 | } | |
91 | ||
92 | ||
93 | // | |
94 | // We don't currently use a database handle to tokend. | |
95 | // This is just to satisfy the TokenAcl. | |
96 | // | |
97 | GenericHandle Token::tokenHandle() const | |
98 | { | |
99 | return noDb; // we don't currently use tokend-side DbHandles | |
100 | } | |
101 | ||
102 | ||
103 | // | |
104 | // Token is the SecurityServerAcl for the token | |
105 | // | |
106 | AclKind Token::aclKind() const | |
107 | { | |
108 | return dbAcl; | |
109 | } | |
110 | ||
111 | Token &Token::token() | |
112 | { | |
113 | return *this; | |
114 | } | |
115 | ||
116 | ||
117 | // | |
118 | // Find Token by subservice id. | |
119 | // Throws if ssid is invalid (i.e. always returns non-NULL) | |
120 | // | |
121 | RefPointer<Token> Token::find(uint32 ssid) | |
122 | { | |
123 | StLock<Mutex> _(mSSIDLock); | |
124 | SSIDMap::const_iterator it = mSubservices.find(ssid); | |
125 | if (it == mSubservices.end()) | |
126 | CssmError::throwMe(CSSMERR_CSSM_INVALID_SUBSERVICEID); | |
127 | else | |
128 | return it->second; | |
129 | } | |
130 | ||
131 | ||
132 | // | |
133 | // We override getAcl to provide PIN state feedback | |
134 | // | |
135 | void Token::getAcl(const char *tag, uint32 &count, AclEntryInfo *&acls) | |
136 | { | |
137 | if (pinFromAclTag(tag, "?")) { // read from tokend - do not cache | |
138 | AclEntryInfo *racls; | |
139 | token().tokend().getAcl(aclKind(), tokenHandle(), tag, count, racls); | |
140 | // make a chunk-copy because that's the contract we have with the caller | |
141 | acls = Allocator::standard().alloc<AclEntryInfo>(count * sizeof(AclEntryInfo)); | |
142 | memcpy(acls, racls, count * sizeof(AclEntryInfo)); | |
143 | ChunkCopyWalker copy; | |
144 | for (uint32 n = 0; n < count; n++) | |
145 | walk(copy, acls[n]); | |
146 | return; | |
147 | } | |
148 | ||
149 | TokenAcl::cssmGetAcl(tag, count, acls); | |
150 | } | |
151 | ||
152 | ||
153 | // | |
154 | // Reset management. | |
155 | // A Token has a "reset level", a number that is incremented whenever a token | |
156 | // (hardware) reset is reported (as an error) by tokend. TokenAcls have their | |
157 | // own matching level, which is that of the Token's when the ACL was last synchronized | |
158 | // with tokend. Thus, incrementing the reset level invalidates all TokenAcls | |
159 | // (without the need to enumerate them all). | |
160 | // Note that a Token starts with a level of 1, while ACLs start at zero. This forces | |
161 | // them to initially load their state from tokend. | |
162 | // | |
163 | Token::ResetGeneration Token::resetGeneration() const | |
164 | { | |
165 | return mResetLevel; | |
166 | } | |
167 | ||
168 | void Token::resetAcls() | |
169 | { | |
170 | CommonSet tmpCommons; | |
171 | { | |
172 | StLock<Mutex> _(*this); | |
173 | mResetLevel++; | |
fa7225c8 | 174 | secinfo("token", "%p reset (level=%d, propagating to %ld common(s)", |
d8f41ccd A |
175 | this, mResetLevel, mCommons.size()); |
176 | // Make a copy to avoid deadlock with TokenDbCommon lock | |
177 | tmpCommons = mCommons; | |
178 | } | |
179 | for (CommonSet::const_iterator it = tmpCommons.begin(); it != tmpCommons.end();) | |
180 | RefPointer<TokenDbCommon>(*it++)->resetAcls(); | |
181 | } | |
182 | ||
183 | void Token::addCommon(TokenDbCommon &dbc) | |
184 | { | |
fa7225c8 | 185 | secinfo("token", "%p addCommon TokenDbCommon %p", this, &dbc); |
d8f41ccd A |
186 | mCommons.insert(&dbc); |
187 | } | |
188 | ||
189 | void Token::removeCommon(TokenDbCommon &dbc) | |
190 | { | |
fa7225c8 | 191 | secinfo("token", "%p removeCommon TokenDbCommon %p", this, &dbc); |
d8f41ccd A |
192 | if (mCommons.find(&dbc) != mCommons.end()) |
193 | mCommons.erase(&dbc); | |
194 | } | |
195 | ||
196 | ||
197 | // | |
198 | // Process the logical insertion of a Token into a Reader. | |
199 | // From the client's point of view, this is where the CSSM subservice is created, | |
200 | // characterized, and activated. From tokend's point of view, this is where | |
201 | // we're analyzing the token, determine its characteristics, and get ready to | |
202 | // use it. | |
203 | // | |
204 | void Token::insert(::Reader &slot, RefPointer<TokenDaemon> tokend) | |
205 | { | |
206 | try { | |
207 | // this might take a while... | |
208 | Server::active().longTermActivity(); | |
209 | referent(slot); | |
210 | mState = slot.pcscState(); | |
211 | ||
212 | if (tokend == NULL) { | |
213 | // no pre-determined Tokend - search for one | |
214 | if (!(tokend = chooseTokend())) { | |
fa7225c8 | 215 | secinfo("token", "%p no token daemons available - faulting this card", this); |
d8f41ccd A |
216 | fault(false); // throws |
217 | } | |
218 | } | |
219 | ||
220 | // take Token lock and hold throughout insertion | |
221 | StLock<Mutex> _(*this); | |
222 | ||
223 | Syslog::debug("token inserted into reader %s", slot.name().c_str()); | |
fa7225c8 | 224 | secinfo("token", "%p begin insertion into slot %p (reader %s)", |
d8f41ccd A |
225 | this, &slot, slot.name().c_str()); |
226 | ||
227 | // tell the tokend object to relay faults to us | |
228 | tokend->faultRelay(this); | |
229 | ||
230 | // locate or establish cache directories | |
231 | if (tokend->hasTokenUid()) { | |
fa7225c8 | 232 | secinfo("token", "%p using %s (score=%d, uid=\"%s\")", |
d8f41ccd A |
233 | this, tokend->bundlePath().c_str(), tokend->score(), tokend->tokenUid().c_str()); |
234 | mCache = new TokenCache::Token(reader().cache, | |
235 | tokend->bundleIdentifier() + ":" + tokend->tokenUid()); | |
236 | } else { | |
fa7225c8 | 237 | secinfo("token", "%p using %s (score=%d, temporary)", |
d8f41ccd A |
238 | this, tokend->bundlePath().c_str(), tokend->score()); |
239 | mCache = new TokenCache::Token(reader().cache); | |
240 | } | |
fa7225c8 | 241 | secinfo("token", "%p token cache at %s", this, mCache->root().c_str()); |
d8f41ccd A |
242 | |
243 | // here's the primary parameters of the new subservice | |
244 | mGuid = gGuidAppleSdCSPDL; | |
245 | mSubservice = mCache->subservice(); | |
246 | ||
247 | // establish work areas with tokend | |
248 | char mdsDirectory[PATH_MAX]; | |
249 | char printName[PATH_MAX]; | |
250 | tokend->establish(mGuid, mSubservice, | |
251 | (mCache->type() != TokenCache::Token::existing ? kSecTokendEstablishNewCache : 0) | kSecTokendEstablishMakeMDS, | |
252 | mCache->cachePath().c_str(), mCache->workPath().c_str(), | |
253 | mdsDirectory, printName); | |
254 | ||
255 | // establish print name | |
256 | if (mCache->type() == TokenCache::Token::existing) { | |
257 | mPrintName = mCache->printName(); | |
258 | if (mPrintName.empty()) | |
259 | mPrintName = printName; | |
260 | } else | |
261 | mPrintName = printName; | |
262 | if (mPrintName.empty()) { | |
263 | // last resort - new card and tokend didn't give us one | |
264 | snprintf(printName, sizeof(printName), "smart card #%d", mSubservice); | |
265 | mPrintName = printName; | |
266 | } | |
267 | if (mCache->type() != TokenCache::Token::existing) | |
268 | mCache->printName(mPrintName); // store in cache | |
269 | ||
270 | // install MDS | |
fa7225c8 | 271 | secinfo("token", "%p installing MDS from %s(%s)", this, |
d8f41ccd A |
272 | tokend->bundlePath().c_str(), |
273 | mdsDirectory[0] ? mdsDirectory : "ALL"); | |
274 | string holdGuid = mGuid.toString(); // extend lifetime of std::string | |
275 | string holdTokenUid; | |
276 | if (tokend->hasTokenUid()) | |
277 | holdTokenUid = tokend->tokenUid(); | |
278 | string holdPrintName = this->printName(); | |
279 | MDS_InstallDefaults mdsDefaults = { | |
280 | holdGuid.c_str(), | |
281 | mSubservice, | |
282 | holdTokenUid.c_str(), | |
283 | holdPrintName.c_str() | |
284 | }; | |
285 | mds().install(&mdsDefaults, | |
286 | tokend->bundlePath().c_str(), | |
287 | mdsDirectory[0] ? mdsDirectory : NULL, | |
288 | NULL); | |
289 | ||
290 | { | |
291 | // commit to insertion | |
292 | StLock<Mutex> _(mSSIDLock); | |
293 | assert(mSubservices.find(mSubservice) == mSubservices.end()); | |
294 | mSubservices.insert(make_pair(mSubservice, this)); | |
295 | } | |
296 | ||
297 | // assign mTokend right before notification - mustn't be set if | |
298 | // anything goes wrong during insertion | |
299 | mTokend = tokend; | |
300 | ||
301 | notify(kNotificationCDSAInsertion); | |
302 | ||
fa7225c8 | 303 | Syslog::notice("reader %s inserted token \"%s\" (%s) subservice %d using driver %s", |
d8f41ccd A |
304 | slot.name().c_str(), mPrintName.c_str(), |
305 | mTokend->hasTokenUid() ? mTokend->tokenUid().c_str() : "NO UID", | |
306 | mSubservice, mTokend->bundleIdentifier().c_str()); | |
fa7225c8 | 307 | secinfo("token", "%p inserted as %s:%d", this, mGuid.toString().c_str(), mSubservice); |
d8f41ccd | 308 | } catch (const CommonError &err) { |
fa7225c8 A |
309 | Syslog::notice("token in reader %s cannot be used (error %d)", slot.name().c_str(), err.osStatus()); |
310 | secinfo("token", "exception during insertion processing"); | |
d8f41ccd A |
311 | fault(false); |
312 | } catch (...) { | |
313 | // exception thrown during insertion processing. Mark faulted | |
314 | Syslog::notice("token in reader %s cannot be used", slot.name().c_str()); | |
fa7225c8 | 315 | secinfo("token", "exception during insertion processing"); |
d8f41ccd A |
316 | fault(false); |
317 | } | |
318 | } | |
319 | ||
320 | ||
321 | // | |
322 | // Process the logical removal of a Token from a Reader. | |
323 | // Most of the time, this is asynchronous - someone has yanked the physical | |
324 | // token out of a physical slot, and we're left with changing our universe | |
325 | // to conform to the new realities. Reality #1 is that we can't talk to the | |
326 | // physical token anymore. | |
327 | // | |
328 | // Note that if we're in FAULT mode, there really isn't a TokenDaemon around | |
329 | // to kick. We're just holding on to represent the fact that there *is* a (useless) | |
330 | // token in the slot, and now it's been finally yanked. Good riddance. | |
331 | // | |
332 | void Token::remove() | |
333 | { | |
334 | StLock<Mutex> _(*this); | |
fa7225c8 | 335 | Syslog::notice("reader %s removed token \"%s\" (%s) subservice %d", |
d8f41ccd A |
336 | reader().name().c_str(), mPrintName.c_str(), |
337 | mTokend | |
338 | ? (mTokend->hasTokenUid() ? mTokend->tokenUid().c_str() : "NO UID") | |
339 | : "NO tokend", | |
340 | mSubservice); | |
fa7225c8 | 341 | secinfo("token", "%p begin removal from slot %p (reader %s)", |
d8f41ccd A |
342 | this, &reader(), reader().name().c_str()); |
343 | if (mTokend) | |
344 | mTokend->faultRelay(NULL); // unregister (no more faults, please) | |
345 | mds().uninstall(mGuid.toString().c_str(), mSubservice); | |
fa7225c8 | 346 | secinfo("token", "%p mds uninstall complete", this); |
d8f41ccd | 347 | this->kill(); |
fa7225c8 | 348 | secinfo("token", "%p kill complete", this); |
d8f41ccd | 349 | notify(kNotificationCDSARemoval); |
fa7225c8 | 350 | secinfo("token", "%p removal complete", this); |
d8f41ccd A |
351 | } |
352 | ||
353 | ||
354 | // | |
355 | // Set the token to fault state. | |
356 | // This essentially "cuts off" all operations on an inserted token and makes | |
357 | // them fail. It also sends a FAULT notification via CSSM to any clients. | |
358 | // Only one fault is actually processed; multiple calls are ignored. | |
359 | // | |
360 | // Note that a faulted token is not REMOVED; it's still physically present. | |
361 | // No fault is declared when a token is actually removed. | |
362 | // | |
363 | void Token::fault(bool async) | |
364 | { | |
365 | StLock<Mutex> _(*this); | |
366 | if (!mFaulted) { // first one | |
fa7225c8 | 367 | secinfo("token", "%p %s FAULT", this, async ? "ASYNCHRONOUS" : "SYNCHRONOUS"); |
d8f41ccd A |
368 | |
369 | // mark faulted | |
370 | mFaulted = true; | |
371 | ||
372 | // send CDSA notification | |
373 | notify(kNotificationCDSAFailure); | |
374 | ||
375 | // cast off our TokenDaemon for good | |
376 | //>>> mTokend = NULL; | |
377 | } | |
378 | ||
379 | // if this is a synchronous fault, abort this operation now | |
380 | if (!async) | |
381 | CssmError::throwMe(CSSM_ERRCODE_DEVICE_FAILED); | |
382 | } | |
383 | ||
384 | ||
385 | void Token::relayFault(bool async) | |
386 | { | |
fa7225c8 | 387 | secinfo("token", "%p fault relayed from tokend", this); |
d8f41ccd A |
388 | this->fault(async); |
389 | } | |
390 | ||
391 | ||
392 | // | |
393 | // This is the "kill" hook for Token as a Node<> object. | |
394 | // | |
395 | void Token::kill() | |
396 | { | |
397 | // Avoid holding the lock across call to resetAcls | |
398 | // This can cause deadlock on card removal | |
399 | { | |
400 | StLock<Mutex> _(*this); | |
401 | if (mTokend) | |
402 | { | |
403 | mTokend = NULL; // cast loose our tokend (if any) | |
404 | // Take us out of the map | |
405 | StLock<Mutex> _(mSSIDLock); | |
406 | SSIDMap::iterator it = mSubservices.find(mSubservice); | |
407 | assert(it != mSubservices.end() && it->second == this); | |
408 | if (it != mSubservices.end() && it->second == this) | |
409 | mSubservices.erase(it); | |
410 | } | |
411 | } | |
412 | ||
413 | resetAcls(); // release our TokenDbCommons | |
414 | PerGlobal::kill(); // generic action | |
415 | ||
416 | } | |
417 | ||
418 | ||
419 | // | |
420 | // Send CDSA-layer notifications for this token. | |
421 | // These events are usually received by CDSA plugins working with securityd. | |
422 | // | |
423 | void Token::notify(NotificationEvent event) | |
424 | { | |
425 | NameValueDictionary nvd; | |
426 | CssmSubserviceUid ssuid(mGuid, NULL, h2n (mSubservice), | |
427 | h2n(CSSM_SERVICE_DL | CSSM_SERVICE_CSP)); | |
428 | nvd.Insert(new NameValuePair(SSUID_KEY, CssmData::wrap(ssuid))); | |
429 | CssmData data; | |
430 | nvd.Export(data); | |
431 | ||
432 | // inject notification into Security event system | |
433 | Listener::notify(kNotificationDomainCDSA, event, data); | |
434 | ||
435 | // clean up | |
436 | free (data.data()); | |
437 | } | |
438 | ||
822b670c A |
439 | static void mt_log_ctk_tokend(const char *signature, const char *signature2) |
440 | { | |
441 | msgtracer_log_with_keys("com.apple.ctk.tokend", ASL_LEVEL_NOTICE, | |
442 | "com.apple.message.signature", signature, | |
443 | "com.apple.message.signature2", signature2, | |
444 | "com.apple.message.summarize", "YES", | |
445 | NULL); | |
446 | } | |
d8f41ccd A |
447 | |
448 | // | |
449 | // Choose a token daemon for our card. | |
450 | // | |
451 | // Right now, we probe tokends sequentially. If there are many tokends, it would be | |
452 | // faster to launch them in parallel (relying on PCSC transactions to separate them); | |
453 | // but it's not altogether clear whether this would slow things down on low-memory | |
454 | // systems by forcing (excessive) swapping. There is room for future experimentation. | |
455 | // | |
456 | RefPointer<TokenDaemon> Token::chooseTokend() | |
457 | { | |
458 | //@@@ CodeRepository should learn to update from disk changes to be re-usable | |
459 | CodeRepository<Bundle> candidates("Security/tokend", ".tokend", "TOKENDAEMONPATH", false); | |
460 | candidates.update(); | |
461 | //@@@ we could sort by reverse "maxScore" and avoid launching those who won't cut it anyway... | |
462 | ||
822b670c A |
463 | string chosenIdentifier; |
464 | set<string> candidateIdentifiers; | |
d8f41ccd A |
465 | RefPointer<TokenDaemon> leader; |
466 | for (CodeRepository<Bundle>::const_iterator it = candidates.begin(); | |
467 | it != candidates.end(); it++) { | |
468 | RefPointer<Bundle> candidate = *it; | |
469 | try { | |
470 | // skip software token daemons - ineligible for automatic choosing | |
471 | if (CFTypeRef type = (*it)->infoPlistItem("TokendType")) | |
472 | if (CFEqual(type, CFSTR("software"))) | |
473 | continue; | |
474 | ||
475 | // okay, launch it and let it try | |
476 | RefPointer<TokenDaemon> tokend = new TokenDaemon(candidate, | |
477 | reader().name(), reader().pcscState(), reader().cache); | |
478 | ||
822b670c A |
479 | // add identifier to candidate names set |
480 | candidateIdentifiers.insert(tokend->bundleIdentifier()); | |
481 | ||
d8f41ccd A |
482 | if (tokend->state() == ServerChild::dead) // ah well, this one's no good |
483 | continue; | |
484 | ||
485 | // probe the (single) tokend | |
486 | if (!tokend->probe()) // non comprende... | |
487 | continue; | |
488 | ||
489 | // we got a contender! | |
822b670c | 490 | if (!leader || tokend->score() > leader->score()) { |
d8f41ccd | 491 | leader = tokend; // a new front runner, he is... |
822b670c A |
492 | chosenIdentifier = leader->bundleIdentifier(); |
493 | } | |
d8f41ccd | 494 | } catch (...) { |
fa7225c8 | 495 | secinfo("token", "exception setting up %s (moving on)", candidate->canonicalPath().c_str()); |
d8f41ccd A |
496 | } |
497 | } | |
822b670c A |
498 | |
499 | // concatenate all candidate identifiers (sorted internally inside std::set) | |
500 | string identifiers; | |
501 | for (set<string>::const_iterator i = candidateIdentifiers.begin(), e = candidateIdentifiers.end(); i != e; ++i) { | |
502 | if (i != candidateIdentifiers.begin()) | |
503 | identifiers.append(";"); | |
504 | identifiers.append(*i); | |
505 | } | |
506 | mt_log_ctk_tokend(identifiers.c_str(), chosenIdentifier.c_str()); | |
507 | ||
d8f41ccd A |
508 | return leader; |
509 | } | |
510 | ||
511 | ||
512 | // | |
513 | // Token::Access mediates calls through TokenDaemon to the actual daemon out there. | |
514 | // | |
515 | Token::Access::Access(Token &myToken) | |
516 | : token(myToken) | |
517 | { | |
518 | mTokend = &token.tokend(); // throws if faulted or otherwise inappropriate | |
519 | } | |
520 | ||
521 | Token::Access::~Access() | |
522 | { | |
523 | } | |
524 | ||
525 | ||
526 | // | |
527 | // Debug dump support | |
528 | // | |
529 | #if defined(DEBUGDUMP) | |
530 | ||
531 | void Token::dumpNode() | |
532 | { | |
533 | PerGlobal::dumpNode(); | |
534 | Debug::dump(" %s[%d] tokend=%p", | |
535 | mGuid.toString().c_str(), mSubservice, mTokend.get()); | |
536 | } | |
537 | ||
538 | #endif //DEBUGDUMP |