]> git.ipfire.org Git - thirdparty/squid.git/blob - src/security/cert_generators/file/security_file_certgen.cc
Source Format Enforcement (#763)
[thirdparty/squid.git] / src / security / cert_generators / file / security_file_certgen.cc
1 /*
2 * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
7 */
8
9 #include "squid.h"
10 #include "helper/protocol_defines.h"
11 #include "security/cert_generators/file/certificate_db.h"
12 #include "ssl/crtd_message.h"
13
14 #include <cstring>
15 #include <iostream>
16 #include <sstream>
17 #include <stdexcept>
18 #include <string>
19 #if HAVE_GETOPT_H
20 #include <getopt.h>
21 #endif
22
23 /**
24 \defgroup ssl_crtd security_file_certgen
25 \ingroup ExternalPrograms
26 \par
27 Because the standard generation of SSL certificates for
28 sslBump feature, Squid must use external process to
29 actually make these calls. This process generate new ssl
30 certificates and worked with ssl certificates disk cache.
31 Typically there will be five certificate generator processes
32 spawned from Squid. Communication occurs via TCP sockets
33 bound to the loopback interface. The class in helper.h are
34 primally concerned with starting and stopping the helpers.
35 Reading and writing to and from the helpers occurs in the
36 \link IPCacheAPI IP\endlink and the dnsservers occurs in
37 the \link IPCacheAPI IP\endlink and \link FQDNCacheAPI
38 FQDN\endlink cache modules.
39
40 \section ssl_crtdInterface Command Line Interface
41 \verbatim
42 usage: security_file_certgen -hv -s directory -M size -b fs_block_size
43 -h Help
44 -v Version
45 -s directory Directory path of SSL storage database.
46 -M size Maximum size of SSL certificate disk storage.
47 -b fs_block_size File system block size in bytes. Need for processing
48 natural size of certificate on disk. Default value is
49 2048 bytes.
50
51 After running write requests in the next format:
52 <request code><whitespace><body_len><whitespace><body>
53 There are two kind of request now:
54 new_certificate 14 host=host.dom
55 Create new private key and selfsigned certificate for "host.dom".
56
57 new_certificate xxx host=host.dom
58 -----BEGIN CERTIFICATE-----
59 ...
60 -----END CERTIFICATE-----
61 -----BEGIN RSA PRIVATE KEY-----
62 ...
63 -----END RSA PRIVATE KEY-----
64 Create new private key and certificate request for "host.dom".
65 Sign new request by received certificate and private key.
66
67 usage: security_file_certgen -c -s ssl_store_path\n
68 -c Init ssl db directories and exit.
69
70 \endverbatim
71 */
72
73 static const char *const B_KBYTES_STR = "KB";
74 static const char *const B_MBYTES_STR = "MB";
75 static const char *const B_GBYTES_STR = "GB";
76 static const char *const B_BYTES_STR = "B";
77
78 /// Get current time.
79 time_t getCurrentTime(void)
80 {
81 struct timeval current_time;
82 #if GETTIMEOFDAY_NO_TZP
83 gettimeofday(&current_time);
84 #else
85 gettimeofday(&current_time, NULL);
86 #endif
87 return current_time.tv_sec;
88 }
89
90 /**
91 * Parse bytes unit. It would be one of the next value: MB, GB, KB or B.
92 * This function is caseinsensitive.
93 */
94 static size_t parseBytesUnits(const char * unit)
95 {
96 if (!strncasecmp(unit, B_BYTES_STR, strlen(B_BYTES_STR)) ||
97 !strncasecmp(unit, "", strlen(unit)))
98 return 1;
99
100 if (!strncasecmp(unit, B_KBYTES_STR, strlen(B_KBYTES_STR)))
101 return 1 << 10;
102
103 if (!strncasecmp(unit, B_MBYTES_STR, strlen(B_MBYTES_STR)))
104 return 1 << 20;
105
106 if (!strncasecmp(unit, B_GBYTES_STR, strlen(B_GBYTES_STR)))
107 return 1 << 30;
108
109 std::cerr << "WARNING: Unknown bytes unit '" << unit << "'" << std::endl;
110
111 return 0;
112 }
113
114 /// Parse uninterrapted string of bytes value. It looks like "4MB".
115 static bool parseBytesOptionValue(size_t * bptr, char const * value)
116 {
117 // Find number from string beginning.
118 char const * number_begin = value;
119 char const * number_end = value;
120
121 while ((*number_end >= '0' && *number_end <= '9')) {
122 ++number_end;
123 }
124
125 std::string number(number_begin, number_end - number_begin);
126 std::istringstream in(number);
127 int d = 0;
128 if (!(in >> d))
129 return false;
130
131 int m;
132 if ((m = parseBytesUnits(number_end)) == 0) {
133 return false;
134 }
135
136 *bptr = static_cast<size_t>(m * d);
137 if (static_cast<long>(*bptr * 2) != m * d * 2)
138 return false;
139
140 return true;
141 }
142
143 /// Print help using response code.
144 static void usage()
145 {
146 std::string example_host_name = "host.dom";
147 std::string request_string = Ssl::CrtdMessage::param_host + "=" + example_host_name;
148 std::stringstream request_string_size_stream;
149 request_string_size_stream << request_string.length();
150 std::string help_string =
151 "usage: security_file_certgen -hv -s directory -M size -b fs_block_size\n"
152 "\t-h Help\n"
153 "\t-v Version\n"
154 "\t-s directory Directory path of SSL storage database.\n"
155 "\t-M size Maximum size of SSL certificate disk storage.\n"
156 "\t-b fs_block_size File system block size in bytes. Need for processing\n"
157 "\t natural size of certificate on disk. Default value is\n"
158 "\t 2048 bytes.\n"
159 "\n"
160 "After running write requests in the next format:\n"
161 "<request code><whitespace><body_len><whitespace><body>\n"
162 "There are two kind of request now:\n"
163 + Ssl::CrtdMessage::code_new_certificate + " " + request_string_size_stream.str() + " " + request_string + "\n" +
164 "\tCreate new private key and selfsigned certificate for \"host.dom\".\n"
165 + Ssl::CrtdMessage::code_new_certificate + " xxx " + request_string + "\n" +
166 "-----BEGIN CERTIFICATE-----\n"
167 "...\n"
168 "-----END CERTIFICATE-----\n"
169 "-----BEGIN RSA PRIVATE KEY-----\n"
170 "...\n"
171 "-----END RSA PRIVATE KEY-----\n"
172 "\tCreate new private key and certificate request for \"host.dom\"\n"
173 "\tSign new request by received certificate and private key.\n"
174 "usage: security_file_certgen -c -s ssl_store_path\n"
175 "\t-c Init ssl db directories and exit.\n";
176 std::cerr << help_string << std::endl;
177 }
178
179 /// Process new request message.
180 static bool processNewRequest(Ssl::CrtdMessage & request_message, std::string const & db_path, size_t max_db_size, size_t fs_block_size)
181 {
182 Ssl::CertificateProperties certProperties;
183 std::string error;
184 if (!request_message.parseRequest(certProperties, error))
185 throw std::runtime_error("Error while parsing the crtd request: " + error);
186
187 // TODO: create a DB object only once, instead re-allocating here on every call.
188 std::unique_ptr<Ssl::CertificateDb> db;
189 if (!db_path.empty())
190 db.reset(new Ssl::CertificateDb(db_path, max_db_size, fs_block_size));
191
192 Security::CertPointer cert;
193 Security::PrivateKeyPointer pkey;
194 Security::CertPointer orig;
195 std::string &certKey = Ssl::OnDiskCertificateDbKey(certProperties);
196
197 bool dbFailed = false;
198 try {
199 if (db)
200 db->find(certKey, certProperties.mimicCert, cert, pkey);
201
202 } catch (std::runtime_error &err) {
203 dbFailed = true;
204 error = err.what();
205 }
206
207 if (!cert || !pkey) {
208 if (!Ssl::generateSslCertificate(cert, pkey, certProperties))
209 throw std::runtime_error("Cannot create ssl certificate or private key.");
210
211 try {
212 /* XXX: this !dbFailed condition prevents the helper fixing DB issues
213 by adding cleanly generated certs. Which is not consistent with other
214 data caches used by Squid - they purge broken entries and allow clean
215 entries to later try and fix the issue.
216 We leave it in place now only to avoid breaking existing installations
217 behaviour with version 1.x of the helper.
218
219 TODO: remove the !dbFailed condition when fixing the CertificateDb
220 object lifecycle and formally altering the helper behaviour.
221 */
222 if (!dbFailed && db && !db->addCertAndPrivateKey(certKey, cert, pkey, certProperties.mimicCert))
223 throw std::runtime_error("Cannot add certificate to db.");
224
225 } catch (const std::runtime_error &err) {
226 dbFailed = true;
227 error = err.what();
228 }
229 }
230
231 if (dbFailed)
232 std::cerr << "security_file_certgen helper database '" << db_path << "' failed: " << error << std::endl;
233
234 std::string bufferToWrite;
235 if (!Ssl::writeCertAndPrivateKeyToMemory(cert, pkey, bufferToWrite))
236 throw std::runtime_error("Cannot write ssl certificate or/and private key to memory.");
237
238 Ssl::CrtdMessage response_message(Ssl::CrtdMessage::REPLY);
239 response_message.setCode("OK");
240 response_message.setBody(bufferToWrite);
241
242 // Use the '\1' char as end-of-message character
243 std::cout << response_message.compose() << '\1' << std::flush;
244
245 return true;
246 }
247
248 /// This is the external security_file_certgen process.
249 int main(int argc, char *argv[])
250 {
251 try {
252 size_t max_db_size = 0;
253 size_t fs_block_size = 0;
254 int8_t c;
255 bool create_new_db = false;
256 std::string db_path;
257 // process options.
258 while ((c = getopt(argc, argv, "dchvs:M:b:")) != -1) {
259 switch (c) {
260 case 'd':
261 debug_enabled = 1;
262 break;
263 case 'b':
264 if (!parseBytesOptionValue(&fs_block_size, optarg)) {
265 throw std::runtime_error("Error when parsing -b options value");
266 }
267 break;
268 case 's':
269 db_path = optarg;
270 break;
271 case 'M':
272 // use of -M without -s is probably an admin mistake, so make it an error
273 if (db_path.empty()) {
274 throw std::runtime_error("Error -M option requires an -s parameter be set first.");
275 }
276 if (!parseBytesOptionValue(&max_db_size, optarg)) {
277 throw std::runtime_error("Error when parsing -M options value");
278 }
279 break;
280 case 'v':
281 std::cout << "security_file_certgen version " << VERSION << std::endl;
282 exit(EXIT_SUCCESS);
283 break;
284 case 'c':
285 create_new_db = true;
286 break;
287 case 'h':
288 usage();
289 exit(EXIT_SUCCESS);
290 default:
291 exit(EXIT_FAILURE);
292 }
293 }
294
295 // when -s is used, -M is required
296 if (!db_path.empty() && max_db_size == 0)
297 throw std::runtime_error("security_file_certgen -s requires an -M parameter");
298
299 if (create_new_db) {
300 // when -c is used, -s is required (implying also -M, which is checked above)
301 if (db_path.empty())
302 throw std::runtime_error("security_file_certgen is missing the required parameter. There should be -s and -M parameters when -c is used.");
303
304 std::cout << "Initialization SSL db..." << std::endl;
305 Ssl::CertificateDb::Create(db_path);
306 std::cout << "Done" << std::endl;
307 exit(EXIT_SUCCESS);
308 }
309
310 // only do filesystem checks when a path (-s) is given
311 if (!db_path.empty()) {
312 if (fs_block_size == 0) {
313 struct statvfs sfs;
314
315 if (xstatvfs(db_path.c_str(), &sfs)) {
316 fs_block_size = 2048;
317 } else {
318 fs_block_size = sfs.f_frsize;
319 // Sanity check; make sure we have a meaningful value.
320 if (fs_block_size < 512)
321 fs_block_size = 2048;
322 }
323 }
324 Ssl::CertificateDb::Check(db_path, max_db_size, fs_block_size);
325 }
326
327 // Initialize SSL subsystem
328 SQUID_OPENSSL_init_ssl();
329 // process request.
330 for (;;) {
331 char request[HELPER_INPUT_BUFFER];
332 Ssl::CrtdMessage request_message(Ssl::CrtdMessage::REQUEST);
333 Ssl::CrtdMessage::ParseResult parse_result = Ssl::CrtdMessage::INCOMPLETE;
334
335 while (parse_result == Ssl::CrtdMessage::INCOMPLETE) {
336 if (fgets(request, HELPER_INPUT_BUFFER, stdin) == NULL)
337 exit(EXIT_FAILURE);
338 size_t gcount = strlen(request);
339 parse_result = request_message.parse(request, gcount);
340 }
341
342 if (parse_result == Ssl::CrtdMessage::ERROR) {
343 throw std::runtime_error("Cannot parse request message.");
344 } else if (request_message.getCode() == Ssl::CrtdMessage::code_new_certificate) {
345 processNewRequest(request_message, db_path, max_db_size, fs_block_size);
346 } else {
347 throw std::runtime_error("Unknown request code: \"" + request_message.getCode() + "\".");
348 }
349 std::cout.flush();
350 }
351 } catch (std::runtime_error & error) {
352 std::cerr << argv[0] << ": " << error.what() << std::endl;
353 return EXIT_FAILURE;
354 }
355 return EXIT_SUCCESS;
356 }
357