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 secdebug("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
);
100 #if FECKLESS_KEYCHAIN_ACCESS_EXCEPTION
101 Process
&process
= Server::active().connection().process
;
102 secdebug("kcacl", "Keychain query from process %d (UID %d)", process
.pid(), process
.uid());
103 if (process
.clientCode())
105 process
.clientCode()->canonicalPath() == "/Applications/Utilities/Keychain Access.app";
107 QueryKeychainUse
query(needPassphrase
);
108 const char* dbName
= db
? db
->dbName() : NULL
;
109 query
.queryUser(db
, dbName
, description
.c_str(), context
.authorization());
111 // process "always allow..." response
112 if (query
.continueGrantingToCaller
) {
113 // mark for special ACL-update override (really soon) later
114 Server::connection().setAclUpdateTrigger(env
->acl
);
115 secdebug("kcacl", "setting acl update trigger for %p(%s)",
116 &env
->acl
, description
.c_str());
117 // fail with prejudice (caller will retry)
118 CssmError::throwMe(CSSMERR_CSP_APPLE_ADD_APPLICATION_ACL_SUBJECT
);
121 // finally, return the actual user response
122 return query
.allowAccess
;
124 return false; // default to deny without prejudice
129 // Make a copy of this subject in CSSM_LIST form
131 CssmList
KeychainPromptAclSubject::toList(CssmAllocator
&alloc
) const
133 // always issue new (non-legacy) form
134 return TypedList(alloc
, CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT
,
135 new(alloc
) ListElement(alloc
, CssmData::wrap(selector
)),
136 new(alloc
) ListElement(alloc
, description
));
141 // Create a KeychainPromptAclSubject
143 KeychainPromptAclSubject
*KeychainPromptAclSubject::Maker::make(const TypedList
&list
) const
145 switch (list
.length()) {
146 #if ACCEPT_LEGACY_FORM
147 case 2: // legacy case: just description
149 ListElement
*params
[1];
150 crack(list
, 1, params
, CSSM_LIST_ELEMENT_DATUM
);
151 return new KeychainPromptAclSubject(*params
[0], defaultSelector
);
153 #endif //ACCEPT_LEGACY_FORM
154 case 3: // standard case: selector + description
156 ListElement
*params
[2];
157 crack(list
, 2, params
, CSSM_LIST_ELEMENT_DATUM
, CSSM_LIST_ELEMENT_DATUM
);
158 return new KeychainPromptAclSubject(*params
[1],
159 *params
[0]->data().interpretedAs
<CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR
>(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE
));
162 CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE
);
166 KeychainPromptAclSubject
*KeychainPromptAclSubject::Maker::make(Version version
,
167 Reader
&pub
, Reader
&) const
169 CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR selector
;
170 const char *description
;
173 selector
= defaultSelector
;
178 selector
.version
= n2h (selector
.version
);
179 selector
.flags
= n2h (selector
.flags
);
183 return new KeychainPromptAclSubject(description
, selector
);
186 KeychainPromptAclSubject::KeychainPromptAclSubject(string descr
,
187 const CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR
&sel
)
188 : SimpleAclSubject(CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT
, CSSM_SAMPLE_TYPE_KEYCHAIN_PROMPT
),
189 selector(sel
), description(descr
)
191 // check selector version
192 if (selector
.version
!= CSSM_ACL_KEYCHAIN_PROMPT_CURRENT_VERSION
)
193 CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE
);
195 // determine binary compatibility version
196 if (selector
.flags
== 0) // compatible with old form
197 version(pumaVersion
);
199 version(jaguarVersion
);
204 // Export the subject to a memory blob
206 void KeychainPromptAclSubject::exportBlob(Writer::Counter
&pub
, Writer::Counter
&priv
)
208 if (version() != 0) {
209 selector
.version
= h2n (selector
.version
);
210 selector
.flags
= h2n (selector
.flags
);
214 pub
.insert(description
.size() + 1);
217 void KeychainPromptAclSubject::exportBlob(Writer
&pub
, Writer
&priv
)
219 if (version() != 0) {
220 selector
.version
= h2n (selector
.version
);
221 selector
.flags
= h2n (selector
.flags
);
224 pub(description
.c_str());
229 // Determine whether this ACL subject is in "legacy compatible" form.
230 // Legacy (<10.2) form contained no selector.
232 bool KeychainPromptAclSubject::isLegacyCompatible() const
234 return selector
.flags
== 0;
240 void KeychainPromptAclSubject::debugDump() const
242 Debug::dump("KeychainPrompt:%s(%s)",
244 (selector
.flags
& CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE
) ? "passphrase" : "standard");