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