]>
Commit | Line | Data |
---|---|---|
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 | 46 | usage: 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 | 71 | usage: 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 | ||
77 | static const char *const B_KBYTES_STR = "KB"; | |
78 | static const char *const B_MBYTES_STR = "MB"; | |
79 | static const char *const B_GBYTES_STR = "GB"; | |
80 | static 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 | */ | |
86 | static 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 | |
106 | static size_t | |
107 | parseBytesOptionValue(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 |
136 | static 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. |
172 | static 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 |
238 | int 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 |