我正在 Mac 上编写一个实用程序,需要自动确定代理信息。我已经设法获取代理主机和端口(来自自动代理配置文件),但是如何从钥匙串中获取用户名呢?
我知道您可以使用 SecKeychainAddInternetPassword 获取代理密码,但我也不知道用户名。有没有办法获取用户名和密码?
我正在 Mac 上编写一个实用程序,需要自动确定代理信息。我已经设法获取代理主机和端口(来自自动代理配置文件),但是如何从钥匙串中获取用户名呢?
我知道您可以使用 SecKeychainAddInternetPassword 获取代理密码,但我也不知道用户名。有没有办法获取用户名和密码?
我发现它有点复杂。首先,您必须询问系统配置代理是打开还是关闭。然后,您必须询问钥匙串是否在给定代理(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");
}
欢迎改进!