Merge pull request #9099 from electron/certificate-trust
macOS: Add certificate trust API
This commit is contained in:
commit
3e9014c371
10 changed files with 258 additions and 0 deletions
|
@ -8,11 +8,13 @@
|
|||
|
||||
#include "atom/browser/api/atom_api_window.h"
|
||||
#include "atom/browser/native_window.h"
|
||||
#include "atom/browser/ui/certificate_trust.h"
|
||||
#include "atom/browser/ui/file_dialog.h"
|
||||
#include "atom/browser/ui/message_box.h"
|
||||
#include "atom/common/native_mate_converters/callback.h"
|
||||
#include "atom/common/native_mate_converters/file_path_converter.h"
|
||||
#include "atom/common/native_mate_converters/image_converter.h"
|
||||
#include "atom/common/native_mate_converters/net_converter.h"
|
||||
#include "native_mate/dictionary.h"
|
||||
|
||||
#include "atom/common/node_includes.h"
|
||||
|
@ -127,6 +129,10 @@ void Initialize(v8::Local<v8::Object> exports, v8::Local<v8::Value> unused,
|
|||
dict.SetMethod("showErrorBox", &atom::ShowErrorBox);
|
||||
dict.SetMethod("showOpenDialog", &ShowOpenDialog);
|
||||
dict.SetMethod("showSaveDialog", &ShowSaveDialog);
|
||||
#if defined(OS_MACOSX)
|
||||
dict.SetMethod("showCertificateTrustDialog",
|
||||
&certificate_trust::ShowCertificateTrust);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
29
atom/browser/ui/certificate_trust.h
Normal file
29
atom/browser/ui/certificate_trust.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
// Copyright (c) 2017 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_UI_CERTIFICATE_TRUST_H_
|
||||
#define ATOM_BROWSER_UI_CERTIFICATE_TRUST_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/callback_forward.h"
|
||||
#include "base/memory/ref_counted.h"
|
||||
#include "net/cert/x509_certificate.h"
|
||||
|
||||
namespace atom {
|
||||
class NativeWindow;
|
||||
} // namespace atom
|
||||
|
||||
namespace certificate_trust {
|
||||
|
||||
typedef base::Callback<void(void)> ShowTrustCallback;
|
||||
|
||||
void ShowCertificateTrust(atom::NativeWindow* parent_window,
|
||||
const scoped_refptr<net::X509Certificate>& cert,
|
||||
const std::string& message,
|
||||
const ShowTrustCallback& callback);
|
||||
|
||||
} // namespace certificate_trust
|
||||
|
||||
#endif // ATOM_BROWSER_UI_CERTIFICATE_TRUST_H_
|
112
atom/browser/ui/certificate_trust_mac.mm
Normal file
112
atom/browser/ui/certificate_trust_mac.mm
Normal file
|
@ -0,0 +1,112 @@
|
|||
// Copyright (c) 2017 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/ui/certificate_trust.h"
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <SecurityInterface/SFCertificateTrustPanel.h>
|
||||
|
||||
#include "atom/browser/native_window.h"
|
||||
#include "base/strings/sys_string_conversions.h"
|
||||
#include "net/cert/cert_database.h"
|
||||
|
||||
@interface TrustDelegate : NSObject {
|
||||
@private
|
||||
certificate_trust::ShowTrustCallback callback_;
|
||||
SFCertificateTrustPanel* panel_;
|
||||
scoped_refptr<net::X509Certificate> cert_;
|
||||
SecTrustRef trust_;
|
||||
CFArrayRef cert_chain_;
|
||||
SecPolicyRef sec_policy_;
|
||||
}
|
||||
|
||||
- (id)initWithCallback:(const certificate_trust::ShowTrustCallback&)callback
|
||||
panel:(SFCertificateTrustPanel*)panel
|
||||
cert:(const scoped_refptr<net::X509Certificate>&)cert
|
||||
trust:(SecTrustRef)trust
|
||||
certChain:(CFArrayRef)certChain
|
||||
secPolicy:(SecPolicyRef)secPolicy;
|
||||
|
||||
- (void)panelDidEnd:(NSWindow*)sheet
|
||||
returnCode:(int)returnCode
|
||||
contextInfo:(void*)contextInfo;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TrustDelegate
|
||||
|
||||
- (void)dealloc {
|
||||
[panel_ release];
|
||||
CFRelease(trust_);
|
||||
CFRelease(cert_chain_);
|
||||
CFRelease(sec_policy_);
|
||||
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (id)initWithCallback:(const certificate_trust::ShowTrustCallback&)callback
|
||||
panel:(SFCertificateTrustPanel*)panel
|
||||
cert:(const scoped_refptr<net::X509Certificate>&)cert
|
||||
trust:(SecTrustRef)trust
|
||||
certChain:(CFArrayRef)certChain
|
||||
secPolicy:(SecPolicyRef)secPolicy {
|
||||
if ((self = [super init])) {
|
||||
callback_ = callback;
|
||||
panel_ = panel;
|
||||
cert_ = cert;
|
||||
trust_ = trust;
|
||||
cert_chain_ = certChain;
|
||||
sec_policy_ = secPolicy;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)panelDidEnd:(NSWindow*)sheet
|
||||
returnCode:(int)returnCode
|
||||
contextInfo:(void*)contextInfo {
|
||||
auto cert_db = net::CertDatabase::GetInstance();
|
||||
// This forces Chromium to reload the certificate since it might be trusted
|
||||
// now.
|
||||
cert_db->NotifyObserversCertDBChanged(cert_.get());
|
||||
|
||||
callback_.Run();
|
||||
|
||||
[self autorelease];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
namespace certificate_trust {
|
||||
|
||||
void ShowCertificateTrust(atom::NativeWindow* parent_window,
|
||||
const scoped_refptr<net::X509Certificate>& cert,
|
||||
const std::string& message,
|
||||
const ShowTrustCallback& callback) {
|
||||
auto sec_policy = SecPolicyCreateBasicX509();
|
||||
auto cert_chain = cert->CreateOSCertChainForCert();
|
||||
SecTrustRef trust = nullptr;
|
||||
SecTrustCreateWithCertificates(cert_chain, sec_policy, &trust);
|
||||
|
||||
NSWindow* window = parent_window ?
|
||||
parent_window->GetNativeWindow() :
|
||||
nil;
|
||||
auto msg = base::SysUTF8ToNSString(message);
|
||||
|
||||
auto panel = [[SFCertificateTrustPanel alloc] init];
|
||||
auto delegate = [[TrustDelegate alloc] initWithCallback:callback
|
||||
panel:panel
|
||||
cert:cert
|
||||
trust:trust
|
||||
certChain:cert_chain
|
||||
secPolicy:sec_policy];
|
||||
[panel beginSheetForWindow:window
|
||||
modalDelegate:delegate
|
||||
didEndSelector:@selector(panelDidEnd:returnCode:contextInfo:)
|
||||
contextInfo:nil
|
||||
trust:trust
|
||||
message:msg];
|
||||
}
|
||||
|
||||
} // namespace certificate_trust
|
|
@ -26,6 +26,27 @@
|
|||
|
||||
namespace mate {
|
||||
|
||||
namespace {
|
||||
|
||||
bool CertFromData(const std::string& data,
|
||||
scoped_refptr<net::X509Certificate>* out) {
|
||||
auto cert_list = net::X509Certificate::CreateCertificateListFromBytes(
|
||||
data.c_str(), data.length(),
|
||||
net::X509Certificate::FORMAT_SINGLE_CERTIFICATE);
|
||||
if (cert_list.empty())
|
||||
return false;
|
||||
|
||||
auto leaf_cert = cert_list.front();
|
||||
if (!leaf_cert)
|
||||
return false;
|
||||
|
||||
*out = leaf_cert;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
v8::Local<v8::Value> Converter<const net::AuthChallengeInfo*>::ToV8(
|
||||
v8::Isolate* isolate, const net::AuthChallengeInfo* val) {
|
||||
|
@ -73,6 +94,37 @@ v8::Local<v8::Value> Converter<scoped_refptr<net::X509Certificate>>::ToV8(
|
|||
return dict.GetHandle();
|
||||
}
|
||||
|
||||
bool Converter<scoped_refptr<net::X509Certificate>>::FromV8(
|
||||
v8::Isolate* isolate, v8::Local<v8::Value> val,
|
||||
scoped_refptr<net::X509Certificate>* out) {
|
||||
mate::Dictionary dict;
|
||||
if (!ConvertFromV8(isolate, val, &dict))
|
||||
return false;
|
||||
|
||||
std::string data;
|
||||
dict.Get("data", &data);
|
||||
scoped_refptr<net::X509Certificate> leaf_cert;
|
||||
if (!CertFromData(data, &leaf_cert))
|
||||
return false;
|
||||
|
||||
scoped_refptr<net::X509Certificate> parent;
|
||||
if (dict.Get("issuerCert", &parent)) {
|
||||
auto parents = std::vector<net::X509Certificate::OSCertHandle>(
|
||||
parent->GetIntermediateCertificates());
|
||||
parents.insert(parents.begin(), parent->os_cert_handle());
|
||||
auto cert = net::X509Certificate::CreateFromHandle(
|
||||
leaf_cert->os_cert_handle(), parents);
|
||||
if (!cert)
|
||||
return false;
|
||||
|
||||
*out = cert;
|
||||
} else {
|
||||
*out = leaf_cert;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// static
|
||||
v8::Local<v8::Value> Converter<net::CertPrincipal>::ToV8(
|
||||
v8::Isolate* isolate, const net::CertPrincipal& val) {
|
||||
|
|
|
@ -33,6 +33,10 @@ template<>
|
|||
struct Converter<scoped_refptr<net::X509Certificate>> {
|
||||
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
|
||||
const scoped_refptr<net::X509Certificate>& val);
|
||||
|
||||
static bool FromV8(v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> val,
|
||||
scoped_refptr<net::X509Certificate>* out);
|
||||
};
|
||||
|
||||
template<>
|
||||
|
|
|
@ -175,6 +175,20 @@ it is usually used to report errors in early stage of startup. If called
|
|||
before the app `ready`event on Linux, the message will be emitted to stderr,
|
||||
and no GUI dialog will appear.
|
||||
|
||||
### `dialog.showCertificateTrustDialog([browserWindow, ]options, callback)` _macOS_
|
||||
|
||||
* `browserWindow` BrowserWindow (optional)
|
||||
* `options` Object
|
||||
* `certificate` [Certificate](structures/certificate.md) - The certificate to trust/import.
|
||||
* `message` String - The message to display to the user.
|
||||
* `callback` Function
|
||||
|
||||
Displays a modal dialog that shows a message and certificate information, and
|
||||
gives the user the option of trusting/importing the certificate.
|
||||
|
||||
The `browserWindow` argument allows the dialog to attach itself to a parent
|
||||
window, making it modal.
|
||||
|
||||
## Sheets
|
||||
|
||||
On macOS, dialogs are presented as sheets attached to a window if you provide
|
||||
|
|
|
@ -549,6 +549,8 @@
|
|||
'$(SDKROOT)/System/Library/Frameworks/Carbon.framework',
|
||||
'$(SDKROOT)/System/Library/Frameworks/QuartzCore.framework',
|
||||
'$(SDKROOT)/System/Library/Frameworks/Quartz.framework',
|
||||
'$(SDKROOT)/System/Library/Frameworks/Security.framework',
|
||||
'$(SDKROOT)/System/Library/Frameworks/SecurityInterface.framework',
|
||||
],
|
||||
},
|
||||
'mac_bundle': 1,
|
||||
|
|
|
@ -279,6 +279,8 @@
|
|||
'atom/browser/ui/accelerator_util_views.cc',
|
||||
'atom/browser/ui/atom_menu_model.cc',
|
||||
'atom/browser/ui/atom_menu_model.h',
|
||||
'atom/browser/ui/certificate_trust.h',
|
||||
'atom/browser/ui/certificate_trust_mac.mm',
|
||||
'atom/browser/ui/cocoa/atom_menu_controller.h',
|
||||
'atom/browser/ui/cocoa/atom_menu_controller.mm',
|
||||
'atom/browser/ui/cocoa/atom_touch_bar.h',
|
||||
|
|
|
@ -280,6 +280,27 @@ module.exports = {
|
|||
|
||||
showErrorBox: function (...args) {
|
||||
return binding.showErrorBox(...args)
|
||||
},
|
||||
|
||||
showCertificateTrustDialog: function (...args) {
|
||||
let [window, options, callback] = parseArgs(...args)
|
||||
|
||||
if (options == null || typeof options !== 'object') {
|
||||
throw new TypeError('options must be an object')
|
||||
}
|
||||
|
||||
let {certificate, message} = options
|
||||
if (certificate == null || typeof certificate !== 'object') {
|
||||
throw new TypeError('certificate must be an object')
|
||||
}
|
||||
|
||||
if (message == null) {
|
||||
message = ''
|
||||
} else if (typeof message !== 'string') {
|
||||
throw new TypeError('message must be a string')
|
||||
}
|
||||
|
||||
return binding.showCertificateTrustDialog(window, certificate, message, callback)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -93,4 +93,20 @@ describe('dialog module', () => {
|
|||
}, /Error processing argument at index 1/)
|
||||
})
|
||||
})
|
||||
|
||||
describe('showCertificateTrustDialog', () => {
|
||||
it('throws errors when the options are invalid', () => {
|
||||
assert.throws(() => {
|
||||
dialog.showCertificateTrustDialog()
|
||||
}, /options must be an object/)
|
||||
|
||||
assert.throws(() => {
|
||||
dialog.showCertificateTrustDialog({})
|
||||
}, /certificate must be an object/)
|
||||
|
||||
assert.throws(() => {
|
||||
dialog.showCertificateTrustDialog({certificate: {}, message: false})
|
||||
}, /message must be a string/)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue