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