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