IOS 8 (OS X Yosemite) a introduit une nouvelle API/constante utilisée pour détecter si l'appareil d'un utilisateur possède un code d'accès.
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
peut être utilisé pour détecter si un code d'accès est défini sur l'appareil.
Le flux est :
- Tentative d'enregistrement d'un nouvel élément du trousseau avec cet attribut.
- S'il réussit, cela indique qu'un code d'accès est actuellement activé.
- Si le mot de passe n'est pas enregistré, cela signifie qu'il n'y a pas de code d'accès.
- Nettoyer l'élément, car s'il est déjà sur le trousseau, l'ajout échouera, comme si le code d'accès n'était pas défini.
Je l'ai testé sur mon iPhone 5S, et j'ai d'abord obtenu le résultat suivant true
puis j'ai désactivé le code d'accès dans les paramètres, et ça m'est revenu. false
. Finalement, j'ai réactivé le code d'accès et il retourne true
. Les versions antérieures du système d'exploitation renverront false
. Le code fonctionne dans le simulateur, renvoyant true
sur une machine avec un mot de passe OS X (je n'ai pas testé d'autres scénarios OS X).
Voir également l'exemple de projet ici : https://github.com/project-imas/passcode-check/pull/5
Enfin, à ma connaissance, iOS 8 ne dispose pas d'un paramètre permettant de désactiver la protection des données. Je suppose donc que c'est tout ce dont vous avez besoin pour garantir le cryptage.
BOOL isAPIAvailable = (&kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly != NULL);
// Not available prior to iOS 8 - safe to return false rather than crashing
if(isAPIAvailable) {
// From http://pastebin.com/T9YwEjnL
NSData* secret = [@"Device has passcode set?" dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *attributes = @{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: @"LocalDeviceServices",
(__bridge id)kSecAttrAccount: @"NoAccount",
(__bridge id)kSecValueData: secret,
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
};
// Original code claimed to check if the item was already on the keychain
// but in reality you can't add duplicates so this will fail with errSecDuplicateItem
// if the item is already on the keychain (which could throw off our check if
// kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly was not set)
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)attributes, NULL);
if (status == errSecSuccess) { // item added okay, passcode has been set
NSDictionary *query = @{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: @"LocalDeviceServices",
(__bridge id)kSecAttrAccount: @"NoAccount"
};
status = SecItemDelete((__bridge CFDictionaryRef)query);
return true;
}
// errSecDecode seems to be the error thrown on a device with no passcode set
if (status == errSecDecode) {
return false;
}
}
return false;
P.S. Comme Apple le souligne dans la vidéo WWDC de présentation (711 Keychain et authentification avec Touch ID), ils ont choisi de ne pas rendre le statut du code de passe directement disponible via l'API à dessein, afin d'empêcher les applications de se retrouver dans des situations où elles ne devraient pas l'être (c'est-à-dire "Est-ce que cet appareil a un code de passe ? Ok, super, je vais stocker cette information privée en texte clair". Il serait beaucoup plus judicieux de créer une clé de chiffrement, de la stocker sous la rubrique kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
et crypter ce fichier, qui sera irrécupérable si un utilisateur décide de désactiver son code d'accès).