]> git.ipfire.org Git - thirdparty/squid.git/blob - src/ssl/certificate_db.cc
Renamed squid.h to squid-old.h and config.h to squid.h
[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::serial_file("serial");
175 const std::string Ssl::CertificateDb::db_file("index.txt");
176 const std::string Ssl::CertificateDb::cert_dir("certs");
177 const std::string Ssl::CertificateDb::size_file("size");
178 const size_t Ssl::CertificateDb::min_db_size(4096);
179
180 Ssl::CertificateDb::CertificateDb(std::string const & aDb_path, size_t aMax_db_size, size_t aFs_block_size)
181 : db_path(aDb_path),
182 serial_full(aDb_path + "/" + serial_file),
183 db_full(aDb_path + "/" + db_file),
184 cert_full(aDb_path + "/" + cert_dir),
185 size_full(aDb_path + "/" + size_file),
186 db(NULL),
187 max_db_size(aMax_db_size),
188 fs_block_size(aFs_block_size),
189 dbLock(db_full),
190 dbSerialLock(serial_full),
191 enabled_disk_store(true)
192 {
193 if (db_path.empty() && !max_db_size)
194 enabled_disk_store = false;
195 else if ((db_path.empty() && max_db_size) || (!db_path.empty() && !max_db_size))
196 throw std::runtime_error("ssl_crtd is missing the required parameter. There should be -s and -M parameters together.");
197 }
198
199 bool Ssl::CertificateDb::find(std::string const & host_name, Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey)
200 {
201 const Locker locker(dbLock, Here);
202 load();
203 return pure_find(host_name, cert, pkey);
204 }
205
206 bool Ssl::CertificateDb::addCertAndPrivateKey(Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey)
207 {
208 const Locker locker(dbLock, Here);
209 load();
210 if (!db || !cert || !pkey || min_db_size > max_db_size)
211 return false;
212 Row row;
213 ASN1_INTEGER * ai = X509_get_serialNumber(cert.get());
214 std::string serial_string;
215 Ssl::BIGNUM_Pointer serial(ASN1_INTEGER_to_BN(ai, NULL));
216 {
217 TidyPointer<char, tidyFree> hex_bn(BN_bn2hex(serial.get()));
218 serial_string = std::string(hex_bn.get());
219 }
220 row.setValue(cnlSerial, serial_string.c_str());
221 char ** rrow = TXT_DB_get_by_index(db.get(), cnlSerial, row.getRow());
222 if (rrow != NULL)
223 return false;
224
225 {
226 TidyPointer<char, tidyFree> subject(X509_NAME_oneline(X509_get_subject_name(cert.get()), NULL, 0));
227 if (pure_find(subject.get(), cert, pkey))
228 return true;
229 }
230 // check db size.
231 while (max_db_size < size()) {
232 if (!deleteInvalidCertificate())
233 break;
234 }
235
236 while (max_db_size < size()) {
237 deleteOldestCertificate();
238 }
239
240 row.setValue(cnlType, "V");
241 ASN1_UTCTIME * tm = X509_get_notAfter(cert.get());
242 row.setValue(cnlExp_date, std::string(reinterpret_cast<char *>(tm->data), tm->length).c_str());
243 row.setValue(cnlFile, "unknown");
244 {
245 TidyPointer<char, tidyFree> subject(X509_NAME_oneline(X509_get_subject_name(cert.get()), NULL, 0));
246 row.setValue(cnlName, subject.get());
247 }
248
249 if (!TXT_DB_insert(db.get(), row.getRow()))
250 return false;
251
252 row.reset();
253 std::string filename(cert_full + "/" + serial_string + ".pem");
254 if (!writeCertAndPrivateKeyToFile(cert, pkey, filename.c_str()))
255 return false;
256 addSize(filename);
257
258 save();
259 return true;
260 }
261
262 BIGNUM * Ssl::CertificateDb::getCurrentSerialNumber()
263 {
264 const Locker locker(dbSerialLock, Here);
265 // load serial number from file.
266 Ssl::BIO_Pointer file(BIO_new(BIO_s_file()));
267 if (!file)
268 return NULL;
269
270 if (BIO_rw_filename(file.get(), const_cast<char *>(serial_full.c_str())) <= 0)
271 return NULL;
272
273 Ssl::ASN1_INT_Pointer serial_ai(ASN1_INTEGER_new());
274 if (!serial_ai)
275 return NULL;
276
277 char buffer[1024];
278 if (!a2i_ASN1_INTEGER(file.get(), serial_ai.get(), buffer, sizeof(buffer)))
279 return NULL;
280
281 Ssl::BIGNUM_Pointer serial(ASN1_INTEGER_to_BN(serial_ai.get(), NULL));
282
283 if (!serial)
284 return NULL;
285
286 // increase serial number.
287 Ssl::BIGNUM_Pointer increased_serial(BN_dup(serial.get()));
288 if (!increased_serial)
289 return NULL;
290
291 BN_add_word(increased_serial.get(), 1);
292
293 // save increased serial number.
294 if (BIO_seek(file.get(), 0))
295 return NULL;
296
297 Ssl::ASN1_INT_Pointer increased_serial_ai(BN_to_ASN1_INTEGER(increased_serial.get(), NULL));
298 if (!increased_serial_ai)
299 return NULL;
300
301 i2a_ASN1_INTEGER(file.get(), increased_serial_ai.get());
302 BIO_puts(file.get(),"\n");
303
304 return serial.release();
305 }
306
307 void Ssl::CertificateDb::create(std::string const & db_path, int serial)
308 {
309 if (db_path == "")
310 throw std::runtime_error("Path to db is empty");
311 std::string serial_full(db_path + "/" + serial_file);
312 std::string db_full(db_path + "/" + db_file);
313 std::string cert_full(db_path + "/" + cert_dir);
314 std::string size_full(db_path + "/" + size_file);
315
316 #if _SQUID_MSWIN_
317 if (mkdir(db_path.c_str()))
318 #else
319 if (mkdir(db_path.c_str(), 0777))
320 #endif
321 throw std::runtime_error("Cannot create " + db_path);
322
323 #if _SQUID_MSWIN_
324 if (mkdir(cert_full.c_str()))
325 #else
326 if (mkdir(cert_full.c_str(), 0777))
327 #endif
328 throw std::runtime_error("Cannot create " + cert_full);
329
330 Ssl::ASN1_INT_Pointer i(ASN1_INTEGER_new());
331 ASN1_INTEGER_set(i.get(), serial);
332
333 Ssl::BIO_Pointer file(BIO_new(BIO_s_file()));
334 if (!file)
335 throw std::runtime_error("SSL error");
336
337 if (BIO_write_filename(file.get(), const_cast<char *>(serial_full.c_str())) <= 0)
338 throw std::runtime_error("Cannot open " + cert_full + " to open");
339
340 i2a_ASN1_INTEGER(file.get(), i.get());
341
342 std::ofstream size(size_full.c_str());
343 if (size)
344 size << 0;
345 else
346 throw std::runtime_error("Cannot open " + size_full + " to open");
347 std::ofstream db(db_full.c_str());
348 if (!db)
349 throw std::runtime_error("Cannot open " + db_full + " to open");
350 }
351
352 void Ssl::CertificateDb::check(std::string const & db_path, size_t max_db_size)
353 {
354 CertificateDb db(db_path, max_db_size, 0);
355 db.load();
356 }
357
358 std::string Ssl::CertificateDb::getSNString() const
359 {
360 const Locker locker(dbSerialLock, Here);
361 std::ifstream file(serial_full.c_str());
362 if (!file)
363 return "";
364 std::string serial;
365 file >> serial;
366 return serial;
367 }
368
369 bool Ssl::CertificateDb::pure_find(std::string const & host_name, Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey)
370 {
371 if (!db)
372 return false;
373
374 Row row;
375 row.setValue(cnlName, host_name.c_str());
376
377 char **rrow = TXT_DB_get_by_index(db.get(), cnlName, row.getRow());
378 if (rrow == NULL)
379 return false;
380
381 if (!sslDateIsInTheFuture(rrow[cnlExp_date])) {
382 deleteByHostname(rrow[cnlName]);
383 return false;
384 }
385
386 // read cert and pkey from file.
387 std::string filename(cert_full + "/" + rrow[cnlSerial] + ".pem");
388 readCertAndPrivateKeyFromFiles(cert, pkey, filename.c_str(), NULL);
389 if (!cert || !pkey)
390 return false;
391 return true;
392 }
393
394 size_t Ssl::CertificateDb::size() const
395 {
396 return readSize();
397 }
398
399 void Ssl::CertificateDb::addSize(std::string const & filename)
400 {
401 writeSize(readSize() + getFileSize(filename));
402 }
403
404 void Ssl::CertificateDb::subSize(std::string const & filename)
405 {
406 writeSize(readSize() - getFileSize(filename));
407 }
408
409 size_t Ssl::CertificateDb::readSize() const
410 {
411 size_t db_size;
412 std::ifstream size_file(size_full.c_str());
413 if (!size_file && enabled_disk_store)
414 throw std::runtime_error("cannot read \"" + size_full + "\" file");
415 size_file >> db_size;
416 return db_size;
417 }
418
419 void Ssl::CertificateDb::writeSize(size_t db_size)
420 {
421 std::ofstream size_file(size_full.c_str());
422 if (!size_file && enabled_disk_store)
423 throw std::runtime_error("cannot write \"" + size_full + "\" file");
424 size_file << db_size;
425 }
426
427 size_t Ssl::CertificateDb::getFileSize(std::string const & filename)
428 {
429 std::ifstream file(filename.c_str(), std::ios::binary);
430 file.seekg(0, std::ios_base::end);
431 size_t file_size = file.tellg();
432 return ((file_size + fs_block_size - 1) / fs_block_size) * fs_block_size;
433 }
434
435 void Ssl::CertificateDb::load()
436 {
437 // Load db from file.
438 Ssl::BIO_Pointer in(BIO_new(BIO_s_file()));
439 if (!in || BIO_read_filename(in.get(), db_full.c_str()) <= 0)
440 throw std::runtime_error("Uninitialized SSL certificate database directory: " + db_path + ". To initialize, run \"ssl_crtd -c -s " + db_path + "\".");
441
442 bool corrupt = false;
443 Ssl::TXT_DB_Pointer temp_db(TXT_DB_read(in.get(), cnlNumber));
444 if (!temp_db)
445 corrupt = true;
446
447 // Create indexes in db.
448 #if OPENSSL_VERSION_NUMBER >= 0x1000004fL
449 if (!corrupt && !TXT_DB_create_index(temp_db.get(), cnlSerial, NULL, LHASH_HASH_FN(index_serial), LHASH_COMP_FN(index_serial)))
450 corrupt = true;
451
452 if (!corrupt && !TXT_DB_create_index(temp_db.get(), cnlName, NULL, LHASH_HASH_FN(index_name), LHASH_COMP_FN(index_name)))
453 corrupt = true;
454 #else
455 if (!corrupt && !TXT_DB_create_index(temp_db.get(), cnlSerial, NULL, LHASH_HASH_FN(index_serial_hash), LHASH_COMP_FN(index_serial_cmp)))
456 corrupt = true;
457
458 if (!corrupt && !TXT_DB_create_index(temp_db.get(), cnlName, NULL, LHASH_HASH_FN(index_name_hash), LHASH_COMP_FN(index_name_cmp)))
459 corrupt = true;
460 #endif
461
462 if (corrupt)
463 throw std::runtime_error("The SSL certificate database " + db_path + " is corrupted. Please rebuild");
464
465 db.reset(temp_db.release());
466 }
467
468 void Ssl::CertificateDb::save()
469 {
470 if (!db)
471 throw std::runtime_error("The certificates database is not loaded");;
472
473 // To save the db to file, create a new BIO with BIO file methods.
474 Ssl::BIO_Pointer out(BIO_new(BIO_s_file()));
475 if (!out || !BIO_write_filename(out.get(), const_cast<char *>(db_full.c_str())))
476 throw std::runtime_error("Failed to initialize " + db_full + " file for writing");;
477
478 if (TXT_DB_write(out.get(), db.get()) < 0)
479 throw std::runtime_error("Failed to write " + db_full + " file");
480 }
481
482 // Normally defined in defines.h file
483 #define countof(arr) (sizeof(arr)/sizeof(*arr))
484 void Ssl::CertificateDb::deleteRow(const char **row, int rowIndex)
485 {
486 const std::string filename(cert_full + "/" + row[cnlSerial] + ".pem");
487 #if OPENSSL_VERSION_NUMBER >= 0x1000004fL
488 sk_OPENSSL_PSTRING_delete(db.get()->data, rowIndex);
489 #else
490 sk_delete(db.get()->data, rowIndex);
491 #endif
492
493 const Columns db_indexes[]={cnlSerial, cnlName};
494 for (unsigned int i = 0; i < countof(db_indexes); i++) {
495 #if OPENSSL_VERSION_NUMBER >= 0x1000004fL
496 if (LHASH_OF(OPENSSL_STRING) *fieldIndex = db.get()->index[db_indexes[i]])
497 lh_OPENSSL_STRING_delete(fieldIndex, (char **)row);
498 #else
499 if (LHASH *fieldIndex = db.get()->index[db_indexes[i]])
500 lh_delete(fieldIndex, row);
501 #endif
502 }
503
504 subSize(filename);
505 int ret = remove(filename.c_str());
506 if (ret < 0)
507 throw std::runtime_error("Failed to remove certficate file " + filename + " from db");
508 }
509
510 bool Ssl::CertificateDb::deleteInvalidCertificate()
511 {
512 if (!db)
513 return false;
514
515 bool removed_one = false;
516 #if OPENSSL_VERSION_NUMBER >= 0x1000004fL
517 for (int i = 0; i < sk_OPENSSL_PSTRING_num(db.get()->data); i++) {
518 const char ** current_row = ((const char **)sk_OPENSSL_PSTRING_value(db.get()->data, i));
519 #else
520 for (int i = 0; i < sk_num(db.get()->data); i++) {
521 const char ** current_row = ((const char **)sk_value(db.get()->data, i));
522 #endif
523
524 if (!sslDateIsInTheFuture(current_row[cnlExp_date])) {
525 deleteRow(current_row, i);
526 removed_one = true;
527 break;
528 }
529 }
530
531 if (!removed_one)
532 return false;
533 return true;
534 }
535
536 bool Ssl::CertificateDb::deleteOldestCertificate()
537 {
538 if (!db)
539 return false;
540
541 #if OPENSSL_VERSION_NUMBER >= 0x1000004fL
542 if (sk_OPENSSL_PSTRING_num(db.get()->data) == 0)
543 #else
544 if (sk_num(db.get()->data) == 0)
545 #endif
546 return false;
547
548 #if OPENSSL_VERSION_NUMBER >= 0x1000004fL
549 const char **row = (const char **)sk_OPENSSL_PSTRING_value(db.get()->data, 0);
550 #else
551 const char **row = (const char **)sk_value(db.get()->data, 0);
552 #endif
553
554 deleteRow(row, 0);
555
556 return true;
557 }
558
559 bool Ssl::CertificateDb::deleteByHostname(std::string const & host)
560 {
561 if (!db)
562 return false;
563
564 #if OPENSSL_VERSION_NUMBER >= 0x1000004fL
565 for (int i = 0; i < sk_OPENSSL_PSTRING_num(db.get()->data); i++) {
566 const char ** current_row = ((const char **)sk_OPENSSL_PSTRING_value(db.get()->data, i));
567 #else
568 for (int i = 0; i < sk_num(db.get()->data); i++) {
569 const char ** current_row = ((const char **)sk_value(db.get()->data, i));
570 #endif
571 if (host == current_row[cnlName]) {
572 deleteRow(current_row, i);
573 return true;
574 }
575 }
576 return false;
577 }
578
579 bool Ssl::CertificateDb::IsEnabledDiskStore() const
580 {
581 return enabled_disk_store;
582 }