/ RegressionTests / secitemcanarytest / secitemcanarytest.m
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  }