2

我正在 Mac 上编写一个实用程序,需要自动确定代理信息。我已经设法获取代理主机和端口(来自自动代理配置文件),但是如何从钥匙串中获取用户名呢?

我知道您可以使用 SecKeychainAddInternetPassword 获取代理密码,但我也不知道用户名。有没有办法获取用户名和密码?

4

1 回答 1

6

我发现它有点复杂。首先,您必须询问系统配置代理是打开还是关闭。然后,您必须询问钥匙串是否在给定代理(HTTP、HTTPS 等)的返回主机名上有任何帐户。然后,如果钥匙串说是,您可以从结果中获取用户名并询问钥匙串以提供匹配的密码。此时,用户可能会看到一条警告,要求您允许您的应用访问密码。

这是一些示例代码(Mac OS X 10.6+,ARC)。

代理检测器.h:

#import <Foundation/Foundation.h>

@interface ProxyDetector : NSObject

-(ProxyDetector *)init;
-(void)detectHttpProxyReturningHostname:(NSString **)hostName port:(int *)port username:(NSString **)username password:(NSString **)password;
-(void)detectHttpsProxyReturningHostname:(NSString **)hostName port:(int *)port username:(NSString **)username password:(NSString **)password;

@end

代理检测器.m:

#import "ProxyDetector.h"
#import <SystemConfiguration/SCDynamicStoreCopySpecific.h>
#import <SystemConfiguration/SCSchemaDefinitions.h>
#import <Security/Security.h>

@implementation ProxyDetector

-(ProxyDetector *)init;
{
    if ((self = [super init])) {
        // init
    }
    return self;
}

void detectProxyWithParams(CFStringRef proxyEnableKey, CFStringRef proxyHostNameKey, CFStringRef proxyPortKey, CFTypeRef proxyProtocol, UInt32 proxyProtocolCode, NSString **hostNamePtr, int *portPtr, NSString **usernamePtr, NSString **passwordPtr)
{
    // get general proxy info
    CFDictionaryRef proxyInfoCPtr = SCDynamicStoreCopyProxies(NULL);
    NSDictionary *proxyInfo = (__bridge NSDictionary *) proxyInfoCPtr;
    NSNumber *proxyEnabled = proxyInfo[(__bridge NSString *)proxyEnableKey];

    // prefill null values for data we may not set later
    *usernamePtr = nil;
    *passwordPtr = nil;

    // is it enabled?
    if (![proxyEnabled intValue]) {
        *hostNamePtr = nil;
        *portPtr = 0;
        return;
    }

    // we can get hostname and port number from this info, but not username and password
    *hostNamePtr = proxyInfo[(__bridge NSString *)proxyHostNameKey];
    NSNumber *portNumber = proxyInfo[(__bridge NSString *)proxyPortKey];
    *portPtr = [portNumber intValue];

    // check in the keychain for username and password
    CFArrayRef result = NULL;
    OSStatus status = SecItemCopyMatching(
                              (__bridge CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys:
                                                          (__bridge id)kSecClassInternetPassword, kSecClass,
                                                          kSecMatchLimitAll, kSecMatchLimit,
                                                          kCFBooleanTrue, kSecReturnAttributes,
                                                          proxyProtocol, kSecAttrProtocol,
                                                          nil],
                              (CFTypeRef *) &result
                              );
    if (status != noErr) {
        if (status != errSecItemNotFound) {
            // unexpected error (else, just no password)
            NSString *errorStr = (__bridge NSString *)SecCopyErrorMessageString(status, NULL);
            NSLog(@"Error while trying to find proxy username and password for hostname %@; assuming no password: %@", *hostNamePtr, errorStr);
        }
        return;
    }

    // check what the keychain got us as results
    CFIndex resultCount = CFArrayGetCount(result);
    for (CFIndex resultIndex = 0; resultIndex < resultCount; resultIndex++) {
        NSDictionary *attrs = (NSDictionary *) CFArrayGetValueAtIndex(result, resultIndex);

        // check if the found host matches the host we got earlier
        NSString *host = [attrs objectForKey:(id)kSecAttrServer];
        if (![host isEqualToString:*hostNamePtr])
            continue;

        const char *hostCStr = [host UTF8String];
        NSString *username = [attrs objectForKey:(id)kSecAttrAccount];
        const char *usernameCStr = [username UTF8String];

        // we know the username now, so ask keychain for the password
        UInt32 passwordLength;
        void *passwordData;

        // this may trigger UI interaction to allow the password to be accessed by this app
        status = SecKeychainFindInternetPassword(NULL, // default user keychains
                                                 (UInt32)strlen(hostCStr), hostCStr,
                                                 0, NULL, // no security domain
                                                 (UInt32)strlen(usernameCStr), usernameCStr,
                                                 0, NULL, // no path
                                                 0, // ignore port
                                                 proxyProtocolCode,
                                                 kSecAuthenticationTypeAny,
                                                 &passwordLength, &passwordData, NULL);

        if (status != noErr) {
            // error getting or accessing this password
            NSString *errorStr = (__bridge NSString *)SecCopyErrorMessageString(status, NULL);
            NSLog(@"Error while trying to find proxy username and password for hostname %@; assuming no password: %@", *hostNamePtr, errorStr);

        } else {
            // we got everything we needed
            *usernamePtr = username;
            *passwordPtr = [NSString stringWithUTF8String:passwordData];
            break; // only one valid item in the results here anyway
        }
    }

    CFRelease(result);
}

-(void)detectHttpProxyReturningHostname:(NSString **)hostName port:(int *)port username:(NSString **)username password:(NSString **)password;
{
    detectProxyWithParams(kSCPropNetProxiesHTTPEnable, kSCPropNetProxiesHTTPProxy, kSCPropNetProxiesHTTPPort, kSecAttrProtocolHTTPProxy, kSecProtocolTypeHTTPProxy, hostName, port, username, password);
}

-(void)detectHttpsProxyReturningHostname:(NSString **)hostName port:(int *)port username:(NSString **)username password:(NSString **)password;
{
    detectProxyWithParams(kSCPropNetProxiesHTTPSEnable, kSCPropNetProxiesHTTPSProxy, kSCPropNetProxiesHTTPSPort, kSecAttrProtocolHTTPSProxy, kSecProtocolTypeHTTPSProxy, hostName, port, username, password);
}

@end

使用示例:

NSString *hostName;
int port;
NSString *username;
NSString *password;

ProxyDetector *proxyDetector = [[ProxyDetector alloc] init];

[proxyDetector detectHttpProxyReturningHostname:&hostName port:&port username:&username password:&password];
if (hostName) {
    if (username) {
        NSLog(@"HTTP proxy with authentication: http://%@:%@@%@:%d", username, password, hostName, port);
    } else {
        NSLog(@"HTTP proxy without authentication: http://%@:%d", hostName, port);
    }
} else {
    NSLog(@"No HTTP proxy");
}

[proxyDetector detectHttpsProxyReturningHostname:&hostName port:&port username:&username password:&password];
if (hostName) {
    if (username) {
        NSLog(@"HTTPS proxy with authentication: http://%@:%@@%@:%d", username, password, hostName, port);
    } else {
        NSLog(@"HTTPS proxy without authentication: http://%@:%d", hostName, port);
    }
} else {
    NSLog(@"No HTTPS proxy");
}

欢迎改进!

于 2013-02-26T09:06:41.490 回答