]> git.saurik.com Git - apple/security.git/blob - securityd/src/token.cpp
Security-58286.1.32.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 #include <msgtracer_client.h>
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 {
65 secinfo("token", "%p created", this);
66 }
67
68
69 Token::~Token()
70 {
71 secinfo("token", "%p (%s:%d) destroyed",
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++;
174 secinfo("token", "%p reset (level=%d, propagating to %ld common(s)",
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 {
185 secinfo("token", "%p addCommon TokenDbCommon %p", this, &dbc);
186 mCommons.insert(&dbc);
187 }
188
189 void Token::removeCommon(TokenDbCommon &dbc)
190 {
191 secinfo("token", "%p removeCommon TokenDbCommon %p", this, &dbc);
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())) {
215 secinfo("token", "%p no token daemons available - faulting this card", this);
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());
224 secinfo("token", "%p begin insertion into slot %p (reader %s)",
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()) {
232 secinfo("token", "%p using %s (score=%d, uid=\"%s\")",
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 {
237 secinfo("token", "%p using %s (score=%d, temporary)",
238 this, tokend->bundlePath().c_str(), tokend->score());
239 mCache = new TokenCache::Token(reader().cache);
240 }
241 secinfo("token", "%p token cache at %s", this, mCache->root().c_str());
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
271 secinfo("token", "%p installing MDS from %s(%s)", this,
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
303 Syslog::notice("reader %s inserted token \"%s\" (%s) subservice %d using driver %s",
304 slot.name().c_str(), mPrintName.c_str(),
305 mTokend->hasTokenUid() ? mTokend->tokenUid().c_str() : "NO UID",
306 mSubservice, mTokend->bundleIdentifier().c_str());
307 secinfo("token", "%p inserted as %s:%d", this, mGuid.toString().c_str(), mSubservice);
308 } catch (const CommonError &err) {
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");
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());
315 secinfo("token", "exception during insertion processing");
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);
335 Syslog::notice("reader %s removed token \"%s\" (%s) subservice %d",
336 reader().name().c_str(), mPrintName.c_str(),
337 mTokend
338 ? (mTokend->hasTokenUid() ? mTokend->tokenUid().c_str() : "NO UID")
339 : "NO tokend",
340 mSubservice);
341 secinfo("token", "%p begin removal from slot %p (reader %s)",
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);
346 secinfo("token", "%p mds uninstall complete", this);
347 this->kill();
348 secinfo("token", "%p kill complete", this);
349 notify(kNotificationCDSARemoval);
350 secinfo("token", "%p removal complete", this);
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
367 secinfo("token", "%p %s FAULT", this, async ? "ASYNCHRONOUS" : "SYNCHRONOUS");
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 {
387 secinfo("token", "%p fault relayed from tokend", this);
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
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 }
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
463 string chosenIdentifier;
464 set<string> candidateIdentifiers;
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
479 // add identifier to candidate names set
480 candidateIdentifiers.insert(tokend->bundleIdentifier());
481
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!
490 if (!leader || tokend->score() > leader->score()) {
491 leader = tokend; // a new front runner, he is...
492 chosenIdentifier = leader->bundleIdentifier();
493 }
494 } catch (...) {
495 secinfo("token", "exception setting up %s (moving on)", candidate->canonicalPath().c_str());
496 }
497 }
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
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