/ secdxctests / KeychainBackupTests.m
KeychainBackupTests.m
  1  #import "KeychainXCTest.h"
  2  #import <Security/Security.h>
  3  #import <Security/SecItemPriv.h>
  4  #include <Security/SecEntitlements.h>
  5  #include <ipc/server_security_helpers.h>
  6  
  7  @interface KeychainBackupTests : KeychainXCTest
  8  @end
  9  
 10  
 11  @implementation KeychainBackupTests {
 12      NSString* _applicationIdentifier;
 13  }
 14  
 15  - (void)setUp {
 16      // Put setup code here. This method is called before the invocation of each test method in the class.
 17      [super setUp];
 18      _applicationIdentifier = @"com.apple.security.backuptests";
 19      SecSecurityClientSetApplicationIdentifier((__bridge CFStringRef)_applicationIdentifier);
 20  }
 21  
 22  - (void)tearDown {
 23      // Put teardown code here. This method is called after the invocation of each test method in the class.
 24  }
 25  
 26  # pragma mark - Test OTA Backups
 27  
 28  // Code lovingly adapted from si-33-keychain-backup
 29  #if USE_KEYSTORE
 30  - (NSData*)createKeybagWithType:(keybag_handle_t)bag_type password:(NSData*)password
 31  {
 32      keybag_handle_t handle = bad_keybag_handle;
 33      kern_return_t bag_created = aks_create_bag(password ? password.bytes : NULL, password ? (int)password.length : 0, bag_type, &handle);
 34      XCTAssertEqual(bag_created, kAKSReturnSuccess, @"Unable to create keybag");
 35  
 36      void *bag = NULL;
 37      int bagLen = 0;
 38      kern_return_t bag_saved = aks_save_bag(handle, &bag, &bagLen);
 39      XCTAssertEqual(bag_saved, kAKSReturnSuccess, @"Unable to save keybag");
 40  
 41      NSData* bagData = [NSData dataWithBytes:bag length:bagLen];
 42      XCTAssertNotNil(bagData, @"Unable to create NSData from bag bytes");
 43  
 44      return bagData;
 45  }
 46  #endif
 47  
 48  // All backup paths ultimately lead to SecServerCopyKeychainPlist which does the actual exporting,
 49  // so this test ought to suffice for all backup configurations
 50  - (void)testAppClipDoesNotBackup {
 51  
 52      // First add a "regular" item for each class, which we expect to be in the backup later
 53      NSMutableDictionary* query = [@{
 54          (id)kSecClass : (id)kSecClassGenericPassword,
 55          (id)kSecUseDataProtectionKeychain : @YES,
 56          (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding],
 57      } mutableCopy];
 58  
 59      XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, NULL), errSecSuccess);
 60      XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecSuccess);
 61  
 62      query[(id)kSecClass] = (id)kSecClassInternetPassword;
 63      XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, NULL), errSecSuccess);
 64      XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecSuccess);
 65  
 66      query[(id)kSecClass] = (id)kSecClassCertificate;
 67      XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, NULL), errSecSuccess);
 68      XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecSuccess);
 69  
 70      query[(id)kSecClass] = (id)kSecClassKey;
 71      XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, NULL), errSecSuccess);
 72      XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecSuccess);
 73  
 74      // Switch to being an app clip, add another item for each class, which we expect not to find in the backup
 75      SecSecurityClientRegularToAppClip();
 76      [self setEntitlements:@{@"com.apple.application-identifier" : _applicationIdentifier} validated:YES];
 77  
 78      query[(id)kSecClass] = (id)kSecClassGenericPassword;
 79      XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, NULL), errSecSuccess);
 80      XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecSuccess);
 81  
 82      query[(id)kSecClass] = (id)kSecClassInternetPassword;
 83      XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, NULL), errSecSuccess);
 84      XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecSuccess);
 85  
 86      query[(id)kSecClass] = (id)kSecClassCertificate;
 87      XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, NULL), errSecSuccess);
 88      XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecSuccess);
 89  
 90      query[(id)kSecClass] = (id)kSecClassKey;
 91      XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, NULL), errSecSuccess);
 92      XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecSuccess);
 93  
 94      SecSecurityClientAppClipToRegular();
 95      SecAddLocalSecuritydXPCFakeEntitlement(kSecEntitlementRestoreKeychain, kCFBooleanTrue);
 96  
 97      // Code lovingly adapted from si-33-keychain-backup
 98      NSData* keybag;
 99  #if USE_KEYSTORE
100      keybag = [self createKeybagWithType:kAppleKeyStoreBackupBag password:nil];
101  #else
102      keybag = [NSData new];
103  #endif
104  
105      NSData* data = CFBridgingRelease(_SecKeychainCopyBackup((__bridge CFDataRef)keybag, nil));
106  
107      XCTAssert(data);
108      XCTAssertGreaterThan([data length], 42, @"Got empty dictionary");
109      NSDictionary* keychain = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListImmutable format:nil error:nil];
110  
111      // Only one item should be here for each class, which is the regular one.
112      XCTAssertEqual([keychain[@"genp"] count], 1);
113      XCTAssertEqual([keychain[@"inet"] count], 1);
114      XCTAssertEqual([keychain[@"cert"] count], 1);
115      XCTAssertEqual([keychain[@"keys"] count], 1);
116  }
117  
118  @end