secitemcanarytest.m
1 #include <Foundation/Foundation.h> 2 #include <Security/Security.h> 3 #include <Security/SecItemPriv.h> 4 #include <err.h> 5 6 static void usage(void) __dead2; 7 static bool create_item(NSString *acct); 8 static bool verify_item(NSString *acct, bool deleteit); 9 static void initial_state(void) __dead2; 10 static uint64_t update_state(void); 11 static void reset(void) __dead2; 12 13 static NSString *kCanaryAccessGroup = @"com.apple.security.test.canary"; 14 static NSString *kCanaryStateAccount = @"com.apple.security.test.canaryState"; 15 16 int 17 main(int argc, char *argv[]) 18 { 19 int ch; 20 uint64_t iter; 21 bool success; 22 23 iter = 0; 24 while ((ch = getopt(argc, argv, "ir")) != -1) { 25 switch (ch) { 26 case 'i': 27 initial_state(); 28 /*notreached*/ 29 case 'r': 30 reset(); 31 /*notreached*/ 32 default: 33 usage(); 34 /*notreached*/ 35 } 36 } 37 38 iter = update_state(); 39 fprintf(stderr, "iter = %llu\n\n", iter); 40 41 @autoreleasepool { 42 if (iter > 0) { 43 printf("[TEST] Verify and delete previous canary item\n"); 44 success = verify_item([NSString stringWithFormat:@"canary%llu", iter - 1], true); 45 printf("[%s]\n", success ? "PASS" : "FAIL"); 46 fprintf(stderr, "\n"); 47 } 48 49 printf("[TEST] Create canary item\n"); 50 success = create_item([NSString stringWithFormat:@"canary%llu", iter]); 51 printf("[%s]\n", success ? "PASS" : "FAIL"); 52 } 53 } 54 55 static void 56 usage(void) 57 { 58 59 fprintf(stderr, "usage: secitemcanarytest -i Generate initial state\n" 60 " secitemcanarytest Normal operation\n" 61 " secitemcanarytest -r Reset everything\n"); 62 exit(1); 63 } 64 65 static bool 66 create_item(NSString *acct) 67 { 68 OSStatus status; 69 NSDictionary *attrs; 70 int nerrors = 0; 71 72 attrs = @{ 73 (id)kSecClass : (id)kSecClassGenericPassword, 74 (id)kSecAttrLabel : @"secitemcanarytest-oneItem", 75 (id)kSecAttrAccount : acct, 76 (id)kSecAttrAccessGroup : kCanaryAccessGroup, 77 (id)kSecAttrAccessible : (id)kSecAttrAccessibleAfterFirstUnlock, 78 (id)kSecUseDataProtectionKeychain : (id)kCFBooleanTrue, 79 (id)kSecValueData : [NSData dataWithBytes:"password" length: 8], 80 }; 81 status = SecItemAdd((__bridge CFDictionaryRef)attrs, NULL); 82 if (status != 0) { 83 nerrors++; 84 fprintf(stderr, "SecItemAdd(%s): %d\n", acct.UTF8String, status); 85 } else { 86 printf("created: %s\n", acct.UTF8String); 87 } 88 89 if (!verify_item(acct, false)) { 90 nerrors++; 91 } 92 93 return (nerrors == 0); 94 } 95 96 static bool 97 verify_item(NSString *acct, bool deleteit) 98 { 99 OSStatus status; 100 NSDictionary *query; 101 CFTypeRef result; 102 int nerrors = 0; 103 104 query = @{ 105 (id)kSecClass : (id)kSecClassGenericPassword, 106 (id)kSecAttrAccessGroup : kCanaryAccessGroup, 107 (id)kSecAttrAccount : acct, 108 (id)kSecReturnAttributes : @YES, 109 (id)kSecMatchLimit : (id)kSecMatchLimitAll, 110 (id)kSecUseDataProtectionKeychain : (id)kCFBooleanTrue, 111 }; 112 result = NULL; 113 status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result); 114 if (status != 0) { 115 nerrors++; 116 fprintf(stderr, "SecItemCopyMatching(%s): %d\n", acct.UTF8String, status); 117 } else { 118 if (CFGetTypeID(result) != CFArrayGetTypeID()) { 119 nerrors++; 120 fprintf(stderr, "SecItemCopyMatching(%s): not array\n", acct.UTF8String); 121 } else if (CFArrayGetCount(result) != 1) { 122 nerrors++; 123 fprintf(stderr, "SecItemCopyMatching(%s): incorrect number of results\n", acct.UTF8String); 124 } else { 125 printf("verified: %s\n", acct.UTF8String); 126 } 127 CFRelease(result); 128 } 129 130 if (deleteit) { 131 status = SecItemDelete((__bridge CFDictionaryRef)query); 132 if (status != 0) { 133 nerrors++; 134 fprintf(stderr, "SecItemDelete(%s): %d\n", acct.UTF8String, status); 135 } else { 136 printf("deleted: %s\n", acct.UTF8String); 137 } 138 } 139 140 return (nerrors == 0); 141 } 142 143 static void 144 initial_state(void) 145 { 146 OSStatus status; 147 uint64_t state; 148 NSDictionary *attrs; 149 150 state = 0; 151 attrs = @{ 152 (id)kSecClass : (id)kSecClassGenericPassword, 153 (id)kSecAttrLabel : @"canary test state", 154 (id)kSecAttrAccount : kCanaryStateAccount, 155 (id)kSecAttrAccessGroup : kCanaryAccessGroup, 156 (id)kSecAttrAccessible : (id)kSecAttrAccessibleAfterFirstUnlock, 157 (id)kSecUseDataProtectionKeychain : (id)kCFBooleanTrue, 158 (id)kSecValueData : [NSData dataWithBytes:&state length:sizeof(state)], 159 }; 160 status = SecItemAdd((__bridge CFDictionaryRef)attrs, NULL); 161 switch (status) { 162 case 0: 163 exit(0); 164 /*notreached*/ 165 default: 166 errx(1, "SecItemAdd: %d", status); 167 /*notreached*/ 168 } 169 } 170 171 static uint64_t 172 update_state(void) 173 { 174 OSStatus status; 175 NSMutableDictionary *query; 176 NSDictionary *update; 177 CFTypeRef result; 178 uint64_t state = 0, next_state; 179 180 query = [NSMutableDictionary dictionary]; 181 query[(id)kSecClass] = (id)kSecClassGenericPassword; 182 query[(id)kSecAttrAccessGroup] = kCanaryAccessGroup; 183 query[(id)kSecAttrAccount] = kCanaryStateAccount; 184 query[(id)kSecUseDataProtectionKeychain] = (id)kCFBooleanTrue; 185 query[(id)kSecReturnData] = @YES; 186 187 status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result); 188 switch (status) { 189 case 0: 190 if (result != NULL && CFGetTypeID(result) == CFDataGetTypeID() && CFDataGetLength(result) == sizeof(state)) { 191 memcpy(&state, CFDataGetBytePtr(result), sizeof(state)); 192 } else { 193 errx(1, "invalid state"); 194 /*notreached*/ 195 } 196 break; 197 default: 198 errx(1, "failed to retrieve state: SecItemCopyMatching(state): %d", status); 199 /*notreached*/ 200 } 201 202 next_state = state + 1; 203 204 query[(id)kSecReturnData] = nil; 205 update = @{ 206 (id)kSecValueData : [NSData dataWithBytes:&next_state length:sizeof(next_state)], 207 }; 208 status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)update); 209 210 if (status != 0) { 211 errx(1, "SecItemUpdate: %d", status); 212 } 213 214 return state; 215 } 216 217 static void 218 reset(void) 219 { 220 OSStatus status; 221 NSDictionary *query; 222 223 query = @{ 224 (id)kSecClass : (id)kSecClassGenericPassword, 225 (id)kSecAttrAccessGroup : kCanaryAccessGroup, 226 (id)kSecUseDataProtectionKeychain : (id)kCFBooleanTrue, 227 }; 228 status = SecItemDelete((__bridge CFDictionaryRef)query); 229 switch (status) { 230 case 0: 231 case errSecItemNotFound: 232 exit(0); 233 /*notreached*/ 234 default: 235 errx(1, "SecItemDelete: %d", status); 236 /*notreached*/ 237 } 238 }