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
25namespace {
26
27class 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
94namespace browser {
95
96void 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