2011-02-03 12 views
38

Estoy tratando de utilizar la clase KeychainWrapper proporcionada en este código de ejemplo de Apple: https://developer.apple.com/library/content/samplecode/GenericKeychain/iOS Keychain Services: solo se permiten valores específicos para kSecAttrGeneric Key?

En la aplicación de ejemplo, la clase tiene este método init que comienza como:

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

En la aplicación de ejemplo, se usa dos valores para la cadena identificadora. "Contraseña" y "Número de cuenta". Cuando implementé la clase en mi código, utilicé algunos identificadores personalizados y el código no funcionó. La llamada a SecItemAdd() falló. Después de algunas pruebas, parece que el uso de valores distintos a "Contraseña" y "Número de cuenta" para el identificador no funciona.

¿Alguien sabe qué valores están permitidos y/o si es posible tener identificadores personalizados para sus artículos llavero?

+0

Pregunta relacionada: http: // stackoverflow.com/questions/11614047/what-makes-a-keychain-item-unique-in-ios –

+0

FWIW, archivé un Radar con Apple sobre este problema con su código de muestra. Vea http://www.openradar.me/13472204 si quiere engañarlo. –

Respuesta

60

Vale, he encontrado la solución en esta entrada del blog Keychain duplicate item when adding password

Para resumir, el problema es que la aplicación de ejemplo GenericKeychain utiliza el valor almacenado en la clave kSecAttrGeneric como el identificador para el elemento llavero cuando en realidad que no es lo que utiliza la API para determinar un elemento único de llavero. Las claves que necesita establecer con valores únicos son la clave kSecAttrAccount y/o la clave kSecAttrService.

Puede volver a escribir la initilizer de KeychainItemWrapper por lo que no es necesario cambiar cualquier otro código cambiando estas líneas:

Cambio:

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

a:

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

y cambio:

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

a:

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

O bien, puede hacer lo que hice y escribir un nuevo initilizer que toma tanto de las claves de identificación:

Editar: Para las personas que utilizan ARC (que debe ser hoy en día) comprobar nycynik's answer para toda la notación puente correcta

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

Espero que esto ayude a alguien más a salir!

+3

Sé que esta respuesta es vieja (er), pero salvó mi tocino esta noche. ¡Gracias por publicar! –

+1

gracias por esto, hice una versión ARC, pero no pude dejarla como un comentario, así que hice otra respuesta. – nycynik

+1

Algo tan importante como el acceso de llavero debería tener mejores API. Gracias por esta respuesta! – radj

1

Simon casi fijo mi problema porque después de cambiar el KeychainItemWrapper.m, tuve problemas para conseguir y el establecimiento de datos hacia y desde el llavero. Después de agregar esto a la KeychainItemWrapper.m, que utiliza esto para obtener y artículos de la tienda:

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

Debido [keychainItem objectForKey: (__bridge id)kSecAttrService] está devolviendo la cuenta (en este ejemplo @"Identifier") que tiene sentido pero me tomó un poco de tiempo antes de que yo me di cuenta de que necesitaba usar kSecAttrGeneric para obtener datos del wrapper.

10

Igual que el anterior, pero funciona para ARC. Gracias 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

He tenido la intención de actualizar mi respuesta para apoyar a ARC, ¡Gracias por salvarme el problema! –

Cuestiones relacionadas