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