source: filezilla/trunk/fuentes/src/interface/verifycertdialog.cpp @ 3185

Last change on this file since 3185 was 3185, checked in by jrpelegrina, 2 years ago

Update new version: 3.15.02

File size: 16.5 KB
Line 
1#include <filezilla.h>
2#include "filezillaapp.h"
3#include "verifycertdialog.h"
4#include "dialogex.h"
5#include "ipcmutex.h"
6#include "Options.h"
7#include "timeformatting.h"
8#include "xrc_helper.h"
9
10#include <wx/scrolwin.h>
11#include <wx/statbox.h>
12#include <wx/tokenzr.h>
13
14CVerifyCertDialog::CVerifyCertDialog()
15        : m_xmlFile(wxGetApp().GetSettingsFile(_T("trustedcerts")))
16{
17}
18
19CVerifyCertDialog::~CVerifyCertDialog()
20{
21        for (auto iter = m_trustedCerts.begin(); iter != m_trustedCerts.end(); ++iter)
22                delete [] iter->data;
23        for (auto iter = m_sessionTrustedCerts.begin(); iter != m_sessionTrustedCerts.end(); ++iter)
24                delete [] iter->data;
25}
26
27bool CVerifyCertDialog::DisplayCert(wxDialogEx* pDlg, const CCertificate& cert)
28{
29        bool warning = false;
30        if (cert.GetActivationTime().empty()) {
31                if (cert.GetActivationTime() > fz::datetime::now()) {
32                        pDlg->SetChildLabel(XRCID("ID_ACTIVATION_TIME"), wxString::Format(_("%s - Not yet valid!"), CTimeFormat::Format(cert.GetActivationTime())));
33                        xrc_call(*pDlg, "ID_ACTIVATION_TIME", &wxWindow::SetForegroundColour, wxColour(255, 0, 0));
34                        warning = true;
35                }
36                else
37                        pDlg->SetChildLabel(XRCID("ID_ACTIVATION_TIME"), CTimeFormat::Format(cert.GetActivationTime()));
38        }
39        else {
40                warning = true;
41                pDlg->SetChildLabel(XRCID("ID_ACTIVATION_TIME"), _("Invalid date"));
42        }
43
44        if (cert.GetExpirationTime().empty()) {
45                if (cert.GetExpirationTime() < fz::datetime::now()) {
46                        pDlg->SetChildLabel(XRCID("ID_EXPIRATION_TIME"), wxString::Format(_("%s - Certificate expired!"), CTimeFormat::Format(cert.GetExpirationTime())));
47                        xrc_call(*pDlg, "ID_EXPIRATION_TIME", &wxWindow::SetForegroundColour, wxColour(255, 0, 0));
48                        warning = true;
49                }
50                else
51                        pDlg->SetChildLabel(XRCID("ID_EXPIRATION_TIME"), CTimeFormat::Format(cert.GetExpirationTime()));
52        }
53        else {
54                warning = true;
55                pDlg->SetChildLabel(XRCID("ID_EXPIRATION_TIME"), _("Invalid date"));
56        }
57
58        if (!cert.GetSerial().empty())
59                pDlg->SetChildLabel(XRCID("ID_SERIAL"), cert.GetSerial());
60        else
61                pDlg->SetChildLabel(XRCID("ID_SERIAL"), _("None"));
62
63        pDlg->SetChildLabel(XRCID("ID_PKALGO"), wxString::Format(_("%s with %d bits"), cert.GetPkAlgoName(), cert.GetPkAlgoBits()));
64        pDlg->SetChildLabel(XRCID("ID_SIGNALGO"), cert.GetSignatureAlgorithm());
65
66        wxString const& sha256 = cert.GetFingerPrintSHA256();
67        pDlg->SetChildLabel(XRCID("ID_FINGERPRINT_SHA256"), sha256.Left(sha256.size() / 2 + 1) + _T("\n") + sha256.Mid(sha256.size() / 2 + 1));
68        pDlg->SetChildLabel(XRCID("ID_FINGERPRINT_SHA1"), cert.GetFingerPrintSHA1());
69
70        ParseDN(XRCCTRL(*pDlg, "ID_ISSUER_BOX", wxStaticBox), cert.GetIssuer(), m_pIssuerSizer);
71
72        auto subjectPanel = XRCCTRL(*pDlg, "ID_SUBJECT_PANEL", wxScrolledWindow);
73        subjectPanel->Freeze();
74
75        ParseDN(subjectPanel, cert.GetSubject(), m_pSubjectSizer);
76
77        auto const& altNames = cert.GetAltSubjectNames();
78        if (!altNames.empty()) {
79                wxString str;
80                for (auto const& altName : altNames) {
81                        str += altName + _T("\n");
82                }
83                str.RemoveLast();
84                m_pSubjectSizer->Add(new wxStaticText(subjectPanel, wxID_ANY, wxPLURAL("Alternative name:", "Alternative names:", altNames.size())));
85                m_pSubjectSizer->Add(new wxStaticText(subjectPanel, wxID_ANY, str));
86        }
87        m_pSubjectSizer->Fit(subjectPanel);
88
89        wxSize min = m_pSubjectSizer->CalcMin();
90        int const maxHeight = (line_height_ + m_pDlg->ConvertDialogToPixels(wxPoint(0, 1)).y) * 15;
91        if (min.y >= maxHeight) {
92                min.y = maxHeight;
93                min.x += wxSystemSettings::GetMetric(wxSYS_VSCROLL_X);
94        }
95
96        // Add extra safety margin to prevent squishing on OS X.
97        min.x += 2;
98
99        subjectPanel->SetMinSize(min);
100        subjectPanel->Thaw();
101
102        return warning;
103}
104
105#include <wx/scrolwin.h>
106
107bool CVerifyCertDialog::DisplayAlgorithm(int controlId, wxString name, bool insecure)
108{
109        if (insecure) {
110                name += _T(" - ");
111                name += _("Insecure algorithm!");
112
113                auto wnd = m_pDlg->FindWindow(controlId);
114                if (wnd) {
115                        wnd->SetForegroundColour(wxColour(255, 0, 0));
116                }
117        }
118
119        m_pDlg->SetChildLabel(controlId, name);
120
121        return insecure;
122}
123
124void CVerifyCertDialog::ShowVerificationDialog(CCertificateNotification& notification, bool displayOnly /*=false*/)
125{
126        LoadTrustedCerts();
127
128        m_pDlg = new wxDialogEx;
129        if (!m_pDlg->Load(0, _T("ID_VERIFYCERT"))) {
130                wxBell();
131                delete m_pDlg;
132                m_pDlg = 0;
133                return;
134        }
135
136        if (displayOnly) {
137                xrc_call(*m_pDlg, "ID_DESC", &wxWindow::Hide);
138                xrc_call(*m_pDlg, "ID_ALWAYS_DESC", &wxWindow::Hide);
139                xrc_call(*m_pDlg, "ID_ALWAYS", &wxWindow::Hide);
140                xrc_call(*m_pDlg, "wxID_CANCEL", &wxWindow::Hide);
141                m_pDlg->SetTitle(_T("Certificate details"));
142        }
143        else {
144                m_pDlg->WrapText(m_pDlg, XRCID("ID_DESC"), 400);
145
146                if (COptions::Get()->GetOptionVal(OPTION_DEFAULT_KIOSKMODE) == 2)
147                        XRCCTRL(*m_pDlg, "ID_ALWAYS", wxCheckBox)->Hide();
148        }
149
150        m_certificates = notification.GetCertificates();
151        if (m_certificates.size() == 1) {
152                XRCCTRL(*m_pDlg, "ID_CHAIN_DESC", wxStaticText)->Hide();
153                XRCCTRL(*m_pDlg, "ID_CHAIN", wxChoice)->Hide();
154        }
155        else {
156                wxChoice* pChoice = XRCCTRL(*m_pDlg, "ID_CHAIN", wxChoice);
157                for (unsigned int i = 0; i < m_certificates.size(); ++i) {
158                        pChoice->Append(wxString::Format(_T("%d"), i));
159                }
160                pChoice->SetSelection(0);
161
162                pChoice->Connect(wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler(CVerifyCertDialog::OnCertificateChoice), 0, this);
163        }
164
165        m_pDlg->SetChildLabel(XRCID("ID_HOST"), wxString::Format(_T("%s:%d"), notification.GetHost(), notification.GetPort()));
166
167        line_height_ = XRCCTRL(*m_pDlg, "ID_SUBJECT_DUMMY", wxStaticText)->GetSize().y;
168
169        m_pSubjectSizer = XRCCTRL(*m_pDlg, "ID_SUBJECT_DUMMY", wxStaticText)->GetContainingSizer();
170        m_pSubjectSizer->Clear(true);
171
172        m_pIssuerSizer = XRCCTRL(*m_pDlg, "ID_ISSUER_DUMMY", wxStaticText)->GetContainingSizer();
173        m_pIssuerSizer->Clear(true);
174
175        wxSize minSize(0, 0);
176        for (unsigned int i = 0; i < m_certificates.size(); ++i) {
177                DisplayCert(m_pDlg, m_certificates[i]);
178                m_pDlg->Layout();
179                m_pDlg->GetSizer()->Fit(m_pDlg);
180                minSize.IncTo(m_pDlg->GetSizer()->GetMinSize());
181        }
182        m_pDlg->GetSizer()->SetMinSize(minSize);
183
184        bool warning = DisplayCert(m_pDlg, m_certificates[0]);
185
186        DisplayAlgorithm(XRCID("ID_PROTOCOL"), notification.GetProtocol(), (notification.GetAlgorithmWarnings() & CCertificateNotification::tlsver) != 0);
187        DisplayAlgorithm(XRCID("ID_KEYEXCHANGE"), notification.GetKeyExchange(), (notification.GetAlgorithmWarnings() & CCertificateNotification::kex) != 0);
188        DisplayAlgorithm(XRCID("ID_CIPHER"), notification.GetSessionCipher(), (notification.GetAlgorithmWarnings() & CCertificateNotification::cipher) != 0);
189        DisplayAlgorithm(XRCID("ID_MAC"), notification.GetSessionMac(), (notification.GetAlgorithmWarnings() & CCertificateNotification::mac) != 0);
190
191        if (notification.GetAlgorithmWarnings() != 0) {
192                warning = true;
193        }
194
195        if (warning) {
196                XRCCTRL(*m_pDlg, "ID_IMAGE", wxStaticBitmap)->SetBitmap(wxArtProvider::GetBitmap(wxART_WARNING));
197                XRCCTRL(*m_pDlg, "ID_ALWAYS", wxCheckBox)->Enable(false);
198        }
199
200        m_pDlg->GetSizer()->Fit(m_pDlg);
201        m_pDlg->GetSizer()->SetSizeHints(m_pDlg);
202
203        int res = m_pDlg->ShowModal();
204
205        if (!displayOnly) {
206                if (res == wxID_OK) {
207                        wxASSERT(!IsTrusted(notification));
208
209                        notification.m_trusted = true;
210
211                        if (!notification.GetAlgorithmWarnings()) {
212                                if (!warning && XRCCTRL(*m_pDlg, "ID_ALWAYS", wxCheckBox)->GetValue())
213                                        SetPermanentlyTrusted(notification);
214                                else {
215                                        t_certData cert;
216                                        cert.host = notification.GetHost();
217                                        cert.port = notification.GetPort();
218                                        const unsigned char* data = m_certificates[0].GetRawData(cert.len);
219                                        cert.data = new unsigned char[cert.len];
220                                        memcpy(cert.data, data, cert.len);
221                                        m_sessionTrustedCerts.push_back(cert);
222                                }
223                        }
224                }
225                else
226                        notification.m_trusted = false;
227        }
228
229        delete m_pDlg;
230        m_pDlg = 0;
231}
232
233void CVerifyCertDialog::ParseDN(wxWindow* parent, const wxString& dn, wxSizer* pSizer)
234{
235        pSizer->Clear(true);
236
237        wxStringTokenizer tokens(dn, _T(","));
238
239        std::list<wxString> tokenlist;
240        while (tokens.HasMoreTokens())
241                tokenlist.push_back(tokens.GetNextToken());
242
243        ParseDN_by_prefix(parent, tokenlist, _T("CN"), _("Common name:"), pSizer);
244        ParseDN_by_prefix(parent, tokenlist, _T("O"), _("Organization:"), pSizer);
245        ParseDN_by_prefix(parent, tokenlist, _T("2.5.4.15"), _("Business category:"), pSizer, true);
246        ParseDN_by_prefix(parent, tokenlist, _T("OU"), _("Unit:"), pSizer);
247        ParseDN_by_prefix(parent, tokenlist, _T("T"), _("Title:"), pSizer);
248        ParseDN_by_prefix(parent, tokenlist, _T("C"), _("Country:"), pSizer);
249        ParseDN_by_prefix(parent, tokenlist, _T("ST"), _("State or province:"), pSizer);
250        ParseDN_by_prefix(parent, tokenlist, _T("L"), _("Locality:"), pSizer);
251        ParseDN_by_prefix(parent, tokenlist, _T("2.5.4.17"), _("Postal code:"), pSizer, true);
252        ParseDN_by_prefix(parent, tokenlist, _T("postalCode"), _("Postal code:"), pSizer, true);
253        ParseDN_by_prefix(parent, tokenlist, _T("STREET"), _("Street:"), pSizer);
254        ParseDN_by_prefix(parent, tokenlist, _T("EMAIL"), _("E-Mail:"), pSizer);
255        ParseDN_by_prefix(parent, tokenlist, _T("serialNumber"), _("Serial number:"), pSizer);
256        ParseDN_by_prefix(parent, tokenlist, _T("1.3.6.1.4.1.311.60.2.1.3"), _("Jurisdiction country:"), pSizer, true);
257        ParseDN_by_prefix(parent, tokenlist, _T("1.3.6.1.4.1.311.60.2.1.2"), _("Jurisdiction state or province:"), pSizer, true);
258        ParseDN_by_prefix(parent, tokenlist, _T("1.3.6.1.4.1.311.60.2.1.1"), _("Jurisdiction locality:"), pSizer, true);
259
260        if (!tokenlist.empty())
261        {
262                wxString value = tokenlist.front();
263                for (std::list<wxString>::const_iterator iter = ++tokenlist.begin(); iter != tokenlist.end(); ++iter)
264                        value += _T(",") + *iter;
265
266                pSizer->Add(new wxStaticText(parent, wxID_ANY, _("Other:")));
267                pSizer->Add(new wxStaticText(parent, wxID_ANY, value));
268        }
269}
270
271void CVerifyCertDialog::ParseDN_by_prefix(wxWindow* parent, std::list<wxString>& tokens, wxString prefix, const wxString& name, wxSizer* pSizer, bool decode /*=false*/)
272{
273        prefix += _T("=");
274        int len = prefix.Length();
275
276        wxString value;
277
278        bool append = false;
279
280        auto iter = tokens.begin();
281        while (iter != tokens.end())
282        {
283                if (!append)
284                {
285                        if (iter->Left(len) != prefix)
286                        {
287                                ++iter;
288                                continue;
289                        }
290
291                        if (!value.empty())
292                                value += _T("\n");
293                }
294                else
295                {
296                        append = false;
297                        value += _T(",");
298                }
299
300                value += iter->Mid(len);
301
302                if (iter->Last() == '\\')
303                {
304                        value.RemoveLast();
305                        append = true;
306                        len = 0;
307                }
308
309                auto remove = iter++;
310                tokens.erase(remove);
311        }
312
313        if (decode)
314                value = DecodeValue(value);
315
316        if (!value.empty())
317        {
318                pSizer->Add(new wxStaticText(parent, wxID_ANY, name));
319                pSizer->Add(new wxStaticText(parent, wxID_ANY, value));
320        }
321}
322
323bool CVerifyCertDialog::IsTrusted(CCertificateNotification const& notification)
324{
325        if (notification.GetAlgorithmWarnings() != 0) {
326                // These certs are never trusted.
327                return false;
328        }
329
330        LoadTrustedCerts();
331
332        unsigned int len;
333        CCertificate cert =  notification.GetCertificates()[0];
334        const unsigned char* data = cert.GetRawData(len);
335
336        return IsTrusted(notification.GetHost(), notification.GetPort(), data, len, false);
337}
338
339bool CVerifyCertDialog::DoIsTrusted(const wxString& host, int port, const unsigned char* data, unsigned int len, std::list<CVerifyCertDialog::t_certData> const& trustedCerts)
340{
341        if( !data || !len ) {
342                return false;
343        }
344
345        for ( auto const& cert : trustedCerts ) {
346                if (host != cert.host)
347                        continue;
348
349                if (port != cert.port)
350                        continue;
351
352                if (cert.len != len)
353                        continue;
354
355                if (!memcmp(cert.data, data, len))
356                        return true;
357        }
358
359        return false;
360}
361
362bool CVerifyCertDialog::IsTrusted(const wxString& host, int port, const unsigned char* data, unsigned int len, bool permanentOnly)
363{
364        bool trusted = DoIsTrusted(host, port, data, len, m_trustedCerts);
365        if( !trusted && !permanentOnly ) {
366                trusted = DoIsTrusted(host, port, data, len, m_sessionTrustedCerts);
367        }
368
369        return trusted;
370}
371
372wxString CVerifyCertDialog::ConvertHexToString(const unsigned char* data, unsigned int len)
373{
374        wxString str;
375        for (unsigned int i = 0; i < len; i++)
376        {
377                const unsigned char& c = data[i];
378
379                const unsigned char low = c & 0x0F;
380                const unsigned char high = (c & 0xF0) >> 4;
381
382                if (high < 10)
383                        str += '0' + high;
384                else
385                        str += 'A' + high - 10;
386
387                if (low < 10)
388                        str += '0' + low;
389                else
390                        str += 'A' + low - 10;
391        }
392
393        return str;
394}
395
396unsigned char* CVerifyCertDialog::ConvertStringToHex(const wxString& str, unsigned int &len)
397{
398        if (str.size() % 2) {
399                return 0;
400        }
401
402        len = str.size() / 2;
403        unsigned char* data = new unsigned char[len];
404
405        unsigned int j = 0;
406        for (unsigned int i = 0; i < str.size(); ++i, ++j)
407        {
408                wxChar high = str[i++];
409                wxChar low = str[i];
410
411                if (high >= '0' && high <= '9')
412                        high -= '0';
413                else if (high >= 'A' && high <= 'F')
414                        high -= 'A' - 10;
415                else
416                {
417                        delete [] data;
418                        return 0;
419                }
420
421                if (low >= '0' && low <= '9')
422                        low -= '0';
423                else if (low >= 'A' && low <= 'F')
424                        low -= 'A' - 10;
425                else
426                {
427                        delete [] data;
428                        return 0;
429                }
430
431                data[j] = ((unsigned char)high << 4) + (unsigned char)low;
432        }
433
434        return data;
435}
436
437void CVerifyCertDialog::LoadTrustedCerts()
438{
439        CReentrantInterProcessMutexLocker mutex(MUTEX_TRUSTEDCERTS);
440        if (!m_xmlFile.Modified()) {
441                return;
442        }
443
444        auto element = m_xmlFile.Load();
445        if (!element) {
446                return;
447        }
448
449        m_trustedCerts.clear();
450
451        if (!(element = element.child("TrustedCerts")))
452                return;
453
454        bool modified = false;
455
456        auto cert = element.child("Certificate");
457        while (cert) {
458                wxString value = GetTextElement(cert, "Data");
459
460                pugi::xml_node remove;
461
462                t_certData data;
463                if (value.empty() || !(data.data = ConvertStringToHex(value, data.len)))
464                        remove = cert;
465
466                data.host = GetTextElement(cert, "Host");
467                data.port = GetTextElementInt(cert, "Port");
468                if (data.host.empty() || data.port < 1 || data.port > 65535)
469                        remove = cert;
470
471                int64_t activationTime = GetTextElementInt(cert, "ActivationTime", 0);
472                if (activationTime == 0 || activationTime > wxDateTime::GetTimeNow())
473                        remove = cert;
474
475                int64_t expirationTime = GetTextElementInt(cert, "ExpirationTime", 0);
476                if (expirationTime == 0 || expirationTime < wxDateTime::GetTimeNow())
477                        remove = cert;
478
479                if (IsTrusted(data.host, data.port, data.data, data.len, true)) // Weed out duplicates
480                        remove = cert;
481
482                if (!remove)
483                        m_trustedCerts.push_back(data);
484                else
485                        delete [] data.data;
486
487                cert = cert.next_sibling("Certificate");
488
489                if (remove) {
490                        modified = true;
491                        element.remove_child(remove);
492                }
493        }
494
495        if (modified)
496                m_xmlFile.Save(false);
497}
498
499void CVerifyCertDialog::SetPermanentlyTrusted(CCertificateNotification const& notification)
500{
501        const CCertificate certificate = notification.GetCertificates()[0];
502        unsigned int len;
503        const unsigned char* const data = certificate.GetRawData(len);
504
505        CReentrantInterProcessMutexLocker mutex(MUTEX_TRUSTEDCERTS);
506        LoadTrustedCerts();
507
508        if (IsTrusted(notification.GetHost(), notification.GetPort(), data, len, true)) {
509                return;
510        }
511
512        t_certData cert;
513        cert.host = notification.GetHost();
514        cert.port = notification.GetPort();
515        cert.len = len;
516        cert.data = new unsigned char[len];
517        memcpy(cert.data, data, len);
518        m_trustedCerts.push_back(cert);
519
520        if (COptions::Get()->GetOptionVal(OPTION_DEFAULT_KIOSKMODE) == 2) {
521                return;
522        }
523
524        auto element = m_xmlFile.GetElement();
525        if (!element) {
526                return;
527        }
528
529        auto certs = element.child("TrustedCerts");
530        if (!certs)
531                certs = element.append_child("TrustedCerts");
532
533        auto xCert = certs.append_child("Certificate");
534        AddTextElement(xCert, "Data", ConvertHexToString(data, len));
535        AddTextElement(xCert, "ActivationTime", static_cast<int64_t>(certificate.GetActivationTime().get_time_t()));
536        AddTextElement(xCert, "ExpirationTime", static_cast<int64_t>(certificate.GetExpirationTime().get_time_t()));
537        AddTextElement(xCert, "Host", notification.GetHost());
538        AddTextElement(xCert, "Port", notification.GetPort());
539
540        m_xmlFile.Save(true);
541}
542
543wxString CVerifyCertDialog::DecodeValue(const wxString& value)
544{
545        // Decodes string in hex notation
546        // #xxxx466F6F626172 -> Foobar
547        // First two encoded bytes are ignored, some weird type information I don't care about
548        // Only accepts ASCII for now.
549        if (value.empty() || value[0] != '#')
550                return value;
551
552        unsigned int len = value.Len();
553
554        wxString out;
555
556        for (unsigned int i = 5; i + 1 < len; i += 2)
557        {
558                wxChar c = value[i];
559                wxChar d = value[i + 1];
560                if (c >= '0' && c <= '9')
561                        c -= '0';
562                else if (c >= 'a' && c <= 'z')
563                        c -= 'a' - 10;
564                else if (c >= 'A' && c <= 'Z')
565                        c -= 'A' - 10;
566                else
567                        continue;
568                        //return value;
569                if (d >= '0' && d <= '9')
570                        d -= '0';
571                else if (d >= 'a' && d <= 'z')
572                        d -= 'a' - 10;
573                else if (d >= 'A' && d <= 'Z')
574                        d -= 'A' - 10;
575                else
576                        continue;
577                        //return value;
578
579                c = c * 16 + d;
580                if (c > 127 || c < 32)
581                        continue;
582                out += c;
583        }
584
585        return out;
586}
587
588void CVerifyCertDialog::OnCertificateChoice(wxCommandEvent& event)
589{
590        int sel = event.GetSelection();
591        if (sel < 0 || sel > (int)m_certificates.size())
592                return;
593        DisplayCert(m_pDlg, m_certificates[sel]);
594
595        m_pDlg->Layout();
596        m_pDlg->GetSizer()->Fit(m_pDlg);
597        m_pDlg->Refresh();
598}
Note: See TracBrowser for help on using the repository browser.