2 * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
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.
10 #include "helper/protocol_defines.h"
11 #include "security/cert_generators/file/certificate_db.h"
12 #include "ssl/crtd_message.h"
24 \defgroup ssl_crtd security_file_certgen
25 \ingroup ExternalPrograms
27 Because the standard generation of SSL certificates for
28 sslBump feature, Squid must use external process 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 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
36 \link IPCacheAPI IP\endlink and the dnsservers occurs in
37 the \link IPCacheAPI IP\endlink and \link FQDNCacheAPI
38 FQDN\endlink cache modules.
40 \section ssl_crtdInterface Command Line Interface
42 usage: security_file_certgen -hv -s directory -M size -b fs_block_size
45 -s directory Directory path of SSL storage database.
46 -M size Maximum size of SSL certificate disk storage.
47 -b fs_block_size File system block size in bytes. Need for processing
48 natural size of certificate on disk. Default value is
51 After running write requests in the next format:
52 <request code><whitespace><body_len><whitespace><body>
53 There are two kind of request now:
54 new_certificate 14 host=host.dom
55 Create new private key and selfsigned certificate for "host.dom".
57 new_certificate xxx host=host.dom
58 -----BEGIN CERTIFICATE-----
60 -----END CERTIFICATE-----
61 -----BEGIN RSA PRIVATE KEY-----
63 -----END RSA PRIVATE KEY-----
64 Create new private key and certificate request for "host.dom".
65 Sign new request by received certificate and private key.
67 usage: security_file_certgen -c -s ssl_store_path\n
68 -c Init ssl db directories and exit.
73 static const char *const B_KBYTES_STR
= "KB";
74 static const char *const B_MBYTES_STR
= "MB";
75 static const char *const B_GBYTES_STR
= "GB";
76 static const char *const B_BYTES_STR
= "B";
79 time_t getCurrentTime(void)
81 struct timeval current_time
;
82 #if GETTIMEOFDAY_NO_TZP
83 gettimeofday(¤t_time
);
85 gettimeofday(¤t_time
, NULL
);
87 return current_time
.tv_sec
;
91 * Parse bytes unit. It would be one of the next value: MB, GB, KB or B.
92 * This function is caseinsensitive.
94 static size_t parseBytesUnits(const char * unit
)
96 if (!strncasecmp(unit
, B_BYTES_STR
, strlen(B_BYTES_STR
)) ||
97 !strncasecmp(unit
, "", strlen(unit
)))
100 if (!strncasecmp(unit
, B_KBYTES_STR
, strlen(B_KBYTES_STR
)))
103 if (!strncasecmp(unit
, B_MBYTES_STR
, strlen(B_MBYTES_STR
)))
106 if (!strncasecmp(unit
, B_GBYTES_STR
, strlen(B_GBYTES_STR
)))
109 std::cerr
<< "WARNING: Unknown bytes unit '" << unit
<< "'" << std::endl
;
114 /// Parse uninterrapted string of bytes value. It looks like "4MB".
115 static bool parseBytesOptionValue(size_t * bptr
, char const * value
)
117 // Find number from string beginning.
118 char const * number_begin
= value
;
119 char const * number_end
= value
;
121 while ((*number_end
>= '0' && *number_end
<= '9')) {
125 std::string
number(number_begin
, number_end
- number_begin
);
126 std::istringstream
in(number
);
132 if ((m
= parseBytesUnits(number_end
)) == 0) {
136 *bptr
= static_cast<size_t>(m
* d
);
137 if (static_cast<long>(*bptr
* 2) != m
* d
* 2)
143 /// Print help using response code.
146 std::string example_host_name
= "host.dom";
147 std::string request_string
= Ssl::CrtdMessage::param_host
+ "=" + example_host_name
;
148 std::stringstream request_string_size_stream
;
149 request_string_size_stream
<< request_string
.length();
150 std::string help_string
=
151 "usage: security_file_certgen -hv -s directory -M size -b fs_block_size\n"
154 "\t-s directory Directory path of SSL storage database.\n"
155 "\t-M size Maximum size of SSL certificate disk storage.\n"
156 "\t-b fs_block_size File system block size in bytes. Need for processing\n"
157 "\t natural size of certificate on disk. Default value is\n"
160 "After running write requests in the next format:\n"
161 "<request code><whitespace><body_len><whitespace><body>\n"
162 "There are two kind of request now:\n"
163 + Ssl::CrtdMessage::code_new_certificate
+ " " + request_string_size_stream
.str() + " " + request_string
+ "\n" +
164 "\tCreate new private key and selfsigned certificate for \"host.dom\".\n"
165 + Ssl::CrtdMessage::code_new_certificate
+ " xxx " + request_string
+ "\n" +
166 "-----BEGIN CERTIFICATE-----\n"
168 "-----END CERTIFICATE-----\n"
169 "-----BEGIN RSA PRIVATE KEY-----\n"
171 "-----END RSA PRIVATE KEY-----\n"
172 "\tCreate new private key and certificate request for \"host.dom\"\n"
173 "\tSign new request by received certificate and private key.\n"
174 "usage: security_file_certgen -c -s ssl_store_path\n"
175 "\t-c Init ssl db directories and exit.\n";
176 std::cerr
<< help_string
<< std::endl
;
179 /// Process new request message.
180 static bool processNewRequest(Ssl::CrtdMessage
& request_message
, std::string
const & db_path
, size_t max_db_size
, size_t fs_block_size
)
182 Ssl::CertificateProperties certProperties
;
184 if (!request_message
.parseRequest(certProperties
, error
))
185 throw std::runtime_error("Error while parsing the crtd request: " + error
);
187 // TODO: create a DB object only once, instead re-allocating here on every call.
188 std::unique_ptr
<Ssl::CertificateDb
> db
;
189 if (!db_path
.empty())
190 db
.reset(new Ssl::CertificateDb(db_path
, max_db_size
, fs_block_size
));
192 Security::CertPointer cert
;
193 Security::PrivateKeyPointer pkey
;
194 Security::CertPointer orig
;
195 std::string
&certKey
= Ssl::OnDiskCertificateDbKey(certProperties
);
197 bool dbFailed
= false;
200 db
->find(certKey
, certProperties
.mimicCert
, cert
, pkey
);
202 } catch (std::runtime_error
&err
) {
207 if (!cert
|| !pkey
) {
208 if (!Ssl::generateSslCertificate(cert
, pkey
, certProperties
))
209 throw std::runtime_error("Cannot create ssl certificate or private key.");
212 /* XXX: this !dbFailed condition prevents the helper fixing DB issues
213 by adding cleanly generated certs. Which is not consistent with other
214 data caches used by Squid - they purge broken entries and allow clean
215 entries to later try and fix the issue.
216 We leave it in place now only to avoid breaking existing installations
217 behaviour with version 1.x of the helper.
219 TODO: remove the !dbFailed condition when fixing the CertificateDb
220 object lifecycle and formally altering the helper behaviour.
222 if (!dbFailed
&& db
&& !db
->addCertAndPrivateKey(certKey
, cert
, pkey
, certProperties
.mimicCert
))
223 throw std::runtime_error("Cannot add certificate to db.");
225 } catch (const std::runtime_error
&err
) {
232 std::cerr
<< "security_file_certgen helper database '" << db_path
<< "' failed: " << error
<< std::endl
;
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.");
238 Ssl::CrtdMessage
response_message(Ssl::CrtdMessage::REPLY
);
239 response_message
.setCode("OK");
240 response_message
.setBody(bufferToWrite
);
242 // Use the '\1' char as end-of-message character
243 std::cout
<< response_message
.compose() << '\1' << std::flush
;
248 /// This is the external security_file_certgen process.
249 int main(int argc
, char *argv
[])
252 size_t max_db_size
= 0;
253 size_t fs_block_size
= 0;
255 bool create_new_db
= false;
258 while ((c
= getopt(argc
, argv
, "dchvs:M:b:")) != -1) {
264 if (!parseBytesOptionValue(&fs_block_size
, optarg
)) {
265 throw std::runtime_error("Error when parsing -b options value");
272 // use of -M without -s is probably an admin mistake, so make it an error
273 if (db_path
.empty()) {
274 throw std::runtime_error("Error -M option requires an -s parameter be set first.");
276 if (!parseBytesOptionValue(&max_db_size
, optarg
)) {
277 throw std::runtime_error("Error when parsing -M options value");
281 std::cout
<< "security_file_certgen version " << VERSION
<< std::endl
;
285 create_new_db
= true;
295 // when -s is used, -M is required
296 if (!db_path
.empty() && max_db_size
== 0)
297 throw std::runtime_error("security_file_certgen -s requires an -M parameter");
300 // when -c is used, -s is required (implying also -M, which is checked above)
302 throw std::runtime_error("security_file_certgen is missing the required parameter. There should be -s and -M parameters when -c is used.");
304 std::cout
<< "Initialization SSL db..." << std::endl
;
305 Ssl::CertificateDb::Create(db_path
);
306 std::cout
<< "Done" << std::endl
;
310 // only do filesystem checks when a path (-s) is given
311 if (!db_path
.empty()) {
312 if (fs_block_size
== 0) {
315 if (xstatvfs(db_path
.c_str(), &sfs
)) {
316 fs_block_size
= 2048;
318 fs_block_size
= sfs
.f_frsize
;
319 // Sanity check; make sure we have a meaningful value.
320 if (fs_block_size
< 512)
321 fs_block_size
= 2048;
324 Ssl::CertificateDb::Check(db_path
, max_db_size
, fs_block_size
);
327 // Initialize SSL subsystem
328 SQUID_OPENSSL_init_ssl();
331 char request
[HELPER_INPUT_BUFFER
];
332 Ssl::CrtdMessage
request_message(Ssl::CrtdMessage::REQUEST
);
333 Ssl::CrtdMessage::ParseResult parse_result
= Ssl::CrtdMessage::INCOMPLETE
;
335 while (parse_result
== Ssl::CrtdMessage::INCOMPLETE
) {
336 if (fgets(request
, HELPER_INPUT_BUFFER
, stdin
) == NULL
)
338 size_t gcount
= strlen(request
);
339 parse_result
= request_message
.parse(request
, gcount
);
342 if (parse_result
== Ssl::CrtdMessage::ERROR
) {
343 throw std::runtime_error("Cannot parse request message.");
344 } else if (request_message
.getCode() == Ssl::CrtdMessage::code_new_certificate
) {
345 processNewRequest(request_message
, db_path
, max_db_size
, fs_block_size
);
347 throw std::runtime_error("Unknown request code: \"" + request_message
.getCode() + "\".");
351 } catch (std::runtime_error
& error
) {
352 std::cerr
<< argv
[0] << ": " << error
.what() << std::endl
;