2 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
4 * The contents of this file constitute Original Code as defined in and are
5 * subject to the Apple Public Source License Version 1.2 (the 'License').
6 * You may not use this file except in compliance with the License. Please obtain
7 * a copy of the License at http://www.apple.com/publicsource and read it before
10 * This Original Code and all software distributed under the License are
11 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
12 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
13 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
15 * specific language governing rights and limitations under the License.
20 // acl_keychain - a subject type for the protected-path
21 // keychain prompt interaction model.
23 // Arguments in CSSM_LIST form:
24 // list[1] = CssmData: CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR structure
25 // list[2] = CssmData: Descriptive String (presented to user in protected dialogs)
26 // For legacy compatibility, we accept a single-entry form
27 // list[1] = CssmData: Descriptive String
28 // which defaults to a particular CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR structure value.
29 // This is never produced by current code, and is considered purely a legacy feature.
31 // On-disk (flattened) representation:
32 // In order to accommodate legacy formats nicely, we use the binary-versioning feature
33 // of the ACL machinery. Version 0 is the legacy format (storing only the description
34 // string), while Version 1 contains both selector and description. To allow for
35 // maximum backward compatibility, legacy-compatible forms are written out as version 0.
36 // See isLegacyCompatible().
38 // Some notes on Acl Update Triggers:
39 // When the user checks the "don't ask me again" checkbox in the access confirmation
40 // dialog, we respond by returning the informational error code
41 // CSSMERR_CSP_APPLE_ADD_APPLICATION_ACL_SUBJECT, and setting a count-down trigger
42 // in the connection. The caller is entitled to bypass our dialog (it succeeds
43 // automatically) within the next few (Connection::aclUpdateTriggerLimit == 3)
44 // requests, in order to update the object's ACL as requested. It must then retry
45 // the original access operation (which will presumably pass because of that edit).
46 // These are the rules: for the trigger to apply, the access must be to the same
47 // object, from the same connection, and within the next aclUpdateTriggerLimit accesses.
48 // (Currently, these are for a "get acl", "get owner", and the "change acl" calls.)
49 // Damage Control Department: The worst this mechanism could do, if subverted, is
50 // to bypass our confirmation dialog (making it appear to succeed to the ACL validation).
51 // But that is exactly what the "don't ask me again" checkbox is meant to do, so any
52 // subversion would be based on a (perhaps intentional) miscommunication between user
53 // and client process as to what the user consents not to be asked about (any more).
54 // The user can always examine the resulting ACL (in Keychain Access or elsewhere), and
55 // edit it to suit her needs.
57 #include "acl_keychain.h"
58 #include "agentquery.h"
60 #include "connection.h"
61 #include "xdatabase.h"
63 #include <Security/debugging.h>
67 #define ACCEPT_LEGACY_FORM 1
68 #define FECKLESS_KEYCHAIN_ACCESS_EXCEPTION 1
72 // The default for the selector structure.
74 CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR
KeychainPromptAclSubject::defaultSelector
= {
75 CSSM_ACL_KEYCHAIN_PROMPT_CURRENT_VERSION
, // version
81 // Validate a credential set against this subject.
83 bool KeychainPromptAclSubject::validate(const AclValidationContext
&context
,
84 const TypedList
&sample
) const
86 if (SecurityServerEnvironment
*env
= context
.environment
<SecurityServerEnvironment
>()) {
87 // check for special ACL-update override
88 if (context
.authorization() == CSSM_ACL_AUTHORIZATION_CHANGE_ACL
89 && Server::connection().aclWasSetForUpdateTrigger(env
->acl
)) {
90 debug("kcacl", "honoring acl update trigger for %p(%s)",
91 &env
->acl
, description
.c_str());
95 // does the user need to type in the passphrase?
96 const Database
*db
= env
->database();
97 bool needPassphrase
= db
&& (selector
.flags
& CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE
);
98 debug("adhoc", "prompt acl db=%p needPassphrase=%d", db
, needPassphrase
);
101 Process
&cltProc
= Server::active().connection().process
;
102 debug("kcacl", "Keychain query from process %d (UID %d)", cltProc
.pid(), cltProc
.uid());
103 #if FECKLESS_KEYCHAIN_ACCESS_EXCEPTION
104 if (cltProc
.clientCode())
106 cltProc
.clientCode()->canonicalPath() == "/Applications/Utilities/Keychain Access.app";
108 QueryKeychainUse
query(cltProc
.uid(), cltProc
.session
, needPassphrase
);
109 query((db
? db
->dbName() : NULL
), description
.c_str(), context
.authorization());
111 // verify keychain passphrase if required
112 if (needPassphrase
&& !env
->database()->validatePassphrase(StringData(query
.passphrase
)))
113 return false; // needed passphrase, passphrase is wrong
115 // process "always allow..." response
116 if (query
.continueGrantingToCaller
) {
117 // mark for special ACL-update override (really soon) later
118 Server::connection().setAclUpdateTrigger(env
->acl
);
119 debug("kcacl", "setting acl update trigger for %p(%s)",
120 &env
->acl
, description
.c_str());
121 // fail with prejudice (caller will retry)
122 CssmError::throwMe(CSSMERR_CSP_APPLE_ADD_APPLICATION_ACL_SUBJECT
);
125 // finally, return the actual user response
126 return query
.allowAccess
;
128 return false; // default to deny without prejudice
133 // Make a copy of this subject in CSSM_LIST form
135 CssmList
KeychainPromptAclSubject::toList(CssmAllocator
&alloc
) const
137 // always issue new (non-legacy) form
138 return TypedList(alloc
, CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT
,
139 new(alloc
) ListElement(alloc
, CssmData::wrap(selector
)),
140 new(alloc
) ListElement(alloc
, description
));
145 // Create a KeychainPromptAclSubject
147 KeychainPromptAclSubject
*KeychainPromptAclSubject::Maker::make(const TypedList
&list
) const
149 switch (list
.length()) {
150 #if ACCEPT_LEGACY_FORM
151 case 2: // legacy case: just description
153 ListElement
*params
[1];
154 crack(list
, 1, params
, CSSM_LIST_ELEMENT_DATUM
);
155 return new KeychainPromptAclSubject(*params
[0], defaultSelector
);
157 #endif //ACCEPT_LEGACY_FORM
158 case 3: // standard case: selector + description
160 ListElement
*params
[2];
161 crack(list
, 2, params
, CSSM_LIST_ELEMENT_DATUM
, CSSM_LIST_ELEMENT_DATUM
);
162 return new KeychainPromptAclSubject(*params
[1],
163 *CssmData(*params
[0]).interpretedAs
<CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR
>());
166 CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE
);
170 KeychainPromptAclSubject
*KeychainPromptAclSubject::Maker::make(Version version
,
171 Reader
&pub
, Reader
&) const
173 CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR selector
;
174 const char *description
;
177 selector
= defaultSelector
;
185 return new KeychainPromptAclSubject(description
, selector
);
188 KeychainPromptAclSubject::KeychainPromptAclSubject(string descr
,
189 const CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR
&sel
)
190 : SimpleAclSubject(CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT
, CSSM_SAMPLE_TYPE_KEYCHAIN_PROMPT
),
191 selector(sel
), description(descr
)
193 // check selector version
194 if (selector
.version
!= CSSM_ACL_KEYCHAIN_PROMPT_CURRENT_VERSION
)
195 CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE
);
197 // determine binary compatibility version
198 if (selector
.flags
== 0) // compatible with old form
199 version(pumaVersion
);
201 version(jaguarVersion
);
206 // Export the subject to a memory blob
208 void KeychainPromptAclSubject::exportBlob(Writer::Counter
&pub
, Writer::Counter
&priv
)
212 pub
.insert(description
.size() + 1);
215 void KeychainPromptAclSubject::exportBlob(Writer
&pub
, Writer
&priv
)
219 pub(description
.c_str());
224 // Determine whether this ACL subject is in "legacy compatible" form.
225 // Legacy (<10.2) form contained no selector.
227 bool KeychainPromptAclSubject::isLegacyCompatible() const
229 return selector
.flags
== 0;
235 void KeychainPromptAclSubject::debugDump() const
237 Debug::dump("KeychainPrompt:%s(%s)",
239 (selector
.flags
& CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE
) ? "passphrase" : "standard");