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/api/atom_api_window.h"
|
||||||
#include "atom/browser/native_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/file_dialog.h"
|
||||||
#include "atom/browser/ui/message_box.h"
|
#include "atom/browser/ui/message_box.h"
|
||||||
#include "atom/common/native_mate_converters/callback.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/file_path_converter.h"
|
||||||
#include "atom/common/native_mate_converters/image_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 "native_mate/dictionary.h"
|
||||||
|
|
||||||
#include "atom/common/node_includes.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("showErrorBox", &atom::ShowErrorBox);
|
||||||
dict.SetMethod("showOpenDialog", &ShowOpenDialog);
|
dict.SetMethod("showOpenDialog", &ShowOpenDialog);
|
||||||
dict.SetMethod("showSaveDialog", &ShowSaveDialog);
|
dict.SetMethod("showSaveDialog", &ShowSaveDialog);
|
||||||
|
#if defined(OS_MACOSX)
|
||||||
|
dict.SetMethod("showCertificateTrustDialog",
|
||||||
|
&certificate_trust::ShowCertificateTrust);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // 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 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
|
// static
|
||||||
v8::Local<v8::Value> Converter<const net::AuthChallengeInfo*>::ToV8(
|
v8::Local<v8::Value> Converter<const net::AuthChallengeInfo*>::ToV8(
|
||||||
v8::Isolate* isolate, const net::AuthChallengeInfo* val) {
|
v8::Isolate* isolate, const net::AuthChallengeInfo* val) {
|
||||||
|
@ -73,6 +94,37 @@ v8::Local<v8::Value> Converter<scoped_refptr<net::X509Certificate>>::ToV8(
|
||||||
return dict.GetHandle();
|
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
|
// static
|
||||||
v8::Local<v8::Value> Converter<net::CertPrincipal>::ToV8(
|
v8::Local<v8::Value> Converter<net::CertPrincipal>::ToV8(
|
||||||
v8::Isolate* isolate, const net::CertPrincipal& val) {
|
v8::Isolate* isolate, const net::CertPrincipal& val) {
|
||||||
|
|
|
@ -33,6 +33,10 @@ template<>
|
||||||
struct Converter<scoped_refptr<net::X509Certificate>> {
|
struct Converter<scoped_refptr<net::X509Certificate>> {
|
||||||
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
|
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
|
||||||
const scoped_refptr<net::X509Certificate>& val);
|
const scoped_refptr<net::X509Certificate>& val);
|
||||||
|
|
||||||
|
static bool FromV8(v8::Isolate* isolate,
|
||||||
|
v8::Local<v8::Value> val,
|
||||||
|
scoped_refptr<net::X509Certificate>* out);
|
||||||
};
|
};
|
||||||
|
|
||||||
template<>
|
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,
|
before the app `ready`event on Linux, the message will be emitted to stderr,
|
||||||
and no GUI dialog will appear.
|
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
|
## Sheets
|
||||||
|
|
||||||
On macOS, dialogs are presented as sheets attached to a window if you provide
|
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/Carbon.framework',
|
||||||
'$(SDKROOT)/System/Library/Frameworks/QuartzCore.framework',
|
'$(SDKROOT)/System/Library/Frameworks/QuartzCore.framework',
|
||||||
'$(SDKROOT)/System/Library/Frameworks/Quartz.framework',
|
'$(SDKROOT)/System/Library/Frameworks/Quartz.framework',
|
||||||
|
'$(SDKROOT)/System/Library/Frameworks/Security.framework',
|
||||||
|
'$(SDKROOT)/System/Library/Frameworks/SecurityInterface.framework',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
'mac_bundle': 1,
|
'mac_bundle': 1,
|
||||||
|
|
|
@ -279,6 +279,8 @@
|
||||||
'atom/browser/ui/accelerator_util_views.cc',
|
'atom/browser/ui/accelerator_util_views.cc',
|
||||||
'atom/browser/ui/atom_menu_model.cc',
|
'atom/browser/ui/atom_menu_model.cc',
|
||||||
'atom/browser/ui/atom_menu_model.h',
|
'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.h',
|
||||||
'atom/browser/ui/cocoa/atom_menu_controller.mm',
|
'atom/browser/ui/cocoa/atom_menu_controller.mm',
|
||||||
'atom/browser/ui/cocoa/atom_touch_bar.h',
|
'atom/browser/ui/cocoa/atom_touch_bar.h',
|
||||||
|
|
|
@ -280,6 +280,27 @@ module.exports = {
|
||||||
|
|
||||||
showErrorBox: function (...args) {
|
showErrorBox: function (...args) {
|
||||||
return binding.showErrorBox(...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/)
|
}, /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