在Mac OS X中列出钥匙串中的条目

4
我最近几天一直在研究Cocoa,我想知道如何列出我创建的密钥链中所有名称/帐户对?Mac OS X自带的小型密钥链访问应用程序可以做到,所以我认为这是可能的。我应该使用SecItemCopyMatching吗?但是我该如何指定要搜索的密钥链呢?在这种情况下,什么是服务名称?
还有,我是不是唯一一个认为Cocoa中的密钥链API绝对可怕的人?我已经阅读了文档好几个小时,但还是毫无头绪 :-/

我碰巧同意KeyChain API相当糟糕,但公平地说,它不是Cocoa API,而是一个老旧的Carbon API。OSStatus返回代码是一个提示。 - Monolo
2个回答

7
你可以使用SecItemCopyMatching遍历你的钥匙串中的项目,并使用SecKeychainFindInternetPasswordSecKeychainFindGenericPassword访问密码。

遍历钥匙串

// iterates over keychain and pass every item found by the query to PrintAccount.
static void IterateOverKeychain() {
    // create query
    CFMutableDictionaryRef query = CFDictionaryCreateMutable(kCFAllocatorDefault, 3, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    CFDictionaryAddValue(query, kSecReturnAttributes, kCFBooleanTrue);
    CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
    CFDictionaryAddValue(query, kSecClass, kSecClassInternetPassword);

    // get search results
    CFArrayRef result = nil;
    OSStatus status = SecItemCopyMatching(query, (CFTypeRef*)&result);
    assert(status == 0);

    // do something with the result
    CFRange range = CFRangeMake(0, CFArrayGetCount(result));
    CFArrayApplyFunction(result, range, PrintAccount, nil);
}

// prints the password for a item from the keychain.
static void PrintAccount(const void *value, void *context) {
    CFDictionaryRef dict = value;
    CFStringRef acct = CFDictionaryGetValue(dict, kSecAttrAccount);
    NSLog(@"%@", acct);
}

打印密码:
static void PrintPassword() {
    const char *acct = "foo.bar@googlemail.com";
    UInt32 acctLen = (UInt32)strlen(acct);

    const char *srvr = "calendar.google.com";
    UInt32 srvrLen = (UInt32)strlen(srvr);

    UInt32 pwLen = 0;
    void *pw = 0;

    SecKeychainFindInternetPassword(nil, srvrLen, srvr, 0, nil, acctLen, acct, 0, nil, 0, kSecProtocolTypeAny, kSecAuthenticationTypeAny, &pwLen, &pw, nil);

    CFStringRef pwString = CFStringCreateWithBytes(kCFAllocatorDefault, pw, pwLen, kCFStringEncodingUTF8, NO);
    NSLog(@"%s %@", acct, pwString);
}

2

我成功地枚举了钥匙串条目,但是密码字段为空。我认为如果需要授权,程序通常会自动要求输入钥匙串密码,难道不是吗?

NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys:
                       (id)kSecClassInternetPassword, kSecClass,
                       (id)kCFBooleanTrue, kSecReturnData,
                       (id)kCFBooleanTrue, kSecReturnAttributes,
                       kSecMatchLimitAll, kSecMatchLimit,
                       nil];

NSArray *itemDicts = nil;
OSStatus status = SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef *)&itemDicts);
if (status)
    [MessageBox Show:(NSString*)SecCopyErrorMessageString(status, NULL)];

NSMutableArray *arr = [[NSMutableArray alloc] init];

for (NSDictionary *itemDict in itemDicts) {
    NSData *data    = [itemDict objectForKey:(id)kSecValueData];
    NSString *pwd   = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
    NSString *acc   = [itemDict objectForKey:(id)kSecAttrAccount];
    NSString *name  = [itemDict objectForKey:(id)kSecAttrLabel];

    if(acc != nil) {
        NSArray *values = [NSArray arrayWithObjects: (id)name, (id)acc, (id)pwd, nil];

        [arr addObject:(id)values];
    }
}
[itemDicts release];

NSInteger c     = arr.count;
NSString *cnt   = [NSString stringWithFormat:@"%d", c]; 
[MessageBox Show: [arr objectAtIndex:10]];

3
我花了半天时间进行深思熟虑,得出的结论是kSecMatchLimit > 1与kSecReturnData不兼容。在http://www.opensource.apple.com上查看SecItem.cpp的源代码,你会发现这样的注释://如果我们返回所有匹配项,则不支持将密码作为数据返回(这可能需要每个密码进行身份验证)。 - bitmusher
@bitmusher 这已经过去了几年,但仍未包含在文档中... 可能值得单独回答而不是评论。 - cutsoy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接