]> git.ipfire.org Git - thirdparty/squid.git/blob - src/ssl/certificate_db.cc
Bug 3497: Bad ssl_crtd db size file causes infinite loop.
[thirdparty/squid.git] / src / ssl / certificate_db.cc
1 /*
2 * $Id$
3 */
4
5 #include "squid.h"
6 #include "ssl/certificate_db.h"
7 #if HAVE_ERRNO_H
8 #include <errno.h>
9 #endif
10 #if HAVE_FSTREAM
11 #include <fstream>
12 #endif
13 #if HAVE_STDEXCEPT
14 #include <stdexcept>
15 #endif
16 #if HAVE_SYS_STAT_H
17 #include <sys/stat.h>
18 #endif
19 #if HAVE_SYS_FILE_H
20 #include <sys/file.h>
21 #endif
22 #if HAVE_FCNTL_H
23 #include <fcntl.h>
24 #endif
25
26 #define HERE "(ssl_crtd) " << __FILE__ << ':' << __LINE__ << ": "
27
28 Ssl::Lock::Lock(std::string const &aFilename) :
29 filename(aFilename),
30 #if _SQUID_MSWIN_
31 hFile(INVALID_HANDLE_VALUE)
32 #else
33 fd(-1)
34 #endif
35 {
36 }
37
38 bool Ssl::Lock::locked() const
39 {
40 #if _SQUID_MSWIN_
41 return hFile != INVALID_HANDLE_VALUE;
42 #else
43 return fd != -1;
44 #endif
45 }
46
47 void Ssl::Lock::lock()
48 {
49
50 #if _SQUID_MSWIN_
51 hFile = CreateFile(TEXT(filename.c_str()), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
52 if (hFile == INVALID_HANDLE_VALUE)
53 #else
54 fd = open(filename.c_str(), 0);
55 if (fd == -1)
56 #endif
57 throw std::runtime_error("Failed to open file " + filename);
58
59
60 #if _SQUID_MSWIN_
61 if (!LockFile(hFile, 0, 0, 1, 0))
62 #else
63 if (flock(fd, LOCK_EX) != 0)
64 #endif
65 throw std::runtime_error("Failed to get a lock of " + filename);
66 }
67
68 void Ssl::Lock::unlock()
69 {
70 #if _SQUID_MSWIN_
71 if (hFile != INVALID_HANDLE_VALUE) {
72 UnlockFile(hFile, 0, 0, 1, 0);
73 CloseHandle(hFile);
74 hFile = INVALID_HANDLE_VALUE;
75 }
76 #else
77 if (fd != -1) {
78 flock(fd, LOCK_UN);
79 close(fd);
80 fd = -1;
81 }
82 #endif
83 else
84 throw std::runtime_error("Lock is already unlocked for " + filename);
85 }
86
87 Ssl::Lock::~Lock()
88 {
89 if (locked())
90 unlock();
91 }
92
93 Ssl::Locker::Locker(Lock &aLock, const char *aFileName, int aLineNo):
94 weLocked(false), lock(aLock), fileName(aFileName), lineNo(aLineNo)
95 {
96 if (!lock.locked()) {
97 lock.lock();
98 weLocked = true;
99 }
100 }
101
102 Ssl::Locker::~Locker()
103 {
104 if (weLocked)
105 lock.unlock();
106 }
107
108 Ssl::CertificateDb::Row::Row()
109 : width(cnlNumber)
110 {
111 row = new char *[width + 1];
112 for (size_t i = 0; i < width + 1; i++)
113 row[i] = NULL;
114 }
115
116 Ssl::CertificateDb::Row::~Row()
117 {
118 if (row) {
119 for (size_t i = 0; i < width + 1; i++) {
120 delete[](row[i]);
121 }
122 delete[](row);
123 }
124 }
125
126 void Ssl::CertificateDb::Row::reset()
127 {
128 row = NULL;
129 }
130
131 void Ssl::CertificateDb::Row::setValue(size_t cell, char const * value)
132 {
133 assert(cell < width);
134 if (row[cell]) {
135 free(row[cell]);
136 }
137 if (value) {
138 row[cell] = static_cast<char *>(malloc(sizeof(char) * (strlen(value) + 1)));
139 memcpy(row[cell], value, sizeof(char) * (strlen(value) + 1));
140 } else
141 row[cell] = NULL;
142 }
143
144 char ** Ssl::CertificateDb::Row::getRow()
145 {
146 return row;
147 }
148
149 unsigned long Ssl::CertificateDb::index_serial_hash(const char **a)
150 {
151 const char *n = a[Ssl::CertificateDb::cnlSerial];
152 while (*n == '0') n++;
153 return lh_strhash(n);
154 }
155
156 int Ssl::CertificateDb::index_serial_cmp(const char **a, const char **b)
157 {
158 const char *aa, *bb;
159 for (aa = a[Ssl::CertificateDb::cnlSerial]; *aa == '0'; aa++);
160 for (bb = b[Ssl::CertificateDb::cnlSerial]; *bb == '0'; bb++);
161 return strcmp(aa, bb);
162 }
163
164 unsigned long Ssl::CertificateDb::index_name_hash(const char **a)
165 {
166 return(lh_strhash(a[Ssl::CertificateDb::cnlName]));
167 }
168
169 int Ssl::CertificateDb::index_name_cmp(const char **a, const char **b)
170 {
171 return(strcmp(a[Ssl::CertificateDb::cnlName], b[CertificateDb::cnlName]));
172 }
173
174 const std::string Ssl::CertificateDb::db_file("index.txt");
175 const std::string Ssl::CertificateDb::cert_dir("certs");
176 const std::string Ssl::CertificateDb::size_file("size");
177
178 Ssl::CertificateDb::CertificateDb(std::string const & aDb_path, size_t aMax_db_size, size_t aFs_block_size)
179 : db_path(aDb_path),
180 db_full(aDb_path + "/" + db_file),
181 cert_full(aDb_path + "/" + cert_dir),
182 size_full(aDb_path + "/" + size_file),
183 db(NULL),
184 max_db_size(aMax_db_size),
185 fs_block_size(aFs_block_size),
186 dbLock(db_full),
187 enabled_disk_store(true)
188 {
189 if (db_path.empty() && !max_db_size)
190 enabled_disk_store = false;
191 else if ((db_path.empty() && max_db_size) || (!db_path.empty() && !max_db_size))
192 throw std::runtime_error("ssl_crtd is missing the required parameter. There should be -s and -M parameters together.");
193 }
194
195 bool Ssl::CertificateDb::find(std::string const & host_name, Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey)
196 {
197 const Locker locker(dbLock, Here);
198 load();
199 return pure_find(host_name, cert, pkey);
200 }
201
202 bool Ssl::CertificateDb::purgeCert(std::string const & key)
203 {
204 const Locker locker(dbLock, Here);
205 load();
206 if (!db)
207 return false;
208
209 if (!deleteByHostname(key))
210 return false;
211
212 save();
213 return true;
214 }
215
216 bool Ssl::CertificateDb::addCertAndPrivateKey(Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey, std::string const & useName)
217 {
218 const Locker locker(dbLock, Here);
219 load();
220 if (!db || !cert || !pkey)
221 return false;
222 Row row;
223 ASN1_INTEGER * ai = X509_get_serialNumber(cert.get());
224 std::string serial_string;
225 Ssl::BIGNUM_Pointer serial(ASN1_INTEGER_to_BN(ai, NULL));
226 {
227 TidyPointer<char, tidyFree> hex_bn(BN_bn2hex(serial.get()));
228 serial_string = std::string(hex_bn.get());
229 }
230 row.setValue(cnlSerial, serial_string.c_str());
231 char ** rrow = TXT_DB_get_by_index(db.get(), cnlSerial, row.getRow());
232 // We are creating certificates with unique serial number. If the serial
233 // number found in the database, means that the certificate already exist
234 // in the database
235 if (rrow != NULL)
236 return true;
237
238 {
239 TidyPointer<char, tidyFree> subject(X509_NAME_oneline(X509_get_subject_name(cert.get()), NULL, 0));
240 if (pure_find(useName.empty() ? subject.get() : useName, cert, pkey))
241 return true;
242 }
243
244 // check db size while trying to minimize calls to size()
245 while (size() > max_db_size) {
246 if (deleteInvalidCertificate())
247 continue; // try to find another invalid certificate if needed
248
249 // there are no more invalid ones, but there must be valid certificates
250 do {
251 if (!deleteOldestCertificate())
252 return false; // errors prevented us from freeing enough space
253 } while (size() > max_db_size);
254 break;
255 }
256
257 row.setValue(cnlType, "V");
258 ASN1_UTCTIME * tm = X509_get_notAfter(cert.get());
259 row.setValue(cnlExp_date, std::string(reinterpret_cast<char *>(tm->data), tm->length).c_str());
260 row.setValue(cnlFile, "unknown");
261 if (!useName.empty())
262 row.setValue(cnlName, useName.c_str());
263 else {
264 TidyPointer<char, tidyFree> subject(X509_NAME_oneline(X509_get_subject_name(cert.get()), NULL, 0));
265 row.setValue(cnlName, subject.get());
266 }
267
268 if (!TXT_DB_insert(db.get(), row.getRow()))
269 return false;
270
271 row.reset();
272 std::string filename(cert_full + "/" + serial_string + ".pem");
273 if (!writeCertAndPrivateKeyToFile(cert, pkey, filename.c_str()))
274 return false;
275 addSize(filename);
276
277 save();
278 return true;
279 }
280
281 void Ssl::CertificateDb::create(std::string const & db_path)
282 {
283 if (db_path == "")
284 throw std::runtime_error("Path to db is empty");
285 std::string db_full(db_path + "/" + db_file);
286 std::string cert_full(db_path + "/" + cert_dir);
287 std::string size_full(db_path + "/" + size_file);
288
289 #if _SQUID_MSWIN_
290 if (mkdir(db_path.c_str()))
291 #else
292 if (mkdir(db_path.c_str(), 0777))
293 #endif
294 throw std::runtime_error("Cannot create " + db_path);
295
296 #if _SQUID_MSWIN_
297 if (mkdir(cert_full.c_str()))
298 #else
299 if (mkdir(cert_full.c_str(), 0777))
300 #endif
301 throw std::runtime_error("Cannot create " + cert_full);
302
303 std::ofstream size(size_full.c_str());
304 if (size)
305 size << 0;
306 else
307 throw std::runtime_error("Cannot open " + size_full + " to open");
308 std::ofstream db(db_full.c_str());
309 if (!db)
310 throw std::runtime_error("Cannot open " + db_full + " to open");
311 }
312
313 void Ssl::CertificateDb::check(std::string const & db_path, size_t max_db_size)
314 {
315 CertificateDb db(db_path, max_db_size, 0);
316 db.load();
317 }
318
319 bool Ssl::CertificateDb::pure_find(std::string const & host_name, Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey)
320 {
321 if (!db)
322 return false;
323
324 Row row;
325 row.setValue(cnlName, host_name.c_str());
326
327 char **rrow = TXT_DB_get_by_index(db.get(), cnlName, row.getRow());
328 if (rrow == NULL)
329 return false;
330
331 if (!sslDateIsInTheFuture(rrow[cnlExp_date])) {
332 deleteByHostname(rrow[cnlName]);
333 return false;
334 }
335
336 // read cert and pkey from file.
337 std::string filename(cert_full + "/" + rrow[cnlSerial] + ".pem");
338 readCertAndPrivateKeyFromFiles(cert, pkey, filename.c_str(), NULL);
339 if (!cert || !pkey)
340 return false;
341 return true;
342 }
343
344 size_t Ssl::CertificateDb::size() const
345 {
346 return readSize();
347 }
348
349 void Ssl::CertificateDb::addSize(std::string const & filename)
350 {
351 writeSize(readSize() + getFileSize(filename));
352 }
353
354 void Ssl::CertificateDb::subSize(std::string const & filename)
355 {
356 writeSize(readSize() - getFileSize(filename));
357 }
358
359 size_t Ssl::CertificateDb::readSize() const
360 {
361 std::ifstream size_file(size_full.c_str());
362 if (!size_file && enabled_disk_store)
363 throw std::runtime_error("cannot open for reading: " + size_full);
364 size_t db_size = 0;
365 if (!(size_file >> db_size))
366 throw std::runtime_error("error while reading " + size_full);
367 return db_size;
368 }
369
370 void Ssl::CertificateDb::writeSize(size_t db_size)
371 {
372 std::ofstream size_file(size_full.c_str());
373 if (!size_file && enabled_disk_store)
374 throw std::runtime_error("cannot write \"" + size_full + "\" file");
375 size_file << db_size;
376 }
377
378 size_t Ssl::CertificateDb::getFileSize(std::string const & filename)
379 {
380 std::ifstream file(filename.c_str(), std::ios::binary);
381 file.seekg(0, std::ios_base::end);
382 size_t file_size = file.tellg();
383 return ((file_size + fs_block_size - 1) / fs_block_size) * fs_block_size;
384 }
385
386 void Ssl::CertificateDb::load()
387 {
388 // Load db from file.
389 Ssl::BIO_Pointer in(BIO_new(BIO_s_file()));
390 if (!in || BIO_read_filename(in.get(), db_full.c_str()) <= 0)
391 throw std::runtime_error("Uninitialized SSL certificate database directory: " + db_path + ". To initialize, run \"ssl_crtd -c -s " + db_path + "\".");
392
393 bool corrupt = false;
394 Ssl::TXT_DB_Pointer temp_db(TXT_DB_read(in.get(), cnlNumber));
395 if (!temp_db)
396 corrupt = true;
397
398 // Create indexes in db.
399 #if OPENSSL_VERSION_NUMBER >= 0x1000004fL
400 if (!corrupt && !TXT_DB_create_index(temp_db.get(), cnlSerial, NULL, LHASH_HASH_FN(index_serial), LHASH_COMP_FN(index_serial)))
401 corrupt = true;
402
403 if (!corrupt && !TXT_DB_create_index(temp_db.get(), cnlName, NULL, LHASH_HASH_FN(index_name), LHASH_COMP_FN(index_name)))
404 corrupt = true;
405 #else
406 if (!corrupt && !TXT_DB_create_index(temp_db.get(), cnlSerial, NULL, LHASH_HASH_FN(index_serial_hash), LHASH_COMP_FN(index_serial_cmp)))
407 corrupt = true;
408
409 if (!corrupt && !TXT_DB_create_index(temp_db.get(), cnlName, NULL, LHASH_HASH_FN(index_name_hash), LHASH_COMP_FN(index_name_cmp)))
410 corrupt = true;
411 #endif
412
413 if (corrupt)
414 throw std::runtime_error("The SSL certificate database " + db_path + " is corrupted. Please rebuild");
415
416 db.reset(temp_db.release());
417 }
418
419 void Ssl::CertificateDb::save()
420 {
421 if (!db)
422 throw std::runtime_error("The certificates database is not loaded");;
423
424 // To save the db to file, create a new BIO with BIO file methods.
425 Ssl::BIO_Pointer out(BIO_new(BIO_s_file()));
426 if (!out || !BIO_write_filename(out.get(), const_cast<char *>(db_full.c_str())))
427 throw std::runtime_error("Failed to initialize " + db_full + " file for writing");;
428
429 if (TXT_DB_write(out.get(), db.get()) < 0)
430 throw std::runtime_error("Failed to write " + db_full + " file");
431 }
432
433 // Normally defined in defines.h file
434 #define countof(arr) (sizeof(arr)/sizeof(*arr))
435 void Ssl::CertificateDb::deleteRow(const char **row, int rowIndex)
436 {
437 const std::string filename(cert_full + "/" + row[cnlSerial] + ".pem");
438 #if OPENSSL_VERSION_NUMBER >= 0x1000004fL
439 sk_OPENSSL_PSTRING_delete(db.get()->data, rowIndex);
440 #else
441 sk_delete(db.get()->data, rowIndex);
442 #endif
443
444 const Columns db_indexes[]={cnlSerial, cnlName};
445 for (unsigned int i = 0; i < countof(db_indexes); i++) {
446 #if OPENSSL_VERSION_NUMBER >= 0x1000004fL
447 if (LHASH_OF(OPENSSL_STRING) *fieldIndex = db.get()->index[db_indexes[i]])
448 lh_OPENSSL_STRING_delete(fieldIndex, (char **)row);
449 #else
450 if (LHASH *fieldIndex = db.get()->index[db_indexes[i]])
451 lh_delete(fieldIndex, row);
452 #endif
453 }
454
455 subSize(filename);
456 int ret = remove(filename.c_str());
457 if (ret < 0 && errno != ENOENT)
458 throw std::runtime_error("Failed to remove certficate file " + filename + " from db");
459 }
460
461 bool Ssl::CertificateDb::deleteInvalidCertificate()
462 {
463 if (!db)
464 return false;
465
466 bool removed_one = false;
467 #if OPENSSL_VERSION_NUMBER >= 0x1000004fL
468 for (int i = 0; i < sk_OPENSSL_PSTRING_num(db.get()->data); i++) {
469 const char ** current_row = ((const char **)sk_OPENSSL_PSTRING_value(db.get()->data, i));
470 #else
471 for (int i = 0; i < sk_num(db.get()->data); i++) {
472 const char ** current_row = ((const char **)sk_value(db.get()->data, i));
473 #endif
474
475 if (!sslDateIsInTheFuture(current_row[cnlExp_date])) {
476 deleteRow(current_row, i);
477 removed_one = true;
478 break;
479 }
480 }
481
482 if (!removed_one)
483 return false;
484 return true;
485 }
486
487 bool Ssl::CertificateDb::deleteOldestCertificate()
488 {
489 if (!db)
490 return false;
491
492 #if OPENSSL_VERSION_NUMBER >= 0x1000004fL
493 if (sk_OPENSSL_PSTRING_num(db.get()->data) == 0)
494 #else
495 if (sk_num(db.get()->data) == 0)
496 #endif
497 return false;
498
499 #if OPENSSL_VERSION_NUMBER >= 0x1000004fL
500 const char **row = (const char **)sk_OPENSSL_PSTRING_value(db.get()->data, 0);
501 #else
502 const char **row = (const char **)sk_value(db.get()->data, 0);
503 #endif
504
505 deleteRow(row, 0);
506
507 return true;
508 }
509
510 bool Ssl::CertificateDb::deleteByHostname(std::string const & host)
511 {
512 if (!db)
513 return false;
514
515 #if OPENSSL_VERSION_NUMBER >= 0x1000004fL
516 for (int i = 0; i < sk_OPENSSL_PSTRING_num(db.get()->data); i++) {
517 const char ** current_row = ((const char **)sk_OPENSSL_PSTRING_value(db.get()->data, i));
518 #else
519 for (int i = 0; i < sk_num(db.get()->data); i++) {
520 const char ** current_row = ((const char **)sk_value(db.get()->data, i));
521 #endif
522 if (host == current_row[cnlName]) {
523 deleteRow(current_row, i);
524 return true;
525 }
526 }
527 return false;
528 }
529
530 bool Ssl::CertificateDb::IsEnabledDiskStore() const
531 {
532 return enabled_disk_store;
533 }