Commit 4b4110dd authored by Francis Dupont's avatar Francis Dupont

applied #2406: OpenSSL alternate crypto backend

parent 314c47ea
794. [func] fdupont
cryptolink: add OpenSSL crypto backend as an alternative to Botan
by specifying --with-openssl[=PATH] on the "configure" command
line. Add hash support to the cryptolink API and use it in DHCP
DDNS, removing the Botan dependency.
(Trac #2406, git xxx)
793. [func] tmark
DHCP-DDNS: Implemented dynamic reconfiguration of the server,
triggered when the SIGHUP signal is received by the server's
......
......@@ -7,6 +7,7 @@ USE_LCOV=@USE_LCOV@
LCOV=@LCOV@
GENHTML=@GENHTML@
DISTCHECK_GTEST_CONFIGURE_FLAG=@DISTCHECK_GTEST_CONFIGURE_FLAG@
DISTCHECK_CRYPTO_CONFIGURE_FLAG=@DISTCHECK_CRYPTO_CONFIGURE_FLAG@
DISTCLEANFILES = config.report
......@@ -16,6 +17,9 @@ DISTCHECK_CONFIGURE_FLAGS = --disable-install-configurations
# Use same --with-gtest flag if set
DISTCHECK_CONFIGURE_FLAGS += $(DISTCHECK_GTEST_CONFIGURE_FLAG)
# Keep the crypto backend config
DISTCHECK_CONFIGURE_FLAGS += $(DISTCHECK_CRYPTO_CONFIGURE_FLAG)
dist_doc_DATA = AUTHORS COPYING ChangeLog README
.PHONY: check-valgrind check-valgrind-suppress
......@@ -73,12 +77,17 @@ report-cpp-coverage:
c++/4.4\*/ext/\* \
c++/4.4\*/\*-\*/bits/\* \
boost/\* \
if HAVE_BOTAN
botan/\* \
endif
ext/asio/\* \
ext/coroutine/\* \
gtest/\* \
log4cplus/\* \
include/\* \
log4cplus/\* \
if HAVE_OPENSSL
openssl/\* \
endif
tests/\* \
unittests/\* \
\*_unittests.cc \
......
......@@ -646,6 +646,13 @@ AC_DEFUN([ACX_CHECK_PROG_NONCACHE], [
IFS="$IFS_SAVED"
])
# Avoid checking Botan if OpenSSL is wanted
AC_ARG_WITH([openssl],
[AS_HELP_STRING([--with-openssl[[=PATH]]], [Enables OpenSSL,
location can be specified optionally])],
[use_openssl="$withval"],
[use_openssl="auto"])
# Botan helper test function
# Tries to compile a botan program, given the output of the given
# config tool
......@@ -661,12 +668,12 @@ AC_DEFUN([ACX_TRY_BOTAN_TOOL], [
AC_MSG_CHECKING([usability of ${TOOL} ${TOOL_ARG}])
if test "$BOTAN_TOOL" != "" ; then
if test -x ${BOTAN_TOOL}; then
BOTAN_LIBS=`$BOTAN_TOOL $TOOL_ARG --libs`
CRYPTO_LIBS=`$BOTAN_TOOL $TOOL_ARG --libs`
LIBS_SAVED=${LIBS}
LIBS="$LIBS $BOTAN_LIBS"
BOTAN_INCLUDES=`$BOTAN_TOOL $TOOL_ARG --cflags`
LIBS="$LIBS $CRYPTO_LIBS"
CRYPTO_INCLUDES=`$BOTAN_TOOL $TOOL_ARG --cflags`
CPPFLAGS_SAVED=${CPPFLAGS}
CPPFLAGS="$BOTAN_INCLUDES $CPPFLAGS"
CPPFLAGS="$CRYPTO_INCLUDES $CPPFLAGS"
#AC_MSG_RESULT([found])
AC_LINK_IFELSE(
[AC_LANG_PROGRAM([#include <botan/botan.h>
......@@ -711,14 +718,18 @@ AC_DEFUN([ACX_TRY_BOTAN_TOOL], [
# against botan should neither -config scripts nor pkgconfig data exist).
#
botan_config="yes"
if test "${use_openssl}" != "auto" -a "${use_openssl}" != "no" ; then
botan_config="no"
fi
AC_ARG_WITH([botan-config],
AC_HELP_STRING([--with-botan-config=PATH],
[specify the path to the botan-config script]),
[botan_config="$withval"])
if test "${botan_config}" = "no" ; then
AC_MSG_ERROR([Need botan for libcryptolink])
fi
if test "${botan_config}" != "yes" ; then
if test "${use_openssl}" = "no" ; then
AC_MSG_ERROR([Need Botan or OpenSSL for libcryptolink])
fi
elif test "${botan_config}" != "yes" ; then
if test -x "${botan_config}" ; then
if test -d "${botan_config}" ; then
AC_MSG_ERROR([${botan_config} is a directory])
......@@ -763,91 +774,101 @@ fi
if test "x${BOTAN_CONFIG}" != "x"
then
BOTAN_LIBS=`${BOTAN_CONFIG} --libs`
BOTAN_INCLUDES=`${BOTAN_CONFIG} --cflags`
CRYPTO_LIBS=`${BOTAN_CONFIG} --libs`
CRYPTO_INCLUDES=`${BOTAN_CONFIG} --cflags`
# We expect botan-config --libs to contain -L<path_to_libbotan>, but
# this is not always the case. As a heuristics workaround we add
# -L`botan-config --prefix/lib` in this case (if not present already).
# Same for BOTAN_INCLUDES (but using include instead of lib) below.
# Same for CRYPTO_INCLUDES (but using include instead of lib) below.
if [ ${BOTAN_CONFIG} --prefix >/dev/null 2>&1 ] ; then
echo ${BOTAN_LIBS} | grep -- -L > /dev/null || \
BOTAN_LIBS="-L`${BOTAN_CONFIG} --prefix`/lib ${BOTAN_LIBS}"
echo ${BOTAN_INCLUDES} | grep -- -I > /dev/null || \
BOTAN_INCLUDES="-I`${BOTAN_CONFIG} --prefix`/include ${BOTAN_INCLUDES}"
echo ${CRYPTO_LIBS} | grep -- -L > /dev/null || \
CRYPTO_LIBS="-L`${BOTAN_CONFIG} --prefix`/lib ${CRYPTO_LIBS}"
echo ${CRYPTO_INCLUDES} | grep -- -I > /dev/null || \
CRYPTO_INCLUDES="-I`${BOTAN_CONFIG} --prefix`/include ${CRYPTO_INCLUDES}"
fi
fi
dnl Determine the Botan version
AC_MSG_CHECKING([Botan version])
cat > conftest.cpp << EOF
if test "x${CRYPTO_LIBS}" != "x"
then
dnl Determine the Botan version
AC_MSG_CHECKING([Botan version])
cat > conftest.cpp << EOF
#include <botan/version.h>
AUTOCONF_BOTAN_VERSION=BOTAN_VERSION_MAJOR . BOTAN_VERSION_MINOR . BOTAN_VERSION_PATCH
EOF
BOTAN_VERSION=`$CPP $CPPFLAGS $BOTAN_INCLUDES conftest.cpp | grep '^AUTOCONF_BOTAN_VERSION=' | $SED -e 's/^AUTOCONF_BOTAN_VERSION=//' -e 's/[[ ]]//g' -e 's/"//g' 2> /dev/null`
if test -z "$BOTAN_VERSION"; then
BOTAN_VERSION="unknown"
fi
$RM -f conftest.cpp
AC_MSG_RESULT([$BOTAN_VERSION])
# botan-config script (and the way we call pkg-config) returns -L and -l
# as one string, but we need them in separate values
BOTAN_LDFLAGS=
BOTAN_NEWLIBS=
for flag in ${BOTAN_LIBS}; do
BOTAN_LDFLAGS="${BOTAN_LDFLAGS} `echo $flag | ${SED} -ne '/^\(\-L\)/p'`"
BOTAN_LIBS="${BOTAN_LIBS} `echo $flag | ${SED} -ne '/^\(\-l\)/p'`"
done
# See python_rpath for some info on why we do this
if test "x$ISC_RPATH_FLAG" != "x"; then
BOTAN_RPATH=
for flag in ${BOTAN_LIBS}; do
BOTAN_RPATH="${BOTAN_RPATH} `echo $flag | ${SED} -ne "s/^\(\-L\)/${ISC_RPATH_FLAG}/p"`"
CRYPTO_VERSION=`$CPP $CPPFLAGS $CRYPTO_INCLUDES conftest.cpp | grep '^AUTOCONF_BOTAN_VERSION=' | $SED -e 's/^AUTOCONF_BOTAN_VERSION=//' -e 's/[[ ]]//g' -e 's/"//g' 2> /dev/null`
if test -z "$CRYPTO_VERSION"; then
CRYPTO_VERSION="unknown"
fi
$RM -f conftest.cpp
AC_MSG_RESULT([$CRYPTO_VERSION])
# botan-config script (and the way we call pkg-config) returns -L and -l
# as one string, but we need them in separate values
CRYPTO_LDFLAGS=
for flag in ${CRYPTO_LIBS}; do
CRYPTO_LDFLAGS="${CRYPTO_LDFLAGS} `echo $flag | ${SED} -ne '/^\(\-L\)/p'`"
CRYPTO_LIBS="${CRYPTO_LIBS} `echo $flag | ${SED} -ne '/^\(\-l\)/p'`"
done
AC_SUBST(BOTAN_RPATH)
# According to the libtool manual, it should be sufficient if we
# specify the "-R libdir" in our wrapper library of botan (no other
# programs will need libbotan directly); "libdir" should be added to
# the program's binary image. But we've seen in our build environments
# that (some versions of?) libtool doesn't propagate -R as documented,
# and it caused a linker error at run time. To work around this, we
# also add the rpath to the global LDFLAGS.
LDFLAGS="$BOTAN_RPATH $LDFLAGS"
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.
CPPFLAGS_SAVED=$CPPFLAGS
CPPFLAGS="$BOTAN_INCLUDES $CPPFLAGS"
LIBS_SAVED="$LIBS"
LIBS="$LIBS $BOTAN_LIBS"
# ac_header_preproc is an autoconf symbol (undocumented but stable) that
# is set if the pre-processor phase passes. Thus by adding a custom
# failure handler we can detect the difference between a header not existing
# (or not even passing the pre-processor phase) and a header file resulting
# in compilation failures.
AC_CHECK_HEADERS([botan/botan.h],,[
# See crypto_rpath for some info on why we do this
if test "x$ISC_RPATH_FLAG" != "x"; then
CRYPTO_RPATH=
for flag in ${CRYPTO_LIBS}; do
CRYPTO_RPATH="${CRYPTO_RPATH} `echo $flag | ${SED} -ne "s/^\(\-L\)/${ISC_RPATH_FLAG}/p"`"
done
# According to the libtool manual, it should be sufficient if we
# specify the "-R libdir" in our wrapper library of botan (no other
# programs will need libbotan directly); "libdir" should be added to
# the program's binary image. But we've seen in our build environments
# that (some versions of?) libtool doesn't propagate -R as documented,
# and it caused a linker error at run time. To work around this, we
# also add the rpath to the global LDFLAGS.
LDFLAGS="$CRYPTO_RPATH $LDFLAGS"
fi
# 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="$CRYPTO_INCLUDES $CPPFLAGS"
LIBS_SAVED="$LIBS"
LIBS="$LIBS $CRYPTO_LIBS"
# ac_header_preproc is an autoconf symbol (undocumented but stable) that
# is set if the pre-processor phase passes. Thus by adding a custom
# failure handler we can detect the difference between a header not existing
# (or not even passing the pre-processor phase) and a header file resulting
# in compilation failures.
AC_CHECK_HEADERS([botan/botan.h],,[
CRYPTO_INCLUDES=""
CRYPTO_LIBS=""
CRYPTO_LDFLAGS=""
CRYPTO_RPATH=""
if test "x$ac_header_preproc" = "xyes"; then
AC_MSG_ERROR([
AC_MSG_RESULT([
botan/botan.h was found but is unusable. The most common cause of this problem
is attempting to use an updated C++ compiler with older C++ libraries, such as
the version of Botan that comes with your distribution. If you have updated
your C++ compiler we highly recommend that you use support libraries such as
Boost and Botan that were compiled with the same compiler version.])
else
AC_MSG_ERROR([Missing required header files.])
AC_MSG_RESULT([Missing required header files.])
fi]
)
AC_LINK_IFELSE(
)
CPPFLAGS=$CPPFLAGS_SAVED
LIBS=$LIBS_SAVED
fi
if test "x${CRYPTO_LIBS}" != "x"
then
CPPFLAGS_SAVED=$CPPFLAGS
CPPFLAGS="$CRYPTO_INCLUDES $CPPFLAGS"
LIBS_SAVED="$LIBS"
LIBS="$LIBS $CRYPTO_LIBS"
AC_LINK_IFELSE(
[AC_LANG_PROGRAM([#include <botan/botan.h>
#include <botan/hash.h>
],
......@@ -857,14 +878,116 @@ AC_LINK_IFELSE(
])],
[AC_MSG_RESULT([checking for Botan library... yes])],
[AC_MSG_RESULT([checking for Botan library... no])
AC_MSG_ERROR([Needs Botan library 1.8 or higher. On some systems,
CRYPTO_INCLUDES=""
CRYPTO_LIBS=""
CRYPTO_LDFLAGS=""
CRYPTO_RPATH=""
AC_MSG_RESULT([Needs Botan library 1.8 or higher. On some systems,
the botan package has a few missing dependencies (libbz2 and
libgmp), if libbotan has been installed and you see this error,
libgmp), if libbotan has been installed and you see this message,
try upgrading to a higher version of botan or installing libbz2
and libgmp.])]
)
CPPFLAGS=$CPPFLAGS_SAVED
LIBS=$LIBS_SAVED
)
CPPFLAGS=$CPPFLAGS_SAVED
LIBS=$LIBS_SAVED
fi
if test "x${CRYPTO_LIBS}" != "x"
then
CRYPTO_NAME="Botan"
DISABLED_CRYPTO="OpenSSL"
CRYPTO_PACKAGE="botan-1.8"
CRYPTO_CFLAGS=""
DISTCHECK_CRYPTO_CONFIGURE_FLAG="--with-botan=$botan_config"
AC_DEFINE_UNQUOTED([WITH_BOTAN], [], [Compile with Botan crypto])
else
CRYPTO_NAME="OpenSSL"
DISABLED_CRYPTO="Botan"
CRYPTO_PACKAGE="openssl-1.0.0"
AC_DEFINE_UNQUOTED([WITH_OPENSSL], [], [Compile with OpenSSL crypto])
AC_MSG_CHECKING(for OpenSSL library)
# from bind9
if test "${use_openssl}" = "auto" ; then
use_openssl="yes"
fi
if test "${use_openssl}" = "yes" ; then
for d in /usr /usr/local /usr/local/ssl /usr/pkg /usr/sfw; do
if test -f $d/include/openssl/opensslv.h; then
use_openssl=$d; break
fi
done
fi
if test "${use_openssl}" = "yes" ; then
AC_MSG_ERROR([OpenSSL auto detection failed])
fi
if ! test -f "${use_openssl}"/include/openssl/opensslv.h ; then
AC_MSG_ERROR([OpenSSL not found at ${use_openssl}])
fi
AC_MSG_RESULT(yes)
if test "${use_openssl}" = "/usr" ; then
CRYPTO_INCLUDES=""
CRYPTO_LIBS="-lcrypto"
DISTCHECK_CRYPTO_CONFIGURE_FLAG="--with-openssl"
case "$host" in
*-apple-darwin*)
# Starting with OSX 10.7 (Lion) OpenSSL is deprecated
CRYPTO_CFLAGS="-Wno-deprecated-declarations"
;;
*)
CRYPTO_CFLAGS=""
;;
esac
else
CRYPTO_CFLAGS=""
CRYPTO_INCLUDES="-I${use_openssl}/include"
DISTCHECK_CRYPTO_CONFIGURE_FLAG="--with-openssl=${use_openssl}"
case $host in
*-solaris*)
CRYPTO_LIBS="-L${use_openssl}/lib -R${use_openssl}/lib -lcrypto"
;;
*-hp-hpux*)
CRYPTO_LIBS="-L${use_openssl}/lib -Wl,+b: -lcrypto"
;;
*-apple-darwin*)
if test -f "${use_openssl}/lib/libcrypto.dylib" ; then
CRYPTO_LIBS="-L${use_openssl}/lib -lcrypto"
else
CRYPTO_LIBS="${use_openssl}/lib/libcrypto.a"
fi
;;
*)
CRYPTO_LIBS="-L${use_openssl}/lib -lcrypto"
;;
esac
fi
dnl Determine the OpenSSL version
AC_MSG_CHECKING([OpenSSL version])
cat > conftest.cpp << EOF
#include <openssl/opensslv.h>
AUTOCONF_OPENSSL_VERSION=OPENSSL_VERSION_TEXT
EOF
CRYPTO_VERSION=`$CPP $CPPFLAGS $CRYPTO_INCLUDES conftest.cpp | grep '^AUTOCONF_OPENSSL_VERSION=' | $SED -e 's/^AUTOCONF_OPENSSL_VERSION=//' -e 's/"//g' 2> /dev/null`
if test -z "$CRYPTO_VERSION" ; then
CRYPTO_VERSION="unknown"
fi
$RM -f conftest.cpp
AC_MSG_RESULT([$CRYPTO_VERSION])
#CRYPTO_LDFLAGS="-ldl"
CRYPTO_LDFLAGS=""
CRYPTO_RPATH=""
fi
AM_CONDITIONAL(HAVE_BOTAN, test "$CRYPTO_NAME" = "Botan")
AM_CONDITIONAL(HAVE_OPENSSL, test "$CRYPTO_NAME" = "OpenSSL")
AC_SUBST(CRYPTO_INCLUDES)
AC_SUBST(CRYPTO_CFLAGS)
AC_SUBST(CRYPTO_LIBS)
AC_SUBST(CRYPTO_LDFLAGS)
AC_SUBST(CRYPTO_PACKAGE)
AC_SUBST(CRYPTO_RPATH)
AC_SUBST(DISTCHECK_CRYPTO_CONFIGURE_FLAG)
# Check for MySql. The path to the mysql_config program is given with
# the --with-mysql-config (default to /usr/bin/mysql-config). By default,
......@@ -1653,11 +1776,14 @@ Boost:
BOOST_VERSION: ${BOOST_VERSION}
BOOST_INCLUDES: ${BOOST_INCLUDES}
Botan:
BOTAN_VERSION: ${BOTAN_VERSION}
BOTAN_INCLUDES: ${BOTAN_INCLUDES}
BOTAN_LDFLAGS: ${BOTAN_LDFLAGS}
BOTAN_LIBS: ${BOTAN_LIBS}
${CRYPTO_NAME}:
CRYPTO_VERSION: ${CRYPTO_VERSION}
CRYPTO_CFLAGS: ${CRYPTO_CFLAGS}
CRYPTO_INCLUDES: ${CRYPTO_INCLUDES}
CRYPTO_LDFLAGS: ${CRYPTO_LDFLAGS}
CRYPTO_LIBS: ${CRYPTO_LIBS}
${DISABLED_CRYPTO}: no
Log4cplus:
LOG4CPLUS_VERSION: ${LOG4CPLUS_VERSION}
......
......@@ -6,6 +6,6 @@ includedir=@includedir@
Name: dns++
Description: BIND 10 DNS library
Version: @PACKAGE_VERSION@
Requires: botan-1.8
Requires: @CRYPTO_PACKAGE@
Cflags: -I${includedir}/@PACKAGE_NAME@
Libs: -L${libdir} -lb10-dns++ -lb10-cryptolink -lb10-util -lb10-exceptions -lm
......@@ -138,9 +138,13 @@
</para>
<para>
Kea uses the Botan crypto library for C++
(<ulink url="http://botan.randombit.net/"/>).
It requires at least Botan version 1.8.
Kea supports two crypto libraries: Botan and OpenSSL. Only one
of them is required during compilation. Kea uses the Botan crypto
library for C++ (<ulink url="http://botan.randombit.net/"/>).
It requires at least Botan version 1.8. As an alternative to Botan,
Kea can use the OpenSSL crypto library
(<ulink url="http://www.openssl.org/"/>).
It requires a version with SHA-2 support.
</para>
<para>
......@@ -546,7 +550,7 @@ $ <userinput>./configure</userinput></screen>
<para>
To build Kea, also install the Botan (at least version
1.8) and the log4cplus (at least version 1.0.3)
1.8) or OpenSSL, and the log4cplus (at least version 1.0.3)
development include headers.
</para>
......@@ -696,6 +700,17 @@ as a dependency earlier -->
</listitem>
</varlistentry>
<varlistentry>
<term>--with-openssl</term>
<listitem>
<simpara>Replace Botan by OpenSSL for the crypto library.
The default is to try to find a working Botan then
OpenSSL only if not found.
<!-- missing -with-botan-config -->
</simpara>
</listitem>
</varlistentry>
<varlistentry>
<term>--without-werror</term>
<listitem>
......
......@@ -52,9 +52,16 @@ b10-cmdctl: cmdctl.py $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.py $(CERTFILE
$(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)
if HAVE_BOTAN
b10_certgen_SOURCES = botan-certgen.cc
EXTRA_DIST += openssl-certgen.cc
endif
if HAVE_OPENSSL
b10_certgen_SOURCES = openssl-certgen.cc
EXTRA_DIST += botan-certgen.cc
endif
b10_certgen_CXXFLAGS = $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES)
b10_certgen_LDFLAGS = $(CRYPTO_LIBS)
# Generate the initial certificates immediately
cmdctl-keyfile.pem: b10-certgen
......
// Copyright (C) 2014 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 <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <cstring>
#include <iostream>
#include <fstream>
#include <memory>
#include <getopt.h>
// For cleaner 'does not exist or is not readable' output than
// openssl 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 OpenSSL. 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 certificate 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);
}
class CertificateTool {
public:
CertificateTool(bool quiet) : quiet_(quiet) {}
int
createKeyAndCertificate(const std::string& key_file_name,
const std::string& cert_file_name) {
// Create and store a private key
print("Creating key file " + key_file_name);
RSA* rsa = RSA_generate_key(2048, 65537UL, NULL, NULL);
std::ofstream key_file(key_file_name.c_str());
if (!key_file.good()) {
print(std::string("Error writing to ") + key_file_name +
": " + std::strerror(errno));
return (WRITE_ERROR);
}
BIO* key_mem = BIO_new(BIO_s_mem());
PEM_write_bio_RSAPrivateKey(key_mem, rsa, NULL, NULL, 0, NULL, NULL);
char* p;
long len = BIO_get_mem_data(key_mem, &p);
key_file.write(p, (unsigned) len);
BIO_free(key_mem);
if (!key_file.good()) {
print(std::string("Error writing to ") + key_file_name +
": " + std::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 = X509_new();
X509_set_version(cert, 2);
BIGNUM* serial = BN_new();
BN_pseudo_rand(serial, 64, 0, 0);
BN_to_ASN1_INTEGER(serial, X509_get_serialNumber(cert));
BN_free(serial);
X509_NAME* name = X509_get_subject_name(cert);
std::string cn("localhost");
X509_NAME_add_entry_by_NID(name, NID_commonName, MBSTRING_ASC,
(unsigned char*) cn.c_str(), cn.size(),
-1, 0);
std::string org("UNKNOWN");
X509_NAME_add_entry_by_NID(name, NID_organizationName, MBSTRING_ASC,
(unsigned char*) org.c_str(), org.size(),
-1, 0);
std::string cc("XX");
X509_NAME_add_entry_by_NID(name, NID_countryName, MBSTRING_ASC,
(unsigned char*) cc.c_str(), cc.size(),
-1, 0);
X509_set_issuer_name(cert, name);
X509_gmtime_adj(X509_get_notBefore(cert), 0);
X509_gmtime_adj(X509_get_notAfter(cert), 60*60*24*365L);
EVP_PKEY* pkey = EVP_PKEY_new();
EVP_PKEY_assign_RSA(pkey, rsa);
X509_set_pubkey(cert, pkey);
X509V3_CTX ec;
X509V3_set_ctx_nodb(&ec);
X509V3_set_ctx(&ec, cert, cert, NULL, NULL, 0);
const std::string bc_val("critical,CA:TRUE,pathlen:1");
X509_EXTENSION* bc = X509V3_EXT_conf_nid(NULL, &ec,
NID_basic_constraints,
(char*) bc_val.c_str());
X509_add_ext(cert, bc, -1);
X509_EXTENSION_free(bc);
const std::string ku_val=("critical,keyCertSign,cRLSign");
X509_EXTENSION* ku = X509V3_EXT_conf_nid(NULL, &ec,
NID_key_usage,
(char*) ku_val.c_str());
X509_add_ext(cert, ku, -1);
X509_EXTENSION_free(ku);
const std::string ski_val("hash");
X509_EXTENSION* ski = X509V3_EXT_conf_nid(NULL, &ec,
NID_subject_key_identifier,
(char*) ski_val.c_str());
X509_add_ext(cert, ski, -1);
X509_EXTENSION_free(ski);
X509_sign(cert, pkey, EVP_sha256());
print("Creating certificate file " + cert_file_name);
std::ofstream cert_file(cert_file_name.c_str());
if (!cert_file.good()) {
print(std::string("Error writing to ") + cert_file_name +
": " + std::strerror(errno));
return (WRITE_ERROR);
}
BIO* cert_mem = BIO_new(BIO_s_mem());
PEM_write_bio_X509(cert_mem, cert);
p = NULL;
len = BIO_get_mem_data(cert_mem, &p);
cert_file.write(p, (unsigned) len);
BIO_free(cert_mem);
if (!cert_file.good()) {
print(std::string("Error writing to ") + cert_file_name +
": " + std::strerror(errno));
return (WRITE_ERROR);
}
cert_file.close();
X509_free(cert);
RSA_free(rsa);
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 certificate itself.
BIO* in = BIO_new_file(certfile.c_str(), "r");
if (in == NULL) {
print("failed to read " + certfile);
return (READ_ERROR);
}
X509* cert = PEM_read_bio_X509(in, NULL, NULL, NULL);
BIO_free(in);
if (cert == NULL) {
print("failed to decode " + certfile);
return (DECODING_ERROR);
}