Commit 43b0ea42 authored by Jelte Jansen's avatar Jelte Jansen
Browse files

[master] Merge branch 'trac1044'

parents 8b10f38a 69e97349
......@@ -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.
......
......@@ -4,3 +4,5 @@
/cmdctl.spec.pre
/run_b10-cmdctl.sh
/b10-cmdctl.8
/cmdctl-keyfile.pem
/cmdctl-certfile.pem
......@@ -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@/ ; \
......
// 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));
}
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
[<!ENTITY mdash "&#8212;">]>
<!--
- 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>