4

From this question, I know you can use SecKeychainFindGenericPassword without a username value. It will still return a keychain item for the given service. But how do I then get the username? Get the Username(s) stored in Keychain, using only the ServiceName? OR: Where are you supposed to store the Username?

//SecKeychainFindGenericPassword
//Finds the first generic password based on the attributes passed.

OSStatus SecKeychainFindGenericPassword (
   CFTypeRef keychainOrArray,
   UInt32 serviceNameLength,
   const char *serviceName,
   UInt32 accountNameLength,
   const char *accountName,
   UInt32 *passwordLength,
   void **passwordData,
   SecKeychainItemRef *itemRef
);

My code looks like this:

UInt32 passwordLength = 0;
char *password = nil;

SecKeychainItemRef item = nil;

OSStatus returnStatus = SecKeychainFindGenericPassword(NULL, 9, @"MyAppName", 0, NULL, &passwordLength, (void **)&password, &item);

//Get password
NSString *passwordString = [[[NSString alloc] initWithData:[NSData dataWithBytes:password length:passwordLength] encoding:NSUTF8StringEncoding] autorelease];
SecKeychainItemFreeContent(NULL, password);

//Get username (not yet working)
UInt32 attributeTags[1];
*attributeTags = 'acct';// TODO: populate with the kSecAttrAccount constant (NOT a string cast to a UInt32)
//kSecAccountItemAttr = 'acct',

UInt32 formatConstants[1];
*formatConstants = 0; // TODO: populate with a format constant found in "cssmtype.h". I'm picking "0" = "string" in list below...

//SecKeychainAttributeInfo doc says: "A pointer to the first attribute format in the array. Attribute formats are of type CSSM_DB_ATTRIBUTE_FORMAT."

/* //found in "cssmtype.h"
 typedef uint32 CSSM_DB_ATTRIBUTE_FORMAT, *CSSM_DB_ATTRIBUTE_FORMAT_PTR;
 enum {
 CSSM_DB_ATTRIBUTE_FORMAT_STRING =          0,
 CSSM_DB_ATTRIBUTE_FORMAT_SINT32 =          1,
 CSSM_DB_ATTRIBUTE_FORMAT_UINT32 =          2,
 CSSM_DB_ATTRIBUTE_FORMAT_BIG_NUM =         3,
 CSSM_DB_ATTRIBUTE_FORMAT_REAL =                4,
 CSSM_DB_ATTRIBUTE_FORMAT_TIME_DATE =       5,
 CSSM_DB_ATTRIBUTE_FORMAT_BLOB =                6,
 CSSM_DB_ATTRIBUTE_FORMAT_MULTI_UINT32 =        7,
 CSSM_DB_ATTRIBUTE_FORMAT_COMPLEX =         8
 };
*/

struct SecKeychainAttributeInfo attributeInfo, 1, *attributeTags, *formatConstants; //ERROR: "Expected ';' at end of declaration list.
//OR:
/*
struct SecKeychainAttributeInfo
{
    UInt32 1; //ERROR: "Expected ';' at end of declaration list.
    UInt32 *attributeTags;
    UInt32 *formatConstants;
}attributeInfo;
*/

/*
SecKeychainAttributeInfo *attributeInfo; //TODO: "change it to hold the structure directly"
attributeInfo->count = 1;
attributeInfo->tag = attributeTags;
attributeInfo->format = formatConstants;
*/

SecKeychainAttributeList *attributeList = nil;
OSStatus attributeStatus = SecKeychainItemCopyAttributesAndData(item, &attributeInfo, NULL, &attributeList, 0, NULL);

if (attributeStatus != noErr)
{
    if (_logsErrors)
        NSLog(@"Error (%@) - %s", NSStringFromSelector(_cmd), GetMacOSStatusErrorString(attributeStatus));
    return nil;
}


for (int i = 0; i < attributeList->count; i ++)
{
    SecKeychainAttribute attr = attributeList->attr[i];
    NSLog(@"%08x %@", attr.tag, [NSData dataWithBytes:attr.data length:attr.length]);
}

How do I get an NSString value of the username contained in that SecKeychainItemRef?

Community
  • 1
  • 1
ck_
  • 3,719
  • 10
  • 49
  • 76

2 Answers2

5
+ (EMGenericKeychainItem *)genericKeychainItemForService:(NSString *)serviceName
{
    if (!serviceName)
        return nil;

    const char *serviceNameCString = [serviceName UTF8String];

    UInt32 passwordLength = 0;
    char *password = nil;

    SecKeychainItemRef item = nil;
    OSStatus returnStatus = SecKeychainFindGenericPassword(NULL, strlen(serviceNameCString), serviceNameCString, 0, NULL, &passwordLength, (void **)&password, &item);

    UInt32 attributeTags[1];
    *attributeTags = kSecAccountItemAttr;

    UInt32 formatConstants[1];
    *formatConstants = CSSM_DB_ATTRIBUTE_FORMAT_STRING; //From "cssmtype.h" under "CSSM_DB_ATTRIBUTE_FORMAT".

    struct SecKeychainAttributeInfo
    {
        UInt32 count;
        UInt32 *tag;
        UInt32 *format;
    }attributeInfo;

    attributeInfo.count = 1;
    attributeInfo.tag = attributeTags;
    attributeInfo.format = formatConstants;

    SecKeychainAttributeList *attributeList = nil;
    OSStatus attributeStatus = SecKeychainItemCopyAttributesAndData(item, &attributeInfo, NULL, &attributeList, 0, NULL);

    if (attributeStatus != noErr || !item)
    {
        if (_logsErrors)
            NSLog(@"Error (%@) - %s", NSStringFromSelector(_cmd), GetMacOSStatusErrorString(returnStatus));
        return nil;
    }

    SecKeychainAttribute accountNameAttribute = attributeList->attr[0];

    NSString* accountName = [[[NSString alloc] initWithData:[NSData dataWithBytes:accountNameAttribute.data length:accountNameAttribute.length] encoding:NSUTF8StringEncoding] autorelease];

    NSString *passwordString = [[[NSString alloc] initWithData:[NSData dataWithBytes:password length:passwordLength] encoding:NSUTF8StringEncoding] autorelease];
    SecKeychainItemFreeContent(NULL, password);         

    return [EMGenericKeychainItem _genericKeychainItemWithCoreKeychainItem:item forServiceName:serviceName username:accountName password:passwordString];
}
ck_
  • 3,719
  • 10
  • 49
  • 76
  • Why are you assigning the literal values of the constants? You can and should just pass the constants themselves. – Peter Hosey Dec 13 '11 at 20:27
  • I'm afraid I don't know how to do that. Where do those constants "exist" and how do I reference them? What should I do instead of `*attributeTags = 'acct';`? – ck_ Dec 13 '11 at 20:37
  • You already found them. You reference them by their names, just like you're already doing with the constant `NSUTF8StringEncoding`. – Peter Hosey Dec 13 '11 at 20:39
  • Ah, gotcha. I thought I did that in the past, but it didn't work, which is why I switched to using the values. But now it works fine. – ck_ Dec 13 '11 at 21:27
0

Use the SecKeychainItemCopyAttributesAndData function to obtain the account name and any other attributes you're interested in.

Peter Hosey
  • 95,783
  • 15
  • 211
  • 370
  • I updated my question, `attributeList->count` is getting an EXC_Bad_Access. Is there some additional step I need to take? – ck_ Dec 11 '11 at 02:50
  • 1
    @cksubs: Yes: You aren't asking for any attributes, so you don't get any. You need to ask for the account name in your `SecKeychainItemCopyAttributesAndData` call. – Peter Hosey Dec 11 '11 at 06:46
  • These data structures are so frustrating. I'm now passing `&attributeInfo` as the second parameter of `CopyAttributesAndData`. But should I be getting the attributes from my keychain item with `SecKeychainAttributeInfoForItemID`? Or creating a new `SecKeychainAttributeInfo` with only the Account Name attribute? I don't understand the syntax for creating a new `SecKeychainAttributeInfo` with only the info I want. – ck_ Dec 11 '11 at 19:48
  • 1
    @cksubs: The documentation says “You can call `SecKeychainAttributeInfoForItemID` to obtain a list of all possible attribute tags and formats for the item's class.”, so if you don't need *all* of the attributes of the keychain item, skip it and ask for the account name specifically. You need two parallel C arrays of `UInt32`s, one containing attribute tags such as `kSecAttrAccount` and the other containing format constants (which are not documented but are listed in cssmtype.h), and your `attributeInfo` structure should contain the pointers to the arrays and their length. – Peter Hosey Dec 11 '11 at 20:07
  • Where are you getting the "two parallel C arrays" thing? The docs show: `struct SecKeychainAttributeList { UInt32 count; SecKeychainAttribute *attr; };` – ck_ Dec 11 '11 at 20:30
  • @cksubs: That's the output. I'm talking about the input. – Peter Hosey Dec 11 '11 at 22:03
  • Well, thanks for your help but this is too difficult. I'm just going to store the username in the .plist. – ck_ Dec 12 '11 at 00:37
  • Unless you are able to help with code for adding `kSecAttrAccount` to the `SecKeychainAttributeInfo *attributeInfo`. It's beyond my level of programming knowledge. – ck_ Dec 12 '11 at 01:18
  • @cksubs: What you need to do is create two C arrays of `UInt32`s, one containing attribute tags and the other containing format constants, and store the pointers to the arrays in your attribute-info structure. – Peter Hosey Dec 12 '11 at 04:09
  • Code updated. `attributeInfo->count = 1;` gives EXC_BAD_ACCESS... is that not the correct way to "store the pointers to the arrays in your attribute-info structure"? – ck_ Dec 12 '11 at 05:09
  • @cksubs: It's not, but that's not the cause of the crash. The crash is because you're trying to dereference a random pointer; you declared `attributeInfo` as holding a pointer, but you didn't assign anything to it, so it points nowhere in particular and trying to dereference it causes a crash in the best case. Either initialize the variable to point at a buffer you've allocated (which must be big enough to hold a `SecKeychainAttributeInfo` structure), or change it to hold the structure directly—don't declare it as a pointer. I'd go with the latter. – Peter Hosey Dec 12 '11 at 06:09
  • @cksubs: As for assigning the arrays, you aren't. You declared `attributeTags` and `formatConstants` incorrectly, as arrays of pointers to `uint32`s rather than arrays of `UInt32`s. Change the type, remove the pointer asterisks, and correct the assignments accordingly—you are currently assigning the arrays' first elements, and once you fix the arrays, you will want to be assigning the arrays themselves. – Peter Hosey Dec 12 '11 at 06:12
  • @cksubs: You're also populating `attributeTags` incorrectly, with a string cast to a `uint32` rather than the `kSecAttrAccount` constant, and not populating `formatConstants` at all. See my earlier comment for where the format constants are declared. – Peter Hosey Dec 12 '11 at 06:14
  • I posted code in the answer below, working! Thanks for your help/lessons. Am I making any stupid mistakes in the code below? – ck_ Dec 13 '11 at 19:42