]> git.saurik.com Git - apple/security.git/blame - securityd/src/token.cpp
Security-59306.11.20.tar.gz
[apple/security.git] / securityd / src / token.cpp
CommitLineData
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
46using namespace MDSClient;
47
48
49//
50// SSID -> Token map
51//
52Token::SSIDMap Token::mSubservices;
53// Make sure to always take mSSIDLock after we take the Token lock
54// itself or own it's own.
55Mutex Token::mSSIDLock;
56
57
58//
59// Token construction and destruction is trivial; the good stuff
60// happens in insert() and remove() below.
61//
62Token::Token()
63 : mFaulted(false), mTokend(NULL), mResetLevel(1)
64{
fa7225c8 65 secinfo("token", "%p created", this);
d8f41ccd
A
66}
67
68
69Token::~Token()
70{
fa7225c8 71 secinfo("token", "%p (%s:%d) destroyed",
d8f41ccd
A
72 this, mGuid.toString().c_str(), mSubservice);
73}
74
75
76Reader &Token::reader() const
77{
78 return referent< ::Reader>();
79}
80
81TokenDaemon &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//
97GenericHandle 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//
106AclKind Token::aclKind() const
107{
108 return dbAcl;
109}
110
111Token &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//
121RefPointer<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//
135void 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//
163Token::ResetGeneration Token::resetGeneration() const
164{
165 return mResetLevel;
166}
167
168void 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
183void Token::addCommon(TokenDbCommon &dbc)
184{
fa7225c8 185 secinfo("token", "%p addCommon TokenDbCommon %p", this, &dbc);
d8f41ccd
A
186 mCommons.insert(&dbc);
187}
188
189void 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//
204void 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//
332void 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//
363void 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
385void 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//
395void 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//
423void 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
439static 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//
456RefPointer<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//
515Token::Access::Access(Token &myToken)
516 : token(myToken)
517{
518 mTokend = &token.tokend(); // throws if faulted or otherwise inappropriate
519}
520
521Token::Access::~Access()
522{
523}
524
525
526//
527// Debug dump support
528//
529#if defined(DEBUGDUMP)
530
531void 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