How can I get the username for a proxy host from K

2019-02-07 13:28发布

问题:

I'm writing an utility on the Mac and need to auto-determine proxy information. I've managed to get the proxy host and port (from an automatic proxy configuration file), however how do I obtain the username from the keychain from this?

I know you can use SecKeychainAddInternetPassword to get the proxy password, but I don't know the username either. Is there a way to get the username AND the password?

回答1:

I found it a little complicated. First, you have to ask the system configuration whether the proxy is on or off. Then, you have to ask the keychain if it has any account on the returned hostname for a given proxy (HTTP, HTTPS, etc.). Then, if the keychain says yes, you can get the username from the result and ask the keychain for the matching password. At this point, the user may see an alert asking to allow your app to access the password.

Here's some sample code (Mac OS X 10.6+, ARC).

ProxyDetector.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

ProxyDetector.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

Usage example:

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");
}

Improvements are welcome!