1/*
2 * Copyright (c) 2000-2004 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#include "SecPassword.h"
25#include "Password.h"
26
27#include "SecBridge.h"
28
29#include "KCExceptions.h"
30#include <Security/Authorization.h>
31#include <Security/AuthorizationTagsPriv.h>
32
33#if 0
34static CFTypeID
35SecPasswordGetTypeID(void)
36{
37	BEGIN_SECAPI
38
39	return gTypes().PasswordImpl.typeID;
40
41	END_SECAPI1(_kCFRuntimeNotATypeID)
42}
43#endif
44
45OSStatus
46SecGenericPasswordCreate(SecKeychainAttributeList *searchAttrList, SecKeychainAttributeList *itemAttrList, SecPasswordRef *itemRef)
47{
48    BEGIN_SECAPI
49    KCThrowParamErrIf_( (itemRef == NULL) );
50    KCThrowParamErrIf_( (searchAttrList == NULL) ^ (itemAttrList == NULL) ); // Both or neither
51
52    Password passwordItem(kSecGenericPasswordItemClass, searchAttrList, itemAttrList);
53    if (itemRef)
54        *itemRef = passwordItem->handle();
55
56	END_SECAPI
57}
58
59OSStatus
60SecPasswordSetInitialAccess(SecPasswordRef itemRef, SecAccessRef accessRef)
61{
62	BEGIN_SECAPI
63	PasswordImpl::required(itemRef)->setAccess(Access::required(accessRef));
64	END_SECAPI
65}
66
67OSStatus
68SecPasswordAction(SecPasswordRef itemRef, CFTypeRef message, UInt32 flags, UInt32 *length, const void **data)
69{
70    BEGIN_SECAPI
71
72    Password passwordRef = PasswordImpl::required(itemRef);
73
74    void *passwordData = NULL;
75    UInt32 passwordLength = 0;
76	bool gotPassword = false;
77
78    // no flags has no meaning, and there is no apparent default
79    assert( flags );
80
81    // fail can only be combined with get or new
82    assert( (flags & kSecPasswordFail) ? ((flags & kSecPasswordGet) || (flags & kSecPasswordNew)) : true );
83
84    // XXX/cs replace this with our CFString->UTF8 conversion
85    const char *messageData = NULL;
86    auto_array<char> messageBuffer;
87
88    if (message && (CFStringGetTypeID() == CFGetTypeID(message)))
89    {
90        messageData = CFStringGetCStringPtr(static_cast<CFStringRef>(message), kCFStringEncodingUTF8);
91
92        if (messageData == NULL)
93        {
94            CFIndex maxLen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(static_cast<CFStringRef>(message)), kCFStringEncodingUTF8) + 1;
95
96            messageBuffer.allocate(maxLen);
97            if (CFStringGetCString(static_cast<CFStringRef>(message), messageBuffer.get(), maxLen, kCFStringEncodingUTF8))
98                messageData = messageBuffer.get();
99        }
100    }
101
102    if (passwordRef->useKeychain() && !(flags & kSecPasswordNew) && !(flags & kSecPasswordFail))
103    {
104            // Pull out data and if it's successful return it
105            if (flags & kSecPasswordGet)
106            {
107
108                // XXX/cs if there are unsaved changes this doesn't work
109                //        so doing a Get followed by a Get|Set will do the wrong thing
110
111                // check mItem whether it's got data
112                if (passwordRef->getData(length, data))
113                    return errSecSuccess;
114            }
115
116            // User might cancel here, immediately return that too (it will be thrown)
117    }
118
119    // If we're still here we're not using the keychain or it wasn't there yet
120
121    // Do the authorization call to get the password, unless only kSecPasswordSet is specified)
122    if ((flags & kSecPasswordNew) || (flags & kSecPasswordGet))
123    {
124        AuthorizationRef authRef;
125        OSStatus status = AuthorizationCreate(NULL,NULL,0,&authRef);
126        if (status != errSecSuccess)
127        {
128            MacOSError::throwMe(status);
129        }
130
131        AuthorizationItem right = { NULL, 0, NULL, 0 };
132        AuthorizationItemSet rightSet = { 1, &right };
133        uint32_t reason, tries;
134        bool keychain = 0, addToKeychain = 0;
135
136        if (passwordRef->useKeychain())
137        {
138            keychain = 1;
139            addToKeychain = passwordRef->rememberInKeychain();
140        }
141		else
142		{
143            keychain = 0;
144            addToKeychain = 0;
145		}
146
147        // Get|Fail conceivable would have it enabled, but since the effect is that it will get overwritten
148        // we'll make the user explicitly do it
149        if (flags & kSecPasswordGet)
150            addToKeychain = 0; // turn it off for old items that weren't successfully retrieved from the keychain
151
152        if (flags & kSecPasswordFail) // set up retry to reflect failure
153        {
154	        tries = 1;
155            if (flags & kSecPasswordNew)
156                reason = 34; // passphraseUnacceptable = 34 passphrase unacceptable for some other reason
157            else
158                reason = 21; // invalidPassphrase = 21 passphrase was wrong
159        }
160        else
161		{
162			reason = 0;
163			tries = 0;
164		}
165
166        if (flags & kSecPasswordNew) // pick new passphrase
167            right.name = "com.apple.builtin.generic-new-passphrase";
168        else
169            right.name = "com.apple.builtin.generic-unlock";
170
171        bool showPassword = false;
172
173        AuthorizationItem envRights[6] = { { AGENT_HINT_RETRY_REASON, sizeof(reason), &reason, 0 },
174                                            { AGENT_HINT_TRIES, sizeof(tries), &tries, 0 },
175                                            { AGENT_HINT_CUSTOM_PROMPT, messageData ? strlen(messageData) : 0, const_cast<char*>(messageData), 0 },
176                                            { AGENT_HINT_ALLOW_SHOW_PASSWORD, showPassword ? strlen("YES") : strlen("NO"), const_cast<char *>(showPassword ? "YES" : "NO"), 0 },
177                                            { AGENT_HINT_SHOW_ADD_TO_KEYCHAIN, keychain ? strlen("YES") : strlen("NO"), const_cast<char *>(keychain ? "YES" : "NO"), 0 },
178                                            { AGENT_ADD_TO_KEYCHAIN, addToKeychain ? strlen("YES") : strlen("NO"), const_cast<char *>(addToKeychain ? "YES" : "NO"), 0 } };
179
180        AuthorizationItemSet envSet = { sizeof(envRights) / sizeof(*envRights), envRights };
181
182	    secdebug("SecPassword", "dialog(%s)%s%s%s.", right.name, tries?" retry":"", keychain?" show-add-keychain":"", addToKeychain?" save-to-keychain":"");
183
184        status = AuthorizationCopyRights(authRef, &rightSet, &envSet, kAuthorizationFlagDefaults|kAuthorizationFlagInteractionAllowed|kAuthorizationFlagExtendRights, NULL);
185
186        if (status)
187        {
188            AuthorizationFree(authRef, 0);
189            return status;
190        }
191
192        // if success pull the data
193        AuthorizationItemSet *returnedInfo;
194        status = AuthorizationCopyInfo(authRef, NULL, &returnedInfo);
195
196        if (status)
197        {
198            AuthorizationFree(authRef, 0);
199
200            return status;
201        }
202
203        if (returnedInfo && (returnedInfo->count > 0))
204        {
205            for (uint32_t index = 0; index < returnedInfo->count; index++)
206            {
207                AuthorizationItem &item = returnedInfo->items[index];
208
209                if (!strcmp(AGENT_PASSWORD, item.name))
210                {
211					gotPassword = true;
212                    passwordLength = (UInt32)item.valueLength;
213
214                    if (passwordLength)
215                    {
216                        Allocator &allocator = Allocator::standard();
217                        passwordData = allocator.malloc(passwordLength);
218                        if (passwordData)
219                            memcpy(passwordData, item.value, passwordLength);
220                    }
221
222                    if (length)
223                        *length = passwordLength;
224                    if (data)
225                        *data = passwordData;
226
227					secdebug("SecPassword", "Got password (%u,%p).", (unsigned int)passwordLength, passwordData);
228                }
229                else if (!strcmp(AGENT_ADD_TO_KEYCHAIN, item.name))
230                {
231                    bool remember = (item.value && item.valueLength == strlen("YES") && !memcmp("YES", static_cast<char *>(item.value), item.valueLength));
232					passwordRef->setRememberInKeychain(remember);
233					if (remember)
234						secdebug("SecPassword", "User wants to add the password to the Keychain.");
235                }
236            }
237        }
238
239        AuthorizationFreeItemSet(returnedInfo);
240        AuthorizationFree(authRef, 0);
241
242    }
243
244    // If we're still here the use gave us his password, store it if keychain is in use
245    if (passwordRef->useKeychain())
246    {
247        if (passwordRef->rememberInKeychain()) {
248            if (gotPassword)
249				passwordRef->setData(passwordLength, passwordData);
250			if (flags & kSecPasswordSet)
251            {
252				passwordRef->save();
253                gotPassword = true;
254            }
255		}
256    }
257
258    if (!gotPassword)
259    {
260        return errAuthorizationDenied;
261    }
262
263    END_SECAPI
264}
265