]> git.ipfire.org Git - thirdparty/squid.git/blob - src/ssl/ssl_crtd.cc
Certificate mimicking
[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 #define CERT_BEGIN_STR "-----BEGIN CERTIFICATE"
87 static const char *const B_KBYTES_STR = "KB";
88 static const char *const B_MBYTES_STR = "MB";
89 static const char *const B_GBYTES_STR = "GB";
90 static const char *const B_BYTES_STR = "B";
91
92 /**
93 \ingroup ssl_crtd
94 * Get current time.
95 */
96 time_t getCurrentTime(void)
97 {
98 struct timeval current_time;
99 #if GETTIMEOFDAY_NO_TZP
100 gettimeofday(&current_time);
101 #else
102 gettimeofday(&current_time, NULL);
103 #endif
104 return current_time.tv_sec;
105 }
106
107 /**
108 \ingroup ssl_crtd
109 * Parse bytes unit. It would be one of the next value: MB, GB, KB or B.
110 * This function is caseinsensitive.
111 */
112 static size_t parseBytesUnits(const char * unit)
113 {
114 if (!strncasecmp(unit, B_BYTES_STR, strlen(B_BYTES_STR)) ||
115 !strncasecmp(unit, "", strlen(unit)))
116 return 1;
117
118 if (!strncasecmp(unit, B_KBYTES_STR, strlen(B_KBYTES_STR)))
119 return 1 << 10;
120
121 if (!strncasecmp(unit, B_MBYTES_STR, strlen(B_MBYTES_STR)))
122 return 1 << 20;
123
124 if (!strncasecmp(unit, B_GBYTES_STR, strlen(B_GBYTES_STR)))
125 return 1 << 30;
126
127 std::cerr << "WARNING: Unknown bytes unit '" << unit << "'" << std::endl;
128
129 return 0;
130 }
131
132 /**
133 \ingroup ssl_crtd
134 * Parse uninterrapted string of bytes value. It looks like "4MB".
135 */
136 static bool parseBytesOptionValue(size_t * bptr, char const * value)
137 {
138 // Find number from string beginning.
139 char const * number_begin = value;
140 char const * number_end = value;
141
142 while ((*number_end >= '0' && *number_end <= '9')) {
143 number_end++;
144 }
145
146 std::string number(number_begin, number_end - number_begin);
147 std::istringstream in(number);
148 int d = 0;
149 if (!(in >> d))
150 return false;
151
152 int m;
153 if ((m = parseBytesUnits(number_end)) == 0) {
154 return false;
155 }
156
157 *bptr = static_cast<size_t>(m * d);
158 if (static_cast<long>(*bptr * 2) != m * d * 2)
159 return false;
160
161 return true;
162 }
163
164 /**
165 \ingroup ssl_crtd
166 * Print help using response code.
167 */
168 static void usage()
169 {
170 std::string example_host_name = "host.dom";
171 std::string request_string = Ssl::CrtdMessage::param_host + "=" + example_host_name;
172 std::stringstream request_string_size_stream;
173 request_string_size_stream << request_string.length();
174 std::string help_string =
175 "usage: ssl_crtd -hv -s ssl_storage_path -M storage_max_size\n"
176 "\t-h Help\n"
177 "\t-v Version\n"
178 "\t-s ssl_storage_path Path to specific disk storage of ssl server\n"
179 "\t certificates.\n"
180 "\t-M storage_max_size max size of ssl certificates storage.\n"
181 "\t-b fs_block_size File system block size in bytes. Need for processing\n"
182 "\t natural size of certificate on disk. Default value is\n"
183 "\t 2048 bytes.\n"
184 "\n"
185 "After running write requests in the next format:\n"
186 "<request code><whitespace><body_len><whitespace><body>\n"
187 "There are two kind of request now:\n"
188 + Ssl::CrtdMessage::code_new_certificate + " " + request_string_size_stream.str() + " " + request_string + "\n" +
189 "\tCreate new private key and selfsigned certificate for \"host.dom\".\n"
190 + Ssl::CrtdMessage::code_new_certificate + " xxx " + request_string + "\n" +
191 "-----BEGIN CERTIFICATE-----\n"
192 "...\n"
193 "-----END CERTIFICATE-----\n"
194 "-----BEGIN RSA PRIVATE KEY-----\n"
195 "...\n"
196 "-----END RSA PRIVATE KEY-----\n"
197 "\tCreate new private key and certificate request for \"host.dom\"\n"
198 "\tSign new request by received certificate and private key.\n"
199 "usage: ssl_crtd -c -s ssl_store_path -n new_serial_number\n"
200 "\t-c Init ssl db directories and exit.\n"
201 "\t-n new_serial_number HEX serial number to use when initializing db.\n"
202 "\t The default value of serial number is\n"
203 "\t the number of seconds since Epoch minus 1200000000\n"
204 "usage: ssl_crtd -g -s ssl_store_path\n"
205 "\t-g Show current serial number and exit.";
206 std::cerr << help_string << std::endl;
207 }
208
209 /**
210 \ingroup ssl_crtd
211 * Proccess new request message.
212 */
213 static bool proccessNewRequest(Ssl::CrtdMessage const & request_message, std::string const & db_path, size_t max_db_size, size_t fs_block_size)
214 {
215 Ssl::CrtdMessage::BodyParams map;
216 std::string body_part;
217 request_message.parseBody(map, body_part);
218
219 Ssl::CrtdMessage::BodyParams::iterator i = map.find(Ssl::CrtdMessage::param_host);
220 if (i == map.end())
221 throw std::runtime_error("Cannot find \"" + Ssl::CrtdMessage::param_host + "\" parameter in request message.");
222 std::string host = i->second;
223
224 Ssl::CertificateDb db(db_path, max_db_size, fs_block_size);
225
226 Ssl::X509_Pointer cert;
227 Ssl::EVP_PKEY_Pointer pkey;
228 Ssl::X509_Pointer certToMimic;
229
230 const char *s;
231 std::string cert_subject;
232 if ((s = strstr(body_part.c_str(), CERT_BEGIN_STR))) {
233 s += strlen(CERT_BEGIN_STR);
234 if ((s = strstr(s, CERT_BEGIN_STR))) {
235 Ssl::readCertFromMemory(certToMimic, s);
236 if (certToMimic.get()) {
237 char buf[1024];
238 cert_subject = X509_NAME_oneline(X509_get_subject_name(certToMimic.get()), buf, sizeof(buf));
239 }
240 }
241 }
242
243 if (cert_subject.empty())
244 cert_subject = "/CN=" + host;
245
246 db.find(cert_subject, cert, pkey);
247
248 if (!cert || !pkey) {
249 Ssl::X509_Pointer certToSign;
250 Ssl::EVP_PKEY_Pointer pkeyToSign;
251 Ssl::readCertAndPrivateKeyFromMemory(certToSign, pkeyToSign, body_part.c_str());
252
253 Ssl::BIGNUM_Pointer serial(db.getCurrentSerialNumber());
254
255 if (certToMimic.get()) {
256 Ssl::generateSslCertificate(certToMimic, certToSign, pkeyToSign, cert, pkey, serial.get());
257 }
258 else
259 if (!Ssl::generateSslCertificateAndPrivateKey(host.c_str(), certToSign, pkeyToSign, cert, pkey, serial.get()))
260 throw std::runtime_error("Cannot create ssl certificate or private key.");
261
262 if (!db.addCertAndPrivateKey(cert, pkey) && db.IsEnabledDiskStore())
263 throw std::runtime_error("Cannot add certificate to db.");
264 }
265
266 std::string bufferToWrite;
267 if (!Ssl::writeCertAndPrivateKeyToMemory(cert, pkey, bufferToWrite))
268 throw std::runtime_error("Cannot write ssl certificate or/and private key to memory.");
269
270 Ssl::CrtdMessage response_message;
271 response_message.setCode("OK");
272 response_message.setBody(bufferToWrite);
273
274 // Use the '\1' char as end-of-message character
275 std::cout << response_message.compose() << '\1' << std::flush;
276
277 return true;
278 }
279
280 /**
281 \ingroup ssl_crtd
282 * This is the external ssl_crtd process.
283 */
284 int main(int argc, char *argv[])
285 {
286 try {
287 int serial = (getCurrentTime() - 1200000000);
288 size_t max_db_size = 0;
289 size_t fs_block_size = 2048;
290 char c;
291 bool create_new_db = false;
292 bool show_sn = false;
293 std::string db_path;
294 // proccess options.
295 while ((c = getopt(argc, argv, "dcghvs:M:b:n:")) != -1) {
296 switch (c) {
297 case 'd':
298 debug_enabled = 1;
299 break;
300 case 'b':
301 if (!parseBytesOptionValue(&fs_block_size, optarg)) {
302 throw std::runtime_error("Error when parsing -b options value");
303 }
304 break;
305 case 's':
306 db_path = optarg;
307 break;
308 case 'n': {
309 std::stringstream sn_stream(optarg);
310 sn_stream >> std::hex >> serial;
311 break;
312 }
313 case 'M':
314 if (!parseBytesOptionValue(&max_db_size, optarg)) {
315 throw std::runtime_error("Error when parsing -M options value");
316 }
317 break;
318 case 'v':
319 std::cout << "ssl_crtd version " << VERSION << std::endl;
320 exit(0);
321 break;
322 case 'c':
323 create_new_db = true;
324 break;
325 case 'g':
326 show_sn = true;
327 break;
328 case 'h':
329 usage();
330 exit(0);
331 default:
332 exit(0);
333 }
334 }
335
336 if (create_new_db) {
337 std::cout << "Initialization SSL db..." << std::endl;
338 Ssl::CertificateDb::create(db_path, serial);
339 std::cout << "Done" << std::endl;
340 exit(0);
341 }
342
343 if (show_sn) {
344 Ssl::CertificateDb db(db_path, 4096, 0);
345 std::cout << db.getSNString() << std::endl;
346 exit(0);
347 }
348 {
349 Ssl::CertificateDb::check(db_path, max_db_size);
350 }
351 // proccess request.
352 for (;;) {
353 char request[HELPER_INPUT_BUFFER];
354 Ssl::CrtdMessage request_message;
355 Ssl::CrtdMessage::ParseResult parse_result = Ssl::CrtdMessage::INCOMPLETE;
356
357 while (parse_result == Ssl::CrtdMessage::INCOMPLETE) {
358 if (fgets(request, HELPER_INPUT_BUFFER, stdin) == NULL)
359 return 1;
360 size_t gcount = strlen(request);
361 parse_result = request_message.parse(request, gcount);
362 }
363
364 if (parse_result == Ssl::CrtdMessage::ERROR) {
365 throw std::runtime_error("Cannot parse request message.");
366 } else if (request_message.getCode() == Ssl::CrtdMessage::code_new_certificate) {
367 proccessNewRequest(request_message, db_path, max_db_size, fs_block_size);
368 } else {
369 throw std::runtime_error("Unknown request code: \"" + request_message.getCode() + "\".");
370 }
371 std::cout.flush();
372 }
373 } catch (std::runtime_error & error) {
374 std::cerr << argv[0] << ": " << error.what() << std::endl;
375 return 0;
376 }
377 return 0;
378 }