William Bowling is sharing code with you
Bitbucket is a code hosting site. Unlimited public and private repositories. Free for small teams.
Don't show this againadium / Plugins / Purple Service / AIPurpleCertificateTrustWarningAlert.m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 | //
// AIPurpleCertificateTrustWarningAlert.m
// Adium
//
// Created by Andreas Monitzer on 2007-11-05.
// Copyright 2007 Andreas Monitzer. All rights reserved.
//
#import "AIPurpleCertificateTrustWarningAlert.h"
#import <SecurityInterface/SFCertificateTrustPanel.h>
#import <Security/SecureTransport.h>
#import <Security/SecPolicySearch.h>
#import <Security/SecPolicy.h>
#import <Security/oidsalg.h>
#import <Adium/AIAccountControllerProtocol.h>
#import "ESPurpleJabberAccount.h"
#import "AIPurpleGTalkAccount.h"
//#define ALWAYS_SHOW_TRUST_WARNING
static NSMutableDictionary *acceptedCertificates = nil;
@interface AIPurpleCertificateTrustWarningAlert ()
- (id)initWithAccount:(AIAccount*)account
hostname:(NSString*)hostname
certificates:(CFArrayRef)certs
resultCallback:(void (*)(gboolean trusted, void *userdata))_query_cert_cb
userData:(void*)ud;
- (IBAction)showWindow:(id)sender;
- (void)runTrustPanelOnWindow:(NSWindow *)window;
@end
@interface SFCertificateTrustPanel (SecretsIKnow)
- (void)setInformativeText:(NSString *)inString;
@end
@implementation AIPurpleCertificateTrustWarningAlert
+ (void)displayTrustWarningAlertWithAccount:(AIAccount *)account
hostname:(NSString *)hostname
certificates:(CFArrayRef)certs
resultCallback:(void (*)(gboolean trusted, void *userdata))_query_cert_cb
userData:(void*)ud
{
if ([hostname caseInsensitiveCompare:@"talk.google.com"] == NSOrderedSame) {
NSString *UID = account.UID;
NSRange startOfDomain = [UID rangeOfString:@"@"];
if (startOfDomain.location == NSNotFound ||
([[UID substringFromIndex:NSMaxRange(startOfDomain)] caseInsensitiveCompare:@"gmail.com"] == NSOrderedSame)) {
/* Google Talk accounts end up with a cert signed using gmail.com as the server.
* However, Google For Domains accounts are signed using talk.google.com.
*/
hostname = @"gmail.com";
} else if ([[UID substringFromIndex:NSMaxRange(startOfDomain)] caseInsensitiveCompare:@"googlemail.com"] == NSOrderedSame) {
/* There are three certificates, as far as I (am) know. Maybe we should ask Sean for confirmation. */
hostname = @"googlemail.com";
}
}
AIPurpleCertificateTrustWarningAlert *alert = [[self alloc] initWithAccount:account hostname:hostname certificates:certs resultCallback:_query_cert_cb userData:ud];
[alert showWindow:nil];
[alert release];
}
- (id)initWithAccount:(AIAccount*)_account
hostname:(NSString*)_hostname
certificates:(CFArrayRef)certs
resultCallback:(void (*)(gboolean trusted, void *userdata))_query_cert_cb
userData:(void*)ud
{
if((self = [super init])) {
if(!acceptedCertificates)
acceptedCertificates = [[NSMutableDictionary alloc] init];
query_cert_cb = _query_cert_cb;
certificates = certs;
CFRetain(certificates);
account = _account;
hostname = [_hostname copy];
userdata = ud;
}
return [self retain];
}
- (void)dealloc {
CFRelease(certificates);
[hostname release];
[super dealloc];
}
- (IBAction)showWindow:(id)sender {
OSStatus err;
SecPolicySearchRef searchRef = NULL;
SecPolicyRef policyRef;
CSSM_DATA data;
err = SecCertificateGetData((SecCertificateRef)CFArrayGetValueAtIndex(certificates, 0), &data);
if(err == noErr) {
// Did we ask the user to confirm this certificate before?
// Note that this information is not stored on the disk, which is on purpose.
NSUInteger oldCertHash = [[acceptedCertificates objectForKey:hostname] unsignedIntegerValue];
if (oldCertHash) {
NSData *certData = [[NSData alloc] initWithBytesNoCopy:data.Data length:data.Length freeWhenDone:NO];
NSUInteger newCertHash = [certData hash];
[certData release];
if (oldCertHash == newCertHash) {
query_cert_cb(true, userdata);
[self release];
return;
}
}
}
err = SecPolicySearchCreate(CSSM_CERT_X_509v3, &CSSMOID_APPLE_TP_SSL, NULL, &searchRef);
if(err != noErr) {
NSBeep();
[self release];
return;
}
err = SecPolicySearchCopyNext(searchRef, &policyRef);
if(err != noErr) {
CFRelease(searchRef);
NSBeep();
[self release];
return;
}
CSSM_APPLE_TP_SSL_OPTIONS ssloptions = {
.Version = CSSM_APPLE_TP_SSL_OPTS_VERSION,
.ServerNameLen = [hostname length]+1,
.ServerName = [hostname cStringUsingEncoding:NSASCIIStringEncoding],
.Flags = 0
};
CSSM_DATA theCssmData = {
.Length = sizeof(ssloptions),
.Data = (uint8*)&ssloptions
};
err = SecPolicySetValue(policyRef, &theCssmData);
// don't care about the error
err = SecTrustCreateWithCertificates(certificates, policyRef, &trustRef);
if(err != noErr) {
CFRelease(searchRef);
CFRelease(policyRef);
if (trustRef)
CFRelease(trustRef);
NSBeep();
[self release];
return;
}
// test whether we aren't already trusting this certificate
SecTrustResultType result;
err = SecTrustEvaluate(trustRef, &result);
if(err == noErr) {
// with help from http://lists.apple.com/archives/Apple-cdsa/2006/Apr/msg00013.html
switch(result) {
case kSecTrustResultProceed: // trust ok, go right ahead
case kSecTrustResultUnspecified: // trust ok, user has no particular opinion about this
#ifndef ALWAYS_SHOW_TRUST_WARNING
query_cert_cb(true, userdata);
[self autorelease];
break;
#endif
case kSecTrustResultConfirm: // trust ok, but user asked (earlier) that you check with him before proceeding
case kSecTrustResultDeny: // trust ok, but user previously said not to trust it anyway
case kSecTrustResultRecoverableTrustFailure: // trust broken, perhaps argue with the user
case kSecTrustResultOtherError: // failure other than trust evaluation; e.g., internal failure of the SecTrustEvaluate function. We'll let the user decide where to go from here.
{
#if 1
//Show on an independent window.
#define TRUST_PANEL_WIDTH 535
NSWindow *fakeWindow = [[[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, TRUST_PANEL_WIDTH, 1)
styleMask:(NSTitledWindowMask | NSMiniaturizableWindowMask)
backing:NSBackingStoreBuffered
defer:NO] autorelease];
[fakeWindow center];
[fakeWindow setTitle:AILocalizedString(@"Verify Certificate", nil)];
[self runTrustPanelOnWindow:fakeWindow];
[fakeWindow makeKeyAndOrderFront:nil];
#else
//Show as a sheet on the account's preferences
[adium.accountController editAccount:account onWindow:nil notifyingTarget:self];
#endif
break;
}
default:
/*
* kSecTrustResultFatalTrustFailure -> trust broken, user can't fix it
* kSecTrustResultInvalid -> logic error; fix your program (SecTrust was used incorrectly)
*/
query_cert_cb(false, userdata);
[self autorelease];
break;
}
} else {
query_cert_cb(false, userdata);
[self autorelease];
}
CFRelease(searchRef);
CFRelease(policyRef);
CFRelease(trustRef);
}
/*
* Function: SSLSecPolicyCopy
* Purpose:
* Returns a copy of the SSL policy.
*/
static SecPolicyRef SSLSecPolicyCopy()
{
SecPolicyRef policy = NULL;
SecPolicySearchRef policy_search;
OSStatus status;
status = SecPolicySearchCreate(CSSM_CERT_X_509v3, &CSSMOID_APPLE_TP_SSL, NULL, &policy_search);
if (status == noErr) {
status = SecPolicySearchCopyNext(policy_search, &policy);
if (status != noErr) policy = NULL;
}
CFRelease(policy_search);
return policy;
}
- (void)runTrustPanelOnWindow:(NSWindow *)window
{
SFCertificateTrustPanel *trustPanel = [[SFCertificateTrustPanel alloc] init];
// this could probably be used for a more detailed message:
// CFArrayRef certChain;
// CSSM_TP_APPLE_EVIDENCE_INFO *statusChain;
// err = SecTrustGetResult(trustRef, &result, &certChain, &statusChain);
NSString *title;
NSString *informativeText = [NSString stringWithFormat:AILocalizedString(@"The certificate of the server %@ is not trusted, which means that the server's identity cannot be automatically verified. Do you want to continue connecting?\n\nFor more information, click \"Show Certificate\".",nil), hostname];
if ([trustPanel respondsToSelector:@selector(setInformativeText:)]) {
[trustPanel setInformativeText:informativeText];
title = [NSString stringWithFormat:AILocalizedString(@"Adium can't verify the identity of \"%@\".", nil), hostname];
} else {
/* We haven't seen a version of SFCertificateTrustPanel which doesn't respond to setInformativeText:, but we're using a private
* call found via class-dump, so have a sane backup strategy in case it changes.
*/
title = informativeText;
}
[trustPanel setAlternateButtonTitle:AILocalizedString(@"Cancel",nil)];
[trustPanel setShowsHelp:YES];
SecPolicyRef sslPolicy = SSLSecPolicyCopy();
if (sslPolicy) {
[trustPanel setPolicies:(id)sslPolicy];
CFRelease(sslPolicy);
}
[trustPanel beginSheetForWindow:window
modalDelegate:self
didEndSelector:@selector(certificateTrustSheetDidEnd:returnCode:contextInfo:)
contextInfo:window
trust:trustRef
message:title];
}
- (void)editAccountWindow:(NSWindow *)window didOpenForAccount:(AIAccount *)inAccount
{
[self runTrustPanelOnWindow:window];
}
- (void)certificateTrustSheetDidEnd:(SFCertificateTrustPanel *)trustpanel returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
BOOL didTrustCerficate = (returnCode == NSOKButton);
NSWindow *parentWindow = (NSWindow *)contextInfo;
query_cert_cb(didTrustCerficate, userdata);
/* If the user confirmed this cert, we store this information until the app is closed so the user doesn't have to re-confirm it every time
* (doing otherwise might be particularily annoying on auto-reconnect)
*/
if (didTrustCerficate) {
CSSM_DATA certdata;
OSStatus err = SecCertificateGetData((SecCertificateRef)CFArrayGetValueAtIndex(certificates, 0), &certdata);
if(err == noErr) {
[acceptedCertificates setObject:[NSNumber numberWithUnsignedInteger:[[NSData dataWithBytes:certdata.Data length:certdata.Length] hash]]
forKey:hostname];
}
}
[trustpanel release];
CFRelease(trustRef);
[parentWindow performClose:nil];
[self release];
}
@end
|