Home | History | Annotate | Download | only in cocoa
      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