1/*
2 * Copyright (c) 2000-2009,2012-2013 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//
26// acls - securityd ACL implementation
27//
28#include "acls.h"
29#include "connection.h"
30#include "server.h"
31#include "agentquery.h"
32#include "tokendatabase.h"
33#include "acl_keychain.h"
34
35// ACL subjects whose Environments we implement
36#include <security_cdsa_utilities/acl_any.h>
37#include <security_cdsa_utilities/acl_password.h>
38#include <security_cdsa_utilities/acl_threshold.h>
39
40#include <sys/sysctl.h>
41#include <security_utilities/logging.h>
42
43//
44// SecurityServerAcl is virtual
45//
46SecurityServerAcl::~SecurityServerAcl()
47{ }
48
49
50//
51// The default implementation of the ACL interface simply uses the local ObjectAcl
52// data. You can customize this by implementing instantiateAcl() [from ObjectAcl]
53// or by overriding these methods as desired.
54// Note: While you can completely ignore the ObjectAcl personality if you wish, it's
55// usually smarter to adapt it.
56//
57void SecurityServerAcl::getOwner(AclOwnerPrototype &owner)
58{
59	StLock<Mutex> _(aclSequence);
60	ObjectAcl::cssmGetOwner(owner);
61}
62
63void SecurityServerAcl::getAcl(const char *tag, uint32 &count, AclEntryInfo *&acls)
64{
65	StLock<Mutex> _(aclSequence);
66	ObjectAcl::cssmGetAcl(tag, count, acls);
67}
68
69void SecurityServerAcl::changeAcl(const AclEdit &edit, const AccessCredentials *cred,
70	Database *db)
71{
72	StLock<Mutex> _(aclSequence);
73	SecurityServerEnvironment env(*this, db);
74	ObjectAcl::cssmChangeAcl(edit, cred, &env);
75}
76
77void SecurityServerAcl::changeOwner(const AclOwnerPrototype &newOwner,
78	const AccessCredentials *cred, Database *db)
79{
80	StLock<Mutex> _(aclSequence);
81	SecurityServerEnvironment env(*this, db);
82	ObjectAcl::cssmChangeOwner(newOwner, cred, &env);
83}
84
85
86//
87// Modified validate() methods to connect all the conduits...
88//
89void SecurityServerAcl::validate(AclAuthorization auth, const AccessCredentials *cred, Database *db)
90{
91    SecurityServerEnvironment env(*this, db);
92
93	StLock<Mutex> objectSequence(aclSequence);
94	StLock<Mutex> processSequence(Server::process().aclSequence);
95    ObjectAcl::validate(auth, cred, &env);
96}
97
98void SecurityServerAcl::validate(AclAuthorization auth, const Context &context, Database *db)
99{
100	validate(auth,
101		context.get<AccessCredentials>(CSSM_ATTRIBUTE_ACCESS_CREDENTIALS), db);
102}
103
104
105//
106// This helper tries to add the (new) subject given to the ACL
107// whose validation is currently proceeding through context.
108// This will succeed if the ACL is in standard form, which means
109// a ThresholdAclSubject.
110// The new subject will be added at the front (so it is checked first
111// from now on), and as a side effect we'll notify the client side to
112// re-encode the object.
113// Returns true if the edit could be done, or false if the ACL wasn't
114// standard enough. May throw if the ACL is malformed or otherwise messed up.
115//
116// This is a self-contained helper that is here merely because it's "about"
117// ACLs and has no better home.
118//
119bool SecurityServerAcl::addToStandardACL(const AclValidationContext &context, AclSubject *subject)
120{
121	if (SecurityServerEnvironment *env = context.environment<SecurityServerEnvironment>())
122		if (ThresholdAclSubject *threshold = env->standardSubject(context)) {
123			unsigned size = threshold->count();
124			if (dynamic_cast<KeychainPromptAclSubject *>(threshold->subject(size-1))) {
125				// looks standard enough
126				secdebug("acl", "adding new subject %p to from of threshold ACL", subject);
127				threshold->add(subject, 0);
128
129				// tell the ACL it's been modified
130				context.acl()->changedAcl();
131
132				// trigger a special notification code on (otherwise successful) return
133				Server::connection().overrideReturn(CSSMERR_CSP_APPLE_ADD_APPLICATION_ACL_SUBJECT);
134				return true;
135			}
136		}
137	secdebug("acl", "ACL is not standard form; cannot edit");
138	return false;
139}
140
141
142//
143// Look at the ACL whose validation is currently proceeding through context.
144// If it LOOKS like a plausible version of a legacy "dot mac item" ACL.
145// We don't have access to the database attributes of the item up here in the
146// securityd sky, so we have to apply a heuristic based on which applications (by path)
147// are given access to the item.
148// So this is strictly a heuristic. The potential downside is that we may inadvertently
149// give access to new .Mac authorized Apple (only) applications when the user only intended
150// a limited set of extremely popular Apple (only) applications that just happen to all be
151// .Mac authorized today. We can live with that.
152//
153bool SecurityServerAcl::looksLikeLegacyDotMac(const AclValidationContext &context)
154{
155	static const char * const prototypicalDotMacPath[] = {
156		"/Applications/Mail.app",
157		"/Applications/Safari.app",
158		"/Applications/iSync.app",
159		"/Applications/System Preferences.app",
160		"/Applications/iCal.app",
161		"/Applications/iChat.app",
162		"/Applications/iTunes.app",
163		"/Applications/Address Book.app",
164		"/Applications/iSync.app",
165		NULL	// sentinel
166	};
167
168	static const unsigned threshold = 6;
169
170	if (SecurityServerEnvironment *env = context.environment<SecurityServerEnvironment>()) {
171		if (ThresholdAclSubject *list = env->standardSubject(context)) {
172			unsigned count = list->count();
173			unsigned matches = 0;
174			for (unsigned n = 0; n < count; ++n) {
175				if (CodeSignatureAclSubject *app = dynamic_cast<CodeSignatureAclSubject *>(list->subject(n))) {
176					for (const char * const *p = prototypicalDotMacPath; *p; p++)
177						if (app->path() == *p)
178							matches++;
179				}
180			}
181			secdebug("codesign", "matched %d of %zd candididates (threshold=%d)",
182				matches, sizeof(prototypicalDotMacPath) / sizeof(char *) - 1, threshold);
183			return matches >= threshold;
184		}
185	}
186	return false;
187}
188
189
190//
191// External storage interface
192//
193Adornable &SecurityServerEnvironment::store(const AclSubject *subject)
194{
195	switch (subject->type()) {
196	case CSSM_ACL_SUBJECT_TYPE_PREAUTH:
197		{
198			if (TokenDatabase *tokenDb = dynamic_cast<TokenDatabase *>(database))
199				return tokenDb->common().store();
200		}
201		break;
202	default:
203		break;
204	}
205	CssmError::throwMe(CSSM_ERRCODE_ACL_SUBJECT_TYPE_NOT_SUPPORTED);
206}
207
208
209//
210// ProcessAclSubject personality: uid/gid/pid come from the active Process object
211//
212uid_t SecurityServerEnvironment::getuid() const
213{
214    return Server::process().uid();
215}
216
217gid_t SecurityServerEnvironment::getgid() const
218{
219    return Server::process().gid();
220}
221
222pid_t SecurityServerEnvironment::getpid() const
223{
224    return Server::process().pid();
225}
226
227
228//
229// CodeSignatureAclSubject personality: take code signature from active Process object
230//
231bool SecurityServerEnvironment::verifyCodeSignature(const OSXVerifier &verifier,
232	const AclValidationContext &context)
233{
234	return Server::codeSignatures().verify(Server::process(), verifier, context);
235}
236
237
238//
239// PromptedAclSubject personality: Get a secret by prompting through SecurityAgent
240//
241bool SecurityServerEnvironment::getSecret(CssmOwnedData &secret, const CssmData &prompt) const
242{
243	//@@@ ignoring prompt - not used right now
244	if (database) {
245		QueryPIN query(*database);
246		query.inferHints(Server::process());
247		if (!query()) {	// success
248			secret = query.pin();
249			return true;
250		}
251	}
252	return false;
253}
254
255
256//
257// SecretAclSubject personality: externally validate a secret (passphrase etc.)
258// Right now, this always goes to the (Token)Database object, because that's where
259// the PIN ACL entries are. We could direct this at the ObjectAcl (database or key)
260// instead and rely on tokend to perform the PIN mapping, but the generic tokend
261// wrappers do not (currently) perform any ACL validation, so every tokend would have
262// to re-implement that. Perhaps in the next ACL revamp cycle...
263//
264bool SecurityServerEnvironment::validateSecret(const SecretAclSubject *me,
265	const AccessCredentials *cred)
266{
267	return database && database->validateSecret(me, cred);
268}
269
270
271//
272// PreAuthenticationAclSubject personality - refer to database (ObjectAcl)
273//
274ObjectAcl *SecurityServerEnvironment::preAuthSource()
275{
276	return database ? &database->acl() : NULL;
277}
278
279
280//
281// Autonomous ACL editing support
282//
283ThresholdAclSubject *SecurityServerEnvironment::standardSubject(const AclValidationContext &context)
284{
285	return dynamic_cast<ThresholdAclSubject *>(context.subject());
286}
287
288
289//
290// The default AclSource denies having an ACL at all
291//
292AclSource::~AclSource()
293{ /* virtual */ }
294
295SecurityServerAcl &AclSource::acl()
296{
297	CssmError::throwMe(CSSM_ERRCODE_OBJECT_ACL_NOT_SUPPORTED);
298}
299
300Database *AclSource::relatedDatabase()
301{
302	return NULL;
303}
304