2 * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
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.
10 #include "ssl/certificate_db.h"
25 #define HERE "(ssl_crtd) " << __FILE__ << ':' << __LINE__ << ": "
27 Ssl::Lock::Lock(std::string
const &aFilename
) :
30 hFile(INVALID_HANDLE_VALUE
)
37 bool Ssl::Lock::locked() const
40 return hFile
!= INVALID_HANDLE_VALUE
;
46 void Ssl::Lock::lock()
50 hFile
= CreateFile(TEXT(filename
.c_str()), GENERIC_READ
, 0, NULL
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
51 if (hFile
== INVALID_HANDLE_VALUE
)
53 fd
= open(filename
.c_str(), 0);
56 throw std::runtime_error("Failed to open file " + filename
);
59 if (!LockFile(hFile
, 0, 0, 1, 0))
61 if (flock(fd
, LOCK_EX
) != 0)
63 throw std::runtime_error("Failed to get a lock of " + filename
);
66 void Ssl::Lock::unlock()
69 if (hFile
!= INVALID_HANDLE_VALUE
) {
70 UnlockFile(hFile
, 0, 0, 1, 0);
72 hFile
= INVALID_HANDLE_VALUE
;
82 throw std::runtime_error("Lock is already unlocked for " + filename
);
91 Ssl::Locker::Locker(Lock
&aLock
, const char *aFileName
, int aLineNo
):
92 weLocked(false), lock(aLock
), fileName(aFileName
), lineNo(aLineNo
)
100 Ssl::Locker::~Locker()
106 Ssl::CertificateDb::Row::Row()
109 row
= (char **)OPENSSL_malloc(sizeof(char *) * (width
+ 1));
110 for (size_t i
= 0; i
< width
+ 1; ++i
)
114 Ssl::CertificateDb::Row::Row(char **aRow
, size_t aWidth
): width(aWidth
)
119 Ssl::CertificateDb::Row::~Row()
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
]);
135 for (size_t i
= 0; i
< width
+ 1; ++i
) {
137 OPENSSL_free(row
[i
]);
143 void Ssl::CertificateDb::Row::reset()
148 void Ssl::CertificateDb::Row::setValue(size_t cell
, char const * value
)
150 assert(cell
< width
);
155 row
[cell
] = static_cast<char *>(OPENSSL_malloc(sizeof(char) * (strlen(value
) + 1)));
156 memcpy(row
[cell
], value
, sizeof(char) * (strlen(value
) + 1));
161 char ** Ssl::CertificateDb::Row::getRow()
166 void Ssl::CertificateDb::sq_TXT_DB_delete(TXT_DB
*db
, const char **row
)
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
));
176 const char ** current_row
= ((const char **)sk_OPENSSL_PSTRING_value(db
->data
, i
));
179 for (int i
= 0; i
< sk_num(db
->data
); ++i
) {
180 const char ** current_row
= ((const char **)sk_value(db
->data
, i
));
182 if (current_row
== row
) {
183 sq_TXT_DB_delete_row(db
, i
);
189 #define countof(arr) (sizeof(arr)/sizeof(*arr))
190 void Ssl::CertificateDb::sq_TXT_DB_delete_row(TXT_DB
*db
, int idx
) {
192 #if SQUID_SSLTXTDB_PSTRINGDATA
193 rrow
= (char **)sk_OPENSSL_PSTRING_delete(db
->data
, idx
);
195 rrow
= (char **)sk_delete(db
->data
, idx
);
201 Row
row(rrow
, cnlNumber
); // row wrapper used to free the rrow
203 const Columns db_indexes
[]={cnlSerial
, cnlName
};
204 for (unsigned int i
= 0; i
< countof(db_indexes
); ++i
) {
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
);
210 if (LHASH
*fieldIndex
= db
->index
[db_indexes
[i
]])
211 data
= lh_delete(fieldIndex
, rrow
);
214 assert(data
== rrow
);
218 unsigned long Ssl::CertificateDb::index_serial_hash(const char **a
) {
219 const char *n
= a
[Ssl::CertificateDb::cnlSerial
];
222 return lh_strhash(n
);
225 int Ssl::CertificateDb::index_serial_cmp(const char **a
, const char **b
) {
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
);
232 unsigned long Ssl::CertificateDb::index_name_hash(const char **a
) {
233 return(lh_strhash(a
[Ssl::CertificateDb::cnlName
]));
236 int Ssl::CertificateDb::index_name_cmp(const char **a
, const char **b
) {
237 return(strcmp(a
[Ssl::CertificateDb::cnlName
], b
[CertificateDb::cnlName
]));
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");
244 Ssl::CertificateDb::CertificateDb(std::string
const & aDb_path
, size_t aMax_db_size
, size_t aFs_block_size
)
246 db_full(aDb_path
+ "/" + db_file
),
247 cert_full(aDb_path
+ "/" + cert_dir
),
248 size_full(aDb_path
+ "/" + size_file
),
250 max_db_size(aMax_db_size
),
251 fs_block_size(aFs_block_size
),
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.");
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
);
263 return pure_find(host_name
, cert
, pkey
);
266 bool Ssl::CertificateDb::purgeCert(std::string
const & key
) {
267 const Locker
locker(dbLock
, Here
);
272 if (!deleteByHostname(key
))
279 bool Ssl::CertificateDb::addCertAndPrivateKey(Ssl::X509_Pointer
& cert
, Ssl::EVP_PKEY_Pointer
& pkey
, std::string
const & useName
) {
280 const Locker
locker(dbLock
, Here
);
282 if (!db
|| !cert
|| !pkey
)
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
));
289 TidyPointer
<char, tidyFree
> hex_bn(BN_bn2hex(serial
.get()));
290 serial_string
= std::string(hex_bn
.get());
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.
297 // TODO: check if the stored row is valid.
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());
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
);
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
321 // there are no more invalid ones, but there must be valid certificates
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
327 } while (size() > max_db_size
);
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());
338 TidyPointer
<char, tidyFree
> subject(X509_NAME_oneline(X509_get_subject_name(cert
.get()), NULL
, 0));
339 row
.setValue(cnlName
, subject
.get());
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
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
);
364 void Ssl::CertificateDb::create(std::string
const & 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
);
371 if (mkdir(db_path
.c_str(), 0777))
372 throw std::runtime_error("Cannot create " + db_path
);
374 if (mkdir(cert_full
.c_str(), 0777))
375 throw std::runtime_error("Cannot create " + cert_full
);
377 std::ofstream
size(size_full
.c_str());
381 throw std::runtime_error("Cannot open " + size_full
+ " to open");
382 std::ofstream
db(db_full
.c_str());
384 throw std::runtime_error("Cannot open " + db_full
+ " to open");
387 void Ssl::CertificateDb::check(std::string
const & db_path
, size_t max_db_size
) {
388 CertificateDb
db(db_path
, max_db_size
, 0);
392 bool Ssl::CertificateDb::pure_find(std::string
const & host_name
, Ssl::X509_Pointer
& cert
, Ssl::EVP_PKEY_Pointer
& pkey
) {
397 row
.setValue(cnlName
, host_name
.c_str());
399 char **rrow
= TXT_DB_get_by_index(db
.get(), cnlName
, row
.getRow());
403 if (!sslDateIsInTheFuture(rrow
[cnlExp_date
]))
406 // read cert and pkey from file.
407 std::string
filename(cert_full
+ "/" + rrow
[cnlSerial
] + ".pem");
408 readCertAndPrivateKeyFromFiles(cert
, pkey
, filename
.c_str(), NULL
);
414 size_t Ssl::CertificateDb::size() const {
418 void Ssl::CertificateDb::addSize(std::string
const & filename
) {
419 writeSize(readSize() + getFileSize(filename
));
422 void Ssl::CertificateDb::subSize(std::string
const & filename
) {
423 writeSize(readSize() - getFileSize(filename
));
426 size_t Ssl::CertificateDb::readSize() const {
427 std::ifstream
ifstr(size_full
.c_str());
428 if (!ifstr
&& enabled_disk_store
)
429 throw std::runtime_error("cannot open for reading: " + size_full
);
431 if (!(ifstr
>> db_size
))
432 throw std::runtime_error("error while reading " + size_full
);
436 void Ssl::CertificateDb::writeSize(size_t db_size
) {
437 std::ofstream
ofstr(size_full
.c_str());
438 if (!ofstr
&& enabled_disk_store
)
439 throw std::runtime_error("cannot write \"" + size_full
+ "\" file");
443 size_t Ssl::CertificateDb::getFileSize(std::string
const & filename
) {
444 std::ifstream
file(filename
.c_str(), std::ios::binary
);
445 file
.seekg(0, std::ios_base::end
);
446 size_t file_size
= file
.tellg();
447 return ((file_size
+ fs_block_size
- 1) / fs_block_size
) * fs_block_size
;
450 void Ssl::CertificateDb::load() {
451 // Load db from file.
452 Ssl::BIO_Pointer
in(BIO_new(BIO_s_file()));
453 if (!in
|| BIO_read_filename(in
.get(), db_full
.c_str()) <= 0)
454 throw std::runtime_error("Uninitialized SSL certificate database directory: " + db_path
+ ". To initialize, run \"ssl_crtd -c -s " + db_path
+ "\".");
456 bool corrupt
= false;
457 Ssl::TXT_DB_Pointer
temp_db(TXT_DB_read(in
.get(), cnlNumber
));
461 // Create indexes in db.
462 if (!corrupt
&& !TXT_DB_create_index(temp_db
.get(), cnlSerial
, NULL
, LHASH_HASH_FN(index_serial_hash
), LHASH_COMP_FN(index_serial_cmp
)))
465 if (!corrupt
&& !TXT_DB_create_index(temp_db
.get(), cnlName
, NULL
, LHASH_HASH_FN(index_name_hash
), LHASH_COMP_FN(index_name_cmp
)))
469 throw std::runtime_error("The SSL certificate database " + db_path
+ " is corrupted. Please rebuild");
471 db
.reset(temp_db
.release());
474 void Ssl::CertificateDb::save() {
476 throw std::runtime_error("The certificates database is not loaded");;
478 // To save the db to file, create a new BIO with BIO file methods.
479 Ssl::BIO_Pointer
out(BIO_new(BIO_s_file()));
480 if (!out
|| !BIO_write_filename(out
.get(), const_cast<char *>(db_full
.c_str())))
481 throw std::runtime_error("Failed to initialize " + db_full
+ " file for writing");;
483 if (TXT_DB_write(out
.get(), db
.get()) < 0)
484 throw std::runtime_error("Failed to write " + db_full
+ " file");
487 // Normally defined in defines.h file
488 void Ssl::CertificateDb::deleteRow(const char **row
, int rowIndex
) {
489 const std::string
filename(cert_full
+ "/" + row
[cnlSerial
] + ".pem");
490 sq_TXT_DB_delete_row(db
.get(), rowIndex
);
493 int ret
= remove(filename
.c_str());
494 if (ret
< 0 && errno
!= ENOENT
)
495 throw std::runtime_error("Failed to remove certficate file " + filename
+ " from db");
498 bool Ssl::CertificateDb::deleteInvalidCertificate() {
502 bool removed_one
= false;
503 #if SQUID_SSLTXTDB_PSTRINGDATA
504 for (int i
= 0; i
< sk_OPENSSL_PSTRING_num(db
.get()->data
); ++i
) {
505 #if SQUID_STACKOF_PSTRINGDATA_HACK
506 const char ** current_row
= ((const char **)sk_value(CHECKED_STACK_OF(OPENSSL_PSTRING
, db
.get()->data
), i
));
508 const char ** current_row
= ((const char **)sk_OPENSSL_PSTRING_value(db
.get()->data
, i
));
511 for (int i
= 0; i
< sk_num(db
.get()->data
); ++i
) {
512 const char ** current_row
= ((const char **)sk_value(db
.get()->data
, i
));
515 if (!sslDateIsInTheFuture(current_row
[cnlExp_date
])) {
516 deleteRow(current_row
, i
);
527 bool Ssl::CertificateDb::deleteOldestCertificate() {
531 #if SQUID_SSLTXTDB_PSTRINGDATA
532 if (sk_OPENSSL_PSTRING_num(db
.get()->data
) == 0)
534 if (sk_num(db
.get()->data
) == 0)
538 #if SQUID_SSLTXTDB_PSTRINGDATA
539 #if SQUID_STACKOF_PSTRINGDATA_HACK
540 const char **row
= ((const char **)sk_value(CHECKED_STACK_OF(OPENSSL_PSTRING
, db
.get()->data
), 0));
542 const char **row
= (const char **)sk_OPENSSL_PSTRING_value(db
.get()->data
, 0);
545 const char **row
= (const char **)sk_value(db
.get()->data
, 0);
553 bool Ssl::CertificateDb::deleteByHostname(std::string
const & host
) {
557 #if SQUID_SSLTXTDB_PSTRINGDATA
558 for (int i
= 0; i
< sk_OPENSSL_PSTRING_num(db
.get()->data
); ++i
) {
559 #if SQUID_STACKOF_PSTRINGDATA_HACK
560 const char ** current_row
= ((const char **)sk_value(CHECKED_STACK_OF(OPENSSL_PSTRING
, db
.get()->data
), i
));
562 const char ** current_row
= ((const char **)sk_OPENSSL_PSTRING_value(db
.get()->data
, i
));
565 for (int i
= 0; i
< sk_num(db
.get()->data
); ++i
) {
566 const char ** current_row
= ((const char **)sk_value(db
.get()->data
, i
));
568 if (host
== current_row
[cnlName
]) {
569 deleteRow(current_row
, i
);
576 bool Ssl::CertificateDb::IsEnabledDiskStore() const {
577 return enabled_disk_store
;