diff --git a/lib/cli/CMakeLists.txt b/lib/cli/CMakeLists.txt index 38756b5ce..75e6ccd4d 100644 --- a/lib/cli/CMakeLists.txt +++ b/lib/cli/CMakeLists.txt @@ -29,6 +29,7 @@ set(cli_SOURCES pkisavecertcommand.cpp pkisavecertcommand.hpp pkisigncsrcommand.cpp pkisigncsrcommand.hpp pkiticketcommand.cpp pkiticketcommand.hpp + pkiverifycommand.cpp pkiverifycommand.hpp variablegetcommand.cpp variablegetcommand.hpp variablelistcommand.cpp variablelistcommand.hpp variableutility.cpp variableutility.hpp diff --git a/lib/cli/pkiverifycommand.cpp b/lib/cli/pkiverifycommand.cpp new file mode 100644 index 000000000..256b21ea8 --- /dev/null +++ b/lib/cli/pkiverifycommand.cpp @@ -0,0 +1,161 @@ +/* Icinga 2 | (c) 2020 Icinga GmbH | GPLv2+ */ + +#include "cli/pkiverifycommand.hpp" +#include "icinga/checkresult.hpp" +#include "remote/pkiutility.hpp" +#include "base/tlsutility.hpp" +#include "base/logger.hpp" +#include + +using namespace icinga; +namespace po = boost::program_options; + +REGISTER_CLICOMMAND("pki/verify", PKIVerifyCommand); + +String PKIVerifyCommand::GetDescription() const +{ + return "Verify TLS certificates: CN, signed by CA, is CA; Print certificate"; +} + +String PKIVerifyCommand::GetShortDescription() const +{ + return "verify TLS certificates: CN, signed by CA, is CA; Print certificate"; +} + +void PKIVerifyCommand::InitParameters(boost::program_options::options_description& visibleDesc, + boost::program_options::options_description& hiddenDesc) const +{ + visibleDesc.add_options() + ("cn", po::value(), "Common Name (optional). Use with '--cert' to check the CN in the certificate.") + ("cert", po::value(), "Certificate file path (optional). Standalone: print certificate. With '--cacert': Verify against CA.") + ("cacert", po::value(), "CA certificate file path (optional). If passed standalone, verifies whether this is a CA certificate"); +} + +std::vector PKIVerifyCommand::GetArgumentSuggestions(const String& argument, const String& word) const +{ + if (argument == "cert" || argument == "cacert") + return GetBashCompletionSuggestions("file", word); + else + return CLICommand::GetArgumentSuggestions(argument, word); +} + +/** + * The entry point for the "pki verify" CLI command. + * + * @returns An exit status. + */ +int PKIVerifyCommand::Run(const boost::program_options::variables_map& vm, const std::vector& ap) const +{ + String cn, certFile, caCertFile; + + if (vm.count("cn")) + cn = vm["cn"].as(); + + if (vm.count("cert")) + certFile = vm["cert"].as(); + + if (vm.count("cacert")) + caCertFile = vm["cacert"].as(); + + /* Verify CN in certificate. */ + if (!cn.IsEmpty() && !certFile.IsEmpty()) { + std::shared_ptr cert = GetX509Certificate(certFile); + + Log(LogInformation, "cli") + << "Verifying common name (CN) '" << cn << " in certificate '" << certFile << "'."; + + std::cout << PkiUtility::GetCertificateInformation(cert) << "\n"; + + String certCN = GetCertificateCN(cert); + + if (cn == certCN) { + Log(LogInformation, "cli") + << "OK: CN '" << cn << "' matches certificate CN '" << certCN << "'."; + + return ServiceOK; + } else { + Log(LogCritical, "cli") + << "CRITICAL: CN '" << cn << "' does NOT match certificate CN '" << certCN << "'."; + + return ServiceCritical; + } + } + + /* Verify certificate. */ + if (!certFile.IsEmpty() && !caCertFile.IsEmpty()) { + std::shared_ptr cert = GetX509Certificate(certFile); + std::shared_ptr cacert = GetX509Certificate(caCertFile); + + Log(LogInformation, "cli") + << "Verifying certificate '" << certFile << "'"; + + std::cout << PkiUtility::GetCertificateInformation(cert) << "\n"; + + Log(LogInformation, "cli") + << " with CA certificate '" << caCertFile << "'."; + + std::cout << PkiUtility::GetCertificateInformation(cacert) << "\n"; + + String certCN = GetCertificateCN(cert); + + bool signedByCA; + + try { + signedByCA = VerifyCertificate(cacert, cert); + } catch (const std::exception& ex) { + Log(LogCritical, "cli") + << "CRITICAL: Certificate with CN '" << certCN << "' is NOT signed by CA: " << DiagnosticInformation(ex, false); + + return ServiceCritical; + } + + if (signedByCA) { + Log(LogInformation, "cli") + << "OK: Certificate with CN '" << certCN << "' is signed by CA."; + + return ServiceOK; + } else { + Log(LogCritical, "cli") + << "CRITICAL: Certificate with CN '" << certCN << "' is NOT signed by CA."; + + return ServiceCritical; + } + } + + + /* Standalone CA checks. */ + if (certFile.IsEmpty() && !caCertFile.IsEmpty()) { + std::shared_ptr cacert = GetX509Certificate(caCertFile); + + Log(LogInformation, "cli") + << "Checking whether certificate '" << caCertFile << "' is a valid CA certificate."; + + std::cout << PkiUtility::GetCertificateInformation(cacert) << "\n"; + + if (IsCa(cacert)) { + Log(LogInformation, "cli") + << "OK: CA certificate file '" << caCertFile << "' was verified successfully.\n"; + + return ServiceOK; + } else { + Log(LogCritical, "cli") + << "CRITICAL: The file '" << caCertFile << "' does not seem to be a CA certificate file.\n"; + + return ServiceCritical; + } + } + + /* Print certificate */ + if (!certFile.IsEmpty()) { + std::shared_ptr cert = GetX509Certificate(certFile); + + Log(LogInformation, "cli") + << "Printing certificate '" << certFile << "'"; + + std::cout << PkiUtility::GetCertificateInformation(cert) << "\n"; + + return ServiceOK; + } + + return ServiceOK; +} diff --git a/lib/cli/pkiverifycommand.hpp b/lib/cli/pkiverifycommand.hpp new file mode 100644 index 000000000..8e4b9dbcb --- /dev/null +++ b/lib/cli/pkiverifycommand.hpp @@ -0,0 +1,32 @@ +/* Icinga 2 | (c) 2020 Icinga GmbH | GPLv2+ */ + +#ifndef PKIVERIFYCOMMAND_H +#define PKIVERIFYCOMMAND_H + +#include "cli/clicommand.hpp" + +namespace icinga +{ + +/** + * The "pki verify" command. + * + * @ingroup cli + */ +class PKIVerifyCommand final : public CLICommand +{ +public: + DECLARE_PTR_TYPEDEFS(PKIVerifyCommand); + + String GetDescription() const override; + String GetShortDescription() const override; + void InitParameters(boost::program_options::options_description& visibleDesc, + boost::program_options::options_description& hiddenDesc) const override; + std::vector GetArgumentSuggestions(const String& argument, const String& word) const override; + int Run(const boost::program_options::variables_map& vm, const std::vector& ap) const override; + +}; + +} + +#endif /* PKIVERIFYCOMMAND_H */