]> git.ipfire.org Git - thirdparty/squid.git/blob - src/security/cert_generators/file/certificate_db.cc
Maintenance: Removed most NULLs using modernize-use-nullptr (#1075)
[thirdparty/squid.git] / src / security / cert_generators / file / certificate_db.cc
1 /*
2 * Copyright (C) 1996-2022 The Squid Software Foundation and contributors
3 *
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.
7 */
8
9 #include "squid.h"
10 #include "base/HardFun.h"
11 #include "base/TextException.h"
12 #include "sbuf/Stream.h"
13 #include "security/cert_generators/file/certificate_db.h"
14
15 #include <cerrno>
16 #include <fstream>
17 #include <memory>
18 #include <stdexcept>
19 #if HAVE_SYS_STAT_H
20 #include <sys/stat.h>
21 #endif
22 #if HAVE_FCNTL_H
23 #include <fcntl.h>
24 #endif
25 #if HAVE_SYS_FILE_H
26 #include <sys/file.h>
27 #endif
28
29 Ssl::Lock::Lock(std::string const &aFilename) :
30 filename(aFilename),
31 #if _SQUID_WINDOWS_
32 hFile(INVALID_HANDLE_VALUE)
33 #else
34 fd(-1)
35 #endif
36 {
37 }
38
39 bool Ssl::Lock::locked() const
40 {
41 #if _SQUID_WINDOWS_
42 return hFile != INVALID_HANDLE_VALUE;
43 #else
44 return fd != -1;
45 #endif
46 }
47
48 void Ssl::Lock::lock()
49 {
50
51 #if _SQUID_WINDOWS_
52 hFile = CreateFile(TEXT(filename.c_str()), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
53 if (hFile == INVALID_HANDLE_VALUE)
54 #else
55 fd = open(filename.c_str(), O_RDWR);
56 if (fd == -1)
57 #endif
58 throw TextException(ToSBuf("Failed to open file ", filename), Here());
59
60 #if _SQUID_WINDOWS_
61 if (!LockFile(hFile, 0, 0, 1, 0))
62 #elif _SQUID_SOLARIS_
63 if (lockf(fd, F_LOCK, 0) != 0)
64 #else
65 if (flock(fd, LOCK_EX) != 0)
66 #endif
67 throw TextException(ToSBuf("Failed to get a lock of ", filename), Here());
68 }
69
70 void Ssl::Lock::unlock()
71 {
72 #if _SQUID_WINDOWS_
73 if (hFile != INVALID_HANDLE_VALUE) {
74 UnlockFile(hFile, 0, 0, 1, 0);
75 CloseHandle(hFile);
76 hFile = INVALID_HANDLE_VALUE;
77 }
78 #else
79 if (fd != -1) {
80 #if _SQUID_SOLARIS_
81 lockf(fd, F_ULOCK, 0);
82 #else
83 flock(fd, LOCK_UN);
84 #endif
85 close(fd);
86 fd = -1;
87 }
88 #endif
89 else
90 throw TextException(ToSBuf("Lock is already unlocked for ", filename), Here());
91 }
92
93 Ssl::Lock::~Lock()
94 {
95 if (locked())
96 unlock();
97 }
98
99 Ssl::Locker::Locker(Lock &aLock, const SourceLocation &aCaller):
100 weLocked(false),
101 lock(aLock),
102 caller(aCaller)
103 {
104 if (!lock.locked()) {
105 lock.lock();
106 weLocked = true;
107 }
108 }
109
110 Ssl::Locker::~Locker()
111 {
112 if (weLocked)
113 lock.unlock();
114 }
115
116 Ssl::CertificateDb::Row::Row() : width(cnlNumber)
117 {
118 row = (char **)OPENSSL_malloc(sizeof(char *) * (width + 1));
119 for (size_t i = 0; i < width + 1; ++i)
120 row[i] = nullptr;
121 }
122
123 Ssl::CertificateDb::Row::Row(char **aRow, size_t aWidth): width(aWidth)
124 {
125 row = aRow;
126 }
127
128 Ssl::CertificateDb::Row::~Row()
129 {
130 if (!row)
131 return;
132
133 void *max;
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]);
142 }
143 } else {
144 for (size_t i = 0; i < width + 1; ++i) {
145 if (row[i])
146 OPENSSL_free(row[i]);
147 }
148 }
149 OPENSSL_free(row);
150 }
151
152 void Ssl::CertificateDb::Row::reset()
153 {
154 row = nullptr;
155 }
156
157 void Ssl::CertificateDb::Row::setValue(size_t cell, char const * value)
158 {
159 assert(cell < width);
160 if (row[cell]) {
161 free(row[cell]);
162 }
163 if (value) {
164 row[cell] = static_cast<char *>(OPENSSL_malloc(sizeof(char) * (strlen(value) + 1)));
165 memcpy(row[cell], value, sizeof(char) * (strlen(value) + 1));
166 } else
167 row[cell] = nullptr;
168 }
169
170 char ** Ssl::CertificateDb::Row::getRow()
171 {
172 return row;
173 }
174
175 void Ssl::CertificateDb::sq_TXT_DB_delete(TXT_DB *db, const char **row)
176 {
177 if (!db)
178 return;
179
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));
184 #else
185 const char ** current_row = ((const char **)sk_OPENSSL_PSTRING_value(db->data, i));
186 #endif
187 #else
188 for (int i = 0; i < sk_num(db->data); ++i) {
189 const char ** current_row = ((const char **)sk_value(db->data, i));
190 #endif
191 if (current_row == row) {
192 sq_TXT_DB_delete_row(db, i);
193 return;
194 }
195 }
196 }
197
198 #define countof(arr) (sizeof(arr)/sizeof(*arr))
199 void Ssl::CertificateDb::sq_TXT_DB_delete_row(TXT_DB *db, int idx) {
200 char **rrow;
201 #if SQUID_SSLTXTDB_PSTRINGDATA
202 rrow = (char **)sk_OPENSSL_PSTRING_delete(db->data, idx);
203 #else
204 rrow = (char **)sk_delete(db->data, idx);
205 #endif
206
207 if (!rrow)
208 return;
209
210 Row row(rrow, cnlNumber); // row wrapper used to free the rrow
211
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);
218 #else
219 if (LHASH *fieldIndex = db->index[db_indexes[i]])
220 data = OPENSSL_LH_delete(fieldIndex, rrow);
221 #endif
222 if (data)
223 assert(data == rrow);
224 }
225 }
226
227 unsigned long Ssl::CertificateDb::index_serial_hash(const char **a) {
228 const char *n = a[Ssl::CertificateDb::cnlSerial];
229 while (*n == '0')
230 ++n;
231 return OPENSSL_LH_strhash(n);
232 }
233
234 int Ssl::CertificateDb::index_serial_cmp(const char **a, const char **b) {
235 const char *aa, *bb;
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);
239 }
240
241 unsigned long Ssl::CertificateDb::index_name_hash(const char **a) {
242 return(OPENSSL_LH_strhash(a[Ssl::CertificateDb::cnlKey]));
243 }
244
245 int Ssl::CertificateDb::index_name_cmp(const char **a, const char **b) {
246 return(strcmp(a[Ssl::CertificateDb::cnlKey], b[CertificateDb::cnlKey]));
247 }
248
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");
252
253 Ssl::CertificateDb::CertificateDb(std::string const & aDb_path, size_t aMax_db_size, size_t aFs_block_size)
254 : db_path(aDb_path),
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)),
260 dbLock(db_full)
261 {}
262
263 bool
264 Ssl::CertificateDb::find(std::string const &key, const Security::CertPointer &expectedOrig, Security::CertPointer &cert, Security::PrivateKeyPointer &pkey)
265 {
266 const Locker locker(dbLock, Here());
267 load();
268 return pure_find(key, expectedOrig, cert, pkey);
269 }
270
271 bool Ssl::CertificateDb::purgeCert(std::string const & key) {
272 const Locker locker(dbLock, Here());
273 load();
274 if (!db)
275 return false;
276
277 if (!deleteByKey(key))
278 return false;
279
280 save();
281 return true;
282 }
283
284 bool
285 Ssl::CertificateDb::addCertAndPrivateKey(std::string const &useKey, const Security::CertPointer &cert, const Security::PrivateKeyPointer &pkey, const Security::CertPointer &orig)
286 {
287 const Locker locker(dbLock, Here());
288 load();
289 if (!db || !cert || !pkey)
290 return false;
291
292 if(useKey.empty())
293 return false;
294
295 Row row;
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));
299 {
300 const UniqueCString hex_bn(BN_bn2hex(serial.get()));
301 serial_string = std::string(hex_bn.get());
302 }
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.
309 return true;
310 }
311
312 // Remove any entry with given key
313 deleteByKey(useKey);
314
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();
322 }
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
326 }
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
333 }
334 dbSize = size(); // get the current database size
335 }
336
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());
342
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
346 save();
347 return false;
348 }
349 rrow = row.getRow();
350 row.reset();
351
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);
356 save();
357 return false;
358 }
359 addSize(filename);
360
361 save();
362 return true;
363 }
364
365 void
366 Ssl::CertificateDb::Create(std::string const & db_path) {
367 if (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);
372
373 if (mkdir(db_path.c_str(), 0777))
374 throw TextException(ToSBuf("Cannot create ", db_path), Here());
375
376 if (mkdir(cert_full.c_str(), 0777))
377 throw TextException(ToSBuf("Cannot create ", cert_full), Here());
378
379 std::ofstream size(size_full.c_str());
380 if (size)
381 size << 0;
382 else
383 throw TextException(ToSBuf("Cannot open ", size_full, " to open"), Here());
384 std::ofstream db(db_full.c_str());
385 if (!db)
386 throw TextException(ToSBuf("Cannot open ", db_full, " to open"), Here());
387 }
388
389 void
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);
392 db.load();
393
394 // Call readSize to force rebuild size file in the case it is corrupted
395 (void)db.readSize();
396 }
397
398 size_t Ssl::CertificateDb::rebuildSize()
399 {
400 size_t dbSize = 0;
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));
405 #else
406 const char ** current_row = ((const char **)sk_OPENSSL_PSTRING_value(db.get()->data, i));
407 #endif
408 #else
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));
411 #endif
412 const std::string filename(cert_full + "/" + current_row[cnlSerial] + ".pem");
413 const size_t fSize = getFileSize(filename);
414 dbSize += fSize;
415 }
416 writeSize(dbSize);
417 return dbSize;
418 }
419
420 bool
421 Ssl::CertificateDb::pure_find(std::string const &key, const Security::CertPointer &expectedOrig, Security::CertPointer &cert, Security::PrivateKeyPointer &pkey)
422 {
423 if (!db)
424 return false;
425
426 Row row;
427 row.setValue(cnlKey, key.c_str());
428
429 char **rrow = TXT_DB_get_by_index(db.get(), cnlKey, row.getRow());
430 if (rrow == nullptr)
431 return false;
432
433 if (!sslDateIsInTheFuture(rrow[cnlExp_date]))
434 return false;
435
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))
440 return false;
441
442 if (!storedOrig && !expectedOrig)
443 return true;
444 else
445 return Ssl::CertificatesCmp(expectedOrig, storedOrig);
446 }
447
448 size_t Ssl::CertificateDb::size() {
449 return readSize();
450 }
451
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);
456 writeSize(dbSize);
457 }
458
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;
464 writeSize(dbSize);
465 }
466
467 size_t Ssl::CertificateDb::readSize() {
468 std::ifstream ifstr(size_full.c_str());
469 size_t db_size = 0;
470 if (!ifstr || !(ifstr >> db_size))
471 return rebuildSize();
472 return db_size;
473 }
474
475 void Ssl::CertificateDb::writeSize(size_t db_size) {
476 std::ofstream ofstr(size_full.c_str());
477 if (!ofstr)
478 throw TextException(ToSBuf("cannot write \"", size_full, "\" file"), Here());
479 ofstr << db_size;
480 }
481
482 size_t Ssl::CertificateDb::getFileSize(std::string const & filename) {
483 std::ifstream file(filename.c_str(), std::ios::binary);
484 if (!file)
485 return 0;
486 file.seekg(0, std::ios_base::end);
487 const std::streampos file_size = file.tellg();
488 if (file_size < 0)
489 return 0;
490 return ((static_cast<size_t>(file_size) + fs_block_size - 1) / fs_block_size) * fs_block_size;
491 }
492
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());
498
499 bool corrupt = false;
500 Ssl::TXT_DB_Pointer temp_db(TXT_DB_read(in.get(), cnlNumber));
501 if (!temp_db)
502 corrupt = true;
503
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)))
506 corrupt = true;
507
508 if (!corrupt && !TXT_DB_create_index(temp_db.get(), cnlKey, nullptr, LHASH_HASH_FN(index_name_hash), LHASH_COMP_FN(index_name_cmp)))
509 corrupt = true;
510
511 if (corrupt)
512 throw TextException(ToSBuf("The SSL certificate database ", db_path, " is corrupted. Please rebuild"), Here());
513
514 db.reset(temp_db.release());
515 }
516
517 void Ssl::CertificateDb::save() {
518 if (!db)
519 throw TextException("The certificates database is not loaded", Here());
520
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());
525
526 if (TXT_DB_write(out.get(), db.get()) < 0)
527 throw TextException(ToSBuf("Failed to write ", db_full, " file"), Here());
528 }
529
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);
534
535 subSize(filename);
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());
539 }
540
541 bool Ssl::CertificateDb::deleteInvalidCertificate() {
542 if (!db)
543 return false;
544
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));
550 #else
551 const char ** current_row = ((const char **)sk_OPENSSL_PSTRING_value(db.get()->data, i));
552 #endif
553 #else
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));
556 #endif
557
558 if (!sslDateIsInTheFuture(current_row[cnlExp_date])) {
559 deleteRow(current_row, i);
560 removed_one = true;
561 break;
562 }
563 }
564
565 if (!removed_one)
566 return false;
567 return true;
568 }
569
570 bool Ssl::CertificateDb::deleteOldestCertificate()
571 {
572 if (!hasRows())
573 return false;
574
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));
578 #else
579 const char **row = (const char **)sk_OPENSSL_PSTRING_value(db.get()->data, 0);
580 #endif
581 #else
582 const char **row = (const char **)sk_value(db.get()->data, 0);
583 #endif
584
585 deleteRow(row, 0);
586
587 return true;
588 }
589
590 bool
591 Ssl::CertificateDb::deleteByKey(std::string const & key) {
592 if (!db)
593 return false;
594
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));
599 #else
600 const char ** current_row = ((const char **)sk_OPENSSL_PSTRING_value(db.get()->data, i));
601 #endif
602 #else
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));
605 #endif
606 if (key == current_row[cnlKey]) {
607 deleteRow(current_row, i);
608 return true;
609 }
610 }
611 return false;
612 }
613
614 bool Ssl::CertificateDb::hasRows() const
615 {
616 if (!db)
617 return false;
618
619 #if SQUID_SSLTXTDB_PSTRINGDATA
620 if (sk_OPENSSL_PSTRING_num(db.get()->data) == 0)
621 #else
622 if (sk_num(db.get()->data) == 0)
623 #endif
624 return false;
625 return true;
626 }
627
628 bool
629 Ssl::CertificateDb::WriteEntry(const std::string &filename, const Security::CertPointer &cert, const Security::PrivateKeyPointer &pkey, const Security::CertPointer &orig)
630 {
631 Ssl::BIO_Pointer bio;
632 if (!Ssl::OpenCertsFileForWriting(bio, filename.c_str()))
633 return false;
634 if (!Ssl::WriteX509Certificate(bio, cert))
635 return false;
636 if (!Ssl::WritePrivateKey(bio, pkey))
637 return false;
638 if (orig && !Ssl::WriteX509Certificate(bio, orig))
639 return false;
640 return true;
641 }
642
643 bool
644 Ssl::CertificateDb::ReadEntry(std::string filename, Security::CertPointer &cert, Security::PrivateKeyPointer &pkey, Security::CertPointer &orig)
645 {
646 Ssl::BIO_Pointer bio;
647 if (!Ssl::OpenCertsFileForReading(bio, filename.c_str()))
648 return false;
649
650 cert = Ssl::ReadCertificate(bio);
651
652 if (!Ssl::ReadPrivateKey(bio, pkey, nullptr))
653 return false;
654
655 orig = Ssl::ReadOptionalCertificate(bio);
656
657 return true;
658 }
659