diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-07-16 15:45:08 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-07-16 15:45:08 +0300 |
commit | 393f6b682c1f67d8fb3f468a60e361d6f4e1b348 (patch) | |
tree | 479b724011f06b8156eab8cdbf63cdc1c36cd8b2 /src | |
parent | d16ec2473b826790238b0f0404f037c3155fe19a (diff) |
Improved TOFU implementation
If a server sends a different certificate (checked by matching public key fingerprints), abort the connection at the TLS handshake stage.
A new error page is shown explaining the situation. A button is provided for conveniently opening Page Information, where trust can be updated.
The file format of "visited.txt" was updated, so it is now called "visited.2.txt". The new format includes server port numbers, and the fingerprints are calculated based on public keys.
IssueID #308
IssueID #309
IssueID #310
Diffstat (limited to 'src')
-rw-r--r-- | src/gmcerts.c | 79 | ||||
-rw-r--r-- | src/gmrequest.c | 12 | ||||
-rw-r--r-- | src/gmutil.c | 4 | ||||
-rw-r--r-- | src/gmutil.h | 1 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 30 |
5 files changed, 83 insertions, 43 deletions
diff --git a/src/gmcerts.c b/src/gmcerts.c index 4b84aa05..32e47b40 100644 --- a/src/gmcerts.c +++ b/src/gmcerts.c | |||
@@ -23,6 +23,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
23 | #include "gmcerts.h" | 23 | #include "gmcerts.h" |
24 | #include "gmutil.h" | 24 | #include "gmutil.h" |
25 | #include "defs.h" | 25 | #include "defs.h" |
26 | #include "app.h" | ||
26 | 27 | ||
27 | #include <the_Foundation/file.h> | 28 | #include <the_Foundation/file.h> |
28 | #include <the_Foundation/fileinfo.h> | 29 | #include <the_Foundation/fileinfo.h> |
@@ -35,7 +36,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
35 | #include <the_Foundation/time.h> | 36 | #include <the_Foundation/time.h> |
36 | #include <ctype.h> | 37 | #include <ctype.h> |
37 | 38 | ||
38 | static const char *filename_GmCerts_ = "trusted.txt"; | 39 | static const char *filename_GmCerts_ = "trusted.2.txt"; |
39 | static const char *identsDir_GmCerts_ = "idents"; | 40 | static const char *identsDir_GmCerts_ = "idents"; |
40 | static const char *oldIdentsFilename_GmCerts_ = "idents.binary"; | 41 | static const char *oldIdentsFilename_GmCerts_ = "idents.binary"; |
41 | static const char *identsFilename_GmCerts_ = "idents.lgr"; | 42 | static const char *identsFilename_GmCerts_ = "idents.lgr"; |
@@ -337,7 +338,7 @@ static void load_GmCerts_(iGmCerts *d) { | |||
337 | iRegExpMatch m; | 338 | iRegExpMatch m; |
338 | init_RegExpMatch(&m); | 339 | init_RegExpMatch(&m); |
339 | if (matchRange_RegExp(pattern, line, &m)) { | 340 | if (matchRange_RegExp(pattern, line, &m)) { |
340 | const iRangecc domain = capturedRange_RegExpMatch(&m, 1); | 341 | const iRangecc key = capturedRange_RegExpMatch(&m, 1); |
341 | const iRangecc until = capturedRange_RegExpMatch(&m, 2); | 342 | const iRangecc until = capturedRange_RegExpMatch(&m, 2); |
342 | const iRangecc fp = capturedRange_RegExpMatch(&m, 3); | 343 | const iRangecc fp = capturedRange_RegExpMatch(&m, 3); |
343 | time_t sec; | 344 | time_t sec; |
@@ -345,7 +346,7 @@ static void load_GmCerts_(iGmCerts *d) { | |||
345 | iDate untilDate; | 346 | iDate untilDate; |
346 | initSinceEpoch_Date(&untilDate, sec); | 347 | initSinceEpoch_Date(&untilDate, sec); |
347 | insert_StringHash(d->trusted, | 348 | insert_StringHash(d->trusted, |
348 | collect_String(newRange_String(domain)), | 349 | collect_String(newRange_String(key)), |
349 | new_TrustEntry(collect_Block(hexDecode_Rangecc(fp)), | 350 | new_TrustEntry(collect_Block(hexDecode_Rangecc(fp)), |
350 | &untilDate)); | 351 | &untilDate)); |
351 | } | 352 | } |
@@ -376,15 +377,35 @@ static void load_GmCerts_(iGmCerts *d) { | |||
376 | } | 377 | } |
377 | } | 378 | } |
378 | 379 | ||
380 | iBool verify_GmCerts_(iTlsRequest *request, const iTlsCertificate *cert, int depth) { | ||
381 | iGmCerts *d = certs_App(); | ||
382 | if (depth != 0) { | ||
383 | /* We only check the primary certificate. */ | ||
384 | return iTrue; | ||
385 | } | ||
386 | const iAddress *address = address_TlsRequest(request); | ||
387 | iRangecc domain = range_String(hostName_Address(address)); | ||
388 | uint16_t port = port_Address(address); | ||
389 | #if 0 | ||
390 | printf("[verify_GmCerts_] peer: %s\n", cstrCollect_String(toString_Address(address))); | ||
391 | printf(" hostname: %s\n", cstr_String(hostName_Address(address))); | ||
392 | printf(" port: %u\n", port_Address(address)); | ||
393 | printf(" cert subject: %s\n", cstrCollect_String(subject_TlsCertificate(cert))); | ||
394 | #endif | ||
395 | return checkTrust_GmCerts(d, domain, port, cert); | ||
396 | } | ||
397 | |||
379 | void init_GmCerts(iGmCerts *d, const char *saveDir) { | 398 | void init_GmCerts(iGmCerts *d, const char *saveDir) { |
380 | d->mtx = new_Mutex(); | 399 | d->mtx = new_Mutex(); |
381 | initCStr_String(&d->saveDir, saveDir); | 400 | initCStr_String(&d->saveDir, saveDir); |
382 | d->trusted = new_StringHash(); | 401 | d->trusted = new_StringHash(); |
383 | init_PtrArray(&d->idents); | 402 | init_PtrArray(&d->idents); |
384 | load_GmCerts_(d); | 403 | load_GmCerts_(d); |
404 | setVerifyFunc_TlsRequest(verify_GmCerts_); | ||
385 | } | 405 | } |
386 | 406 | ||
387 | void deinit_GmCerts(iGmCerts *d) { | 407 | void deinit_GmCerts(iGmCerts *d) { |
408 | setVerifyFunc_TlsRequest(NULL); | ||
388 | iGuardMutex(d->mtx, { | 409 | iGuardMutex(d->mtx, { |
389 | saveIdentities_GmCerts(d); | 410 | saveIdentities_GmCerts(d); |
390 | iForEach(PtrArray, i, &d->idents) { | 411 | iForEach(PtrArray, i, &d->idents) { |
@@ -426,6 +447,11 @@ iBool verifyDomain_GmCerts(const iTlsCertificate *cert, iRangecc domain) { | |||
426 | return iFalse; | 447 | return iFalse; |
427 | } | 448 | } |
428 | 449 | ||
450 | static void makeTrustKey_(iRangecc domain, uint16_t port, iString *key_out) { | ||
451 | punyEncodeDomain_Rangecc(domain, key_out); | ||
452 | appendFormat_String(key_out, ";%u", port ? port : GEMINI_DEFAULT_PORT); | ||
453 | } | ||
454 | |||
429 | iBool checkTrust_GmCerts(iGmCerts *d, iRangecc domain, uint16_t port, const iTlsCertificate *cert) { | 455 | iBool checkTrust_GmCerts(iGmCerts *d, iRangecc domain, uint16_t port, const iTlsCertificate *cert) { |
430 | if (!cert) { | 456 | if (!cert) { |
431 | return iFalse; | 457 | return iFalse; |
@@ -434,30 +460,30 @@ iBool checkTrust_GmCerts(iGmCerts *d, iRangecc domain, uint16_t port, const iTls | |||
434 | return iFalse; | 460 | return iFalse; |
435 | } | 461 | } |
436 | /* We trust CA verification implicitly. */ | 462 | /* We trust CA verification implicitly. */ |
437 | const iBool isAuth = verify_TlsCertificate(cert) == authority_TlsCertificateVerifyStatus; | 463 | //const iBool isAuth = verify_TlsCertificate(cert) == authority_TlsCertificateVerifyStatus; |
438 | if (!isAuth && !verifyDomain_GmCerts(cert, domain)) { | 464 | // const iBool isAuth = iFalse; /* CA verification done during handshake */ |
465 | if (/*!isAuth &&*/ !verifyDomain_GmCerts(cert, domain)) { | ||
439 | return iFalse; | 466 | return iFalse; |
440 | } | 467 | } |
441 | /* TODO: Could call setTrusted_GmCerts() instead of duplicating the trust-setting. */ | 468 | /* TODO: Could call setTrusted_GmCerts() instead of duplicating the trust-setting. */ |
442 | /* Good certificate. If not already trusted, add it now. */ | 469 | /* Good certificate. If not already trusted, add it now. */ |
443 | iString *key = newRange_String(domain); | ||
444 | if (port && port != GEMINI_DEFAULT_PORT) { | ||
445 | appendFormat_String(key, ":%u", port); | ||
446 | } | ||
447 | iDate until; | 470 | iDate until; |
448 | validUntil_TlsCertificate(cert, &until); | 471 | validUntil_TlsCertificate(cert, &until); |
449 | iBlock *fingerprint = fingerprint_TlsCertificate(cert); | 472 | iBlock *fingerprint = publicKeyFingerprint_TlsCertificate(cert); |
473 | iString key; | ||
474 | init_String(&key); | ||
475 | makeTrustKey_(domain, port, &key); | ||
450 | lock_Mutex(d->mtx); | 476 | lock_Mutex(d->mtx); |
451 | iTrustEntry *trust = value_StringHash(d->trusted, key); | 477 | iTrustEntry *trust = value_StringHash(d->trusted, &key); |
452 | if (trust) { | 478 | if (trust) { |
453 | /* We already have it, check if it matches the one we trust for this domain (if it's | 479 | /* We already have it, check if it matches the one we trust for this domain (if it's |
454 | still valid. */ | 480 | still valid. */ |
455 | if (!isAuth && elapsedSeconds_Time(&trust->validUntil) < 0) { | 481 | if (/*!isAuth && */elapsedSeconds_Time(&trust->validUntil) < 0) { |
456 | /* Trusted cert is still valid. */ | 482 | /* Trusted cert is still valid. */ |
457 | const iBool isTrusted = cmp_Block(fingerprint, &trust->fingerprint) == 0; | 483 | const iBool isTrusted = cmp_Block(fingerprint, &trust->fingerprint) == 0; |
458 | unlock_Mutex(d->mtx); | 484 | unlock_Mutex(d->mtx); |
459 | delete_Block(fingerprint); | 485 | delete_Block(fingerprint); |
460 | delete_String(key); | 486 | deinit_String(&key); |
461 | return isTrusted; | 487 | return isTrusted; |
462 | } | 488 | } |
463 | /* Update the trusted cert. */ | 489 | /* Update the trusted cert. */ |
@@ -465,50 +491,47 @@ iBool checkTrust_GmCerts(iGmCerts *d, iRangecc domain, uint16_t port, const iTls | |||
465 | set_Block(&trust->fingerprint, fingerprint); | 491 | set_Block(&trust->fingerprint, fingerprint); |
466 | } | 492 | } |
467 | else { | 493 | else { |
468 | insert_StringHash(d->trusted, key, iClob(new_TrustEntry(fingerprint, &until))); | 494 | insert_StringHash(d->trusted, &key, iClob(new_TrustEntry(fingerprint, &until))); |
469 | } | 495 | } |
470 | save_GmCerts_(d); | 496 | save_GmCerts_(d); |
471 | unlock_Mutex(d->mtx); | 497 | unlock_Mutex(d->mtx); |
472 | delete_Block(fingerprint); | 498 | delete_Block(fingerprint); |
473 | delete_String(key); | 499 | deinit_String(&key); |
474 | return iTrue; | 500 | return iTrue; |
475 | } | 501 | } |
476 | 502 | ||
477 | void setTrusted_GmCerts(iGmCerts *d, iRangecc domain, uint16_t port, const iBlock *fingerprint, | 503 | void setTrusted_GmCerts(iGmCerts *d, iRangecc domain, uint16_t port, const iBlock *fingerprint, |
478 | const iDate *validUntil) { | 504 | const iDate *validUntil) { |
479 | iString *key = collectNew_String(); | 505 | iString key; |
480 | punyEncodeDomain_Rangecc(domain, key); | 506 | init_String(&key); |
481 | if (port && port != GEMINI_DEFAULT_PORT) { | 507 | makeTrustKey_(domain, port, &key); |
482 | appendFormat_String(key, ":%u", port); | ||
483 | } | ||
484 | lock_Mutex(d->mtx); | 508 | lock_Mutex(d->mtx); |
485 | iTrustEntry *trust = value_StringHash(d->trusted, key); | 509 | iTrustEntry *trust = value_StringHash(d->trusted, &key); |
486 | if (trust) { | 510 | if (trust) { |
487 | init_Time(&trust->validUntil, validUntil); | 511 | init_Time(&trust->validUntil, validUntil); |
488 | set_Block(&trust->fingerprint, fingerprint); | 512 | set_Block(&trust->fingerprint, fingerprint); |
489 | } | 513 | } |
490 | else { | 514 | else { |
491 | insert_StringHash(d->trusted, key, iClob(trust = new_TrustEntry(fingerprint, validUntil))); | 515 | insert_StringHash(d->trusted, &key, iClob(trust = new_TrustEntry(fingerprint, validUntil))); |
492 | } | 516 | } |
493 | save_GmCerts_(d); | 517 | save_GmCerts_(d); |
494 | unlock_Mutex(d->mtx); | 518 | unlock_Mutex(d->mtx); |
519 | deinit_String(&key); | ||
495 | } | 520 | } |
496 | 521 | ||
497 | iTime domainValidUntil_GmCerts(const iGmCerts *d, iRangecc domain, uint16_t port) { | 522 | iTime domainValidUntil_GmCerts(const iGmCerts *d, iRangecc domain, uint16_t port) { |
498 | iTime expiry; | 523 | iTime expiry; |
499 | iZap(expiry); | 524 | iZap(expiry); |
500 | lock_Mutex(d->mtx); | ||
501 | iString key; | 525 | iString key; |
502 | initRange_String(&key, domain); | 526 | init_String(&key); |
503 | if (port && port != GEMINI_DEFAULT_PORT) { | 527 | makeTrustKey_(domain, port, &key); |
504 | appendFormat_String(&key, ":%u", port); | 528 | lock_Mutex(d->mtx); |
505 | } | ||
506 | const iTrustEntry *trust = constValue_StringHash(d->trusted, &key); | 529 | const iTrustEntry *trust = constValue_StringHash(d->trusted, &key); |
507 | if (trust) { | 530 | if (trust) { |
508 | expiry = trust->validUntil; | 531 | expiry = trust->validUntil; |
509 | } | 532 | } |
510 | deinit_String(&key); | ||
511 | unlock_Mutex(d->mtx); | 533 | unlock_Mutex(d->mtx); |
534 | deinit_String(&key); | ||
512 | return expiry; | 535 | return expiry; |
513 | } | 536 | } |
514 | 537 | ||
diff --git a/src/gmrequest.c b/src/gmrequest.c index 8ae8f736..9b640cee 100644 --- a/src/gmrequest.c +++ b/src/gmrequest.c | |||
@@ -161,7 +161,7 @@ static void checkServerCertificate_GmRequest_(iGmRequest *d) { | |||
161 | if (cert) { | 161 | if (cert) { |
162 | const iRangecc domain = range_String(hostName_Address(address_TlsRequest(d->req))); | 162 | const iRangecc domain = range_String(hostName_Address(address_TlsRequest(d->req))); |
163 | resp->certFlags |= available_GmCertFlag; | 163 | resp->certFlags |= available_GmCertFlag; |
164 | set_Block(&resp->certFingerprint, collect_Block(fingerprint_TlsCertificate(cert))); | 164 | set_Block(&resp->certFingerprint, collect_Block(publicKeyFingerprint_TlsCertificate(cert))); |
165 | resp->certFlags |= haveFingerprint_GmCertFlag; | 165 | resp->certFlags |= haveFingerprint_GmCertFlag; |
166 | if (!isExpired_TlsCertificate(cert)) { | 166 | if (!isExpired_TlsCertificate(cert)) { |
167 | resp->certFlags |= timeVerified_GmCertFlag; | 167 | resp->certFlags |= timeVerified_GmCertFlag; |
@@ -289,8 +289,14 @@ static void requestFinished_GmRequest_(iGmRequest *d, iTlsRequest *req) { | |||
289 | d->state = (status_TlsRequest(req) == error_TlsRequestStatus ? failure_GmRequestState | 289 | d->state = (status_TlsRequest(req) == error_TlsRequestStatus ? failure_GmRequestState |
290 | : finished_GmRequestState); | 290 | : finished_GmRequestState); |
291 | if (d->state == failure_GmRequestState) { | 291 | if (d->state == failure_GmRequestState) { |
292 | d->resp->statusCode = tlsFailure_GmStatusCode; | 292 | if (!isVerified_TlsRequest(req)) { |
293 | set_String(&d->resp->meta, errorMessage_TlsRequest(req)); | 293 | d->resp->statusCode = tlsServerCertificateNotVerified_GmStatusCode; |
294 | setCStr_String(&d->resp->meta, "Server certificate could not be verified"); | ||
295 | } | ||
296 | else { | ||
297 | d->resp->statusCode = tlsFailure_GmStatusCode; | ||
298 | set_String(&d->resp->meta, errorMessage_TlsRequest(req)); | ||
299 | } | ||
294 | } | 300 | } |
295 | checkServerCertificate_GmRequest_(d); | 301 | checkServerCertificate_GmRequest_(d); |
296 | unlock_Mutex(d->mtx); | 302 | unlock_Mutex(d->mtx); |
diff --git a/src/gmutil.c b/src/gmutil.c index d8f58a10..0ae0861c 100644 --- a/src/gmutil.c +++ b/src/gmutil.c | |||
@@ -682,6 +682,10 @@ static const struct { | |||
682 | { 0x1f5a7, /* networked computers */ | 682 | { 0x1f5a7, /* networked computers */ |
683 | "${error.tls}", | 683 | "${error.tls}", |
684 | "${error.tls.msg}" } }, | 684 | "${error.tls.msg}" } }, |
685 | { tlsServerCertificateNotVerified_GmStatusCode, | ||
686 | { 0x1f645, | ||
687 | "${error.certverify}", | ||
688 | "${error.certverify.msg}" } }, | ||
685 | { temporaryFailure_GmStatusCode, | 689 | { temporaryFailure_GmStatusCode, |
686 | { 0x1f50c, /* electric plug */ | 690 | { 0x1f50c, /* electric plug */ |
687 | "${error.temporary}", | 691 | "${error.temporary}", |
diff --git a/src/gmutil.h b/src/gmutil.h index 40fa1cf9..1bf41775 100644 --- a/src/gmutil.h +++ b/src/gmutil.h | |||
@@ -43,6 +43,7 @@ enum iGmStatusCode { | |||
43 | unknownStatusCode_GmStatusCode, | 43 | unknownStatusCode_GmStatusCode, |
44 | invalidLocalResource_GmStatusCode, | 44 | invalidLocalResource_GmStatusCode, |
45 | tlsFailure_GmStatusCode, | 45 | tlsFailure_GmStatusCode, |
46 | tlsServerCertificateNotVerified_GmStatusCode, | ||
46 | 47 | ||
47 | none_GmStatusCode = 0, | 48 | none_GmStatusCode = 0, |
48 | /* general status code categories */ | 49 | /* general status code categories */ |
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 7e6c2ea4..4a4101b1 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -1135,6 +1135,16 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode | |||
1135 | useBanner = iFalse; /* valid data wasn't received from host */ | 1135 | useBanner = iFalse; /* valid data wasn't received from host */ |
1136 | appendFormat_String(src, "\n\n>%s\n", cstr_String(meta)); | 1136 | appendFormat_String(src, "\n\n>%s\n", cstr_String(meta)); |
1137 | break; | 1137 | break; |
1138 | /* fall through */ | ||
1139 | case tlsServerCertificateNotVerified_GmStatusCode: | ||
1140 | makeFooterButtons_DocumentWidget_( | ||
1141 | d, | ||
1142 | (iMenuItem[]){ { info_Icon " ${menu.pageinfo}", | ||
1143 | SDLK_i, | ||
1144 | KMOD_PRIMARY, | ||
1145 | "document.info" } }, | ||
1146 | 1); | ||
1147 | break; | ||
1138 | case failedToOpenFile_GmStatusCode: | 1148 | case failedToOpenFile_GmStatusCode: |
1139 | case certificateNotValid_GmStatusCode: | 1149 | case certificateNotValid_GmStatusCode: |
1140 | appendFormat_String(src, "\n\n%s", cstr_String(meta)); | 1150 | appendFormat_String(src, "\n\n%s", cstr_String(meta)); |
@@ -1143,10 +1153,6 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode | |||
1143 | iString *key = collectNew_String(); | 1153 | iString *key = collectNew_String(); |
1144 | toString_Sym(SDLK_s, KMOD_PRIMARY, key); | 1154 | toString_Sym(SDLK_s, KMOD_PRIMARY, key); |
1145 | appendFormat_String(src, "\n```\n%s\n```\n", cstr_String(meta)); | 1155 | appendFormat_String(src, "\n```\n%s\n```\n", cstr_String(meta)); |
1146 | // appendFormat_String(src, | ||
1147 | // cstr_Lang("error.unsupported.suggestsave"), | ||
1148 | // cstr_String(key), | ||
1149 | // saveToDownloads_Label); | ||
1150 | makeFooterButtons_DocumentWidget_( | 1156 | makeFooterButtons_DocumentWidget_( |
1151 | d, | 1157 | d, |
1152 | (iMenuItem[]){ { translateCStr_Lang(download_Icon " " saveToDownloads_Label), | 1158 | (iMenuItem[]){ { translateCStr_Lang(download_Icon " " saveToDownloads_Label), |
@@ -1565,7 +1571,8 @@ static void updateTrust_DocumentWidget_(iDocumentWidget *d, const iGmResponse *r | |||
1565 | } | 1571 | } |
1566 | setFlags_Widget(as_Widget(lock), disabled_WidgetFlag, iFalse); | 1572 | setFlags_Widget(as_Widget(lock), disabled_WidgetFlag, iFalse); |
1567 | const iBool isDarkMode = isDark_ColorTheme(colorTheme_App()); | 1573 | const iBool isDarkMode = isDark_ColorTheme(colorTheme_App()); |
1568 | if (~d->certFlags & domainVerified_GmCertFlag) { | 1574 | if (~d->certFlags & domainVerified_GmCertFlag || |
1575 | ~d->certFlags & trusted_GmCertFlag) { | ||
1569 | updateTextCStr_LabelWidget(lock, red_ColorEscape warning_Icon); | 1576 | updateTextCStr_LabelWidget(lock, red_ColorEscape warning_Icon); |
1570 | } | 1577 | } |
1571 | else if (d->certFlags & trusted_GmCertFlag) { | 1578 | else if (d->certFlags & trusted_GmCertFlag) { |
@@ -2592,8 +2599,11 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2592 | setFocus_Widget(NULL); | 2599 | setFocus_Widget(NULL); |
2593 | iArray *items = new_Array(sizeof(iMenuItem)); | 2600 | iArray *items = new_Array(sizeof(iMenuItem)); |
2594 | if (canTrust) { | 2601 | if (canTrust) { |
2595 | pushBack_Array( | 2602 | pushBack_Array(items, |
2596 | items, &(iMenuItem){ uiTextCaution_ColorEscape "${dlg.cert.trust}", 0, 0, "server.trustcert" }); | 2603 | &(iMenuItem){ uiTextCaution_ColorEscape "${dlg.cert.trust}", |
2604 | SDLK_u, | ||
2605 | KMOD_PRIMARY | KMOD_SHIFT, | ||
2606 | "server.trustcert" }); | ||
2597 | } | 2607 | } |
2598 | if (haveFingerprint) { | 2608 | if (haveFingerprint) { |
2599 | pushBack_Array(items, &(iMenuItem){ "${dlg.cert.fingerprint}", 0, 0, "server.copycert" }); | 2609 | pushBack_Array(items, &(iMenuItem){ "${dlg.cert.fingerprint}", 0, 0, "server.copycert" }); |
@@ -2627,11 +2637,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2627 | if (!isEmpty_Block(d->certFingerprint) && !isEmpty_Range(&host)) { | 2637 | if (!isEmpty_Block(d->certFingerprint) && !isEmpty_Range(&host)) { |
2628 | setTrusted_GmCerts(certs_App(), host, port, d->certFingerprint, &d->certExpiry); | 2638 | setTrusted_GmCerts(certs_App(), host, port, d->certFingerprint, &d->certExpiry); |
2629 | d->certFlags |= trusted_GmCertFlag; | 2639 | d->certFlags |= trusted_GmCertFlag; |
2630 | postCommand_Widget(w, "document.info"); | 2640 | postCommand_Widget(w, "navigate.reload"); |
2631 | updateTrust_DocumentWidget_(d, NULL); | ||
2632 | redoLayout_GmDocument(d->doc); | ||
2633 | invalidate_DocumentWidget_(d); | ||
2634 | refresh_Widget(d); | ||
2635 | } | 2641 | } |
2636 | return iTrue; | 2642 | return iTrue; |
2637 | } | 2643 | } |