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