blob: c8183de3ffe1e68353e5f53bb99d742b9a35f973 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <Cocoa/Cocoa.h>
#import <SecurityInterface/SFCertificatePanel.h>
#include "base/mac/foundation_util.h"
#include "base/mac/scoped_cftyperef.h"
#import "base/mac/scoped_nsobject.h"
#include "chrome/browser/certificate_viewer.h"
#include "components/constrained_window/constrained_window_views.h"
#include "components/web_modal/web_contents_modal_dialog_manager.h"
#include "content/public/browser/web_contents.h"
#include "net/cert/x509_certificate.h"
#include "net/cert/x509_util_ios_and_mac.h"
#include "net/cert/x509_util_mac.h"
#include "ui/base/cocoa/remote_views_window.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
@interface SFCertificatePanel (SystemPrivate)
// A system-private interface that dismisses a panel whose sheet was started by
// -beginSheetForWindow:
// modalDelegate:
// didEndSelector:
// contextInfo:
// certificates:
// showGroup:
// as though the user clicked the button identified by returnCode. Verified
// present in 10.8.
- (void)_dismissWithCode:(NSInteger)code;
@end
@interface SSLCertificateViewerMac : NSObject
// Initializes |certificates_| with the certificate chain for a given
// certificate.
- (instancetype)initWithCertificate:(net::X509Certificate*)certificate
forWebContents:(content::WebContents*)webContents;
// Shows the certificate viewer as a Cocoa sheet.
- (void)showCertificateSheet:(NSWindow*)window;
// Closes the certificate viewer sheet.
- (void)closeCertificateSheet;
- (void)setOverlayWindow:(views::Widget*)overlayWindow;
// Closes the certificate viewer Cocoa sheet.
- (void)sheetDidEnd:(NSWindow*)parent
returnCode:(NSInteger)returnCode
context:(void*)context;
@end
@implementation SSLCertificateViewerMac {
// The corresponding list of certificates.
base::scoped_nsobject<NSArray> certificates_;
base::scoped_nsobject<SFCertificatePanel> panel_;
// Invisible overlay window used to block interaction with the tab underneath.
views::Widget* overlayWindow_;
}
- (instancetype)initWithCertificate:(net::X509Certificate*)certificate
forWebContents:(content::WebContents*)webContents {
if ((self = [super init])) {
base::ScopedCFTypeRef<CFArrayRef> certChain(
net::x509_util::CreateSecCertificateArrayForX509Certificate(
certificate));
if (!certChain)
return self;
NSArray* certificates = base::mac::CFToNSCast(certChain.get());
certificates_.reset([certificates retain]);
}
// Explicitly disable revocation checking, regardless of user preferences
// or system settings. The behaviour of SFCertificatePanel is to call
// SecTrustEvaluate on the certificate(s) supplied, effectively
// duplicating the behaviour of net::X509Certificate::Verify(). However,
// this call stalls the UI if revocation checking is enabled in the
// Keychain preferences or if the cert may be an EV cert. By disabling
// revocation checking, the stall is limited to the time taken for path
// building and verification, which should be minimized due to the path
// being provided in |certificates|. This does not affect normal
// revocation checking from happening, which is controlled by
// net::X509Certificate::Verify() and user preferences, but will prevent
// the certificate viewer UI from displaying which certificate is revoked.
// This is acceptable, as certificate revocation will still be shown in
// the page info bubble if a certificate in the chain is actually revoked.
base::ScopedCFTypeRef<CFMutableArrayRef> policies(
CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks));
if (!policies.get()) {
NOTREACHED();
return self;
}
// Add a basic X.509 policy, in order to match the behaviour of
// SFCertificatePanel when no policies are specified.
base::ScopedCFTypeRef<SecPolicyRef> basicPolicy;
OSStatus status =
net::x509_util::CreateBasicX509Policy(basicPolicy.InitializeInto());
if (status != noErr) {
NOTREACHED();
return self;
}
CFArrayAppendValue(policies, basicPolicy.get());
status = net::x509_util::CreateRevocationPolicies(false, policies);
if (status != noErr) {
NOTREACHED();
return self;
}
panel_.reset([[SFCertificatePanel alloc] init]);
[panel_ setPolicies:base::mac::CFToNSCast(policies.get())];
return self;
}
- (void)showCertificateSheet:(NSWindow*)window {
[panel_ beginSheetForWindow:window
modalDelegate:self
didEndSelector:@selector(sheetDidEnd:returnCode:context:)
contextInfo:nil
certificates:certificates_
showGroup:YES];
}
- (void)closeCertificateSheet {
// Closing the sheet using -[NSApp endSheet:] doesn't work so use the private
// method. If the sheet is already closed then this is a call on nil and thus
// a no-op.
[panel_ _dismissWithCode:NSModalResponseCancel];
}
- (void)sheetDidEnd:(NSWindow*)parent
returnCode:(NSInteger)returnCode
context:(void*)context {
overlayWindow_->Close(); // Asynchronously releases |self|.
panel_.reset();
}
- (void)setOverlayWindow:(views::Widget*)overlayWindow {
overlayWindow_ = overlayWindow;
}
@end
namespace {
// A fully transparent, borderless web-modal dialog used to display the
// OS-provided window-modal sheet that displays certificate information.
class CertificateAnchorWidgetDelegate : public views::WidgetDelegateView {
public:
CertificateAnchorWidgetDelegate(content::WebContents* web_contents,
net::X509Certificate* cert)
: certificate_viewer_([[SSLCertificateViewerMac alloc]
initWithCertificate:cert
forWebContents:web_contents]) {
views::Widget* overlayWindow =
constrained_window::ShowWebModalDialogWithOverlayViews(this,
web_contents);
NSWindow* overlayNSWindow =
overlayWindow->GetNativeWindow().GetNativeNSWindow();
// TODO(https://crbug.com/913303): The certificate viewer's interface to
// Cocoa should be wrapped in a mojo interface in order to allow
// instantiating across processes. As a temporary solution, create a
// transparent in-process window to the front.
if (ui::IsWindowUsingRemoteViews(overlayNSWindow)) {
remote_views_clone_window_ =
ui::CreateTransparentRemoteViewsClone(overlayNSWindow);
overlayNSWindow = remote_views_clone_window_;
}
[certificate_viewer_ showCertificateSheet:overlayNSWindow];
[certificate_viewer_ setOverlayWindow:overlayWindow];
}
~CertificateAnchorWidgetDelegate() override {
// Note that the SFCertificatePanel takes a reference to its delegate in its
// -beginSheetForWindow:... method (bad SFCertificatePanel!) so break the
// retain cycle by explicitly canceling the dialog.
[certificate_viewer_ closeCertificateSheet];
[remote_views_clone_window_ close];
}
// WidgetDelegate:
ui::ModalType GetModalType() const override { return ui::MODAL_TYPE_CHILD; }
private:
base::scoped_nsobject<SSLCertificateViewerMac> certificate_viewer_;
NSWindow* remote_views_clone_window_ = nil;
DISALLOW_COPY_AND_ASSIGN(CertificateAnchorWidgetDelegate);
};
} // namespace
void ShowCertificateViewer(content::WebContents* web_contents,
gfx::NativeWindow parent,
net::X509Certificate* cert) {
// Shows a new widget, which owns the delegate.
new CertificateAnchorWidgetDelegate(web_contents, cert);
}