2 * Copyright (c) 2020 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
24 #import <Security/Security.h>
25 #import <Security/SecItemPriv.h>
26 #include <Security/SecEntitlements.h>
27 #include <ipc/server_security_helpers.h>
29 #import "KeychainXCTest.h"
32 @interface KeychainAppClipTests : KeychainXCTest
35 @implementation KeychainAppClipTests {
36 // App Clips are only permitted to store items with agrp == appID, so we set and track it
37 NSString* _applicationIdentifier;
46 SecSecurityClientAppClipToRegular();
47 _applicationIdentifier = @"com.apple.security.appcliptests";
48 SecSecurityClientSetApplicationIdentifier((__bridge CFStringRef)_applicationIdentifier);
52 SecSecurityClientAppClipToRegular();
53 SecSecurityClientSetApplicationIdentifier(NULL);
57 # pragma mark - Test App Clip API Restrictions (SecItemAdd)
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],
68 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, NULL), errSecSuccess);
69 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecSuccess);
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],
82 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, NULL), errSecRestrictedAPI);
83 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecItemNotFound);
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],
95 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, NULL), errSecRestrictedAPI);
97 SecSecurityClientAppClipToRegular();
98 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecItemNotFound);
101 # pragma mark - Test App Clip API Restrictions (SecItemUpdate)
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],
112 SecItemAdd((__bridge CFDictionaryRef)query, NULL);
114 NSDictionary* update = @{
115 (id)kSecValueData : [@"different" dataUsingEncoding:NSUTF8StringEncoding],
116 (id)kSecAttrService : @"DifferentAppClipTestService",
119 XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)update), errSecSuccess);
120 query[(id)kSecAttrService] = @"DifferentAppClipTestService";
121 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecSuccess);
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],
133 SecItemAdd((__bridge CFDictionaryRef)query, NULL);
135 NSDictionary* update = @{
136 (id)kSecValueData : [@"different" dataUsingEncoding:NSUTF8StringEncoding],
137 (id)kSecAttrSynchronizable : @YES,
140 XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)update), errSecRestrictedAPI);
141 query[(id)kSecAttrSynchronizable] = @YES;
142 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecItemNotFound);
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],
154 SecItemAdd((__bridge CFDictionaryRef)query, NULL);
156 NSDictionary* update = @{
157 (id)kSecValueData : [@"different" dataUsingEncoding:NSUTF8StringEncoding],
158 (id)kSecAttrAccessGroup : @"someotheraccessgroup",
161 XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)update), errSecMissingEntitlement);
162 query[(id)kSecAttrAccessGroup] = @"someotheraccessgroup";
163 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecMissingEntitlement);
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)
169 - (void)testAppClipCopyMatchingNoAgrpsAllowed {
170 SecSecurityClientRegularToAppClip();
172 NSDictionary* query = @{
173 (id)kSecClass : (id)kSecClassGenericPassword,
174 (id)kSecAttrService : @"AppClipTestService",
175 (id)kSecUseDataProtectionKeychain : @YES,
176 (id)kSecReturnAttributes : @YES,
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);
184 # pragma mark - Test App Clip API Restrictions (SecItemDelete)
186 - (void)testAppClipDeleteNoAgrpsAllowed {
187 NSDictionary* query = @{
188 (id)kSecClass : (id)kSecClassGenericPassword,
189 (id)kSecAttrService : @"AppClipTestService",
190 (id)kSecUseDataProtectionKeychain : @YES,
193 XCTAssertEqual(SecItemDelete((__bridge CFDictionaryRef)query), errSecItemNotFound);
194 SecSecurityClientRegularToAppClip();
195 XCTAssertEqual(SecItemDelete((__bridge CFDictionaryRef)query), errSecRestrictedAPI);
198 # pragma mark - Test App Clip API Restrictions (Misc)
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);
206 - (void)testAppClipCanPassAppIDAccessGroup {
207 SecSecurityClientRegularToAppClip();
208 [self setEntitlements:@{@"com.apple.application-identifier" : _applicationIdentifier} validated:YES];
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],
219 NSDictionary* update = @{
220 (id)kSecValueData : [@"betterpassword" dataUsingEncoding:NSUTF8StringEncoding],
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);
231 # pragma mark - Test Item Deletion SPI
233 - (void)testDeletionSPINoEntitlement {
234 XCTAssertEqual(SecItemDeleteKeychainItemsForAppClip((__bridge CFStringRef)@"nonexistent"), errSecMissingEntitlement);
237 - (void)testDeletionSPINoItems {
238 SecAddLocalSecuritydXPCFakeEntitlement(kSecEntitlementPrivateAppClipDeletion, kCFBooleanTrue);
239 XCTAssertEqual(SecItemDeleteKeychainItemsForAppClip((__bridge CFStringRef)@"nonexistent"), errSecSuccess);
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);
248 NSDictionary* query = @{
249 (id)kSecClass : (id)kSecClassGenericPassword,
250 (id)kSecUseDataProtectionKeychain : @YES,
251 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding],
252 (id)kSecReturnAttributes : @YES,
255 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, NULL), errSecSuccess);
256 XCTAssertEqual(SecItemDeleteKeychainItemsForAppClip((__bridge CFStringRef)_applicationIdentifier), errSecSuccess);
257 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecItemNotFound);
260 - (void)testDeletionSPILeaveRegularItemsAlone {
261 SecAddLocalSecuritydXPCFakeEntitlement(kSecEntitlementPrivateAppClipDeletion, kCFBooleanTrue);
262 NSString* agrp = @"com.apple.keychain.test.notanappclip";
263 [self setEntitlements:@{ @"keychain-access-groups": @[agrp] } validated:YES];
265 NSDictionary* query = @{
266 (id)kSecClass : (id)kSecClassGenericPassword,
267 (id)kSecUseDataProtectionKeychain : @YES,
268 (id)kSecAttrAccessGroup : agrp,
269 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding]
272 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, NULL), errSecSuccess);
273 XCTAssertEqual(SecItemDeleteKeychainItemsForAppClip((__bridge CFStringRef)agrp), errSecSuccess);
274 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecSuccess);
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);
283 NSDictionary* query = @{
284 (id)kSecClass : (id)kSecClassGenericPassword,
285 (id)kSecUseDataProtectionKeychain : @YES,
286 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding],
287 (id)kSecReturnAttributes : @YES,
289 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, NULL), errSecSuccess);
291 [self setEntitlements:@{@"com.apple.application-identifier" : otherAppID} validated:YES];
292 SecSecurityClientSetApplicationIdentifier((__bridge CFStringRef)otherAppID);
293 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, NULL), errSecSuccess);
295 XCTAssertEqual(SecItemDeleteKeychainItemsForAppClip((__bridge CFStringRef)_applicationIdentifier), errSecSuccess);
296 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecSuccess);
298 [self setEntitlements:@{@"com.apple.application-identifier" : _applicationIdentifier} validated:YES];
299 SecSecurityClientSetApplicationIdentifier((__bridge CFStringRef)_applicationIdentifier);
300 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecItemNotFound);