]> git.ipfire.org Git - thirdparty/squid.git/blame_incremental - src/security/cert_generators/file/security_file_certgen.cc
Simplify appending SBuf to String (#2108)
[thirdparty/squid.git] / src / security / cert_generators / file / security_file_certgen.cc
... / ...
CommitLineData
1/*
2 * Copyright (C) 1996-2025 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 "base/TextException.h"
11#include "debug/Stream.h"
12#include "helper/protocol_defines.h"
13#include "sbuf/Stream.h"
14#include "security/cert_generators/file/certificate_db.h"
15#include "ssl/crtd_message.h"
16#include "time/gadgets.h"
17
18#include <cstring>
19#include <iostream>
20#include <sstream>
21#include <stdexcept>
22#include <string>
23#if HAVE_GETOPT_H
24#include <getopt.h>
25#endif
26
27/**
28 \defgroup ssl_crtd security_file_certgen
29 \ingroup ExternalPrograms
30 \par
31 Because the standard generation of SSL certificates for
32 sslBump feature, Squid must use external process to
33 actually make these calls. This process generate new ssl
34 certificates and worked with ssl certificates disk cache.
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
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
46usage: security_file_certgen -hv -s directory -M size -b fs_block_size
47 -h Help
48 -v Version
49 -s directory Directory path of SSL storage database.
50 -M size Maximum size of SSL certificate disk storage.
51 -b fs_block_size File system block size in bytes. Need for processing
52 natural size of certificate on disk. Default value is
53 2048 bytes.
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
71usage: security_file_certgen -c -s ssl_store_path\n
72 -c Init ssl db directories and exit.
73
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
82/**
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
101 throw TextException(ToSBuf("Unknown bytes unit: ", unit), Here());
102}
103
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)
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')) {
114 ++number_end;
115 }
116
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
120 std::string number(number_begin, number_end - number_begin);
121 std::istringstream in(number);
122 size_t base = 0;
123 if (!(in >> base) || !in.eof())
124 throw TextException(ToSBuf("unsupported integer part of ", name, " value: ", number), Here());
125
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());
131
132 return product;
133}
134
135/// Print help using response code.
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 =
143 "usage: security_file_certgen -hv -s directory -M size -b fs_block_size\n"
144 "\t-h Help\n"
145 "\t-v Version\n"
146 "\t-s directory Directory path of SSL storage database.\n"
147 "\t-M size Maximum size of SSL certificate disk storage.\n"
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"
166 "usage: security_file_certgen -c -s ssl_store_path\n"
167 "\t-c Init ssl db directories and exit.\n";
168 std::cerr << help_string << std::endl;
169}
170
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)
173{
174 Ssl::CertificateProperties certProperties;
175 request_message.parseRequest(certProperties);
176
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));
181
182 Security::CertPointer cert;
183 Security::PrivateKeyPointer pkey;
184 Security::CertPointer orig;
185 std::string &certKey = Ssl::OnDiskCertificateDbKey(certProperties);
186
187 bool dbFailed = false;
188 try {
189 if (db)
190 db->find(certKey, certProperties.mimicCert, cert, pkey);
191
192 } catch (...) {
193 dbFailed = true;
194 debugs(83, DBG_IMPORTANT, "ERROR: Database search failure: " << CurrentException <<
195 Debug::Extra << "database location: " << db_path);
196 }
197
198 if (!cert || !pkey) {
199 if (!Ssl::generateSslCertificate(cert, pkey, certProperties))
200 throw TextException("Cannot create ssl certificate or private key.", Here());
201
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))
214 throw TextException("Cannot add certificate to db.", Here());
215
216 } catch (...) {
217 dbFailed = true;
218 debugs(83, DBG_IMPORTANT, "ERROR: Database update failure: " << CurrentException <<
219 Debug::Extra << "database location: " << db_path);
220 }
221 }
222
223 std::string bufferToWrite;
224 if (!Ssl::writeCertAndPrivateKeyToMemory(cert, pkey, bufferToWrite))
225 throw TextException("Cannot write ssl certificate or/and private key to memory.", Here());
226
227 Ssl::CrtdMessage response_message(Ssl::CrtdMessage::REPLY);
228 response_message.setCode("OK");
229 response_message.setBody(bufferToWrite);
230
231 // Use the '\1' char as end-of-message character
232 std::cout << response_message.compose() << '\1' << std::flush;
233
234 return true;
235}
236
237/// This is the external security_file_certgen process.
238int main(int argc, char *argv[])
239{
240 try {
241 Debug::NameThisHelper("sslcrtd_program");
242
243 size_t max_db_size = 0;
244 size_t fs_block_size = 0;
245 int8_t c;
246 bool create_new_db = false;
247 std::string db_path;
248 // process options.
249 while ((c = getopt(argc, argv, "dchvs:M:b:")) != -1) {
250 switch (c) {
251 case 'd':
252 debug_enabled = 1;
253 break;
254 case 'b':
255 fs_block_size = parseBytesOptionValue("-b", optarg);
256 break;
257 case 's':
258 db_path = optarg;
259 break;
260 case 'M':
261 // use of -M without -s is probably an admin mistake, so make it an error
262 if (db_path.empty()) {
263 throw TextException("Error -M option requires an -s parameter be set first.", Here());
264 }
265 max_db_size = parseBytesOptionValue("-M", optarg);
266 break;
267 case 'v':
268 std::cout << "security_file_certgen version " << VERSION << std::endl;
269 exit(EXIT_SUCCESS);
270 break;
271 case 'c':
272 create_new_db = true;
273 break;
274 case 'h':
275 usage();
276 exit(EXIT_SUCCESS);
277 default:
278 exit(EXIT_FAILURE);
279 }
280 }
281
282 // when -s is used, -M is required
283 if (!db_path.empty() && max_db_size == 0)
284 throw TextException("security_file_certgen -s requires an -M parameter", Here());
285
286 if (create_new_db) {
287 // when -c is used, -s is required (implying also -M, which is checked above)
288 if (db_path.empty())
289 throw TextException("security_file_certgen is missing the required parameter. There should be -s and -M parameters when -c is used.", Here());
290
291 std::cout << "Initialization SSL db..." << std::endl;
292 Ssl::CertificateDb::Create(db_path);
293 std::cout << "Done" << std::endl;
294 exit(EXIT_SUCCESS);
295 }
296
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)) {
303 fs_block_size = 2048;
304 } else {
305 fs_block_size = sfs.f_frsize;
306 // Sanity check; make sure we have a meaningful value.
307 if (fs_block_size < 512)
308 fs_block_size = 2048;
309 }
310 }
311 Ssl::CertificateDb::Check(db_path, max_db_size, fs_block_size);
312 }
313
314 // Initialize SSL subsystem
315 SQUID_OPENSSL_init_ssl();
316 // process request.
317 for (;;) {
318 char request[HELPER_INPUT_BUFFER];
319 Ssl::CrtdMessage request_message(Ssl::CrtdMessage::REQUEST);
320 Ssl::CrtdMessage::ParseResult parse_result = Ssl::CrtdMessage::INCOMPLETE;
321
322 while (parse_result == Ssl::CrtdMessage::INCOMPLETE) {
323 if (fgets(request, HELPER_INPUT_BUFFER, stdin) == nullptr)
324 exit(EXIT_FAILURE);
325 size_t gcount = strlen(request);
326 parse_result = request_message.parse(request, gcount);
327 }
328
329 if (parse_result == Ssl::CrtdMessage::ERROR) {
330 throw TextException("Cannot parse request message.", Here());
331 } else if (request_message.getCode() == Ssl::CrtdMessage::code_new_certificate) {
332 processNewRequest(request_message, db_path, max_db_size, fs_block_size);
333 } else {
334 throw TextException(ToSBuf("Unknown request code: \"", request_message.getCode(), "\"."), Here());
335 }
336 std::cout.flush();
337 }
338 } catch (...) {
339 debugs(83, DBG_CRITICAL, "FATAL: Cannot generate certificates: " << CurrentException);
340 return EXIT_FAILURE;
341 }
342 return EXIT_SUCCESS;
343}
344