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