Keychain access and MDM

I have a question about Keychain access and MDM management on iPad OS.

My iPad app save some information in Keychain with the sample code below. Information is correctly stored in Keychain on personal iPads. However, in the case of an iPad managed by MDM in the company, the information remains nil when read from keychain.

I found an explanation about MDM on the website below. "IPhone and iPad MDM functionality restrictions" https://support.apple.com/guide/mdm/restrictions-for-iphone-and-ipad-mdm0f7dd3d8/1/web/1.0

At the "iCloud Keychain" section in this explanation, you can read that "Keychain cannot be used on an iPad managed by MDM". Is this limitation the reason why information is not stored in Keychain? And, can this restriction be changed by modifying the settings on the MDM side?

Or, what kind of coding technics can be used on the app side? I want to save the information entered by the user and read that information even if the application is reinstalled. Even if the app is running under MDM environment.

My app is assumed to be used in the following situations.

  • IPad app used in the factory
  • IPad is MDM managed
  • Do not use Apple ID
  • Do not use a server to store information

Information is saved/read using saveObject:forKey and loadObjectForKey:

@implementation IOSKeyChain

+ (BOOL) checkOSStatus: (OSStatus) status {
return status == noErr;
}

+ (NSMutableDictionary *) keychainQueryForKey: (NSString *) key {
return [@ {(__ bridge id) kSecClass: (__ bridge id) kSecClassGenericPassword,
(__bridge id) kSecAttrService: key,
(__bridge id) kSecAttrAccount: key,
(__bridge id) kSecAttrAccessible: (__bridge id) kSecAttrAccessibleAfterFirstUnlock
} mutableCopy];
}

+ (BOOL) saveObject: (id) object for Key: (NSString *) key {
NSMutableDictionary * keychainQuery = [self keychainQueryForKey: key];
// Deleting previous object with this key, because SecItemUpdate is more complicated.
[self deleteObjectForKey: key];
.
[keychainQuery setObject: [NSKeyedArchiver archivedDataWithRootObject: object] forKey: (__ bridge id) kSecValueData];
return [self checkOSStatus: SecItemAdd ((_ _ bridge CFDictionaryRef) keychainQuery, NULL)];
}

+ (id) loadObjectForKey: (NSString *) key {
id object = nil;
.
NSMutableDictionary * keychainQuery = [self keychainQueryForKey: key];
.
[keychainQuery setObject: (__ bridge id) kCFBooleanTrue forKey: (__ bridge id) kSecReturnData];
[keychainQuery setObject: (__ bridge id) kSecMatchLimitOne for Key: (__ bridge id) kSecMatchLimit];
.
CFDataRef keyData = NULL;
.
if ([self checkOSStatus: SecItemCopyMatching ((__ bridge CFDictionaryRef) keychainQuery, (CFTypeRef *) & keyData)]) {
@try {
object = [NSKeyedUnarchiver unarchiveObjectWithData: (__ bridge NSData *) keyData];
}
@catch (NSException * exception) {
NSLog (@ "Unarchiving for key% @ failed with exception% @", key, exception.name);
object = nil;
}
@finally {}
}
.
if (keyData) {
CF Release (keyData);
}
.
return object;
}

+ (BOOL) deleteObjectForKey: (NSString *) key {
NSMutableDictionary * keychainQuery = [self keychainQueryForKey: key];
return [self checkOSStatus: SecItemDelete ((_ _ bridge CFDictionaryRef) keychainQuery)];
}

@end
code-block