How can I get the username for a proxy host from KeyChain?

后端 未结 1 787
粉色の甜心
粉色の甜心 2021-01-01 07:19

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), h

相关标签:
1条回答
  • 2021-01-01 07:49

    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!

    0 讨论(0)
提交回复
热议问题