1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#import "chrome/browser/ui/cocoa/ssl_client_certificate_selector_cocoa.h" 6 7#import <SecurityInterface/SFChooseIdentityPanel.h> 8 9#include "base/logging.h" 10#include "base/mac/foundation_util.h" 11#include "base/strings/string_util.h" 12#include "base/strings/sys_string_conversions.h" 13#include "base/strings/utf_string_conversions.h" 14#include "chrome/browser/ssl/ssl_client_auth_observer.h" 15#import "chrome/browser/ui/cocoa/constrained_window/constrained_window_mac.h" 16#include "chrome/grit/generated_resources.h" 17#include "content/public/browser/browser_thread.h" 18#include "content/public/browser/web_contents.h" 19#include "net/cert/x509_certificate.h" 20#include "net/cert/x509_util_mac.h" 21#include "net/ssl/ssl_cert_request_info.h" 22#include "ui/base/cocoa/window_size_constants.h" 23#include "ui/base/l10n/l10n_util_mac.h" 24 25using content::BrowserThread; 26 27@interface SFChooseIdentityPanel (SystemPrivate) 28// A system-private interface that dismisses a panel whose sheet was started by 29// -beginSheetForWindow:modalDelegate:didEndSelector:contextInfo:identities:message: 30// as though the user clicked the button identified by returnCode. Verified 31// present in 10.5 through 10.8. 32- (void)_dismissWithCode:(NSInteger)code; 33@end 34 35@interface SSLClientCertificateSelectorCocoa () 36- (void)onConstrainedWindowClosed; 37@end 38 39class SSLClientAuthObserverCocoaBridge : public SSLClientAuthObserver, 40 public ConstrainedWindowMacDelegate { 41 public: 42 SSLClientAuthObserverCocoaBridge( 43 const net::HttpNetworkSession* network_session, 44 net::SSLCertRequestInfo* cert_request_info, 45 const chrome::SelectCertificateCallback& callback, 46 SSLClientCertificateSelectorCocoa* controller) 47 : SSLClientAuthObserver(network_session, cert_request_info, callback), 48 controller_(controller) { 49 } 50 51 // SSLClientAuthObserver implementation: 52 virtual void OnCertSelectedByNotification() OVERRIDE { 53 [controller_ closeWebContentsModalDialog]; 54 } 55 56 // ConstrainedWindowMacDelegate implementation: 57 virtual void OnConstrainedWindowClosed( 58 ConstrainedWindowMac* window) OVERRIDE { 59 // |onConstrainedWindowClosed| will delete the sheet which might be still 60 // in use higher up the call stack. Wait for the next cycle of the event 61 // loop to call this function. 62 [controller_ performSelector:@selector(onConstrainedWindowClosed) 63 withObject:nil 64 afterDelay:0]; 65 } 66 67 private: 68 SSLClientCertificateSelectorCocoa* controller_; // weak 69}; 70 71namespace chrome { 72 73void ShowSSLClientCertificateSelector( 74 content::WebContents* contents, 75 const net::HttpNetworkSession* network_session, 76 net::SSLCertRequestInfo* cert_request_info, 77 const SelectCertificateCallback& callback) { 78 DCHECK_CURRENTLY_ON(BrowserThread::UI); 79 // The dialog manages its own lifetime. 80 SSLClientCertificateSelectorCocoa* selector = 81 [[SSLClientCertificateSelectorCocoa alloc] 82 initWithNetworkSession:network_session 83 certRequestInfo:cert_request_info 84 callback:callback]; 85 [selector displayForWebContents:contents]; 86} 87 88} // namespace chrome 89 90@implementation SSLClientCertificateSelectorCocoa 91 92- (id)initWithNetworkSession:(const net::HttpNetworkSession*)networkSession 93 certRequestInfo:(net::SSLCertRequestInfo*)certRequestInfo 94 callback:(const chrome::SelectCertificateCallback&)callback { 95 DCHECK(networkSession); 96 DCHECK(certRequestInfo); 97 if ((self = [super init])) { 98 observer_.reset(new SSLClientAuthObserverCocoaBridge( 99 networkSession, certRequestInfo, callback, self)); 100 } 101 return self; 102} 103 104- (void)sheetDidEnd:(NSWindow*)parent 105 returnCode:(NSInteger)returnCode 106 context:(void*)context { 107 net::X509Certificate* cert = NULL; 108 if (returnCode == NSFileHandlingPanelOKButton) { 109 CFRange range = CFRangeMake(0, CFArrayGetCount(identities_)); 110 CFIndex index = 111 CFArrayGetFirstIndexOfValue(identities_, range, [panel_ identity]); 112 if (index != -1) 113 cert = certificates_[index].get(); 114 else 115 NOTREACHED(); 116 } 117 118 // Finally, tell the backend which identity (or none) the user selected. 119 observer_->StopObserving(); 120 observer_->CertificateSelected(cert); 121 122 if (!closePending_) 123 constrainedWindow_->CloseWebContentsModalDialog(); 124} 125 126- (void)displayForWebContents:(content::WebContents*)webContents { 127 // Create an array of CFIdentityRefs for the certificates: 128 size_t numCerts = observer_->cert_request_info()->client_certs.size(); 129 identities_.reset(CFArrayCreateMutable( 130 kCFAllocatorDefault, numCerts, &kCFTypeArrayCallBacks)); 131 for (size_t i = 0; i < numCerts; ++i) { 132 SecCertificateRef cert = 133 observer_->cert_request_info()->client_certs[i]->os_cert_handle(); 134 SecIdentityRef identity; 135 if (SecIdentityCreateWithCertificate(NULL, cert, &identity) == noErr) { 136 CFArrayAppendValue(identities_, identity); 137 CFRelease(identity); 138 certificates_.push_back(observer_->cert_request_info()->client_certs[i]); 139 } 140 } 141 142 // Get the message to display: 143 NSString* message = l10n_util::GetNSStringF( 144 IDS_CLIENT_CERT_DIALOG_TEXT, 145 base::ASCIIToUTF16( 146 observer_->cert_request_info()->host_and_port.ToString())); 147 148 // Create and set up a system choose-identity panel. 149 panel_.reset([[SFChooseIdentityPanel alloc] init]); 150 [panel_ setInformativeText:message]; 151 [panel_ setDefaultButtonTitle:l10n_util::GetNSString(IDS_OK)]; 152 [panel_ setAlternateButtonTitle:l10n_util::GetNSString(IDS_CANCEL)]; 153 SecPolicyRef sslPolicy; 154 if (net::x509_util::CreateSSLClientPolicy(&sslPolicy) == noErr) { 155 [panel_ setPolicies:(id)sslPolicy]; 156 CFRelease(sslPolicy); 157 } 158 159 constrainedWindow_.reset( 160 new ConstrainedWindowMac(observer_.get(), webContents, self)); 161 observer_->StartObserving(); 162} 163 164- (void)closeWebContentsModalDialog { 165 DCHECK(constrainedWindow_); 166 constrainedWindow_->CloseWebContentsModalDialog(); 167} 168 169- (NSWindow*)overlayWindow { 170 return overlayWindow_; 171} 172 173- (SFChooseIdentityPanel*)panel { 174 return panel_; 175} 176 177- (void)showSheetForWindow:(NSWindow*)window { 178 NSString* title = l10n_util::GetNSString(IDS_CLIENT_CERT_DIALOG_TITLE); 179 overlayWindow_.reset([window retain]); 180 [panel_ beginSheetForWindow:window 181 modalDelegate:self 182 didEndSelector:@selector(sheetDidEnd:returnCode:context:) 183 contextInfo:NULL 184 identities:base::mac::CFToNSCast(identities_) 185 message:title]; 186} 187 188- (void)closeSheetWithAnimation:(BOOL)withAnimation { 189 closePending_ = YES; 190 overlayWindow_.reset(); 191 // Closing the sheet using -[NSApp endSheet:] doesn't work so use the private 192 // method. 193 [panel_ _dismissWithCode:NSFileHandlingPanelCancelButton]; 194} 195 196- (void)hideSheet { 197 NSWindow* sheetWindow = [overlayWindow_ attachedSheet]; 198 [sheetWindow setAlphaValue:0.0]; 199 200 oldResizesSubviews_ = [[sheetWindow contentView] autoresizesSubviews]; 201 [[sheetWindow contentView] setAutoresizesSubviews:NO]; 202 203 oldSheetFrame_ = [sheetWindow frame]; 204 NSRect overlayFrame = [overlayWindow_ frame]; 205 oldSheetFrame_.origin.x -= NSMinX(overlayFrame); 206 oldSheetFrame_.origin.y -= NSMinY(overlayFrame); 207 [sheetWindow setFrame:ui::kWindowSizeDeterminedLater display:NO]; 208} 209 210- (void)unhideSheet { 211 NSWindow* sheetWindow = [overlayWindow_ attachedSheet]; 212 NSRect overlayFrame = [overlayWindow_ frame]; 213 oldSheetFrame_.origin.x += NSMinX(overlayFrame); 214 oldSheetFrame_.origin.y += NSMinY(overlayFrame); 215 [sheetWindow setFrame:oldSheetFrame_ display:NO]; 216 [[sheetWindow contentView] setAutoresizesSubviews:oldResizesSubviews_]; 217 [[overlayWindow_ attachedSheet] setAlphaValue:1.0]; 218} 219 220- (void)pulseSheet { 221 // NOOP 222} 223 224- (void)makeSheetKeyAndOrderFront { 225 [[overlayWindow_ attachedSheet] makeKeyAndOrderFront:nil]; 226} 227 228- (void)updateSheetPosition { 229 // NOOP 230} 231 232- (void)onConstrainedWindowClosed { 233 observer_->StopObserving(); 234 panel_.reset(); 235 constrainedWindow_.reset(); 236 [self release]; 237} 238 239@end 240