ACL.cpp
  1  /*
  2   * Copyright (c) 2002-2004,2011-2012,2014 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  // 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/SecRandom.h>
 35  #include <memory>
 36  
 37  
 38  using namespace KeychainCore;
 39  using namespace DataWalkers;
 40  
 41  
 42  //
 43  // The default form of a prompt selector
 44  //
 45  const CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR ACL::defaultSelector = {
 46  	CSSM_ACL_KEYCHAIN_PROMPT_CURRENT_VERSION, 0
 47  };
 48  
 49  
 50  //
 51  // ACL static constants
 52  //
 53  const CSSM_ACL_HANDLE ACL::ownerHandle;
 54  
 55  
 56  //
 57  // Create an ACL object from the result of a CSSM ACL query
 58  //
 59  ACL::ACL(const AclEntryInfo &info, Allocator &alloc)
 60  	: allocator(alloc), mState(unchanged), mSubjectForm(NULL), mIntegrity(alloc), mMutex(Mutex::recursive)
 61  {
 62  	// parse the subject
 63  	parse(info.proto().subject());
 64  
 65  	// fill in AclEntryInfo layer information
 66  	const AclEntryPrototype &proto = info.proto();
 67  	mAuthorizations = proto.authorization();
 68  	mDelegate = proto.delegate();
 69  	mEntryTag = proto.s_tag();
 70  
 71  	// take CSSM entry handle from info layer
 72  	mCssmHandle = info.handle();
 73  }
 74  
 75  
 76  ACL::ACL(const AclOwnerPrototype &owner, Allocator &alloc)
 77  	: allocator(alloc), mState(unchanged), mSubjectForm(NULL), mIntegrity(alloc), 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  //
 98  ACL::ACL(Allocator &alloc)
 99  	: allocator(alloc), mSubjectForm(NULL), mIntegrity(alloc), 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      MacOSError::check(SecRandomCopyBytes(kSecRandomDefault, sizeof(mCssmHandle), (void *)mCssmHandle));
111  }
112  
113  
114  //
115  // Create a new ACL in standard form.
116  // As created, it authorizes all activities.
117  //
118  ACL::ACL(string description, const CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR &promptSelector,
119  		Allocator &alloc)
120  	: allocator(alloc), mSubjectForm(NULL), mIntegrity(alloc), 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      MacOSError::check(SecRandomCopyBytes(kSecRandomDefault, sizeof(mCssmHandle), &mCssmHandle));
132  }
133  
134  
135  //
136  // Create an "integrity" ACL
137  //
138  ACL::ACL(const CssmData &digest, Allocator &alloc)
139  : allocator(alloc), mSubjectForm(NULL), mIntegrity(alloc, digest), mMutex(Mutex::recursive)
140  {
141      mState = inserted;		// new
142      mForm = integrityForm;
143      mAuthorizations.insert(CSSM_ACL_AUTHORIZATION_INTEGRITY);
144      mEntryTag = CSSM_APPLE_ACL_TAG_INTEGRITY;
145      mDelegate = false;
146  
147      //mPromptDescription stays empty
148      //mPromptSelector stays empty
149  
150      // randomize the CSSM handle
151      MacOSError::check(SecRandomCopyBytes(kSecRandomDefault, sizeof(mCssmHandle), &mCssmHandle));
152  }
153  
154  
155  //
156  // Destroy an ACL
157  //
158  ACL::~ACL() 
159  {
160  	// release subject form (if any)
161  	chunkFree(mSubjectForm, allocator);
162  }
163  
164  
165  //
166  // Does this ACL authorize a particular right?
167  //
168  bool ACL::authorizes(AclAuthorization right)
169  {
170  	StLock<Mutex>_(mMutex);
171  	return mAuthorizations.find(right) != mAuthorizations.end()
172  		|| mAuthorizations.find(CSSM_ACL_AUTHORIZATION_ANY) != mAuthorizations.end()
173  		|| mAuthorizations.empty();
174  }
175  
176  //
177  // Does this ACL have a specific authorization for a particular right?
178  //
179  bool ACL::authorizesSpecifically(AclAuthorization right)
180  {
181      StLock<Mutex>_(mMutex);
182      return mAuthorizations.find(right) != mAuthorizations.end();
183  }
184  
185  void ACL::setIntegrity(const CssmData& digest) {
186      if(mForm != integrityForm) {
187          secnotice("integrity", "acl has incorrect form: %d", mForm);
188          CssmError::throwMe(CSSMERR_CSP_INVALID_ACL_SUBJECT_VALUE);
189      }
190  
191      mIntegrity = digest;
192      modify();
193  }
194  
195  const CssmData& ACL::integrity() {
196      return mIntegrity.get();
197  }
198  
199  //
200  // Add an application to the trusted-app list of this ACL.
201  // Will fail unless this is a standard "simple" form ACL.
202  //
203  void ACL::addApplication(TrustedApplication *app)
204  {
205  	StLock<Mutex>_(mMutex);
206  	switch (mForm) {
207  	case appListForm:	// simple...
208  		mAppList.push_back(app);
209  		modify();
210  		break;
211  	case allowAllForm:	// hmm...
212  		if (!mPromptDescription.empty()) {
213  			// verbose "any" form (has description, "any" override)
214  			mAppList.push_back(app);
215  			modify();
216  			break;
217  		}
218  		// pure "any" form without description. Cannot convert to appListForm	
219  	default:
220  		MacOSError::throwMe(errSecACLNotSimple);
221  	}
222  }
223  
224  
225  //
226  // Mark an ACL as modified.
227  //
228  void ACL::modify()
229  {
230  	StLock<Mutex>_(mMutex);
231  	if (mState == unchanged) {
232  		secinfo("SecAccess", "ACL %p marked modified", this);
233  		mState = modified;
234  	}
235  }
236  
237  
238  //
239  // Mark an ACL as "removed"
240  // Removed ACLs have no valid contents (they are invalid on their face).
241  // When "updated" to the originating item, they will cause the corresponding
242  // ACL entry to be deleted. Otherwise, they are irrelevant.
243  // Note: Removing an ACL does not actually remove it from its Access's map.
244  //
245  void ACL::remove()
246  {
247  	StLock<Mutex>_(mMutex);
248  	mAppList.clear();
249  	mForm = invalidForm;
250      secinfo("SecAccess", "ACL %p marked deleted", this);
251  	mState = deleted;
252  }
253  
254  
255  //
256  // Produce CSSM-layer form (ACL prototype) copies of our content.
257  // Note that the result is chunk-allocated, and becomes owned by the caller.
258  //
259  void ACL::copyAclEntry(AclEntryPrototype &proto, Allocator &alloc)
260  {
261  	StLock<Mutex>_(mMutex);
262  	proto.clearPod();	// preset
263  	
264  	// carefully copy the subject
265  	makeSubject();
266  	assert(mSubjectForm);
267  	proto = AclEntryPrototype(*mSubjectForm, mDelegate);	// shares subject
268  	ChunkCopyWalker w(alloc);
269  	walk(w, proto.subject());	// copy subject in-place
270  	
271  	// the rest of a prototype
272  	proto.tag(mEntryTag);
273  	AuthorizationGroup tags(mAuthorizations, allocator);
274  	proto.authorization() = tags;
275  }
276  
277  void ACL::copyAclOwner(AclOwnerPrototype &proto, Allocator &alloc)
278  {
279  	StLock<Mutex>_(mMutex);
280  	proto.clearPod();
281  	
282  	makeSubject();
283  	assert(mSubjectForm);
284  	proto = AclOwnerPrototype(*mSubjectForm, mDelegate);	// shares subject
285  	ChunkCopyWalker w(alloc);
286  	walk(w, proto.subject());	// copy subject in-place
287  }
288  
289  
290  //
291  // (Re)place this ACL's setting into the AclBearer specified.
292  // If update, assume this is an update operation and the ACL was
293  // originally derived from this object; specifically, assume the
294  // CSSM handle is valid. If not update, assume this is a different
295  // object that has no related ACL entry (yet).
296  //
297  void ACL::setAccess(AclBearer &target, bool update,
298  	const AccessCredentials *cred)
299  {
300  	StLock<Mutex>_(mMutex);
301  	// determine what action we need to perform
302  	State action = state();
303  	if (!update)
304  		action = (action == deleted) ? unchanged : inserted;
305  	
306  	// the owner acl (pseudo) "entry" is a special case
307  	if (isOwner()) {
308  		switch (action) {
309  		case unchanged:
310  			secinfo("SecAccess", "ACL %p owner unchanged", this);
311  			return;
312  		case inserted:		// means modify the initial owner
313  		case modified:
314  			{
315  				secinfo("SecAccess", "ACL %p owner modified", this);
316  				makeSubject();
317  				assert(mSubjectForm);
318  				AclOwnerPrototype proto(*mSubjectForm, mDelegate);
319  				target.changeOwner(proto, cred);
320  				return;
321  			}
322  		default:
323  			assert(false);
324  			return;
325  		}
326  	}
327  
328  	// simple cases
329  	switch (action) {
330  	case unchanged:	// ignore
331  		secinfo("SecAccess", "ACL %p handle 0x%lx unchanged", this, entryHandle());
332  		return;
333  	case deleted:	// delete
334  		secinfo("SecAccess", "ACL %p handle 0x%lx deleted", this, entryHandle());
335  		target.deleteAcl(entryHandle(), cred);
336  		return;
337  	default:
338  		break;
339  	}
340  	
341  	// build the byzantine data structures that CSSM loves so much
342  	makeSubject();
343  	assert(mSubjectForm);
344  	AclEntryPrototype proto(*mSubjectForm, mDelegate);
345  	proto.tag(mEntryTag);
346  	AutoAuthorizationGroup tags(mAuthorizations, allocator);
347  	proto.authorization() = tags;
348  	AclEntryInput input(proto);
349  	switch (action) {
350  	case inserted:	// insert
351  		secinfo("SecAccess", "ACL %p inserted", this);
352  		target.addAcl(input, cred);
353          mState = unchanged;
354  		break;
355  	case modified:	// update
356  		secinfo("SecAccess", "ACL %p handle 0x%lx modified", this, entryHandle());
357  		target.changeAcl(entryHandle(), input, cred);
358          mState = unchanged;
359  		break;
360  	default:
361  		assert(false);
362  	}
363  }
364  
365  
366  //
367  // Parse an AclEntryPrototype (presumably from a CSSM "Get" ACL operation
368  // into internal form.
369  //
370  void ACL::parse(const TypedList &subject)
371  {
372  	StLock<Mutex>_(mMutex);
373  	try {
374  		switch (subject.type()) {
375  		case CSSM_ACL_SUBJECT_TYPE_ANY:
376  			// subsume an "any" as a standard form
377  			mForm = allowAllForm;
378              secinfo("SecAccess", "parsed an allowAllForm (%d) (%d)", subject.type(), mForm);
379  			return;
380  		case CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT:
381  			// pure keychain prompt - interpret as applist form with no apps
382  			parsePrompt(subject);
383  			mForm = appListForm;
384              secinfo("SecAccess", "parsed a Keychain Prompt (%d) as an appListForm (%d)", subject.type(), mForm);
385  			return;
386  		case CSSM_ACL_SUBJECT_TYPE_THRESHOLD:
387  			{
388  				// app-list format: THRESHOLD(1, n): sign(1), ..., sign(n), PROMPT
389  				if (subject[1] != 1)
390  					throw ParseError();
391  				uint32 count = subject[2];
392  				
393  				// parse final (PROMPT) element
394  				TypedList &end = subject[count + 2];	// last choice
395  				if (end.type() != CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT)
396  					throw ParseError();	// not PROMPT at end
397  				parsePrompt(end);
398  				
399  				// check for leading ANY
400  				TypedList &first = subject[3];
401  				if (first.type() == CSSM_ACL_SUBJECT_TYPE_ANY) {
402  					mForm = allowAllForm;
403                      secinfo("SecAccess", "parsed a Threshhold (%d) as an allowAllForm (%d)", subject.type(), mForm);
404  					return;
405  				}
406  				
407  				// parse other (code signing) elements
408                  for (uint32 n = 0; n < count - 1; n++) {
409                      mAppList.push_back(new TrustedApplication(TypedList(subject[n + 3].list())));
410                      secinfo("SecAccess", "found an application: %s", mAppList.back()->path());
411                  }
412  			}
413  			mForm = appListForm;
414              secinfo("SecAccess", "parsed a Threshhold (%d) as an appListForm (%d)", subject.type(), mForm);
415  			return;
416          case CSSM_ACL_SUBJECT_TYPE_PARTITION:
417              mForm = integrityForm;
418              mIntegrity.copy(subject.last()->data());
419              secinfo("SecAccess", "parsed a Partition (%d) as an integrityForm (%d)", subject.type(), mForm);
420              return;
421          default:
422              secinfo("SecAccess", "didn't find a type for %d, marking custom (%d)", subject.type(), mForm);
423  			mForm = customForm;
424  			mSubjectForm = chunkCopy(&subject);
425  			return;
426  		}
427  	} catch (const ParseError &) {
428  		secinfo("SecAccess", "acl compile failed for type (%d); marking custom", subject.type());
429  		mForm = customForm;
430  		mSubjectForm = chunkCopy(&subject);
431  		mAppList.clear();
432  	}
433  }
434  
435  void ACL::parsePrompt(const TypedList &subject)
436  {
437  	StLock<Mutex>_(mMutex);
438  	assert(subject.length() == 3);
439  	mPromptSelector =
440  		*subject[1].data().interpretedAs<CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR>(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
441  	mPromptDescription = subject[2].toString();
442  }
443  
444  
445  //
446  // Take this ACL and produce its meaning as a CSSM ACL subject in mSubjectForm
447  //
448  void ACL::makeSubject()
449  {
450  	StLock<Mutex>_(mMutex);
451  	switch (form()) {
452  	case allowAllForm:
453  		chunkFree(mSubjectForm, allocator);	// release previous
454  		if (mPromptDescription.empty()) {
455  			// no description -> pure ANY
456  			mSubjectForm = new(allocator) TypedList(allocator, CSSM_ACL_SUBJECT_TYPE_ANY);
457  		} else {
458  			// have description -> threshold(1 of 2) of { ANY, PROMPT }
459  			mSubjectForm = new(allocator) TypedList(allocator, CSSM_ACL_SUBJECT_TYPE_THRESHOLD,
460  				new(allocator) ListElement(1),
461  				new(allocator) ListElement(2));
462  			*mSubjectForm += new(allocator) ListElement(TypedList(allocator, CSSM_ACL_SUBJECT_TYPE_ANY));
463  			TypedList prompt(allocator, CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT,
464  				new(allocator) ListElement(allocator, CssmData::wrap(mPromptSelector)),
465  				new(allocator) ListElement(allocator, mPromptDescription));
466  			*mSubjectForm += new(allocator) ListElement(prompt);
467  		}
468          secinfo("SecAccess", "made an allowAllForm (%d) into a subjectForm (%d)", mForm, mSubjectForm->type());
469  		return;
470  	case appListForm: {
471  		// threshold(1 of n+1) of { app1, ..., appn, PROMPT }
472  		chunkFree(mSubjectForm, allocator);	// release previous
473  		uint32 appCount = (uint32)mAppList.size();
474  		mSubjectForm = new(allocator) TypedList(allocator, CSSM_ACL_SUBJECT_TYPE_THRESHOLD,
475  			new(allocator) ListElement(1),
476  			new(allocator) ListElement(appCount + 1));
477  		for (uint32 n = 0; n < appCount; n++)
478  			*mSubjectForm +=
479  				new(allocator) ListElement(mAppList[n]->makeSubject(allocator));
480  		TypedList prompt(allocator, CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT,
481  			new(allocator) ListElement(allocator, CssmData::wrap(mPromptSelector)),
482  			new(allocator) ListElement(allocator, mPromptDescription));
483  		*mSubjectForm += new(allocator) ListElement(prompt);
484  		}
485          secinfo("SecAccess", "made an appListForm (%d) into a subjectForm (%d)", mForm, mSubjectForm->type());
486  		return;
487      case integrityForm:
488          chunkFree(mSubjectForm, allocator);
489          mSubjectForm = new(allocator) TypedList(allocator, CSSM_ACL_SUBJECT_TYPE_PARTITION,
490                                                   new(allocator) ListElement(allocator, mIntegrity));
491          secinfo("SecAccess", "made an integrityForm (%d) into a subjectForm (%d)", mForm, mSubjectForm->type());
492          return;
493  	case customForm:
494  		assert(mSubjectForm);	// already set; keep it
495          secinfo("SecAccess", "have a customForm (%d), already have a subjectForm (%d)", mForm, mSubjectForm->type());
496  		return;
497  
498  	default:
499  		assert(false);	// unexpected
500  	}
501  }