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