39 votes

Comment puis-je savoir si l'utilisateur de l'iPhone a actuellement un code d'accès défini et le cryptage activé ?

J'écris une application iPhone dont les données doivent être cryptées. J'ai appris à activer le cryptage des fichiers en définissant l'attribut NSFileProtectionComplete. Je sais également comment vérifier la version de l'iPhone pour m'assurer qu'il fonctionne sous iOS 4.0 ou mieux.

J'ai toutefois réalisé que si l'utilisateur n'a pas choisi de code d'accès et n'a pas spécifiquement activé la protection des données sur l'écran Paramètres > Général > Verrouillage par passade, les données ne sont pas protégées du tout.

J'aimerais faire apparaître un avertissement et dire à l'utilisateur qu'il doit activer un code d'accès et activer la protection des données (ce qui nécessite une sauvegarde et une restauration sur les iPhones pré-4), puis quitter l'application si le code d'accès et la protection des données ne sont pas activés. Je n'arrive pas à trouver un moyen de connaître l'état de ces paramètres. Toutes les API que j'ai trouvées, comme "protectedDataAvailable" dans UIApplication, passent toutes avec succès si la protection des données est désactivée.

18voto

Heath Borders Points 8067

Avertissement : Cette réponse était valable jusqu'à ios 4.3.3

Si la protection des données est activée, un fichier nouvellement créé aura un numéro d'identification d'utilisateur. nil NSFileProtectionKey par défaut.

Si la protection des données est désactivée, un fichier nouvellement créé aura une valeur de NSFileProtectionNone NSFileProtectionKey par défaut.

Ainsi, vous pourriez détecter la présence d'une protection de fichier avec le code suivant :

NSString *tmpDirectoryPath = 
    [NSHomeDirectory() stringByAppendingPathComponent:@"tmp"];
NSString *testFilePath = 
    [tmpDirectoryPath stringByAppendingPathComponent:@"testFile"];
[@"" writeToFile:testFilePath 
      atomically:YES
        encoding:NSUTF8StringEncoding
           error:NULL]; // obviously, do better error handling
NSDictionary *testFileAttributes = 
    [[NSFileManager defaultManager] attributesOfItemAtPath:testFile1Path
                                                     error:NULL];
BOOL fileProtectionEnabled = 
    [NSFileProtectionNone isEqualToString:[testFile1Attributes objectForKey:NSFileProtectionKey]];

13voto

owenfi Points 527

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 :

  1. Tentative d'enregistrement d'un nouvel élément du trousseau avec cet attribut.
  2. S'il réussit, cela indique qu'un code d'accès est actuellement activé.
  3. Si le mot de passe n'est pas enregistré, cela signifie qu'il n'y a pas de code d'accès.
  4. 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).

3voto

Eric Points 998

Apple ne fournit pas de méthode permettant de déterminer si l'utilisateur a défini un code d'accès.

Si votre application doit être chiffrée, vous devez envisager de chiffrer et de déchiffrer les fichiers avec une implémentation de chiffrement fiable et de demander à l'utilisateur un code d'accès ou de stocker la clé dans le trousseau.

1voto

igraczech Points 11

Que ce soit NSDataWritingAtomic ou NSDataWritingFileProtectionComplete, le résultat est toujours le même pour moi. Comportement étrange, voici le code :

BOOL expandTilde = YES;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, expandTilde);
NSString *filePath;
filePath = [[paths lastObject] stringByAppendingPathComponent:@"passcode-check"];

NSMutableData *testData;
testData = [NSMutableData dataWithLength:1024];

NSLog(@"Attempt to write data of length %u file: %@", [testData length], filePath);

NSError *error = nil;

if (![testData writeToFile:filePath options:NSDataWritingAtomic error:&error]) {
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    return NO;
} else {
    NSLog(@"File write successful.");

    error = nil;
    NSDictionary *testFileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:&error];

    NSLog(@"Getting attributes: %@", testFileAttributes);

    if ([NSFileProtectionComplete isEqualToString:[testFileAttributes objectForKey:NSFileProtectionKey]]) {
        error = nil;
        [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];
        // passcode disabled
        return YES;
    } else {
        error = nil;
        [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];
        return NO;
    }

}

0voto

Vojta Points 457

Desde iOS 9 il y a un drapeau LAPolicyDeviceOwnerAuthentication en Authentification locale cadre.

+ (BOOL)isPasscodeEnabled
{
    NSError *error = nil;
    LAContext *context = [[LAContext alloc] init];

    BOOL passcodeEnabled = [context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&error];

    if(passcodeEnabled) {
        return YES;
    }

    return NO;
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X