1 // Copyright (c) 2011 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 #include "chrome/browser/ssl_client_certificate_selector.h" 6 7 #import <SecurityInterface/SFChooseIdentityPanel.h> 8 9 #include <vector> 10 11 #include "base/logging.h" 12 #include "base/memory/ref_counted.h" 13 #import "base/memory/scoped_nsobject.h" 14 #include "base/string_util.h" 15 #include "base/sys_string_conversions.h" 16 #include "base/utf_string_conversions.h" 17 #include "chrome/browser/ssl/ssl_client_auth_handler.h" 18 #import "chrome/browser/ui/cocoa/constrained_window_mac.h" 19 #include "content/browser/browser_thread.h" 20 #include "content/browser/tab_contents/tab_contents.h" 21 #include "grit/generated_resources.h" 22 #include "net/base/x509_certificate.h" 23 #include "ui/base/l10n/l10n_util_mac.h" 24 25 namespace { 26 27 class ConstrainedSFChooseIdentityPanel 28 : public ConstrainedWindowMacDelegateSystemSheet { 29 public: 30 ConstrainedSFChooseIdentityPanel(SFChooseIdentityPanel* panel, 31 id delegate, SEL didEndSelector, 32 NSArray* identities, NSString* message) 33 : ConstrainedWindowMacDelegateSystemSheet(delegate, didEndSelector), 34 identities_([identities retain]), 35 message_([message retain]) { 36 set_sheet(panel); 37 } 38 39 virtual ~ConstrainedSFChooseIdentityPanel() { 40 // As required by ConstrainedWindowMacDelegate, close the sheet if 41 // it's still open. 42 if (is_sheet_open()) { 43 [NSApp endSheet:sheet() 44 returnCode:NSFileHandlingPanelCancelButton]; 45 } 46 } 47 48 // ConstrainedWindowMacDelegateSystemSheet implementation: 49 virtual void DeleteDelegate() { 50 delete this; 51 } 52 53 // SFChooseIdentityPanel's beginSheetForWindow: method has more arguments 54 // than the usual one. Also pass the panel through contextInfo argument 55 // because the callback has the wrong signature. 56 virtual NSArray* GetSheetParameters(id delegate, SEL didEndSelector) { 57 return [NSArray arrayWithObjects: 58 [NSNull null], // window, must be [NSNull null] 59 delegate, 60 [NSValue valueWithPointer:didEndSelector], 61 [NSValue valueWithPointer:sheet()], 62 identities_.get(), 63 message_.get(), 64 nil]; 65 } 66 67 private: 68 scoped_nsobject<NSArray> identities_; 69 scoped_nsobject<NSString> message_; 70 DISALLOW_COPY_AND_ASSIGN(ConstrainedSFChooseIdentityPanel); 71 }; 72 73 } // namespace 74 75 @interface SSLClientCertificateSelectorCocoa : NSObject { 76 @private 77 // The handler to report back to. 78 scoped_refptr<SSLClientAuthHandler> handler_; 79 // The certificate request we serve. 80 scoped_refptr<net::SSLCertRequestInfo> certRequestInfo_; 81 // The list of identities offered to the user. 82 scoped_nsobject<NSMutableArray> identities_; 83 // The corresponding list of certificates. 84 std::vector<scoped_refptr<net::X509Certificate> > certificates_; 85 // The currently open dialog. 86 ConstrainedWindow* window_; 87 } 88 89 - (id)initWithHandler:(SSLClientAuthHandler*)handler 90 certRequestInfo:(net::SSLCertRequestInfo*)certRequestInfo; 91 - (void)displayDialog:(TabContents*)parent; 92 @end 93 94 namespace browser { 95 96 void ShowSSLClientCertificateSelector( 97 TabContents* parent, 98 net::SSLCertRequestInfo* cert_request_info, 99 SSLClientAuthHandler* delegate) { 100 // TODO(davidben): Implement a tab-modal dialog. 101 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 102 SSLClientCertificateSelectorCocoa* selector = 103 [[[SSLClientCertificateSelectorCocoa alloc] 104 initWithHandler:delegate 105 certRequestInfo:cert_request_info] autorelease]; 106 [selector displayDialog:parent]; 107 } 108 109 } // namespace browser 110 111 @implementation SSLClientCertificateSelectorCocoa 112 113 - (id)initWithHandler:(SSLClientAuthHandler*)handler 114 certRequestInfo:(net::SSLCertRequestInfo*)certRequestInfo { 115 DCHECK(handler); 116 DCHECK(certRequestInfo); 117 if ((self = [super init])) { 118 handler_ = handler; 119 certRequestInfo_ = certRequestInfo; 120 window_ = NULL; 121 } 122 return self; 123 } 124 125 - (void)sheetDidEnd:(NSWindow*)parent 126 returnCode:(NSInteger)returnCode 127 context:(void*)context { 128 DCHECK(context); 129 SFChooseIdentityPanel* panel = static_cast<SFChooseIdentityPanel*>(context); 130 131 net::X509Certificate* cert = NULL; 132 if (returnCode == NSFileHandlingPanelOKButton) { 133 NSUInteger index = [identities_ indexOfObject:(id)[panel identity]]; 134 if (index != NSNotFound) 135 cert = certificates_[index]; 136 else 137 NOTREACHED(); 138 } 139 140 // Finally, tell the backend which identity (or none) the user selected. 141 handler_->CertificateSelected(cert); 142 // Close the constrained window. 143 DCHECK(window_); 144 window_->CloseConstrainedWindow(); 145 146 // Now that the panel has closed, release it. Note that the autorelease is 147 // needed. After this callback returns, the panel is still accessed, so a 148 // normal release crashes. 149 [panel autorelease]; 150 } 151 152 - (void)displayDialog:(TabContents*)parent { 153 DCHECK(!window_); 154 // Create an array of CFIdentityRefs for the certificates: 155 size_t numCerts = certRequestInfo_->client_certs.size(); 156 identities_.reset([[NSMutableArray alloc] initWithCapacity:numCerts]); 157 for (size_t i = 0; i < numCerts; ++i) { 158 SecCertificateRef cert; 159 cert = certRequestInfo_->client_certs[i]->os_cert_handle(); 160 SecIdentityRef identity; 161 if (SecIdentityCreateWithCertificate(NULL, cert, &identity) == noErr) { 162 [identities_ addObject:(id)identity]; 163 CFRelease(identity); 164 certificates_.push_back(certRequestInfo_->client_certs[i]); 165 } 166 } 167 168 // Get the message to display: 169 NSString* title = l10n_util::GetNSString(IDS_CLIENT_CERT_DIALOG_TITLE); 170 NSString* message = l10n_util::GetNSStringF( 171 IDS_CLIENT_CERT_DIALOG_TEXT, 172 ASCIIToUTF16(certRequestInfo_->host_and_port)); 173 174 // Create and set up a system choose-identity panel. 175 SFChooseIdentityPanel* panel = [[SFChooseIdentityPanel alloc] init]; 176 [panel setInformativeText:message]; 177 [panel setDefaultButtonTitle:l10n_util::GetNSString(IDS_OK)]; 178 [panel setAlternateButtonTitle:l10n_util::GetNSString(IDS_CANCEL)]; 179 SecPolicyRef sslPolicy; 180 if (net::X509Certificate::CreateSSLClientPolicy(&sslPolicy) == noErr) { 181 [panel setPolicies:(id)sslPolicy]; 182 CFRelease(sslPolicy); 183 } 184 185 window_ = 186 parent->CreateConstrainedDialog(new ConstrainedSFChooseIdentityPanel( 187 panel, self, 188 @selector(sheetDidEnd:returnCode:context:), 189 identities_, title)); 190 // Note: SFChooseIdentityPanel does not take a reference to itself while the 191 // sheet is open. Don't release the ownership claim until the sheet has ended 192 // in |-sheetDidEnd:returnCode:context:|. 193 } 194 195 @end 196