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