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 "chrome/grit/generated_resources.h"
17 #include "components/web_modal/popup_manager.h"
18 #include "content/public/browser/browser_thread.h"
19 #include "content/public/browser/client_certificate_delegate.h"
20 #include "content/public/browser/web_contents.h"
21 #include "net/cert/x509_certificate.h"
22 #include "net/cert/x509_util_mac.h"
23 #include "net/ssl/ssl_cert_request_info.h"
24 #include "ui/base/cocoa/window_size_constants.h"
25 #include "ui/base/l10n/l10n_util_mac.h"
27 using content::BrowserThread;
29 @interface SFChooseIdentityPanel (SystemPrivate)
30 // A system-private interface that dismisses a panel whose sheet was started by
31 // -beginSheetForWindow:modalDelegate:didEndSelector:contextInfo:identities:message:
32 // as though the user clicked the button identified by returnCode. Verified
33 // present in 10.5 through 10.8.
34 - (void)_dismissWithCode:(NSInteger)code;
37 @interface SSLClientCertificateSelectorCocoa ()
38 - (void)onConstrainedWindowClosed;
41 class SSLClientAuthObserverCocoaBridge : public SSLClientAuthObserver,
42 public ConstrainedWindowMacDelegate {
44 SSLClientAuthObserverCocoaBridge(
45 const content::BrowserContext* browser_context,
46 net::SSLCertRequestInfo* cert_request_info,
47 scoped_ptr<content::ClientCertificateDelegate> delegate,
48 SSLClientCertificateSelectorCocoa* controller)
49 : SSLClientAuthObserver(browser_context,
52 controller_(controller) {}
54 // SSLClientAuthObserver implementation:
55 void OnCertSelectedByNotification() override {
56 [controller_ closeWebContentsModalDialog];
59 // ConstrainedWindowMacDelegate implementation:
60 void OnConstrainedWindowClosed(ConstrainedWindowMac* window) override {
61 // |onConstrainedWindowClosed| will delete the sheet which might be still
62 // in use higher up the call stack. Wait for the next cycle of the event
63 // loop to call this function.
64 [controller_ performSelector:@selector(onConstrainedWindowClosed)
70 SSLClientCertificateSelectorCocoa* controller_; // weak
75 void ShowSSLClientCertificateSelector(
76 content::WebContents* contents,
77 net::SSLCertRequestInfo* cert_request_info,
78 scoped_ptr<content::ClientCertificateDelegate> delegate) {
79 DCHECK_CURRENTLY_ON(BrowserThread::UI);
81 // Not all WebContentses can show modal dialogs.
83 // TODO(davidben): Move this hook to the WebContentsDelegate and only try to
84 // show a dialog in Browser's implementation. https://crbug.com/456255
85 if (web_modal::PopupManager::FromWebContents(contents) == nullptr)
88 // The dialog manages its own lifetime.
89 SSLClientCertificateSelectorCocoa* selector =
90 [[SSLClientCertificateSelectorCocoa alloc]
91 initWithBrowserContext:contents->GetBrowserContext()
92 certRequestInfo:cert_request_info
93 delegate:delegate.Pass()];
94 [selector displayForWebContents:contents];
99 @implementation SSLClientCertificateSelectorCocoa
101 - (id)initWithBrowserContext:(const content::BrowserContext*)browserContext
102 certRequestInfo:(net::SSLCertRequestInfo*)certRequestInfo
103 delegate:(scoped_ptr<content::ClientCertificateDelegate>)
105 DCHECK(browserContext);
106 DCHECK(certRequestInfo);
107 if ((self = [super init])) {
108 observer_.reset(new SSLClientAuthObserverCocoaBridge(
109 browserContext, certRequestInfo, delegate.Pass(), self));
114 - (void)sheetDidEnd:(NSWindow*)parent
115 returnCode:(NSInteger)returnCode
116 context:(void*)context {
117 net::X509Certificate* cert = NULL;
118 if (returnCode == NSFileHandlingPanelOKButton) {
119 CFRange range = CFRangeMake(0, CFArrayGetCount(identities_));
121 CFArrayGetFirstIndexOfValue(identities_, range, [panel_ identity]);
123 cert = certificates_[index].get();
128 if (!closePending_) {
129 // If |closePending_| is already set, |closeSheetWithAnimation:| was called
130 // already to cancel the selection rather than continue with no
131 // certificate. Otherwise, tell the backend which identity (or none) the
133 userResponded_ = YES;
134 observer_->CertificateSelected(cert);
136 constrainedWindow_->CloseWebContentsModalDialog();
140 - (void)displayForWebContents:(content::WebContents*)webContents {
141 // Create an array of CFIdentityRefs for the certificates:
142 size_t numCerts = observer_->cert_request_info()->client_certs.size();
143 identities_.reset(CFArrayCreateMutable(
144 kCFAllocatorDefault, numCerts, &kCFTypeArrayCallBacks));
145 for (size_t i = 0; i < numCerts; ++i) {
146 SecCertificateRef cert =
147 observer_->cert_request_info()->client_certs[i]->os_cert_handle();
148 SecIdentityRef identity;
149 if (SecIdentityCreateWithCertificate(NULL, cert, &identity) == noErr) {
150 CFArrayAppendValue(identities_, identity);
152 certificates_.push_back(observer_->cert_request_info()->client_certs[i]);
156 // Get the message to display:
157 NSString* message = l10n_util::GetNSStringF(
158 IDS_CLIENT_CERT_DIALOG_TEXT,
160 observer_->cert_request_info()->host_and_port.ToString()));
162 // Create and set up a system choose-identity panel.
163 panel_.reset([[SFChooseIdentityPanel alloc] init]);
164 [panel_ setInformativeText:message];
165 [panel_ setDefaultButtonTitle:l10n_util::GetNSString(IDS_OK)];
166 [panel_ setAlternateButtonTitle:l10n_util::GetNSString(IDS_CANCEL)];
167 SecPolicyRef sslPolicy;
168 if (net::x509_util::CreateSSLClientPolicy(&sslPolicy) == noErr) {
169 [panel_ setPolicies:(id)sslPolicy];
170 CFRelease(sslPolicy);
173 constrainedWindow_.reset(
174 new ConstrainedWindowMac(observer_.get(), webContents, self));
175 observer_->StartObserving();
178 - (void)closeWebContentsModalDialog {
179 DCHECK(constrainedWindow_);
180 constrainedWindow_->CloseWebContentsModalDialog();
183 - (NSWindow*)overlayWindow {
184 return overlayWindow_;
187 - (SFChooseIdentityPanel*)panel {
191 - (void)showSheetForWindow:(NSWindow*)window {
192 NSString* title = l10n_util::GetNSString(IDS_CLIENT_CERT_DIALOG_TITLE);
193 overlayWindow_.reset([window retain]);
194 [panel_ beginSheetForWindow:window
196 didEndSelector:@selector(sheetDidEnd:returnCode:context:)
198 identities:base::mac::CFToNSCast(identities_)
202 - (void)closeSheetWithAnimation:(BOOL)withAnimation {
203 if (!userResponded_) {
204 // If the sheet is closed by closing the tab rather than the user explicitly
205 // hitting Cancel, |closeSheetWithAnimation:| gets called before
206 // |sheetDidEnd:|. In this case, the selection should be canceled rather
207 // than continue with no certificate. The |returnCode| parameter to
208 // |sheetDidEnd:| is the same in both cases.
209 observer_->CancelCertificateSelection();
212 overlayWindow_.reset();
213 // Closing the sheet using -[NSApp endSheet:] doesn't work so use the private
215 [panel_ _dismissWithCode:NSFileHandlingPanelCancelButton];
219 NSWindow* sheetWindow = [overlayWindow_ attachedSheet];
220 [sheetWindow setAlphaValue:0.0];
222 oldResizesSubviews_ = [[sheetWindow contentView] autoresizesSubviews];
223 [[sheetWindow contentView] setAutoresizesSubviews:NO];
225 oldSheetFrame_ = [sheetWindow frame];
226 NSRect overlayFrame = [overlayWindow_ frame];
227 oldSheetFrame_.origin.x -= NSMinX(overlayFrame);
228 oldSheetFrame_.origin.y -= NSMinY(overlayFrame);
229 [sheetWindow setFrame:ui::kWindowSizeDeterminedLater display:NO];
232 - (void)unhideSheet {
233 NSWindow* sheetWindow = [overlayWindow_ attachedSheet];
234 NSRect overlayFrame = [overlayWindow_ frame];
235 oldSheetFrame_.origin.x += NSMinX(overlayFrame);
236 oldSheetFrame_.origin.y += NSMinY(overlayFrame);
237 [sheetWindow setFrame:oldSheetFrame_ display:NO];
238 [[sheetWindow contentView] setAutoresizesSubviews:oldResizesSubviews_];
239 [[overlayWindow_ attachedSheet] setAlphaValue:1.0];
246 - (void)makeSheetKeyAndOrderFront {
247 [[overlayWindow_ attachedSheet] makeKeyAndOrderFront:nil];
250 - (void)updateSheetPosition {
254 - (NSWindow*)sheetWindow {
258 - (void)onConstrainedWindowClosed {
259 observer_->StopObserving();
261 constrainedWindow_.reset();