]> git.ipfire.org Git - thirdparty/squid.git/blob - src/security/cert_generators/file/certificate_db.cc
Merge form trunk rev.14722
[thirdparty/squid.git] / src / security / cert_generators / file / certificate_db.cc
1 /*
2 * Copyright (C) 1996-2016 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 "security/cert_generators/file/certificate_db.h"
11
12 #include <cerrno>
13 #include <fstream>
14 #include <memory>
15 #include <stdexcept>
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 "(security_file_certgen) " << __FILE__ << ':' << __LINE__ << ": "
27
28 Ssl::Lock::Lock(std::string const &aFilename) :
29 filename(aFilename),
30 #if _SQUID_WINDOWS_
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_WINDOWS_
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_WINDOWS_
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(), O_RDWR);
55 if (fd == -1)
56 #endif
57 throw std::runtime_error("Failed to open file " + filename);
58
59 #if _SQUID_WINDOWS_
60 if (!LockFile(hFile, 0, 0, 1, 0))
61 #elif _SQUID_SOLARIS_
62 if (lockf(fd, F_LOCK, 0) != 0)
63 #else
64 if (flock(fd, LOCK_EX) != 0)
65 #endif
66 throw std::runtime_error("Failed to get a lock of " + filename);
67 }
68
69 void Ssl::Lock::unlock()
70 {
71 #if _SQUID_WINDOWS_
72 if (hFile != INVALID_HANDLE_VALUE) {
73 UnlockFile(hFile, 0, 0, 1, 0);
74 CloseHandle(hFile);
75 hFile = INVALID_HANDLE_VALUE;
76 }
77 #else
78 if (fd != -1) {
79 #if _SQUID_SOLARIS_
80 lockf(fd, F_ULOCK, 0);
81 #else
82 flock(fd, LOCK_UN);
83 #endif
84 close(fd);
85 fd = -1;
86 }
87 #endif
88 else
89 throw std::runtime_error("Lock is already unlocked for " + filename);
90 }
91
92 Ssl::Lock::~Lock()
93 {
94 if (locked())
95 unlock();
96 }
97
98 Ssl::Locker::Locker(Lock &aLock, const char *aFileName, int aLineNo):
99 weLocked(false), lock(aLock), fileName(aFileName), lineNo(aLineNo)
100 {
101 if (!lock.locked()) {
102 lock.lock();
103 weLocked = true;
104 }
105 }
106
107 Ssl::Locker::~Locker()
108 {
109 if (weLocked)
110 lock.unlock();
111 }
112
113 Ssl::CertificateDb::Row::Row()
114 : width(cnlNumber)
115 {
116 row = (char **)OPENSSL_malloc(sizeof(char *) * (width + 1));
117 for (size_t i = 0; i < width + 1; ++i)
118 row[i] = NULL;
119 }
120
121 Ssl::CertificateDb::Row::Row(char **aRow, size_t aWidth): width(aWidth)
122 {
123 row = aRow;
124 }
125
126 Ssl::CertificateDb::Row::~Row()
127 {
128 if (!row)
129 return;
130
131 void *max;
132 if ((max = (void *)row[width]) != NULL) {
133 // It is an openSSL allocated row. The TXT_DB_read function stores the
134 // index and row items one one memory segment. The row[width] points
135 // to the end of buffer. We have to check for items in the array which
136 // are not stored in this segment. These items should released.
137 for (size_t i = 0; i < width + 1; ++i) {
138 if (((row[i] < (char *)row) || (row[i] > max)) && (row[i] != NULL))
139 OPENSSL_free(row[i]);
140 }
141 } else {
142 for (size_t i = 0; i < width + 1; ++i) {
143 if (row[i])
144 OPENSSL_free(row[i]);
145 }
146 }
147 OPENSSL_free(row);
148 }
149
150 void Ssl::CertificateDb::Row::reset()
151 {
152 row = NULL;
153 }
154
155 void Ssl::CertificateDb::Row::setValue(size_t cell, char const * value)
156 {
157 assert(cell < width);
158 if (row[cell]) {
159 free(row[cell]);
160 }
161 if (value) {
162 row[cell] = static_cast<char *>(OPENSSL_malloc(sizeof(char) * (strlen(value) + 1)));
163 memcpy(row[cell], value, sizeof(char) * (strlen(value) + 1));
164 } else
165 row[cell] = NULL;
166 }
167
168 char ** Ssl::CertificateDb::Row::getRow()
169 {
170 return row;
171 }
172
173 void Ssl::CertificateDb::sq_TXT_DB_delete(TXT_DB *db, const char **row)
174 {
175 if (!db)
176 return;
177
178 #if SQUID_SSLTXTDB_PSTRINGDATA
179 for (int i = 0; i < sk_OPENSSL_PSTRING_num(db->data); ++i) {
180 #if SQUID_STACKOF_PSTRINGDATA_HACK
181 const char ** current_row = ((const char **)sk_value(CHECKED_STACK_OF(OPENSSL_PSTRING, db->data), i));
182 #else
183 const char ** current_row = ((const char **)sk_OPENSSL_PSTRING_value(db->data, i));
184 #endif
185 #else
186 for (int i = 0; i < sk_num(db->data); ++i) {
187 const char ** current_row = ((const char **)sk_value(db->data, i));
188 #endif
189 if (current_row == row) {
190 sq_TXT_DB_delete_row(db, i);
191 return;
192 }
193 }
194 }
195
196 #define countof(arr) (sizeof(arr)/sizeof(*arr))
197 void Ssl::CertificateDb::sq_TXT_DB_delete_row(TXT_DB *db, int idx) {
198 char **rrow;
199 #if SQUID_SSLTXTDB_PSTRINGDATA
200 rrow = (char **)sk_OPENSSL_PSTRING_delete(db->data, idx);
201 #else
202 rrow = (char **)sk_delete(db->data, idx);
203 #endif
204
205 if (!rrow)
206 return;
207
208 Row row(rrow, cnlNumber); // row wrapper used to free the rrow
209
210 const Columns db_indexes[]= {cnlSerial, cnlName};
211 for (unsigned int i = 0; i < countof(db_indexes); ++i) {
212 void *data = NULL;
213 #if SQUID_SSLTXTDB_PSTRINGDATA
214 if (LHASH_OF(OPENSSL_STRING) *fieldIndex = db->index[db_indexes[i]])
215 data = lh_OPENSSL_STRING_delete(fieldIndex, rrow);
216 #else
217 if (LHASH *fieldIndex = db->index[db_indexes[i]])
218 data = lh_delete(fieldIndex, rrow);
219 #endif
220 if (data)
221 assert(data == rrow);
222 }
223 }
224
225 unsigned long Ssl::CertificateDb::index_serial_hash(const char **a) {
226 const char *n = a[Ssl::CertificateDb::cnlSerial];
227 while (*n == '0')
228 ++n;
229 return lh_strhash(n);
230 }
231
232 int Ssl::CertificateDb::index_serial_cmp(const char **a, const char **b) {
233 const char *aa, *bb;
234 for (aa = a[Ssl::CertificateDb::cnlSerial]; *aa == '0'; ++aa);
235 for (bb = b[Ssl::CertificateDb::cnlSerial]; *bb == '0'; ++bb);
236 return strcmp(aa, bb);
237 }
238
239 unsigned long Ssl::CertificateDb::index_name_hash(const char **a) {
240 return(lh_strhash(a[Ssl::CertificateDb::cnlName]));
241 }
242
243 int Ssl::CertificateDb::index_name_cmp(const char **a, const char **b) {
244 return(strcmp(a[Ssl::CertificateDb::cnlName], b[CertificateDb::cnlName]));
245 }
246
247 const std::string Ssl::CertificateDb::db_file("index.txt");
248 const std::string Ssl::CertificateDb::cert_dir("certs");
249 const std::string Ssl::CertificateDb::size_file("size");
250
251 Ssl::CertificateDb::CertificateDb(std::string const & aDb_path, size_t aMax_db_size, size_t aFs_block_size)
252 : db_path(aDb_path),
253 db_full(aDb_path + "/" + db_file),
254 cert_full(aDb_path + "/" + cert_dir),
255 size_full(aDb_path + "/" + size_file),
256 max_db_size(aMax_db_size),
257 fs_block_size((aFs_block_size ? aFs_block_size : 2048)),
258 dbLock(db_full),
259 enabled_disk_store(true) {
260 if (db_path.empty() && !max_db_size)
261 enabled_disk_store = false;
262 else if ((db_path.empty() && max_db_size) || (!db_path.empty() && !max_db_size))
263 throw std::runtime_error("security_file_certgen is missing the required parameter. There should be -s and -M parameters together.");
264 }
265
266 bool Ssl::CertificateDb::find(std::string const & host_name, Security::CertPointer & cert, Ssl::EVP_PKEY_Pointer & pkey) {
267 const Locker locker(dbLock, Here);
268 load();
269 return pure_find(host_name, cert, pkey);
270 }
271
272 bool Ssl::CertificateDb::purgeCert(std::string const & key) {
273 const Locker locker(dbLock, Here);
274 load();
275 if (!db)
276 return false;
277
278 if (!deleteByHostname(key))
279 return false;
280
281 save();
282 return true;
283 }
284
285 /// DeAllocator functor for unique_ptr that need free(3) from the std C library
286 struct xfree_char {
287 void operator()(char *a) { xfree(a); }
288 };
289
290 bool Ssl::CertificateDb::addCertAndPrivateKey(Security::CertPointer & cert, Ssl::EVP_PKEY_Pointer & pkey, std::string const & useName) {
291 const Locker locker(dbLock, Here);
292 load();
293 if (!db || !cert || !pkey)
294 return false;
295 Row row;
296 ASN1_INTEGER * ai = X509_get_serialNumber(cert.get());
297 std::string serial_string;
298 Ssl::BIGNUM_Pointer serial(ASN1_INTEGER_to_BN(ai, NULL));
299 {
300 std::unique_ptr<char, xfree_char> hex_bn(BN_bn2hex(serial.get()));
301 serial_string = std::string(hex_bn.get());
302 }
303 row.setValue(cnlSerial, serial_string.c_str());
304 char ** rrow = TXT_DB_get_by_index(db.get(), cnlSerial, row.getRow());
305 // We are creating certificates with unique serial numbers. If the serial
306 // number is found in the database, the same certificate is already stored.
307 if (rrow != NULL) {
308 // TODO: check if the stored row is valid.
309 return true;
310 }
311
312 {
313 std::unique_ptr<char, xfree_char> subject(X509_NAME_oneline(X509_get_subject_name(cert.get()), nullptr, 0));
314 Security::CertPointer findCert;
315 Ssl::EVP_PKEY_Pointer findPkey;
316 if (pure_find(useName.empty() ? subject.get() : useName, findCert, findPkey)) {
317 // Replace with database certificate
318 cert.reset(findCert.release());
319 pkey.reset(findPkey.release());
320 return true;
321 }
322 // pure_find may fail because the entry is expired, or because the
323 // certs file is corrupted. Remove any entry with given hostname
324 deleteByHostname(useName.empty() ? subject.get() : useName);
325 }
326
327 // check db size while trying to minimize calls to size()
328 size_t dbSize = size();
329 if ((dbSize == 0 && hasRows()) ||
330 (dbSize > 0 && !hasRows()) ||
331 (dbSize > 10 * max_db_size)) {
332 // Invalid database size, rebuild
333 dbSize = rebuildSize();
334 }
335 while (dbSize > max_db_size && deleteInvalidCertificate()) {
336 dbSize = size(); // get the current database size
337 // and try to find another invalid certificate if needed
338 }
339 // there are no more invalid ones, but there must be valid certificates
340 while (dbSize > max_db_size) {
341 if (!deleteOldestCertificate()) {
342 rebuildSize(); // No certificates in database.Update the size file.
343 save(); // Some entries may have been removed. Update the index file.
344 return false; // errors prevented us from freeing enough space
345 }
346 dbSize = size(); // get the current database size
347 }
348
349 row.setValue(cnlType, "V");
350 ASN1_UTCTIME * tm = X509_get_notAfter(cert.get());
351 row.setValue(cnlExp_date, std::string(reinterpret_cast<char *>(tm->data), tm->length).c_str());
352 row.setValue(cnlFile, "unknown");
353 if (!useName.empty())
354 row.setValue(cnlName, useName.c_str());
355 else {
356 std::unique_ptr<char, xfree_char> subject(X509_NAME_oneline(X509_get_subject_name(cert.get()), nullptr, 0));
357 row.setValue(cnlName, subject.get());
358 }
359
360 if (!TXT_DB_insert(db.get(), row.getRow())) {
361 // failed to add index (???) but we may have already modified
362 // the database so save before exit
363 save();
364 return false;
365 }
366 rrow = row.getRow();
367 row.reset();
368
369 std::string filename(cert_full + "/" + serial_string + ".pem");
370 if (!writeCertAndPrivateKeyToFile(cert, pkey, filename.c_str())) {
371 //remove row from txt_db and save
372 sq_TXT_DB_delete(db.get(), (const char **)rrow);
373 save();
374 return false;
375 }
376 addSize(filename);
377
378 save();
379 return true;
380 }
381
382 void Ssl::CertificateDb::create(std::string const & db_path) {
383 if (db_path == "")
384 throw std::runtime_error("Path to db is empty");
385 std::string db_full(db_path + "/" + db_file);
386 std::string cert_full(db_path + "/" + cert_dir);
387 std::string size_full(db_path + "/" + size_file);
388
389 if (mkdir(db_path.c_str(), 0777))
390 throw std::runtime_error("Cannot create " + db_path);
391
392 if (mkdir(cert_full.c_str(), 0777))
393 throw std::runtime_error("Cannot create " + cert_full);
394
395 std::ofstream size(size_full.c_str());
396 if (size)
397 size << 0;
398 else
399 throw std::runtime_error("Cannot open " + size_full + " to open");
400 std::ofstream db(db_full.c_str());
401 if (!db)
402 throw std::runtime_error("Cannot open " + db_full + " to open");
403 }
404
405 void Ssl::CertificateDb::check(std::string const & db_path, size_t max_db_size, size_t fs_block_size) {
406 CertificateDb db(db_path, max_db_size, fs_block_size);
407 db.load();
408
409 // Call readSize to force rebuild size file in the case it is corrupted
410 (void)db.readSize();
411 }
412
413 size_t Ssl::CertificateDb::rebuildSize()
414 {
415 size_t dbSize = 0;
416 #if SQUID_SSLTXTDB_PSTRINGDATA
417 for (int i = 0; i < sk_OPENSSL_PSTRING_num(db.get()->data); ++i) {
418 #if SQUID_STACKOF_PSTRINGDATA_HACK
419 const char ** current_row = ((const char **)sk_value(CHECKED_STACK_OF(OPENSSL_PSTRING, db.get()->data), i));
420 #else
421 const char ** current_row = ((const char **)sk_OPENSSL_PSTRING_value(db.get()->data, i));
422 #endif
423 #else
424 for (int i = 0; i < sk_num(db.get()->data); ++i) {
425 const char ** current_row = ((const char **)sk_value(db.get()->data, i));
426 #endif
427 const std::string filename(cert_full + "/" + current_row[cnlSerial] + ".pem");
428 const size_t fSize = getFileSize(filename);
429 dbSize += fSize;
430 }
431 writeSize(dbSize);
432 return dbSize;
433 }
434
435 bool Ssl::CertificateDb::pure_find(std::string const & host_name, Security::CertPointer & cert, Ssl::EVP_PKEY_Pointer & pkey) {
436 if (!db)
437 return false;
438
439 Row row;
440 row.setValue(cnlName, host_name.c_str());
441
442 char **rrow = TXT_DB_get_by_index(db.get(), cnlName, row.getRow());
443 if (rrow == NULL)
444 return false;
445
446 if (!sslDateIsInTheFuture(rrow[cnlExp_date]))
447 return false;
448
449 // read cert and pkey from file.
450 std::string filename(cert_full + "/" + rrow[cnlSerial] + ".pem");
451 readCertAndPrivateKeyFromFiles(cert, pkey, filename.c_str(), NULL);
452 if (!cert || !pkey)
453 return false;
454 return true;
455 }
456
457 size_t Ssl::CertificateDb::size() {
458 return readSize();
459 }
460
461 void Ssl::CertificateDb::addSize(std::string const & filename) {
462 // readSize will rebuild 'size' file if missing or it is corrupted
463 size_t dbSize = readSize();
464 dbSize += getFileSize(filename);
465 writeSize(dbSize);
466 }
467
468 void Ssl::CertificateDb::subSize(std::string const & filename) {
469 // readSize will rebuild 'size' file if missing or it is corrupted
470 size_t dbSize = readSize();
471 const size_t fileSize = getFileSize(filename);
472 dbSize = dbSize > fileSize ? dbSize - fileSize : 0;
473 writeSize(dbSize);
474 }
475
476 size_t Ssl::CertificateDb::readSize() {
477 std::ifstream ifstr(size_full.c_str());
478 size_t db_size = 0;
479 if (!ifstr || !(ifstr >> db_size))
480 return rebuildSize();
481 return db_size;
482 }
483
484 void Ssl::CertificateDb::writeSize(size_t db_size) {
485 std::ofstream ofstr(size_full.c_str());
486 if (!ofstr)
487 throw std::runtime_error("cannot write \"" + size_full + "\" file");
488 ofstr << db_size;
489 }
490
491 size_t Ssl::CertificateDb::getFileSize(std::string const & filename) {
492 std::ifstream file(filename.c_str(), std::ios::binary);
493 if (!file)
494 return 0;
495 file.seekg(0, std::ios_base::end);
496 const std::streampos file_size = file.tellg();
497 if (file_size < 0)
498 return 0;
499 return ((static_cast<size_t>(file_size) + fs_block_size - 1) / fs_block_size) * fs_block_size;
500 }
501
502 void Ssl::CertificateDb::load() {
503 // Load db from file.
504 Ssl::BIO_Pointer in(BIO_new(BIO_s_file()));
505 if (!in || BIO_read_filename(in.get(), db_full.c_str()) <= 0)
506 throw std::runtime_error("Uninitialized SSL certificate database directory: " + db_path + ". To initialize, run \"security_file_certgen -c -s " + db_path + "\".");
507
508 bool corrupt = false;
509 Ssl::TXT_DB_Pointer temp_db(TXT_DB_read(in.get(), cnlNumber));
510 if (!temp_db)
511 corrupt = true;
512
513 // Create indexes in db.
514 if (!corrupt && !TXT_DB_create_index(temp_db.get(), cnlSerial, NULL, LHASH_HASH_FN(index_serial_hash), LHASH_COMP_FN(index_serial_cmp)))
515 corrupt = true;
516
517 if (!corrupt && !TXT_DB_create_index(temp_db.get(), cnlName, NULL, LHASH_HASH_FN(index_name_hash), LHASH_COMP_FN(index_name_cmp)))
518 corrupt = true;
519
520 if (corrupt)
521 throw std::runtime_error("The SSL certificate database " + db_path + " is corrupted. Please rebuild");
522
523 db.reset(temp_db.release());
524 }
525
526 void Ssl::CertificateDb::save() {
527 if (!db)
528 throw std::runtime_error("The certificates database is not loaded");;
529
530 // To save the db to file, create a new BIO with BIO file methods.
531 Ssl::BIO_Pointer out(BIO_new(BIO_s_file()));
532 if (!out || !BIO_write_filename(out.get(), const_cast<char *>(db_full.c_str())))
533 throw std::runtime_error("Failed to initialize " + db_full + " file for writing");;
534
535 if (TXT_DB_write(out.get(), db.get()) < 0)
536 throw std::runtime_error("Failed to write " + db_full + " file");
537 }
538
539 // Normally defined in defines.h file
540 void Ssl::CertificateDb::deleteRow(const char **row, int rowIndex) {
541 const std::string filename(cert_full + "/" + row[cnlSerial] + ".pem");
542 sq_TXT_DB_delete_row(db.get(), rowIndex);
543
544 subSize(filename);
545 int ret = remove(filename.c_str());
546 if (ret < 0 && errno != ENOENT)
547 throw std::runtime_error("Failed to remove certficate file " + filename + " from db");
548 }
549
550 bool Ssl::CertificateDb::deleteInvalidCertificate() {
551 if (!db)
552 return false;
553
554 bool removed_one = false;
555 #if SQUID_SSLTXTDB_PSTRINGDATA
556 for (int i = 0; i < sk_OPENSSL_PSTRING_num(db.get()->data); ++i) {
557 #if SQUID_STACKOF_PSTRINGDATA_HACK
558 const char ** current_row = ((const char **)sk_value(CHECKED_STACK_OF(OPENSSL_PSTRING, db.get()->data), i));
559 #else
560 const char ** current_row = ((const char **)sk_OPENSSL_PSTRING_value(db.get()->data, i));
561 #endif
562 #else
563 for (int i = 0; i < sk_num(db.get()->data); ++i) {
564 const char ** current_row = ((const char **)sk_value(db.get()->data, i));
565 #endif
566
567 if (!sslDateIsInTheFuture(current_row[cnlExp_date])) {
568 deleteRow(current_row, i);
569 removed_one = true;
570 break;
571 }
572 }
573
574 if (!removed_one)
575 return false;
576 return true;
577 }
578
579 bool Ssl::CertificateDb::deleteOldestCertificate()
580 {
581 if (!hasRows())
582 return false;
583
584 #if SQUID_SSLTXTDB_PSTRINGDATA
585 #if SQUID_STACKOF_PSTRINGDATA_HACK
586 const char **row = ((const char **)sk_value(CHECKED_STACK_OF(OPENSSL_PSTRING, db.get()->data), 0));
587 #else
588 const char **row = (const char **)sk_OPENSSL_PSTRING_value(db.get()->data, 0);
589 #endif
590 #else
591 const char **row = (const char **)sk_value(db.get()->data, 0);
592 #endif
593
594 deleteRow(row, 0);
595
596 return true;
597 }
598
599 bool Ssl::CertificateDb::deleteByHostname(std::string const & host) {
600 if (!db)
601 return false;
602
603 #if SQUID_SSLTXTDB_PSTRINGDATA
604 for (int i = 0; i < sk_OPENSSL_PSTRING_num(db.get()->data); ++i) {
605 #if SQUID_STACKOF_PSTRINGDATA_HACK
606 const char ** current_row = ((const char **)sk_value(CHECKED_STACK_OF(OPENSSL_PSTRING, db.get()->data), i));
607 #else
608 const char ** current_row = ((const char **)sk_OPENSSL_PSTRING_value(db.get()->data, i));
609 #endif
610 #else
611 for (int i = 0; i < sk_num(db.get()->data); ++i) {
612 const char ** current_row = ((const char **)sk_value(db.get()->data, i));
613 #endif
614 if (host == current_row[cnlName]) {
615 deleteRow(current_row, i);
616 return true;
617 }
618 }
619 return false;
620 }
621
622 bool Ssl::CertificateDb::hasRows() const
623 {
624 if (!db)
625 return false;
626
627 #if SQUID_SSLTXTDB_PSTRINGDATA
628 if (sk_OPENSSL_PSTRING_num(db.get()->data) == 0)
629 #else
630 if (sk_num(db.get()->data) == 0)
631 #endif
632 return false;
633 return true;
634 }
635
636 bool Ssl::CertificateDb::IsEnabledDiskStore() const {
637 return enabled_disk_store;
638 }
639