]> git.ipfire.org Git - thirdparty/squid.git/blob - src/ssl/ssl_crtd.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / ssl / ssl_crtd.cc
1 /*
2 * Copyright (C) 1996-2016 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 "helpers/defines.h"
11 #include "ssl/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 ssl_crtd
25 \ingroup ExternalPrograms
26 \par
27 Because the standart generation of ssl certificate for
28 sslBump feature, Squid must use external proccess 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 ssl_crtd processes spawned
32 from Squid. Communication occurs via TCP sockets bound
33 to the loopback interface. The class in helper.h are
34 primally concerned with starting and stopping the ssl_crtd.
35 Reading and writing to and from the ssl_crtd 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: ssl_crtd -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: ssl_crtd -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 /**
80 \ingroup ssl_crtd
81 * Get current time.
82 */
83 time_t getCurrentTime(void)
84 {
85 struct timeval current_time;
86 #if GETTIMEOFDAY_NO_TZP
87 gettimeofday(&current_time);
88 #else
89 gettimeofday(&current_time, NULL);
90 #endif
91 return current_time.tv_sec;
92 }
93
94 /**
95 \ingroup ssl_crtd
96 * Parse bytes unit. It would be one of the next value: MB, GB, KB or B.
97 * This function is caseinsensitive.
98 */
99 static size_t parseBytesUnits(const char * unit)
100 {
101 if (!strncasecmp(unit, B_BYTES_STR, strlen(B_BYTES_STR)) ||
102 !strncasecmp(unit, "", strlen(unit)))
103 return 1;
104
105 if (!strncasecmp(unit, B_KBYTES_STR, strlen(B_KBYTES_STR)))
106 return 1 << 10;
107
108 if (!strncasecmp(unit, B_MBYTES_STR, strlen(B_MBYTES_STR)))
109 return 1 << 20;
110
111 if (!strncasecmp(unit, B_GBYTES_STR, strlen(B_GBYTES_STR)))
112 return 1 << 30;
113
114 std::cerr << "WARNING: Unknown bytes unit '" << unit << "'" << std::endl;
115
116 return 0;
117 }
118
119 /**
120 \ingroup ssl_crtd
121 * Parse uninterrapted string of bytes value. It looks like "4MB".
122 */
123 static bool parseBytesOptionValue(size_t * bptr, char const * value)
124 {
125 // Find number from string beginning.
126 char const * number_begin = value;
127 char const * number_end = value;
128
129 while ((*number_end >= '0' && *number_end <= '9')) {
130 ++number_end;
131 }
132
133 std::string number(number_begin, number_end - number_begin);
134 std::istringstream in(number);
135 int d = 0;
136 if (!(in >> d))
137 return false;
138
139 int m;
140 if ((m = parseBytesUnits(number_end)) == 0) {
141 return false;
142 }
143
144 *bptr = static_cast<size_t>(m * d);
145 if (static_cast<long>(*bptr * 2) != m * d * 2)
146 return false;
147
148 return true;
149 }
150
151 /**
152 \ingroup ssl_crtd
153 * Print help using response code.
154 */
155 static void usage()
156 {
157 std::string example_host_name = "host.dom";
158 std::string request_string = Ssl::CrtdMessage::param_host + "=" + example_host_name;
159 std::stringstream request_string_size_stream;
160 request_string_size_stream << request_string.length();
161 std::string help_string =
162 "usage: ssl_crtd -hv -s ssl_storage_path -M storage_max_size\n"
163 "\t-h Help\n"
164 "\t-v Version\n"
165 "\t-s ssl_storage_path Path to specific disk storage of ssl server\n"
166 "\t certificates.\n"
167 "\t-M storage_max_size max size of ssl certificates storage.\n"
168 "\t-b fs_block_size File system block size in bytes. Need for processing\n"
169 "\t natural size of certificate on disk. Default value is\n"
170 "\t 2048 bytes.\n"
171 "\n"
172 "After running write requests in the next format:\n"
173 "<request code><whitespace><body_len><whitespace><body>\n"
174 "There are two kind of request now:\n"
175 + Ssl::CrtdMessage::code_new_certificate + " " + request_string_size_stream.str() + " " + request_string + "\n" +
176 "\tCreate new private key and selfsigned certificate for \"host.dom\".\n"
177 + Ssl::CrtdMessage::code_new_certificate + " xxx " + request_string + "\n" +
178 "-----BEGIN CERTIFICATE-----\n"
179 "...\n"
180 "-----END CERTIFICATE-----\n"
181 "-----BEGIN RSA PRIVATE KEY-----\n"
182 "...\n"
183 "-----END RSA PRIVATE KEY-----\n"
184 "\tCreate new private key and certificate request for \"host.dom\"\n"
185 "\tSign new request by received certificate and private key.\n"
186 "usage: ssl_crtd -c -s ssl_store_path\n"
187 "\t-c Init ssl db directories and exit.\n";
188 std::cerr << help_string << std::endl;
189 }
190
191 /**
192 \ingroup ssl_crtd
193 * Proccess new request message.
194 */
195 static bool proccessNewRequest(Ssl::CrtdMessage & request_message, std::string const & db_path, size_t max_db_size, size_t fs_block_size)
196 {
197 Ssl::CertificateProperties certProperties;
198 std::string error;
199 if (!request_message.parseRequest(certProperties, error))
200 throw std::runtime_error("Error while parsing the crtd request: " + error);
201
202 Ssl::CertificateDb db(db_path, max_db_size, fs_block_size);
203
204 Security::CertPointer cert;
205 Ssl::EVP_PKEY_Pointer pkey;
206 std::string &cert_subject = certProperties.dbKey();
207
208 bool dbFailed = false;
209 try {
210 db.find(cert_subject, cert, pkey);
211 } catch (std::runtime_error &err) {
212 dbFailed = true;
213 error = err.what();
214 }
215
216 if (cert.get()) {
217 if (!Ssl::certificateMatchesProperties(cert.get(), certProperties)) {
218 // The certificate changed (renewed or other reason).
219 // Generete a new one with the updated fields.
220 cert.reset(NULL);
221 pkey.reset(NULL);
222 db.purgeCert(cert_subject);
223 }
224 }
225
226 if (!cert || !pkey) {
227 if (!Ssl::generateSslCertificate(cert, pkey, certProperties))
228 throw std::runtime_error("Cannot create ssl certificate or private key.");
229
230 if (!dbFailed && db.IsEnabledDiskStore()) {
231 try {
232 if (!db.addCertAndPrivateKey(cert, pkey, cert_subject)) {
233 dbFailed = true;
234 error = "Cannot add certificate to db.";
235 }
236 } catch (const std::runtime_error &err) {
237 dbFailed = true;
238 error = err.what();
239 }
240 }
241 }
242
243 if (dbFailed)
244 std::cerr << "ssl_crtd helper database '" << db_path << "' failed: " << error << std::endl;
245
246 std::string bufferToWrite;
247 if (!Ssl::writeCertAndPrivateKeyToMemory(cert, pkey, bufferToWrite))
248 throw std::runtime_error("Cannot write ssl certificate or/and private key to memory.");
249
250 Ssl::CrtdMessage response_message(Ssl::CrtdMessage::REPLY);
251 response_message.setCode("OK");
252 response_message.setBody(bufferToWrite);
253
254 // Use the '\1' char as end-of-message character
255 std::cout << response_message.compose() << '\1' << std::flush;
256
257 return true;
258 }
259
260 /**
261 \ingroup ssl_crtd
262 * This is the external ssl_crtd process.
263 */
264 int main(int argc, char *argv[])
265 {
266 try {
267 size_t max_db_size = 0;
268 size_t fs_block_size = 2048;
269 int8_t c;
270 bool create_new_db = false;
271 std::string db_path;
272 // proccess options.
273 while ((c = getopt(argc, argv, "dcghvs:M:b:n:")) != -1) {
274 switch (c) {
275 case 'd':
276 debug_enabled = 1;
277 break;
278 case 'b':
279 if (!parseBytesOptionValue(&fs_block_size, optarg)) {
280 throw std::runtime_error("Error when parsing -b options value");
281 }
282 break;
283 case 's':
284 db_path = optarg;
285 break;
286 case 'M':
287 if (!parseBytesOptionValue(&max_db_size, optarg)) {
288 throw std::runtime_error("Error when parsing -M options value");
289 }
290 break;
291 case 'v':
292 std::cout << "ssl_crtd version " << VERSION << std::endl;
293 exit(0);
294 break;
295 case 'c':
296 create_new_db = true;
297 break;
298 case 'h':
299 usage();
300 exit(0);
301 default:
302 exit(0);
303 }
304 }
305
306 if (create_new_db) {
307 std::cout << "Initialization SSL db..." << std::endl;
308 Ssl::CertificateDb::create(db_path);
309 std::cout << "Done" << std::endl;
310 exit(0);
311 }
312
313 {
314 Ssl::CertificateDb::check(db_path, max_db_size, fs_block_size);
315 }
316 // Initialize SSL subsystem
317 SSL_load_error_strings();
318 SSLeay_add_ssl_algorithms();
319 // proccess request.
320 for (;;) {
321 char request[HELPER_INPUT_BUFFER];
322 Ssl::CrtdMessage request_message(Ssl::CrtdMessage::REQUEST);
323 Ssl::CrtdMessage::ParseResult parse_result = Ssl::CrtdMessage::INCOMPLETE;
324
325 while (parse_result == Ssl::CrtdMessage::INCOMPLETE) {
326 if (fgets(request, HELPER_INPUT_BUFFER, stdin) == NULL)
327 return 1;
328 size_t gcount = strlen(request);
329 parse_result = request_message.parse(request, gcount);
330 }
331
332 if (parse_result == Ssl::CrtdMessage::ERROR) {
333 throw std::runtime_error("Cannot parse request message.");
334 } else if (request_message.getCode() == Ssl::CrtdMessage::code_new_certificate) {
335 proccessNewRequest(request_message, db_path, max_db_size, fs_block_size);
336 } else {
337 throw std::runtime_error("Unknown request code: \"" + request_message.getCode() + "\".");
338 }
339 std::cout.flush();
340 }
341 } catch (std::runtime_error & error) {
342 std::cerr << argv[0] << ": " << error.what() << std::endl;
343 return 0;
344 }
345 return 0;
346 }
347