1 /* Icinga 2 | (c) 2020 Icinga GmbH | GPLv2+ */
2
3 #include "cli/pkiverifycommand.hpp"
4 #include "icinga/service.hpp"
5 #include "remote/pkiutility.hpp"
6 #include "base/tlsutility.hpp"
7 #include "base/logger.hpp"
8 #include <iostream>
9
10 using namespace icinga;
11 namespace po = boost::program_options;
12
13 REGISTER_CLICOMMAND("pki/verify", PKIVerifyCommand);
14
GetDescription() const15 String PKIVerifyCommand::GetDescription() const
16 {
17 return "Verify TLS certificates: CN, signed by CA, is CA; Print certificate";
18 }
19
GetShortDescription() const20 String PKIVerifyCommand::GetShortDescription() const
21 {
22 return "verify TLS certificates: CN, signed by CA, is CA; Print certificate";
23 }
24
InitParameters(boost::program_options::options_description & visibleDesc,boost::program_options::options_description & hiddenDesc) const25 void PKIVerifyCommand::InitParameters(boost::program_options::options_description& visibleDesc,
26 boost::program_options::options_description& hiddenDesc) const
27 {
28 visibleDesc.add_options()
29 ("cn", po::value<std::string>(), "Common Name (optional). Use with '--cert' to check the CN in the certificate.")
30 ("cert", po::value<std::string>(), "Certificate file path (optional). Standalone: print certificate. With '--cacert': Verify against CA.")
31 ("cacert", po::value<std::string>(), "CA certificate file path (optional). If passed standalone, verifies whether this is a CA certificate")
32 ("crl", po::value<std::string>(), "CRL file path (optional). Check the certificate against this revocation list when verifying against CA.");
33 }
34
GetArgumentSuggestions(const String & argument,const String & word) const35 std::vector<String> PKIVerifyCommand::GetArgumentSuggestions(const String& argument, const String& word) const
36 {
37 if (argument == "cert" || argument == "cacert" || argument == "crl")
38 return GetBashCompletionSuggestions("file", word);
39 else
40 return CLICommand::GetArgumentSuggestions(argument, word);
41 }
42
43 /**
44 * The entry point for the "pki verify" CLI command.
45 *
46 * @returns An exit status.
47 */
Run(const boost::program_options::variables_map & vm,const std::vector<std::string> & ap) const48 int PKIVerifyCommand::Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const
49 {
50 String cn, certFile, caCertFile, crlFile;
51
52 if (vm.count("cn"))
53 cn = vm["cn"].as<std::string>();
54
55 if (vm.count("cert"))
56 certFile = vm["cert"].as<std::string>();
57
58 if (vm.count("cacert"))
59 caCertFile = vm["cacert"].as<std::string>();
60
61 if (vm.count("crl"))
62 crlFile = vm["crl"].as<std::string>();
63
64 /* Verify CN in certificate. */
65 if (!cn.IsEmpty() && !certFile.IsEmpty()) {
66 std::shared_ptr<X509> cert;
67 try {
68 cert = GetX509Certificate(certFile);
69 } catch (const std::exception& ex) {
70 Log(LogCritical, "cli")
71 << "Cannot read certificate file '" << certFile << "'. Please ensure that it exists and is readable.";
72
73 return ServiceCritical;
74 }
75
76 Log(LogInformation, "cli")
77 << "Verifying common name (CN) '" << cn << " in certificate '" << certFile << "'.";
78
79 std::cout << PkiUtility::GetCertificateInformation(cert) << "\n";
80
81 String certCN = GetCertificateCN(cert);
82
83 if (cn == certCN) {
84 Log(LogInformation, "cli")
85 << "OK: CN '" << cn << "' matches certificate CN '" << certCN << "'.";
86
87 return ServiceOK;
88 } else {
89 Log(LogCritical, "cli")
90 << "CRITICAL: CN '" << cn << "' does NOT match certificate CN '" << certCN << "'.";
91
92 return ServiceCritical;
93 }
94 }
95
96 /* Verify certificate. */
97 if (!certFile.IsEmpty() && !caCertFile.IsEmpty()) {
98 std::shared_ptr<X509> cert;
99 try {
100 cert = GetX509Certificate(certFile);
101 } catch (const std::exception& ex) {
102 Log(LogCritical, "cli")
103 << "Cannot read certificate file '" << certFile << "'. Please ensure that it exists and is readable.";
104
105 return ServiceCritical;
106 }
107
108 std::shared_ptr<X509> cacert;
109 try {
110 cacert = GetX509Certificate(caCertFile);
111 } catch (const std::exception& ex) {
112 Log(LogCritical, "cli")
113 << "Cannot read CA certificate file '" << caCertFile << "'. Please ensure that it exists and is readable.";
114
115 return ServiceCritical;
116 }
117
118 Log(LogInformation, "cli")
119 << "Verifying certificate '" << certFile << "'";
120
121 std::cout << PkiUtility::GetCertificateInformation(cert) << "\n";
122
123 Log(LogInformation, "cli")
124 << " with CA certificate '" << caCertFile << "'.";
125
126 std::cout << PkiUtility::GetCertificateInformation(cacert) << "\n";
127
128 String certCN = GetCertificateCN(cert);
129
130 bool signedByCA;
131
132 try {
133 signedByCA = VerifyCertificate(cacert, cert, crlFile);
134 } catch (const std::exception& ex) {
135 Log logmsg (LogCritical, "cli");
136 logmsg << "CRITICAL: Certificate with CN '" << certCN << "' is NOT signed by CA: ";
137 if (const unsigned long *openssl_code = boost::get_error_info<errinfo_openssl_error>(ex)) {
138 logmsg << X509_verify_cert_error_string(*openssl_code) << " (code " << *openssl_code << ")";
139 } else {
140 logmsg << DiagnosticInformation(ex, false);
141 }
142
143 return ServiceCritical;
144 }
145
146 if (signedByCA) {
147 Log(LogInformation, "cli")
148 << "OK: Certificate with CN '" << certCN << "' is signed by CA.";
149
150 return ServiceOK;
151 } else {
152 Log(LogCritical, "cli")
153 << "CRITICAL: Certificate with CN '" << certCN << "' is NOT signed by CA.";
154
155 return ServiceCritical;
156 }
157 }
158
159
160 /* Standalone CA checks. */
161 if (certFile.IsEmpty() && !caCertFile.IsEmpty()) {
162 std::shared_ptr<X509> cacert;
163 try {
164 cacert = GetX509Certificate(caCertFile);
165 } catch (const std::exception& ex) {
166 Log(LogCritical, "cli")
167 << "Cannot read CA certificate file '" << caCertFile << "'. Please ensure that it exists and is readable.";
168
169 return ServiceCritical;
170 }
171
172 Log(LogInformation, "cli")
173 << "Checking whether certificate '" << caCertFile << "' is a valid CA certificate.";
174
175 std::cout << PkiUtility::GetCertificateInformation(cacert) << "\n";
176
177 if (IsCa(cacert)) {
178 Log(LogInformation, "cli")
179 << "OK: CA certificate file '" << caCertFile << "' was verified successfully.\n";
180
181 return ServiceOK;
182 } else {
183 Log(LogCritical, "cli")
184 << "CRITICAL: The file '" << caCertFile << "' does not seem to be a CA certificate file.\n";
185
186 return ServiceCritical;
187 }
188 }
189
190 /* Print certificate */
191 if (!certFile.IsEmpty()) {
192 std::shared_ptr<X509> cert;
193 try {
194 cert = GetX509Certificate(certFile);
195 } catch (const std::exception& ex) {
196 Log(LogCritical, "cli")
197 << "Cannot read certificate file '" << certFile << "'. Please ensure that it exists and is readable.";
198
199 return ServiceCritical;
200 }
201
202 Log(LogInformation, "cli")
203 << "Printing certificate '" << certFile << "'";
204
205 std::cout << PkiUtility::GetCertificateInformation(cert) << "\n";
206
207 return ServiceOK;
208 }
209
210 /* Error handling. */
211 if (!cn.IsEmpty() && certFile.IsEmpty()) {
212 Log(LogCritical, "cli")
213 << "The '--cn' parameter requires the '--cert' parameter.";
214
215 return ServiceCritical;
216 }
217
218 if (cn.IsEmpty() && certFile.IsEmpty() && caCertFile.IsEmpty()) {
219 Log(LogInformation, "cli")
220 << "Please add the '--help' parameter to see all available options.";
221
222 return ServiceOK;
223 }
224
225 return ServiceOK;
226 }
227