2 * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved.
8 * This file contains Original Code and/or Modifications of Original Code
9 * as defined in and that are subject to the Apple Public Source License
10 * Version 2.0 (the 'License'). You may not use this file except in
11 * compliance with the License. Please obtain a copy of the License at
12 * http://www.opensource.apple.com/apsl/ and read it before using this
15 * The Original Code and all software distributed under the License are
16 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
17 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
18 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
20 * Please see the License for the specific language governing rights and
21 * limitations under the License.
23 * @APPLE_LICENSE_HEADER_END@
28 // acl_keychain - a subject type for the protected-path
29 // keychain prompt interaction model.
31 // Arguments in CSSM_LIST form:
32 // list[1] = CssmData: CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR structure
33 // list[2] = CssmData: Descriptive String (presented to user in protected dialogs)
34 // For legacy compatibility, we accept a single-entry form
35 // list[1] = CssmData: Descriptive String
36 // which defaults to a particular CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR structure value.
37 // This is never produced by current code, and is considered purely a legacy feature.
39 // On-disk (flattened) representation:
40 // In order to accommodate legacy formats nicely, we use the binary-versioning feature
41 // of the ACL machinery. Version 0 is the legacy format (storing only the description
42 // string), while Version 1 contains both selector and description. To allow for
43 // maximum backward compatibility, legacy-compatible forms are written out as version 0.
44 // See isLegacyCompatible().
46 // Some notes on Acl Update Triggers:
47 // When the user checks the "don't ask me again" checkbox in the access confirmation
48 // dialog, we respond by returning the informational error code
49 // CSSMERR_CSP_APPLE_ADD_APPLICATION_ACL_SUBJECT, and setting a count-down trigger
50 // in the connection. The caller is entitled to bypass our dialog (it succeeds
51 // automatically) within the next few (Connection::aclUpdateTriggerLimit == 3)
52 // requests, in order to update the object's ACL as requested. It must then retry
53 // the original access operation (which will presumably pass because of that edit).
54 // These are the rules: for the trigger to apply, the access must be to the same
55 // object, from the same connection, and within the next aclUpdateTriggerLimit accesses.
56 // (Currently, these are for a "get acl", "get owner", and the "change acl" calls.)
57 // Damage Control Department: The worst this mechanism could do, if subverted, is
58 // to bypass our confirmation dialog (making it appear to succeed to the ACL validation).
59 // But that is exactly what the "don't ask me again" checkbox is meant to do, so any
60 // subversion would be based on a (perhaps intentional) miscommunication between user
61 // and client process as to what the user consents not to be asked about (any more).
62 // The user can always examine the resulting ACL (in Keychain Access or elsewhere), and
63 // edit it to suit her needs.
65 #include "acl_keychain.h"
66 #include "agentquery.h"
68 #include "connection.h"
71 #include <security_utilities/debugging.h>
75 #define ACCEPT_LEGACY_FORM 1
76 #define FECKLESS_KEYCHAIN_ACCESS_EXCEPTION 1
80 // The default for the selector structure.
82 CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR
KeychainPromptAclSubject::defaultSelector
= {
83 CSSM_ACL_KEYCHAIN_PROMPT_CURRENT_VERSION
, // version
89 // Validate a credential set against this subject.
91 bool KeychainPromptAclSubject::validate(const AclValidationContext
&context
,
92 const TypedList
&sample
) const
94 if (SecurityServerEnvironment
*env
= context
.environment
<SecurityServerEnvironment
>()) {
95 // check for special ACL-update override
96 if (context
.authorization() == CSSM_ACL_AUTHORIZATION_CHANGE_ACL
97 && Server::connection().aclWasSetForUpdateTrigger(env
->acl
)) {
98 secdebug("kcacl", "honoring acl update trigger for %p(%s)",
99 &env
->acl
, description
.c_str());
103 // does the user need to type in the passphrase?
104 const Database
*db
= env
->database();
105 bool needPassphrase
= db
&& (selector
.flags
& CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE
);
108 #if FECKLESS_KEYCHAIN_ACCESS_EXCEPTION
109 Process
&process
= Server::process();
110 secdebug("kcacl", "Keychain query from process %d (UID %d)", process
.pid(), process
.uid());
111 if (process
.clientCode())
113 process
.clientCode()->canonicalPath() == "/Applications/Utilities/Keychain Access.app";
115 QueryKeychainUse
query(needPassphrase
, db
);
116 query
.inferHints(Server::process());
118 if (query
.queryUser(db
? db
->dbName() : NULL
,
119 description
.c_str(), context
.authorization())
120 != SecurityAgent::noReason
)
123 // process "always allow..." response
124 if (query
.remember
) {
125 // mark for special ACL-update override (really soon) later
126 Server::connection().setAclUpdateTrigger(env
->acl
);
127 secdebug("kcacl", "setting acl update trigger for %p(%s)",
128 &env
->acl
, description
.c_str());
129 // fail with prejudice (caller will retry)
130 CssmError::throwMe(CSSMERR_CSP_APPLE_ADD_APPLICATION_ACL_SUBJECT
);
133 // finally, return the actual user response
136 return false; // default to deny without prejudice
141 // Make a copy of this subject in CSSM_LIST form
143 CssmList
KeychainPromptAclSubject::toList(Allocator
&alloc
) const
145 // always issue new (non-legacy) form
146 return TypedList(alloc
, CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT
,
147 new(alloc
) ListElement(alloc
, CssmData::wrap(selector
)),
148 new(alloc
) ListElement(alloc
, description
));
153 // Create a KeychainPromptAclSubject
155 KeychainPromptAclSubject
*KeychainPromptAclSubject::Maker::make(const TypedList
&list
) const
157 switch (list
.length()) {
158 #if ACCEPT_LEGACY_FORM
159 case 2: // legacy case: just description
161 ListElement
*params
[1];
162 crack(list
, 1, params
, CSSM_LIST_ELEMENT_DATUM
);
163 return new KeychainPromptAclSubject(*params
[0], defaultSelector
);
165 #endif //ACCEPT_LEGACY_FORM
166 case 3: // standard case: selector + description
168 ListElement
*params
[2];
169 crack(list
, 2, params
, CSSM_LIST_ELEMENT_DATUM
, CSSM_LIST_ELEMENT_DATUM
);
170 return new KeychainPromptAclSubject(*params
[1],
171 *params
[0]->data().interpretedAs
<CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR
>(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE
));
174 CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE
);
178 KeychainPromptAclSubject
*KeychainPromptAclSubject::Maker::make(Version version
,
179 Reader
&pub
, Reader
&) const
181 CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR selector
;
182 const char *description
;
185 selector
= defaultSelector
;
190 selector
.version
= n2h (selector
.version
);
191 selector
.flags
= n2h (selector
.flags
);
195 return new KeychainPromptAclSubject(description
, selector
);
198 KeychainPromptAclSubject::KeychainPromptAclSubject(string descr
,
199 const CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR
&sel
)
200 : SimpleAclSubject(CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT
, CSSM_SAMPLE_TYPE_KEYCHAIN_PROMPT
),
201 selector(sel
), description(descr
)
203 // check selector version
204 if (selector
.version
!= CSSM_ACL_KEYCHAIN_PROMPT_CURRENT_VERSION
)
205 CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE
);
207 // determine binary compatibility version
208 if (selector
.flags
== 0) // compatible with old form
209 version(pumaVersion
);
211 version(jaguarVersion
);
216 // Export the subject to a memory blob
218 void KeychainPromptAclSubject::exportBlob(Writer::Counter
&pub
, Writer::Counter
&priv
)
220 if (version() != 0) {
221 selector
.version
= h2n (selector
.version
);
222 selector
.flags
= h2n (selector
.flags
);
226 pub
.insert(description
.size() + 1);
229 void KeychainPromptAclSubject::exportBlob(Writer
&pub
, Writer
&priv
)
231 if (version() != 0) {
232 selector
.version
= h2n (selector
.version
);
233 selector
.flags
= h2n (selector
.flags
);
236 pub(description
.c_str());
241 // Determine whether this ACL subject is in "legacy compatible" form.
242 // Legacy (<10.2) form contained no selector.
244 bool KeychainPromptAclSubject::isLegacyCompatible() const
246 return selector
.flags
== 0;
252 void KeychainPromptAclSubject::debugDump() const
254 Debug::dump("KeychainPrompt:%s(%s)",
256 (selector
.flags
& CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE
) ? "passphrase" : "standard");