]> git.ipfire.org Git - thirdparty/squid.git/blame - src/security/cert_generators/file/security_file_certgen.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / security / cert_generators / file / security_file_certgen.cc
CommitLineData
bbc27441 1/*
4ac4a490 2 * Copyright (C) 1996-2017 The Squid Software Foundation and contributors
bbc27441
AJ
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
f7f3304a 9#include "squid.h"
079b1d0f 10#include "helper/protocol_defines.h"
cb0b3d63 11#include "security/cert_generators/file/certificate_db.h"
602d9612 12#include "ssl/crtd_message.h"
95d2589c 13
95d2589c 14#include <cstring>
95d2589c 15#include <iostream>
074d6a40 16#include <sstream>
95d2589c 17#include <stdexcept>
95d2589c 18#include <string>
95d2589c
CT
19#if HAVE_GETOPT_H
20#include <getopt.h>
21#endif
22
23/**
051da40c 24 \defgroup ssl_crtd security_file_certgen
95d2589c
CT
25 \ingroup ExternalPrograms
26 \par
051da40c 27 Because the standard generation of SSL certificates for
ba5d55c1 28 sslBump feature, Squid must use external process to
95d2589c
CT
29 actually make these calls. This process generate new ssl
30 certificates and worked with ssl certificates disk cache.
051da40c
AJ
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
95d2589c
CT
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
051da40c 42usage: security_file_certgen -hv -s ssl_storage_path -M storage_max_size
95d2589c
CT
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
051da40c 68usage: security_file_certgen -c -s ssl_store_path\n
95d2589c 69 -c Init ssl db directories and exit.
95d2589c 70
95d2589c
CT
71 \endverbatim
72 */
73
74static const char *const B_KBYTES_STR = "KB";
75static const char *const B_MBYTES_STR = "MB";
76static const char *const B_GBYTES_STR = "GB";
77static const char *const B_BYTES_STR = "B";
78
051da40c 79/// Get current time.
95d2589c
CT
80time_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/**
95d2589c
CT
92 * Parse bytes unit. It would be one of the next value: MB, GB, KB or B.
93 * This function is caseinsensitive.
94 */
95static 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
30c48b1a
AJ
110 std::cerr << "WARNING: Unknown bytes unit '" << unit << "'" << std::endl;
111
95d2589c
CT
112 return 0;
113}
114
051da40c 115/// Parse uninterrapted string of bytes value. It looks like "4MB".
95d2589c
CT
116static 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')) {
d7ae3534 123 ++number_end;
95d2589c
CT
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
051da40c 144/// Print help using response code.
95d2589c
CT
145static 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 =
051da40c 152 "usage: security_file_certgen -hv -s ssl_storage_path -M storage_max_size\n"
95d2589c
CT
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"
051da40c 176 "usage: security_file_certgen -c -s ssl_store_path\n"
a0b971d5 177 "\t-c Init ssl db directories and exit.\n";
95d2589c
CT
178 std::cerr << help_string << std::endl;
179}
180
ba5d55c1
NH
181/// Process new request message.
182static bool processNewRequest(Ssl::CrtdMessage & request_message, std::string const & db_path, size_t max_db_size, size_t fs_block_size)
95d2589c 183{
aebe6888 184 Ssl::CertificateProperties certProperties;
06997a38
CT
185 std::string error;
186 if (!request_message.parseRequest(certProperties, error))
ce394ae1 187 throw std::runtime_error("Error while parsing the crtd request: " + error);
95d2589c
CT
188
189 Ssl::CertificateDb db(db_path, max_db_size, fs_block_size);
190
f97700a0 191 Security::CertPointer cert;
95d2589c 192 Ssl::EVP_PKEY_Pointer pkey;
06997a38 193 std::string &cert_subject = certProperties.dbKey();
95d2589c 194
541e6ab5
CT
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 }
95d2589c 202
754f901e 203 if (cert) {
4ece76b2 204 if (!Ssl::certificateMatchesProperties(cert.get(), certProperties)) {
e7bcc25f
CT
205 // The certificate changed (renewed or other reason).
206 // Generete a new one with the updated fields.
1ca8bbfb
AJ
207 cert.reset();
208 pkey.reset();
4ece76b2 209 db.purgeCert(cert_subject);
e7bcc25f
CT
210 }
211 }
95d2589c 212
87f237a9 213 if (!cert || !pkey) {
aebe6888 214 if (!Ssl::generateSslCertificate(cert, pkey, certProperties))
95d2589c 215 throw std::runtime_error("Cannot create ssl certificate or private key.");
9a90aace 216
541e6ab5
CT
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 }
95d2589c
CT
228 }
229
541e6ab5 230 if (dbFailed)
051da40c 231 std::cerr << "security_file_certgen helper database '" << db_path << "' failed: " << error << std::endl;
541e6ab5 232
95d2589c
CT
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
ff2d7d92 237 Ssl::CrtdMessage response_message(Ssl::CrtdMessage::REPLY);
419ac015 238 response_message.setCode("OK");
95d2589c
CT
239 response_message.setBody(bufferToWrite);
240
0af9303a
CT
241 // Use the '\1' char as end-of-message character
242 std::cout << response_message.compose() << '\1' << std::flush;
95d2589c
CT
243
244 return true;
245}
246
051da40c 247/// This is the external security_file_certgen process.
95d2589c
CT
248int main(int argc, char *argv[])
249{
250 try {
95d2589c 251 size_t max_db_size = 0;
ba5d55c1 252 size_t fs_block_size = 0;
c3373ae4 253 int8_t c;
95d2589c 254 bool create_new_db = false;
95d2589c 255 std::string db_path;
ba5d55c1 256 // process options.
95d2589c
CT
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;
95d2589c
CT
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':
051da40c 276 std::cout << "security_file_certgen version " << VERSION << std::endl;
95d2589c
CT
277 exit(0);
278 break;
279 case 'c':
280 create_new_db = true;
281 break;
95d2589c
CT
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;
a0b971d5 292 Ssl::CertificateDb::create(db_path);
95d2589c
CT
293 std::cout << "Done" << std::endl;
294 exit(0);
295 }
296
ba5d55c1
NH
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
95d2589c 310 {
a066059b 311 Ssl::CertificateDb::check(db_path, max_db_size, fs_block_size);
95d2589c 312 }
3c26b00a
CT
313 // Initialize SSL subsystem
314 SSL_load_error_strings();
315 SSLeay_add_ssl_algorithms();
ba5d55c1 316 // process request.
95d2589c
CT
317 for (;;) {
318 char request[HELPER_INPUT_BUFFER];
ff2d7d92 319 Ssl::CrtdMessage request_message(Ssl::CrtdMessage::REQUEST);
95d2589c
CT
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) {
ba5d55c1 332 processNewRequest(request_message, db_path, max_db_size, fs_block_size);
95d2589c
CT
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}
f53969cc 344