1/*
2 * Copyright (c) 2014 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#include <stdio.h>
26#include <utilities/SecCFWrappers.h>
27
28#include "security.h"
29#include "keychain_util.h"
30#include "SecAccessControlPriv.h"
31#include <libaks_acl_cf_keys.h>
32
33void
34display_sac_line(SecAccessControlRef sac, CFMutableStringRef line) {
35    CFTypeRef protection = SecAccessControlGetProtection(sac);
36    if(CFStringGetTypeID() == CFGetTypeID(protection))
37    {
38        CFStringAppend(line, protection);
39    }
40
41    CFDictionaryRef constraints = SecAccessControlGetConstraints(sac);
42    if(constraints != NULL)
43    {
44        CFDictionaryForEach(constraints, ^(const void *key, const void *value) {
45            CFStringAppend(line, CFSTR(";"));
46            CFStringAppend(line, key);
47            CFDictionaryRef constraintData = (CFDictionaryRef)value;
48
49            if(CFStringGetTypeID() == CFGetTypeID(key) && CFDictionaryGetTypeID() == CFGetTypeID(value)) {
50                CFDictionaryForEach(constraintData, ^(const void *constraintKey, const void *constraintValue) {
51                    CFStringRef constraintType = (CFStringRef)constraintKey;
52                    CFStringAppend(line, CFSTR(":"));
53                    CFStringAppend(line, constraintType);
54                    CFStringAppend(line, CFSTR("("));
55
56                    if(CFStringCompare(constraintType, CFSTR("policy"), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
57                    {
58                        // for policy, argument is plain string
59                        CFStringAppend(line, (CFStringRef)constraintValue);
60                    }
61                    else if(CFStringCompare(constraintType, CFSTR("passcode"), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
62                    {
63                        // for passcode, we have to decode if system-passcode is used
64                        if(CFStringCompare((CFStringRef)constraintValue, CFSTR("passcode"), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
65                            CFStringAppend(line, CFSTR("yes"));
66                        else
67                            CFStringAppend(line, CFSTR("no"));
68                    }
69                    else if(CFStringCompare(constraintType, CFSTR("bio"), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
70                    {
71                        // for bio, argument is plain string
72                        CFStringAppend(line, (CFStringRef)constraintValue);
73                    }
74                    else
75                        CFStringAppend(line, CFSTR("Not yet supported"));
76
77                    CFStringAppend(line, CFSTR(")"));
78                });
79            } else {
80                CFStringAppend(line, CFSTR(":"));
81                if (value == kCFBooleanTrue) {
82                    CFStringAppend(line, CFSTR("true"));
83                } else if (value == kCFBooleanFalse) {
84                    CFStringAppend(line, CFSTR("false"));
85                } else {
86                    CFStringAppend(line, CFSTR("unrecognized value"));
87                }
88            }
89        });
90    }
91}
92
93bool
94keychain_query_parse_cstring(CFMutableDictionaryRef q, const char *query) {
95    CFStringRef s;
96    s = CFStringCreateWithCStringNoCopy(0, query, kCFStringEncodingUTF8, kCFAllocatorNull);
97    bool result = keychain_query_parse_string(q, s);
98    CFRelease(s);
99    return result;
100}
101
102/* Parse a string of the form attr=value,attr=value,attr=value */
103bool
104keychain_query_parse_string(CFMutableDictionaryRef q, CFStringRef s) {
105    bool inkey = true;
106    bool escaped = false;
107    bool error = false;
108    CFStringRef key = NULL;
109    CFMutableStringRef str = CFStringCreateMutable(0, 0);
110    CFRange rng = { .location = 0, .length = CFStringGetLength(s) };
111    CFCharacterSetRef cs_key = CFCharacterSetCreateWithCharactersInString(0, CFSTR("=\\"));
112    CFCharacterSetRef cs_value = CFCharacterSetCreateWithCharactersInString(0, CFSTR(",\\"));
113    while (rng.length) {
114        CFRange r;
115        CFStringRef sub;
116        bool complete = false;
117        if (escaped) {
118            r.location = rng.location;
119            r.length = 1;
120            sub = CFStringCreateWithSubstring(0, s, r);
121            escaped = false;
122        } else if (CFStringFindCharacterFromSet(s, inkey ? cs_key : cs_value, rng, 0, &r)) {
123            if (CFStringGetCharacterAtIndex(s, r.location) == '\\') {
124                escaped = true;
125            } else {
126                complete = true;
127            }
128            CFIndex next = r.location + 1;
129            r.length = r.location - rng.location;
130            r.location = rng.location;
131            sub = CFStringCreateWithSubstring(0, s, r);
132            rng.length -= next - rng.location;
133            rng.location = next;
134        } else {
135            sub = CFStringCreateWithSubstring(0, s, rng);
136            rng.location += rng.length;
137            rng.length = 0;
138            complete = true;
139        }
140        CFStringAppend(str, sub);
141        CFRelease(sub);
142
143        if (complete) {
144            CFStringRef value = CFStringCreateCopy(0, str);
145            CFStringReplaceAll(str, CFSTR(""));
146            if (inkey) {
147                key = value;
148            } else {
149                if(key && CFStringCompare(key, kSecAttrAccessControl, kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
150                    SecAccessControlRef sac = keychain_query_parse_sac(value);
151                    if(sac) {
152                        CFDictionarySetValue(q, key, sac);
153                    } else {
154                        fprintf(stderr, "SecItemCopyMatching returned unexpected results:");
155                        error = true;
156                    }
157                } else {
158                    CFDictionarySetValue(q, key, value);
159                }
160                CFReleaseNull(value);
161                CFReleaseNull(key);
162            }
163            inkey = !inkey;
164        }
165        if(error)
166            break;
167    }
168    if (key) {
169        /* Dangeling key value is true?. */
170        CFDictionarySetValue(q, key, kCFBooleanTrue);
171        CFReleaseNull(key);
172    }
173    CFRelease(str);
174    CFRelease(cs_key);
175    CFRelease(cs_value);
176    return error == false;
177}
178
179SecAccessControlRef
180keychain_query_parse_sac(CFStringRef s) {
181    SecAccessControlRef sac;
182    CFArrayRef tokens = CFStringCreateArrayBySeparatingStrings(NULL, s, CFSTR(";"));
183
184    // process protection part
185    CFStringRef protection = CFArrayGetValueAtIndex(tokens, 0);
186
187    CFErrorRef error = NULL;
188    sac = SecAccessControlCreateWithFlags(NULL, protection, 0, &error);
189    if(error != NULL)
190    {
191        return NULL;
192    }
193
194    CFIndex tokensCnt = CFArrayGetCount(tokens);
195    CFArrayRef params = NULL;
196    CFArrayRef constraints = NULL;
197    CFArrayRef pair = NULL;
198    CFStringRef constraintDetails = NULL;
199    CFStringRef constraintType = NULL;
200    bool paramError = false;
201
202    for(CFIndex i = 1; i < tokensCnt; ++i)         // process all constraints
203    {
204        SecAccessConstraintRef constr = NULL;
205
206        pair = CFStringCreateArrayBySeparatingStrings(NULL, CFArrayGetValueAtIndex(tokens, i), CFSTR(":"));
207        if(CFArrayGetCount(pair) != 2)
208        {
209            paramError = true;
210            goto paramErr;
211        }
212        CFStringRef operationName = CFArrayGetValueAtIndex(pair, 0);
213        CFStringRef strConstraint = CFArrayGetValueAtIndex(pair, 1);
214
215        if (CFStringHasSuffix(strConstraint, CFSTR(")"))) {
216            CFStringRef tmp;
217            tmp = CFStringCreateWithSubstring(NULL, strConstraint, CFRangeMake(0, CFStringGetLength(strConstraint) - 1));
218            constraints = CFStringCreateArrayBySeparatingStrings(NULL, tmp, CFSTR("("));
219            CFReleaseSafe(tmp);
220
221            if ( CFArrayGetCount(constraints) != 2) {
222                paramError = true;
223                goto paramErr;
224            }
225
226            constraintType = CFArrayGetValueAtIndex(constraints, 0);
227            constraintDetails = CFArrayGetValueAtIndex(constraints, 1);
228
229            if (CFStringCompare(constraintType, CFSTR("policy"), kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
230                constr = SecAccessConstraintCreatePolicy(constraintDetails, &error);
231            }
232
233
234        /* NOT SUPPORTED YET
235        else if(CFStringCompare(constraintType, CFSTR("passcode"), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
236        {
237            bool system;
238            if(CFStringCompare(constraintDetails, CFSTR("yes"), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
239                system = true;
240            else if(CFStringCompare(constraintDetails, CFSTR("no"), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
241                system = false;
242            else
243            {
244                printf("Wrong system parameter for passcode policy: [%s]\n", CFStringGetCStringPtr(constraintDetails, kCFStringEncodingUTF8));
245                paramError = true;
246                goto paramErr;
247            }
248            constr = SecAccessConstraintCreatePasscode(system);
249        }
250        else if(CFStringCompare(constraintType, CFSTR("bio"), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
251        {
252            constr = SecAccessConstraintCreateTouchID(CFStringCreateExternalRepresentation(NULL, constraintDetails, kCFStringEncodingASCII, 32), &error);
253        }
254        else if(CFStringCompare(constraintType, CFSTR("kofn"), kCFCompareCaseInsensitive) == kCFCompareEqualTo)
255        {
256            CFIndex kofnParamCount = 0;
257            bool foundBracket = false;
258            for(CFIndex j = i + 1; j < tokensCnt; ++j)
259            {
260                ++kofnParamCount;
261                if(CFStringHasSuffix(CFArrayGetValueAtIndex(tokens, j), CFSTR(")")))
262                {
263                    foundBracket = true;
264                    break;
265                }
266            }
267            if(!foundBracket || kofnParamCount < 2)
268            {
269                printf("Invalid syntax for kofn params\n");
270                paramError = true;
271                goto paramErr;
272            }
273            // process params
274            size_t kofnRequired = CFStringGetIntValue(constraintDetails);
275            CFMutableArrayRef kofnParams = CFArrayCreateMutable(NULL, kofnRequired, NULL);
276            CFArrayAppendArray(kofnParams, tokens, CFRangeMake(i + 1, kofnParamCount)); // first param of kofn is number of required methods
277            // remove ")" for the last method
278            CFStringRef tmp = CFStringCreateWithSubstring(NULL, CFArrayGetValueAtIndex(kofnParams, kofnParamCount - 1),
279                                                          CFRangeMake(0, CFStringGetLength(CFArrayGetValueAtIndex(kofnParams, kofnParamCount - 1)) - 1));
280            CFArraySetValueAtIndex(kofnParams, kofnParamCount -1, tmp);
281            i += kofnParamCount;
282
283            // TODO: add this as soon as kOfn starts to work
284                constr = SecAccessConstraintCreateKofN(kofnRequired, kofnParams, &error);
285                printf("Created KOFN constraint required %zu\n", kofnRequired);
286            CFReleaseNull(kofnParams);
287            CFReleaseNull(tmp);
288
289        }
290        NOT YET IMPLEMENTED */
291        else {
292            paramError = true;
293            goto paramErr;
294        }
295
296        } else if (CFStringCompare(strConstraint, CFSTR("true"), kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
297            constr = kCFBooleanTrue;
298
299        } else if (CFStringCompare(strConstraint, CFSTR("false"), kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
300            constr = kCFBooleanFalse;
301        }
302
303
304        if (error || constr == NULL) {
305            paramError = true;
306            goto paramErr;
307        }
308
309        SecAccessControlAddConstraintForOperation(sac, operationName, constr, &error);
310        if (error) {
311            paramError = true;
312            goto paramErr;
313        }
314    paramErr:
315        CFReleaseNull(pair);
316        CFReleaseNull(params);
317        CFReleaseNull(constraints);
318        CFReleaseNull(constr);
319
320        if (paramError) {
321            break;
322        }
323    }
324
325    CFReleaseSafe(tokens);
326
327    SecAccessConstraintRef constraintForDelete = SecAccessControlGetConstraint(sac, kAKSKeyOpDelete);
328    if (!constraintForDelete) {
329        if(!SecAccessControlAddConstraintForOperation(sac, kAKSKeyOpDelete, kCFBooleanTrue, &error)) {
330            fprintf(stderr, "adding delete operation to sac object failed \n");
331        }
332    }
333
334    if (paramError) {
335        fprintf(stderr, "Error constructing SecAccessConstraint object\n");
336        CFReleaseSafe(sac);
337        return NULL;
338    }
339
340    return sac;
341}
342