diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-11-08 13:45:51 +0200 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-11-08 13:45:51 +0200 |
commit | bb53ff49396cae88836ff391fd20589a687ae83f (patch) | |
tree | aadc25a24ed2e12809298791f4566d7e1ecfb770 | |
parent | c0280998be065ab075581e46c52c6cc27e4b21a9 (diff) |
Manually trusting a server certificate
-rw-r--r-- | src/gmcerts.c | 17 | ||||
-rw-r--r-- | src/gmcerts.h | 3 | ||||
-rw-r--r-- | src/gmrequest.c | 10 | ||||
-rw-r--r-- | src/gmrequest.h | 10 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 33 |
5 files changed, 62 insertions, 11 deletions
diff --git a/src/gmcerts.c b/src/gmcerts.c index 27b226a0..f7475348 100644 --- a/src/gmcerts.c +++ b/src/gmcerts.c | |||
@@ -381,6 +381,7 @@ iBool checkTrust_GmCerts(iGmCerts *d, iRangecc domain, const iTlsCertificate *ce | |||
381 | if (!verifyDomain_TlsCertificate(cert, domain)) { | 381 | if (!verifyDomain_TlsCertificate(cert, domain)) { |
382 | return iFalse; | 382 | return iFalse; |
383 | } | 383 | } |
384 | /* TODO: Could call setTrusted_GmCerts() instead of duplicating the trust-setting. */ | ||
384 | /* Good certificate. If not already trusted, add it now. */ | 385 | /* Good certificate. If not already trusted, add it now. */ |
385 | iString *key = newRange_String(domain); | 386 | iString *key = newRange_String(domain); |
386 | iDate until; | 387 | iDate until; |
@@ -415,6 +416,22 @@ iBool checkTrust_GmCerts(iGmCerts *d, iRangecc domain, const iTlsCertificate *ce | |||
415 | return iTrue; | 416 | return iTrue; |
416 | } | 417 | } |
417 | 418 | ||
419 | void setTrusted_GmCerts(iGmCerts *d, iRangecc domain, const iBlock *fingerprint, | ||
420 | const iDate *validUntil) { | ||
421 | iString *key = collect_String(newRange_String(domain)); | ||
422 | lock_Mutex(d->mtx); | ||
423 | iTrustEntry *trust = value_StringHash(d->trusted, key); | ||
424 | if (trust) { | ||
425 | init_Time(&trust->validUntil, validUntil); | ||
426 | set_Block(&trust->fingerprint, fingerprint); | ||
427 | } | ||
428 | else { | ||
429 | insert_StringHash(d->trusted, key, iClob(trust = new_TrustEntry(fingerprint, validUntil))); | ||
430 | } | ||
431 | save_GmCerts_(d); | ||
432 | unlock_Mutex(d->mtx); | ||
433 | } | ||
434 | |||
418 | iGmIdentity *identity_GmCerts(iGmCerts *d, unsigned int id) { | 435 | iGmIdentity *identity_GmCerts(iGmCerts *d, unsigned int id) { |
419 | return at_PtrArray(&d->idents, id); | 436 | return at_PtrArray(&d->idents, id); |
420 | } | 437 | } |
diff --git a/src/gmcerts.h b/src/gmcerts.h index f0ed37b5..2fd1023a 100644 --- a/src/gmcerts.h +++ b/src/gmcerts.h | |||
@@ -60,7 +60,8 @@ iDeclareTypeConstructionArgs(GmCerts, const char *saveDir) | |||
60 | typedef iBool (*iGmCertsIdentityFilterFunc)(void *context, const iGmIdentity *); | 60 | typedef iBool (*iGmCertsIdentityFilterFunc)(void *context, const iGmIdentity *); |
61 | 61 | ||
62 | iBool checkTrust_GmCerts (iGmCerts *, iRangecc domain, const iTlsCertificate *cert); | 62 | iBool checkTrust_GmCerts (iGmCerts *, iRangecc domain, const iTlsCertificate *cert); |
63 | 63 | void setTrusted_GmCerts (iGmCerts *, iRangecc domain, const iBlock *fingerprint, | |
64 | const iDate *validUntil); | ||
64 | /** | 65 | /** |
65 | * Create a new self-signed TLS client certificate for identifying the user. | 66 | * Create a new self-signed TLS client certificate for identifying the user. |
66 | * @a commonName and the other name parameters are inserted in the subject field | 67 | * @a commonName and the other name parameters are inserted in the subject field |
diff --git a/src/gmrequest.c b/src/gmrequest.c index e65847e1..32b71922 100644 --- a/src/gmrequest.c +++ b/src/gmrequest.c | |||
@@ -45,6 +45,7 @@ void init_GmResponse(iGmResponse *d) { | |||
45 | init_String(&d->meta); | 45 | init_String(&d->meta); |
46 | init_Block(&d->body, 0); | 46 | init_Block(&d->body, 0); |
47 | d->certFlags = 0; | 47 | d->certFlags = 0; |
48 | init_Block(&d->certFingerprint, 0); | ||
48 | iZap(d->certValidUntil); | 49 | iZap(d->certValidUntil); |
49 | init_String(&d->certSubject); | 50 | init_String(&d->certSubject); |
50 | iZap(d->when); | 51 | iZap(d->when); |
@@ -55,6 +56,7 @@ void initCopy_GmResponse(iGmResponse *d, const iGmResponse *other) { | |||
55 | initCopy_String(&d->meta, &other->meta); | 56 | initCopy_String(&d->meta, &other->meta); |
56 | initCopy_Block(&d->body, &other->body); | 57 | initCopy_Block(&d->body, &other->body); |
57 | d->certFlags = other->certFlags; | 58 | d->certFlags = other->certFlags; |
59 | initCopy_Block(&d->certFingerprint, &other->certFingerprint); | ||
58 | d->certValidUntil = other->certValidUntil; | 60 | d->certValidUntil = other->certValidUntil; |
59 | initCopy_String(&d->certSubject, &other->certSubject); | 61 | initCopy_String(&d->certSubject, &other->certSubject); |
60 | d->when = other->when; | 62 | d->when = other->when; |
@@ -63,6 +65,7 @@ void initCopy_GmResponse(iGmResponse *d, const iGmResponse *other) { | |||
63 | void deinit_GmResponse(iGmResponse *d) { | 65 | void deinit_GmResponse(iGmResponse *d) { |
64 | deinit_String(&d->certSubject); | 66 | deinit_String(&d->certSubject); |
65 | deinit_Block(&d->body); | 67 | deinit_Block(&d->body); |
68 | deinit_Block(&d->certFingerprint); | ||
66 | deinit_String(&d->meta); | 69 | deinit_String(&d->meta); |
67 | } | 70 | } |
68 | 71 | ||
@@ -71,6 +74,7 @@ void clear_GmResponse(iGmResponse *d) { | |||
71 | clear_String(&d->meta); | 74 | clear_String(&d->meta); |
72 | clear_Block(&d->body); | 75 | clear_Block(&d->body); |
73 | d->certFlags = 0; | 76 | d->certFlags = 0; |
77 | clear_Block(&d->certFingerprint); | ||
74 | iZap(d->certValidUntil); | 78 | iZap(d->certValidUntil); |
75 | clear_String(&d->certSubject); | 79 | clear_String(&d->certSubject); |
76 | iZap(d->when); | 80 | iZap(d->when); |
@@ -86,7 +90,8 @@ void serialize_GmResponse(const iGmResponse *d, iStream *outs) { | |||
86 | write32_Stream(outs, d->statusCode); | 90 | write32_Stream(outs, d->statusCode); |
87 | serialize_String(&d->meta, outs); | 91 | serialize_String(&d->meta, outs); |
88 | serialize_Block(&d->body, outs); | 92 | serialize_Block(&d->body, outs); |
89 | write32_Stream(outs, d->certFlags); | 93 | /* TODO: Add certificate fingerprint, but need to bump file version first. */ |
94 | write32_Stream(outs, d->certFlags & ~haveFingerprint_GmCertFlag); | ||
90 | serialize_Date(&d->certValidUntil, outs); | 95 | serialize_Date(&d->certValidUntil, outs); |
91 | serialize_String(&d->certSubject, outs); | 96 | serialize_String(&d->certSubject, outs); |
92 | writeU64_Stream(outs, d->when.ts.tv_sec); | 97 | writeU64_Stream(outs, d->when.ts.tv_sec); |
@@ -100,6 +105,7 @@ void deserialize_GmResponse(iGmResponse *d, iStream *ins) { | |||
100 | deserialize_Date(&d->certValidUntil, ins); | 105 | deserialize_Date(&d->certValidUntil, ins); |
101 | deserialize_String(&d->certSubject, ins); | 106 | deserialize_String(&d->certSubject, ins); |
102 | iZap(d->when); | 107 | iZap(d->when); |
108 | clear_Block(&d->certFingerprint); | ||
103 | if (version_Stream(ins) >= addedResponseTimestamps_FileVersion) { | 109 | if (version_Stream(ins) >= addedResponseTimestamps_FileVersion) { |
104 | d->when.ts.tv_sec = readU64_Stream(ins); | 110 | d->when.ts.tv_sec = readU64_Stream(ins); |
105 | } | 111 | } |
@@ -138,6 +144,8 @@ static void checkServerCertificate_GmRequest_(iGmRequest *d) { | |||
138 | if (cert) { | 144 | if (cert) { |
139 | const iRangecc domain = range_String(hostName_Address(address_TlsRequest(d->req))); | 145 | const iRangecc domain = range_String(hostName_Address(address_TlsRequest(d->req))); |
140 | d->resp.certFlags |= available_GmCertFlag; | 146 | d->resp.certFlags |= available_GmCertFlag; |
147 | set_Block(&d->resp.certFingerprint, collect_Block(fingerprint_TlsCertificate(cert))); | ||
148 | d->resp.certFlags |= haveFingerprint_GmCertFlag; | ||
141 | if (!isExpired_TlsCertificate(cert)) { | 149 | if (!isExpired_TlsCertificate(cert)) { |
142 | d->resp.certFlags |= timeVerified_GmCertFlag; | 150 | d->resp.certFlags |= timeVerified_GmCertFlag; |
143 | } | 151 | } |
diff --git a/src/gmrequest.h b/src/gmrequest.h index 311b9ad6..31059a44 100644 --- a/src/gmrequest.h +++ b/src/gmrequest.h | |||
@@ -31,10 +31,11 @@ iDeclareType(GmCerts) | |||
31 | iDeclareType(GmResponse) | 31 | iDeclareType(GmResponse) |
32 | 32 | ||
33 | enum iGmCertFlags { | 33 | enum iGmCertFlags { |
34 | available_GmCertFlag = iBit(1), /* certificate provided by server */ | 34 | available_GmCertFlag = iBit(1), /* certificate provided by server */ |
35 | trusted_GmCertFlag = iBit(2), /* TOFU status */ | 35 | trusted_GmCertFlag = iBit(2), /* TOFU status */ |
36 | timeVerified_GmCertFlag = iBit(3), /* has not expired */ | 36 | timeVerified_GmCertFlag = iBit(3), /* has not expired */ |
37 | domainVerified_GmCertFlag = iBit(4), /* cert matches server domain */ | 37 | domainVerified_GmCertFlag = iBit(4), /* cert matches server domain */ |
38 | haveFingerprint_GmCertFlag = iBit(5), | ||
38 | }; | 39 | }; |
39 | 40 | ||
40 | struct Impl_GmResponse { | 41 | struct Impl_GmResponse { |
@@ -42,6 +43,7 @@ struct Impl_GmResponse { | |||
42 | iString meta; /* MIME type or other metadata */ | 43 | iString meta; /* MIME type or other metadata */ |
43 | iBlock body; | 44 | iBlock body; |
44 | int certFlags; | 45 | int certFlags; |
46 | iBlock certFingerprint; | ||
45 | iDate certValidUntil; | 47 | iDate certValidUntil; |
46 | iString certSubject; | 48 | iString certSubject; |
47 | iTime when; | 49 | iTime when; |
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 33f49ad9..94168d28 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -26,6 +26,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
26 | #include "audio/player.h" | 26 | #include "audio/player.h" |
27 | #include "command.h" | 27 | #include "command.h" |
28 | #include "defs.h" | 28 | #include "defs.h" |
29 | #include "gmcerts.h" | ||
29 | #include "gmdocument.h" | 30 | #include "gmdocument.h" |
30 | #include "gmrequest.h" | 31 | #include "gmrequest.h" |
31 | #include "gmutil.h" | 32 | #include "gmutil.h" |
@@ -137,6 +138,7 @@ struct Impl_DocumentWidget { | |||
137 | iTime sourceTime; | 138 | iTime sourceTime; |
138 | iGmDocument * doc; | 139 | iGmDocument * doc; |
139 | int certFlags; | 140 | int certFlags; |
141 | iBlock * certFingerprint; | ||
140 | iDate certExpiry; | 142 | iDate certExpiry; |
141 | iString * certSubject; | 143 | iString * certSubject; |
142 | int redirectCount; | 144 | int redirectCount; |
@@ -177,6 +179,7 @@ void init_DocumentWidget(iDocumentWidget *d) { | |||
177 | init_PersistentDocumentState(&d->mod); | 179 | init_PersistentDocumentState(&d->mod); |
178 | d->flags = 0; | 180 | d->flags = 0; |
179 | iZap(d->certExpiry); | 181 | iZap(d->certExpiry); |
182 | d->certFingerprint = new_Block(0); | ||
180 | d->certFlags = 0; | 183 | d->certFlags = 0; |
181 | d->certSubject = new_String(); | 184 | d->certSubject = new_String(); |
182 | d->state = blank_RequestState; | 185 | d->state = blank_RequestState; |
@@ -242,6 +245,7 @@ void deinit_DocumentWidget(iDocumentWidget *d) { | |||
242 | } | 245 | } |
243 | deinit_PtrArray(&d->visiblePlayers); | 246 | deinit_PtrArray(&d->visiblePlayers); |
244 | deinit_PtrArray(&d->visibleLinks); | 247 | deinit_PtrArray(&d->visibleLinks); |
248 | delete_Block(d->certFingerprint); | ||
245 | delete_String(d->certSubject); | 249 | delete_String(d->certSubject); |
246 | delete_String(d->titleUser); | 250 | delete_String(d->titleUser); |
247 | deinit_PersistentDocumentState(&d->mod); | 251 | deinit_PersistentDocumentState(&d->mod); |
@@ -881,6 +885,7 @@ static void updateTrust_DocumentWidget_(iDocumentWidget *d, const iGmResponse *r | |||
881 | if (response) { | 885 | if (response) { |
882 | d->certFlags = response->certFlags; | 886 | d->certFlags = response->certFlags; |
883 | d->certExpiry = response->certValidUntil; | 887 | d->certExpiry = response->certValidUntil; |
888 | set_Block(d->certFingerprint, &response->certFingerprint); | ||
884 | set_String(d->certSubject, &response->certSubject); | 889 | set_String(d->certSubject, &response->certSubject); |
885 | } | 890 | } |
886 | iLabelWidget *lock = findWidget_App("navbar.lock"); | 891 | iLabelWidget *lock = findWidget_App("navbar.lock"); |
@@ -1271,9 +1276,14 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
1271 | return iFalse; | 1276 | return iFalse; |
1272 | } | 1277 | } |
1273 | else if (equal_Command(cmd, "server.showcert") && d == document_App()) { | 1278 | else if (equal_Command(cmd, "server.showcert") && d == document_App()) { |
1274 | const char *unchecked = red_ColorEscape "\u2610"; | 1279 | const char *unchecked = red_ColorEscape "\u2610"; |
1275 | const char *checked = green_ColorEscape "\u2611"; | 1280 | const char *checked = green_ColorEscape "\u2611"; |
1276 | makeMessage_Widget( | 1281 | const char *actionLabels[] = { "Dismiss", uiTextCaution_ColorEscape "Trust" }; |
1282 | const char *actionCmds[] = { "message.ok", "server.trustcert" }; | ||
1283 | const iBool canTrust = | ||
1284 | (d->certFlags == (available_GmCertFlag | haveFingerprint_GmCertFlag | | ||
1285 | timeVerified_GmCertFlag | domainVerified_GmCertFlag)); | ||
1286 | iWidget *dlg = makeQuestion_Widget( | ||
1277 | uiHeading_ColorEscape "CERTIFICATE STATUS", | 1287 | uiHeading_ColorEscape "CERTIFICATE STATUS", |
1278 | format_CStr("%s%s Domain name %s%s\n" | 1288 | format_CStr("%s%s Domain name %s%s\n" |
1279 | "%s%s %s (%04d-%02d-%02d %02d:%02d:%02d)\n" | 1289 | "%s%s %s (%04d-%02d-%02d %02d:%02d:%02d)\n" |
@@ -1295,8 +1305,21 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
1295 | d->certExpiry.second, | 1305 | d->certExpiry.second, |
1296 | d->certFlags & trusted_GmCertFlag ? checked : unchecked, | 1306 | d->certFlags & trusted_GmCertFlag ? checked : unchecked, |
1297 | uiText_ColorEscape, | 1307 | uiText_ColorEscape, |
1298 | d->certFlags & trusted_GmCertFlag ? "Trusted on first use" | 1308 | d->certFlags & trusted_GmCertFlag ? "Trusted" : "Not trusted"), |
1299 | : "Not trusted")); | 1309 | actionLabels, |
1310 | actionCmds, | ||
1311 | canTrust ? 2 : 1); | ||
1312 | addAction_Widget(dlg, SDLK_ESCAPE, 0, "message.ok"); | ||
1313 | addAction_Widget(dlg, SDLK_SPACE, 0, "message.ok"); | ||
1314 | return iTrue; | ||
1315 | } | ||
1316 | else if (equal_Command(cmd, "server.trustcert")) { | ||
1317 | const iRangecc host = urlHost_String(d->mod.url); | ||
1318 | if (!isEmpty_Block(d->certFingerprint) && !isEmpty_Range(&host)) { | ||
1319 | setTrusted_GmCerts(certs_App(), host, d->certFingerprint, &d->certExpiry); | ||
1320 | d->certFlags |= trusted_GmCertFlag; | ||
1321 | postCommand_App("server.showcert"); | ||
1322 | } | ||
1300 | return iTrue; | 1323 | return iTrue; |
1301 | } | 1324 | } |
1302 | else if (equal_Command(cmd, "copy") && document_App() == d && !focus_Widget()) { | 1325 | else if (equal_Command(cmd, "copy") && document_App() == d && !focus_Widget()) { |