1/*
2 * Copyright (c) 2004-2007,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// tokenacl - Token-based ACL implementation
27//
28#include "tokenacl.h"
29#include "tokend.h"
30#include "token.h"
31#include "tokendatabase.h"
32#include "agentquery.h"
33#include <security_utilities/trackingallocator.h>
34#include <security_cdsa_utilities/cssmbridge.h>
35
36
37//
38// A TokenAcl initializes to "invalid, needs update".
39// Note how our Token will start its ResetGeneration at 1, while we start at zero.
40//
41TokenAcl::TokenAcl()
42	: mLastReset(0)
43{
44}
45
46
47//
48// Instantiate is called (by the ACL machinery core) before this ACL object's
49// contents are used in any way. Here is where we fetch the ACL data from tokend
50// (if we haven't got it yet).
51//
52void TokenAcl::instantiateAcl()
53{
54	if (token().resetGeneration(mLastReset))
55		return;
56
57	secdebug("tokenacl", "%p loading ACLs from tokend", this);
58
59	// read owner
60	AclOwnerPrototype *owner = NULL;
61	token().tokend().getOwner(aclKind(), tokenHandle(), owner);
62	assert(owner);
63
64	// read entries
65	uint32 count;
66	AclEntryInfo *infos;
67	token().tokend().getAcl(aclKind(), tokenHandle(), NULL, count, infos);
68
69	// commit to setting the ACL data
70	ObjectAcl::owner(*owner);
71	ObjectAcl::entries(count, infos);
72
73	// and if we actually made it to here...
74	mLastReset = token().resetGeneration();
75}
76
77
78//
79// The ACL machinery core calls this after successfully making changes to our ACL.
80//
81void TokenAcl::changedAcl()
82{
83}
84
85
86//
87// CSSM-layer read gates. This accesses a cached version prepared in our instantiateAcl().
88//
89void TokenAcl::getOwner(AclOwnerPrototype &owner)
90{
91	ObjectAcl::cssmGetOwner(owner);
92}
93
94void TokenAcl::getAcl(const char *tag, uint32 &count, AclEntryInfo *&acls)
95{
96	ObjectAcl::cssmGetAcl(tag, count, acls);
97}
98
99
100//
101// CSSM-layer write gates.
102// This doesn't directly write to the local ObjectAcl at all. The call is relayed to
103// tokend, and the resulting ACL is being re-read when next needed.
104//
105void TokenAcl::changeAcl(const AclEdit &edit, const AccessCredentials *cred, Database *db)
106{
107	// changeAcl from/to a PIN (source) ACL has special UI handling here
108	// @@@ this is an ad-hoc hack; general solution awaits the ACL machinery rebuild later
109	instantiateAcl();		// (redundant except in error cases)
110	if (TokenDatabase *tokenDb = dynamic_cast<TokenDatabase *>(db))
111		if (edit.mode() == CSSM_ACL_EDIT_MODE_REPLACE)
112			if (const AclEntryInput *input = edit.newEntry()) {
113				if (unsigned pin = pinFromAclTag(input->proto().tag())) {
114					// assume this is a PIN change request
115					pinChange(pin, edit.handle(), *tokenDb);
116					invalidateAcl();
117					return;
118				}
119			}
120
121	// hand the request off to tokend to do as it will
122	token().tokend().changeAcl(aclKind(), tokenHandle(), Required(cred), edit);
123	invalidateAcl();
124}
125
126void TokenAcl::changeOwner(const AclOwnerPrototype &newOwner,
127	const AccessCredentials *cred, Database *db)
128{
129	token().tokend().changeOwner(aclKind(), tokenHandle(), Required(cred), newOwner);
130	invalidateAcl();
131}
132
133
134//
135// Ad-hoc PIN change processing.
136// This cooks a suitable changeAcl call to tokend, ad hoc.
137// It does NOT complete the originating request; it replaces it completely.
138// (Completion processing requires not-yet-implemented ACL machine UI coalescing features.)
139//
140class QueryNewPin : public QueryNewPassphrase {
141public:
142	QueryNewPin(unsigned int pinn, CSSM_ACL_HANDLE h, TokenDatabase &db, Reason reason)
143		: QueryNewPassphrase(db, reason), pin(pinn), handle(h) { }
144
145	const unsigned int pin;
146	const CSSM_ACL_HANDLE handle;
147
148	Reason accept(CssmManagedData &passphrase, CssmData *oldPassphrase);
149};
150
151SecurityAgent::Reason QueryNewPin::accept(CssmManagedData &passphrase, CssmData *oldPassphrase)
152{
153	assert(oldPassphrase);		// we don't handle the new-pin case (yet)
154
155	// form a changeAcl query and send it to tokend
156	try {
157		TrackingAllocator alloc(Allocator::standard());
158		AclEntryPrototype proto(TypedList(alloc, CSSM_ACL_SUBJECT_TYPE_PROMPTED_PASSWORD,
159			new(alloc) ListElement(passphrase)
160			));
161		proto.authorization() = AuthorizationGroup(CSSM_ACL_AUTHORIZATION_PREAUTH(pin), alloc);
162		char pintag[20]; sprintf(pintag, "PIN%d", pin);
163		proto.tag(pintag);
164		AclEntryInput input(proto);
165		AclEdit edit(CSSM_ACL_EDIT_MODE_REPLACE, handle, &input);
166		AutoCredentials cred(alloc);
167		cred += TypedList(alloc, CSSM_SAMPLE_TYPE_PROMPTED_PASSWORD,
168			new(alloc) ListElement(*oldPassphrase));
169		safer_cast<TokenDatabase &>(database).token().tokend().changeAcl(dbAcl, noDb, cred, edit);
170		return SecurityAgent::noReason;
171	} catch (const CssmError &err) {
172		switch (err.error) {
173		default:
174			return SecurityAgent::unknownReason;
175		}
176	} catch (...) {
177		return SecurityAgent::unknownReason;
178	}
179}
180
181void TokenAcl::pinChange(unsigned int pin, CSSM_ACL_HANDLE handle, TokenDatabase &database)
182{
183	QueryNewPin query(pin, handle, database, SecurityAgent::changePassphrase);
184	query.inferHints(Server::process());
185	CssmAutoData newPin(Allocator::standard(Allocator::sensitive));
186    CssmAutoData oldPin(Allocator::standard(Allocator::sensitive));
187	switch (query(oldPin, newPin)) {
188	case SecurityAgent::noReason:		// worked
189		return;
190	default:
191		CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
192	}
193}
194