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 | |
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
-rw-r--r-- | po/en.po | 9 | ||||
-rw-r--r-- | res/lang/de.bin | bin | 21120 -> 21546 bytes | |||
-rw-r--r-- | res/lang/en.bin | bin | 19817 -> 20243 bytes | |||
-rw-r--r-- | res/lang/es.bin | bin | 22199 -> 22625 bytes | |||
-rw-r--r-- | res/lang/fi.bin | bin | 22322 -> 22748 bytes | |||
-rw-r--r-- | res/lang/fr.bin | bin | 22732 -> 23158 bytes | |||
-rw-r--r-- | res/lang/ia.bin | bin | 22030 -> 22456 bytes | |||
-rw-r--r-- | res/lang/ie.bin | bin | 21588 -> 22014 bytes | |||
-rw-r--r-- | res/lang/pl.bin | bin | 22836 -> 23262 bytes | |||
-rw-r--r-- | res/lang/ru.bin | bin | 33067 -> 33493 bytes | |||
-rw-r--r-- | res/lang/sr.bin | bin | 32472 -> 32898 bytes | |||
-rw-r--r-- | res/lang/tok.bin | bin | 20213 -> 20639 bytes | |||
-rw-r--r-- | res/lang/zh_Hans.bin | bin | 18989 -> 19415 bytes | |||
-rw-r--r-- | res/lang/zh_Hant.bin | bin | 19174 -> 19600 bytes | |||
-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 |
19 files changed, 92 insertions, 43 deletions
@@ -177,6 +177,9 @@ msgstr "Open Location…" | |||
177 | msgid "menu.downloads" | 177 | msgid "menu.downloads" |
178 | msgstr "Show Downloads" | 178 | msgstr "Show Downloads" |
179 | 179 | ||
180 | msgid "menu.pageinfo" | ||
181 | msgstr "Show Page Information" | ||
182 | |||
180 | msgid "menu.find" | 183 | msgid "menu.find" |
181 | msgstr "Find on Page" | 184 | msgstr "Find on Page" |
182 | 185 | ||
@@ -1495,6 +1498,12 @@ msgstr "Network/TLS Failure" | |||
1495 | msgid "error.tls.msg" | 1498 | msgid "error.tls.msg" |
1496 | msgstr "Failed to communicate with the server. Here is the error message:" | 1499 | msgstr "Failed to communicate with the server. Here is the error message:" |
1497 | 1500 | ||
1501 | msgid "error.certverify" | ||
1502 | msgstr "Untrusted Server" | ||
1503 | |||
1504 | msgid "error.certverify.msg" | ||
1505 | msgstr "Connection to the server was aborted because the received TLS certificate does not match the one we trust. Please check if the server has announced a certificate change. If not, it is possible that a malicious third party is masquerading as the server you tried to reach.\n\nThe certificate can be marked as trusted in Page Information." | ||
1506 | |||
1498 | msgid "error.temporary" | 1507 | msgid "error.temporary" |
1499 | msgstr "Temporary Failure" | 1508 | msgstr "Temporary Failure" |
1500 | 1509 | ||
diff --git a/res/lang/de.bin b/res/lang/de.bin index cbcbad56..d13dd44c 100644 --- a/res/lang/de.bin +++ b/res/lang/de.bin | |||
Binary files differ | |||
diff --git a/res/lang/en.bin b/res/lang/en.bin index 48f2695e..e8b50a07 100644 --- a/res/lang/en.bin +++ b/res/lang/en.bin | |||
Binary files differ | |||
diff --git a/res/lang/es.bin b/res/lang/es.bin index c49f9a1b..7c3697c4 100644 --- a/res/lang/es.bin +++ b/res/lang/es.bin | |||
Binary files differ | |||
diff --git a/res/lang/fi.bin b/res/lang/fi.bin index ff2ca5cd..86accba7 100644 --- a/res/lang/fi.bin +++ b/res/lang/fi.bin | |||
Binary files differ | |||
diff --git a/res/lang/fr.bin b/res/lang/fr.bin index 0d5cfc55..a7d25794 100644 --- a/res/lang/fr.bin +++ b/res/lang/fr.bin | |||
Binary files differ | |||
diff --git a/res/lang/ia.bin b/res/lang/ia.bin index b4016d13..44daa5a1 100644 --- a/res/lang/ia.bin +++ b/res/lang/ia.bin | |||
Binary files differ | |||
diff --git a/res/lang/ie.bin b/res/lang/ie.bin index ead591e7..932f37a1 100644 --- a/res/lang/ie.bin +++ b/res/lang/ie.bin | |||
Binary files differ | |||
diff --git a/res/lang/pl.bin b/res/lang/pl.bin index f220a34e..ca5814ad 100644 --- a/res/lang/pl.bin +++ b/res/lang/pl.bin | |||
Binary files differ | |||
diff --git a/res/lang/ru.bin b/res/lang/ru.bin index 8c0431a5..a5396cff 100644 --- a/res/lang/ru.bin +++ b/res/lang/ru.bin | |||
Binary files differ | |||
diff --git a/res/lang/sr.bin b/res/lang/sr.bin index 5c4174cc..efc74ced 100644 --- a/res/lang/sr.bin +++ b/res/lang/sr.bin | |||
Binary files differ | |||
diff --git a/res/lang/tok.bin b/res/lang/tok.bin index 8df467ef..2b7fda91 100644 --- a/res/lang/tok.bin +++ b/res/lang/tok.bin | |||
Binary files differ | |||
diff --git a/res/lang/zh_Hans.bin b/res/lang/zh_Hans.bin index 3874b28a..7664ecd5 100644 --- a/res/lang/zh_Hans.bin +++ b/res/lang/zh_Hans.bin | |||
Binary files differ | |||
diff --git a/res/lang/zh_Hant.bin b/res/lang/zh_Hant.bin index e5feb691..b68bba0d 100644 --- a/res/lang/zh_Hant.bin +++ b/res/lang/zh_Hant.bin | |||
Binary files differ | |||
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 | } |