]>
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 | // | |
60 | ACL::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 | ||
76 | ACL::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 | // | |
98 | ACL::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 | // | |
118 | ACL::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 | // | |
138 | ACL::~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 | // | |
148 | bool 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 | // | |
161 | void 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 | // | |
186 | void 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 | // | |
203 | void 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 | // | |
216 | void 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 | ||
234 | void 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 | // | |
254 | void 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 | // | |
325 | void 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 | ||
378 | void 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 | // | |
391 | void 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 | |
427c49bc | 415 | uint32 appCount = (uint32)mAppList.size(); |
b1ab9ed8 A |
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 | } |