]> git.saurik.com Git - apple/security.git/blob - secdxctests/KeychainEntitlementsTest.m
Security-59754.80.3.tar.gz
[apple/security.git] / secdxctests / KeychainEntitlementsTest.m
1 /*
2 * Copyright (c) 2018 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 #import <os/feature_private.h>
27
28 #import "KeychainXCTest.h"
29
30 #if USE_KEYSTORE
31 @interface KeychainEntitlementsTest : KeychainXCTest
32 @end
33
34 @implementation KeychainEntitlementsTest
35
36 - (void)testNoEntitlements {
37 NSDictionary *params = @{ (id)kSecUseDataProtectionKeychain: @YES,
38 (id)kSecClass: (id)kSecClassGenericPassword, (id)kSecAttrLabel: @"label", };
39
40 // Application with no keychain-related entitlements at all, but CopyMatching must work in order to support
41 // backward compatibility with smart-card-enabled macos applications (com.apple.token AG is added automatically in this case).
42 [self setEntitlements:@{} validated:false];
43 if (os_feature_enabled(CryptoTokenKit, UseTokens)) {
44 #if TARGET_OS_OSX
45 XCTAssertEqual(SecItemCopyMatching((CFDictionaryRef)params, NULL), errSecItemNotFound);
46 #else
47 // On non-macOS targets, token items must be explicitly enabled, and that requires entitlements.
48 // But since this test has no entitlements, it will always fail with errSecMissingEntitlement.
49 XCTAssertEqual(SecItemCopyMatching((CFDictionaryRef)params, NULL), errSecMissingEntitlement);
50 #endif
51 } else {
52 // If tokens are not enabled, this situation really means that there is an entitlement problem.
53 XCTAssertEqual(SecItemCopyMatching((CFDictionaryRef)params, NULL), errSecMissingEntitlement);
54 }
55
56 // However, write access is declined for such application.
57 XCTAssertEqual(SecItemAdd((CFDictionaryRef)params, NULL), errSecMissingEntitlement);
58 XCTAssertEqual(SecItemDelete((CFDictionaryRef)params), errSecMissingEntitlement);
59 }
60
61 #if TARGET_OS_OSX
62 - (void)testInvalidEntitlementsAppID {
63 NSDictionary *params;
64 params = @{ (id)kSecUseDataProtectionKeychain: @YES,
65 (id)kSecClass: (id)kSecClassGenericPassword,
66 (id)kSecAttrLabel: @"label", };
67
68 // Un-validated app-identifier entitlements must disallow any access to the keychain.
69 [self setEntitlements:@{ @"com.apple.application-identifier": @"com.apple.test-app-identifier" } validated:NO];
70 XCTAssertEqual(SecItemCopyMatching((CFDictionaryRef)params, NULL), errSecMissingEntitlement);
71 XCTAssertEqual(SecItemAdd((CFDictionaryRef)params, NULL), errSecMissingEntitlement);
72 XCTAssertEqual(SecItemDelete((CFDictionaryRef)params), errSecMissingEntitlement);
73
74 // However, keychain-access-groups entitlements should work even if not validated, AMFI will take care
75 // about cases when keychain-access-groups is not correctly used and we have to support cases when
76 // process contains application-groups entitlement but that entitlement is not present in provisioned profile, thus
77 // failing entitlement validation test.
78 [self setEntitlements:@{ @"keychain-access-groups": @[@"com.apple.test-app-identifier"] } validated:NO];
79 XCTAssertEqual(SecItemCopyMatching((CFDictionaryRef)params, NULL), errSecItemNotFound);
80 XCTAssertEqual(SecItemAdd((CFDictionaryRef)params, NULL), errSecSuccess);
81 XCTAssertEqual(SecItemDelete((CFDictionaryRef)params), errSecSuccess);
82 }
83 #endif // TARGET_OS_OSX
84
85 - (void)testValidEntitlementsAppID {
86 NSDictionary *params;
87 params = @{ (id)kSecUseDataProtectionKeychain: @YES,
88 (id)kSecClass: (id)kSecClassGenericPassword,
89 (id)kSecAttrLabel: @"label",
90 (id)kSecAttrAccessGroup: @"com.apple.test-app-identifier", };
91 #if TARGET_OS_OSX
92 [self setEntitlements:@{ @"com.apple.application-identifier": @"com.apple.test-app-identifier" } validated:YES];
93 #else
94 [self setEntitlements:@{ @"application-identifier": @"com.apple.test-app-identifier" } validated:YES];
95 #endif
96 XCTAssertEqual(SecItemCopyMatching((CFDictionaryRef)params, NULL), errSecItemNotFound);
97 XCTAssertEqual(SecItemAdd((CFDictionaryRef)params, NULL), errSecSuccess);
98 XCTAssertEqual(SecItemDelete((CFDictionaryRef)params), errSecSuccess);
99 }
100
101 - (void)testEntitlementsAssociatedAppID {
102 NSMutableDictionary *params = [@{(id)kSecUseDataProtectionKeychain: @YES,
103 (id)kSecClass: (id)kSecClassGenericPassword,
104 (id)kSecAttrLabel: @"label" } mutableCopy];
105
106 [self setEntitlements:@{
107 #if TARGET_OS_OSX
108 @"com.apple.application-identifier": @"com.apple.test-app-identifier",
109 #else
110 @"application-identifier": @"com.apple.test-app-identifier",
111 #endif
112 @"com.apple.developer.associated-application-identifier": @[ @"com.apple.test-associated-app-identifier" ]
113 } validated:YES];
114
115 XCTAssertEqual(SecItemCopyMatching((CFDictionaryRef)params, NULL), errSecItemNotFound);
116
117 // The associated app identifier is preferred over the 'regular' app identifier (in practice this is only relevant on macOS)
118 XCTAssertEqual(SecItemAdd((CFDictionaryRef)params, NULL), errSecSuccess);
119 params[(id)kSecReturnAttributes] = @(YES);
120 CFTypeRef result = NULL;
121 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)params, &result), errSecSuccess);
122 XCTAssertTrue(CFEqual(CFDictionaryGetValue(result, kSecAttrAccessGroup), CFSTR("com.apple.test-associated-app-identifier")));
123 CFReleaseNull(result);
124 XCTAssertEqual(SecItemDelete((CFDictionaryRef)params), errSecSuccess);
125 }
126
127 - (void)testDisallowTokenGroupWrite {
128 NSDictionary *params;
129
130 // Explicit com.apple.token agrp is not acceptable for writing operations, but acceptable for reading.
131 params = @{ (id)kSecUseDataProtectionKeychain: @YES,
132 (id)kSecClass: (id)kSecClassGenericPassword,
133 (id)kSecAttrLabel: @"label",
134 (id)kSecAttrAccessGroup: (id)kSecAttrAccessGroupToken, };
135 [self setEntitlements:@{ @"keychain-access-groups": @[ (id)kSecAttrAccessGroupToken ] } validated:YES];
136 if (os_feature_enabled(CryptoTokenKit, UseTokens)) {
137 XCTAssertEqual(SecItemCopyMatching((CFDictionaryRef)params, NULL), errSecItemNotFound);
138 } else {
139 XCTAssertEqual(SecItemCopyMatching((CFDictionaryRef)params, NULL), errSecMissingEntitlement);
140 }
141 XCTAssertEqual(SecItemAdd((CFDictionaryRef)params, NULL), errSecMissingEntitlement);
142 XCTAssertEqual(SecItemDelete((CFDictionaryRef)params), errSecMissingEntitlement);
143 }
144
145 #if TARGET_OS_OSX
146 - (void)testInvalidAppGroups {
147 NSDictionary *params;
148 params = @{ (id)kSecUseDataProtectionKeychain: @YES,
149 (id)kSecClass: (id)kSecClassGenericPassword,
150 (id)kSecAttrLabel: @"label", };
151 [self setEntitlements:@{ @"com.apple.security.application-groups": @[@"com.apple.test-app-groups"] } validated:NO];
152
153 // Invalid access group entitlement should still allow querying com.apple.token, if tokens are enabled
154 if (os_feature_enabled(CryptoTokenKit, UseTokens)) {
155 XCTAssertEqual(SecItemCopyMatching((CFDictionaryRef)params, NULL), errSecItemNotFound);
156 } else {
157 XCTAssertEqual(SecItemCopyMatching((CFDictionaryRef)params, NULL), errSecMissingEntitlement);
158 }
159
160 // But write-access is forbidden,
161 XCTAssertEqual(SecItemAdd((CFDictionaryRef)params, NULL), errSecMissingEntitlement);
162 XCTAssertEqual(SecItemDelete((CFDictionaryRef)params), errSecMissingEntitlement);
163
164 // Similarly as explicitly referring to AG specified in unverified entitlements.
165 params = @{ (id)kSecUseDataProtectionKeychain: @YES,
166 (id)kSecClass: (id)kSecClassGenericPassword,
167 (id)kSecAttrLabel: @"label",
168 (id)kSecAttrAccessGroup: @"com.apple.test-app-groups", };
169 XCTAssertEqual(SecItemCopyMatching((CFDictionaryRef)params, NULL), errSecMissingEntitlement);
170 XCTAssertEqual(SecItemAdd((CFDictionaryRef)params, NULL), errSecMissingEntitlement);
171 XCTAssertEqual(SecItemDelete((CFDictionaryRef)params), errSecMissingEntitlement);
172
173 params = @{ (id)kSecUseDataProtectionKeychain: @YES,
174 (id)kSecClass: (id)kSecClassGenericPassword,
175 (id)kSecAttrLabel: @"label",
176 (id)kSecAttrAccessGroup: (id)kSecAttrAccessGroupToken, };
177 if (os_feature_enabled(CryptoTokenKit, UseTokens)) {
178 XCTAssertEqual(SecItemCopyMatching((CFDictionaryRef)params, NULL), errSecItemNotFound);
179 } else {
180 XCTAssertEqual(SecItemCopyMatching((CFDictionaryRef)params, NULL), errSecMissingEntitlement);
181 }
182 }
183 #endif // TARGET_OS_OSX
184
185 - (void)testTokenItemsGroup {
186 NSDictionary *params;
187
188 [self setEntitlements:@{
189 #if TARGET_OS_OSX
190 @"com.apple.application-identifier": @"com.apple.test-app-identifier",
191 #else
192 @"application-identifier": @"com.apple.test-app-identifier",
193 #endif
194 @"keychain-access-groups": @[ @"com.apple.token" ],
195 } validated:YES];
196
197 // Add token items for testing into the keychain.
198 NSArray *tokenItems = @[ @{
199 (id)kSecClass: (id)kSecClassGenericPassword,
200 (id)kSecAttrAccessGroup: (id)kSecAttrAccessGroupToken,
201 (id)kSecAttrLabel: @"label",
202 } ];
203 XCTAssertEqual(SecItemUpdateTokenItemsForAccessGroups(@"com.apple.testtoken", (__bridge CFArrayRef)@[(id)kSecAttrAccessGroupToken], (__bridge CFArrayRef)tokenItems), errSecSuccess);
204
205 // Query should find items, because we have token access group in entitlements.
206 params = @{ (id)kSecUseDataProtectionKeychain: @YES,
207 (id)kSecClass: (id)kSecClassGenericPassword,
208 (id)kSecAttrLabel: @"label", };
209 if (os_feature_enabled(CryptoTokenKit, UseTokens)) {
210 XCTAssertEqual(SecItemCopyMatching((CFDictionaryRef)params, NULL), errSecSuccess);
211 } else {
212 XCTAssertEqual(SecItemCopyMatching((CFDictionaryRef)params, NULL), errSecItemNotFound);
213 }
214
215 #if TARGET_OS_IPHONE
216 // Not having access group in entitlements will not find items.
217 [self setEntitlements:@{
218 @"application-identifier": @"com.apple.test-app-identifier",
219 } validated:YES];
220 XCTAssertEqual(SecItemCopyMatching((CFDictionaryRef)params, NULL), errSecItemNotFound);
221 #endif
222
223 // Delete all test token items.
224 [self setEntitlements:@{
225 #if TARGET_OS_OSX
226 @"com.apple.application-identifier": @"com.apple.test-app-identifier",
227 #else
228 @"application-identifier": @"com.apple.test-app-identifier",
229 #endif
230 @"keychain-access-groups": @[ @"com.apple.token" ],
231 } validated:YES];
232 SecItemUpdateTokenItemsForAccessGroups(@"com.apple.testtoken", (__bridge CFArrayRef)@[(id)kSecAttrAccessGroupToken], (__bridge CFArrayRef)@[]);
233 }
234
235 - (void)testEntitlementForExplicitAccessGroupLacking {
236 [self setEntitlements:@{@"com.apple.application-identifier": @"com.apple.test-app-identifier", @"application-identifier": @"com.apple.test-app-identifier"} validated:YES];
237
238 NSMutableDictionary* query = [@{(id)kSecClass : (id)kSecClassGenericPassword,
239 (id)kSecUseDataProtectionKeychain : @YES,
240 (id)kSecAttrAccount : @"TestAccount",
241 (id)kSecAttrLabel : @"TestLabel",
242 (id)kSecAttrAccessGroup : @"com.apple.test.myaccessgroup",
243 (id)kSecValueData : [@"passwd" dataUsingEncoding:NSUTF8StringEncoding],
244 } mutableCopy];
245 NSMutableDictionary* update = [@{(id)kSecAttrLabel : @"NewLabel"} mutableCopy];
246
247 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, NULL), errSecMissingEntitlement);
248 query[(id)kSecValueData] = nil;
249 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecMissingEntitlement);
250 XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)update), errSecMissingEntitlement);
251 XCTAssertEqual(SecItemDelete((__bridge CFDictionaryRef)query), errSecMissingEntitlement);
252
253 [self setEntitlements:@{@"com.apple.application-identifier": @"com.apple.test-app-identifier", @"application-identifier": @"com.apple.test-app-identifier", @"keychain-access-groups": @[@"com.apple.test.myaccessgroup"]} validated:YES];
254 query[(id)kSecValueData] = [@"secret" dataUsingEncoding:NSUTF8StringEncoding];
255 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, NULL), errSecSuccess);
256 query[(id)kSecValueData] = nil;
257
258 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecSuccess);
259 [self setEntitlements:@{@"com.apple.application-identifier": @"com.apple.test-app-identifier"} validated:YES];
260 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecMissingEntitlement);
261
262 query[(id)kSecAttrAccessGroup] = nil;
263 update[(id)kSecAttrAccessGroup] = @"com.apple.test.myotheraccessgroup";
264 XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)update), errSecMissingEntitlement);
265
266 query[(id)kSecAttrAccessGroup] = @"com.apple.test.myaccessgroup";
267 [self setEntitlements:@{@"com.apple.application-identifier": @"com.apple.test-app-identifier", @"application-identifier": @"com.apple.test-app-identifier", @"keychain-access-groups": @[@"com.apple.test.myaccessgroup"]} validated:YES];
268 XCTAssertEqual(SecItemDelete((__bridge CFDictionaryRef)query), errSecSuccess, @"keychain item not deleted after querying without entitlements");
269 }
270
271 - (void)testEntitlementForImplicitAccessGroupLacking {
272 NSMutableDictionary* query = [@{(id)kSecClass : (id)kSecClassGenericPassword,
273 (id)kSecUseDataProtectionKeychain : @YES,
274 (id)kSecAttrAccount : @"TestAccount",
275 (id)kSecAttrLabel : @"TestLabel",
276 (id)kSecAttrAccessGroup : @"com.apple.test.myaccessgroup",
277 (id)kSecValueData : [@"passwd" dataUsingEncoding:NSUTF8StringEncoding],
278 } mutableCopy];
279 NSDictionary* update = @{(id)kSecAttrLabel : @"NewLabel"};
280
281 // Have to use explicit access group here or we just get the app identifier
282 [self setEntitlements:@{@"com.apple.application-identifier": @"com.apple.test-app-identifier", @"application-identifier": @"com.apple.test-app-identifier", @"keychain-access-groups": @[@"com.apple.test.myaccessgroup"]} validated:YES];
283 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, NULL), errSecSuccess);
284
285 [self setEntitlements:@{@"com.apple.application-identifier": @"com.apple.test-app-identifier", @"application-identifier": @"com.apple.test-app-identifier"} validated:YES];
286 query[(id)kSecValueData] = nil;
287 query[(id)kSecAttrAccessGroup] = nil;
288
289 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecItemNotFound);
290 XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)update), errSecItemNotFound);
291 XCTAssertEqual(SecItemDelete((__bridge CFDictionaryRef)query), errSecItemNotFound);
292
293 [self setEntitlements:@{@"com.apple.application-identifier": @"com.apple.test-app-identifier", @"application-identifier": @"com.apple.test-app-identifier", @"keychain-access-groups": @[@"com.apple.test.myaccessgroup"]} validated:YES];
294 XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)update), errSecSuccess);
295 query[(id)kSecAttrLabel] = @"NewLabel";
296
297 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecSuccess);
298 XCTAssertEqual(SecItemDelete((__bridge CFDictionaryRef)query), errSecSuccess, @"keychain item not deleted after querying without entitlements");
299 }
300
301 @end
302
303 #endif // USE_KEYSTORE