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
45using namespace MDSClient;
46
47
48//
49// SSID -> Token map
50//
51Token::SSIDMap Token::mSubservices;
52// Make sure to always take mSSIDLock after we take the Token lock
53// itself or own it's own.
54Mutex Token::mSSIDLock;
55
56
57//
58// Token construction and destruction is trivial; the good stuff
59// happens in insert() and remove() below.
60//
61Token::Token()
62	: mFaulted(false), mTokend(NULL), mResetLevel(1)
63{
64	secdebug("token", "%p created", this);
65}
66
67
68Token::~Token()
69{
70	secdebug("token", "%p (%s:%d) destroyed",
71		this, mGuid.toString().c_str(), mSubservice);
72}
73
74
75Reader &Token::reader() const
76{
77	return referent< ::Reader>();
78}
79
80TokenDaemon &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//
96GenericHandle 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//
105AclKind Token::aclKind() const
106{
107	return dbAcl;
108}
109
110Token &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//
120RefPointer<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//
134void 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//
162Token::ResetGeneration Token::resetGeneration() const
163{
164	return mResetLevel;
165}
166
167void Token::resetAcls()
168{
169	CommonSet tmpCommons;
170	{
171		StLock<Mutex> _(*this);
172		mResetLevel++;
173		secdebug("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
182void Token::addCommon(TokenDbCommon &dbc)
183{
184	secdebug("token", "%p addCommon TokenDbCommon %p", this, &dbc);
185	mCommons.insert(&dbc);
186}
187
188void Token::removeCommon(TokenDbCommon &dbc)
189{
190	secdebug("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//
203void 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				secdebug("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		secdebug("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			secdebug("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			secdebug("token", "%p using %s (score=%d, temporary)",
237				this, tokend->bundlePath().c_str(), tokend->score());
238			mCache = new TokenCache::Token(reader().cache);
239		}
240		secdebug("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		secdebug("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 %ld 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		secdebug("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 %ld)", slot.name().c_str(), err.osStatus());
309		secdebug("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		secdebug("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//
331void Token::remove()
332{
333	StLock<Mutex> _(*this);
334	Syslog::notice("reader %s removed token \"%s\" (%s) subservice %ld",
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	secdebug("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	secdebug("token", "%p mds uninstall complete", this);
346	this->kill();
347	secdebug("token", "%p kill complete", this);
348	notify(kNotificationCDSARemoval);
349	secdebug("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//
362void Token::fault(bool async)
363{
364	StLock<Mutex> _(*this);
365	if (!mFaulted) {	// first one
366		secdebug("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
384void Token::relayFault(bool async)
385{
386	secdebug("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//
394void 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//
422void 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//
440// Choose a token daemon for our card.
441//
442// Right now, we probe tokends sequentially. If there are many tokends, it would be
443// faster to launch them in parallel (relying on PCSC transactions to separate them);
444// but it's not altogether clear whether this would slow things down on low-memory
445// systems by forcing (excessive) swapping. There is room for future experimentation.
446//
447RefPointer<TokenDaemon> Token::chooseTokend()
448{
449	//@@@ CodeRepository should learn to update from disk changes to be re-usable
450	CodeRepository<Bundle> candidates("Security/tokend", ".tokend", "TOKENDAEMONPATH", false);
451	candidates.update();
452	//@@@ we could sort by reverse "maxScore" and avoid launching those who won't cut it anyway...
453
454	RefPointer<TokenDaemon> leader;
455	for (CodeRepository<Bundle>::const_iterator it = candidates.begin();
456			it != candidates.end(); it++) {
457		RefPointer<Bundle> candidate = *it;
458		try {
459			// skip software token daemons - ineligible for automatic choosing
460			if (CFTypeRef type = (*it)->infoPlistItem("TokendType"))
461				if (CFEqual(type, CFSTR("software")))
462					continue;
463
464			// okay, launch it and let it try
465			RefPointer<TokenDaemon> tokend = new TokenDaemon(candidate,
466				reader().name(), reader().pcscState(), reader().cache);
467
468			if (tokend->state() == ServerChild::dead)	// ah well, this one's no good
469				continue;
470
471			// probe the (single) tokend
472			if (!tokend->probe())		// non comprende...
473				continue;
474
475			// we got a contender!
476			if (!leader || tokend->score() > leader->score())
477				leader = tokend;		// a new front runner, he is...
478		} catch (...) {
479			secdebug("token", "exception setting up %s (moving on)", candidate->canonicalPath().c_str());
480		}
481	}
482	return leader;
483}
484
485
486//
487// Token::Access mediates calls through TokenDaemon to the actual daemon out there.
488//
489Token::Access::Access(Token &myToken)
490	: token(myToken)
491{
492	mTokend = &token.tokend();	// throws if faulted or otherwise inappropriate
493}
494
495Token::Access::~Access()
496{
497}
498
499
500//
501// Debug dump support
502//
503#if defined(DEBUGDUMP)
504
505void Token::dumpNode()
506{
507	PerGlobal::dumpNode();
508	Debug::dump(" %s[%d] tokend=%p",
509		mGuid.toString().c_str(), mSubservice, mTokend.get());
510}
511
512#endif //DEBUGDUMP
513