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.
5 #import "chrome/browser/ui/cocoa/ssl_client_certificate_selector_cocoa.h"
7 #import <SecurityInterface/SFChooseIdentityPanel.h>
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 "content/public/browser/browser_thread.h"
17 #include "content/public/browser/web_contents.h"
18 #include "grit/generated_resources.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"
25 using content::BrowserThread;
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;
35 @interface SSLClientCertificateSelectorCocoa ()
36 - (void)onConstrainedWindowClosed;
39 class SSLClientAuthObserverCocoaBridge : public SSLClientAuthObserver,
40 public ConstrainedWindowMacDelegate {
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) {
51 // SSLClientAuthObserver implementation:
52 virtual void OnCertSelectedByNotification() OVERRIDE {
53 [controller_ closeWebContentsModalDialog];
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)
68 SSLClientCertificateSelectorCocoa* controller_; // weak
73 void 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
85 [selector displayForWebContents:contents];
90 @implementation SSLClientCertificateSelectorCocoa
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));
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_));
111 CFArrayGetFirstIndexOfValue(identities_, range, [panel_ identity]);
113 cert = certificates_[index].get();
118 // Finally, tell the backend which identity (or none) the user selected.
119 observer_->StopObserving();
120 observer_->CertificateSelected(cert);
123 constrainedWindow_->CloseWebContentsModalDialog();
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);
138 certificates_.push_back(observer_->cert_request_info()->client_certs[i]);
142 // Get the message to display:
143 NSString* message = l10n_util::GetNSStringF(
144 IDS_CLIENT_CERT_DIALOG_TEXT,
146 observer_->cert_request_info()->host_and_port.ToString()));
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);
159 constrainedWindow_.reset(
160 new ConstrainedWindowMac(observer_.get(), webContents, self));
161 observer_->StartObserving();
164 - (void)closeWebContentsModalDialog {
165 DCHECK(constrainedWindow_);
166 constrainedWindow_->CloseWebContentsModalDialog();
169 - (NSWindow*)overlayWindow {
170 return overlayWindow_;
173 - (SFChooseIdentityPanel*)panel {
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
182 didEndSelector:@selector(sheetDidEnd:returnCode:context:)
184 identities:base::mac::CFToNSCast(identities_)
188 - (void)closeSheetWithAnimation:(BOOL)withAnimation {
190 overlayWindow_.reset();
191 // Closing the sheet using -[NSApp endSheet:] doesn't work so use the private
193 [panel_ _dismissWithCode:NSFileHandlingPanelCancelButton];
197 NSWindow* sheetWindow = [overlayWindow_ attachedSheet];
198 [sheetWindow setAlphaValue:0.0];
200 oldResizesSubviews_ = [[sheetWindow contentView] autoresizesSubviews];
201 [[sheetWindow contentView] setAutoresizesSubviews:NO];
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];
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];
224 - (void)makeSheetKeyAndOrderFront {
225 [[overlayWindow_ attachedSheet] makeKeyAndOrderFront:nil];
228 - (void)updateSheetPosition {
232 - (void)onConstrainedWindowClosed {
233 observer_->StopObserving();
235 constrainedWindow_.reset();