]> git.ipfire.org Git - thirdparty/squid.git/blob - src/security/cert_generators/file/security_file_certgen.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / security / cert_generators / file / security_file_certgen.cc
1 /*
2 * Copyright (C) 1996-2017 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 "helper/protocol_defines.h"
11 #include "security/cert_generators/file/certificate_db.h"
12 #include "ssl/crtd_message.h"
13
14 #include <cstring>
15 #include <iostream>
16 #include <sstream>
17 #include <stdexcept>
18 #include <string>
19 #if HAVE_GETOPT_H
20 #include <getopt.h>
21 #endif
22
23 /**
24 \defgroup ssl_crtd security_file_certgen
25 \ingroup ExternalPrograms
26 \par
27 Because the standard generation of SSL certificates for
28 sslBump feature, Squid must use external process to
29 actually make these calls. This process generate new ssl
30 certificates and worked with ssl certificates disk cache.
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
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
42 usage: security_file_certgen -hv -s ssl_storage_path -M storage_max_size
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
68 usage: security_file_certgen -c -s ssl_store_path\n
69 -c Init ssl db directories and exit.
70
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
79 /// Get current time.
80 time_t getCurrentTime(void)
81 {
82 struct timeval current_time;
83 #if GETTIMEOFDAY_NO_TZP
84 gettimeofday(&current_time);
85 #else
86 gettimeofday(&current_time, NULL);
87 #endif
88 return current_time.tv_sec;
89 }
90
91 /**
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
110 std::cerr << "WARNING: Unknown bytes unit '" << unit << "'" << std::endl;
111
112 return 0;
113 }
114
115 /// Parse uninterrapted string of bytes value. It looks like "4MB".
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')) {
123 ++number_end;
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
144 /// Print help using response code.
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 =
152 "usage: security_file_certgen -hv -s ssl_storage_path -M storage_max_size\n"
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"
176 "usage: security_file_certgen -c -s ssl_store_path\n"
177 "\t-c Init ssl db directories and exit.\n";
178 std::cerr << help_string << std::endl;
179 }
180
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)
183 {
184 Ssl::CertificateProperties certProperties;
185 std::string error;
186 if (!request_message.parseRequest(certProperties, error))
187 throw std::runtime_error("Error while parsing the crtd request: " + error);
188
189 Ssl::CertificateDb db(db_path, max_db_size, fs_block_size);
190
191 Security::CertPointer cert;
192 Ssl::EVP_PKEY_Pointer pkey;
193 std::string &cert_subject = certProperties.dbKey();
194
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 }
202
203 if (cert) {
204 if (!Ssl::certificateMatchesProperties(cert.get(), certProperties)) {
205 // The certificate changed (renewed or other reason).
206 // Generete a new one with the updated fields.
207 cert.reset();
208 pkey.reset();
209 db.purgeCert(cert_subject);
210 }
211 }
212
213 if (!cert || !pkey) {
214 if (!Ssl::generateSslCertificate(cert, pkey, certProperties))
215 throw std::runtime_error("Cannot create ssl certificate or private key.");
216
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 }
228 }
229
230 if (dbFailed)
231 std::cerr << "security_file_certgen helper database '" << db_path << "' failed: " << error << std::endl;
232
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
237 Ssl::CrtdMessage response_message(Ssl::CrtdMessage::REPLY);
238 response_message.setCode("OK");
239 response_message.setBody(bufferToWrite);
240
241 // Use the '\1' char as end-of-message character
242 std::cout << response_message.compose() << '\1' << std::flush;
243
244 return true;
245 }
246
247 /// This is the external security_file_certgen process.
248 int main(int argc, char *argv[])
249 {
250 try {
251 size_t max_db_size = 0;
252 size_t fs_block_size = 0;
253 int8_t c;
254 bool create_new_db = false;
255 std::string db_path;
256 // process options.
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;
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':
276 std::cout << "security_file_certgen version " << VERSION << std::endl;
277 exit(0);
278 break;
279 case 'c':
280 create_new_db = true;
281 break;
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;
292 Ssl::CertificateDb::create(db_path);
293 std::cout << "Done" << std::endl;
294 exit(0);
295 }
296
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
310 {
311 Ssl::CertificateDb::check(db_path, max_db_size, fs_block_size);
312 }
313 // Initialize SSL subsystem
314 SSL_load_error_strings();
315 SSLeay_add_ssl_algorithms();
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) == 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) {
332 processNewRequest(request_message, db_path, max_db_size, fs_block_size);
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 }
344