Merge pull request #5197 from deepak1556/client_certificate_patch

app: api to import client certificate
This commit is contained in:
Cheng Zhao 2016-04-20 14:35:00 +09:00
commit 965c3f605e
19 changed files with 874 additions and 41 deletions

View file

@ -15,15 +15,17 @@
#include "atom/browser/browser.h" #include "atom/browser/browser.h"
#include "atom/browser/login_handler.h" #include "atom/browser/login_handler.h"
#include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/callback.h"
#include "atom/common/native_mate_converters/net_converter.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/gurl_converter.h" #include "atom/common/native_mate_converters/gurl_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 "atom/common/native_mate_converters/value_converter.h"
#include "atom/common/node_includes.h" #include "atom/common/node_includes.h"
#include "atom/common/options_switches.h" #include "atom/common/options_switches.h"
#include "base/command_line.h" #include "base/command_line.h"
#include "base/environment.h" #include "base/environment.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/path_service.h" #include "base/path_service.h"
#include "brightray/browser/brightray_paths.h" #include "brightray/browser/brightray_paths.h"
#include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_paths.h"
@ -157,6 +159,39 @@ void PassLoginInformation(scoped_refptr<LoginHandler> login_handler,
login_handler->CancelAuth(); login_handler->CancelAuth();
} }
#if defined(USE_NSS_CERTS)
int ImportIntoCertStore(
CertificateManagerModel* model,
const base::DictionaryValue& options) {
std::string file_data, cert_path;
base::string16 password;
net::CertificateList imported_certs;
int rv = -1;
options.GetString("certificate", &cert_path);
options.GetString("password", &password);
if (!cert_path.empty()) {
if (base::ReadFileToString(base::FilePath(cert_path), &file_data)) {
auto module = model->cert_db()->GetPublicModule();
rv = model->ImportFromPKCS12(module,
file_data,
password,
true,
&imported_certs);
if (imported_certs.size() > 1) {
auto it = imported_certs.begin();
++it; // skip first which would be the client certificate.
for (; it != imported_certs.end(); ++it)
rv &= model->SetCertTrust(it->get(),
net::CA_CERT,
net::NSSCertDatabase::TRUSTED_SSL);
}
}
}
return rv;
}
#endif
} // namespace } // namespace
App::App() { App::App() {
@ -369,6 +404,36 @@ bool App::MakeSingleInstance(
} }
} }
#if defined(USE_NSS_CERTS)
void App::ImportCertificate(
const base::DictionaryValue& options,
const net::CompletionCallback& callback) {
auto browser_context = AtomBrowserMainParts::Get()->browser_context();
if (!certificate_manager_model_) {
scoped_ptr<base::DictionaryValue> copy = options.CreateDeepCopy();
CertificateManagerModel::Create(browser_context,
base::Bind(&App::OnCertificateManagerModelCreated,
base::Unretained(this),
base::Passed(&copy),
callback));
return;
}
int rv = ImportIntoCertStore(certificate_manager_model_.get(), options);
callback.Run(rv);
}
void App::OnCertificateManagerModelCreated(
scoped_ptr<base::DictionaryValue> options,
const net::CompletionCallback& callback,
scoped_ptr<CertificateManagerModel> model) {
certificate_manager_model_ = std::move(model);
int rv = ImportIntoCertStore(certificate_manager_model_.get(),
*(options.get()));
callback.Run(rv);
}
#endif
mate::ObjectTemplateBuilder App::GetObjectTemplateBuilder( mate::ObjectTemplateBuilder App::GetObjectTemplateBuilder(
v8::Isolate* isolate) { v8::Isolate* isolate) {
auto browser = base::Unretained(Browser::Get()); auto browser = base::Unretained(Browser::Get());
@ -408,6 +473,9 @@ mate::ObjectTemplateBuilder App::GetObjectTemplateBuilder(
.SetMethod("allowNTLMCredentialsForAllDomains", .SetMethod("allowNTLMCredentialsForAllDomains",
&App::AllowNTLMCredentialsForAllDomains) &App::AllowNTLMCredentialsForAllDomains)
.SetMethod("getLocale", &App::GetLocale) .SetMethod("getLocale", &App::GetLocale)
#if defined(USE_NSS_CERTS)
.SetMethod("importCertificate", &App::ImportCertificate)
#endif
.SetMethod("makeSingleInstance", &App::MakeSingleInstance); .SetMethod("makeSingleInstance", &App::MakeSingleInstance);
} }
@ -427,7 +495,6 @@ void AppendSwitch(const std::string& switch_string, mate::Arguments* args) {
auto command_line = base::CommandLine::ForCurrentProcess(); auto command_line = base::CommandLine::ForCurrentProcess();
if (switch_string == atom::switches::kPpapiFlashPath || if (switch_string == atom::switches::kPpapiFlashPath ||
switch_string == atom::switches::kClientCertificate ||
switch_string == switches::kLogNetLog) { switch_string == switches::kLogNetLog) {
base::FilePath path; base::FilePath path;
args->GetNext(&path); args->GetNext(&path);

View file

@ -14,6 +14,11 @@
#include "chrome/browser/process_singleton.h" #include "chrome/browser/process_singleton.h"
#include "content/public/browser/gpu_data_manager_observer.h" #include "content/public/browser/gpu_data_manager_observer.h"
#include "native_mate/handle.h" #include "native_mate/handle.h"
#include "net/base/completion_callback.h"
#if defined(USE_NSS_CERTS)
#include "chrome/browser/certificate_manager_model.h"
#endif
namespace base { namespace base {
class FilePath; class FilePath;
@ -41,6 +46,13 @@ class App : public AtomBrowserClient::Delegate,
int render_process_id, int render_process_id,
int render_frame_id); int render_frame_id);
#if defined(USE_NSS_CERTS)
void OnCertificateManagerModelCreated(
scoped_ptr<base::DictionaryValue> options,
const net::CompletionCallback& callback,
scoped_ptr<CertificateManagerModel> model);
#endif
protected: protected:
App(); App();
virtual ~App(); virtual ~App();
@ -98,12 +110,21 @@ class App : public AtomBrowserClient::Delegate,
const ProcessSingleton::NotificationCallback& callback); const ProcessSingleton::NotificationCallback& callback);
std::string GetLocale(); std::string GetLocale();
#if defined(USE_NSS_CERTS)
void ImportCertificate(const base::DictionaryValue& options,
const net::CompletionCallback& callback);
#endif
#if defined(OS_WIN) #if defined(OS_WIN)
bool IsAeroGlassEnabled(); bool IsAeroGlassEnabled();
#endif #endif
scoped_ptr<ProcessSingleton> process_singleton_; scoped_ptr<ProcessSingleton> process_singleton_;
#if defined(USE_NSS_CERTS)
scoped_ptr<CertificateManagerModel> certificate_manager_model_;
#endif
DISALLOW_COPY_AND_ASSIGN(App); DISALLOW_COPY_AND_ASSIGN(App);
}; };

View file

@ -37,7 +37,6 @@
#include "content/public/browser/site_instance.h" #include "content/public/browser/site_instance.h"
#include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents.h"
#include "content/public/common/web_preferences.h" #include "content/public/common/web_preferences.h"
#include "net/cert/x509_certificate.h"
#include "net/ssl/ssl_cert_request_info.h" #include "net/ssl/ssl_cert_request_info.h"
#include "ppapi/host/ppapi_host.h" #include "ppapi/host/ppapi_host.h"
#include "ui/base/l10n/l10n_util.h" #include "ui/base/l10n/l10n_util.h"
@ -55,26 +54,6 @@ std::string g_custom_schemes = "";
// Custom schemes to be registered to handle service worker. // Custom schemes to be registered to handle service worker.
std::string g_custom_service_worker_schemes = ""; std::string g_custom_service_worker_schemes = "";
scoped_refptr<net::X509Certificate> ImportCertFromFile(
const base::FilePath& path) {
if (path.empty())
return nullptr;
std::string cert_data;
if (!base::ReadFileToString(path, &cert_data))
return nullptr;
net::CertificateList certs =
net::X509Certificate::CreateCertificateListFromBytes(
cert_data.data(), cert_data.size(),
net::X509Certificate::FORMAT_AUTO);
if (certs.empty())
return nullptr;
return certs[0];
}
} // namespace } // namespace
// static // static
@ -242,16 +221,6 @@ void AtomBrowserClient::SelectClientCertificate(
content::WebContents* web_contents, content::WebContents* web_contents,
net::SSLCertRequestInfo* cert_request_info, net::SSLCertRequestInfo* cert_request_info,
scoped_ptr<content::ClientCertificateDelegate> delegate) { scoped_ptr<content::ClientCertificateDelegate> delegate) {
// --client-certificate=`path`
auto cmd = base::CommandLine::ForCurrentProcess();
if (cmd->HasSwitch(switches::kClientCertificate)) {
auto cert_path = cmd->GetSwitchValuePath(switches::kClientCertificate);
auto certificate = ImportCertFromFile(cert_path);
if (certificate.get())
delegate->ContinueWithCertificate(certificate.get());
return;
}
if (!cert_request_info->client_certs.empty() && delegate_) { if (!cert_request_info->client_certs.empty() && delegate_) {
delegate_->SelectClientCertificate( delegate_->SelectClientCertificate(
web_contents, cert_request_info, std::move(delegate)); web_contents, cert_request_info, std::move(delegate));

View file

@ -119,9 +119,6 @@ const char kPpapiFlashPath[] = "ppapi-flash-path";
// Ppapi Flash version. // Ppapi Flash version.
const char kPpapiFlashVersion[] = "ppapi-flash-version"; const char kPpapiFlashVersion[] = "ppapi-flash-version";
// Path to client certificate.
const char kClientCertificate[] = "client-certificate";
// Disable HTTP cache. // Disable HTTP cache.
const char kDisableHttpCache[] = "disable-http-cache"; const char kDisableHttpCache[] = "disable-http-cache";

View file

@ -68,7 +68,6 @@ namespace switches {
extern const char kEnablePlugins[]; extern const char kEnablePlugins[];
extern const char kPpapiFlashPath[]; extern const char kPpapiFlashPath[];
extern const char kPpapiFlashVersion[]; extern const char kPpapiFlashVersion[];
extern const char kClientCertificate[];
extern const char kDisableHttpCache[]; extern const char kDisableHttpCache[];
extern const char kRegisterStandardSchemes[]; extern const char kRegisterStandardSchemes[];
extern const char kRegisterServiceWorkerSchemes[]; extern const char kRegisterServiceWorkerSchemes[];

View file

@ -0,0 +1,173 @@
// Copyright (c) 2012 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.
#include "chrome/browser/certificate_manager_model.h"
#include <utility>
#include "base/bind.h"
#include "base/logging.h"
#include "base/strings/utf_string_conversions.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/resource_context.h"
#include "crypto/nss_util.h"
#include "crypto/nss_util_internal.h"
#include "net/base/crypto_module.h"
#include "net/base/net_errors.h"
#include "net/cert/nss_cert_database.h"
#include "net/cert/x509_certificate.h"
using content::BrowserThread;
namespace {
net::NSSCertDatabase* g_nss_cert_database = nullptr;
net::NSSCertDatabase* GetNSSCertDatabaseForResourceContext(
content::ResourceContext* context,
const base::Callback<void(net::NSSCertDatabase*)>& callback) {
// This initialization is not thread safe. This CHECK ensures that this code
// is only run on a single thread.
CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
if (!g_nss_cert_database) {
// Linux has only a single persistent slot compared to ChromeOS's separate
// public and private slot.
// Redirect any slot usage to this persistent slot on Linux.
g_nss_cert_database = new net::NSSCertDatabase(
crypto::ScopedPK11Slot(
crypto::GetPersistentNSSKeySlot()) /* public slot */,
crypto::ScopedPK11Slot(
crypto::GetPersistentNSSKeySlot()) /* private slot */);
}
return g_nss_cert_database;
}
} // namespace
// CertificateManagerModel is created on the UI thread. It needs a
// NSSCertDatabase handle (and on ChromeOS it needs to get the TPM status) which
// needs to be done on the IO thread.
//
// The initialization flow is roughly:
//
// UI thread IO Thread
//
// CertificateManagerModel::Create
// \--------------------------------------v
// CertificateManagerModel::GetCertDBOnIOThread
// |
// GetNSSCertDatabaseForResourceContext
// |
// CertificateManagerModel::DidGetCertDBOnIOThread
// v--------------------------------------/
// CertificateManagerModel::DidGetCertDBOnUIThread
// |
// new CertificateManagerModel
// |
// callback
// static
void CertificateManagerModel::Create(
content::BrowserContext* browser_context,
const CreationCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(&CertificateManagerModel::GetCertDBOnIOThread,
browser_context->GetResourceContext(),
callback));
}
CertificateManagerModel::CertificateManagerModel(
net::NSSCertDatabase* nss_cert_database,
bool is_user_db_available)
: cert_db_(nss_cert_database),
is_user_db_available_(is_user_db_available) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
}
CertificateManagerModel::~CertificateManagerModel() {
}
int CertificateManagerModel::ImportFromPKCS12(net::CryptoModule* module,
const std::string& data,
const base::string16& password,
bool is_extractable,
net::CertificateList* imported_certs) {
return cert_db_->ImportFromPKCS12(module, data, password,
is_extractable, imported_certs);
}
int CertificateManagerModel::ImportUserCert(const std::string& data) {
return cert_db_->ImportUserCert(data);
}
bool CertificateManagerModel::ImportCACerts(
const net::CertificateList& certificates,
net::NSSCertDatabase::TrustBits trust_bits,
net::NSSCertDatabase::ImportCertFailureList* not_imported) {
return cert_db_->ImportCACerts(certificates, trust_bits, not_imported);
}
bool CertificateManagerModel::ImportServerCert(
const net::CertificateList& certificates,
net::NSSCertDatabase::TrustBits trust_bits,
net::NSSCertDatabase::ImportCertFailureList* not_imported) {
return cert_db_->ImportServerCert(certificates, trust_bits,
not_imported);
}
bool CertificateManagerModel::SetCertTrust(
const net::X509Certificate* cert,
net::CertType type,
net::NSSCertDatabase::TrustBits trust_bits) {
return cert_db_->SetCertTrust(cert, type, trust_bits);
}
bool CertificateManagerModel::Delete(net::X509Certificate* cert) {
return cert_db_->DeleteCertAndKey(cert);
}
// static
void CertificateManagerModel::DidGetCertDBOnUIThread(
net::NSSCertDatabase* cert_db,
bool is_user_db_available,
const CreationCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
scoped_ptr<CertificateManagerModel> model(new CertificateManagerModel(
cert_db, is_user_db_available));
callback.Run(std::move(model));
}
// static
void CertificateManagerModel::DidGetCertDBOnIOThread(
const CreationCallback& callback,
net::NSSCertDatabase* cert_db) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
bool is_user_db_available = !!cert_db->GetPublicSlot();
BrowserThread::PostTask(
BrowserThread::UI,
FROM_HERE,
base::Bind(&CertificateManagerModel::DidGetCertDBOnUIThread,
cert_db,
is_user_db_available,
callback));
}
// static
void CertificateManagerModel::GetCertDBOnIOThread(
content::ResourceContext* context,
const CreationCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
net::NSSCertDatabase* cert_db = GetNSSCertDatabaseForResourceContext(
context,
base::Bind(&CertificateManagerModel::DidGetCertDBOnIOThread,
callback));
if (cert_db)
DidGetCertDBOnIOThread(callback, cert_db);
}

View file

@ -0,0 +1,119 @@
// Copyright (c) 2012 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.
#ifndef CHROME_BROWSER_CERTIFICATE_MANAGER_MODEL_H_
#define CHROME_BROWSER_CERTIFICATE_MANAGER_MODEL_H_
#include <map>
#include <string>
#include "base/callback.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/string16.h"
#include "net/cert/nss_cert_database.h"
namespace content {
class BrowserContext;
class ResourceContext;
} // namespace content
// CertificateManagerModel provides the data to be displayed in the certificate
// manager dialog, and processes changes from the view.
class CertificateManagerModel {
public:
typedef base::Callback<void(scoped_ptr<CertificateManagerModel>)>
CreationCallback;
// Creates a CertificateManagerModel. The model will be passed to the callback
// when it is ready. The caller must ensure the model does not outlive the
// |browser_context|.
static void Create(content::BrowserContext* browser_context,
const CreationCallback& callback);
~CertificateManagerModel();
bool is_user_db_available() const { return is_user_db_available_; }
// Accessor for read-only access to the underlying NSSCertDatabase.
const net::NSSCertDatabase* cert_db() const { return cert_db_; }
// Import private keys and certificates from PKCS #12 encoded
// |data|, using the given |password|. If |is_extractable| is false,
// mark the private key as unextractable from the module.
// Returns a net error code on failure.
int ImportFromPKCS12(net::CryptoModule* module,
const std::string& data,
const base::string16& password,
bool is_extractable,
net::CertificateList* imported_certs);
// Import user certificate from DER encoded |data|.
// Returns a net error code on failure.
int ImportUserCert(const std::string& data);
// Import CA certificates.
// Tries to import all the certificates given. The root will be trusted
// according to |trust_bits|. Any certificates that could not be imported
// will be listed in |not_imported|.
// |trust_bits| should be a bit field of TRUST* values from NSSCertDatabase.
// Returns false if there is an internal error, otherwise true is returned and
// |not_imported| should be checked for any certificates that were not
// imported.
bool ImportCACerts(const net::CertificateList& certificates,
net::NSSCertDatabase::TrustBits trust_bits,
net::NSSCertDatabase::ImportCertFailureList* not_imported);
// Import server certificate. The first cert should be the server cert. Any
// additional certs should be intermediate/CA certs and will be imported but
// not given any trust.
// Any certificates that could not be imported will be listed in
// |not_imported|.
// |trust_bits| can be set to explicitly trust or distrust the certificate, or
// use TRUST_DEFAULT to inherit trust as normal.
// Returns false if there is an internal error, otherwise true is returned and
// |not_imported| should be checked for any certificates that were not
// imported.
bool ImportServerCert(
const net::CertificateList& certificates,
net::NSSCertDatabase::TrustBits trust_bits,
net::NSSCertDatabase::ImportCertFailureList* not_imported);
// Set trust values for certificate.
// |trust_bits| should be a bit field of TRUST* values from NSSCertDatabase.
// Returns true on success or false on failure.
bool SetCertTrust(const net::X509Certificate* cert,
net::CertType type,
net::NSSCertDatabase::TrustBits trust_bits);
// Delete the cert. Returns true on success. |cert| is still valid when this
// function returns.
bool Delete(net::X509Certificate* cert);
private:
CertificateManagerModel(net::NSSCertDatabase* nss_cert_database,
bool is_user_db_available);
// Methods used during initialization, see the comment at the top of the .cc
// file for details.
static void DidGetCertDBOnUIThread(
net::NSSCertDatabase* cert_db,
bool is_user_db_available,
const CreationCallback& callback);
static void DidGetCertDBOnIOThread(
const CreationCallback& callback,
net::NSSCertDatabase* cert_db);
static void GetCertDBOnIOThread(content::ResourceContext* context,
const CreationCallback& callback);
net::NSSCertDatabase* cert_db_;
// Whether the certificate database has a public slot associated with the
// profile. If not set, importing certificates is not allowed with this model.
bool is_user_db_available_;
DISALLOW_COPY_AND_ASSIGN(CertificateManagerModel);
};
#endif // CHROME_BROWSER_CERTIFICATE_MANAGER_MODEL_H_

View file

@ -514,6 +514,18 @@ if (browserOptions.transparent) {
This method returns `true` if the system is in Dark Mode, and `false` otherwise. This method returns `true` if the system is in Dark Mode, and `false` otherwise.
### `app.importCertificate(options, callback)` _LINUX_
* `options` Object
* `certificate` String - Path for the pkcs12 file.
* `password` String - Passphrase for the certificate.
* `callback` Function
* `result` Integer - Result of import.
Imports the certificate in pkcs12 format into the platform certificate store.
`callback` is called with the `result` of import operation, a value of `0` indicates
success while any other value indicates failure according to chromium [net_error_list](https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h).
### `app.commandLine.appendSwitch(switch[, value])` ### `app.commandLine.appendSwitch(switch[, value])`
Append a switch (with optional `value`) to Chromium's command line. Append a switch (with optional `value`) to Chromium's command line.

View file

@ -15,10 +15,6 @@ app.on('ready', function() {
}); });
``` ```
## --client-certificate=`path`
Sets the `path` of client certificate file.
## --ignore-connections-limit=`domains` ## --ignore-connections-limit=`domains`
Ignore the connections limit for `domains` list separated by `,`. Ignore the connections limit for `domains` list separated by `,`.

View file

@ -310,6 +310,9 @@
], ],
}], # OS=="mac" and mas_build==1 }], # OS=="mac" and mas_build==1
['OS=="linux"', { ['OS=="linux"', {
'sources': [
'<@(lib_sources_nss)',
],
'link_settings': { 'link_settings': {
'ldflags': [ 'ldflags': [
# Make binary search for libraries under current directory, so we # Make binary search for libraries under current directory, so we

View file

@ -517,6 +517,10 @@
'<@(native_mate_files)', '<@(native_mate_files)',
'<(SHARED_INTERMEDIATE_DIR)/atom_natives.h', '<(SHARED_INTERMEDIATE_DIR)/atom_natives.h',
], ],
'lib_sources_nss': [
'chromium_src/chrome/browser/certificate_manager_model.cc',
'chromium_src/chrome/browser/certificate_manager_model.h',
],
'lib_sources_win': [ 'lib_sources_win': [
'chromium_src/chrome/browser/ui/views/color_chooser_dialog.cc', 'chromium_src/chrome/browser/ui/views/color_chooser_dialog.cc',
'chromium_src/chrome/browser/ui/views/color_chooser_dialog.h', 'chromium_src/chrome/browser/ui/views/color_chooser_dialog.h',

View file

@ -1,5 +1,7 @@
const assert = require('assert') const assert = require('assert')
const ChildProcess = require('child_process') const ChildProcess = require('child_process')
const https = require('https')
const fs = require('fs')
const path = require('path') const path = require('path')
const remote = require('electron').remote const remote = require('electron').remote
@ -87,6 +89,70 @@ describe('app module', function () {
}) })
}) })
describe('app.importCertificate', function () {
if (process.platform !== 'linux')
return
this.timeout(5000)
var w = null
var certPath = path.join(__dirname, 'fixtures', 'certificates')
var options = {
key: fs.readFileSync(path.join(certPath, 'server.key')),
cert: fs.readFileSync(path.join(certPath, 'server.pem')),
ca: [
fs.readFileSync(path.join(certPath, 'rootCA.pem')),
fs.readFileSync(path.join(certPath, 'intermediateCA.pem'))
],
requestCert: true,
rejectUnauthorized: false
}
var server = https.createServer(options, function (req, res) {
if (req.client.authorized) {
res.writeHead(200);
res.end('authorized');
}
})
afterEach(function () {
if (w != null) {
w.destroy()
}
w = null
})
it('can import certificate into platform cert store', function (done) {
let options = {
certificate: path.join(certPath, 'client.p12'),
password: 'electron'
}
w = new BrowserWindow({
show: false
})
w.webContents.on('did-finish-load', function () {
server.close()
done()
})
app.on('select-client-certificate', function (event, webContents, url, list, callback) {
assert.equal(list.length, 1)
assert.equal(list[0].issuerName, 'Intermediate CA')
callback(list[0])
})
app.importCertificate(options, function (result) {
assert(!result)
server.listen(0, '127.0.0.1', function () {
var port = server.address().port
w.loadURL(`https://127.0.0.1:${port}`)
})
})
})
})
describe('BrowserWindow events', function () { describe('BrowserWindow events', function () {
var w = null var w = null

68
spec/fixtures/certificates/certs.cnf vendored Normal file
View file

@ -0,0 +1,68 @@
ID=1
CA_DIR=out
[ca]
default_ca = ca_settings
[ca_settings]
dir = ${ENV::CA_DIR}
database = $dir/${ENV::ID}-index.txt
new_certs_dir = $dir
serial = $dir/${ENV::ID}-serial
certificate = $dir/${ENV::ID}.pem
private_key = $dir/${ENV::ID}.key
RANDFILE = $dir/rand
default_md = sha256
default_days = 3650
policy = policy_anything
preserve = no
[policy_anything]
# Default signing policy
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
[req]
default_bits = 2048
default_md = sha256
string_mask = utf8only
distinguished_name = req_env_dn
prompt = no
[user_cert]
basicConstraints = CA:FALSE
nsCertType = client
nsComment = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, emailProtection
[server_cert]
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
[ca_cert]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
[ca_intermediate_cert]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
[req_env_dn]
commonName = ${ENV::COMMON_NAME}

BIN
spec/fixtures/certificates/client.p12 vendored Normal file

Binary file not shown.

127
spec/fixtures/certificates/generate_certs.sh vendored Executable file
View file

@ -0,0 +1,127 @@
#!/bin/bash
# This script generates certificates that can be used to test SSL client
# authentication.
#
# 1. A (end-entity) -> B -> C (self-signed root)
# 2. D (end-entity) -> B -> C (self-signed root)
try () {
echo "$@"
"$@" || exit 1
}
try mkdir out
echo Create the serial number files and indices.
serial=1000
for i in B C
do
try /bin/sh -c "echo $serial > out/$i-serial"
serial=$(expr $serial + 1)
touch out/$i-index.txt
touch out/$i-index.txt.attr
done
echo Generate the keys.
for i in A B C D
do
try openssl genrsa -out out/$i.key 2048
done
echo Generate the C CSR
COMMON_NAME="Root CA" \
CA_DIR=out \
ID=C \
try openssl req \
-new \
-key out/C.key \
-out out/C.csr \
-config certs.cnf
echo C signs itself.
COMMON_NAME="Root CA" \
CA_DIR=out \
ID=C \
try openssl x509 \
-req -days 3650 \
-in out/C.csr \
-extensions ca_cert \
-extfile certs.cnf \
-signkey out/C.key \
-out out/C.pem
echo Generate the intermediates
COMMON_NAME="Intermediate CA" \
CA_DIR=out \
ID=B \
try openssl req \
-new \
-key out/B.key \
-out out/B.csr \
-config certs.cnf
COMMON_NAME="Root CA" \
CA_DIR=out \
ID=C \
try openssl ca \
-batch \
-extensions ca_intermediate_cert \
-in out/B.csr \
-out out/B.pem \
-config certs.cnf
echo Generate the leaf certs
COMMON_NAME="Client Cert" \
ID=A \
try openssl req \
-new \
-key out/A.key \
-out out/A.csr \
-config certs.cnf
echo B signs A
COMMON_NAME="Intermediate CA" \
CA_DIR=out \
ID=B \
try openssl ca \
-batch \
-extensions user_cert \
-in out/A.csr \
-out out/A.pem \
-config certs.cnf
COMMON_NAME="localhost" \
ID=D \
try openssl req \
-new \
-key out/D.key \
-out out/D.csr \
-config certs.cnf
echo B signs D
COMMON_NAME="Intermediate CA" \
CA_DIR=out \
ID=B \
try openssl ca \
-batch \
-extensions server_cert \
-in out/D.csr \
-out out/D.pem \
-config certs.cnf
echo Package the client cert and private key into PKCS12 file
try /bin/sh -c "cat out/A.pem out/A.key out/B.pem out/C.pem > out/A-chain.pem"
try openssl pkcs12 \
-in out/A-chain.pem \
-out client.p12 \
-export \
-passout pass:electron
echo Package the certs
try cp out/C.pem rootCA.pem
try cp out/B.pem intermediateCA.pem
try cp out/D.key server.key
try cp out/D.pem server.pem
try rm -rf out

View file

@ -0,0 +1,78 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 4097 (0x1001)
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN=Root CA
Validity
Not Before: Apr 18 16:14:29 2016 GMT
Not After : Apr 16 16:14:29 2026 GMT
Subject: CN=Intermediate CA
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:b6:42:02:13:25:40:13:a6:05:99:69:da:0c:c9:
a8:bf:86:3b:fc:c6:51:ba:64:65:7e:33:11:31:d5:
03:45:30:4c:ca:49:d2:96:42:52:2f:f9:e6:6c:9a:
50:1c:fe:fa:e2:e8:63:36:14:47:f7:49:9f:78:28:
5e:1f:0b:9d:9e:f8:d3:33:77:06:4d:6d:14:c0:57:
01:83:2b:ef:99:06:48:21:ec:c1:d7:05:48:2c:ea:
83:06:6a:20:df:73:ce:8a:a5:e4:81:00:41:84:cf:
89:81:78:2e:3a:bd:1b:fd:3e:96:08:8d:44:1b:00:
c8:d6:4e:7a:6a:75:c0:9b:3c:e0:fa:aa:3a:82:5b:
3c:39:32:ca:4a:ba:82:bc:60:47:6f:e4:4a:fd:dc:
a0:72:8a:1b:fe:cd:2e:10:f4:27:4c:08:4e:d1:ed:
dc:08:b0:f8:1f:e4:fc:45:72:43:58:6e:dd:05:37:
8c:04:a1:fb:64:f4:3f:90:bb:85:f2:4c:97:46:fd:
1f:29:e5:19:d0:0f:24:fd:d1:00:c5:b6:be:da:84:
62:77:be:db:67:f6:ec:98:5d:97:f5:df:0a:bd:b8:
07:7f:0a:d5:92:29:1f:c4:b0:97:4f:e4:87:d7:a9:
00:c9:61:d5:6c:cd:6a:fc:56:c3:f3:b7:ca:53:70:
02:3f
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Key Identifier:
A9:75:99:CF:9C:92:54:A4:4B:65:CD:3D:FC:93:98:8D:9E:09:1F:47
X509v3 Authority Key Identifier:
keyid:E3:51:87:E3:CD:7A:B3:26:9F:8F:EC:62:D1:0E:15:0C:39:36:47:4F
X509v3 Basic Constraints: critical
CA:TRUE, pathlen:0
X509v3 Key Usage: critical
Digital Signature, Certificate Sign, CRL Sign
Signature Algorithm: sha256WithRSAEncryption
55:69:d6:1d:33:ad:ab:40:46:fd:34:02:c1:43:50:7b:90:ea:
f3:5f:4f:b6:2c:28:aa:72:e0:4b:36:2e:8f:44:93:15:52:14:
f6:61:b3:50:e0:ba:43:91:ba:a9:5d:ac:43:b7:52:ca:91:a3:
d7:0e:ac:a7:9e:ee:28:7f:2d:0f:93:b5:d9:23:35:68:54:29:
2a:e7:3a:4c:41:24:d0:5e:2d:f3:1e:b9:52:f1:3e:16:76:93:
89:6d:a1:4c:63:f5:4a:cc:08:36:61:29:0a:29:5f:f4:5a:55:
98:10:b3:de:b3:90:f9:03:e5:bd:1b:61:01:a7:22:03:ae:0f:
77:c4:a8:bf:31:b4:af:c8:c7:e3:25:a1:2b:b9:43:37:3b:08:
ea:c4:46:60:b8:5f:ee:2a:0d:ce:18:75:63:ba:32:28:84:f4:
56:95:1b:c5:f9:46:7e:14:2e:83:5e:a9:ff:b2:80:ca:25:fd:
22:90:b5:de:bd:e6:f1:0c:ee:7e:09:71:0d:82:6a:ca:2f:9c:
96:45:73:3a:65:bc:d8:9d:e0:61:01:5d:a8:de:de:61:8c:82:
52:0c:ef:97:39:b3:13:c6:7d:d0:c0:f5:6d:c8:70:5b:96:e8:
99:31:d8:75:3a:21:58:ab:01:21:9e:38:8e:53:ff:f8:48:a7:
af:01:9a:93
-----BEGIN CERTIFICATE-----
MIIDDjCCAfagAwIBAgICEAEwDQYJKoZIhvcNAQELBQAwEjEQMA4GA1UEAwwHUm9v
dCBDQTAeFw0xNjA0MTgxNjE0MjlaFw0yNjA0MTYxNjE0MjlaMBoxGDAWBgNVBAMM
D0ludGVybWVkaWF0ZSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
ALZCAhMlQBOmBZlp2gzJqL+GO/zGUbpkZX4zETHVA0UwTMpJ0pZCUi/55myaUBz+
+uLoYzYUR/dJn3goXh8LnZ740zN3Bk1tFMBXAYMr75kGSCHswdcFSCzqgwZqIN9z
zoql5IEAQYTPiYF4Ljq9G/0+lgiNRBsAyNZOemp1wJs84PqqOoJbPDkyykq6grxg
R2/kSv3coHKKG/7NLhD0J0wITtHt3Aiw+B/k/EVyQ1hu3QU3jASh+2T0P5C7hfJM
l0b9HynlGdAPJP3RAMW2vtqEYne+22f27Jhdl/XfCr24B38K1ZIpH8Swl0/kh9ep
AMlh1WzNavxWw/O3ylNwAj8CAwEAAaNmMGQwHQYDVR0OBBYEFKl1mc+cklSkS2XN
PfyTmI2eCR9HMB8GA1UdIwQYMBaAFONRh+PNerMmn4/sYtEOFQw5NkdPMBIGA1Ud
EwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IB
AQBVadYdM62rQEb9NALBQ1B7kOrzX0+2LCiqcuBLNi6PRJMVUhT2YbNQ4LpDkbqp
XaxDt1LKkaPXDqynnu4ofy0Pk7XZIzVoVCkq5zpMQSTQXi3zHrlS8T4WdpOJbaFM
Y/VKzAg2YSkKKV/0WlWYELPes5D5A+W9G2EBpyIDrg93xKi/MbSvyMfjJaEruUM3
OwjqxEZguF/uKg3OGHVjujIohPRWlRvF+UZ+FC6DXqn/soDKJf0ikLXevebxDO5+
CXENgmrKL5yWRXM6ZbzYneBhAV2o3t5hjIJSDO+XObMTxn3QwPVtyHBbluiZMdh1
OiFYqwEhnjiOU//4SKevAZqT
-----END CERTIFICATE-----

19
spec/fixtures/certificates/rootCA.pem vendored Normal file
View file

@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDCjCCAfKgAwIBAgIJAOcWbv0WHll0MA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNV
BAMMB1Jvb3QgQ0EwHhcNMTYwNDE4MTYxNDI5WhcNMjYwNDE2MTYxNDI5WjASMRAw
DgYDVQQDDAdSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
0iZvelv6MjxqdmQsAmKqIfc5gvQjYB3CqWlEH3g5czPFBMMtmOI9czlk+0jc1VEf
t1SKst7zwe1rpxFArgudV45NBHQH3ZlzkLeO7Ol2kPzlyMHNJ70vT3CBitKnLl4B
bg7xf6kDQQlC3/QeWxvbR5cvp131uwcpXKdJ9k4dwpfS2BKiRb5Uk46DgX5kGaka
q/tQ2F7b6AlAoTq608tZBuOInkg2tTbGe9PDWSL8oMZRwCSbF543SAR45zjWBa0k
ymY31VvlYbEd/3lfE5Mrn/JwZQpTKOfcOI//kUkcClJVpSMObh4eiy1oNjqcJ4KR
/4hkY7oTQCA7zWD34jQpkQIDAQABo2MwYTAdBgNVHQ4EFgQU41GH4816syafj+xi
0Q4VDDk2R08wHwYDVR0jBBgwFoAU41GH4816syafj+xi0Q4VDDk2R08wDwYDVR0T
AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggEBADrt
1bSbOYxDmB1x9KaHNFVSzs0XsipsbZW/XTqtNMPMFXh+I57NVptVanCwcfU5zmOF
AjL+R8v0NkPU9+6nSM2PYqWxEavf0f6pkxIj+ThFwtVwXEKocPG9RFUvZNZv+rSH
yAnzuAzFI71EsT9VgJGHI0pgPjrGbSlNfb0OJFOlwtbGWGofmg+N6hHcx5nVKlgL
ZWLtYT+/mT2pSGuIpJtdnuUv0vcrRa4mxAa8NPF4+Qpi6yErkfogE+T4RYf2L4rp
CaRIFicLoNUmwK0nCerJaPFLwGkiNGNX81CHnw3+xLisSPvxze2ZRA0DhUWUGInq
grjWDMO9P1hPWu5jmbo=
-----END CERTIFICATE-----

27
spec/fixtures/certificates/server.key vendored Normal file
View file

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAuByk7Ib6anz0xOFRqyogGsTTEAAGduquIckVFajDHrk7vdq+
TflkDp+UhbISqu1rj0OuaqODxiPSSw5fHClqNKpjhHS1ry86knS847BGwrByY0oa
zBdN0UgtWiXOK+iCuSI9p0KSIORJ3Y70pUZmm12EHRwb0tANv4ogYxjAxwnyqYgn
PnrDsoyCh/Cb4Fu8XucrnepYbInXi6yOdwTuivTx9qy7tlQzvYJ2LLEUIJdBtCUZ
dZkxky9CJUbfu5rm6PFvbLII5YCSlpXLxg9bumZCR1z9IXE6rLYcJIp3HIquR2cN
tAs9M8OHuR5V6vhUG51bP3aTkg3asJVdUe10dwIDAQABAoIBAQCJGINSwZv86blW
VbX7r+2iIUhNVMd7i3tJGzQBId7RpPswf49P/tIb9YaiG5y8/PgoAS0CqWn5hDkW
vMfj747vUqWyPzn/DjseTaFOJrg6RyuWddsIeJ3wpj9nLlmc5pFZDH8+alrn9TZv
rgDMhWTocjVre7/YNibWpyNAx3DdhG5DzNVLnu1R68d5k3JutQVqm01xCAV9ne9n
xE1RB5Z1xLvpQfW2qLYT0yFB7Xxw8awGyzVesPhGW1aa5F4urQjdCt2baa06Xolu
T3wXJ6wA9BuF2KOCi8DxELDaXoB//+82HafgWbOWIhJFOzEZaMNqZkfS/GbCgpEr
mE2r8zGBAoGBAOHNcUPgnIIeGdgFZZvL3Ge3Hp5mi2Vd2KBkAPNCjVSXeCu57yRC
SetlYuZlIhd7o+wdxUmWtg73DU19eDXJsOjXgNAoJfT9Zsyi4RClmJ0FRcSzvFU/
m/TKrBbnFFAI+1pKwDnQ7envuRiTECFSsvKqdr8hddx0cPCgDtbe+75BAoGBANC7
4ozkgsUTtdojz0DYBYBwUjN1gUETIhl91tt+48hmmEedROWDQDCT9gJfpAVFe1I6
RyKKJnBcgNDJ7mqPUB1f5xb5rtaZS1owPNYTi3GrdVVg3lAf0j5ch8XoRJn/plnL
M0Sj5lLMviHJjyk8CPHbnE2k2vERAW4/SgzfA3S3AoGAHx55Jamm6CfN1/+maTpH
PeP2zE3FmEq+uBwQJXZek/HsFdqiIpUgKtjmMGpvsFzR0pCnx+SFYrqZkrxf/Mm3
H9/TWNyvnnvt1vX7npez2LAJVXqP0g/aJnpoDR/7pKwYN/FlXJJ2t27aS5C5AF6t
WtQzWVP7Mk654e+tG9/PQgECgYEAiTCT7EpccK9NvLwAgfv5UbuBK3U1qNGsfdip
mMZDa/mSaK9DEx462DLHZDP8F8LdFORc0KTAMuV5fMDbxInA/C2GMyGT+lPypKpD
sehSpDku+xiZxUvE4VvrmPXZ8OWILkhRv/GBdjY/WPGi+FUPA/d1Ocr6Y6rrp8xN
HTyOhu0CgYBKxTSH6RCQsm8Q8uqmcP7cwe6fciTC0c2CRRqlzuXeseG72MBRDk/8
P1jtOIlIsax/8NwNm5ReAiLgVn/h6/YgN4fpMkV1XIaH4j7HiGf5CWgOTWxS9jWA
cV09H22BaNkT0fZ71IlXQI11cVRodX0g4cJXeuyTxY9OkMd6cGs8+A==
-----END RSA PRIVATE KEY-----

88
spec/fixtures/certificates/server.pem vendored Normal file
View file

@ -0,0 +1,88 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 4097 (0x1001)
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN=Intermediate CA
Validity
Not Before: Apr 18 16:14:29 2016 GMT
Not After : Apr 16 16:14:29 2026 GMT
Subject: CN=localhost
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:b8:1c:a4:ec:86:fa:6a:7c:f4:c4:e1:51:ab:2a:
20:1a:c4:d3:10:00:06:76:ea:ae:21:c9:15:15:a8:
c3:1e:b9:3b:bd:da:be:4d:f9:64:0e:9f:94:85:b2:
12:aa:ed:6b:8f:43:ae:6a:a3:83:c6:23:d2:4b:0e:
5f:1c:29:6a:34:aa:63:84:74:b5:af:2f:3a:92:74:
bc:e3:b0:46:c2:b0:72:63:4a:1a:cc:17:4d:d1:48:
2d:5a:25:ce:2b:e8:82:b9:22:3d:a7:42:92:20:e4:
49:dd:8e:f4:a5:46:66:9b:5d:84:1d:1c:1b:d2:d0:
0d:bf:8a:20:63:18:c0:c7:09:f2:a9:88:27:3e:7a:
c3:b2:8c:82:87:f0:9b:e0:5b:bc:5e:e7:2b:9d:ea:
58:6c:89:d7:8b:ac:8e:77:04:ee:8a:f4:f1:f6:ac:
bb:b6:54:33:bd:82:76:2c:b1:14:20:97:41:b4:25:
19:75:99:31:93:2f:42:25:46:df:bb:9a:e6:e8:f1:
6f:6c:b2:08:e5:80:92:96:95:cb:c6:0f:5b:ba:66:
42:47:5c:fd:21:71:3a:ac:b6:1c:24:8a:77:1c:8a:
ae:47:67:0d:b4:0b:3d:33:c3:87:b9:1e:55:ea:f8:
54:1b:9d:5b:3f:76:93:92:0d:da:b0:95:5d:51:ed:
74:77
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
Netscape Cert Type:
SSL Server
Netscape Comment:
OpenSSL Generated Server Certificate
X509v3 Subject Key Identifier:
1D:60:82:FA:3A:EC:27:91:BA:8D:F5:ED:B2:E3:85:0B:22:5A:8E:38
X509v3 Authority Key Identifier:
keyid:A9:75:99:CF:9C:92:54:A4:4B:65:CD:3D:FC:93:98:8D:9E:09:1F:47
DirName:/CN=Root CA
serial:10:01
X509v3 Key Usage: critical
Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Server Authentication
Signature Algorithm: sha256WithRSAEncryption
89:90:3d:2c:b8:0d:36:63:68:9a:cd:f9:14:56:94:d9:18:11:
b5:08:35:af:f9:34:cd:70:db:7d:66:06:e3:57:9b:06:8f:11:
d6:ea:ac:a6:07:db:ae:a2:c0:66:69:84:d8:2d:3c:cc:d7:4d:
3c:75:60:4f:98:fc:56:df:30:39:c6:55:2c:73:92:9e:0c:b5:
7c:75:40:5d:21:aa:01:c1:8a:03:86:eb:d7:02:7d:f5:7b:12:
cc:18:90:23:ad:8f:d7:05:18:6d:f0:11:a8:6b:27:fd:4c:07:
07:53:f5:7f:f7:a2:e5:18:1e:4e:90:1b:10:5f:f3:5c:cb:c7:
37:63:d0:d5:1d:3a:65:66:24:ee:0e:ce:7f:b1:fb:ee:17:d0:
b5:4d:64:2f:5a:9c:bc:7a:1c:c0:b4:0f:32:c9:a9:5c:cb:57:
26:fd:49:39:8d:f2:89:54:c4:92:b5:35:ec:fe:cf:87:07:a6:
84:01:98:00:e4:2a:44:26:b7:48:00:11:d3:e4:5a:c1:ad:46:
36:53:f9:28:b7:e4:c5:bb:66:88:ab:8e:cc:30:d0:96:aa:3e:
c1:12:6a:8f:fa:6d:19:15:f4:90:66:54:62:84:97:06:2d:5c:
b9:18:71:90:f4:ca:4c:8c:a5:8b:32:14:93:89:f1:93:f4:00:
bd:1d:42:4f
-----BEGIN CERTIFICATE-----
MIIDgjCCAmqgAwIBAgICEAEwDQYJKoZIhvcNAQELBQAwGjEYMBYGA1UEAwwPSW50
ZXJtZWRpYXRlIENBMB4XDTE2MDQxODE2MTQyOVoXDTI2MDQxNjE2MTQyOVowFDES
MBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEAuByk7Ib6anz0xOFRqyogGsTTEAAGduquIckVFajDHrk7vdq+TflkDp+UhbIS
qu1rj0OuaqODxiPSSw5fHClqNKpjhHS1ry86knS847BGwrByY0oazBdN0UgtWiXO
K+iCuSI9p0KSIORJ3Y70pUZmm12EHRwb0tANv4ogYxjAxwnyqYgnPnrDsoyCh/Cb
4Fu8XucrnepYbInXi6yOdwTuivTx9qy7tlQzvYJ2LLEUIJdBtCUZdZkxky9CJUbf
u5rm6PFvbLII5YCSlpXLxg9bumZCR1z9IXE6rLYcJIp3HIquR2cNtAs9M8OHuR5V
6vhUG51bP3aTkg3asJVdUe10dwIDAQABo4HXMIHUMAkGA1UdEwQCMAAwEQYJYIZI
AYb4QgEBBAQDAgZAMDMGCWCGSAGG+EIBDQQmFiRPcGVuU1NMIEdlbmVyYXRlZCBT
ZXJ2ZXIgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFB1ggvo67CeRuo317bLjhQsiWo44
MDsGA1UdIwQ0MDKAFKl1mc+cklSkS2XNPfyTmI2eCR9HoRakFDASMRAwDgYDVQQD
DAdSb290IENBggIQATAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUH
AwEwDQYJKoZIhvcNAQELBQADggEBAImQPSy4DTZjaJrN+RRWlNkYEbUINa/5NM1w
231mBuNXmwaPEdbqrKYH266iwGZphNgtPMzXTTx1YE+Y/FbfMDnGVSxzkp4MtXx1
QF0hqgHBigOG69cCffV7EswYkCOtj9cFGG3wEahrJ/1MBwdT9X/3ouUYHk6QGxBf
81zLxzdj0NUdOmVmJO4Ozn+x++4X0LVNZC9anLx6HMC0DzLJqVzLVyb9STmN8olU
xJK1Nez+z4cHpoQBmADkKkQmt0gAEdPkWsGtRjZT+Si35MW7Zoirjsww0JaqPsES
ao/6bRkV9JBmVGKElwYtXLkYcZD0ykyMpYsyFJOJ8ZP0AL0dQk8=
-----END CERTIFICATE-----