VALSecureEnclaveValet.m 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  1. //
  2. // VALSecureEnclaveValet.m
  3. // Valet
  4. //
  5. // Created by Dan Federman on 3/16/15.
  6. // Copyright 2015 Square, Inc.
  7. //
  8. // Licensed under the Apache License, Version 2.0 (the "License");
  9. // you may not use this file except in compliance with the License.
  10. // You may obtain a copy of the License at
  11. //
  12. //    http://www.apache.org/licenses/LICENSE-2.0
  13. //
  14. // Unless required by applicable law or agreed to in writing, software
  15. // distributed under the License is distributed on an "AS IS" BASIS,
  16. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17. // See the License for the specific language governing permissions and
  18. // limitations under the License.
  19. //
  20. #import "VALSecureEnclaveValet.h"
  21. #import "VALSecureEnclaveValet_Protected.h"
  22. #import "VALValet_Protected.h"
  23. #import "ValetDefines.h"
  24. /// Compiler flag for building against an SDK where VALAccessControlTouchIDAnyFingerprint and VALAccessControlTouchIDCurrentFingerprintSet are available.
  25. #define VAL_ACCESS_CONTROL_TOUCH_ID_SDK_AVAILABLE ((TARGET_OS_IPHONE && __IPHONE_9_0) || (TARGET_OS_MAC && __MAC_10_12))
  26. /// Compiler flag for building against an SDK where VALAccessControlDevicePasscode is available.
  27. #define VAL_ACCESS_CONTROL_DEVICE_PASSCODE_SDK_AVAILABLE ((TARGET_OS_IPHONE && __IPHONE_9_0) || (TARGET_OS_MAC && __MAC_10_11))
  28. NSString *__nonnull VALStringForAccessControl(VALAccessControl accessControl)
  29. {
  30. switch (accessControl) {
  31. case VALAccessControlUserPresence:
  32. return @"AccessControlUserPresence";
  33. case VALAccessControlTouchIDAnyFingerprint:
  34. return @"AccessControlTouchIDAnyFingerprint";
  35. case VALAccessControlTouchIDCurrentFingerprintSet:
  36. return @"AccessControlTouchIDCurrentFingerprintSet";
  37. case VALAccessControlDevicePasscode:
  38. return @"AccessControlDevicePasscode";
  39. }
  40. return @"AccessControlInvalid";
  41. }
  42. #if VAL_SECURE_ENCLAVE_SDK_AVAILABLE
  43. @implementation VALSecureEnclaveValet
  44. @synthesize baseQuery = _baseQuery;
  45. #pragma mark - Class Methods
  46. + (BOOL)supportsSecureEnclaveKeychainItems;
  47. {
  48. #pragma clang diagnostic push
  49. #pragma clang diagnostic ignored "-Wtautological-compare"
  50. return (&kSecAttrAccessControl != NULL && &kSecUseOperationPrompt != NULL);
  51. #pragma clang diagnostic pop
  52. }
  53. #pragma mark - Private Class Methods
  54. + (BOOL)_macOSElCapitanOrLater;
  55. {
  56. #if TARGET_OS_MAC && __MAC_10_11
  57. #pragma clang diagnostic push
  58. #pragma clang diagnostic ignored "-Wtautological-compare"
  59. return (&kSecUseAuthenticationUI != NULL);
  60. #pragma clang diagnostic pop
  61. #else
  62. return NO;
  63. #endif
  64. }
  65. + (BOOL)_macOSSierraOrLater;
  66. {
  67. #if TARGET_OS_MAC && __MAC_10_12
  68. #pragma clang diagnostic push
  69. #pragma clang diagnostic ignored "-Wtautological-compare"
  70. return (&kSecAttrTokenIDSecureEnclave != NULL);
  71. #pragma clang diagnostic pop
  72. #else
  73. return NO;
  74. #endif
  75. }
  76. + (BOOL)_iOS8OrLater;
  77. {
  78. #if TARGET_OS_IPHONE
  79. #pragma clang diagnostic push
  80. #pragma clang diagnostic ignored "-Wtautological-compare"
  81. return (&kSecUseOperationPrompt != NULL);
  82. #pragma clang diagnostic pop
  83. #else
  84. return NO;
  85. #endif
  86. }
  87. + (BOOL)_iOS9OrLater;
  88. {
  89. #if TARGET_OS_IPHONE && __IPHONE_9_0
  90. #pragma clang diagnostic push
  91. #pragma clang diagnostic ignored "-Wtautological-compare"
  92. return (&kSecUseAuthenticationUI != NULL);
  93. #pragma clang diagnostic pop
  94. #else
  95. return NO;
  96. #endif
  97. }
  98. + (BOOL)_currentOSSupportedForAccessControl:(VALAccessControl)accessControl;
  99. {
  100. switch (accessControl) {
  101. case VALAccessControlUserPresence:
  102. return ([self _iOS8OrLater] || [self _macOSElCapitanOrLater]);
  103. case VALAccessControlTouchIDAnyFingerprint:
  104. case VALAccessControlTouchIDCurrentFingerprintSet:
  105. return [self _iOS9OrLater] || [self _macOSSierraOrLater];
  106. case VALAccessControlDevicePasscode:
  107. return ([self _iOS9OrLater] || [self _macOSElCapitanOrLater]);
  108. }
  109. return NO;
  110. }
  111. + (void)_augmentBaseQuery:(nonnull NSMutableDictionary *)mutableBaseQuery accessControl:(VALAccessControl)accessControl;
  112. {
  113. // Add the access control, which opts us in to Secure Element storage.
  114. [mutableBaseQuery addEntriesFromDictionary:@{ (__bridge id)kSecAttrAccessControl : (__bridge_transfer id)SecAccessControlCreateWithFlags(NULL, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, ({
  115. SecAccessControlCreateFlags accessControlFlag = 0;
  116. switch (accessControl) {
  117. case VALAccessControlUserPresence:
  118. accessControlFlag = kSecAccessControlUserPresence;
  119. break;
  120. #if VAL_ACCESS_CONTROL_TOUCH_ID_SDK_AVAILABLE
  121. case VALAccessControlTouchIDAnyFingerprint:
  122. accessControlFlag = kSecAccessControlTouchIDAny;
  123. break;
  124. case VALAccessControlTouchIDCurrentFingerprintSet:
  125. accessControlFlag = kSecAccessControlTouchIDCurrentSet;
  126. break;
  127. #else
  128. case VALAccessControlTouchIDAnyFingerprint:
  129. case VALAccessControlTouchIDCurrentFingerprintSet:
  130. // This SDK does not support these access controls. But on this SDK we'll never reach this line, so just fake it.
  131. break;
  132. #endif
  133. #if VAL_ACCESS_CONTROL_DEVICE_PASSCODE_SDK_AVAILABLE
  134. case VALAccessControlDevicePasscode:
  135. accessControlFlag = kSecAccessControlDevicePasscode;
  136. break;
  137. #else
  138. case VALAccessControlDevicePasscode:
  139. // This SDK does not support these access controls. But on this SDK we'll never reach this line, so just fake it.
  140. break;
  141. #endif
  142. }
  143. accessControlFlag;
  144. }), NULL) }];
  145. // kSecAttrAccessControl and kSecAttrAccessible are mutually exclusive, so remove kSecAttrAccessible from our query.
  146. [mutableBaseQuery removeObjectForKey:(__bridge id)kSecAttrAccessible];
  147. NSString *const service = mutableBaseQuery[(__bridge id)kSecAttrService];
  148. NSString *const accessControlServiceSuffix = ({
  149. NSString *accessControlServiceSuffix = @"";
  150. switch (accessControl) {
  151. case VALAccessControlUserPresence:
  152. /*
  153. VALSecureEnclaveValet v1.0-v2.0.7 used UserPresence without a suffix – the concept of a customizable AccessControl was added in v2.1.
  154. For backwards compatibility, do not append an access control suffix for UserPresence.
  155. */
  156. break;
  157. case VALAccessControlTouchIDAnyFingerprint:
  158. case VALAccessControlTouchIDCurrentFingerprintSet:
  159. case VALAccessControlDevicePasscode:
  160. accessControlServiceSuffix = [@"_" stringByAppendingString:VALStringForAccessControl(accessControl)];
  161. break;
  162. }
  163. accessControlServiceSuffix;
  164. });
  165. if (service.length > 0 && accessControlServiceSuffix.length > 0) {
  166. // Ensure that our service identifier includes our access control suffix.
  167. mutableBaseQuery[(__bridge id)kSecAttrService] = [service stringByAppendingString:accessControlServiceSuffix];
  168. }
  169. }
  170. #pragma mark - Initialization
  171. - (nullable instancetype)initWithIdentifier:(nonnull NSString *)identifier accessControl:(VALAccessControl)accessControl;
  172. {
  173. VALCheckCondition([[self class] supportsSecureEnclaveKeychainItems], nil, @"This device does not support storing data on the secure enclave.");
  174. VALCheckCondition([[self class] _currentOSSupportedForAccessControl:accessControl], nil, @"This device does not support %@", VALStringForAccessControl(accessControl));
  175. VALAccessibility const accessibility = VALAccessibilityWhenPasscodeSetThisDeviceOnly;
  176. self = [super initWithIdentifier:identifier accessibility:accessibility];
  177. SEL const backwardsCompatibleInitializer = @selector(initWithIdentifier:accessibility:);
  178. NSMutableDictionary *const baseQuery = [[self class] mutableBaseQueryWithIdentifier:identifier
  179. accessibility:accessibility
  180. initializer:backwardsCompatibleInitializer];
  181. [[self class] _augmentBaseQuery:baseQuery
  182. accessControl:accessControl];
  183. _baseQuery = baseQuery;
  184. _accessControl = accessControl;
  185. return [[self class] sharedValetForValet:self];
  186. }
  187. - (nullable instancetype)initWithSharedAccessGroupIdentifier:(nonnull NSString *)sharedAccessGroupIdentifier accessControl:(VALAccessControl)accessControl
  188. {
  189. VALCheckCondition([[self class] supportsSecureEnclaveKeychainItems], nil, @"This device does not support storing data on the secure enclave.");
  190. VALCheckCondition([[self class] _currentOSSupportedForAccessControl:accessControl], nil, @"This device does not support %@", VALStringForAccessControl(accessControl));
  191. VALAccessibility const accessibility = VALAccessibilityWhenPasscodeSetThisDeviceOnly;
  192. self = [super initWithSharedAccessGroupIdentifier:sharedAccessGroupIdentifier accessibility:accessibility];
  193. SEL const backwardsCompatibleInitializer = @selector(initWithSharedAccessGroupIdentifier:accessibility:);
  194. NSMutableDictionary *const baseQuery = [[self class] mutableBaseQueryWithSharedAccessGroupIdentifier:sharedAccessGroupIdentifier
  195. accessibility:accessibility
  196. initializer:backwardsCompatibleInitializer];
  197. [[self class] _augmentBaseQuery:baseQuery
  198. accessControl:accessControl];
  199. _baseQuery = baseQuery;
  200. _accessControl = accessControl;
  201. return [[self class] sharedValetForValet:self];
  202. }
  203. #pragma mark - VALValet
  204. - (BOOL)canAccessKeychain;
  205. {
  206. // To avoid prompting the user for Touch ID or passcode, create a VALValet with our identifier and accessibility and ask it if it can access the keychain.
  207. VALValet *noPromptValet = nil;
  208. if ([self isSharedAcrossApplications]) {
  209. noPromptValet = [[VALValet alloc] initWithSharedAccessGroupIdentifier:self.identifier accessibility:self.accessibility];
  210. } else {
  211. noPromptValet = [[VALValet alloc] initWithIdentifier:self.identifier accessibility:self.accessibility];
  212. }
  213. return [noPromptValet canAccessKeychain];
  214. }
  215. - (BOOL)containsObjectForKey:(nonnull NSString *)key;
  216. {
  217. OSStatus const status = [self containsObjectForKey:key options:nil];
  218. BOOL const keyAlreadyInKeychain = (status == errSecInteractionNotAllowed || status == errSecSuccess);
  219. return keyAlreadyInKeychain;
  220. }
  221. - (nonnull NSSet *)allKeys;
  222. {
  223. VALCheckCondition(NO, [NSSet new], @"%s is not supported on %@", __PRETTY_FUNCTION__, NSStringFromClass([self class]));
  224. }
  225. - (BOOL)removeAllObjects;
  226. {
  227. VALCheckCondition(NO, NO, @"%s is not supported on %@", __PRETTY_FUNCTION__, NSStringFromClass([self class]));
  228. }
  229. - (nullable NSError *)migrateObjectsMatchingQuery:(nonnull NSDictionary *)secItemQuery removeOnCompletion:(BOOL)remove;
  230. {
  231. if ([[self class] supportsSecureEnclaveKeychainItems]) {
  232. VALCheckCondition(secItemQuery[(__bridge id)kSecUseOperationPrompt] == nil, [NSError errorWithDomain:VALMigrationErrorDomain code:VALMigrationErrorInvalidQuery userInfo:nil], @"kSecUseOperationPrompt is not supported in a migration query. Keychain items can not be migrated en masse from the Secure Enclave.");
  233. }
  234. return [super migrateObjectsMatchingQuery:secItemQuery removeOnCompletion:remove];
  235. }
  236. #pragma mark - Public Methods
  237. - (nullable NSData *)objectForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt;
  238. {
  239. return [self objectForKey:key userPrompt:userPrompt userCancelled:NULL];
  240. }
  241. - (nullable NSData *)objectForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt userCancelled:(nullable inout BOOL *)userCancelled;
  242. {
  243. return [self objectForKey:key userPrompt:userPrompt userCancelled:userCancelled options:nil];
  244. }
  245. - (nullable NSString *)stringForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt;
  246. {
  247. return [self stringForKey:key userPrompt:userPrompt userCancelled:NULL];
  248. }
  249. - (nullable NSString *)stringForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt userCancelled:(nullable inout BOOL *)userCancelled;
  250. {
  251. return [self stringForKey:key userPrompt:userPrompt userCancelled:userCancelled options:nil];
  252. }
  253. #pragma mark - VALValet Protected Methods
  254. - (BOOL)setObject:(nonnull NSData *)value forKey:(nonnull NSString *)key options:(nullable NSDictionary *)options;
  255. {
  256. // Remove the key before trying to set it. This will prevent us from calling SecItemUpdate on an item stored on the Secure Enclave, which would cause iOS to prompt the user for authentication.
  257. [self removeObjectForKey:key];
  258. return [super setObject:value forKey:key options:options];
  259. }
  260. - (OSStatus)containsObjectForKey:(nonnull NSString *)key options:(nullable NSDictionary *)options;
  261. {
  262. NSDictionary *baseOptions = nil;
  263. // iOS 9 and macOS 10.11 use kSecUseAuthenticationUI, not kSecUseNoAuthenticationUI.
  264. #if ((TARGET_OS_IPHONE && __IPHONE_9_0) || (TARGET_OS_MAC && __MAC_10_11))
  265. if ([[self class] _iOS9OrLater] || [[self class] _macOSElCapitanOrLater]) {
  266. baseOptions = @{ (__bridge id)kSecUseAuthenticationUI : (__bridge id)kSecUseAuthenticationUIFail };
  267. } else {
  268. #pragma GCC diagnostic push
  269. #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
  270. // kSecUseNoAuthenticationUI is deprecated in the iOS 9 SDK, but we still need it on iOS 8.
  271. #if (TARGET_OS_IPHONE && __IPHONE_9_0)
  272. options = @{ (__bridge id)kSecUseNoAuthenticationUI : @YES };
  273. #endif
  274. #pragma GCC diagnostic pop
  275. }
  276. #else
  277. options = @{ (__bridge id)kSecUseNoAuthenticationUI : @YES };
  278. #endif
  279. NSMutableDictionary *const allOptions = [baseOptions mutableCopy];
  280. [allOptions addEntriesFromDictionary:options];
  281. return [super containsObjectForKey:key options:allOptions];
  282. }
  283. #pragma mark - VALSecureEnclaveValet Protected Methods
  284. - (nullable NSData *)objectForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt userCancelled:(nullable inout BOOL *)userCancelled options:(nullable NSDictionary *)options;
  285. {
  286. OSStatus status = errSecSuccess;
  287. NSMutableDictionary *const allOptions = [[self _optionsDictionaryForUserPrompt:userPrompt] mutableCopy];
  288. if (options.count > 0) {
  289. [allOptions addEntriesFromDictionary:options];
  290. }
  291. NSData *const objectForKey = [self objectForKey:key options:allOptions status:&status];
  292. if (userCancelled != NULL) {
  293. *userCancelled = (status == errSecUserCanceled || status == errSecAuthFailed);
  294. }
  295. return objectForKey;
  296. }
  297. - (nullable NSString *)stringForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt userCancelled:(nullable inout BOOL *)userCancelled options:(nullable NSDictionary *)options;
  298. {
  299. OSStatus status = errSecSuccess;
  300. NSMutableDictionary *const allOptions = [[self _optionsDictionaryForUserPrompt:userPrompt] mutableCopy];
  301. if (options.count > 0) {
  302. [allOptions addEntriesFromDictionary:options];
  303. }
  304. NSString *const stringForKey = [self stringForKey:key options:allOptions status:&status];
  305. if (userCancelled != NULL) {
  306. *userCancelled = (status == errSecUserCanceled || status == errSecAuthFailed);
  307. }
  308. return stringForKey;
  309. }
  310. #pragma mark - Private Methods
  311. - (nullable NSDictionary *)_optionsDictionaryForUserPrompt:(nullable NSString *)userPrompt;
  312. {
  313. if (userPrompt.length == 0) {
  314. return nil;
  315. } else {
  316. return @{ (__bridge id)kSecUseOperationPrompt : userPrompt };
  317. }
  318. }
  319. @end
  320. #pragma mark - Deprecated Category
  321. @implementation VALSecureEnclaveValet (Deprecated)
  322. #pragma mark - Deprecated Initializers
  323. - (nullable instancetype)initWithIdentifier:(nonnull NSString *)identifier;
  324. {
  325. return [self initWithIdentifier:identifier accessibility:VALAccessibilityWhenPasscodeSetThisDeviceOnly];
  326. }
  327. - (nullable instancetype)initWithIdentifier:(nonnull NSString *)identifier accessibility:(VALAccessibility)accessibility;
  328. {
  329. VALCheckCondition(accessibility == VALAccessibilityWhenPasscodeSetThisDeviceOnly, nil, @"Accessibility on SecureEnclaveValet must be VALAccessibilityWhenPasscodeSetThisDeviceOnly");
  330. return [self initWithIdentifier:identifier accessControl:VALAccessControlUserPresence];
  331. }
  332. - (nullable instancetype)initWithSharedAccessGroupIdentifier:(nonnull NSString *)sharedAccessGroupIdentifier;
  333. {
  334. return [self initWithSharedAccessGroupIdentifier:sharedAccessGroupIdentifier accessibility:VALAccessibilityWhenPasscodeSetThisDeviceOnly];
  335. }
  336. - (nullable instancetype)initWithSharedAccessGroupIdentifier:(nonnull NSString *)sharedAccessGroupIdentifier accessibility:(VALAccessibility)accessibility;
  337. {
  338. VALCheckCondition(accessibility == VALAccessibilityWhenPasscodeSetThisDeviceOnly, nil, @"Accessibility on SecureEnclaveValet must be VALAccessibilityWhenPasscodeSetThisDeviceOnly");
  339. return [self initWithSharedAccessGroupIdentifier:sharedAccessGroupIdentifier accessControl:VALAccessControlUserPresence];
  340. }
  341. @end
  342. #else // Below this line we're in !VAL_SECURE_ENCLAVE_SDK_AVAILABLE, meaning none of our API is actually usable. Return NO or nil everywhere.
  343. @implementation VALSecureEnclaveValet
  344. + (BOOL)supportsSecureEnclaveKeychainItems;
  345. {
  346. VALCheckCondition(NO, NO, @"VALSecureEnclaveValet unsupported on this SDK");
  347. }
  348. - (nullable instancetype)initWithIdentifier:(nonnull NSString *)identifier accessControl:(VALAccessControl)accessControl;
  349. {
  350. VALCheckCondition(NO, nil, @"VALSecureEnclaveValet unsupported on this SDK");
  351. }
  352. - (nullable instancetype)initWithSharedAccessGroupIdentifier:(nonnull NSString *)sharedAccessGroupIdentifier accessControl:(VALAccessControl)accessControl;
  353. {
  354. VALCheckCondition(NO, nil, @"VALSecureEnclaveValet unsupported on this SDK");
  355. }
  356. - (nullable NSData *)objectForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt;
  357. {
  358. VALCheckCondition(NO, nil, @"VALSecureEnclaveValet unsupported on this SDK");
  359. }
  360. - (nullable NSData *)objectForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt userCancelled:(nullable inout BOOL *)userCancelled;
  361. {
  362. VALCheckCondition(NO, nil, @"VALSecureEnclaveValet unsupported on this SDK");
  363. }
  364. - (nullable NSString *)stringForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt;
  365. {
  366. VALCheckCondition(NO, nil, @"VALSecureEnclaveValet unsupported on this SDK");
  367. }
  368. - (nullable NSString *)stringForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt userCancelled:(nullable inout BOOL *)userCancelled;
  369. {
  370. VALCheckCondition(NO, nil, @"VALSecureEnclaveValet unsupported on this SDK");
  371. }
  372. - (nullable NSData *)objectForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt userCancelled:(nullable inout BOOL *)userCancelled options:(nullable NSDictionary *)options;
  373. {
  374. VALCheckCondition(NO, nil, @"VALSecureEnclaveValet unsupported on this SDK");
  375. }
  376. - (nullable NSString *)stringForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt userCancelled:(nullable inout BOOL *)userCancelled options:(nullable NSDictionary *)options;
  377. {
  378. VALCheckCondition(NO, nil, @"VALSecureEnclaveValet unsupported on this SDK");
  379. }
  380. @end
  381. @implementation VALSecureEnclaveValet (Deprecated)
  382. - (nullable instancetype)initWithIdentifier:(nonnull NSString *)identifier;
  383. {
  384. VALCheckCondition(NO, nil, @"VALSecureEnclaveValet unsupported on this SDK");
  385. }
  386. - (nullable instancetype)initWithIdentifier:(nonnull NSString *)identifier accessibility:(VALAccessibility)accessibility;
  387. {
  388. VALCheckCondition(NO, nil, @"VALSecureEnclaveValet unsupported on this SDK");
  389. }
  390. - (nullable instancetype)initWithSharedAccessGroupIdentifier:(nonnull NSString *)sharedAccessGroupIdentifier;
  391. {
  392. VALCheckCondition(NO, nil, @"VALSecureEnclaveValet unsupported on this SDK");
  393. }
  394. - (nullable instancetype)initWithSharedAccessGroupIdentifier:(nonnull NSString *)sharedAccessGroupIdentifier accessibility:(VALAccessibility)accessibility;
  395. {
  396. VALCheckCondition(NO, nil, @"VALSecureEnclaveValet unsupported on this SDK");
  397. }
  398. @end
  399. #endif // VAL_SECURE_ENCLAVE_SDK_AVAILABLE