// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import "chrome/browser/ui/cocoa/ssl_client_certificate_selector_cocoa.h" #import #include "base/logging.h" #include "base/mac/foundation_util.h" #include "base/string_util.h" #include "base/strings/sys_string_conversions.h" #include "base/utf_string_conversions.h" #include "chrome/browser/ssl/ssl_client_auth_observer.h" #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_mac.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents_view.h" #include "grit/generated_resources.h" #include "net/cert/x509_certificate.h" #include "net/cert/x509_util_mac.h" #include "net/ssl/ssl_cert_request_info.h" #include "ui/base/cocoa/window_size_constants.h" #include "ui/base/l10n/l10n_util_mac.h" using content::BrowserThread; @interface SFChooseIdentityPanel (SystemPrivate) // A system-private interface that dismisses a panel whose sheet was started by // -beginSheetForWindow:modalDelegate:didEndSelector:contextInfo:identities:message: // as though the user clicked the button identified by returnCode. Verified // present in 10.5 through 10.8. - (void)_dismissWithCode:(NSInteger)code; @end @interface SSLClientCertificateSelectorCocoa () - (void)onConstrainedWindowClosed; @end class SSLClientAuthObserverCocoaBridge : public SSLClientAuthObserver, public ConstrainedWindowMacDelegate { public: SSLClientAuthObserverCocoaBridge( const net::HttpNetworkSession* network_session, net::SSLCertRequestInfo* cert_request_info, const chrome::SelectCertificateCallback& callback, SSLClientCertificateSelectorCocoa* controller) : SSLClientAuthObserver(network_session, cert_request_info, callback), controller_(controller) { } // SSLClientAuthObserver implementation: virtual void OnCertSelectedByNotification() OVERRIDE { [controller_ closeSheetWithAnimation:NO]; } // ConstrainedWindowMacDelegate implementation: virtual void OnConstrainedWindowClosed( ConstrainedWindowMac* window) OVERRIDE { // |onConstrainedWindowClosed| will delete the sheet which might be still // in use higher up the call stack. Wait for the next cycle of the event // loop to call this function. [controller_ performSelector:@selector(onConstrainedWindowClosed) withObject:nil afterDelay:0]; } private: SSLClientCertificateSelectorCocoa* controller_; // weak }; namespace chrome { void ShowSSLClientCertificateSelector( content::WebContents* contents, const net::HttpNetworkSession* network_session, net::SSLCertRequestInfo* cert_request_info, const SelectCertificateCallback& callback) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); // The dialog manages its own lifetime. SSLClientCertificateSelectorCocoa* selector = [[SSLClientCertificateSelectorCocoa alloc] initWithNetworkSession:network_session certRequestInfo:cert_request_info callback:callback]; [selector displayForWebContents:contents]; } } // namespace chrome @implementation SSLClientCertificateSelectorCocoa - (id)initWithNetworkSession:(const net::HttpNetworkSession*)networkSession certRequestInfo:(net::SSLCertRequestInfo*)certRequestInfo callback:(const chrome::SelectCertificateCallback&)callback { DCHECK(networkSession); DCHECK(certRequestInfo); if ((self = [super init])) { observer_.reset(new SSLClientAuthObserverCocoaBridge( networkSession, certRequestInfo, callback, self)); } return self; } - (void)sheetDidEnd:(NSWindow*)parent returnCode:(NSInteger)returnCode context:(void*)context { net::X509Certificate* cert = NULL; if (returnCode == NSFileHandlingPanelOKButton) { CFRange range = CFRangeMake(0, CFArrayGetCount(identities_)); CFIndex index = CFArrayGetFirstIndexOfValue(identities_, range, [panel_ identity]); if (index != -1) cert = certificates_[index]; else NOTREACHED(); } // Finally, tell the backend which identity (or none) the user selected. observer_->StopObserving(); observer_->CertificateSelected(cert); if (!closePending_) constrainedWindow_->CloseWebContentsModalDialog(); } - (void)displayForWebContents:(content::WebContents*)webContents { // Create an array of CFIdentityRefs for the certificates: size_t numCerts = observer_->cert_request_info()->client_certs.size(); identities_.reset(CFArrayCreateMutable( kCFAllocatorDefault, numCerts, &kCFTypeArrayCallBacks)); for (size_t i = 0; i < numCerts; ++i) { SecCertificateRef cert = observer_->cert_request_info()->client_certs[i]->os_cert_handle(); SecIdentityRef identity; if (SecIdentityCreateWithCertificate(NULL, cert, &identity) == noErr) { CFArrayAppendValue(identities_, identity); CFRelease(identity); certificates_.push_back(observer_->cert_request_info()->client_certs[i]); } } // Get the message to display: NSString* message = l10n_util::GetNSStringF( IDS_CLIENT_CERT_DIALOG_TEXT, ASCIIToUTF16(observer_->cert_request_info()->host_and_port)); // Create and set up a system choose-identity panel. panel_.reset([[SFChooseIdentityPanel alloc] init]); [panel_ setInformativeText:message]; [panel_ setDefaultButtonTitle:l10n_util::GetNSString(IDS_OK)]; [panel_ setAlternateButtonTitle:l10n_util::GetNSString(IDS_CANCEL)]; SecPolicyRef sslPolicy; if (net::x509_util::CreateSSLClientPolicy(&sslPolicy) == noErr) { [panel_ setPolicies:(id)sslPolicy]; CFRelease(sslPolicy); } constrainedWindow_.reset( new ConstrainedWindowMac(observer_.get(), webContents, self)); } - (NSWindow*)overlayWindow { return overlayWindow_; } - (SFChooseIdentityPanel*)panel { return panel_; } - (void)showSheetForWindow:(NSWindow*)window { NSString* title = l10n_util::GetNSString(IDS_CLIENT_CERT_DIALOG_TITLE); overlayWindow_.reset([window retain]); [panel_ beginSheetForWindow:window modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:context:) contextInfo:NULL identities:base::mac::CFToNSCast(identities_) message:title]; observer_->StartObserving(); } - (void)closeSheetWithAnimation:(BOOL)withAnimation { closePending_ = YES; overlayWindow_.reset(); // Closing the sheet using -[NSApp endSheet:] doesn't work so use the private // method. [panel_ _dismissWithCode:NSFileHandlingPanelCancelButton]; } - (void)hideSheet { NSWindow* sheetWindow = [overlayWindow_ attachedSheet]; [sheetWindow setAlphaValue:0.0]; oldResizesSubviews_ = [[sheetWindow contentView] autoresizesSubviews]; [[sheetWindow contentView] setAutoresizesSubviews:NO]; oldSheetFrame_ = [sheetWindow frame]; NSRect overlayFrame = [overlayWindow_ frame]; oldSheetFrame_.origin.x -= NSMinX(overlayFrame); oldSheetFrame_.origin.y -= NSMinY(overlayFrame); [sheetWindow setFrame:ui::kWindowSizeDeterminedLater display:NO]; } - (void)unhideSheet { NSWindow* sheetWindow = [overlayWindow_ attachedSheet]; NSRect overlayFrame = [overlayWindow_ frame]; oldSheetFrame_.origin.x += NSMinX(overlayFrame); oldSheetFrame_.origin.y += NSMinY(overlayFrame); [sheetWindow setFrame:oldSheetFrame_ display:NO]; [[sheetWindow contentView] setAutoresizesSubviews:oldResizesSubviews_]; [[overlayWindow_ attachedSheet] setAlphaValue:1.0]; } - (void)pulseSheet { // NOOP } - (void)makeSheetKeyAndOrderFront { [[overlayWindow_ attachedSheet] makeKeyAndOrderFront:nil]; } - (void)updateSheetPosition { // NOOP } - (void)onConstrainedWindowClosed { panel_.reset(); constrainedWindow_.reset(); [self release]; } @end