security_file_certgen.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
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 
9 #include "squid.h"
10 #include "base/TextException.h"
11 #include "debug/Stream.h"
13 #include "sbuf/Stream.h"
15 #include "ssl/crtd_message.h"
16 #include "time/gadgets.h"
17 
18 #include <cstring>
19 #include <iostream>
20 #include <sstream>
21 #include <stdexcept>
22 #include <string>
23 #if HAVE_GETOPT_H
24 #include <getopt.h>
25 #endif
26 
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";
81 
86 static size_t parseBytesUnits(const char * unit)
87 {
88  if (!strncasecmp(unit, B_BYTES_STR, strlen(B_BYTES_STR)) ||
89  !strncasecmp(unit, "", strlen(unit)))
90  return 1;
91 
92  if (!strncasecmp(unit, B_KBYTES_STR, strlen(B_KBYTES_STR)))
93  return 1 << 10;
94 
95  if (!strncasecmp(unit, B_MBYTES_STR, strlen(B_MBYTES_STR)))
96  return 1 << 20;
97 
98  if (!strncasecmp(unit, B_GBYTES_STR, strlen(B_GBYTES_STR)))
99  return 1 << 30;
100 
101  throw TextException(ToSBuf("Unknown bytes unit: ", unit), Here());
102 }
103 
106 static size_t
107 parseBytesOptionValue(const char * const name, const char * const value)
108 {
109  // Find number from string beginning.
110  char const * number_begin = value;
111  char const * number_end = value;
112 
113  while ((*number_end >= '0' && *number_end <= '9')) {
114  ++number_end;
115  }
116 
117  if (number_end <= number_begin)
118  throw TextException(ToSBuf("expecting a decimal number at the beginning of ", name, " value but got: ", value), Here());
119 
120  std::string number(number_begin, number_end - number_begin);
121  std::istringstream in(number);
122  size_t base = 0;
123  if (!(in >> base) || !in.eof())
124  throw TextException(ToSBuf("unsupported integer part of ", name, " value: ", number), Here());
125 
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());
131 
132  return product;
133 }
134 
136 static void usage()
137 {
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"
144  "\t-h Help\n"
145  "\t-v Version\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"
150  "\t 2048 bytes.\n"
151  "\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"
159  "...\n"
160  "-----END CERTIFICATE-----\n"
161  "-----BEGIN RSA PRIVATE KEY-----\n"
162  "...\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;
169 }
170 
172 static bool processNewRequest(Ssl::CrtdMessage & request_message, std::string const & db_path, size_t max_db_size, size_t fs_block_size)
173 {
174  Ssl::CertificateProperties certProperties;
175  request_message.parseRequest(certProperties);
176 
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));
181 
183  Security::PrivateKeyPointer pkey;
185  std::string &certKey = Ssl::OnDiskCertificateDbKey(certProperties);
186 
187  bool dbFailed = false;
188  try {
189  if (db)
190  db->find(certKey, certProperties.mimicCert, cert, pkey);
191 
192  } catch (...) {
193  dbFailed = true;
194  debugs(83, DBG_IMPORTANT, "ERROR: Database search failure: " << CurrentException <<
195  Debug::Extra << "database location: " << db_path);
196  }
197 
198  if (!cert || !pkey) {
199  if (!Ssl::generateSslCertificate(cert, pkey, certProperties))
200  throw TextException("Cannot create ssl certificate or private key.", Here());
201 
202  try {
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.
209 
210  TODO: remove the !dbFailed condition when fixing the CertificateDb
211  object lifecycle and formally altering the helper behaviour.
212  */
213  if (!dbFailed && db && !db->addCertAndPrivateKey(certKey, cert, pkey, certProperties.mimicCert))
214  throw TextException("Cannot add certificate to db.", Here());
215 
216  } catch (...) {
217  dbFailed = true;
218  debugs(83, DBG_IMPORTANT, "ERROR: Database update failure: " << CurrentException <<
219  Debug::Extra << "database location: " << db_path);
220  }
221  }
222 
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());
226 
227  Ssl::CrtdMessage response_message(Ssl::CrtdMessage::REPLY);
228  response_message.setCode("OK");
229  response_message.setBody(bufferToWrite);
230 
231  // Use the '\1' char as end-of-message character
232  std::cout << response_message.compose() << '\1' << std::flush;
233 
234  return true;
235 }
236 
238 int main(int argc, char *argv[])
239 {
240  try {
241  Debug::NameThisHelper("sslcrtd_program");
242 
243  size_t max_db_size = 0;
244  size_t fs_block_size = 0;
245  int8_t c;
246  bool create_new_db = false;
247  std::string db_path;
248  // process options.
249  while ((c = getopt(argc, argv, "dchvs:M:b:")) != -1) {
250  switch (c) {
251  case 'd':
252  debug_enabled = 1;
253  break;
254  case 'b':
255  fs_block_size = parseBytesOptionValue("-b", optarg);
256  break;
257  case 's':
258  db_path = optarg;
259  break;
260  case 'M':
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());
264  }
265  max_db_size = parseBytesOptionValue("-M", optarg);
266  break;
267  case 'v':
268  std::cout << "security_file_certgen version " << VERSION << std::endl;
269  exit(EXIT_SUCCESS);
270  break;
271  case 'c':
272  create_new_db = true;
273  break;
274  case 'h':
275  usage();
276  exit(EXIT_SUCCESS);
277  default:
278  exit(EXIT_FAILURE);
279  }
280  }
281 
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());
285 
286  if (create_new_db) {
287  // when -c is used, -s is required (implying also -M, which is checked above)
288  if (db_path.empty())
289  throw TextException("security_file_certgen is missing the required parameter. There should be -s and -M parameters when -c is used.", Here());
290 
291  std::cout << "Initialization SSL db..." << std::endl;
293  std::cout << "Done" << std::endl;
294  exit(EXIT_SUCCESS);
295  }
296 
297  // only do filesystem checks when a path (-s) is given
298  if (!db_path.empty()) {
299  if (fs_block_size == 0) {
300  struct statvfs sfs;
301 
302  if (xstatvfs(db_path.c_str(), &sfs)) {
303  fs_block_size = 2048;
304  } else {
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;
309  }
310  }
311  Ssl::CertificateDb::Check(db_path, max_db_size, fs_block_size);
312  }
313 
314  // Initialize SSL subsystem
316  // process request.
317  for (;;) {
318  char request[HELPER_INPUT_BUFFER];
321 
322  while (parse_result == Ssl::CrtdMessage::INCOMPLETE) {
323  if (fgets(request, HELPER_INPUT_BUFFER, stdin) == nullptr)
324  exit(EXIT_FAILURE);
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 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);
333  } else {
334  throw TextException(ToSBuf("Unknown request code: \"", request_message.getCode(), "\"."), Here());
335  }
336  std::cout.flush();
337  }
338  } catch (...) {
339  debugs(83, DBG_CRITICAL, "FATAL: Cannot generate certificates: " << CurrentException);
340  return EXIT_FAILURE;
341  }
342  return EXIT_SUCCESS;
343 }
344 
static const std::string param_host
Parameter name for passing hostname.
Definition: crtd_message.h:78
#define Here()
source code location of the caller
Definition: Here.h:15
#define DBG_CRITICAL
Definition: Stream.h:37
Security::CertPointer mimicCert
Certificate to mimic.
Definition: gadgets.h:235
static size_t parseBytesUnits(const char *unit)
TDB_CONTEXT * db
char * optarg
Definition: getopt.c:51
int xstatvfs(const char *path, struct statvfs *sfs)
Definition: statvfs.cc:22
static const char *const B_GBYTES_STR
char * db_path
int getopt(int nargc, char *const *nargv, const char *ostr)
Definition: getopt.c:62
static bool processNewRequest(Ssl::CrtdMessage &request_message, std::string const &db_path, size_t max_db_size, size_t fs_block_size)
Process new request message.
number
Definition: testStatHist.cc:32
static size_t parseBytesOptionValue(const char *const name, const char *const value)
const std::string & getCode() const
Current response/request code. If parsing is not finished the method may return incompleted code.
static const std::string code_new_certificate
String code for "new_certificate" messages.
Definition: crtd_message.h:76
static void NameThisHelper(const char *name)
Definition: debug.cc:384
std::string compose() const
bool generateSslCertificate(Security::CertPointer &cert, Security::PrivateKeyPointer &pkey, CertificateProperties const &properties)
Definition: gadgets.cc:769
int debug_enabled
Definition: debug.cc:13
static void Create(std::string const &db_path)
Create and initialize a database under the db_path.
std::ostream & CurrentException(std::ostream &os)
prints active (i.e., thrown but not yet handled) exception
ParseResult
Parse result codes.
Definition: crtd_message.h:29
static std::ostream & Extra(std::ostream &)
Definition: debug.cc:1316
void setCode(std::string const &aCode)
Set new request/reply code to compose.
static const char *const B_BYTES_STR
void SQUID_OPENSSL_init_ssl(void)
Definition: openssl.h:308
ParseResult parse(const char *buffer, size_t len)
Definition: crtd_message.cc:23
#define HELPER_INPUT_BUFFER
Definition: UserRequest.cc:24
int main(int argc, char *argv[])
This is the external security_file_certgen process.
an std::runtime_error with thrower location info
Definition: TextException.h:20
static const char *const B_MBYTES_STR
SBuf ToSBuf(Args &&... args)
slowly stream-prints all arguments into a freshly allocated SBuf
Definition: Stream.h:63
unsigned long f_frsize
Definition: statvfs.h:45
#define DBG_IMPORTANT
Definition: Stream.h:38
static void Check(std::string const &db_path, size_t max_db_size, size_t fs_block_size)
Check the database stored under the db_path.
bool writeCertAndPrivateKeyToMemory(Security::CertPointer const &cert, Security::PrivateKeyPointer const &pkey, std::string &bufferToWrite)
Definition: gadgets.cc:99
void parseRequest(CertificateProperties &)
orchestrates entire request parsing
std::string & OnDiskCertificateDbKey(const CertificateProperties &)
Definition: gadgets.cc:269
#define VERSION
static void usage()
Print help using response code.
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:192
static const char *const B_KBYTES_STR
void setBody(std::string const &aBody)
Set new body to encode.

 

Introduction

Documentation

Support

Miscellaneous