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