/ 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