From 3fe98e57f6b69024d1fef3e5140a788e3ccd99ca Mon Sep 17 00:00:00 2001 From: Tomek Mrugalski Date: Tue, 5 Aug 2014 07:56:06 +0200 Subject: [PATCH] [3417] Obsolete directory src/bin/cmdctl removed. --- src/bin/cmdctl/openssl-certgen.cc | 416 ------------------- src/bin/cmdctl/tests/openssl-certgen_test.py | 253 ----------- 2 files changed, 669 deletions(-) delete mode 100644 src/bin/cmdctl/openssl-certgen.cc delete mode 100644 src/bin/cmdctl/tests/openssl-certgen_test.py diff --git a/src/bin/cmdctl/openssl-certgen.cc b/src/bin/cmdctl/openssl-certgen.cc deleted file mode 100644 index 07e8a3e2fa..0000000000 --- a/src/bin/cmdctl/openssl-certgen.cc +++ /dev/null @@ -1,416 +0,0 @@ -// 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 -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -// For cleaner 'does not exist or is not readable' output than -// openssl provides -#include -#include - -// 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); - } - X509_STORE* store = X509_STORE_new(); - X509_STORE_CTX* csc = X509_STORE_CTX_new(); - X509_STORE_CTX_init(csc, store, cert, NULL); - STACK_OF(X509)* trusted = sk_X509_new_null(); - sk_X509_push(trusted, X509_dup(cert)); - X509_STORE_CTX_trusted_stack(csc, trusted); - const int result = X509_verify_cert(csc); - const int cerror = X509_STORE_CTX_get_error(csc); - X509_STORE_CTX_free(csc); - X509_free(cert); - - if (result > 0) { - print(certfile + " is valid"); - return (X509_V_OK); - } else { - print(certfile + " failed to verify: " + - X509_verify_cert_error_string(cerror)); - return (cerror); - } - } - - /// \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: " + std::strerror(errno)); - create_cert = false; - } - if (!fileIsWritable(certfile)) { - print(certfile + " not writable: " + std::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: " + std::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) != X509_V_OK) { - return (createKeyAndCertificate(keyfile, certfile)); - } else { - print("Not creating a new certificate (use -f to force)"); - } - } else { - if (!fileExists(certfile)) { - print(certfile + ": " + std::strerror(errno)); - return (NO_SUCH_FILE); - } - if (!fileIsReadable(certfile)) { - print(certfile + " not readable: " + std::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[]) -{ - // ERR_load_crypto_strings(); - - // 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; - - // It would appear some environments insist on - // char* here (Sunstudio on Solaris), so we const_cast - // them to get rid of compiler warnings. - const struct option long_options[] = { - { const_cast("certfile"), required_argument, NULL, 'c' }, - { const_cast("force"), no_argument, NULL, 'f' }, - { const_cast("help"), no_argument, NULL, 'h' }, - { const_cast("keyfile"), required_argument, NULL, 'k' }, - { const_cast("write"), no_argument, NULL, 'w' }, - { const_cast("quiet"), no_argument, NULL, 'q' }, - { NULL, 0, NULL, 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/tests/openssl-certgen_test.py b/src/bin/cmdctl/tests/openssl-certgen_test.py deleted file mode 100644 index 40e01af7c2..0000000000 --- a/src/bin/cmdctl/tests/openssl-certgen_test.py +++ /dev/null @@ -1,253 +0,0 @@ -# Copyright (C) 2014 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 - ), 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 - """ - path = os.environ['CMDCTL_SRC_PATH'] + '/tests/testdata/' - self.validate_certificate(10, path + 'expired-certfile.pem') - self.validate_certificate(100, path + 'mangled-certfile.pem') - self.validate_certificate(20, 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']) - - @unittest.skipIf(os.getuid() == 0, - 'test cannot be run as root user') - 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() -