BIND 10 master, updated. e8dd780ac88735b31bf3e7df6dd9f4a2b972da94 [master] Update changelog for merge of #1044

BIND 10 source code commits bind10-changes at lists.isc.org
Wed Nov 28 11:12:37 UTC 2012


The branch, master has been updated
       via  e8dd780ac88735b31bf3e7df6dd9f4a2b972da94 (commit)
       via  43b0ea4216ce8578e630be460fa5eadacb712c40 (commit)
       via  69e9734996d5d1edf533929598093cba0432e406 (commit)
       via  ebe0d8a602ba6157bf519f1f79759a5bfcf1a4a1 (commit)
       via  2241bdcfffcc27aea35b79926eb2769fd479f1e9 (commit)
       via  2066e02cfc6950bf8cf8e40a9a678ad74563bd36 (commit)
       via  35491a7585e0e7ed13ea9012ff03415ffb85103a (commit)
      from  8b10f38aeb02dba5b454c94e8788de450a5955be (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit e8dd780ac88735b31bf3e7df6dd9f4a2b972da94
Author: Jelte Jansen <jelte at isc.org>
Date:   Wed Nov 28 11:17:56 2012 +0100

    [master] Update changelog for merge of #1044

commit 43b0ea4216ce8578e630be460fa5eadacb712c40
Merge: 8b10f38 69e9734
Author: Jelte Jansen <jelte at isc.org>
Date:   Wed Nov 28 11:17:12 2012 +0100

    [master] Merge branch 'trac1044'

-----------------------------------------------------------------------

Summary of changes:
 ChangeLog                                          |    8 +
 configure.ac                                       |    1 -
 src/bin/cmdctl/.gitignore                          |    2 +
 src/bin/cmdctl/Makefile.am                         |   24 +-
 src/bin/cmdctl/b10-certgen.cc                      |  426 ++++++++++++++++++++
 src/bin/cmdctl/b10-certgen.xml                     |  214 ++++++++++
 src/bin/cmdctl/cmdctl-keyfile.pem                  |   15 -
 src/bin/cmdctl/tests/Makefile.am                   |    9 +-
 src/bin/cmdctl/tests/b10-certgen_test.py           |  254 ++++++++++++
 .../testdata/expired-certfile.pem}                 |    0
 .../testdata/mangled-certfile.pem}                 |    8 +-
 src/bin/cmdctl/tests/testdata/noca-certfile.pem    |   19 +
 12 files changed, 954 insertions(+), 26 deletions(-)
 create mode 100644 src/bin/cmdctl/b10-certgen.cc
 create mode 100644 src/bin/cmdctl/b10-certgen.xml
 delete mode 100644 src/bin/cmdctl/cmdctl-keyfile.pem
 create mode 100644 src/bin/cmdctl/tests/b10-certgen_test.py
 copy src/bin/cmdctl/{cmdctl-certfile.pem => tests/testdata/expired-certfile.pem} (100%)
 rename src/bin/cmdctl/{cmdctl-certfile.pem => tests/testdata/mangled-certfile.pem} (80%)
 create mode 100644 src/bin/cmdctl/tests/testdata/noca-certfile.pem

-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index b412ce4..5d9c697 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+512.	[func]		jelte
+	Added a new tool b10-certgen, to check and update the self-signed
+	SSL certificate used by b10-cmdctl. The original certificate
+	provided has been removed, and a fresh one is generated upon first
+	build. See the b10-certgen manpage for information on how to update
+	existing installed certificates.
+	(Trac #1044, git 510773dd9057ccf6caa8241e74a7a0b34ca971ab)
+
 511.	[bug]		stephen
 	Fixed a race condition in the DHCP tests whereby the test program
 	spawned a subprocess and attempted to read (without waiting) from
diff --git a/configure.ac b/configure.ac
index b0ca18a..636f8aa 100644
--- a/configure.ac
+++ b/configure.ac
@@ -705,7 +705,6 @@ fi
 AC_SUBST(BOTAN_LDFLAGS)
 AC_SUBST(BOTAN_LIBS)
 AC_SUBST(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.
diff --git a/src/bin/cmdctl/.gitignore b/src/bin/cmdctl/.gitignore
index 01e3ef0..dd6bbc2 100644
--- a/src/bin/cmdctl/.gitignore
+++ b/src/bin/cmdctl/.gitignore
@@ -4,3 +4,5 @@
 /cmdctl.spec.pre
 /run_b10-cmdctl.sh
 /b10-cmdctl.8
+/cmdctl-keyfile.pem
+/cmdctl-certfile.pem
diff --git a/src/bin/cmdctl/Makefile.am b/src/bin/cmdctl/Makefile.am
index 3b88f4b..bfc13af 100644
--- a/src/bin/cmdctl/Makefile.am
+++ b/src/bin/cmdctl/Makefile.am
@@ -4,6 +4,8 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@
 
 pkglibexec_SCRIPTS = b10-cmdctl
 
+bin_PROGRAMS = b10-certgen
+
 nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.py
 pylogmessagedir = $(pyexecdir)/isc/log_messages/
 
@@ -25,15 +27,18 @@ CLEANFILES= b10-cmdctl cmdctl.pyc cmdctl.spec
 CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.py
 CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.pyc
 
-man_MANS = b10-cmdctl.8
-DISTCLEANFILES = $(man_MANS)
-EXTRA_DIST += $(man_MANS) b10-cmdctl.xml cmdctl_messages.mes
+man_MANS = b10-cmdctl.8 b10-certgen.1
+DISTCLEANFILES = $(man_MANS) cmdctl-certfile.pem cmdctl-keyfile.pem
+EXTRA_DIST += $(man_MANS) b10-certgen.xml b10-cmdctl.xml cmdctl_messages.mes
 
 if GENERATE_DOCS
 
 b10-cmdctl.8: b10-cmdctl.xml
 	@XSLTPROC@ --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-cmdctl.xml
 
+b10-certgen.1: b10-certgen.xml
+	@XSLTPROC@ --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-certgen.xml
+
 else
 
 $(man_MANS):
@@ -54,12 +59,23 @@ b10-cmdctl: cmdctl.py $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.py
 	$(SED) "s|@@PYTHONPATH@@|@pyexecdir@|" cmdctl.py >$@
 	chmod a+x $@
 
+b10_certgen_SOURCES = b10-certgen.cc
+b10_certgen_CXXFLAGS = $(BOTAN_INCLUDES)
+b10_certgen_LDFLAGS = $(BOTAN_LIBS)
+
+# Generate the initial certificates immediately
+cmdctl-certfile.pem: b10-certgen
+	./b10-certgen -q -w
+
+cmdctl-keyfile.pem: b10-certgen
+	./b10-certgen -q -w
+
 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@/ ;	\
diff --git a/src/bin/cmdctl/b10-certgen.cc b/src/bin/cmdctl/b10-certgen.cc
new file mode 100644
index 0000000..96a08ba
--- /dev/null
+++ b/src/bin/cmdctl/b10-certgen.cc
@@ -0,0 +1,426 @@
+// 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;
+const int FILE_PERMISSION_ERROR = 106;
+
+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"
+              << std::endl << "\t\t\t\tdoes 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
+///
+/// \param filename The file to check
+/// \return true if file exists
+bool
+fileExists(const std::string& filename) {
+    return (access(filename.c_str(), F_OK) == 0);
+}
+
+/// \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
+fileIsReadable(const std::string& filename) {
+    return (access(filename.c_str(), R_OK) == 0);
+}
+
+/// \brief Returns true if the given file exists and is writable
+///
+/// \param filename The file to check
+/// \return true if file exists and is writable
+bool
+fileIsWritable(const std::string& filename) {
+    return (access(filename.c_str(), W_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
+            print("Creating key file " + key_file_name);
+            RSA_PrivateKey key(rng, 2048);
+            std::ofstream key_file(key_file_name.c_str());
+            if (!key_file.good()) {
+                print(std::string("Error writing to ") + key_file_name +
+                      ": " + strerror(errno));
+                return (WRITE_ERROR);
+            }
+            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 = "UNKNOWN";
+            opts.country = "XX";
+
+            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();
+            if (!cert_file.good()) {
+                print(std::string("Error writing to ") + cert_file_name +
+                      ": " + strerror(errno));
+                return (WRITE_ERROR);
+            }
+            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
+
+            // First do some basic permission checks; both files
+            // should either not exist, or be both readable
+            // and writable
+            // The checks are done one by one so all errors can
+            // be enumerated in one go
+            if (fileExists(certfile)) {
+                if (!fileIsReadable(certfile)) {
+                    print(certfile + " not readable: " + strerror(errno));
+                    create_cert = false;
+                }
+                if (!fileIsWritable(certfile)) {
+                    print(certfile + " not writable: " + strerror(errno));
+                    create_cert = false;
+                }
+            }
+            // The key file really only needs write permissions (for
+            // b10-certgen that is)
+            if (fileExists(keyfile)) {
+                if (!fileIsWritable(keyfile)) {
+                    print(keyfile + " not writable: " + strerror(errno));
+                    create_cert = false;
+                }
+            }
+            if (!create_cert) {
+                print("Not creating new certificate, "
+                      "check file permissions");
+                return (FILE_PERMISSION_ERROR);
+            }
+
+            // If we reach this, we know that if they exist, we can both
+            // read and write them, so now it's up to content checking
+            // and/or force_create
+
+            if (force_create || !fileExists(certfile) ||
+                validateCertificate(certfile) != VERIFIED) {
+                return (createKeyAndCertificate(keyfile, certfile));
+            } else {
+                print("Not creating a new certificate (use -f to force)");
+            }
+        } else {
+            if (!fileExists(certfile)) {
+                print(certfile + ": " + strerror(errno));
+                return (NO_SUCH_FILE);
+            }
+            if (!fileIsReadable(certfile)) {
+                print(certfile + " not readable: " + strerror(errno));
+                return (FILE_PERMISSION_ERROR);
+            }
+            int result = validateCertificate(certfile);
+            if (result != 0) {
+                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)) {
+        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));
+}
diff --git a/src/bin/cmdctl/b10-certgen.xml b/src/bin/cmdctl/b10-certgen.xml
new file mode 100644
index 0000000..1e3c8e3
--- /dev/null
+++ b/src/bin/cmdctl/b10-certgen.xml
@@ -0,0 +1,214 @@
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+               "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
+	       [<!ENTITY mdash "—">]>
+<!--
+ - 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.
+-->
+
+<refentry>
+
+  <refentryinfo>
+    <date>November 15, 2012</date>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>b10-certgen</refentrytitle>
+    <manvolnum>1</manvolnum>
+    <refmiscinfo>BIND10</refmiscinfo>
+  </refmeta>
+
+  <refnamediv>
+    <refname>b10-certgen</refname>
+    <refpurpose>X509 Certificate generation tool for use with b10-cmdctl</refpurpose>
+  </refnamediv>
+
+  <docinfo>
+    <copyright>
+      <year>2012</year>
+      <holder>Internet Systems Consortium, Inc. ("ISC")</holder>
+    </copyright>
+  </docinfo>
+
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>b10-certgen</command>
+        <group choice="opt">
+          <arg choice="[OPTION]..."><option>-</option></arg>
+        </group>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>DESCRIPTION</title>
+    <para>The <command>b10-certgen</command> tool validates, creates, or
+      updates a self-signed X509 certificate for use in b10-cmdctl.
+    </para>
+
+    <para>
+      The connection between <command>bindctl</command> and
+      <command>b10-cmdctl</command> is done over HTTPS, and therefore
+      <command>b10-cmdctl</command> needs a certificate. Since these
+      certificates have expiry dates, they also need to be regenerated at
+      some point.
+
+      There are many tools to do so, but for ease of use, <command>
+      b10-certgen</command> can create a simple self-signed certificate.
+
+      By default, it will not create anything, but it will merely check an
+      existing certificate (if not specified, cmdctl-certfile.pem, in the
+      current working directory). And print whether it is valid, and
+      whether it would update if the option '-w' is given.
+
+      With that option, the certificate could then be replaced by a newly
+      created one. If the certificate is still valid, it would still not
+      be overwritten (however, if it is found to be invalid, for example
+      because it has expired, it would create a new one).
+
+      A new certificate is always created if the certificate file does
+      not exist, or if creation is forced (with the -f option).
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>ARGUMENTS</title>
+
+    <para>The arguments are as follows:</para>
+
+    <variablelist>
+
+      <varlistentry>
+        <term>
+          <option>-c <replaceable>file</replaceable></option>,
+          <option>--certfile=<replaceable>file</replaceable></option>
+        </term>
+        <listitem>
+          <para>
+            File to read the certificate from, or write the certificate to.
+            If <option>-w</option> and <option>-c</option> are used,
+            <option>-k</option> is mandatory as well.
+          </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
+          <option>-f</option>,
+          <option>--force</option>
+        </term>
+        <listitem>
+          <para>
+            Force updating of certificate when <option>-w</option> is used,
+            even if the existing certificate is still valid.
+          </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
+          <option>-h</option>,
+          <option>--help</option>
+        </term>
+        <listitem>
+          <para>
+            Print the command line arguments and exit.
+          </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
+          <option>-k <replaceable>file</replaceable></option>,
+          <option>--keyfile=<replaceable>file</replaceable></option>
+        </term>
+        <listitem>
+          <para>
+            File to write the private key to. This option is only valid when <option>-w</option> is used, and if this option is used, <option>-c</option> is mandatory as well.
+          </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
+          <option>-w</option>,
+          <option>--write</option>
+        </term>
+        <listitem>
+          <para>
+            Check the given certificate file. If it does not exist, a new
+            private key and certificate are created. If it does exist, the
+            certificate is validated. If it is not valid (for instance
+            because it has expired), it is overwritten with a newly created
+            certificate. If it is valid, nothing happens (use
+            <option>-f</option> to force an update in that case).
+          </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
+          <option>-q</option>,
+          <option>--quiet</option>
+        </term>
+        <listitem>
+          <para>
+            Don't print informational messages (only command-line errors are
+            printed). Useful in scripts when only the return code is needed.
+          </para>
+        </listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>SEE ALSO</title>
+    <para>
+      <citerefentry>
+        <refentrytitle>b10-cmdctl</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citetitle>BIND 10 Guide</citetitle>.
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>HISTORY</title>
+    <para>
+      The <command>b10-certgen</command> tool was first implemented
+      in November 2012 for the ISC BIND 10 project.
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>EXAMPLE</title>
+    <para>
+      To update an expired certificate in BIND 10 that has been installed to
+      /usr/local:
+      <screen>
+$> cd /usr/local/etc/bind10-devel/
+
+$> b10-certgen
+cmdctl-certfile.pem failed to verify: certificate has expired
+Running with -w would overwrite the certificate
+
+$> b10-certgen --write
+cmdctl-certfile.pem failed to verify: certificate has expired
+Creating key file cmdctl-keyfile.pem
+Creating certificate file cmdctl-certfile.pem
+
+$> b10-certgen --write
+cmdctl-certfile.pem is valid
+Not creating a new certificate (use -f to force)
+      </screen>
+    </para>
+  </refsect1>
+</refentry><!--
+ - Local variables:
+ - mode: sgml
+ - End:
+-->
diff --git a/src/bin/cmdctl/cmdctl-certfile.pem b/src/bin/cmdctl/cmdctl-certfile.pem
deleted file mode 100644
index 384a222..0000000
--- a/src/bin/cmdctl/cmdctl-certfile.pem
+++ /dev/null
@@ -1,21 +0,0 @@
------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-----
diff --git a/src/bin/cmdctl/cmdctl-keyfile.pem b/src/bin/cmdctl/cmdctl-keyfile.pem
deleted file mode 100644
index 8fff2dc..0000000
--- a/src/bin/cmdctl/cmdctl-keyfile.pem
+++ /dev/null
@@ -1,15 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIICXAIBAAKBgQDpICWxJGKMvUhLFPbf5n8ZWogqjYcQqqoHqHVRHYjyiey6FZdt
-ZkY2s1gYh0G0NXtimlIgic+vEcFe7vdmyKntW7DYDaqAj0KrED7RKAj8324jNbSJ
-HtLP4evvJep3vsoNtTvNuceQJ46vukxyxgg3DuC9kVqPuD8CZ1Rq4ATyiwIDAQAB
-AoGBAOJlOtV+DUq6Y2Ou91VXRiU8GzKgAQP5iWgoe84Ljbxkn4XThBxVD2j94Fbp
-u7AjpDCMx6cbzpoo9w6XqaGizAmAehIfTE3eFYs74N/FM09Wg2OSDyxMY0jgyECU
-A4ukjlPwcGDbmgbmlY3i+FVHp+zCgtZEsMC1IAosMac1BoX5AkEA/lrXWaVtH8bo
-mut3GBaXvubZMdaUr0BUd5a9q+tt4dQcKG1kFqgCNKhNhBIcpiMVcz+jGmOuopNA
-8dnUGqv3FQJBAOqiJ54ZvOTWNDpJIe02wIXRxRmc1xhHFCqYP23KxBVrAcTYB19J
-lesov/hEbnGLCbKS/naZJ1zrTImUPNRLqx8CQCzDtA7U7GWhTiKluioFH+O7IRKC
-X1yQh80cPHlbT9VkzSfYSLssCmdWD35k6aHbntTPqFbmoD+AhveJjKi9BxkCQDwX
-1c+/RcrSNcQr0N2hZUOgyztZGRnlsnuKTMyA3yGhK23P6mt0PEpjQG+Ej0jTVGOB
-FF0pspQwy4R9C+tPif8CQH36NNlXBfVNmT7kDtyLmaE6pID0vY9duX56BJbU1R0x
-SQ8/LcfJagk6gvp08OyYCPA+WZ7u/bas9R/nMTCLivc=
------END RSA PRIVATE KEY-----
diff --git a/src/bin/cmdctl/tests/Makefile.am b/src/bin/cmdctl/tests/Makefile.am
index b5b65f6..6d8f282 100644
--- a/src/bin/cmdctl/tests/Makefile.am
+++ b/src/bin/cmdctl/tests/Makefile.am
@@ -1,6 +1,9 @@
 PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
-PYTESTS = cmdctl_test.py
+PYTESTS = cmdctl_test.py b10-certgen_test.py
 EXTRA_DIST = $(PYTESTS)
+EXTRA_DIST += testdata/expired-certfile.pem
+EXTRA_DIST += testdata/mangled-certfile.pem
+EXTRA_DIST += testdata/noca-certfile.pem
 
 # If necessary (rare cases), explicitly specify paths to dynamic libraries
 # required by loadable python modules.
@@ -9,10 +12,12 @@ if SET_ENV_LIBRARY_PATH
 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
diff --git a/src/bin/cmdctl/tests/b10-certgen_test.py b/src/bin/cmdctl/tests/b10-certgen_test.py
new file mode 100644
index 0000000..d54efa3
--- /dev/null
+++ b/src/bin/cmdctl/tests/b10-certgen_test.py
@@ -0,0 +1,254 @@
+# 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
+import stat
+
+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)
+
+class FilePermissionContext:
+    """
+    Simple Context Manager that temporarily modifies file permissions for
+    a given file
+    """
+    def __init__(self, f, unset_flags = [], set_flags = []):
+        """
+        Initialize file permission context.
+        See the stat module for possible flags to set or unset.
+        The flags are changed when the context is entered (i.e.
+        you can create the context first without any change)
+        The flags are changed back when the context is left.
+
+        Parameters:
+        f: string, file to change permissions for
+        unset_flags: list of flags to unset
+        set_flags: list of flags to set
+        """
+        self.file = f
+        self.orig_mode = os.stat(f).st_mode
+        new_mode = self.orig_mode
+        for flag in unset_flags:
+            new_mode = new_mode & ~flag
+        for flag in set_flags:
+            new_mode = new_mode | flag
+        self.new_mode = new_mode
+
+    def __enter__(self):
+        os.chmod(self.file, self.new_mode)
+
+    def __exit__(self, type, value, traceback):
+        os.chmod(self.file, self.orig_mode)
+
+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
+        """
+        if ('CMDCTL_SRC_PATH' in os.environ):
+            path = os.environ['CMDCTL_SRC_PATH'] + "/tests/testdata/"
+        else:
+            path = "testdata/"
+        self.validate_certificate(10, path + 'expired-certfile.pem')
+        self.validate_certificate(100, path + 'mangled-certfile.pem')
+        self.validate_certificate(17, path + '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'])
+
+    def test_permissions(self):
+        """
+        Test some combinations of correct and bad permissions.
+        """
+        keyfile = 'mod-keyfile.pem'
+        certfile = 'mod-certfile.pem'
+        command = [ self.TOOL, '-q', '-w', '-c', certfile, '-k', keyfile ]
+        # Delete them at the end
+        with FileDeleterContext([keyfile, certfile]):
+            # Create the two files first
+            self.run_check(0, '', '', command)
+            self.validate_certificate(0, certfile)
+
+            # Make the key file unwritable
+            with FilePermissionContext(keyfile, unset_flags = [stat.S_IWUSR]):
+                self.run_check(106, '', '', command)
+                # Should have no effect on validation
+                self.validate_certificate(0, certfile)
+
+            # Make the cert file unwritable
+            with FilePermissionContext(certfile, unset_flags = [stat.S_IWUSR]):
+                self.run_check(106, '', '', command)
+                # Should have no effect on validation
+                self.validate_certificate(0, certfile)
+
+            # Make the key file unreadable (this should not matter)
+            with FilePermissionContext(keyfile, unset_flags = [stat.S_IRUSR]):
+                self.run_check(0, '', '', command)
+
+                # unreadable key file should also not have any effect on
+                # validation
+                self.validate_certificate(0, certfile)
+
+            # Make the cert file unreadable (this should matter)
+            with FilePermissionContext(certfile, unset_flags = [stat.S_IRUSR]):
+                self.run_check(106, '', '', command)
+
+                # Unreadable cert file should also fail validation
+                self.validate_certificate(106, certfile)
+
+        # Not directly a permission problem, but trying to check or create
+        # in a nonexistent directory returns different error codes
+        self.validate_certificate(105, 'fakedir/cert')
+        self.run_check(103, '', '', [ self.TOOL, '-q', '-w', '-c',
+                                      'fakedir/cert', '-k', 'fakedir/key' ])
+
+if __name__== '__main__':
+    unittest.main()
+
diff --git a/src/bin/cmdctl/tests/testdata/expired-certfile.pem b/src/bin/cmdctl/tests/testdata/expired-certfile.pem
new file mode 100644
index 0000000..384a222
--- /dev/null
+++ b/src/bin/cmdctl/tests/testdata/expired-certfile.pem
@@ -0,0 +1,21 @@
+-----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-----
diff --git a/src/bin/cmdctl/tests/testdata/mangled-certfile.pem b/src/bin/cmdctl/tests/testdata/mangled-certfile.pem
new file mode 100644
index 0000000..7c47fda
--- /dev/null
+++ b/src/bin/cmdctl/tests/testdata/mangled-certfile.pem
@@ -0,0 +1,21 @@
+-----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-----
diff --git a/src/bin/cmdctl/tests/testdata/noca-certfile.pem b/src/bin/cmdctl/tests/testdata/noca-certfile.pem
new file mode 100644
index 0000000..5636869
--- /dev/null
+++ b/src/bin/cmdctl/tests/testdata/noca-certfile.pem
@@ -0,0 +1,19 @@
+-----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-----



More information about the bind10-changes mailing list