/*
- * $Id$
+ * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
*/
#include "squid.h"
#include "ssl/certificate_db.h"
-#if HAVE_ERRNO_H
-#include <errno.h>
-#endif
-#if HAVE_FSTREAM
+
+#include <cerrno>
#include <fstream>
-#endif
-#if HAVE_STDEXCEPT
#include <stdexcept>
-#endif
#if HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#define HERE "(ssl_crtd) " << __FILE__ << ':' << __LINE__ << ": "
Ssl::Lock::Lock(std::string const &aFilename) :
- filename(aFilename),
-#if _SQUID_MSWIN_
- hFile(INVALID_HANDLE_VALUE)
+ filename(aFilename),
+#if _SQUID_WINDOWS_
+ hFile(INVALID_HANDLE_VALUE)
#else
- fd(-1)
+ fd(-1)
#endif
{
}
bool Ssl::Lock::locked() const
{
-#if _SQUID_MSWIN_
+#if _SQUID_WINDOWS_
return hFile != INVALID_HANDLE_VALUE;
#else
return fd != -1;
void Ssl::Lock::lock()
{
-#if _SQUID_MSWIN_
+#if _SQUID_WINDOWS_
hFile = CreateFile(TEXT(filename.c_str()), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
#else
- fd = open(filename.c_str(), 0);
+ fd = open(filename.c_str(), O_RDWR);
if (fd == -1)
#endif
throw std::runtime_error("Failed to open file " + filename);
-
-#if _SQUID_MSWIN_
+#if _SQUID_WINDOWS_
if (!LockFile(hFile, 0, 0, 1, 0))
#else
- if (flock(fd, LOCK_EX) != 0)
+ if (lockf(fd, F_LOCK, 0) != 0)
#endif
throw std::runtime_error("Failed to get a lock of " + filename);
}
void Ssl::Lock::unlock()
{
-#if _SQUID_MSWIN_
+#if _SQUID_WINDOWS_
if (hFile != INVALID_HANDLE_VALUE) {
UnlockFile(hFile, 0, 0, 1, 0);
CloseHandle(hFile);
}
#else
if (fd != -1) {
- flock(fd, LOCK_UN);
+ lockf(fd, F_ULOCK, 0);
close(fd);
fd = -1;
}
}
Ssl::Locker::Locker(Lock &aLock, const char *aFileName, int aLineNo):
- weLocked(false), lock(aLock), fileName(aFileName), lineNo(aLineNo)
+ weLocked(false), lock(aLock), fileName(aFileName), lineNo(aLineNo)
{
if (!lock.locked()) {
lock.lock();
}
Ssl::CertificateDb::Row::Row()
- : width(cnlNumber)
+ : width(cnlNumber)
{
- row = new char *[width + 1];
+ row = (char **)OPENSSL_malloc(sizeof(char *) * (width + 1));
for (size_t i = 0; i < width + 1; ++i)
row[i] = NULL;
}
+Ssl::CertificateDb::Row::Row(char **aRow, size_t aWidth): width(aWidth)
+{
+ row = aRow;
+}
+
Ssl::CertificateDb::Row::~Row()
{
- if (row) {
+ if (!row)
+ return;
+
+ void *max;
+ if ((max = (void *)row[width]) != NULL) {
+ // It is an openSSL allocated row. The TXT_DB_read function stores the
+ // index and row items one one memory segment. The row[width] points
+ // to the end of buffer. We have to check for items in the array which
+ // are not stored in this segment. These items should released.
for (size_t i = 0; i < width + 1; ++i) {
- delete[](row[i]);
+ if (((row[i] < (char *)row) || (row[i] > max)) && (row[i] != NULL))
+ OPENSSL_free(row[i]);
+ }
+ } else {
+ for (size_t i = 0; i < width + 1; ++i) {
+ if (row[i])
+ OPENSSL_free(row[i]);
}
- delete[](row);
}
+ OPENSSL_free(row);
}
void Ssl::CertificateDb::Row::reset()
free(row[cell]);
}
if (value) {
- row[cell] = static_cast<char *>(malloc(sizeof(char) * (strlen(value) + 1)));
+ row[cell] = static_cast<char *>(OPENSSL_malloc(sizeof(char) * (strlen(value) + 1)));
memcpy(row[cell], value, sizeof(char) * (strlen(value) + 1));
} else
row[cell] = NULL;
return row;
}
-unsigned long Ssl::CertificateDb::index_serial_hash(const char **a)
+void Ssl::CertificateDb::sq_TXT_DB_delete(TXT_DB *db, const char **row)
{
+ if (!db)
+ return;
+
+#if SQUID_SSLTXTDB_PSTRINGDATA
+ for (int i = 0; i < sk_OPENSSL_PSTRING_num(db->data); ++i) {
+#if SQUID_STACKOF_PSTRINGDATA_HACK
+ const char ** current_row = ((const char **)sk_value(CHECKED_STACK_OF(OPENSSL_PSTRING, db->data), i));
+#else
+ const char ** current_row = ((const char **)sk_OPENSSL_PSTRING_value(db->data, i));
+#endif
+#else
+ for (int i = 0; i < sk_num(db->data); ++i) {
+ const char ** current_row = ((const char **)sk_value(db->data, i));
+#endif
+ if (current_row == row) {
+ sq_TXT_DB_delete_row(db, i);
+ return;
+ }
+ }
+}
+
+#define countof(arr) (sizeof(arr)/sizeof(*arr))
+void Ssl::CertificateDb::sq_TXT_DB_delete_row(TXT_DB *db, int idx) {
+ char **rrow;
+#if SQUID_SSLTXTDB_PSTRINGDATA
+ rrow = (char **)sk_OPENSSL_PSTRING_delete(db->data, idx);
+#else
+ rrow = (char **)sk_delete(db->data, idx);
+#endif
+
+ if (!rrow)
+ return;
+
+ Row row(rrow, cnlNumber); // row wrapper used to free the rrow
+
+ const Columns db_indexes[]= {cnlSerial, cnlName};
+ for (unsigned int i = 0; i < countof(db_indexes); ++i) {
+ void *data = NULL;
+#if SQUID_SSLTXTDB_PSTRINGDATA
+ if (LHASH_OF(OPENSSL_STRING) *fieldIndex = db->index[db_indexes[i]])
+ data = lh_OPENSSL_STRING_delete(fieldIndex, rrow);
+#else
+ if (LHASH *fieldIndex = db->index[db_indexes[i]])
+ data = lh_delete(fieldIndex, rrow);
+#endif
+ if (data)
+ assert(data == rrow);
+ }
+}
+
+unsigned long Ssl::CertificateDb::index_serial_hash(const char **a) {
const char *n = a[Ssl::CertificateDb::cnlSerial];
while (*n == '0')
++n;
return lh_strhash(n);
}
-int Ssl::CertificateDb::index_serial_cmp(const char **a, const char **b)
-{
+int Ssl::CertificateDb::index_serial_cmp(const char **a, const char **b) {
const char *aa, *bb;
for (aa = a[Ssl::CertificateDb::cnlSerial]; *aa == '0'; ++aa);
for (bb = b[Ssl::CertificateDb::cnlSerial]; *bb == '0'; ++bb);
return strcmp(aa, bb);
}
-unsigned long Ssl::CertificateDb::index_name_hash(const char **a)
-{
+unsigned long Ssl::CertificateDb::index_name_hash(const char **a) {
return(lh_strhash(a[Ssl::CertificateDb::cnlName]));
}
-int Ssl::CertificateDb::index_name_cmp(const char **a, const char **b)
-{
+int Ssl::CertificateDb::index_name_cmp(const char **a, const char **b) {
return(strcmp(a[Ssl::CertificateDb::cnlName], b[CertificateDb::cnlName]));
}
const std::string Ssl::CertificateDb::size_file("size");
Ssl::CertificateDb::CertificateDb(std::string const & aDb_path, size_t aMax_db_size, size_t aFs_block_size)
- : db_path(aDb_path),
- db_full(aDb_path + "/" + db_file),
- cert_full(aDb_path + "/" + cert_dir),
- size_full(aDb_path + "/" + size_file),
- db(NULL),
- max_db_size(aMax_db_size),
- fs_block_size(aFs_block_size),
- dbLock(db_full),
- enabled_disk_store(true)
-{
+ : db_path(aDb_path),
+ db_full(aDb_path + "/" + db_file),
+ cert_full(aDb_path + "/" + cert_dir),
+ size_full(aDb_path + "/" + size_file),
+ db(NULL),
+ max_db_size(aMax_db_size),
+ fs_block_size((aFs_block_size ? aFs_block_size : 2048)),
+ dbLock(db_full),
+ enabled_disk_store(true) {
if (db_path.empty() && !max_db_size)
enabled_disk_store = false;
else if ((db_path.empty() && max_db_size) || (!db_path.empty() && !max_db_size))
throw std::runtime_error("ssl_crtd is missing the required parameter. There should be -s and -M parameters together.");
}
-bool Ssl::CertificateDb::find(std::string const & host_name, Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey)
-{
+bool Ssl::CertificateDb::find(std::string const & host_name, Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey) {
const Locker locker(dbLock, Here);
load();
return pure_find(host_name, cert, pkey);
}
-bool Ssl::CertificateDb::purgeCert(std::string const & key)
-{
+bool Ssl::CertificateDb::purgeCert(std::string const & key) {
const Locker locker(dbLock, Here);
load();
if (!db)
return true;
}
-bool Ssl::CertificateDb::addCertAndPrivateKey(Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey, std::string const & useName)
-{
+bool Ssl::CertificateDb::addCertAndPrivateKey(Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey, std::string const & useName) {
const Locker locker(dbLock, Here);
load();
if (!db || !cert || !pkey)
char ** rrow = TXT_DB_get_by_index(db.get(), cnlSerial, row.getRow());
// We are creating certificates with unique serial numbers. If the serial
// number is found in the database, the same certificate is already stored.
- if (rrow != NULL)
+ if (rrow != NULL) {
+ // TODO: check if the stored row is valid.
return true;
+ }
{
TidyPointer<char, tidyFree> subject(X509_NAME_oneline(X509_get_subject_name(cert.get()), NULL, 0));
- if (pure_find(useName.empty() ? subject.get() : useName, cert, pkey))
+ Ssl::X509_Pointer findCert;
+ Ssl::EVP_PKEY_Pointer findPkey;
+ if (pure_find(useName.empty() ? subject.get() : useName, findCert, findPkey)) {
+ // Replace with database certificate
+ cert.reset(findCert.release());
+ pkey.reset(findPkey.release());
return true;
+ }
+ // pure_find may fail because the entry is expired, or because the
+ // certs file is corrupted. Remove any entry with given hostname
+ deleteByHostname(useName.empty() ? subject.get() : useName);
}
// check db size while trying to minimize calls to size()
// there are no more invalid ones, but there must be valid certificates
do {
- if (!deleteOldestCertificate())
+ if (!deleteOldestCertificate()) {
+ save(); // Some entries may have been removed. Update the index file.
return false; // errors prevented us from freeing enough space
+ }
} while (size() > max_db_size);
break;
}
row.setValue(cnlName, subject.get());
}
- if (!TXT_DB_insert(db.get(), row.getRow()))
+ if (!TXT_DB_insert(db.get(), row.getRow())) {
+ // failed to add index (???) but we may have already modified
+ // the database so save before exit
+ save();
return false;
-
+ }
+ rrow = row.getRow();
row.reset();
+
std::string filename(cert_full + "/" + serial_string + ".pem");
- if (!writeCertAndPrivateKeyToFile(cert, pkey, filename.c_str()))
+ if (!writeCertAndPrivateKeyToFile(cert, pkey, filename.c_str())) {
+ //remove row from txt_db and save
+ sq_TXT_DB_delete(db.get(), (const char **)rrow);
+ save();
return false;
+ }
addSize(filename);
save();
return true;
}
-void Ssl::CertificateDb::create(std::string const & db_path)
-{
+void Ssl::CertificateDb::create(std::string const & db_path) {
if (db_path == "")
throw std::runtime_error("Path to db is empty");
std::string db_full(db_path + "/" + db_file);
throw std::runtime_error("Cannot open " + db_full + " to open");
}
-void Ssl::CertificateDb::check(std::string const & db_path, size_t max_db_size)
-{
- CertificateDb db(db_path, max_db_size, 0);
+void Ssl::CertificateDb::check(std::string const & db_path, size_t max_db_size, size_t fs_block_size) {
+ CertificateDb db(db_path, max_db_size, fs_block_size);
db.load();
+
+ // Call readSize to force rebuild size file in the case it is corrupted
+ (void)db.readSize();
}
-bool Ssl::CertificateDb::pure_find(std::string const & host_name, Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey)
+size_t Ssl::CertificateDb::rebuildSize()
{
+ size_t dbSize = 0;
+#if SQUID_SSLTXTDB_PSTRINGDATA
+ for (int i = 0; i < sk_OPENSSL_PSTRING_num(db.get()->data); ++i) {
+#if SQUID_STACKOF_PSTRINGDATA_HACK
+ const char ** current_row = ((const char **)sk_value(CHECKED_STACK_OF(OPENSSL_PSTRING, db.get()->data), i));
+#else
+ const char ** current_row = ((const char **)sk_OPENSSL_PSTRING_value(db.get()->data, i));
+#endif
+#else
+ for (int i = 0; i < sk_num(db.get()->data); ++i) {
+ const char ** current_row = ((const char **)sk_value(db.get()->data, i));
+#endif
+ const std::string filename(cert_full + "/" + current_row[cnlSerial] + ".pem");
+ const size_t fSize = getFileSize(filename);
+ dbSize += fSize;
+ }
+ writeSize(dbSize);
+ return dbSize;
+}
+
+bool Ssl::CertificateDb::pure_find(std::string const & host_name, Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey) {
if (!db)
return false;
if (rrow == NULL)
return false;
- if (!sslDateIsInTheFuture(rrow[cnlExp_date])) {
- deleteByHostname(rrow[cnlName]);
+ if (!sslDateIsInTheFuture(rrow[cnlExp_date]))
return false;
- }
// read cert and pkey from file.
std::string filename(cert_full + "/" + rrow[cnlSerial] + ".pem");
return true;
}
-size_t Ssl::CertificateDb::size() const
-{
+size_t Ssl::CertificateDb::size() {
return readSize();
}
-void Ssl::CertificateDb::addSize(std::string const & filename)
-{
- writeSize(readSize() + getFileSize(filename));
+void Ssl::CertificateDb::addSize(std::string const & filename) {
+ // readSize will rebuild 'size' file if missing or it is corrupted
+ size_t dbSize = readSize();
+ dbSize += getFileSize(filename);
+ writeSize(dbSize);
}
-void Ssl::CertificateDb::subSize(std::string const & filename)
-{
- writeSize(readSize() - getFileSize(filename));
+void Ssl::CertificateDb::subSize(std::string const & filename) {
+ // readSize will rebuild 'size' file if missing or it is corrupted
+ size_t dbSize = readSize();
+ dbSize -= getFileSize(filename);
+ writeSize(dbSize);
}
-size_t Ssl::CertificateDb::readSize() const
-{
- std::ifstream size_file(size_full.c_str());
- if (!size_file && enabled_disk_store)
- throw std::runtime_error("cannot open for reading: " + size_full);
+size_t Ssl::CertificateDb::readSize() {
+ std::ifstream ifstr(size_full.c_str());
size_t db_size = 0;
- if (!(size_file >> db_size))
- throw std::runtime_error("error while reading " + size_full);
+ if (!ifstr || !(ifstr >> db_size))
+ return rebuildSize();
return db_size;
}
-void Ssl::CertificateDb::writeSize(size_t db_size)
-{
- std::ofstream size_file(size_full.c_str());
- if (!size_file && enabled_disk_store)
+void Ssl::CertificateDb::writeSize(size_t db_size) {
+ std::ofstream ofstr(size_full.c_str());
+ if (!ofstr)
throw std::runtime_error("cannot write \"" + size_full + "\" file");
- size_file << db_size;
+ ofstr << db_size;
}
-size_t Ssl::CertificateDb::getFileSize(std::string const & filename)
-{
+size_t Ssl::CertificateDb::getFileSize(std::string const & filename) {
std::ifstream file(filename.c_str(), std::ios::binary);
+ if (!file)
+ return 0;
file.seekg(0, std::ios_base::end);
size_t file_size = file.tellg();
return ((file_size + fs_block_size - 1) / fs_block_size) * fs_block_size;
}
-void Ssl::CertificateDb::load()
-{
+void Ssl::CertificateDb::load() {
// Load db from file.
Ssl::BIO_Pointer in(BIO_new(BIO_s_file()));
if (!in || BIO_read_filename(in.get(), db_full.c_str()) <= 0)
corrupt = true;
// Create indexes in db.
-#if OPENSSL_VERSION_NUMBER >= 0x1000004fL
- if (!corrupt && !TXT_DB_create_index(temp_db.get(), cnlSerial, NULL, LHASH_HASH_FN(index_serial), LHASH_COMP_FN(index_serial)))
- corrupt = true;
-
- if (!corrupt && !TXT_DB_create_index(temp_db.get(), cnlName, NULL, LHASH_HASH_FN(index_name), LHASH_COMP_FN(index_name)))
- corrupt = true;
-#else
if (!corrupt && !TXT_DB_create_index(temp_db.get(), cnlSerial, NULL, LHASH_HASH_FN(index_serial_hash), LHASH_COMP_FN(index_serial_cmp)))
corrupt = true;
if (!corrupt && !TXT_DB_create_index(temp_db.get(), cnlName, NULL, LHASH_HASH_FN(index_name_hash), LHASH_COMP_FN(index_name_cmp)))
corrupt = true;
-#endif
if (corrupt)
throw std::runtime_error("The SSL certificate database " + db_path + " is corrupted. Please rebuild");
db.reset(temp_db.release());
}
-void Ssl::CertificateDb::save()
-{
+void Ssl::CertificateDb::save() {
if (!db)
throw std::runtime_error("The certificates database is not loaded");;
}
// Normally defined in defines.h file
-#define countof(arr) (sizeof(arr)/sizeof(*arr))
-void Ssl::CertificateDb::deleteRow(const char **row, int rowIndex)
-{
+void Ssl::CertificateDb::deleteRow(const char **row, int rowIndex) {
const std::string filename(cert_full + "/" + row[cnlSerial] + ".pem");
-#if OPENSSL_VERSION_NUMBER >= 0x1000004fL
- sk_OPENSSL_PSTRING_delete(db.get()->data, rowIndex);
-#else
- sk_delete(db.get()->data, rowIndex);
-#endif
-
- const Columns db_indexes[]={cnlSerial, cnlName};
- for (unsigned int i = 0; i < countof(db_indexes); ++i) {
-#if OPENSSL_VERSION_NUMBER >= 0x1000004fL
- if (LHASH_OF(OPENSSL_STRING) *fieldIndex = db.get()->index[db_indexes[i]])
- lh_OPENSSL_STRING_delete(fieldIndex, (char **)row);
-#else
- if (LHASH *fieldIndex = db.get()->index[db_indexes[i]])
- lh_delete(fieldIndex, row);
-#endif
- }
+ sq_TXT_DB_delete_row(db.get(), rowIndex);
subSize(filename);
int ret = remove(filename.c_str());
throw std::runtime_error("Failed to remove certficate file " + filename + " from db");
}
-bool Ssl::CertificateDb::deleteInvalidCertificate()
-{
+bool Ssl::CertificateDb::deleteInvalidCertificate() {
if (!db)
return false;
bool removed_one = false;
-#if OPENSSL_VERSION_NUMBER >= 0x1000004fL
+#if SQUID_SSLTXTDB_PSTRINGDATA
for (int i = 0; i < sk_OPENSSL_PSTRING_num(db.get()->data); ++i) {
+#if SQUID_STACKOF_PSTRINGDATA_HACK
+ const char ** current_row = ((const char **)sk_value(CHECKED_STACK_OF(OPENSSL_PSTRING, db.get()->data), i));
+#else
const char ** current_row = ((const char **)sk_OPENSSL_PSTRING_value(db.get()->data, i));
+#endif
#else
for (int i = 0; i < sk_num(db.get()->data); ++i) {
const char ** current_row = ((const char **)sk_value(db.get()->data, i));
return true;
}
-bool Ssl::CertificateDb::deleteOldestCertificate()
-{
+bool Ssl::CertificateDb::deleteOldestCertificate() {
if (!db)
return false;
-#if OPENSSL_VERSION_NUMBER >= 0x1000004fL
+#if SQUID_SSLTXTDB_PSTRINGDATA
if (sk_OPENSSL_PSTRING_num(db.get()->data) == 0)
#else
if (sk_num(db.get()->data) == 0)
#endif
return false;
-#if OPENSSL_VERSION_NUMBER >= 0x1000004fL
+#if SQUID_SSLTXTDB_PSTRINGDATA
+#if SQUID_STACKOF_PSTRINGDATA_HACK
+ const char **row = ((const char **)sk_value(CHECKED_STACK_OF(OPENSSL_PSTRING, db.get()->data), 0));
+#else
const char **row = (const char **)sk_OPENSSL_PSTRING_value(db.get()->data, 0);
+#endif
#else
const char **row = (const char **)sk_value(db.get()->data, 0);
#endif
return true;
}
-bool Ssl::CertificateDb::deleteByHostname(std::string const & host)
-{
+bool Ssl::CertificateDb::deleteByHostname(std::string const & host) {
if (!db)
return false;
-#if OPENSSL_VERSION_NUMBER >= 0x1000004fL
+#if SQUID_SSLTXTDB_PSTRINGDATA
for (int i = 0; i < sk_OPENSSL_PSTRING_num(db.get()->data); ++i) {
+#if SQUID_STACKOF_PSTRINGDATA_HACK
+ const char ** current_row = ((const char **)sk_value(CHECKED_STACK_OF(OPENSSL_PSTRING, db.get()->data), i));
+#else
const char ** current_row = ((const char **)sk_OPENSSL_PSTRING_value(db.get()->data, i));
+#endif
#else
for (int i = 0; i < sk_num(db.get()->data); ++i) {
const char ** current_row = ((const char **)sk_value(db.get()->data, i));
return false;
}
-bool Ssl::CertificateDb::IsEnabledDiskStore() const
-{
+bool Ssl::CertificateDb::IsEnabledDiskStore() const {
return enabled_disk_store;
}
+