]> git.ipfire.org Git - thirdparty/squid.git/blob - src/ssl/ssl_crtd.cc
Update ssl_crtd to use 'OK' status inline with other helpers
[thirdparty/squid.git] / src / ssl / ssl_crtd.cc
1 /*
2 * $Id$
3 */
4
5 #include "config.h"
6 #include "helpers/defines.h"
7 #include "ssl/gadgets.h"
8 #include "ssl/crtd_message.h"
9 #include "ssl/certificate_db.h"
10
11 #if HAVE_CSTRING
12 #include <cstring>
13 #endif
14 #if HAVE_SSTREAM
15 #include <sstream>
16 #endif
17 #if HAVE_IOSTREAM
18 #include <iostream>
19 #endif
20 #if HAVE_STDEXCEPT
21 #include <stdexcept>
22 #endif
23 #if HAVE_STRING
24 #include <string>
25 #endif
26 #if HAVE_GETOPT_H
27 #include <getopt.h>
28 #endif
29
30 /**
31 \defgroup ssl_crtd ssl_crtd
32 \ingroup ExternalPrograms
33 \par
34 Because the standart generation of ssl certificate for
35 sslBump feature, Squid must use external proccess to
36 actually make these calls. This process generate new ssl
37 certificates and worked with ssl certificates disk cache.
38 Typically there will be five ssl_crtd processes spawned
39 from Squid. Communication occurs via TCP sockets bound
40 to the loopback interface. The class in helper.h are
41 primally concerned with starting and stopping the ssl_crtd.
42 Reading and writing to and from the ssl_crtd occurs in the
43 \link IPCacheAPI IP\endlink and the dnsservers occurs in
44 the \link IPCacheAPI IP\endlink and \link FQDNCacheAPI
45 FQDN\endlink cache modules.
46
47 \section ssl_crtdInterface Command Line Interface
48 \verbatim
49 usage: ssl_crtd -hv -s ssl_storage_path -M storage_max_size
50 -h Help
51 -v Version
52 -s ssl_storage_path Path to specific disk storage of ssl server
53 certificates.
54 -M storage_max_size max size of ssl certificates storage.
55 -b fs_block_size File system block size in bytes. Need for processing
56 natural size of certificate on disk. Default value is
57 2048 bytes."
58
59 After running write requests in the next format:
60 <request code><whitespace><body_len><whitespace><body>
61 There are two kind of request now:
62 new_certificate 14 host=host.dom
63 Create new private key and selfsigned certificate for "host.dom".
64
65 new_certificate xxx host=host.dom
66 -----BEGIN CERTIFICATE-----
67 ...
68 -----END CERTIFICATE-----
69 -----BEGIN RSA PRIVATE KEY-----
70 ...
71 -----END RSA PRIVATE KEY-----
72 Create new private key and certificate request for "host.dom".
73 Sign new request by received certificate and private key.
74
75 usage: ssl_crtd -c -s ssl_store_path\n -n new_serial_number
76 -c Init ssl db directories and exit.
77 -n new_serial_number HEX serial number to use when initializing db.
78 The default value of serial number is
79 the number of seconds since Epoch minus 1200000000
80
81 usage: ssl_crtd -g -s ssl_store_path
82 -g Show current serial number and exit.
83 \endverbatim
84 */
85
86 static const char *const B_KBYTES_STR = "KB";
87 static const char *const B_MBYTES_STR = "MB";
88 static const char *const B_GBYTES_STR = "GB";
89 static const char *const B_BYTES_STR = "B";
90
91 /**
92 \ingroup ssl_crtd
93 * Get current time.
94 */
95 time_t getCurrentTime(void)
96 {
97 struct timeval current_time;
98 #if GETTIMEOFDAY_NO_TZP
99 gettimeofday(&current_time);
100 #else
101 gettimeofday(&current_time, NULL);
102 #endif
103 return current_time.tv_sec;
104 }
105
106 /**
107 \ingroup ssl_crtd
108 * Parse bytes unit. It would be one of the next value: MB, GB, KB or B.
109 * This function is caseinsensitive.
110 */
111 static size_t parseBytesUnits(const char * unit)
112 {
113 if (!strncasecmp(unit, B_BYTES_STR, strlen(B_BYTES_STR)) ||
114 !strncasecmp(unit, "", strlen(unit)))
115 return 1;
116
117 if (!strncasecmp(unit, B_KBYTES_STR, strlen(B_KBYTES_STR)))
118 return 1 << 10;
119
120 if (!strncasecmp(unit, B_MBYTES_STR, strlen(B_MBYTES_STR)))
121 return 1 << 20;
122
123 if (!strncasecmp(unit, B_GBYTES_STR, strlen(B_GBYTES_STR)))
124 return 1 << 30;
125
126 return 0;
127 }
128
129 /**
130 \ingroup ssl_crtd
131 * Parse uninterrapted string of bytes value. It looks like "4MB".
132 */
133 static bool parseBytesOptionValue(size_t * bptr, char const * value)
134 {
135 // Find number from string beginning.
136 char const * number_begin = value;
137 char const * number_end = value;
138
139 while ((*number_end >= '0' && *number_end <= '9')) {
140 number_end++;
141 }
142
143 std::string number(number_begin, number_end - number_begin);
144 std::istringstream in(number);
145 int d = 0;
146 if (!(in >> d))
147 return false;
148
149 int m;
150 if ((m = parseBytesUnits(number_end)) == 0) {
151 return false;
152 }
153
154 *bptr = static_cast<size_t>(m * d);
155 if (static_cast<long>(*bptr * 2) != m * d * 2)
156 return false;
157
158 return true;
159 }
160
161 /**
162 \ingroup ssl_crtd
163 * Print help using response code.
164 */
165 static void usage()
166 {
167 std::string example_host_name = "host.dom";
168 std::string request_string = Ssl::CrtdMessage::param_host + "=" + example_host_name;
169 std::stringstream request_string_size_stream;
170 request_string_size_stream << request_string.length();
171 std::string help_string =
172 "usage: ssl_crtd -hv -s ssl_storage_path -M storage_max_size\n"
173 "\t-h Help\n"
174 "\t-v Version\n"
175 "\t-s ssl_storage_path Path to specific disk storage of ssl server\n"
176 "\t certificates.\n"
177 "\t-M storage_max_size max size of ssl certificates storage.\n"
178 "\t-b fs_block_size File system block size in bytes. Need for processing\n"
179 "\t natural size of certificate on disk. Default value is\n"
180 "\t 2048 bytes.\n"
181 "\n"
182 "After running write requests in the next format:\n"
183 "<request code><whitespace><body_len><whitespace><body>\n"
184 "There are two kind of request now:\n"
185 + Ssl::CrtdMessage::code_new_certificate + " " + request_string_size_stream.str() + " " + request_string + "\n" +
186 "\tCreate new private key and selfsigned certificate for \"host.dom\".\n"
187 + Ssl::CrtdMessage::code_new_certificate + " xxx " + request_string + "\n" +
188 "-----BEGIN CERTIFICATE-----\n"
189 "...\n"
190 "-----END CERTIFICATE-----\n"
191 "-----BEGIN RSA PRIVATE KEY-----\n"
192 "...\n"
193 "-----END RSA PRIVATE KEY-----\n"
194 "\tCreate new private key and certificate request for \"host.dom\"\n"
195 "\tSign new request by received certificate and private key.\n"
196 "usage: ssl_crtd -c -s ssl_store_path -n new_serial_number\n"
197 "\t-c Init ssl db directories and exit.\n"
198 "\t-n new_serial_number HEX serial number to use when initializing db.\n"
199 "\t The default value of serial number is\n"
200 "\t the number of seconds since Epoch minus 1200000000\n"
201 "usage: ssl_crtd -g -s ssl_store_path\n"
202 "\t-g Show current serial number and exit.";
203 std::cerr << help_string << std::endl;
204 }
205
206 /**
207 \ingroup ssl_crtd
208 * Proccess new request message.
209 */
210 static bool proccessNewRequest(Ssl::CrtdMessage const & request_message, std::string const & db_path, size_t max_db_size, size_t fs_block_size)
211 {
212 Ssl::CrtdMessage::BodyParams map;
213 std::string body_part;
214 request_message.parseBody(map, body_part);
215
216 Ssl::CrtdMessage::BodyParams::iterator i = map.find(Ssl::CrtdMessage::param_host);
217 if (i == map.end())
218 throw std::runtime_error("Cannot find \"" + Ssl::CrtdMessage::param_host + "\" parameter in request message.");
219 std::string host = i->second;
220
221 Ssl::CertificateDb db(db_path, max_db_size, fs_block_size);
222
223 Ssl::X509_Pointer cert;
224 Ssl::EVP_PKEY_Pointer pkey;
225 db.find("/CN=" + host, cert, pkey);
226
227 if (!cert || !pkey) {
228 Ssl::X509_Pointer certToSign;
229 Ssl::EVP_PKEY_Pointer pkeyToSign;
230 Ssl::readCertAndPrivateKeyFromMemory(certToSign, pkeyToSign, body_part.c_str());
231
232 Ssl::BIGNUM_Pointer serial(db.getCurrentSerialNumber());
233
234 if (!Ssl::generateSslCertificateAndPrivateKey(host.c_str(), certToSign, pkeyToSign, cert, pkey, serial.get()))
235 throw std::runtime_error("Cannot create ssl certificate or private key.");
236 if (!db.addCertAndPrivateKey(cert, pkey) && db.IsEnabledDiskStore())
237 throw std::runtime_error("Cannot add certificate to db.");
238 }
239
240 std::string bufferToWrite;
241 if (!Ssl::writeCertAndPrivateKeyToMemory(cert, pkey, bufferToWrite))
242 throw std::runtime_error("Cannot write ssl certificate or/and private key to memory.");
243
244 Ssl::CrtdMessage response_message;
245 response_message.setCode("OK");
246 response_message.setBody(bufferToWrite);
247
248 // Use the '\1' char as end-of-message character
249 std::cout << response_message.compose() << '\1' << std::flush;
250
251 return true;
252 }
253
254 /**
255 \ingroup ssl_crtd
256 * This is the external ssl_crtd process.
257 */
258 int main(int argc, char *argv[])
259 {
260 try {
261 int serial = (getCurrentTime() - 1200000000);
262 size_t max_db_size = 0;
263 size_t fs_block_size = 2048;
264 char c;
265 bool create_new_db = false;
266 bool show_sn = false;
267 std::string db_path;
268 // proccess options.
269 while ((c = getopt(argc, argv, "dcghvs:M:b:n:")) != -1) {
270 switch (c) {
271 case 'd':
272 debug_enabled = 1;
273 break;
274 case 'b':
275 if (!parseBytesOptionValue(&fs_block_size, optarg)) {
276 throw std::runtime_error("Error when parsing -b options value");
277 }
278 break;
279 case 's':
280 db_path = optarg;
281 break;
282 case 'n': {
283 std::stringstream sn_stream(optarg);
284 sn_stream >> std::hex >> serial;
285 break;
286 }
287 case 'M':
288 if (!parseBytesOptionValue(&max_db_size, optarg)) {
289 throw std::runtime_error("Error when parsing -M options value");
290 }
291 break;
292 case 'v':
293 std::cout << "ssl_crtd version " << VERSION << std::endl;
294 exit(0);
295 break;
296 case 'c':
297 create_new_db = true;
298 break;
299 case 'g':
300 show_sn = true;
301 break;
302 case 'h':
303 usage();
304 exit(0);
305 default:
306 exit(0);
307 }
308 }
309
310 if (create_new_db) {
311 std::cout << "Initialization SSL db..." << std::endl;
312 Ssl::CertificateDb::create(db_path, serial);
313 std::cout << "Done" << std::endl;
314 exit(0);
315 }
316
317 if (show_sn) {
318 Ssl::CertificateDb db(db_path, 4096, 0);
319 std::cout << db.getSNString() << std::endl;
320 exit(0);
321 }
322 {
323 Ssl::CertificateDb::check(db_path, max_db_size);
324 }
325 // proccess request.
326 for (;;) {
327 char request[HELPER_INPUT_BUFFER];
328 Ssl::CrtdMessage request_message;
329 Ssl::CrtdMessage::ParseResult parse_result = Ssl::CrtdMessage::INCOMPLETE;
330
331 while (parse_result == Ssl::CrtdMessage::INCOMPLETE) {
332 if (fgets(request, HELPER_INPUT_BUFFER, stdin) == NULL)
333 return 1;
334 size_t gcount = strlen(request);
335 parse_result = request_message.parse(request, gcount);
336 }
337
338 if (parse_result == Ssl::CrtdMessage::ERROR) {
339 throw std::runtime_error("Cannot parse request message.");
340 } else if (request_message.getCode() == Ssl::CrtdMessage::code_new_certificate) {
341 proccessNewRequest(request_message, db_path, max_db_size, fs_block_size);
342 } else {
343 throw std::runtime_error("Unknown request code: \"" + request_message.getCode() + "\".");
344 }
345 std::cout.flush();
346 }
347 } catch (std::runtime_error & error) {
348 std::cerr << argv[0] << ": " << error.what() << std::endl;
349 return 0;
350 }
351 return 0;
352 }