To verify and update the b10-cmdctl certificate, using a few hardcoded certificate options (cn=localhost, o=BIND10, country=US).
The tool can also be used to update the existing certificate if it has expired (it is only valid for 1 year)
then
BOTAN_LIBS=`${BOTAN_CONFIG} --libs`
BOTAN_INCLUDES=`${BOTAN_CONFIG} --cflags`
+ echo "XXX"
+ echo "XXX ${BOTAN_CONFIG}"
+ echo "XXX libs: ${BOTAN_LIBS}"
+ echo "XXX includes: ${BOTAN_INCLUDES}"
+ echo "XXX"
+ echo "XXX"
# We expect botan-config --libs to contain -L<path_to_libbotan>, but
# this is not always the case. As a heuristics workaround we add
AC_SUBST(BOTAN_LDFLAGS)
AC_SUBST(BOTAN_LIBS)
AC_SUBST(BOTAN_INCLUDES)
-
+echo "XXX substed: ${BOTAN_INCLUDES}"
# Even though chances are high we already performed a real compilation check
# in the search for the right (pkg)config data, we try again here, to
# be sure.
CPPFLAGS_SAVED=$CPPFLAGS
CPPFLAGS="$BOTAN_INCLUDES $CPPFLAGS"
+echo "XXX CPPFLAGS: ${CPPFLAGS}"
LIBS_SAVED="$LIBS"
LIBS="$LIBS $BOTAN_LIBS"
AC_CHECK_HEADERS([botan/botan.h],,AC_MSG_ERROR([Missing required header files.]))
$(SED) "s|@@PYTHONPATH@@|@pyexecdir@|" cmdctl.py >$@
chmod a+x $@
+bin_PROGRAMS = b10-certgen
+b10_certgen_SOURCES = b10-certgen.cc
+b10_certgen_CXXFLAGS = $(BOTAN_INCLUDES)
+b10_certgen_LDFLAGS = $(BOTAN_LIBS)
+
if INSTALL_CONFIGURATIONS
# Below we intentionally use ${INSTALL} -m 640 instead of $(INSTALL_DATA)
# because these file will contain sensitive information.
install-data-local:
- $(mkinstalldirs) $(DESTDIR)/@sysconfdir@/@PACKAGE@
+ $(mkinstalldirs) $(DESTDIR)/@sysconfdir@/@PACKAGE@
for f in $(CMDCTL_CONFIGURATIONS) ; do \
if test ! -f $(DESTDIR)$(sysconfdir)/@PACKAGE@/$$f; then \
${INSTALL} -m 640 $(srcdir)/$$f $(DESTDIR)$(sysconfdir)/@PACKAGE@/ ; \
--- /dev/null
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <botan/botan.h>
+#include <botan/x509self.h>
+#include <botan/x509stor.h>
+#include <botan/rsa.h>
+#include <botan/dsa.h>
+#include <botan/data_src.h>
+using namespace Botan;
+
+#include <iostream>
+#include <fstream>
+#include <memory>
+#include <getopt.h>
+
+// For cleaner 'does not exist or is not readable' output than
+// botan provides
+#include <unistd.h>
+#include <errno.h>
+
+// This is a simple tool that creates a self-signed PEM certificate
+// for use with BIND 10. It creates a simple certificate for initial
+// setup. Currently, all values are hardcoded defaults. For future
+// versions, we may want to add more options for administrators.
+
+// It will create a PEM file containing a certificate with the following
+// values:
+// common name: localhost
+// organization: BIND10
+// country code: US
+
+// Additional error return codes; these are specifically
+// chosen to be distinct from validation error codes as
+// provided by Botan. Their main use is to distinguish
+// error cases in the unit tests.
+const int DECODING_ERROR = 100;
+const int BAD_OPTIONS = 101;
+const int READ_ERROR = 102;
+const int WRITE_ERROR = 103;
+const int UNKNOWN_ERROR = 104;
+const int NO_SUCH_FILE = 105;
+
+void
+usage() {
+ std::cout << "Usage: b10-certgen [OPTION]..." << std::endl;
+ std::cout << "Validate, create, or update a self-signed certificate for "
+ "use with b10-cmdctl" << std::endl;
+ std::cout << "" << std::endl;
+ std::cout << "Options:" << std::endl;
+ std::cout << "-c, --certfile=FILE\t\tfile to read or store the certificate"
+ << std::endl;
+ std::cout << "-f, --force\t\t\toverwrite existing certficate even if it"
+ << std::endl <<"\t\t\t\tis valid" << std::endl;
+ std::cout << "-h, --help\t\t\tshow this help" << std::endl;
+ std::cout << "-k, --keyfile=FILE\t\tfile to store the generated private key"
+ << std::endl;
+ std::cout << "-w, --write\t\t\tcreate a new certificate if the given file "
+ "does not exist, or if is is not valid" << std::endl;
+ std::cout << "-q, --quiet\t\t\tprint no output when creating or validating"
+ << std::endl;
+}
+
+/// \brief Returns true if the given file exists and is readable
+///
+/// \param filename The file to check
+/// \return true if file exists and is readable
+bool
+fileExists(const std::string& filename) {
+ return (access(filename.c_str(), R_OK) == 0);
+}
+
+/// \brief Helper function for readable error output;
+///
+/// Returns string representation of X509 result code
+/// This does not appear to be provided by Botan itself
+///
+/// \param code An \c X509_Code instance
+/// \return A human-readable c string
+const char*
+X509CodeToString(const X509_Code& code) {
+ // note that this list provides more than we would
+ // need in this context, it is just the enum from
+ // the source code of Botan.
+ switch (code) {
+ case VERIFIED:
+ return ("verified");
+ case UNKNOWN_X509_ERROR:
+ return ("unknown x509 error");
+ case CANNOT_ESTABLISH_TRUST:
+ return ("cannot establish trust");
+ case CERT_CHAIN_TOO_LONG:
+ return ("certificate chain too long");
+ case SIGNATURE_ERROR:
+ return ("signature error");
+ case POLICY_ERROR:
+ return ("policy error");
+ case INVALID_USAGE:
+ return ("invalid usage");
+ case CERT_FORMAT_ERROR:
+ return ("certificate format error");
+ case CERT_ISSUER_NOT_FOUND:
+ return ("certificate issuer not found");
+ case CERT_NOT_YET_VALID:
+ return ("certificate not yet valid");
+ case CERT_HAS_EXPIRED:
+ return ("certificate has expired");
+ case CERT_IS_REVOKED:
+ return ("certificate has been revoked");
+ case CRL_FORMAT_ERROR:
+ return ("crl format error");
+ case CRL_NOT_YET_VALID:
+ return ("crl not yet valid");
+ case CRL_HAS_EXPIRED:
+ return ("crl has expired");
+ case CA_CERT_CANNOT_SIGN:
+ return ("CA cert cannot sign");
+ case CA_CERT_NOT_FOR_CERT_ISSUER:
+ return ("CA certificate not for certificate issuer");
+ case CA_CERT_NOT_FOR_CRL_ISSUER:
+ return ("CA certificate not for crl issuer");
+ default:
+ return ("Unknown X509 code");
+ }
+}
+
+class CertificateTool {
+public:
+ CertificateTool(bool quiet) : quiet_(quiet) {}
+
+ int
+ createKeyAndCertificate(const std::string& key_file_name,
+ const std::string& cert_file_name) {
+ try {
+ AutoSeeded_RNG rng;
+
+ // Create and store a private key
+ RSA_PrivateKey key(rng, 2048);
+
+ print("Creating key file " + key_file_name);
+ std::ofstream key_file(key_file_name.c_str());
+ key_file << PKCS8::PEM_encode(key, rng, "");
+ if (!key_file.good()) {
+ print(std::string("Error writing to ") + key_file_name +
+ ": " + strerror(errno));
+ return WRITE_ERROR;
+ }
+ key_file.close();
+
+ // Certificate options, currently hardcoded.
+ // For a future version we may want to make these
+ // settable.
+ X509_Cert_Options opts;
+ opts.common_name = "localhost";
+ opts.organization = "BIND10";
+ opts.country = "US";
+
+ opts.CA_key();
+
+ print("Creating certificate file " + cert_file_name);
+
+ // The exact call changed aftert 1.8, adding the
+ // hash function option
+#if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,9,0)
+ X509_Certificate cert =
+ X509::create_self_signed_cert(opts, key, "SHA-256", rng);
+#else
+ X509_Certificate cert =
+ X509::create_self_signed_cert(opts, key, rng);
+#endif
+
+ std::ofstream cert_file(cert_file_name.c_str());
+ if (!cert_file.good()) {
+ print(std::string("Error writing to ") + cert_file_name +
+ ": " + strerror(errno));
+ return (WRITE_ERROR);
+ }
+ cert_file << cert.PEM_encode();
+ cert_file.close();
+ } catch(std::exception& e) {
+ std::cout << "Error creating key or certificate: " << e.what()
+ << std::endl;
+ return (UNKNOWN_ERROR);
+ }
+ return (0);
+ }
+
+ int
+ validateCertificate(const std::string& certfile) {
+ // Since we are dealing with a self-signed certificate here, we
+ // also use the certificate to check itself; i.e. we add it
+ // as a trusted certificate, then validate the certficate itself.
+ //const X509_Certificate cert(certfile);
+ try {
+ X509_Store store;
+ DataSource_Stream in(certfile);
+ store.add_trusted_certs(in);
+
+ const X509_Code result = store.validate_cert(certfile);
+
+ if (result == VERIFIED) {
+ print(certfile + " is valid");
+ } else {
+ print(certfile + " failed to verify: " +
+ X509CodeToString(result));
+ }
+ return (result);
+ } catch (const Botan::Decoding_Error& bde) {
+ print(certfile + " failed to verify: " + bde.what());
+ return (DECODING_ERROR);
+ } catch (const Botan::Stream_IO_Error& bsie) {
+ print(certfile + " not read: " + bsie.what());
+ return (READ_ERROR);
+ }
+ }
+
+ /// \brief Runs the tool
+ ///
+ /// \param create_cert Create certificate if true, validate if false.
+ /// Does nothing if certificate exists and is valid.
+ /// \param force_create Create new certificate even if it is valid.
+ /// \param certfile Certificate file to read to or write from.
+ /// \param keyfile Key file to write if certificate is created.
+ /// Ignored if create_cert is false
+ /// \return zero on success, non-zero on failure
+ int
+ run(bool create_cert, bool force_create, const std::string& certfile,
+ const std::string& keyfile)
+ {
+ if (create_cert) {
+ // Unless force is given, only create it if the current
+ // one is not OK
+ if (force_create || !fileExists(certfile) ||
+ validateCertificate(certfile) != VERIFIED) {
+ return (createKeyAndCertificate(keyfile, certfile));
+ } else {
+ print(certfile + " exists and is valid. Not creating a new one");
+ }
+ } else {
+ if (!fileExists(certfile)) {
+ print(certfile + ": " + strerror(errno));
+ return (NO_SUCH_FILE);
+ }
+ int result = validateCertificate(certfile);
+ if (result != 0 && !quiet_) {
+ print("Running with -w would overwrite the certificate");
+ }
+ return (result);
+ }
+ return (0);
+ }
+private:
+ /// Prints the message to stdout unless quiet_ is true
+ void print(const std::string& msg) {
+ if (!quiet_) {
+ std::cout << msg << std::endl;
+ }
+ }
+
+ bool quiet_;
+};
+
+int
+main(int argc, char* argv[])
+{
+ Botan::LibraryInitializer init;
+
+ // create or check certificate
+ bool create_cert = false;
+ // force creation even if not necessary
+ bool force_create = false;
+ // don't print any output
+ bool quiet = false;
+
+ // default certificate file
+ std::string certfile("cmdctl-certfile.pem");
+ // default key file
+ std::string keyfile("cmdctl-keyfile.pem");
+
+ // whether or not the above values have been
+ // overridden (used in command line checking)
+ bool certfile_default = true;
+ bool keyfile_default = true;
+
+ struct option long_options[] = {
+ { "certfile", required_argument, 0, 'c' },
+ { "force", no_argument, 0, 'f' },
+ { "help", no_argument, 0, 'h' },
+ { "keyfile", required_argument, 0, 'k' },
+ { "write", no_argument, 0, 'w' },
+ { "quiet", no_argument, 0, 'q' },
+ { 0, 0, 0, 0 }
+ };
+
+ int opt, option_index;
+ while ((opt = getopt_long(argc, argv, "c:fhk:wq", long_options,
+ &option_index)) != -1) {
+ switch (opt) {
+ case 'c':
+ certfile = optarg;
+ certfile_default = false;
+ break;
+ case 'f':
+ force_create = true;
+ break;
+ case 'h':
+ usage();
+ return (0);
+ break;
+ case 'k':
+ keyfile = optarg;
+ keyfile_default = false;
+ break;
+ case 'w':
+ create_cert = true;
+ break;
+ case 'q':
+ quiet = true;
+ break;
+ default:
+ // A message will have already been output about the error.
+ return (BAD_OPTIONS);
+ }
+ }
+
+ if (optind < argc) {
+ std::cout << "Error: extraneous arguments" << std::endl << std::endl;
+ usage();
+ return (BAD_OPTIONS);
+ }
+
+ // Some sanity checks on option combinations
+ if ((create_cert && certfile_default && !keyfile_default) ||
+ (create_cert && !certfile_default && keyfile_default)) {
+ std::cout << "Error: keyfile and certfile must both be specified "
+ "if one of them is when calling b10-certgen in write "
+ "mode." << std::endl;
+ return (BAD_OPTIONS);
+ }
+ if (!create_cert && !keyfile_default) {
+ std::cout << "Error: keyfile is not used when not in write mode"
+ << std::endl;
+ return (BAD_OPTIONS);
+ }
+
+ // Initialize the tool and perform the appropriate action(s)
+ CertificateTool tool(quiet);
+ return (tool.run(create_cert, force_create, certfile, keyfile));
+}
PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
-PYTESTS = cmdctl_test.py
+PYTESTS = cmdctl_test.py b10-certgen_test.py
EXTRA_DIST = $(PYTESTS)
# If necessary (rare cases), explicitly specify paths to dynamic libraries
LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
+CLEANFILES = test-keyfile.pem test-certfile.pem
+
# test using command-line arguments, so use check-local target instead of TESTS
check-local:
if ENABLE_PYTHON_COVERAGE
- touch $(abs_top_srcdir)/.coverage
+ touch $(abs_top_srcdir)/.coverage
rm -f .coverage
${LN_S} $(abs_top_srcdir)/.coverage .coverage
endif
--- /dev/null
+# Copyright (C) 2012 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# Note: the main code is in C++, but what we are mostly testing is
+# options and behaviour (output/file creation, etc), which is easier
+# to test in python.
+
+import unittest
+import os
+from subprocess import call
+import subprocess
+import ssl
+
+def run(command):
+ """
+ Small helper function that returns a tuple of (rcode, stdout, stderr) after
+ running the given command (an array of command and arguments, as passed on
+ to subprocess).
+ """
+ subp = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ (stdout, stderr) = subp.communicate()
+ return (subp.returncode, stdout, stderr)
+
+class FileDeleterContext:
+ """
+ Simple Context Manager that deletes a given set of files when the context
+ is left.
+ """
+ def __init__(self, files):
+ self.files = files
+
+ def __enter__(self):
+ pass
+
+ def __exit__(self, type, value, traceback):
+ for f in self.files:
+ if os.path.exists(f):
+ os.unlink(f)
+
+def read_file_data(filename):
+ """
+ Simple text file reader that returns its contents as an array
+ """
+ with open(filename) as f:
+ return f.readlines()
+
+class TestCertGenTool(unittest.TestCase):
+ TOOL = '../b10-certgen'
+
+ def run_check(self, expected_returncode, expected_stdout, expected_stderr, command):
+ """
+ Runs the given command, and checks return code, and outputs (if provided).
+ Arguments:
+ expected_returncode, return code of the command
+ expected_stdout, (multiline) string that is checked agains stdout.
+ May be None, in which case the check is skipped.
+ expected_stderr, (multiline) string that is checked agains stderr.
+ May be None, in which case the check is skipped.
+ """
+ (returncode, stdout, stderr) = run(command)
+ self.assertEqual(expected_returncode, returncode, " ".join(command))
+ if expected_stdout is not None:
+ self.assertEqual(expected_stdout, stdout.decode())
+ if expected_stderr is not None:
+ self.assertEqual(expected_stderr, stderr.decode())
+
+ def validate_certificate(self, expected_result, certfile):
+ """
+ Validate a certificate, using the quiet option of the tool; it runs
+ the check option (-c) for the given base name of the certificate (-f
+ <certfile>), and compares the return code to the given
+ expected_result value
+ """
+ self.run_check(expected_result, '', '',
+ [self.TOOL, '-q', '-c', certfile])
+ # Same with long options
+ self.run_check(expected_result, '', '',
+ [self.TOOL, '--quiet', '--certfile', certfile])
+
+
+ def test_basic_creation(self):
+ """
+ Tests whether basic creation with no arguments (except output
+ file name) successfully creates a key and certificate
+ """
+ keyfile = 'test-keyfile.pem'
+ certfile = 'test-certfile.pem'
+ command = [ self.TOOL, '-q', '-w', '-c', certfile, '-k', keyfile ]
+ self.creation_helper(command, certfile, keyfile)
+ # Do same with long options
+ command = [ self.TOOL, '--quiet', '--write', '--certfile=' + certfile, '--keyfile=' + keyfile ]
+ self.creation_helper(command, certfile, keyfile)
+
+ def creation_helper(self, command, certfile, keyfile):
+ """
+ Helper method for test_basic_creation.
+ Performs the actual checks
+ """
+ with FileDeleterContext([keyfile, certfile]):
+ self.assertFalse(os.path.exists(keyfile))
+ self.assertFalse(os.path.exists(certfile))
+ self.run_check(0, '', '', command)
+ self.assertTrue(os.path.exists(keyfile))
+ self.assertTrue(os.path.exists(certfile))
+
+ # Validate the certificate that was just created
+ self.validate_certificate(0, certfile)
+
+ # When run with the same options, it should *not* create it again,
+ # as the current certificate should still be valid
+ certdata = read_file_data(certfile)
+ keydata = read_file_data(keyfile)
+
+ self.run_check(0, '', '', command)
+
+ self.assertEqual(certdata, read_file_data(certfile))
+ self.assertEqual(keydata, read_file_data(keyfile))
+
+ # but if we add -f, it should force a new creation
+ command.append('-f')
+ self.run_check(0, '', '', command)
+ self.assertNotEqual(certdata, read_file_data(certfile))
+ self.assertNotEqual(keydata, read_file_data(keyfile))
+
+ def test_check_bad_certificates(self):
+ """
+ Tests a few pre-created certificates with the -c option
+ """
+ self.validate_certificate(10, 'testdata/expired-certfile.pem')
+ self.validate_certificate(100, 'testdata/mangled-certfile.pem')
+ self.validate_certificate(17, 'testdata/noca-certfile.pem')
+
+ def test_bad_options(self):
+ """
+ Tests some combinations of commands that should fail.
+ """
+ # specify -c but not -k
+ self.run_check(101,
+ 'Error: keyfile and certfile must both be specified '
+ 'if one of them is when calling b10-certgen in write '
+ 'mode.\n',
+ '', [self.TOOL, '-w', '-c', 'foo'])
+ self.run_check(101,
+ 'Error: keyfile and certfile must both be specified '
+ 'if one of them is when calling b10-certgen in write '
+ 'mode.\n',
+ '', [self.TOOL, '-w', '-k', 'foo'])
+ self.run_check(101,
+ 'Error: keyfile is not used when not in write mode\n',
+ '', [self.TOOL, '-k', 'foo'])
+ # Extraneous argument
+ self.run_check(101, None, None, [self.TOOL, 'foo'])
+ # No such file
+ self.run_check(105, None, None, [self.TOOL, '-c', 'foo'])
+
+if __name__== '__main__':
+ unittest.main()
+
--- /dev/null
+-----BEGIN CERTIFICATE-----
+MIIDhzCCAvCgAwIBAgIJALwngNFik7ONMA0GCSqGSIb3DQEBBQUAMIGKMQswCQYD
+VQQGEwJjbjEQMA4GA1UECBMHYmVpamluZzEQMA4GA1UEBxMHYmVpamluZzEOMAwG
+A1UEChMFY25uaWMxDjAMBgNVBAsTBWNubmljMRMwEQYDVQQDEwp6aGFuZ2xpa3Vu
+MSIwIAYJKoZIhvcNAQkBFhN6aGFuZ2xpa3VuQGNubmljLmNuMB4XDTEwMDEwNzEy
+NDcxOFoXDTExMDEwNzEyNDcxOFowgYoxCzAJBgNVBAYTAmNuMRAwDgYDVQQIEwdi
+ZWlqaW5nMRAwDgYDVQQHEwdiZWlqaW5nMQ4wDAYDVQQKEwVjbm5pYzEOMAwGA1UE
+CxMFY25uaWMxEzARBgNVBAMTCnpoYW5nbGlrdW4xIjAgBgkqhkiG9w0BCQEWE3po
+YW5nbGlrdW5AY25uaWMuY24wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOkg
+JbEkYoy9SEsU9t/mfxlaiCqNhxCqqgeodVEdiPKJ7LoVl21mRjazWBiHQbQ1e2Ka
+UiCJz68RwV7u92bIqe1bsNgNqoCPQqsQPtEoCPzfbiM1tIke0s/h6+8l6ne+yg21
+O825x5Anjq+6THLGCDcO4L2RWo+4PwJnVGrgBPKLAgMBAAGjgfIwge8wHQYDVR0O
+BBYEFJKM/O0ViGlwtb3JEci/DLTO/7DaMIG/BgNVHSMEgbcwgbSAFJKM/O0ViGlw
+tb3JEci/DLTO/7DaoYGQpIGNMIGKMQswCQYDVQQGEwJjbjEQMA4GA1UECBMHYmVp
+amluZzEQMA4GA1UEBxMHYmVpamluZzEOMAwGA1UEChMFY25uaWMxDjAMBgNVBAsT
+BWNubmljMRMwEQYDVQQDEwp6aGFuZ2xpa3VuMSIwIAYJKoZIhvcNAQkBFhN6aGFu
+Z2xpa3VuQGNubmljLmNuggkAvCeA0WKTs40wDAYDVR0TBAUwAwEB/zANBgkqhkiG
+9w0BAQUFAAOBgQBh5N6isMAQAFFD+pbfpppjQlO4vUNcEdzPdeuBFaf9CsX5ZdxV
+jmn1ZuGm6kRzqUPwPSxvCIAY0wuSu1g7YREPAZ3XBVwcg6262iGOA6n7E+nv5PLz
+EuZ1oUg+IfykUIoflKH6xZB4MyPL+EgkMT+i9BrngaXHXF8tEO30YppMiA==
+-----END CERTIFICATE-----
--- /dev/null
+-----BEGIN CERTIFICATE-----
+MIIDhzCCAvCgAwIBAgIJALwngNFik7ONMA0GCSqGSIb3DQEBBQUAMIGKMQswCQYD
+VQQGEwJjbjEQMA4GA1UECBMHYmVpamluZzEQMA4GA1UEBxMHYmVpamluZzEOMAwG
+A1UEChMFY25uaWMxDjAMBgNVBAsTBWNubmljMRMwEQYDVQQDEwp6aGFuZ2xpa3Vu
+MSIwIAYJKoZIhvcNAQkBFhN6aGFuZ2xpa3VuQGNubmljLmNuMB4XDTEwMDEwNzEy
+NDcxOFoXDTExMDEwNzEyNDcxOFowgYoxCzAJBgNVBAYTAmNuMRAwDgYDVQQIEwdi
+ZWlqaW5nMraWDgYDVQQHEwdiZWlqaW5nMQ4wDAYDVQQKEwVjbm5pYzEOMAwGA1UE
+CxMFY25uaWMxeZaRBgNVBAMTCnpoYW5nbGlrdW4xIjAgBgkqhkiG9w0BCQEWE3po
+YW5nbGlrdW5AY25UAwMuY24wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOkg
+JbEkYoy9SEsU9t/mfxLAICqNhxCqqgeodVEdiPKJ7LoVl21mRjazWBiHQbQ1e2Ka
+UiCJz68RwV7u92bIqe1bsNgNqoCPQqsQPtEoCPzfbiM1tIke0s/h6+8l6ne+yg21
+O825x5Anjq+6THLGCDcO4L2RWo+4PwJnVGrgBPKLAgMBAAGjgfIwge8wHQYDVR0O
+BBYEFJKM/O0ViGlwtb3JEci/DLTO/7DaMIG/BgNVHSMEgbcwgbSAFJKM/O0ViGlw
+tb3JEci/DLTO/7DaoYGQpIGNMIGKMQswCQYDVQQGEwJjbjEQMA4GA1UECBMHYmVp
+amluZzEQMA4GA1UEBxMHYmVpamluZzEOMAwGA1UEChMFY25uaWMxDjAMBgNVBAsT
+BWNubmljMRMwEQYDVQQDEwp6aGFuZ2xpa3VuMSIwIAYJKoZIhvcNAQkBFhN6aGFu
+Z2xpa3VuQGNubmljLmNuggkAvCeA0WKTs40wDAYDVR0TBAUwAwEB/zANBgkqhkiG
+9w0BAQUFAAOBgQBh5N6isMAQAFFD+pbfpppjQlO4vUNcEdzPdeuBFaf9CsX5ZdxV
+jmn1ZuGm6kRzqUPwPSxvCIAY0wuSu1g7YREPAZ3XBVwcg6262iGOA6n7E+nv5PLz
+EuZ1oUg+IfykUIoflKH6xZB4MyPL+EgkMT+i9BrngaXHXF8tEO30YppMiA==
+-----END CERTIFICATE-----
--- /dev/null
+-----BEGIN CERTIFICATE-----
+MIIDBjCCAe6gAwIBAgIRALUIj3nnW5uDE/+fglPvUDwwDQYJKoZIhvcNAQELBQAw
+HjELMAkGA1UEBhMCVVMxDzANBgNVBAMTBkJJTkQxMDAeFw0xMjExMTQxMjQ5MjVa
+Fw0xMzExMTQxMjQ5MjVaMB4xCzAJBgNVBAYTAlVTMQ8wDQYDVQQDEwZCSU5EMTAw
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkIOPfs3Aw9kNDu1JqA2w3
+84/n9oUgAwAlHVmuJv7ZDw1MDaIKHjsh3DW09z+nv67GVksI7pFtAw5O4mnTDxpa
+JT0NKzhvYGfe8VdV/hWDogTIdk1QBJNZ2/id8z0h8z5001sARXPf+4mHBJslenH3
+YtZs22BG5RBLULtZ/2Nr7JkdfLlc6D5PCoDG22r1OiFkYVdCWfLDjisVIbSYPBtY
+BlKAIrvbmOtWcaGM+vQAhl0T5N8WRCKhaQH0DEmzQNckkYd7rSECo57KYiuvOdzp
+d+3bWTgGGy2ff0o3LZypv0O5s0TDC2H6hYtN4bUbcChUJbFu9b5sVZaOEVZtUsyD
+AgMBAAGjPzA9MAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgTwMB0GA1UdDgQW
+BBSqGzsEDNs9E7gBL5pD6XVAwUo4DTANBgkqhkiG9w0BAQsFAAOCAQEAMTNB8NCU
+dnLFZ0jNpvecbECkX/OWGlBYU4/CsoNiibwp4CtUYS2A4NFVjWAyuzLSHhRQi0vJ
+CCWLpKL4VTkaDN5Oft42iUhvEXMnriJqpfXHnjCiBwFFSPl5WKfMIaRNK+tF4zbB
+F+FGNEEmYG3t/ni82orDLq4oy+7CoQwzZNzj5yoV6q7O9kLR9OMPNwJrc27A4erB
+7VMRZslSrNA4uA6YhMZl8iEvO1H801ct0zTxawrCihPOZOCSLew35xjztO7d3YH8
+YavOu5kzeu7AgZ2n75H/qU47ZgBjbonn9Osvrct+RIwZuWTB2bDML8JhNaZCq0aA
+TDBC0QWqIYypLg==
+-----END CERTIFICATE-----