2011-02-03 5 views
38

Sto cercando di utilizzare la classe KeychainWrapper fornite in questo codice di esempio di Apple: https://developer.apple.com/library/content/samplecode/GenericKeychain/iOS Keychain Services: solo valori specifici consentiti per kSecAttrGeneric Key?

Nell'applicazione di esempio, la classe ha questo metodo init che inizia come:

- (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]; 

Nell'applicazione campione, utilizza due valori per la stringa identificatore. "Password" e "Numero account". Quando ho implementato la classe nel mio codice, ho usato alcuni identificatori personalizzati e il codice non funzionava. La chiamata a SecItemAdd() non è riuscita. Dopo alcuni test, sembra che l'utilizzo di valori diversi da "Password" e "Numero account" per l'identificatore non funzioni.

Qualcuno sa quali valori sono consentiti e/o se è possibile avere identificatori personalizzati per gli elementi del portachiavi?

+0

domanda relativa: http: // stackoverflow.it/questions/11614047/what-makes-a-keychain-item-unique-in-ios –

+0

FWIW, ho archiviato un radar con Apple su questo problema con il loro codice di esempio. Vedi http://www.openradar.me/13472204 se lo vuoi ingannare. –

risposta

60

Va bene, ho trovato la soluzione in questo post del blog Keychain duplicate item when adding password

Per riassumere, il problema è che il campione app GenericKeychain utilizza il valore memorizzato nella chiave kSecAttrGeneric come l'identificatore per la voce di portachiavi quando in realtà che non è ciò che l'API usa per determinare un oggetto portachiavi unico. Le chiavi che è necessario impostare con valori univoci sono la chiave kSecAttrAccount e/o la chiave kSecAttrService.

È possibile riscrivere l'initilizer di KeychainItemWrapper quindi non c'è bisogno di cambiare qualsiasi altro codice, modificando queste righe:

Cambio:

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

a:

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

e cambio:

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

a:

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

Oppure, si potrebbe fare quello che ho fatto e scrivere un nuovo initilizer che prende entrambe le chiavi che identificano:

Edit: Per le persone che utilizzano ARC (si dovrebbe essere al giorno d'oggi) controllare nycynik's answer per tutte le annotazioni di collegamento corrette

- (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; 
} 

Spero che questo aiuti qualcun altro!

+3

So che questa risposta è vecchia (ehm), ma ho salvato il bacon stasera. Grazie per la pubblicazione! –

+1

grazie per questo, ho creato una versione ARC, ma non potevo lasciarlo come commento, quindi ho fatto un'altra risposta. – nycynik

+1

Qualcosa di importante come l'accesso portachiavi dovrebbe avere API migliori. Grazie per questa risposta! – radj

1

Simon ha quasi risolto il problema perché dopo aver modificato KeychainItemWrapper.m, ho riscontrato problemi nell'ottenere e impostare i dati da e verso il portachiavi. Così, dopo l'aggiunta di questo al KeychainItemWrapper.m, ho usato questo per ottenere e memorizzare gli oggetti:

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]; 

Perché [keychainItem objectForKey: (__bridge id)kSecAttrService] sta tornando l'account (in questo esempio @"Identifier") che ha un senso, ma mi c'è voluto un po 'di tempo prima che io ho capito che avevo bisogno di usare kSecAttrGeneric per recuperare i dati dal wrapper.

10

Come sopra, ma funziona per ARC. Grazie 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; 
} 
+0

Ho intenzione di aggiornare la mia risposta per supportare ARC, grazie per avermi salvato il problema! –