summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-07-16 15:45:08 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-07-16 15:45:08 +0300
commit393f6b682c1f67d8fb3f468a60e361d6f4e1b348 (patch)
tree479b724011f06b8156eab8cdbf63cdc1c36cd8b2 /src
parentd16ec2473b826790238b0f0404f037c3155fe19a (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.c79
-rw-r--r--src/gmrequest.c12
-rw-r--r--src/gmutil.c4
-rw-r--r--src/gmutil.h1
-rw-r--r--src/ui/documentwidget.c30
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
38static const char *filename_GmCerts_ = "trusted.txt"; 39static const char *filename_GmCerts_ = "trusted.2.txt";
39static const char *identsDir_GmCerts_ = "idents"; 40static const char *identsDir_GmCerts_ = "idents";
40static const char *oldIdentsFilename_GmCerts_ = "idents.binary"; 41static const char *oldIdentsFilename_GmCerts_ = "idents.binary";
41static const char *identsFilename_GmCerts_ = "idents.lgr"; 42static 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
380iBool 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
379void init_GmCerts(iGmCerts *d, const char *saveDir) { 398void 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
387void deinit_GmCerts(iGmCerts *d) { 407void 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
450static 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
429iBool checkTrust_GmCerts(iGmCerts *d, iRangecc domain, uint16_t port, const iTlsCertificate *cert) { 455iBool 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
477void setTrusted_GmCerts(iGmCerts *d, iRangecc domain, uint16_t port, const iBlock *fingerprint, 503void 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
497iTime domainValidUntil_GmCerts(const iGmCerts *d, iRangecc domain, uint16_t port) { 522iTime 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 }