]>
Commit | Line | Data |
---|---|---|
bbc27441 | 1 | /* |
4ac4a490 | 2 | * Copyright (C) 1996-2017 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" |
602d9612 | 12 | #include "ssl/crtd_message.h" |
95d2589c | 13 | |
95d2589c | 14 | #include <cstring> |
95d2589c | 15 | #include <iostream> |
074d6a40 | 16 | #include <sstream> |
95d2589c | 17 | #include <stdexcept> |
95d2589c | 18 | #include <string> |
95d2589c CT |
19 | #if HAVE_GETOPT_H |
20 | #include <getopt.h> | |
21 | #endif | |
22 | ||
23 | /** | |
051da40c | 24 | \defgroup ssl_crtd security_file_certgen |
95d2589c CT |
25 | \ingroup ExternalPrograms |
26 | \par | |
051da40c | 27 | Because the standard generation of SSL certificates for |
ba5d55c1 | 28 | sslBump feature, Squid must use external process to |
95d2589c CT |
29 | actually make these calls. This process generate new ssl |
30 | certificates and worked with ssl certificates disk cache. | |
051da40c AJ |
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 | |
95d2589c CT |
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 | |
051da40c | 42 | usage: security_file_certgen -hv -s ssl_storage_path -M storage_max_size |
95d2589c CT |
43 | -h Help |
44 | -v Version | |
45 | -s ssl_storage_path Path to specific disk storage of ssl server | |
46 | certificates. | |
47 | -M storage_max_size max size of ssl certificates storage. | |
48 | -b fs_block_size File system block size in bytes. Need for processing | |
49 | natural size of certificate on disk. Default value is | |
50 | 2048 bytes." | |
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 | 68 | usage: 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 | ||
74 | static const char *const B_KBYTES_STR = "KB"; | |
75 | static const char *const B_MBYTES_STR = "MB"; | |
76 | static const char *const B_GBYTES_STR = "GB"; | |
77 | static const char *const B_BYTES_STR = "B"; | |
78 | ||
051da40c | 79 | /// Get current time. |
95d2589c CT |
80 | time_t getCurrentTime(void) |
81 | { | |
82 | struct timeval current_time; | |
83 | #if GETTIMEOFDAY_NO_TZP | |
84 | gettimeofday(¤t_time); | |
85 | #else | |
86 | gettimeofday(¤t_time, NULL); | |
87 | #endif | |
88 | return current_time.tv_sec; | |
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 | */ | |
95 | static 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 |
116 | static 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 |
145 | static 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 = | |
051da40c | 152 | "usage: security_file_certgen -hv -s ssl_storage_path -M storage_max_size\n" |
95d2589c CT |
153 | "\t-h Help\n" |
154 | "\t-v Version\n" | |
155 | "\t-s ssl_storage_path Path to specific disk storage of ssl server\n" | |
156 | "\t certificates.\n" | |
157 | "\t-M storage_max_size max size of ssl certificates storage.\n" | |
158 | "\t-b fs_block_size File system block size in bytes. Need for processing\n" | |
159 | "\t natural size of certificate on disk. Default value is\n" | |
160 | "\t 2048 bytes.\n" | |
161 | "\n" | |
162 | "After running write requests in the next format:\n" | |
163 | "<request code><whitespace><body_len><whitespace><body>\n" | |
164 | "There are two kind of request now:\n" | |
165 | + Ssl::CrtdMessage::code_new_certificate + " " + request_string_size_stream.str() + " " + request_string + "\n" + | |
166 | "\tCreate new private key and selfsigned certificate for \"host.dom\".\n" | |
167 | + Ssl::CrtdMessage::code_new_certificate + " xxx " + request_string + "\n" + | |
168 | "-----BEGIN CERTIFICATE-----\n" | |
169 | "...\n" | |
170 | "-----END CERTIFICATE-----\n" | |
171 | "-----BEGIN RSA PRIVATE KEY-----\n" | |
172 | "...\n" | |
173 | "-----END RSA PRIVATE KEY-----\n" | |
174 | "\tCreate new private key and certificate request for \"host.dom\"\n" | |
175 | "\tSign new request by received certificate and private key.\n" | |
051da40c | 176 | "usage: security_file_certgen -c -s ssl_store_path\n" |
a0b971d5 | 177 | "\t-c Init ssl db directories and exit.\n"; |
95d2589c CT |
178 | std::cerr << help_string << std::endl; |
179 | } | |
180 | ||
ba5d55c1 NH |
181 | /// Process new request message. |
182 | static bool processNewRequest(Ssl::CrtdMessage & request_message, std::string const & db_path, size_t max_db_size, size_t fs_block_size) | |
95d2589c | 183 | { |
aebe6888 | 184 | Ssl::CertificateProperties certProperties; |
06997a38 CT |
185 | std::string error; |
186 | if (!request_message.parseRequest(certProperties, error)) | |
ce394ae1 | 187 | throw std::runtime_error("Error while parsing the crtd request: " + error); |
95d2589c CT |
188 | |
189 | Ssl::CertificateDb db(db_path, max_db_size, fs_block_size); | |
190 | ||
f97700a0 | 191 | Security::CertPointer cert; |
95d2589c | 192 | Ssl::EVP_PKEY_Pointer pkey; |
06997a38 | 193 | std::string &cert_subject = certProperties.dbKey(); |
95d2589c | 194 | |
541e6ab5 CT |
195 | bool dbFailed = false; |
196 | try { | |
197 | db.find(cert_subject, cert, pkey); | |
198 | } catch (std::runtime_error &err) { | |
199 | dbFailed = true; | |
200 | error = err.what(); | |
201 | } | |
95d2589c | 202 | |
754f901e | 203 | if (cert) { |
4ece76b2 | 204 | if (!Ssl::certificateMatchesProperties(cert.get(), certProperties)) { |
e7bcc25f CT |
205 | // The certificate changed (renewed or other reason). |
206 | // Generete a new one with the updated fields. | |
1ca8bbfb AJ |
207 | cert.reset(); |
208 | pkey.reset(); | |
4ece76b2 | 209 | db.purgeCert(cert_subject); |
e7bcc25f CT |
210 | } |
211 | } | |
95d2589c | 212 | |
87f237a9 | 213 | if (!cert || !pkey) { |
aebe6888 | 214 | if (!Ssl::generateSslCertificate(cert, pkey, certProperties)) |
95d2589c | 215 | throw std::runtime_error("Cannot create ssl certificate or private key."); |
9a90aace | 216 | |
541e6ab5 CT |
217 | if (!dbFailed && db.IsEnabledDiskStore()) { |
218 | try { | |
219 | if (!db.addCertAndPrivateKey(cert, pkey, cert_subject)) { | |
220 | dbFailed = true; | |
221 | error = "Cannot add certificate to db."; | |
222 | } | |
223 | } catch (const std::runtime_error &err) { | |
224 | dbFailed = true; | |
225 | error = err.what(); | |
226 | } | |
227 | } | |
95d2589c CT |
228 | } |
229 | ||
541e6ab5 | 230 | if (dbFailed) |
051da40c | 231 | std::cerr << "security_file_certgen helper database '" << db_path << "' failed: " << error << std::endl; |
541e6ab5 | 232 | |
95d2589c CT |
233 | std::string bufferToWrite; |
234 | if (!Ssl::writeCertAndPrivateKeyToMemory(cert, pkey, bufferToWrite)) | |
235 | throw std::runtime_error("Cannot write ssl certificate or/and private key to memory."); | |
236 | ||
ff2d7d92 | 237 | Ssl::CrtdMessage response_message(Ssl::CrtdMessage::REPLY); |
419ac015 | 238 | response_message.setCode("OK"); |
95d2589c CT |
239 | response_message.setBody(bufferToWrite); |
240 | ||
0af9303a CT |
241 | // Use the '\1' char as end-of-message character |
242 | std::cout << response_message.compose() << '\1' << std::flush; | |
95d2589c CT |
243 | |
244 | return true; | |
245 | } | |
246 | ||
051da40c | 247 | /// This is the external security_file_certgen process. |
95d2589c CT |
248 | int main(int argc, char *argv[]) |
249 | { | |
250 | try { | |
95d2589c | 251 | size_t max_db_size = 0; |
ba5d55c1 | 252 | size_t fs_block_size = 0; |
c3373ae4 | 253 | int8_t c; |
95d2589c | 254 | bool create_new_db = false; |
95d2589c | 255 | std::string db_path; |
ba5d55c1 | 256 | // process options. |
95d2589c CT |
257 | while ((c = getopt(argc, argv, "dcghvs:M:b:n:")) != -1) { |
258 | switch (c) { | |
259 | case 'd': | |
260 | debug_enabled = 1; | |
261 | break; | |
262 | case 'b': | |
263 | if (!parseBytesOptionValue(&fs_block_size, optarg)) { | |
264 | throw std::runtime_error("Error when parsing -b options value"); | |
265 | } | |
266 | break; | |
267 | case 's': | |
268 | db_path = optarg; | |
269 | break; | |
95d2589c CT |
270 | case 'M': |
271 | if (!parseBytesOptionValue(&max_db_size, optarg)) { | |
272 | throw std::runtime_error("Error when parsing -M options value"); | |
273 | } | |
274 | break; | |
275 | case 'v': | |
051da40c | 276 | std::cout << "security_file_certgen version " << VERSION << std::endl; |
95d2589c CT |
277 | exit(0); |
278 | break; | |
279 | case 'c': | |
280 | create_new_db = true; | |
281 | break; | |
95d2589c CT |
282 | case 'h': |
283 | usage(); | |
284 | exit(0); | |
285 | default: | |
286 | exit(0); | |
287 | } | |
288 | } | |
289 | ||
290 | if (create_new_db) { | |
291 | std::cout << "Initialization SSL db..." << std::endl; | |
a0b971d5 | 292 | Ssl::CertificateDb::create(db_path); |
95d2589c CT |
293 | std::cout << "Done" << std::endl; |
294 | exit(0); | |
295 | } | |
296 | ||
ba5d55c1 NH |
297 | if (fs_block_size == 0) { |
298 | struct statvfs sfs; | |
299 | ||
300 | if (xstatvfs(db_path.c_str(), &sfs)) { | |
301 | fs_block_size = 2048; | |
302 | } else { | |
303 | fs_block_size = sfs.f_frsize; | |
304 | // Sanity check; make sure we have a meaningful value. | |
305 | if (fs_block_size < 512) | |
306 | fs_block_size = 2048; | |
307 | } | |
308 | } | |
309 | ||
95d2589c | 310 | { |
a066059b | 311 | Ssl::CertificateDb::check(db_path, max_db_size, fs_block_size); |
95d2589c | 312 | } |
3c26b00a CT |
313 | // Initialize SSL subsystem |
314 | SSL_load_error_strings(); | |
315 | SSLeay_add_ssl_algorithms(); | |
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) { | |
323 | if (fgets(request, HELPER_INPUT_BUFFER, stdin) == NULL) | |
324 | return 1; | |
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 std::runtime_error("Cannot parse request message."); | |
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 CT |
333 | } else { |
334 | throw std::runtime_error("Unknown request code: \"" + request_message.getCode() + "\"."); | |
335 | } | |
336 | std::cout.flush(); | |
337 | } | |
338 | } catch (std::runtime_error & error) { | |
339 | std::cerr << argv[0] << ": " << error.what() << std::endl; | |
340 | return 0; | |
341 | } | |
342 | return 0; | |
343 | } | |
f53969cc | 344 |