1/*
2 * Copyright (c) 2000-2004,2006 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//
26// objectacl - core implementation of an ACL-bearing object
27//
28#include <security_cdsa_utilities/objectacl.h>
29#include <security_cdsa_utilities/cssmbridge.h>
30#include <security_utilities/endian.h>
31#include <security_utilities/debugging.h>
32#include <algorithm>
33#include <cstdarg>
34
35#include <security_cdsa_utilities/acl_preauth.h>	//@@@ impure - will be removed
36
37using namespace DataWalkers;
38
39
40//
41// The static map of available ACL subject makers.
42// These are the kinds of ACL subjects we can deal with.
43//
44ModuleNexus<ObjectAcl::MakerMap> ObjectAcl::makers;
45
46
47//
48// Create an ObjectAcl
49//
50ObjectAcl::ObjectAcl(Allocator &alloc) : allocator(alloc), mNextHandle(1)
51{
52}
53
54ObjectAcl::ObjectAcl(const AclEntryPrototype &proto, Allocator &alloc)
55    : allocator(alloc), mNextHandle(1)
56{
57    cssmSetInitial(proto);
58}
59
60ObjectAcl::~ObjectAcl()
61{ }
62
63
64//
65// Set an "initial ACL" from a CSSM-style initial ACL argument.
66// This will replace the owner, as well as replace the entire ACL
67// with a single-item slot, as per CSSM specification.
68//
69void ObjectAcl::cssmSetInitial(const AclEntryPrototype &proto)
70{
71    mOwner = OwnerEntry(proto);
72	add(proto.s_tag(), proto);
73	IFDUMPING("acl", debugDump("create/proto"));
74}
75
76void ObjectAcl::cssmSetInitial(const AclSubjectPointer &subject)
77{
78    mOwner = OwnerEntry(subject);
79	add("", subject);
80	IFDUMPING("acl", debugDump("create/subject"));
81}
82
83ObjectAcl::Entry::~Entry()
84{
85}
86
87
88//
89// ObjectAcl::validate validates an access authorization claim.
90// Returns normally if 'auth' is granted to the bearer of 'cred'.
91// Otherwise, throws a suitable (ACL-related) CssmError exception.
92//
93class BaseValidationContext : public AclValidationContext {
94public:
95	BaseValidationContext(const AccessCredentials *cred,
96		AclAuthorization auth, AclValidationEnvironment *env)
97		: AclValidationContext(cred, auth, env) { }
98
99	uint32 count() const	{ return cred() ? cred()->samples().length() : 0; }
100	uint32 size() const		{ return count(); }
101	const TypedList &sample(uint32 n) const
102	{ assert(n < count()); return cred()->samples()[n]; }
103
104	void matched(const TypedList *) const { }		// ignore match info
105};
106
107
108bool ObjectAcl::validates(AclAuthorization auth, const AccessCredentials *cred,
109    AclValidationEnvironment *env)
110{
111	BaseValidationContext ctx(cred, auth, env);
112	return validates(ctx);
113}
114
115bool ObjectAcl::validates(AclValidationContext &ctx)
116{
117	// make sure we are ready to go
118	instantiateAcl();
119
120	IFDUMPING("acleval", Debug::dump("<<WANT(%d)<", ctx.authorization()));
121
122    //@@@ should pre-screen based on requested auth, maybe?
123
124#if defined(ACL_OMNIPOTENT_OWNER)
125    // try owner (owner can do anything)
126    if (mOwner.validate(ctx))
127        return;
128#endif //ACL_OMNIPOTENT_OWNER
129
130    // try applicable ACLs
131    pair<EntryMap::const_iterator, EntryMap::const_iterator> range;
132    if (getRange(ctx.s_credTag(), range) == 0)	// no such tag
133        CssmError::throwMe(CSSM_ERRCODE_ACL_ENTRY_TAG_NOT_FOUND);
134    // try each entry in turn
135    for (EntryMap::const_iterator it = range.first; it != range.second; it++) {
136        const AclEntry &slot = it->second;
137		IFDUMPING("acleval", (Debug::dump(" EVAL["), slot.debugDump(), Debug::dump("]")));
138        if (slot.authorizes(ctx.authorization())) {
139			ctx.init(this, slot.subject);
140			ctx.entryTag(slot.tag);
141			if (slot.validate(ctx)) {
142				IFDUMPING("acleval", Debug::dump(">PASS>>\n"));
143				return true;		// passed
144			}
145			IFDUMPING("acleval", Debug::dump(" NO"));
146		}
147    }
148	IFDUMPING("acleval", Debug::dump(">FAIL>>\n"));
149	return false;	// no joy
150}
151
152void ObjectAcl::validate(AclAuthorization auth, const AccessCredentials *cred,
153	AclValidationEnvironment *env)
154{
155	if (!validates(auth, cred, env))
156		CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED);
157}
158
159void ObjectAcl::validate(AclValidationContext &ctx)
160{
161	if (!validates(ctx))
162		CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED);
163}
164
165
166void ObjectAcl::validateOwner(AclAuthorization authorizationHint,
167	const AccessCredentials *cred, AclValidationEnvironment *env)
168{
169    BaseValidationContext ctx(cred, authorizationHint, env);
170	validateOwner(ctx);
171}
172
173void ObjectAcl::validateOwner(AclValidationContext &ctx)
174{
175    instantiateAcl();
176
177    ctx.init(this, mOwner.subject);
178    if (mOwner.validate(ctx))
179        return;
180    CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED);
181}
182
183
184//
185// Export an ObjectAcl to two memory blobs: public and private data separated.
186// This is a standard two-pass size+copy operation.
187//
188void ObjectAcl::exportBlob(CssmData &publicBlob, CssmData &privateBlob)
189{
190	instantiateAcl();
191    Writer::Counter pubSize, privSize;
192	Endian<uint32> entryCount = (uint32)mEntries.size();
193    mOwner.exportBlob(pubSize, privSize);
194	pubSize(entryCount);
195    for (EntryMap::iterator it = begin(); it != end(); it++)
196        it->second.exportBlob(pubSize, privSize);
197    publicBlob = CssmData(allocator.malloc(pubSize), pubSize);
198    privateBlob = CssmData(allocator.malloc(privSize), privSize);
199    Writer pubWriter(publicBlob), privWriter(privateBlob);
200    mOwner.exportBlob(pubWriter, privWriter);
201	pubWriter(entryCount);
202    for (EntryMap::iterator it = begin(); it != end(); it++)
203        it->second.exportBlob(pubWriter, privWriter);
204	IFDUMPING("acl", debugDump("exported"));
205}
206
207
208//
209// Import an ObjectAcl's contents from two memory blobs representing public and
210// private contents, respectively. These blobs must have been generated by the
211// export method.
212// Prior contents (if any) are deleted and replaced.
213//
214void ObjectAcl::importBlob(const void *publicBlob, const void *privateBlob)
215{
216    Reader pubReader(publicBlob), privReader(privateBlob);
217    mOwner.importBlob(pubReader, privReader);
218	Endian<uint32> entryCountIn; pubReader(entryCountIn);
219	uint32 entryCount = entryCountIn;
220
221	mEntries.erase(begin(), end());
222	for (uint32 n = 0; n < entryCount; n++) {
223		AclEntry newEntry;
224		newEntry.importBlob(pubReader, privReader);
225		add(newEntry.tag, newEntry);
226	}
227	IFDUMPING("acl", debugDump("imported"));
228}
229
230
231//
232// Import/export helpers for subjects.
233// This is exported to (subject implementation) callers to maintain consistency
234// in binary format handling.
235//
236AclSubject *ObjectAcl::importSubject(Reader &pub, Reader &priv)
237{
238    Endian<uint32> typeAndVersion; pub(typeAndVersion);
239	return make(typeAndVersion, pub, priv);
240}
241
242
243//
244// Setup/update hooks
245//
246void ObjectAcl::instantiateAcl()
247{
248	// nothing by default
249}
250
251void ObjectAcl::changedAcl()
252{
253	// nothing by default
254}
255
256
257//
258// ACL utility methods
259//
260unsigned int ObjectAcl::getRange(const std::string &tag,
261	pair<EntryMap::const_iterator, EntryMap::const_iterator> &range) const
262{
263    if (!tag.empty()) {	// tag restriction in effect
264        range = mEntries.equal_range(tag);
265        unsigned int count = (unsigned int)mEntries.count(tag);
266        if (count == 0)
267            CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_ENTRY_TAG);
268        return count;
269    } else {				// try all tags
270        range.first = mEntries.begin();
271        range.second = mEntries.end();
272        return (unsigned int)mEntries.size();
273    }
274}
275
276ObjectAcl::EntryMap::iterator ObjectAcl::findEntryHandle(CSSM_ACL_HANDLE handle)
277{
278    for (EntryMap::iterator it = mEntries.begin(); it != mEntries.end(); it++)
279        if (it->second.handle == handle)
280            return it;
281    CssmError::throwMe(CSSMERR_CSSM_INVALID_HANDLE_USAGE);	//%%% imprecise error code
282}
283
284
285//
286// CSSM style ACL access and modification functions.
287//
288void ObjectAcl::cssmGetAcl(const char *tag, uint32 &count, AclEntryInfo * &acls)
289{
290	instantiateAcl();
291    pair<EntryMap::const_iterator, EntryMap::const_iterator> range;
292    count = getRange(tag ? tag : "", range);
293    acls = allocator.alloc<AclEntryInfo>(count);
294    uint32 n = 0;
295    for (EntryMap::const_iterator it = range.first; it != range.second; it++, n++) {
296        acls[n].EntryHandle = it->second.handle;
297        it->second.toEntryInfo(acls[n].EntryPublicInfo, allocator);
298    }
299    count = n;
300}
301
302void ObjectAcl::cssmChangeAcl(const AclEdit &edit,
303	const AccessCredentials *cred, AclValidationEnvironment *env)
304{
305	IFDUMPING("acl", debugDump("acl-change-from"));
306
307	// make sure we're ready to go
308	instantiateAcl();
309
310    // validate access credentials
311    validateOwner(CSSM_ACL_AUTHORIZATION_CHANGE_ACL, cred, env);
312
313    // what is Thy wish, effendi?
314    switch (edit.EditMode) {
315    case CSSM_ACL_EDIT_MODE_ADD: {
316		const AclEntryInput &input = Required(edit.newEntry());
317		add(input.proto().s_tag(), input.proto());
318		}
319        break;
320    case CSSM_ACL_EDIT_MODE_REPLACE: {
321		// keep the handle, and try for some modicum of atomicity
322        EntryMap::iterator it = findEntryHandle(edit.handle());
323		AclEntryPrototype proto = Required(edit.newEntry()).proto(); // (bypassing callbacks)
324		add(proto.s_tag(), proto, edit.handle());
325		mEntries.erase(it);
326        }
327        break;
328    case CSSM_ACL_EDIT_MODE_DELETE:
329        mEntries.erase(findEntryHandle(edit.OldEntryHandle));
330        break;
331    default:
332        CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_EDIT_MODE);
333    }
334
335	// notify change
336	changedAcl();
337
338	IFDUMPING("acl", debugDump("acl-change-to"));
339}
340
341void ObjectAcl::cssmGetOwner(AclOwnerPrototype &outOwner)
342{
343	instantiateAcl();
344    outOwner.TypedSubject = mOwner.subject->toList(allocator);
345    outOwner.Delegate = mOwner.delegate;
346}
347
348void ObjectAcl::cssmChangeOwner(const AclOwnerPrototype &newOwner,
349                                const AccessCredentials *cred, AclValidationEnvironment *env)
350{
351	IFDUMPING("acl", debugDump("owner-change-from"));
352
353	instantiateAcl();
354
355    // only the owner entry can match
356    validateOwner(CSSM_ACL_AUTHORIZATION_CHANGE_OWNER, cred, env);
357
358    // okay, replace it
359    mOwner = newOwner;
360
361	changedAcl();
362
363	IFDUMPING("acl", debugDump("owner-change-to"));
364}
365
366
367//
368// Load a set of ACL entries from an AclEntryInfo array.
369// This completely replaces the ACL's entries.
370// Note that we will adopt the handles in the infos, so they better be proper
371// (unique, nonzero).
372//
373template <class Input>
374void ObjectAcl::owner(const Input &input)
375{
376	IFDUMPING("acl", debugDump("owner-load-old"));
377	mOwner = OwnerEntry(input);
378	IFDUMPING("acl", debugDump("owner-load-new"));
379}
380
381template void ObjectAcl::owner(const AclOwnerPrototype &);
382template void ObjectAcl::owner(const AclSubjectPointer &);
383
384
385void ObjectAcl::entries(uint32 count, const AclEntryInfo *info)
386{
387	IFDUMPING("acl", debugDump("entries-load-old"));
388	mEntries.erase(mEntries.begin(), mEntries.end());
389	for (uint32 n = 0; n < count; n++, info++)
390		add(info->proto().s_tag(), info->proto());
391	IFDUMPING("acl", debugDump("entries-load-new"));
392}
393
394
395//
396// Clear out the ACL and return it to un-initialized state
397//
398void ObjectAcl::clear()
399{
400	mOwner = OwnerEntry();
401	mEntries.erase(mEntries.begin(), mEntries.end());
402	secdebug("acl", "%p cleared", this);
403}
404
405
406//
407// Common gate to add an ACL entry
408//
409void ObjectAcl::add(const std::string &tag, const AclEntry &newEntry)
410{
411	add(tag, newEntry, mNextHandle++);
412}
413
414void ObjectAcl::add(const std::string &tag, AclEntry newEntry, CSSM_ACL_HANDLE handle)
415{
416	//@@@ This should use a hook-registry mechanism. But for now, we are explicit:
417	if (!newEntry.authorizesAnything) {
418		for (AclAuthorizationSet::const_iterator it = newEntry.authorizations.begin();
419				it != newEntry.authorizations.end(); it++)
420			if (*it >= CSSM_ACL_AUTHORIZATION_PREAUTH_BASE &&
421					*it < CSSM_ACL_AUTHORIZATION_PREAUTH_END) {
422				// preauthorization right - special handling
423				if (newEntry.subject->type() != CSSM_ACL_SUBJECT_TYPE_PREAUTH_SOURCE)
424					newEntry.subject =
425						new PreAuthorizationAcls::SourceAclSubject(newEntry.subject);
426			}
427	}
428
429	mEntries.insert(make_pair(tag, newEntry))->second.handle = handle;
430	if (handle >= mNextHandle)
431		mNextHandle = handle + 1;	// don't reuse this handle (in this ACL)
432}
433
434
435//
436// Common features of ACL entries/owners
437//
438void ObjectAcl::Entry::init(const AclSubjectPointer &subject, bool delegate)
439{
440    this->subject = subject;
441    this->delegate = delegate;
442}
443
444void ObjectAcl::Entry::importBlob(Reader &pub, Reader &priv)
445{
446	// the delegation flag is 4 bytes for historic reasons
447    Endian<uint32> del;
448	pub(del);
449	delegate = del;
450
451	subject = importSubject(pub, priv);
452}
453
454
455//
456// An OwnerEntry is a restricted EntryPrototype for use as the ACL owner.
457//
458bool ObjectAcl::OwnerEntry::authorizes(AclAuthorization) const
459{
460    return true;	// owner can do anything
461}
462
463bool ObjectAcl::OwnerEntry::validate(const AclValidationContext &ctx) const
464{
465    return subject->validate(ctx);		// simple subject match - no strings attached
466}
467
468
469//
470// An AclEntry has some extra goodies
471//
472ObjectAcl::AclEntry::AclEntry(const AclEntryPrototype &proto) : Entry(proto)
473{
474    tag = proto.s_tag();
475    if (proto.authorization().contains(CSSM_ACL_AUTHORIZATION_ANY))
476        authorizesAnything = true;	// anything else wouldn't add anything
477    else if (proto.authorization().empty())
478        authorizesAnything = true;	// not in standard, but common sense
479    else {
480        authorizesAnything = false;
481        authorizations = proto.authorization();
482    }
483    //@@@ not setting time range
484    // handle = not set here. Set by caller when the AclEntry is created.
485}
486
487ObjectAcl::AclEntry::AclEntry(const AclSubjectPointer &subject) : Entry(subject)
488{
489    authorizesAnything = true;	// by default, everything
490    //@@@ not setting time range
491}
492
493void ObjectAcl::AclEntry::toEntryInfo(CSSM_ACL_ENTRY_PROTOTYPE &info, Allocator &alloc) const
494{
495    info.TypedSubject = subject->toList(alloc);
496    info.Delegate = delegate;
497	info.Authorization = authorizesAnything ?
498		AuthorizationGroup(CSSM_ACL_AUTHORIZATION_ANY, alloc) :
499		AuthorizationGroup(authorizations, alloc);
500    //@@@ info.TimeRange =
501    assert(tag.length() <= CSSM_MODULE_STRING_SIZE);
502    memcpy(info.EntryTag, tag.c_str(), tag.length() + 1);
503}
504
505bool ObjectAcl::AclEntry::authorizes(AclAuthorization auth) const
506{
507    return authorizesAnything || authorizations.find(auth) != authorizations.end();
508}
509
510bool ObjectAcl::AclEntry::validate(const AclValidationContext &ctx) const
511{
512    //@@@ not checking time ranges
513    return subject->validate(ctx);
514}
515
516void ObjectAcl::AclEntry::importBlob(Reader &pub, Reader &priv)
517{
518    Entry::importBlob(pub, priv);
519    const char *s; pub(s); tag = s;
520
521	// authorizesAnything is on disk as a 4-byte flag
522    Endian<uint32> tmpAuthorizesAnything;
523    pub(tmpAuthorizesAnything);
524    authorizesAnything = tmpAuthorizesAnything;
525
526    authorizations.erase(authorizations.begin(), authorizations.end());
527    if (!authorizesAnything) {
528        Endian<uint32> countIn; pub(countIn);
529		uint32 count = countIn;
530
531        for (uint32 n = 0; n < count; n++) {
532            Endian<AclAuthorization> auth; pub(auth);
533            authorizations.insert(auth);
534        }
535    }
536    //@@@ import time range
537}
538
539
540//
541// Subject factory and makers
542//
543AclSubject::Maker::Maker(CSSM_ACL_SUBJECT_TYPE type)
544	: mType(type)
545{
546    ObjectAcl::makers()[type] = this;
547}
548
549AclSubject *ObjectAcl::make(const TypedList &list)
550{
551    if (!list.isProper())
552        CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
553    return makerFor(list.type()).make(list);
554}
555
556AclSubject *ObjectAcl::make(uint32 typeAndVersion, Reader &pub, Reader &priv)
557{
558	// this type is encoded as (version << 24) | type
559    return makerFor(typeAndVersion & ~AclSubject::versionMask).make(typeAndVersion >> AclSubject::versionShift, pub, priv);
560}
561
562AclSubject::Maker &ObjectAcl::makerFor(CSSM_ACL_SUBJECT_TYPE type)
563{
564    AclSubject::Maker *maker = makers()[type];
565    if (maker == NULL)
566        CssmError::throwMe(CSSM_ERRCODE_ACL_SUBJECT_TYPE_NOT_SUPPORTED);
567    return *maker;
568}
569
570
571//
572// Parsing helper for subject makers.
573// Note that count/array exclude the first element of list, which is the subject type wordid.
574//
575void AclSubject::Maker::crack(const CssmList &list, uint32 count, ListElement **array, ...)
576{
577    if (count != list.length() - 1)
578        CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
579    if (count > 0) {
580        va_list args;
581        va_start(args, array);
582        ListElement *elem = list.first()->next();
583        for (uint32 n = 0; n < count; n++, elem = elem->next()) {
584            CSSM_LIST_ELEMENT_TYPE expectedType = va_arg(args, CSSM_LIST_ELEMENT_TYPE);
585            if (elem->type() != expectedType)
586                CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
587            array[n] = elem;
588        }
589        va_end(args);
590    }
591}
592
593CSSM_WORDID_TYPE AclSubject::Maker::getWord(const ListElement &elem,
594    int min /*= 0*/, int max /*= INT_MAX*/)
595{
596    if (elem.type() != CSSM_LIST_ELEMENT_WORDID)
597        CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
598    CSSM_WORDID_TYPE value = elem;
599    if (value < min || value > max)
600        CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE);
601    return value;
602}
603
604
605//
606// Debug dumping support.
607// Leave the ObjectAcl::debugDump method in (stubbed out)
608// to keep the virtual table layout stable, and to allow
609// proper linking in weird mix-and-match scenarios.
610//
611void ObjectAcl::debugDump(const char *what) const
612{
613#if defined(DEBUGDUMP)
614	if (!what)
615		what = "Dump";
616	Debug::dump("%p ACL %s: %d entries\n", this, what, int(mEntries.size()));
617	Debug::dump(" OWNER ["); mOwner.debugDump(); Debug::dump("]\n");
618	for (EntryMap::const_iterator it = begin(); it != end(); it++) {
619		const AclEntry &ent = it->second;
620		Debug::dump(" (%ld:%s) [", ent.handle, ent.tag.c_str());
621		ent.debugDump();
622		Debug::dump("]\n");
623	}
624	Debug::dump("%p ACL END\n", this);
625#endif //DEBUGDUMP
626}
627
628#if defined(DEBUGDUMP)
629
630void ObjectAcl::Entry::debugDump() const
631{
632	if (subject) {
633		if (AclSubject::Version v = subject->version())
634			Debug::dump("V=%d ", v);
635		subject->debugDump();
636	} else {
637		Debug::dump("NULL subject");
638	}
639	if (delegate)
640		Debug::dump(" DELEGATE");
641}
642
643void ObjectAcl::AclEntry::debugDump() const
644{
645	Entry::debugDump();
646	if (authorizesAnything) {
647		Debug::dump(" auth(ALL)");
648	} else {
649		Debug::dump(" auth(");
650		for (AclAuthorizationSet::iterator it = authorizations.begin();
651				it != authorizations.end(); it++) {
652			if (*it >= CSSM_ACL_AUTHORIZATION_PREAUTH_BASE
653					&& *it < CSSM_ACL_AUTHORIZATION_PREAUTH_END)
654				Debug::dump(" PRE(%d)", *it - CSSM_ACL_AUTHORIZATION_PREAUTH_BASE);
655			else
656				Debug::dump(" %d", *it);
657		}
658		Debug::dump(")");
659	}
660}
661
662#endif //DEBUGDUMP
663