2 * Copyright (C) 1996-2025 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 "base/TextException.h"
11 #include "debug/Stream.h"
12 #include "helper/protocol_defines.h"
13 #include "sbuf/Stream.h"
14 #include "security/cert_generators/file/certificate_db.h"
15 #include "ssl/crtd_message.h"
16 #include "time/gadgets.h"
28 \defgroup ssl_crtd security_file_certgen
29 \ingroup ExternalPrograms
31 Because the standard generation of SSL certificates for
32 sslBump feature, Squid must use external process to
33 actually make these calls. This process generate new ssl
34 certificates and worked with ssl certificates disk cache.
35 Typically there will be five certificate generator processes
36 spawned from Squid. Communication occurs via TCP sockets
37 bound to the loopback interface. The class in helper.h are
38 primally concerned with starting and stopping the helpers.
39 Reading and writing to and from the helpers occurs in the
40 \link IPCacheAPI IP\endlink and the dnsservers occurs in
41 the \link IPCacheAPI IP\endlink and \link FQDNCacheAPI
42 FQDN\endlink cache modules.
44 \section ssl_crtdInterface Command Line Interface
46 usage: security_file_certgen -hv -s directory -M size -b fs_block_size
49 -s directory Directory path of SSL storage database.
50 -M size Maximum size of SSL certificate disk storage.
51 -b fs_block_size File system block size in bytes. Need for processing
52 natural size of certificate on disk. Default value is
55 After running write requests in the next format:
56 <request code><whitespace><body_len><whitespace><body>
57 There are two kind of request now:
58 new_certificate 14 host=host.dom
59 Create new private key and selfsigned certificate for "host.dom".
61 new_certificate xxx host=host.dom
62 -----BEGIN CERTIFICATE-----
64 -----END CERTIFICATE-----
65 -----BEGIN RSA PRIVATE KEY-----
67 -----END RSA PRIVATE KEY-----
68 Create new private key and certificate request for "host.dom".
69 Sign new request by received certificate and private key.
71 usage: security_file_certgen -c -s ssl_store_path\n
72 -c Init ssl db directories and exit.
77 static const char *const B_KBYTES_STR
= "KB";
78 static const char *const B_MBYTES_STR
= "MB";
79 static const char *const B_GBYTES_STR
= "GB";
80 static const char *const B_BYTES_STR
= "B";
83 * Parse bytes unit. It would be one of the next value: MB, GB, KB or B.
84 * This function is caseinsensitive.
86 static size_t parseBytesUnits(const char * unit
)
88 if (!strncasecmp(unit
, B_BYTES_STR
, strlen(B_BYTES_STR
)) ||
89 !strncasecmp(unit
, "", strlen(unit
)))
92 if (!strncasecmp(unit
, B_KBYTES_STR
, strlen(B_KBYTES_STR
)))
95 if (!strncasecmp(unit
, B_MBYTES_STR
, strlen(B_MBYTES_STR
)))
98 if (!strncasecmp(unit
, B_GBYTES_STR
, strlen(B_GBYTES_STR
)))
101 throw TextException(ToSBuf("Unknown bytes unit: ", unit
), Here());
104 /// Parse the number of bytes given as <integer><unit> value (e.g., 4MB).
105 /// \param name the name of the option being parsed
107 parseBytesOptionValue(const char * const name
, const char * const value
)
109 // Find number from string beginning.
110 char const * number_begin
= value
;
111 char const * number_end
= value
;
113 while ((*number_end
>= '0' && *number_end
<= '9')) {
117 if (number_end
<= number_begin
)
118 throw TextException(ToSBuf("expecting a decimal number at the beginning of ", name
, " value but got: ", value
), Here());
120 std::string
number(number_begin
, number_end
- number_begin
);
121 std::istringstream
in(number
);
123 if (!(in
>> base
) || !in
.eof())
124 throw TextException(ToSBuf("unsupported integer part of ", name
, " value: ", number
), Here());
126 const auto multiplier
= parseBytesUnits(number_end
);
127 static_assert(std::is_unsigned
<decltype(multiplier
* base
)>::value
, "no signed overflows");
128 const auto product
= multiplier
* base
;
129 if (base
&& multiplier
!= product
/ base
)
130 throw TextException(ToSBuf(name
, " size too large: ", value
), Here());
135 /// Print help using response code.
138 std::string example_host_name
= "host.dom";
139 std::string request_string
= Ssl::CrtdMessage::param_host
+ "=" + example_host_name
;
140 std::stringstream request_string_size_stream
;
141 request_string_size_stream
<< request_string
.length();
142 std::string help_string
=
143 "usage: security_file_certgen -hv -s directory -M size -b fs_block_size\n"
146 "\t-s directory Directory path of SSL storage database.\n"
147 "\t-M size Maximum size of SSL certificate disk storage.\n"
148 "\t-b fs_block_size File system block size in bytes. Need for processing\n"
149 "\t natural size of certificate on disk. Default value is\n"
152 "After running write requests in the next format:\n"
153 "<request code><whitespace><body_len><whitespace><body>\n"
154 "There are two kind of request now:\n"
155 + Ssl::CrtdMessage::code_new_certificate
+ " " + request_string_size_stream
.str() + " " + request_string
+ "\n" +
156 "\tCreate new private key and selfsigned certificate for \"host.dom\".\n"
157 + Ssl::CrtdMessage::code_new_certificate
+ " xxx " + request_string
+ "\n" +
158 "-----BEGIN CERTIFICATE-----\n"
160 "-----END CERTIFICATE-----\n"
161 "-----BEGIN RSA PRIVATE KEY-----\n"
163 "-----END RSA PRIVATE KEY-----\n"
164 "\tCreate new private key and certificate request for \"host.dom\"\n"
165 "\tSign new request by received certificate and private key.\n"
166 "usage: security_file_certgen -c -s ssl_store_path\n"
167 "\t-c Init ssl db directories and exit.\n";
168 std::cerr
<< help_string
<< std::endl
;
171 /// Process new request message.
172 static bool processNewRequest(Ssl::CrtdMessage
& request_message
, std::string
const & db_path
, size_t max_db_size
, size_t fs_block_size
)
174 Ssl::CertificateProperties certProperties
;
175 request_message
.parseRequest(certProperties
);
177 // TODO: create a DB object only once, instead re-allocating here on every call.
178 std::unique_ptr
<Ssl::CertificateDb
> db
;
179 if (!db_path
.empty())
180 db
.reset(new Ssl::CertificateDb(db_path
, max_db_size
, fs_block_size
));
182 Security::CertPointer cert
;
183 Security::PrivateKeyPointer pkey
;
184 Security::CertPointer orig
;
185 std::string
&certKey
= Ssl::OnDiskCertificateDbKey(certProperties
);
187 bool dbFailed
= false;
190 db
->find(certKey
, certProperties
.mimicCert
, cert
, pkey
);
194 debugs(83, DBG_IMPORTANT
, "ERROR: Database search failure: " << CurrentException
<<
195 Debug::Extra
<< "database location: " << db_path
);
198 if (!cert
|| !pkey
) {
199 if (!Ssl::generateSslCertificate(cert
, pkey
, certProperties
))
200 throw TextException("Cannot create ssl certificate or private key.", Here());
203 /* XXX: this !dbFailed condition prevents the helper fixing DB issues
204 by adding cleanly generated certs. Which is not consistent with other
205 data caches used by Squid - they purge broken entries and allow clean
206 entries to later try and fix the issue.
207 We leave it in place now only to avoid breaking existing installations
208 behaviour with version 1.x of the helper.
210 TODO: remove the !dbFailed condition when fixing the CertificateDb
211 object lifecycle and formally altering the helper behaviour.
213 if (!dbFailed
&& db
&& !db
->addCertAndPrivateKey(certKey
, cert
, pkey
, certProperties
.mimicCert
))
214 throw TextException("Cannot add certificate to db.", Here());
218 debugs(83, DBG_IMPORTANT
, "ERROR: Database update failure: " << CurrentException
<<
219 Debug::Extra
<< "database location: " << db_path
);
223 std::string bufferToWrite
;
224 if (!Ssl::writeCertAndPrivateKeyToMemory(cert
, pkey
, bufferToWrite
))
225 throw TextException("Cannot write ssl certificate or/and private key to memory.", Here());
227 Ssl::CrtdMessage
response_message(Ssl::CrtdMessage::REPLY
);
228 response_message
.setCode("OK");
229 response_message
.setBody(bufferToWrite
);
231 // Use the '\1' char as end-of-message character
232 std::cout
<< response_message
.compose() << '\1' << std::flush
;
237 /// This is the external security_file_certgen process.
238 int main(int argc
, char *argv
[])
241 Debug::NameThisHelper("sslcrtd_program");
243 size_t max_db_size
= 0;
244 size_t fs_block_size
= 0;
246 bool create_new_db
= false;
249 while ((c
= getopt(argc
, argv
, "dchvs:M:b:")) != -1) {
255 fs_block_size
= parseBytesOptionValue("-b", optarg
);
261 // use of -M without -s is probably an admin mistake, so make it an error
262 if (db_path
.empty()) {
263 throw TextException("Error -M option requires an -s parameter be set first.", Here());
265 max_db_size
= parseBytesOptionValue("-M", optarg
);
268 std::cout
<< "security_file_certgen version " << VERSION
<< std::endl
;
272 create_new_db
= true;
282 // when -s is used, -M is required
283 if (!db_path
.empty() && max_db_size
== 0)
284 throw TextException("security_file_certgen -s requires an -M parameter", Here());
287 // when -c is used, -s is required (implying also -M, which is checked above)
289 throw TextException("security_file_certgen is missing the required parameter. There should be -s and -M parameters when -c is used.", Here());
291 std::cout
<< "Initialization SSL db..." << std::endl
;
292 Ssl::CertificateDb::Create(db_path
);
293 std::cout
<< "Done" << std::endl
;
297 // only do filesystem checks when a path (-s) is given
298 if (!db_path
.empty()) {
299 if (fs_block_size
== 0) {
302 if (xstatvfs(db_path
.c_str(), &sfs
)) {
303 fs_block_size
= 2048;
305 fs_block_size
= sfs
.f_frsize
;
306 // Sanity check; make sure we have a meaningful value.
307 if (fs_block_size
< 512)
308 fs_block_size
= 2048;
311 Ssl::CertificateDb::Check(db_path
, max_db_size
, fs_block_size
);
314 // Initialize SSL subsystem
315 SQUID_OPENSSL_init_ssl();
318 char request
[HELPER_INPUT_BUFFER
];
319 Ssl::CrtdMessage
request_message(Ssl::CrtdMessage::REQUEST
);
320 Ssl::CrtdMessage::ParseResult parse_result
= Ssl::CrtdMessage::INCOMPLETE
;
322 while (parse_result
== Ssl::CrtdMessage::INCOMPLETE
) {
323 if (fgets(request
, HELPER_INPUT_BUFFER
, stdin
) == nullptr)
325 size_t gcount
= strlen(request
);
326 parse_result
= request_message
.parse(request
, gcount
);
329 if (parse_result
== Ssl::CrtdMessage::ERROR
) {
330 throw TextException("Cannot parse request message.", Here());
331 } else if (request_message
.getCode() == Ssl::CrtdMessage::code_new_certificate
) {
332 processNewRequest(request_message
, db_path
, max_db_size
, fs_block_size
);
334 throw TextException(ToSBuf("Unknown request code: \"", request_message
.getCode(), "\"."), Here());
339 debugs(83, DBG_CRITICAL
, "FATAL: Cannot generate certificates: " << CurrentException
);