/ secdxctests / KeychainAppClipTests.m
KeychainAppClipTests.m
1 /* 2 * Copyright (c) 2020 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 #import <Security/Security.h> 25 #import <Security/SecItemPriv.h> 26 #include <Security/SecEntitlements.h> 27 #include <ipc/server_security_helpers.h> 28 29 #import "KeychainXCTest.h" 30 31 #if USE_KEYSTORE 32 @interface KeychainAppClipTests : KeychainXCTest 33 @end 34 35 @implementation KeychainAppClipTests { 36 // App Clips are only permitted to store items with agrp == appID, so we set and track it 37 NSString* _applicationIdentifier; 38 } 39 40 + (void)setUp { 41 [super setUp]; 42 } 43 44 - (void)setUp { 45 [super setUp]; 46 SecSecurityClientAppClipToRegular(); 47 _applicationIdentifier = @"com.apple.security.appcliptests"; 48 SecSecurityClientSetApplicationIdentifier((__bridge CFStringRef)_applicationIdentifier); 49 } 50 51 + (void)tearDown { 52 SecSecurityClientAppClipToRegular(); 53 SecSecurityClientSetApplicationIdentifier(NULL); 54 [super tearDown]; 55 } 56 57 # pragma mark - Test App Clip API Restrictions (SecItemAdd) 58 59 - (void)testAppclipCanAddItem { 60 SecSecurityClientRegularToAppClip(); 61 [self setEntitlements:@{@"com.apple.application-identifier" : _applicationIdentifier} validated:YES]; 62 NSDictionary* query = @{ 63 (id)kSecClass : (id)kSecClassGenericPassword, 64 (id)kSecAttrService : @"AppClipTestService", 65 (id)kSecUseDataProtectionKeychain : @YES, 66 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding], 67 }; 68 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, NULL), errSecSuccess); 69 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecSuccess); 70 } 71 72 - (void)testAppClipAddNoSyncAllowed { 73 SecSecurityClientRegularToAppClip(); 74 [self setEntitlements:@{@"com.apple.application-identifier" : _applicationIdentifier} validated:YES]; 75 NSDictionary* query = @{ 76 (id)kSecClass : (id)kSecClassGenericPassword, 77 (id)kSecAttrService : @"AppClipTestService", 78 (id)kSecUseDataProtectionKeychain : @YES, 79 (id)kSecAttrSynchronizable : @YES, 80 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding], 81 }; 82 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, NULL), errSecRestrictedAPI); 83 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecItemNotFound); 84 } 85 86 - (void)testAppClipAddNoAgrpAllowed { 87 SecSecurityClientRegularToAppClip(); 88 // By not explicitly setting entitlements we get the default set which is not permitted for an app clip 89 NSDictionary* query = @{ 90 (id)kSecClass : (id)kSecClassGenericPassword, 91 (id)kSecUseDataProtectionKeychain : @YES, 92 (id)kSecAttrService : @"AppClipTestService", 93 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding], 94 }; 95 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, NULL), errSecRestrictedAPI); 96 97 SecSecurityClientAppClipToRegular(); 98 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecItemNotFound); 99 } 100 101 # pragma mark - Test App Clip API Restrictions (SecItemUpdate) 102 103 - (void)testAppClipCanUpdateItem { 104 SecSecurityClientRegularToAppClip(); 105 [self setEntitlements:@{@"com.apple.application-identifier" : _applicationIdentifier} validated:YES]; 106 NSMutableDictionary* query = [@{ 107 (id)kSecClass : (id)kSecClassGenericPassword, 108 (id)kSecAttrService : @"AppClipTestService", 109 (id)kSecUseDataProtectionKeychain : @YES, 110 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding], 111 } mutableCopy]; 112 SecItemAdd((__bridge CFDictionaryRef)query, NULL); 113 114 NSDictionary* update = @{ 115 (id)kSecValueData : [@"different" dataUsingEncoding:NSUTF8StringEncoding], 116 (id)kSecAttrService : @"DifferentAppClipTestService", 117 }; 118 119 XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)update), errSecSuccess); 120 query[(id)kSecAttrService] = @"DifferentAppClipTestService"; 121 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecSuccess); 122 } 123 124 - (void)testAppClipUpdateNoSyncAllowed { 125 SecSecurityClientRegularToAppClip(); 126 [self setEntitlements:@{@"com.apple.application-identifier" : _applicationIdentifier} validated:YES]; 127 NSMutableDictionary* query = [@{ 128 (id)kSecClass : (id)kSecClassGenericPassword, 129 (id)kSecAttrService : @"AppClipTestService", 130 (id)kSecUseDataProtectionKeychain : @YES, 131 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding], 132 } mutableCopy]; 133 SecItemAdd((__bridge CFDictionaryRef)query, NULL); 134 135 NSDictionary* update = @{ 136 (id)kSecValueData : [@"different" dataUsingEncoding:NSUTF8StringEncoding], 137 (id)kSecAttrSynchronizable : @YES, 138 }; 139 140 XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)update), errSecRestrictedAPI); 141 query[(id)kSecAttrSynchronizable] = @YES; 142 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecItemNotFound); 143 } 144 145 - (void)testAppClipUpdateNoAgrpAllowed { 146 SecSecurityClientRegularToAppClip(); 147 [self setEntitlements:@{@"com.apple.application-identifier" : _applicationIdentifier} validated:YES]; 148 NSMutableDictionary* query = [@{ 149 (id)kSecClass : (id)kSecClassGenericPassword, 150 (id)kSecAttrService : @"AppClipTestService", 151 (id)kSecUseDataProtectionKeychain : @YES, 152 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding], 153 } mutableCopy]; 154 SecItemAdd((__bridge CFDictionaryRef)query, NULL); 155 156 NSDictionary* update = @{ 157 (id)kSecValueData : [@"different" dataUsingEncoding:NSUTF8StringEncoding], 158 (id)kSecAttrAccessGroup : @"someotheraccessgroup", 159 }; 160 161 XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)update), errSecMissingEntitlement); 162 query[(id)kSecAttrAccessGroup] = @"someotheraccessgroup"; 163 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecMissingEntitlement); 164 } 165 166 # pragma mark - Test App Clip API Restrictions (SecItemCopyMatching) 167 // For now SICM doesn't care about sync, because there shouldn't be any items where (clip == 1 && sync == 1) 168 169 - (void)testAppClipCopyMatchingNoAgrpsAllowed { 170 SecSecurityClientRegularToAppClip(); 171 172 NSDictionary* query = @{ 173 (id)kSecClass : (id)kSecClassGenericPassword, 174 (id)kSecAttrService : @"AppClipTestService", 175 (id)kSecUseDataProtectionKeychain : @YES, 176 (id)kSecReturnAttributes : @YES, 177 }; 178 179 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecRestrictedAPI); 180 [self setEntitlements:@{@"com.apple.application-identifier" : _applicationIdentifier} validated:YES]; 181 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecItemNotFound); 182 } 183 184 # pragma mark - Test App Clip API Restrictions (SecItemDelete) 185 186 - (void)testAppClipDeleteNoAgrpsAllowed { 187 NSDictionary* query = @{ 188 (id)kSecClass : (id)kSecClassGenericPassword, 189 (id)kSecAttrService : @"AppClipTestService", 190 (id)kSecUseDataProtectionKeychain : @YES, 191 }; 192 193 XCTAssertEqual(SecItemDelete((__bridge CFDictionaryRef)query), errSecItemNotFound); 194 SecSecurityClientRegularToAppClip(); 195 XCTAssertEqual(SecItemDelete((__bridge CFDictionaryRef)query), errSecRestrictedAPI); 196 } 197 198 # pragma mark - Test App Clip API Restrictions (Misc) 199 200 - (void)testAppClipNoSecItemUpdateTokenItemsAllowed { 201 SecSecurityClientRegularToAppClip(); 202 // Don't bother setting it up, app clips aren't even welcome at the door. 203 XCTAssertEqual(SecItemUpdateTokenItemsForAccessGroups(NULL, (__bridge CFArrayRef)@[(id)kSecAttrAccessGroupToken], NULL), errSecRestrictedAPI); 204 } 205 206 - (void)testAppClipCanPassAppIDAccessGroup { 207 SecSecurityClientRegularToAppClip(); 208 [self setEntitlements:@{@"com.apple.application-identifier" : _applicationIdentifier} validated:YES]; 209 210 NSMutableDictionary* query = [@{ 211 (id)kSecClass : (id)kSecClassGenericPassword, 212 (id)kSecAttrService : @"AppClipTestService", 213 (id)kSecUseDataProtectionKeychain : @YES, 214 (id)kSecReturnAttributes : @YES, 215 (id)kSecAttrAccessGroup : _applicationIdentifier, 216 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding], 217 } mutableCopy]; 218 219 NSDictionary* update = @{ 220 (id)kSecValueData : [@"betterpassword" dataUsingEncoding:NSUTF8StringEncoding], 221 }; 222 223 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, NULL), errSecSuccess); 224 query[(id)kSecValueData] = nil; 225 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecSuccess); 226 query[(id)kSecReturnAttributes] = nil; 227 XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)update), errSecSuccess); 228 XCTAssertEqual(SecItemDelete((__bridge CFDictionaryRef)query), errSecSuccess); 229 } 230 231 # pragma mark - Test Item Deletion SPI 232 233 - (void)testDeletionSPINoEntitlement { 234 XCTAssertEqual(SecItemDeleteKeychainItemsForAppClip((__bridge CFStringRef)@"nonexistent"), errSecMissingEntitlement); 235 } 236 237 - (void)testDeletionSPINoItems { 238 SecAddLocalSecuritydXPCFakeEntitlement(kSecEntitlementPrivateAppClipDeletion, kCFBooleanTrue); 239 XCTAssertEqual(SecItemDeleteKeychainItemsForAppClip((__bridge CFStringRef)@"nonexistent"), errSecSuccess); 240 } 241 242 - (void)testDeletionSPIDeleteAppClipItem { 243 // The SPI does not check if app clip, and the API does not check private entitlement 244 SecSecurityClientRegularToAppClip(); 245 [self setEntitlements:@{@"com.apple.application-identifier" : _applicationIdentifier} validated:YES]; 246 SecAddLocalSecuritydXPCFakeEntitlement(kSecEntitlementPrivateAppClipDeletion, kCFBooleanTrue); 247 248 NSDictionary* query = @{ 249 (id)kSecClass : (id)kSecClassGenericPassword, 250 (id)kSecUseDataProtectionKeychain : @YES, 251 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding], 252 (id)kSecReturnAttributes : @YES, 253 }; 254 255 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, NULL), errSecSuccess); 256 XCTAssertEqual(SecItemDeleteKeychainItemsForAppClip((__bridge CFStringRef)_applicationIdentifier), errSecSuccess); 257 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecItemNotFound); 258 } 259 260 - (void)testDeletionSPILeaveRegularItemsAlone { 261 SecAddLocalSecuritydXPCFakeEntitlement(kSecEntitlementPrivateAppClipDeletion, kCFBooleanTrue); 262 NSString* agrp = @"com.apple.keychain.test.notanappclip"; 263 [self setEntitlements:@{ @"keychain-access-groups": @[agrp] } validated:YES]; 264 265 NSDictionary* query = @{ 266 (id)kSecClass : (id)kSecClassGenericPassword, 267 (id)kSecUseDataProtectionKeychain : @YES, 268 (id)kSecAttrAccessGroup : agrp, 269 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding] 270 }; 271 272 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, NULL), errSecSuccess); 273 XCTAssertEqual(SecItemDeleteKeychainItemsForAppClip((__bridge CFStringRef)agrp), errSecSuccess); 274 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecSuccess); 275 } 276 277 - (void)testDeletionSPILeaveOtherAppClipItemsAlone { 278 SecSecurityClientRegularToAppClip(); 279 [self setEntitlements:@{@"com.apple.application-identifier" : _applicationIdentifier} validated:YES]; 280 NSString* otherAppID = @"not-the-same-application-identifier"; 281 SecAddLocalSecuritydXPCFakeEntitlement(kSecEntitlementPrivateAppClipDeletion, kCFBooleanTrue); 282 283 NSDictionary* query = @{ 284 (id)kSecClass : (id)kSecClassGenericPassword, 285 (id)kSecUseDataProtectionKeychain : @YES, 286 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding], 287 (id)kSecReturnAttributes : @YES, 288 }; 289 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, NULL), errSecSuccess); 290 291 [self setEntitlements:@{@"com.apple.application-identifier" : otherAppID} validated:YES]; 292 SecSecurityClientSetApplicationIdentifier((__bridge CFStringRef)otherAppID); 293 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, NULL), errSecSuccess); 294 295 XCTAssertEqual(SecItemDeleteKeychainItemsForAppClip((__bridge CFStringRef)_applicationIdentifier), errSecSuccess); 296 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecSuccess); 297 298 [self setEntitlements:@{@"com.apple.application-identifier" : _applicationIdentifier} validated:YES]; 299 SecSecurityClientSetApplicationIdentifier((__bridge CFStringRef)_applicationIdentifier); 300 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecItemNotFound); 301 } 302 303 @end 304 #endif