1/*
2 * Copyright (c) 2002-2004 Apple Computer, 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// ACL.cpp
26//
27#include <security_keychain/ACL.h>
28#include <security_keychain/SecCFTypes.h>
29#include <security_utilities/osxcode.h>
30#include <security_utilities/trackingallocator.h>
31#include <security_cdsa_utilities/walkers.h>
32#include <security_keychain/TrustedApplication.h>
33#include <Security/SecTrustedApplication.h>
34#include <security_utilities/devrandom.h>
35#include <security_cdsa_utilities/uniformrandom.h>
36#include <memory>
37
38
39using namespace KeychainCore;
40using namespace DataWalkers;
41
42
43//
44// The default form of a prompt selector
45//
46const CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR ACL::defaultSelector = {
47	CSSM_ACL_KEYCHAIN_PROMPT_CURRENT_VERSION, 0
48};
49
50
51//
52// ACL static constants
53//
54const CSSM_ACL_HANDLE ACL::ownerHandle;
55
56
57//
58// Create an ACL object from the result of a CSSM ACL query
59//
60ACL::ACL(Access &acc, const AclEntryInfo &info, Allocator &alloc)
61	: allocator(alloc), access(acc), mState(unchanged), mSubjectForm(NULL), mMutex(Mutex::recursive)
62{
63	// parse the subject
64	parse(info.proto().subject());
65
66	// fill in AclEntryInfo layer information
67	const AclEntryPrototype &proto = info.proto();
68	mAuthorizations = proto.authorization();
69	mDelegate = proto.delegate();
70	mEntryTag = proto.s_tag();
71
72	// take CSSM entry handle from info layer
73	mCssmHandle = info.handle();
74}
75
76ACL::ACL(Access &acc, const AclOwnerPrototype &owner, Allocator &alloc)
77	: allocator(alloc), access(acc), mState(unchanged), mSubjectForm(NULL), mMutex(Mutex::recursive)
78{
79	// parse subject
80	parse(owner.subject());
81
82	// for an owner "entry", the next-layer information is fixed (and fake)
83	mAuthorizations.insert(CSSM_ACL_AUTHORIZATION_CHANGE_ACL);
84	mDelegate = owner.delegate();
85	mEntryTag[0] = '\0';
86
87	// use fixed (fake) entry handle
88	mCssmHandle = ownerHandle;
89}
90
91
92//
93// Create a new ACL that authorizes anyone to do anything.
94// This constructor produces a "pure" ANY ACL, without descriptor or selector.
95// To generate a "standard" form of ANY, use the appListForm constructor below,
96// then change its form to allowAnyForm.
97//
98ACL::ACL(Access &acc, Allocator &alloc)
99	: allocator(alloc), access(acc), mSubjectForm(NULL), mMutex(Mutex::recursive)
100{
101	mState = inserted;		// new
102	mForm = allowAllForm;	// everybody
103	mAuthorizations.insert(CSSM_ACL_AUTHORIZATION_ANY);	// anything
104	mDelegate = false;
105
106	//mPromptDescription stays empty
107	mPromptSelector = defaultSelector;
108
109	// randomize the CSSM handle
110	UniformRandomBlobs<DevRandomGenerator>().random(mCssmHandle);
111}
112
113
114//
115// Create a new ACL in standard form.
116// As created, it authorizes all activities.
117//
118ACL::ACL(Access &acc, string description, const CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR &promptSelector,
119		Allocator &alloc)
120	: allocator(alloc), access(acc), mSubjectForm(NULL), mMutex(Mutex::recursive)
121{
122	mState = inserted;		// new
123	mForm = appListForm;
124	mAuthorizations.insert(CSSM_ACL_AUTHORIZATION_ANY);	// anything
125	mDelegate = false;
126
127	mPromptDescription = description;
128	mPromptSelector = promptSelector;
129
130	// randomize the CSSM handle
131	UniformRandomBlobs<DevRandomGenerator>().random(mCssmHandle);
132}
133
134
135//
136// Destroy an ACL
137//
138ACL::~ACL()
139{
140	// release subject form (if any)
141	chunkFree(mSubjectForm, allocator);
142}
143
144
145//
146// Does this ACL authorize a particular right?
147//
148bool ACL::authorizes(AclAuthorization right)
149{
150	StLock<Mutex>_(mMutex);
151	return mAuthorizations.find(right) != mAuthorizations.end()
152		|| mAuthorizations.find(CSSM_ACL_AUTHORIZATION_ANY) != mAuthorizations.end()
153		|| mAuthorizations.empty();
154}
155
156
157//
158// Add an application to the trusted-app list of this ACL.
159// Will fail unless this is a standard "simple" form ACL.
160//
161void ACL::addApplication(TrustedApplication *app)
162{
163	StLock<Mutex>_(mMutex);
164	switch (mForm) {
165	case appListForm:	// simple...
166		mAppList.push_back(app);
167		modify();
168		break;
169	case allowAllForm:	// hmm...
170		if (!mPromptDescription.empty()) {
171			// verbose "any" form (has description, "any" override)
172			mAppList.push_back(app);
173			modify();
174			break;
175		}
176		// pure "any" form without description. Cannot convert to appListForm
177	default:
178		MacOSError::throwMe(errSecACLNotSimple);
179	}
180}
181
182
183//
184// Mark an ACL as modified.
185//
186void ACL::modify()
187{
188	StLock<Mutex>_(mMutex);
189	if (mState == unchanged) {
190		secdebug("SecAccess", "ACL %p marked modified", this);
191		mState = modified;
192	}
193}
194
195
196//
197// Mark an ACL as "removed"
198// Removed ACLs have no valid contents (they are invalid on their face).
199// When "updated" to the originating item, they will cause the corresponding
200// ACL entry to be deleted. Otherwise, they are irrelevant.
201// Note: Removing an ACL does not actually remove it from its Access's map.
202//
203void ACL::remove()
204{
205	StLock<Mutex>_(mMutex);
206	mAppList.clear();
207	mForm = invalidForm;
208	mState = deleted;
209}
210
211
212//
213// Produce CSSM-layer form (ACL prototype) copies of our content.
214// Note that the result is chunk-allocated, and becomes owned by the caller.
215//
216void ACL::copyAclEntry(AclEntryPrototype &proto, Allocator &alloc)
217{
218	StLock<Mutex>_(mMutex);
219	proto.clearPod();	// preset
220
221	// carefully copy the subject
222	makeSubject();
223	assert(mSubjectForm);
224	proto = AclEntryPrototype(*mSubjectForm, mDelegate);	// shares subject
225	ChunkCopyWalker w(alloc);
226	walk(w, proto.subject());	// copy subject in-place
227
228	// the rest of a prototype
229	proto.tag(mEntryTag);
230	AuthorizationGroup tags(mAuthorizations, allocator);
231	proto.authorization() = tags;
232}
233
234void ACL::copyAclOwner(AclOwnerPrototype &proto, Allocator &alloc)
235{
236	StLock<Mutex>_(mMutex);
237	proto.clearPod();
238
239	makeSubject();
240	assert(mSubjectForm);
241	proto = AclOwnerPrototype(*mSubjectForm, mDelegate);	// shares subject
242	ChunkCopyWalker w(alloc);
243	walk(w, proto.subject());	// copy subject in-place
244}
245
246
247//
248// (Re)place this ACL's setting into the AclBearer specified.
249// If update, assume this is an update operation and the ACL was
250// originally derived from this object; specifically, assume the
251// CSSM handle is valid. If not update, assume this is a different
252// object that has no related ACL entry (yet).
253//
254void ACL::setAccess(AclBearer &target, bool update,
255	const AccessCredentials *cred)
256{
257	StLock<Mutex>_(mMutex);
258	// determine what action we need to perform
259	State action = state();
260	if (!update)
261		action = (action == deleted) ? unchanged : inserted;
262
263	// the owner acl (pseudo) "entry" is a special case
264	if (isOwner()) {
265		switch (action) {
266		case unchanged:
267			secdebug("SecAccess", "ACL %p owner unchanged", this);
268			return;
269		case inserted:		// means modify the initial owner
270		case modified:
271			{
272				secdebug("SecAccess", "ACL %p owner modified", this);
273				makeSubject();
274				assert(mSubjectForm);
275				AclOwnerPrototype proto(*mSubjectForm, mDelegate);
276				target.changeOwner(proto, cred);
277				return;
278			}
279		default:
280			assert(false);
281			return;
282		}
283	}
284
285	// simple cases
286	switch (action) {
287	case unchanged:	// ignore
288		secdebug("SecAccess", "ACL %p handle 0x%lx unchanged", this, entryHandle());
289		return;
290	case deleted:	// delete
291		secdebug("SecAccess", "ACL %p handle 0x%lx deleted", this, entryHandle());
292		target.deleteAcl(entryHandle(), cred);
293		return;
294	default:
295		break;
296	}
297
298	// build the byzantine data structures that CSSM loves so much
299	makeSubject();
300	assert(mSubjectForm);
301	AclEntryPrototype proto(*mSubjectForm, mDelegate);
302	proto.tag(mEntryTag);
303	AutoAuthorizationGroup tags(mAuthorizations, allocator);
304	proto.authorization() = tags;
305	AclEntryInput input(proto);
306	switch (action) {
307	case inserted:	// insert
308		secdebug("SecAccess", "ACL %p inserted", this);
309		target.addAcl(input, cred);
310		break;
311	case modified:	// update
312		secdebug("SecAccess", "ACL %p handle 0x%lx modified", this, entryHandle());
313		target.changeAcl(entryHandle(), input, cred);
314		break;
315	default:
316		assert(false);
317	}
318}
319
320
321//
322// Parse an AclEntryPrototype (presumably from a CSSM "Get" ACL operation
323// into internal form.
324//
325void ACL::parse(const TypedList &subject)
326{
327	StLock<Mutex>_(mMutex);
328	try {
329		switch (subject.type()) {
330		case CSSM_ACL_SUBJECT_TYPE_ANY:
331			// subsume an "any" as a standard form
332			mForm = allowAllForm;
333			return;
334		case CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT:
335			// pure keychain prompt - interpret as applist form with no apps
336			parsePrompt(subject);
337			mForm = appListForm;
338			return;
339		case CSSM_ACL_SUBJECT_TYPE_THRESHOLD:
340			{
341				// app-list format: THRESHOLD(1, n): sign(1), ..., sign(n), PROMPT
342				if (subject[1] != 1)
343					throw ParseError();
344				uint32 count = subject[2];
345
346				// parse final (PROMPT) element
347				TypedList &end = subject[count + 2];	// last choice
348				if (end.type() != CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT)
349					throw ParseError();	// not PROMPT at end
350				parsePrompt(end);
351
352				// check for leading ANY
353				TypedList &first = subject[3];
354				if (first.type() == CSSM_ACL_SUBJECT_TYPE_ANY) {
355					mForm = allowAllForm;
356					return;
357				}
358
359				// parse other (code signing) elements
360				for (uint32 n = 0; n < count - 1; n++)
361					mAppList.push_back(new TrustedApplication(TypedList(subject[n + 3].list())));
362			}
363			mForm = appListForm;
364			return;
365		default:
366			mForm = customForm;
367			mSubjectForm = chunkCopy(&subject);
368			return;
369		}
370	} catch (const ParseError &) {
371		secdebug("SecAccess", "acl compile failed; marking custom");
372		mForm = customForm;
373		mSubjectForm = chunkCopy(&subject);
374		mAppList.clear();
375	}
376}
377
378void ACL::parsePrompt(const TypedList &subject)
379{
380	StLock<Mutex>_(mMutex);
381	assert(subject.length() == 3);
382	mPromptSelector =
383		*subject[1].data().interpretedAs<CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR>(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
384	mPromptDescription = subject[2].toString();
385}
386
387
388//
389// Take this ACL and produce its meaning as a CSSM ACL subject in mSubjectForm
390//
391void ACL::makeSubject()
392{
393	StLock<Mutex>_(mMutex);
394	switch (form()) {
395	case allowAllForm:
396		chunkFree(mSubjectForm, allocator);	// release previous
397		if (mPromptDescription.empty()) {
398			// no description -> pure ANY
399			mSubjectForm = new(allocator) TypedList(allocator, CSSM_ACL_SUBJECT_TYPE_ANY);
400		} else {
401			// have description -> threshold(1 of 2) of { ANY, PROMPT }
402			mSubjectForm = new(allocator) TypedList(allocator, CSSM_ACL_SUBJECT_TYPE_THRESHOLD,
403				new(allocator) ListElement(1),
404				new(allocator) ListElement(2));
405			*mSubjectForm += new(allocator) ListElement(TypedList(allocator, CSSM_ACL_SUBJECT_TYPE_ANY));
406			TypedList prompt(allocator, CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT,
407				new(allocator) ListElement(allocator, CssmData::wrap(mPromptSelector)),
408				new(allocator) ListElement(allocator, mPromptDescription));
409			*mSubjectForm += new(allocator) ListElement(prompt);
410		}
411		return;
412	case appListForm: {
413		// threshold(1 of n+1) of { app1, ..., appn, PROMPT }
414		chunkFree(mSubjectForm, allocator);	// release previous
415		uint32 appCount = (uint32)mAppList.size();
416		mSubjectForm = new(allocator) TypedList(allocator, CSSM_ACL_SUBJECT_TYPE_THRESHOLD,
417			new(allocator) ListElement(1),
418			new(allocator) ListElement(appCount + 1));
419		for (uint32 n = 0; n < appCount; n++)
420			*mSubjectForm +=
421				new(allocator) ListElement(mAppList[n]->makeSubject(allocator));
422		TypedList prompt(allocator, CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT,
423			new(allocator) ListElement(allocator, CssmData::wrap(mPromptSelector)),
424			new(allocator) ListElement(allocator, mPromptDescription));
425		*mSubjectForm += new(allocator) ListElement(prompt);
426		}
427		return;
428	case customForm:
429		assert(mSubjectForm);	// already set; keep it
430		return;
431	default:
432		assert(false);	// unexpected
433	}
434}
435