Home | History | Annotate | Download | only in cocoa
      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.
      4 
      5 #import "chrome/browser/ui/cocoa/certificate_viewer_mac.h"
      6 
      7 #include <Security/Security.h>
      8 #include <SecurityInterface/SFCertificatePanel.h>
      9 #include <vector>
     10 
     11 #include "base/mac/foundation_util.h"
     12 #include "base/mac/scoped_cftyperef.h"
     13 #include "chrome/browser/certificate_viewer.h"
     14 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_mac.h"
     15 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_sheet.h"
     16 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_sheet_controller.h"
     17 #include "net/cert/x509_certificate.h"
     18 #include "net/cert/x509_util_mac.h"
     19 #import "ui/base/cocoa/window_size_constants.h"
     20 
     21 class SSLCertificateViewerCocoaBridge;
     22 
     23 @interface SFCertificatePanel (SystemPrivate)
     24 // A system-private interface that dismisses a panel whose sheet was started by
     25 // -beginSheetForWindow:
     26 //        modalDelegate:
     27 //       didEndSelector:
     28 //          contextInfo:
     29 //         certificates:
     30 //            showGroup:
     31 // as though the user clicked the button identified by returnCode. Verified
     32 // present in 10.8.
     33 - (void)_dismissWithCode:(NSInteger)code;
     34 @end
     35 
     36 @interface SSLCertificateViewerCocoa ()
     37 - (void)onConstrainedWindowClosed;
     38 @end
     39 
     40 class SSLCertificateViewerCocoaBridge : public ConstrainedWindowMacDelegate {
     41  public:
     42   explicit SSLCertificateViewerCocoaBridge(SSLCertificateViewerCocoa *
     43                                            controller)
     44       : controller_(controller) {
     45   }
     46 
     47   virtual ~SSLCertificateViewerCocoaBridge() {}
     48 
     49   // ConstrainedWindowMacDelegate implementation:
     50   virtual void OnConstrainedWindowClosed(
     51       ConstrainedWindowMac * window) OVERRIDE {
     52     // |onConstrainedWindowClosed| will delete the sheet which might be still
     53     // in use higher up the call stack. Wait for the next cycle of the event
     54     // loop to call this function.
     55     [controller_ performSelector:@selector(onConstrainedWindowClosed)
     56                       withObject:nil
     57                       afterDelay:0];
     58   }
     59 
     60  private:
     61   SSLCertificateViewerCocoa* controller_;  // weak
     62 
     63   DISALLOW_COPY_AND_ASSIGN(SSLCertificateViewerCocoaBridge);
     64 };
     65 
     66 void ShowCertificateViewer(content::WebContents* web_contents,
     67                            gfx::NativeWindow parent,
     68                            net::X509Certificate* cert) {
     69   // SSLCertificateViewerCocoa will manage its own lifetime and will release
     70   // itself when the dialog is closed.
     71   // See -[SSLCertificateViewerCocoa onConstrainedWindowClosed].
     72   SSLCertificateViewerCocoa* viewer =
     73       [[SSLCertificateViewerCocoa alloc] initWithCertificate:cert];
     74   [viewer displayForWebContents:web_contents];
     75 }
     76 
     77 @implementation SSLCertificateViewerCocoa
     78 
     79 - (id)initWithCertificate:(net::X509Certificate*)certificate {
     80   if ((self = [super init])) {
     81     base::ScopedCFTypeRef<CFArrayRef> cert_chain(
     82         certificate->CreateOSCertChainForCert());
     83     NSArray* certificates = base::mac::CFToNSCast(cert_chain.get());
     84     certificates_.reset([certificates retain]);
     85   }
     86   return self;
     87 }
     88 
     89 - (void)sheetDidEnd:(NSWindow*)parent
     90          returnCode:(NSInteger)returnCode
     91             context:(void*)context {
     92   if (!closePending_)
     93     constrainedWindow_->CloseWebContentsModalDialog();
     94 }
     95 
     96 - (void)displayForWebContents:(content::WebContents*)webContents {
     97   // Explicitly disable revocation checking, regardless of user preferences
     98   // or system settings. The behaviour of SFCertificatePanel is to call
     99   // SecTrustEvaluate on the certificate(s) supplied, effectively
    100   // duplicating the behaviour of net::X509Certificate::Verify(). However,
    101   // this call stalls the UI if revocation checking is enabled in the
    102   // Keychain preferences or if the cert may be an EV cert. By disabling
    103   // revocation checking, the stall is limited to the time taken for path
    104   // building and verification, which should be minimized due to the path
    105   // being provided in |certificates|. This does not affect normal
    106   // revocation checking from happening, which is controlled by
    107   // net::X509Certificate::Verify() and user preferences, but will prevent
    108   // the certificate viewer UI from displaying which certificate is revoked.
    109   // This is acceptable, as certificate revocation will still be shown in
    110   // the page info bubble if a certificate in the chain is actually revoked.
    111   base::ScopedCFTypeRef<CFMutableArrayRef> policies(
    112       CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks));
    113   if (!policies.get()) {
    114     NOTREACHED();
    115     return;
    116   }
    117   // Add a basic X.509 policy, in order to match the behaviour of
    118   // SFCertificatePanel when no policies are specified.
    119   SecPolicyRef basic_policy = NULL;
    120   OSStatus status = net::x509_util::CreateBasicX509Policy(&basic_policy);
    121   if (status != noErr) {
    122     NOTREACHED();
    123     return;
    124   }
    125   CFArrayAppendValue(policies, basic_policy);
    126   CFRelease(basic_policy);
    127 
    128   status = net::x509_util::CreateRevocationPolicies(false, false, policies);
    129   if (status != noErr) {
    130     NOTREACHED();
    131     return;
    132   }
    133 
    134   panel_.reset([[SFCertificatePanel alloc] init]);
    135   [panel_ setPolicies:(id) policies.get()];
    136 
    137   constrainedWindow_.reset(
    138       new ConstrainedWindowMac(observer_.get(), webContents, self));
    139 }
    140 
    141 - (NSWindow*)overlayWindow {
    142   return overlayWindow_;
    143 }
    144 
    145 - (void)showSheetForWindow:(NSWindow*)window {
    146   overlayWindow_.reset([window retain]);
    147   [panel_ beginSheetForWindow:window
    148                 modalDelegate:self
    149                didEndSelector:@selector(sheetDidEnd:
    150                                          returnCode:
    151                                             context:)
    152                   contextInfo:NULL
    153                  certificates:certificates_
    154                     showGroup:YES];
    155 }
    156 
    157 - (void)closeSheetWithAnimation:(BOOL)withAnimation {
    158   closePending_ = YES;
    159   overlayWindow_.reset();
    160   // Closing the sheet using -[NSApp endSheet:] doesn't work so use the private
    161   // method.
    162   [panel_ _dismissWithCode:NSFileHandlingPanelCancelButton];
    163 }
    164 
    165 - (void)hideSheet {
    166   NSWindow* sheetWindow = [overlayWindow_ attachedSheet];
    167   [sheetWindow setAlphaValue:0.0];
    168 
    169   oldResizesSubviews_ = [[sheetWindow contentView] autoresizesSubviews];
    170   [[sheetWindow contentView] setAutoresizesSubviews:NO];
    171 
    172   oldSheetFrame_ = [sheetWindow frame];
    173   NSRect overlayFrame = [overlayWindow_ frame];
    174   oldSheetFrame_.origin.x -= NSMinX(overlayFrame);
    175   oldSheetFrame_.origin.y -= NSMinY(overlayFrame);
    176   [sheetWindow setFrame:ui::kWindowSizeDeterminedLater display:NO];
    177 }
    178 
    179 - (void)unhideSheet {
    180   NSWindow* sheetWindow = [overlayWindow_ attachedSheet];
    181   NSRect overlayFrame = [overlayWindow_ frame];
    182   oldSheetFrame_.origin.x += NSMinX(overlayFrame);
    183   oldSheetFrame_.origin.y += NSMinY(overlayFrame);
    184   [sheetWindow setFrame:oldSheetFrame_ display:NO];
    185   [[sheetWindow contentView] setAutoresizesSubviews:oldResizesSubviews_];
    186   [[overlayWindow_ attachedSheet] setAlphaValue:1.0];
    187 }
    188 
    189 - (void)pulseSheet {
    190   // NOOP
    191 }
    192 
    193 - (void)makeSheetKeyAndOrderFront {
    194   [[overlayWindow_ attachedSheet] makeKeyAndOrderFront:nil];
    195 }
    196 
    197 - (void)updateSheetPosition {
    198   // NOOP
    199 }
    200 
    201 - (void)onConstrainedWindowClosed {
    202   panel_.reset();
    203   constrainedWindow_.reset();
    204   [self release];
    205 }
    206 
    207 @end
    208