web-dev-qa-db-fra.com

Services de porte-clés iOS: seules des valeurs spécifiques autorisées pour la clé kSecAttrGeneric?

J'essaie d'utiliser la classe KeychainWrapper fournie dans ce Apple exemple de code: https://developer.Apple.com/library/content/samplecode/GenericKeychain/

Dans l'exemple d'application, la classe a cette méthode init qui commence comme suit:

- (id)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *) accessGroup;
{
    if (self = [super init])
    {
        // Begin Keychain search setup. The genericPasswordQuery leverages the special user
        // defined attribute kSecAttrGeneric to distinguish itself between other generic Keychain
        // items which may be included by the same application.
        genericPasswordQuery = [[NSMutableDictionary alloc] init];

        [genericPasswordQuery setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
        [genericPasswordQuery setObject:identifier forKey:(id)kSecAttrGeneric];

Dans l'exemple d'application, il utilise deux valeurs pour la chaîne d'identifiant. "Mot de passe" et "Numéro de compte". Lors de l'implémentation de la classe dans mon code, j'ai utilisé des identifiants personnalisés et le code n'a pas fonctionné. L'appel à SecItemAdd () a échoué. Après quelques tests, il semble que l'utilisation de valeurs autres que "Mot de passe" et "Numéro de compte" pour l'identifiant ne fonctionne pas.

Est-ce que quelqu'un sait quelles valeurs sont autorisées et/ou s'il est possible d'avoir des identifiants personnalisés pour vos éléments de trousseau?

38
Simon Goldeen

D'accord, j'ai trouvé la solution dans cet article de blog Élément en double de trousseau lors de l'ajout de mot de passe

Pour résumer, le problème est que l'exemple d'application GenericKeychain utilise la valeur stockée dans la clé kSecAttrGeneric comme identifiant pour l'élément de trousseau alors qu'en fait ce n'est pas ce que l'API utilise pour déterminer un élément de trousseau unique. Les clés que vous devez définir avec des valeurs uniques sont la clé kSecAttrAccount et/ou la clé kSecAttrService.

Vous pouvez réécrire l'initiateur de KeychainItemWrapper afin que vous n'ayez pas besoin de changer tout autre code en changeant ces lignes:

Changement:

[genericPasswordQuery setObject:identifier forKey:(id)kSecAttrGeneric];

à:

[genericPasswordQuery setObject:identifier forKey:(id)kSecAttrAccount];

et changer:

[keychainItemData setObject:identifier forKey:(id)kSecAttrGeneric];

à:

[keychainItemData setObject:identifier forKey:(id)kSecAttrAccount];

Ou, vous pouvez faire ce que j'ai fait et écrire un nouvel initiateur qui prend les deux clés d'identification:

Edit: Pour les personnes utilisant ARC (vous devriez l'être de nos jours), vérifiez réponse de nycynik pour toute la notation de pontage correcte

- (id)initWithAccount:(NSString *)account service:(NSString *)service accessGroup:(NSString *) accessGroup;
{
    if (self = [super init])
    {
        NSAssert(account != nil || service != nil, @"Both account and service are nil.  Must specifiy at least one.");
        // Begin Keychain search setup. The genericPasswordQuery the attributes kSecAttrAccount and 
       // kSecAttrService are used as unique identifiers differentiating keychain items from one another
        genericPasswordQuery = [[NSMutableDictionary alloc] init];

        [genericPasswordQuery setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];

        [genericPasswordQuery setObject:account forKey:(id)kSecAttrAccount];
        [genericPasswordQuery setObject:service forKey:(id)kSecAttrService];

        // The keychain access group attribute determines if this item can be shared
        // amongst multiple apps whose code signing entitlements contain the same keychain access group.
        if (accessGroup != nil)
        {
#if TARGET_IPHONE_SIMULATOR
            // Ignore the access group if running on the iPhone simulator.
            // 
            // Apps that are built for the simulator aren't signed, so there's no keychain access group
            // for the simulator to check. This means that all apps can see all keychain items when run
            // on the simulator.
            //
            // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
            // simulator will return -25243 (errSecNoAccessForItem).
#else            
            [genericPasswordQuery setObject:accessGroup forKey:(id)kSecAttrAccessGroup];
#endif
        }

        // Use the proper search constants, return only the attributes of the first match.
        [genericPasswordQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
        [genericPasswordQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes];

        NSDictionary *tempQuery = [NSDictionary dictionaryWithDictionary:genericPasswordQuery];

        NSMutableDictionary *outDictionary = nil;

        if (! SecItemCopyMatching((CFDictionaryRef)tempQuery, (CFTypeRef *)&outDictionary) == noErr)
        {
            // Stick these default values into keychain item if nothing found.
            [self resetKeychainItem];

            //Adding the account and service identifiers to the keychain
            [keychainItemData setObject:account forKey:(id)kSecAttrAccount];
            [keychainItemData setObject:service forKey:(id)kSecAttrService];

            if (accessGroup != nil)
            {
#if TARGET_IPHONE_SIMULATOR
                // Ignore the access group if running on the iPhone simulator.
                // 
                // Apps that are built for the simulator aren't signed, so there's no keychain access group
                // for the simulator to check. This means that all apps can see all keychain items when run
                // on the simulator.
                //
                // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
                // simulator will return -25243 (errSecNoAccessForItem).
#else            
                [keychainItemData setObject:accessGroup forKey:(id)kSecAttrAccessGroup];
#endif
            }
        }
        else
        {
            // load the saved data from Keychain.
            self.keychainItemData = [self secItemFormatToDictionary:outDictionary];
        }

        [outDictionary release];
    }

    return self;
}

J'espère que cela aide quelqu'un d'autre!

61
Simon Goldeen

Comme ci-dessus, mais cela fonctionne pour ARC. Merci Simon

- (id)initWithAccount:(NSString *)account service:(NSString *)service accessGroup:(NSString *) accessGroup;

{
    if (self = [super init])
    {
        NSAssert(account != nil || service != nil, @"Both account and service are nil.  Must specifiy at least one.");
        // Begin Keychain search setup. The genericPasswordQuery the attributes kSecAttrAccount and
        // kSecAttrService are used as unique identifiers differentiating keychain items from one another
        genericPasswordQuery = [[NSMutableDictionary alloc] init];

        [genericPasswordQuery setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];

        [genericPasswordQuery setObject:account forKey:(__bridge id)kSecAttrAccount];
        [genericPasswordQuery setObject:service forKey:(__bridge id)kSecAttrService];

        // The keychain access group attribute determines if this item can be shared
        // amongst multiple apps whose code signing entitlements contain the same keychain access group.
        if (accessGroup != nil)
        {
#if TARGET_IPHONE_SIMULATOR
            // Ignore the access group if running on the iPhone simulator.
            //
            // Apps that are built for the simulator aren't signed, so there's no keychain access group
            // for the simulator to check. This means that all apps can see all keychain items when run
            // on the simulator.
            //
            // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
            // simulator will return -25243 (errSecNoAccessForItem).
#else
            [genericPasswordQuery setObject:accessGroup forKey:(__bridge id)kSecAttrAccessGroup];
#endif
        }

        // Use the proper search constants, return only the attributes of the first match.
        [genericPasswordQuery setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
        [genericPasswordQuery setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnAttributes];

        NSDictionary *tempQuery = [NSDictionary dictionaryWithDictionary:genericPasswordQuery];

        CFMutableDictionaryRef outDictionary = NULL;

        if (! SecItemCopyMatching((__bridge CFDictionaryRef)tempQuery, (CFTypeRef *)&outDictionary) == noErr)
        {
            // Stick these default values into keychain item if nothing found.
            [self resetKeychainItem];

            //Adding the account and service identifiers to the keychain
            [keychainItemData setObject:account forKey:(__bridge id)kSecAttrAccount];
            [keychainItemData setObject:service forKey:(__bridge id)kSecAttrService];

            if (accessGroup != nil)
            {
#if TARGET_IPHONE_SIMULATOR
                // Ignore the access group if running on the iPhone simulator.
                //
                // Apps that are built for the simulator aren't signed, so there's no keychain access group
                // for the simulator to check. This means that all apps can see all keychain items when run
                // on the simulator.
                //
                // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
                // simulator will return -25243 (errSecNoAccessForItem).
#else
                [keychainItemData setObject:accessGroup forKey:(__bridge id)kSecAttrAccessGroup];
#endif
            }
        }
        else
        {
            // load the saved data from Keychain.
            keychainItemData = [self secItemFormatToDictionary:(__bridge NSDictionary *)outDictionary];
        }

        if(outDictionary) CFRelease(outDictionary);
    }

    return self;
}
10
nycynik

Simon a presque résolu mon problème car après avoir changé le KeychainItemWrapper.m, j'ai eu des problèmes pour obtenir et définir des données vers et depuis le trousseau. Donc, après avoir ajouté cela à KeychainItemWrapper.m, je l'ai utilisé pour obtenir et stocker des éléments:

KeychainItemWrapper *keychainItem = [[KeychainItemWrapper alloc] initWithAccount:@"Identfier" service:@"AppName" accessGroup:nil];
[keychainItem setObject:@"some value" forKey:(__bridge id)kSecAttrGeneric];
NSString *value = [keychainItem objectForKey: (__bridge id)kSecAttrGeneric];

Parce que [keychainItem objectForKey: (__bridge id)kSecAttrService] retourne le compte (dans cet exemple @"Identifier") Ce qui est logique mais il m'a fallu un certain temps avant de réaliser que je devais utiliser kSecAttrGeneric pour récupérer les données du wrapper.

1
Zenuka