]>
git.ipfire.org Git - thirdparty/squid.git/blob - src/security/cert_generators/file/certificate_db.cc
2 * Copyright (C) 1996-2022 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 "base/HardFun.h"
11 #include "base/TextException.h"
12 #include "sbuf/Stream.h"
13 #include "security/cert_generators/file/certificate_db.h"
29 Ssl::Lock::Lock(std::string
const &aFilename
) :
32 hFile(INVALID_HANDLE_VALUE
)
39 bool Ssl::Lock::locked() const
42 return hFile
!= INVALID_HANDLE_VALUE
;
48 void Ssl::Lock::lock()
52 hFile
= CreateFile(TEXT(filename
.c_str()), GENERIC_READ
, 0, NULL
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
53 if (hFile
== INVALID_HANDLE_VALUE
)
55 fd
= open(filename
.c_str(), O_RDWR
);
58 throw TextException(ToSBuf("Failed to open file ", filename
), Here());
61 if (!LockFile(hFile
, 0, 0, 1, 0))
63 if (lockf(fd
, F_LOCK
, 0) != 0)
65 if (flock(fd
, LOCK_EX
) != 0)
67 throw TextException(ToSBuf("Failed to get a lock of ", filename
), Here());
70 void Ssl::Lock::unlock()
73 if (hFile
!= INVALID_HANDLE_VALUE
) {
74 UnlockFile(hFile
, 0, 0, 1, 0);
76 hFile
= INVALID_HANDLE_VALUE
;
81 lockf(fd
, F_ULOCK
, 0);
90 throw TextException(ToSBuf("Lock is already unlocked for ", filename
), Here());
99 Ssl::Locker::Locker(Lock
&aLock
, const SourceLocation
&aCaller
):
104 if (!lock
.locked()) {
110 Ssl::Locker::~Locker()
116 Ssl::CertificateDb::Row::Row() : width(cnlNumber
)
118 row
= (char **)OPENSSL_malloc(sizeof(char *) * (width
+ 1));
119 for (size_t i
= 0; i
< width
+ 1; ++i
)
123 Ssl::CertificateDb::Row::Row(char **aRow
, size_t aWidth
): width(aWidth
)
128 Ssl::CertificateDb::Row::~Row()
134 if ((max
= (void *)row
[width
]) != nullptr) {
135 // It is an openSSL allocated row. The TXT_DB_read function stores the
136 // index and row items one one memory segment. The row[width] points
137 // to the end of buffer. We have to check for items in the array which
138 // are not stored in this segment. These items should released.
139 for (size_t i
= 0; i
< width
+ 1; ++i
) {
140 if (((row
[i
] < (char *)row
) || (row
[i
] > max
)) && (row
[i
] != nullptr))
141 OPENSSL_free(row
[i
]);
144 for (size_t i
= 0; i
< width
+ 1; ++i
) {
146 OPENSSL_free(row
[i
]);
152 void Ssl::CertificateDb::Row::reset()
157 void Ssl::CertificateDb::Row::setValue(size_t cell
, char const * value
)
159 assert(cell
< width
);
164 row
[cell
] = static_cast<char *>(OPENSSL_malloc(sizeof(char) * (strlen(value
) + 1)));
165 memcpy(row
[cell
], value
, sizeof(char) * (strlen(value
) + 1));
170 char ** Ssl::CertificateDb::Row::getRow()
175 void Ssl::CertificateDb::sq_TXT_DB_delete(TXT_DB
*db
, const char **row
)
180 #if SQUID_SSLTXTDB_PSTRINGDATA
181 for (int i
= 0; i
< sk_OPENSSL_PSTRING_num(db
->data
); ++i
) {
182 #if SQUID_STACKOF_PSTRINGDATA_HACK
183 const char ** current_row
= ((const char **)sk_value(CHECKED_STACK_OF(OPENSSL_PSTRING
, db
->data
), i
));
185 const char ** current_row
= ((const char **)sk_OPENSSL_PSTRING_value(db
->data
, i
));
188 for (int i
= 0; i
< sk_num(db
->data
); ++i
) {
189 const char ** current_row
= ((const char **)sk_value(db
->data
, i
));
191 if (current_row
== row
) {
192 sq_TXT_DB_delete_row(db
, i
);
198 #define countof(arr) (sizeof(arr)/sizeof(*arr))
199 void Ssl::CertificateDb::sq_TXT_DB_delete_row(TXT_DB
*db
, int idx
) {
201 #if SQUID_SSLTXTDB_PSTRINGDATA
202 rrow
= (char **)sk_OPENSSL_PSTRING_delete(db
->data
, idx
);
204 rrow
= (char **)sk_delete(db
->data
, idx
);
210 Row
row(rrow
, cnlNumber
); // row wrapper used to free the rrow
212 const Columns db_indexes
[]= {cnlSerial
, cnlKey
};
213 for (unsigned int i
= 0; i
< countof(db_indexes
); ++i
) {
214 void *data
= nullptr;
215 #if SQUID_SSLTXTDB_PSTRINGDATA
216 if (LHASH_OF(OPENSSL_STRING
) *fieldIndex
= db
->index
[db_indexes
[i
]])
217 data
= lh_OPENSSL_STRING_delete(fieldIndex
, rrow
);
219 if (LHASH
*fieldIndex
= db
->index
[db_indexes
[i
]])
220 data
= OPENSSL_LH_delete(fieldIndex
, rrow
);
223 assert(data
== rrow
);
227 unsigned long Ssl::CertificateDb::index_serial_hash(const char **a
) {
228 const char *n
= a
[Ssl::CertificateDb::cnlSerial
];
231 return OPENSSL_LH_strhash(n
);
234 int Ssl::CertificateDb::index_serial_cmp(const char **a
, const char **b
) {
236 for (aa
= a
[Ssl::CertificateDb::cnlSerial
]; *aa
== '0'; ++aa
);
237 for (bb
= b
[Ssl::CertificateDb::cnlSerial
]; *bb
== '0'; ++bb
);
238 return strcmp(aa
, bb
);
241 unsigned long Ssl::CertificateDb::index_name_hash(const char **a
) {
242 return(OPENSSL_LH_strhash(a
[Ssl::CertificateDb::cnlKey
]));
245 int Ssl::CertificateDb::index_name_cmp(const char **a
, const char **b
) {
246 return(strcmp(a
[Ssl::CertificateDb::cnlKey
], b
[CertificateDb::cnlKey
]));
249 const std::string
Ssl::CertificateDb::db_file("index.txt");
250 const std::string
Ssl::CertificateDb::cert_dir("certs");
251 const std::string
Ssl::CertificateDb::size_file("size");
253 Ssl::CertificateDb::CertificateDb(std::string
const & aDb_path
, size_t aMax_db_size
, size_t aFs_block_size
)
255 db_full(aDb_path
+ "/" + db_file
),
256 cert_full(aDb_path
+ "/" + cert_dir
),
257 size_full(aDb_path
+ "/" + size_file
),
258 max_db_size(aMax_db_size
),
259 fs_block_size((aFs_block_size
? aFs_block_size
: 2048)),
264 Ssl::CertificateDb::find(std::string
const &key
, const Security::CertPointer
&expectedOrig
, Security::CertPointer
&cert
, Security::PrivateKeyPointer
&pkey
)
266 const Locker
locker(dbLock
, Here());
268 return pure_find(key
, expectedOrig
, cert
, pkey
);
271 bool Ssl::CertificateDb::purgeCert(std::string
const & key
) {
272 const Locker
locker(dbLock
, Here());
277 if (!deleteByKey(key
))
285 Ssl::CertificateDb::addCertAndPrivateKey(std::string
const &useKey
, const Security::CertPointer
&cert
, const Security::PrivateKeyPointer
&pkey
, const Security::CertPointer
&orig
)
287 const Locker
locker(dbLock
, Here());
289 if (!db
|| !cert
|| !pkey
)
296 ASN1_INTEGER
* ai
= X509_get_serialNumber(cert
.get());
297 std::string serial_string
;
298 Ssl::BIGNUM_Pointer
serial(ASN1_INTEGER_to_BN(ai
, nullptr));
300 const UniqueCString
hex_bn(BN_bn2hex(serial
.get()));
301 serial_string
= std::string(hex_bn
.get());
303 row
.setValue(cnlSerial
, serial_string
.c_str());
304 char ** rrow
= TXT_DB_get_by_index(db
.get(), cnlSerial
, row
.getRow());
305 // We are creating certificates with unique serial numbers. If the serial
306 // number is found in the database, the same certificate is already stored.
307 if (rrow
!= nullptr) {
308 // TODO: check if the stored row is valid.
312 // Remove any entry with given key
315 // check db size while trying to minimize calls to size()
316 size_t dbSize
= size();
317 if ((dbSize
== 0 && hasRows()) ||
318 (dbSize
> 0 && !hasRows()) ||
319 (dbSize
> 10 * max_db_size
)) {
320 // Invalid database size, rebuild
321 dbSize
= rebuildSize();
323 while (dbSize
> max_db_size
&& deleteInvalidCertificate()) {
324 dbSize
= size(); // get the current database size
325 // and try to find another invalid certificate if needed
327 // there are no more invalid ones, but there must be valid certificates
328 while (dbSize
> max_db_size
) {
329 if (!deleteOldestCertificate()) {
330 rebuildSize(); // No certificates in database.Update the size file.
331 save(); // Some entries may have been removed. Update the index file.
332 return false; // errors prevented us from freeing enough space
334 dbSize
= size(); // get the current database size
337 const auto tm
= X509_getm_notAfter(cert
.get());
338 row
.setValue(cnlExp_date
, std::string(reinterpret_cast<char *>(tm
->data
), tm
->length
).c_str());
339 const auto subject
= OneLineSummary(*X509_get_subject_name(cert
.get()));
340 row
.setValue(cnlName
, subject
.get());
341 row
.setValue(cnlKey
, useKey
.c_str());
343 if (!TXT_DB_insert(db
.get(), row
.getRow())) {
344 // failed to add index (???) but we may have already modified
345 // the database so save before exit
352 std::string
filename(cert_full
+ "/" + serial_string
+ ".pem");
353 if (!WriteEntry(filename
.c_str(), cert
, pkey
, orig
)) {
354 //remove row from txt_db and save
355 sq_TXT_DB_delete(db
.get(), (const char **)rrow
);
366 Ssl::CertificateDb::Create(std::string
const & db_path
) {
368 throw TextException("Path to db is empty", Here());
369 std::string
db_full(db_path
+ "/" + db_file
);
370 std::string
cert_full(db_path
+ "/" + cert_dir
);
371 std::string
size_full(db_path
+ "/" + size_file
);
373 if (mkdir(db_path
.c_str(), 0777))
374 throw TextException(ToSBuf("Cannot create ", db_path
), Here());
376 if (mkdir(cert_full
.c_str(), 0777))
377 throw TextException(ToSBuf("Cannot create ", cert_full
), Here());
379 std::ofstream
size(size_full
.c_str());
383 throw TextException(ToSBuf("Cannot open ", size_full
, " to open"), Here());
384 std::ofstream
db(db_full
.c_str());
386 throw TextException(ToSBuf("Cannot open ", db_full
, " to open"), Here());
390 Ssl::CertificateDb::Check(std::string
const & db_path
, size_t max_db_size
, size_t fs_block_size
) {
391 CertificateDb
db(db_path
, max_db_size
, fs_block_size
);
394 // Call readSize to force rebuild size file in the case it is corrupted
398 size_t Ssl::CertificateDb::rebuildSize()
401 #if SQUID_SSLTXTDB_PSTRINGDATA
402 for (int i
= 0; i
< sk_OPENSSL_PSTRING_num(db
.get()->data
); ++i
) {
403 #if SQUID_STACKOF_PSTRINGDATA_HACK
404 const char ** current_row
= ((const char **)sk_value(CHECKED_STACK_OF(OPENSSL_PSTRING
, db
.get()->data
), i
));
406 const char ** current_row
= ((const char **)sk_OPENSSL_PSTRING_value(db
.get()->data
, i
));
409 for (int i
= 0; i
< sk_num(db
.get()->data
); ++i
) {
410 const char ** current_row
= ((const char **)sk_value(db
.get()->data
, i
));
412 const std::string
filename(cert_full
+ "/" + current_row
[cnlSerial
] + ".pem");
413 const size_t fSize
= getFileSize(filename
);
421 Ssl::CertificateDb::pure_find(std::string
const &key
, const Security::CertPointer
&expectedOrig
, Security::CertPointer
&cert
, Security::PrivateKeyPointer
&pkey
)
427 row
.setValue(cnlKey
, key
.c_str());
429 char **rrow
= TXT_DB_get_by_index(db
.get(), cnlKey
, row
.getRow());
433 if (!sslDateIsInTheFuture(rrow
[cnlExp_date
]))
436 Security::CertPointer storedOrig
;
437 // read cert and pkey from file.
438 std::string
filename(cert_full
+ "/" + rrow
[cnlSerial
] + ".pem");
439 if (!ReadEntry(filename
.c_str(), cert
, pkey
, storedOrig
))
442 if (!storedOrig
&& !expectedOrig
)
445 return Ssl::CertificatesCmp(expectedOrig
, storedOrig
);
448 size_t Ssl::CertificateDb::size() {
452 void Ssl::CertificateDb::addSize(std::string
const & filename
) {
453 // readSize will rebuild 'size' file if missing or it is corrupted
454 size_t dbSize
= readSize();
455 dbSize
+= getFileSize(filename
);
459 void Ssl::CertificateDb::subSize(std::string
const & filename
) {
460 // readSize will rebuild 'size' file if missing or it is corrupted
461 size_t dbSize
= readSize();
462 const size_t fileSize
= getFileSize(filename
);
463 dbSize
= dbSize
> fileSize
? dbSize
- fileSize
: 0;
467 size_t Ssl::CertificateDb::readSize() {
468 std::ifstream
ifstr(size_full
.c_str());
470 if (!ifstr
|| !(ifstr
>> db_size
))
471 return rebuildSize();
475 void Ssl::CertificateDb::writeSize(size_t db_size
) {
476 std::ofstream
ofstr(size_full
.c_str());
478 throw TextException(ToSBuf("cannot write \"", size_full
, "\" file"), Here());
482 size_t Ssl::CertificateDb::getFileSize(std::string
const & filename
) {
483 std::ifstream
file(filename
.c_str(), std::ios::binary
);
486 file
.seekg(0, std::ios_base::end
);
487 const std::streampos file_size
= file
.tellg();
490 return ((static_cast<size_t>(file_size
) + fs_block_size
- 1) / fs_block_size
) * fs_block_size
;
493 void Ssl::CertificateDb::load() {
494 // Load db from file.
495 Ssl::BIO_Pointer
in(BIO_new(BIO_s_file()));
496 if (!in
|| BIO_read_filename(in
.get(), db_full
.c_str()) <= 0)
497 throw TextException(ToSBuf("Uninitialized SSL certificate database directory: ", db_path
, ". To initialize, run \"security_file_certgen -c -s ", db_path
, "\"."), Here());
499 bool corrupt
= false;
500 Ssl::TXT_DB_Pointer
temp_db(TXT_DB_read(in
.get(), cnlNumber
));
504 // Create indexes in db.
505 if (!corrupt
&& !TXT_DB_create_index(temp_db
.get(), cnlSerial
, nullptr, LHASH_HASH_FN(index_serial_hash
), LHASH_COMP_FN(index_serial_cmp
)))
508 if (!corrupt
&& !TXT_DB_create_index(temp_db
.get(), cnlKey
, nullptr, LHASH_HASH_FN(index_name_hash
), LHASH_COMP_FN(index_name_cmp
)))
512 throw TextException(ToSBuf("The SSL certificate database ", db_path
, " is corrupted. Please rebuild"), Here());
514 db
.reset(temp_db
.release());
517 void Ssl::CertificateDb::save() {
519 throw TextException("The certificates database is not loaded", Here());
521 // To save the db to file, create a new BIO with BIO file methods.
522 Ssl::BIO_Pointer
out(BIO_new(BIO_s_file()));
523 if (!out
|| !BIO_write_filename(out
.get(), const_cast<char *>(db_full
.c_str())))
524 throw TextException(ToSBuf("Failed to initialize ", db_full
, " file for writing"), Here());
526 if (TXT_DB_write(out
.get(), db
.get()) < 0)
527 throw TextException(ToSBuf("Failed to write ", db_full
, " file"), Here());
530 // Normally defined in defines.h file
531 void Ssl::CertificateDb::deleteRow(const char **row
, int rowIndex
) {
532 const std::string
filename(cert_full
+ "/" + row
[cnlSerial
] + ".pem");
533 sq_TXT_DB_delete_row(db
.get(), rowIndex
);
536 int ret
= remove(filename
.c_str());
537 if (ret
< 0 && errno
!= ENOENT
)
538 throw TextException(ToSBuf("Failed to remove certificate file ", filename
, " from db"), Here());
541 bool Ssl::CertificateDb::deleteInvalidCertificate() {
545 bool removed_one
= false;
546 #if SQUID_SSLTXTDB_PSTRINGDATA
547 for (int i
= 0; i
< sk_OPENSSL_PSTRING_num(db
.get()->data
); ++i
) {
548 #if SQUID_STACKOF_PSTRINGDATA_HACK
549 const char ** current_row
= ((const char **)sk_value(CHECKED_STACK_OF(OPENSSL_PSTRING
, db
.get()->data
), i
));
551 const char ** current_row
= ((const char **)sk_OPENSSL_PSTRING_value(db
.get()->data
, i
));
554 for (int i
= 0; i
< sk_num(db
.get()->data
); ++i
) {
555 const char ** current_row
= ((const char **)sk_value(db
.get()->data
, i
));
558 if (!sslDateIsInTheFuture(current_row
[cnlExp_date
])) {
559 deleteRow(current_row
, i
);
570 bool Ssl::CertificateDb::deleteOldestCertificate()
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));
579 const char **row
= (const char **)sk_OPENSSL_PSTRING_value(db
.get()->data
, 0);
582 const char **row
= (const char **)sk_value(db
.get()->data
, 0);
591 Ssl::CertificateDb::deleteByKey(std::string
const & key
) {
595 #if SQUID_SSLTXTDB_PSTRINGDATA
596 for (int i
= 0; i
< sk_OPENSSL_PSTRING_num(db
.get()->data
); ++i
) {
597 #if SQUID_STACKOF_PSTRINGDATA_HACK
598 const char ** current_row
= ((const char **)sk_value(CHECKED_STACK_OF(OPENSSL_PSTRING
, db
.get()->data
), i
));
600 const char ** current_row
= ((const char **)sk_OPENSSL_PSTRING_value(db
.get()->data
, i
));
603 for (int i
= 0; i
< sk_num(db
.get()->data
); ++i
) {
604 const char ** current_row
= ((const char **)sk_value(db
.get()->data
, i
));
606 if (key
== current_row
[cnlKey
]) {
607 deleteRow(current_row
, i
);
614 bool Ssl::CertificateDb::hasRows() const
619 #if SQUID_SSLTXTDB_PSTRINGDATA
620 if (sk_OPENSSL_PSTRING_num(db
.get()->data
) == 0)
622 if (sk_num(db
.get()->data
) == 0)
629 Ssl::CertificateDb::WriteEntry(const std::string
&filename
, const Security::CertPointer
&cert
, const Security::PrivateKeyPointer
&pkey
, const Security::CertPointer
&orig
)
631 Ssl::BIO_Pointer bio
;
632 if (!Ssl::OpenCertsFileForWriting(bio
, filename
.c_str()))
634 if (!Ssl::WriteX509Certificate(bio
, cert
))
636 if (!Ssl::WritePrivateKey(bio
, pkey
))
638 if (orig
&& !Ssl::WriteX509Certificate(bio
, orig
))
644 Ssl::CertificateDb::ReadEntry(std::string filename
, Security::CertPointer
&cert
, Security::PrivateKeyPointer
&pkey
, Security::CertPointer
&orig
)
646 Ssl::BIO_Pointer bio
;
647 if (!Ssl::OpenCertsFileForReading(bio
, filename
.c_str()))
650 cert
= Ssl::ReadCertificate(bio
);
652 if (!Ssl::ReadPrivateKey(bio
, pkey
, nullptr))
655 orig
= Ssl::ReadOptionalCertificate(bio
);