Try to improve the UI for decryption/signing keys by hiding
[dcpomatic.git] / src / wx / config_dialog.cc
1 /*
2     Copyright (C) 2012-2018 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
6     DCP-o-matic is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     DCP-o-matic is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21 #include "config_dialog.h"
22 #include "nag_dialog.h"
23
24 using std::string;
25 using boost::bind;
26 using boost::optional;
27 using boost::shared_ptr;
28
29 static
30 void
31 do_nothing ()
32 {
33
34 }
35
36 Page::Page (wxSize panel_size, int border)
37         : _border (border)
38         , _panel (0)
39         , _panel_size (panel_size)
40         , _window_exists (false)
41 {
42         _config_connection = Config::instance()->Changed.connect (boost::bind (&Page::config_changed_wrapper, this));
43 }
44
45 wxWindow*
46 Page::create_window (wxWindow* parent)
47 {
48         _panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
49         wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
50         _panel->SetSizer (s);
51
52         setup ();
53         _window_exists = true;
54         config_changed ();
55
56         _panel->Bind (wxEVT_DESTROY, boost::bind (&Page::window_destroyed, this));
57
58         return _panel;
59 }
60
61 void
62 Page::config_changed_wrapper ()
63 {
64         if (_window_exists) {
65                 config_changed ();
66         }
67 }
68
69 void
70 Page::window_destroyed ()
71 {
72         _window_exists = false;
73 }
74
75
76 StockPage::StockPage (Kind kind, wxSize panel_size, int border)
77         : wxStockPreferencesPage (kind)
78         , Page (panel_size, border)
79 {
80
81 }
82
83 wxWindow*
84 StockPage::CreateWindow (wxWindow* parent)
85 {
86         return create_window (parent);
87 }
88
89 StandardPage::StandardPage (wxSize panel_size, int border)
90         : Page (panel_size, border)
91 {
92
93 }
94
95 wxWindow*
96 StandardPage::CreateWindow (wxWindow* parent)
97 {
98         return create_window (parent);
99 }
100
101 GeneralPage::GeneralPage (wxSize panel_size, int border)
102         : StockPage (Kind_General, panel_size, border)
103 {
104
105 }
106
107 void
108 GeneralPage::add_language_controls (wxGridBagSizer* table, int& r)
109 {
110         _set_language = new wxCheckBox (_panel, wxID_ANY, _("Set language"));
111         table->Add (_set_language, wxGBPosition (r, 0));
112         _language = new wxChoice (_panel, wxID_ANY);
113         std::vector<std::pair<std::string, std::string> > languages;
114         languages.push_back (std::make_pair ("Čeština", "cs_CZ"));
115         languages.push_back (std::make_pair ("汉语/漢語", "zh_CN"));
116         languages.push_back (std::make_pair ("Dansk", "da_DK"));
117         languages.push_back (std::make_pair ("Deutsch", "de_DE"));
118         languages.push_back (std::make_pair ("English", "en_GB"));
119         languages.push_back (std::make_pair ("Español", "es_ES"));
120         languages.push_back (std::make_pair ("Français", "fr_FR"));
121         languages.push_back (std::make_pair ("Italiano", "it_IT"));
122         languages.push_back (std::make_pair ("Nederlands", "nl_NL"));
123         languages.push_back (std::make_pair ("Русский", "ru_RU"));
124         languages.push_back (std::make_pair ("Polski", "pl_PL"));
125         languages.push_back (std::make_pair ("Português europeu", "pt_PT"));
126         languages.push_back (std::make_pair ("Português do Brasil", "pt_BR"));
127         languages.push_back (std::make_pair ("Svenska", "sv_SE"));
128         languages.push_back (std::make_pair ("Slovenský jazyk", "sk_SK"));
129         languages.push_back (std::make_pair ("українська мова", "uk_UA"));
130         checked_set (_language, languages);
131         table->Add (_language, wxGBPosition (r, 1));
132         ++r;
133
134         wxStaticText* restart = add_label_to_sizer (
135                 table, _panel, _("(restart DCP-o-matic to see language changes)"), false, wxGBPosition (r, 0), wxGBSpan (1, 2)
136                 );
137         wxFont font = restart->GetFont();
138         font.SetStyle (wxFONTSTYLE_ITALIC);
139         font.SetPointSize (font.GetPointSize() - 1);
140         restart->SetFont (font);
141         ++r;
142
143         _set_language->Bind (wxEVT_CHECKBOX, boost::bind (&GeneralPage::set_language_changed, this));
144         _language->Bind     (wxEVT_CHOICE,   boost::bind (&GeneralPage::language_changed,     this));
145 }
146
147 void
148 GeneralPage::add_play_sound_controls (wxGridBagSizer* table, int& r)
149 {
150         _sound = new wxCheckBox (_panel, wxID_ANY, _("Play sound via"));
151         table->Add (_sound, wxGBPosition (r, 0));
152         _sound_output = new wxChoice (_panel, wxID_ANY);
153         table->Add (_sound_output, wxGBPosition (r, 1));
154         ++r;
155
156         RtAudio audio (DCPOMATIC_RTAUDIO_API);
157         for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
158                 RtAudio::DeviceInfo dev = audio.getDeviceInfo (i);
159                 if (dev.probed && dev.outputChannels > 0) {
160                         _sound_output->Append (std_to_wx (dev.name));
161                 }
162         }
163
164         _sound->Bind        (wxEVT_CHECKBOX, boost::bind (&GeneralPage::sound_changed, this));
165         _sound_output->Bind (wxEVT_CHOICE,   boost::bind (&GeneralPage::sound_output_changed, this));
166 }
167
168 void
169 GeneralPage::add_update_controls (wxGridBagSizer* table, int& r)
170 {
171         _check_for_updates = new wxCheckBox (_panel, wxID_ANY, _("Check for updates on startup"));
172         table->Add (_check_for_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
173         ++r;
174
175         _check_for_test_updates = new wxCheckBox (_panel, wxID_ANY, _("Check for testing updates on startup"));
176         table->Add (_check_for_test_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
177         ++r;
178
179         _check_for_updates->Bind (wxEVT_CHECKBOX, boost::bind (&GeneralPage::check_for_updates_changed, this));
180         _check_for_test_updates->Bind (wxEVT_CHECKBOX, boost::bind (&GeneralPage::check_for_test_updates_changed, this));
181 }
182
183 void
184 GeneralPage::config_changed ()
185 {
186         Config* config = Config::instance ();
187
188         checked_set (_set_language, static_cast<bool>(config->language()));
189
190         /* Backwards compatibility of config file */
191
192         std::map<std::string, std::string> compat_map;
193         compat_map["fr"] = "fr_FR";
194         compat_map["it"] = "it_IT";
195         compat_map["es"] = "es_ES";
196         compat_map["sv"] = "sv_SE";
197         compat_map["de"] = "de_DE";
198         compat_map["nl"] = "nl_NL";
199         compat_map["ru"] = "ru_RU";
200         compat_map["pl"] = "pl_PL";
201         compat_map["da"] = "da_DK";
202         compat_map["pt"] = "pt_PT";
203         compat_map["sk"] = "sk_SK";
204         compat_map["cs"] = "cs_CZ";
205         compat_map["uk"] = "uk_UA";
206
207         std::string lang = config->language().get_value_or ("en_GB");
208         if (compat_map.find (lang) != compat_map.end ()) {
209                 lang = compat_map[lang];
210         }
211
212         checked_set (_language, lang);
213
214         checked_set (_check_for_updates, config->check_for_updates ());
215         checked_set (_check_for_test_updates, config->check_for_test_updates ());
216
217         checked_set (_sound, config->sound ());
218
219         boost::optional<std::string> const current_so = get_sound_output ();
220         boost::optional<std::string> configured_so;
221
222         if (config->sound_output()) {
223                 configured_so = config->sound_output().get();
224         } else {
225                 /* No configured output means we should use the default */
226                 RtAudio audio (DCPOMATIC_RTAUDIO_API);
227                 try {
228                         configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
229                 } catch (RtAudioError& e) {
230                         /* Probably no audio devices at all */
231                 }
232         }
233
234         if (configured_so && current_so != configured_so) {
235                 /* Update _sound_output with the configured value */
236                 unsigned int i = 0;
237                 while (i < _sound_output->GetCount()) {
238                         if (_sound_output->GetString(i) == std_to_wx(*configured_so)) {
239                                 _sound_output->SetSelection (i);
240                                 break;
241                         }
242                         ++i;
243                 }
244         }
245
246         setup_sensitivity ();
247 }
248
249 void
250 GeneralPage::setup_sensitivity ()
251 {
252         _language->Enable (_set_language->GetValue ());
253         _check_for_test_updates->Enable (_check_for_updates->GetValue ());
254         _sound_output->Enable (_sound->GetValue ());
255 }
256
257 /** @return Currently-selected preview sound output in the dialogue */
258 boost::optional<std::string>
259 GeneralPage::get_sound_output ()
260 {
261         int const sel = _sound_output->GetSelection ();
262         if (sel == wxNOT_FOUND) {
263                 return boost::optional<std::string> ();
264         }
265
266         return wx_to_std (_sound_output->GetString (sel));
267 }
268
269 void
270 GeneralPage::set_language_changed ()
271 {
272         setup_sensitivity ();
273         if (_set_language->GetValue ()) {
274                 language_changed ();
275         } else {
276                 Config::instance()->unset_language ();
277         }
278 }
279
280 void
281 GeneralPage::language_changed ()
282 {
283         int const sel = _language->GetSelection ();
284         if (sel != -1) {
285                 Config::instance()->set_language (string_client_data (_language->GetClientObject (sel)));
286         } else {
287                 Config::instance()->unset_language ();
288         }
289 }
290
291 void
292 GeneralPage::check_for_updates_changed ()
293 {
294         Config::instance()->set_check_for_updates (_check_for_updates->GetValue ());
295 }
296
297 void
298 GeneralPage::check_for_test_updates_changed ()
299 {
300         Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
301 }
302
303 void
304 GeneralPage::sound_changed ()
305 {
306         Config::instance()->set_sound (_sound->GetValue ());
307 }
308
309 void
310 GeneralPage::sound_output_changed ()
311 {
312         RtAudio audio (DCPOMATIC_RTAUDIO_API);
313         boost::optional<std::string> const so = get_sound_output();
314         if (!so || *so == audio.getDeviceInfo(audio.getDefaultOutputDevice()).name) {
315                 Config::instance()->unset_sound_output ();
316         } else {
317                 Config::instance()->set_sound_output (*so);
318         }
319 }
320
321 CertificateChainEditor::CertificateChainEditor (
322         wxWindow* parent,
323         wxString title,
324         int border,
325         boost::function<void (boost::shared_ptr<dcp::CertificateChain>)> set,
326         boost::function<boost::shared_ptr<const dcp::CertificateChain> (void)> get,
327         boost::function<void (void)> nag_remake
328         )
329         : wxDialog (parent, wxID_ANY, title)
330         , _set (set)
331         , _get (get)
332         , _nag_remake (nag_remake)
333 {
334         wxFont subheading_font (*wxNORMAL_FONT);
335         subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
336
337         _sizer = new wxBoxSizer (wxVERTICAL);
338
339         {
340                 wxStaticText* m = new wxStaticText (this, wxID_ANY, title);
341                 m->SetFont (subheading_font);
342                 _sizer->Add (m, 0, wxALL, border);
343         }
344
345         wxBoxSizer* certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
346         _sizer->Add (certificates_sizer, 0, wxLEFT | wxRIGHT, border);
347
348         _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
349
350         {
351                 wxListItem ip;
352                 ip.SetId (0);
353                 ip.SetText (_("Type"));
354                 ip.SetWidth (100);
355                 _certificates->InsertColumn (0, ip);
356         }
357
358         {
359                 wxListItem ip;
360                 ip.SetId (1);
361                 ip.SetText (_("Thumbprint"));
362                 ip.SetWidth (340);
363
364                 wxFont font = ip.GetFont ();
365                 font.SetFamily (wxFONTFAMILY_TELETYPE);
366                 ip.SetFont (font);
367
368                 _certificates->InsertColumn (1, ip);
369         }
370
371         certificates_sizer->Add (_certificates, 1, wxEXPAND);
372
373         {
374                 wxSizer* s = new wxBoxSizer (wxVERTICAL);
375                 _add_certificate = new wxButton (this, wxID_ANY, _("Add..."));
376                 s->Add (_add_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
377                 _remove_certificate = new wxButton (this, wxID_ANY, _("Remove"));
378                 s->Add (_remove_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
379                 _export_certificate = new wxButton (this, wxID_ANY, _("Export"));
380                 s->Add (_export_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
381                 certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
382         }
383
384         wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
385         _sizer->Add (table, 1, wxALL | wxEXPAND, border);
386         int r = 0;
387
388         add_label_to_sizer (table, this, _("Leaf private key"), true, wxGBPosition (r, 0));
389         _private_key = new wxStaticText (this, wxID_ANY, wxT (""));
390         wxFont font = _private_key->GetFont ();
391         font.SetFamily (wxFONTFAMILY_TELETYPE);
392         _private_key->SetFont (font);
393         table->Add (_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
394         _import_private_key = new wxButton (this, wxID_ANY, _("Import..."));
395         table->Add (_import_private_key, wxGBPosition (r, 2));
396         _export_private_key = new wxButton (this, wxID_ANY, _("Export..."));
397         table->Add (_export_private_key, wxGBPosition (r, 3));
398         ++r;
399
400         _button_sizer = new wxBoxSizer (wxHORIZONTAL);
401         _remake_certificates = new wxButton (this, wxID_ANY, _("Re-make certificates and key..."));
402         _button_sizer->Add (_remake_certificates, 1, wxRIGHT, border);
403         table->Add (_button_sizer, wxGBPosition (r, 0), wxGBSpan (1, 4));
404         ++r;
405
406         _private_key_bad = new wxStaticText (this, wxID_ANY, _("Leaf private key does not match leaf certificate!"));
407         font = *wxSMALL_FONT;
408         font.SetWeight (wxFONTWEIGHT_BOLD);
409         _private_key_bad->SetFont (font);
410         table->Add (_private_key_bad, wxGBPosition (r, 0), wxGBSpan (1, 3));
411         ++r;
412
413         _add_certificate->Bind     (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::add_certificate, this));
414         _remove_certificate->Bind  (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::remove_certificate, this));
415         _export_certificate->Bind  (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::export_certificate, this));
416         _certificates->Bind        (wxEVT_LIST_ITEM_SELECTED,   boost::bind (&CertificateChainEditor::update_sensitivity, this));
417         _certificates->Bind        (wxEVT_LIST_ITEM_DESELECTED, boost::bind (&CertificateChainEditor::update_sensitivity, this));
418         _remake_certificates->Bind (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::remake_certificates, this));
419         _import_private_key->Bind  (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::import_private_key, this));
420         _export_private_key->Bind  (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::export_private_key, this));
421
422         wxSizer* buttons = CreateSeparatedButtonSizer (wxCLOSE);
423         if (buttons) {
424                 _sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
425         }
426
427         SetSizerAndFit (_sizer);
428
429         update_certificate_list ();
430         update_private_key ();
431         update_sensitivity ();
432 }
433
434 void
435 CertificateChainEditor::add_button (wxWindow* button)
436 {
437         _button_sizer->Add (button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
438         _sizer->Layout ();
439 }
440
441 void
442 CertificateChainEditor::add_certificate ()
443 {
444         wxFileDialog* d = new wxFileDialog (this, _("Select Certificate File"));
445
446         if (d->ShowModal() == wxID_OK) {
447                 try {
448                         dcp::Certificate c;
449                         std::string extra;
450                         try {
451                                 extra = c.read_string (dcp::file_to_string (wx_to_std (d->GetPath ())));
452                         } catch (boost::filesystem::filesystem_error& e) {
453                                 error_dialog (this, wxString::Format (_("Could not import certificate (%s)"), d->GetPath().data()));
454                                 d->Destroy ();
455                                 return;
456                         }
457
458                         if (!extra.empty ()) {
459                                 message_dialog (
460                                         this,
461                                         _("This file contains other certificates (or other data) after its first certificate. "
462                                           "Only the first certificate will be used.")
463                                         );
464                         }
465                         shared_ptr<dcp::CertificateChain> chain(new dcp::CertificateChain(*_get().get()));
466                         chain->add (c);
467                         if (!chain->chain_valid ()) {
468                                 error_dialog (
469                                         this,
470                                         _("Adding this certificate would make the chain inconsistent, so it will not be added. "
471                                           "Add certificates in order from root to intermediate to leaf.")
472                                         );
473                                 chain->remove (c);
474                         } else {
475                                 _set (chain);
476                                 update_certificate_list ();
477                         }
478                 } catch (dcp::MiscError& e) {
479                         error_dialog (this, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
480                 }
481         }
482
483         d->Destroy ();
484
485         update_sensitivity ();
486 }
487
488 void
489 CertificateChainEditor::remove_certificate ()
490 {
491         int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
492         if (i == -1) {
493                 return;
494         }
495
496         _certificates->DeleteItem (i);
497         shared_ptr<dcp::CertificateChain> chain(new dcp::CertificateChain(*_get().get()));
498         chain->remove (i);
499         _set (chain);
500
501         update_sensitivity ();
502 }
503
504 void
505 CertificateChainEditor::export_certificate ()
506 {
507         int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
508         if (i == -1) {
509                 return;
510         }
511
512         wxFileDialog* d = new wxFileDialog (
513                 this, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
514                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
515                 );
516
517         dcp::CertificateChain::List all = _get()->root_to_leaf ();
518         dcp::CertificateChain::List::iterator j = all.begin ();
519         for (int k = 0; k < i; ++k) {
520                 ++j;
521         }
522
523         if (d->ShowModal () == wxID_OK) {
524                 FILE* f = fopen_boost (path_from_file_dialog (d, "pem"), "w");
525                 if (!f) {
526                         throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
527                 }
528
529                 std::string const s = j->certificate (true);
530                 fwrite (s.c_str(), 1, s.length(), f);
531                 fclose (f);
532         }
533         d->Destroy ();
534 }
535
536 void
537 CertificateChainEditor::update_certificate_list ()
538 {
539         _certificates->DeleteAllItems ();
540         size_t n = 0;
541         dcp::CertificateChain::List certs = _get()->root_to_leaf ();
542         BOOST_FOREACH (dcp::Certificate const & i, certs) {
543                 wxListItem item;
544                 item.SetId (n);
545                 _certificates->InsertItem (item);
546                 _certificates->SetItem (n, 1, std_to_wx (i.thumbprint ()));
547
548                 if (n == 0) {
549                         _certificates->SetItem (n, 0, _("Root"));
550                 } else if (n == (certs.size() - 1)) {
551                         _certificates->SetItem (n, 0, _("Leaf"));
552                 } else {
553                         _certificates->SetItem (n, 0, _("Intermediate"));
554                 }
555
556                 ++n;
557         }
558
559         static wxColour normal = _private_key_bad->GetForegroundColour ();
560
561         if (_get()->private_key_valid()) {
562                 _private_key_bad->Hide ();
563                 _private_key_bad->SetForegroundColour (normal);
564         } else {
565                 _private_key_bad->Show ();
566                 _private_key_bad->SetForegroundColour (wxColour (255, 0, 0));
567         }
568 }
569
570 void
571 CertificateChainEditor::remake_certificates ()
572 {
573         boost::shared_ptr<const dcp::CertificateChain> chain = _get();
574
575         std::string subject_organization_name;
576         std::string subject_organizational_unit_name;
577         std::string root_common_name;
578         std::string intermediate_common_name;
579         std::string leaf_common_name;
580
581         dcp::CertificateChain::List all = chain->root_to_leaf ();
582
583         if (all.size() >= 1) {
584                 /* Have a root */
585                 subject_organization_name = chain->root().subject_organization_name ();
586                 subject_organizational_unit_name = chain->root().subject_organizational_unit_name ();
587                 root_common_name = chain->root().subject_common_name ();
588         }
589
590         if (all.size() >= 2) {
591                 /* Have a leaf */
592                 leaf_common_name = chain->leaf().subject_common_name ();
593         }
594
595         if (all.size() >= 3) {
596                 /* Have an intermediate */
597                 dcp::CertificateChain::List::iterator i = all.begin ();
598                 ++i;
599                 intermediate_common_name = i->subject_common_name ();
600         }
601
602         _nag_remake ();
603
604         MakeChainDialog* d = new MakeChainDialog (
605                 this,
606                 subject_organization_name,
607                 subject_organizational_unit_name,
608                 root_common_name,
609                 intermediate_common_name,
610                 leaf_common_name
611                 );
612
613         if (d->ShowModal () == wxID_OK) {
614                 _set (
615                         shared_ptr<dcp::CertificateChain> (
616                                 new dcp::CertificateChain (
617                                         openssl_path (),
618                                         d->organisation (),
619                                         d->organisational_unit (),
620                                         d->root_common_name (),
621                                         d->intermediate_common_name (),
622                                         d->leaf_common_name ()
623                                         )
624                                 )
625                         );
626
627                 update_certificate_list ();
628                 update_private_key ();
629         }
630
631         d->Destroy ();
632 }
633
634 void
635 CertificateChainEditor::update_sensitivity ()
636 {
637         /* We can only remove the leaf certificate */
638         _remove_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == (_certificates->GetItemCount() - 1));
639         _export_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
640 }
641
642 void
643 CertificateChainEditor::update_private_key ()
644 {
645         checked_set (_private_key, dcp::private_key_fingerprint (_get()->key().get()));
646         _sizer->Layout ();
647 }
648
649 void
650 CertificateChainEditor::import_private_key ()
651 {
652         wxFileDialog* d = new wxFileDialog (this, _("Select Key File"));
653
654         if (d->ShowModal() == wxID_OK) {
655                 try {
656                         boost::filesystem::path p (wx_to_std (d->GetPath ()));
657                         if (boost::filesystem::file_size (p) > 8192) {
658                                 error_dialog (
659                                         this,
660                                         wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
661                                         );
662                                 return;
663                         }
664
665                         shared_ptr<dcp::CertificateChain> chain(new dcp::CertificateChain(*_get().get()));
666                         chain->set_key (dcp::file_to_string (p));
667                         _set (chain);
668                         update_private_key ();
669                 } catch (dcp::MiscError& e) {
670                         error_dialog (this, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
671                 }
672         }
673
674         d->Destroy ();
675
676         update_sensitivity ();
677 }
678
679 void
680 CertificateChainEditor::export_private_key ()
681 {
682         boost::optional<std::string> key = _get()->key();
683         if (!key) {
684                 return;
685         }
686
687         wxFileDialog* d = new wxFileDialog (
688                 this, _("Select Key File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
689                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
690                 );
691
692         if (d->ShowModal () == wxID_OK) {
693                 FILE* f = fopen_boost (path_from_file_dialog (d, "pem"), "w");
694                 if (!f) {
695                         throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
696                 }
697
698                 std::string const s = _get()->key().get ();
699                 fwrite (s.c_str(), 1, s.length(), f);
700                 fclose (f);
701         }
702         d->Destroy ();
703 }
704
705 wxString
706 KeysPage::GetName () const
707 {
708         return _("Keys");
709 }
710
711 void
712 KeysPage::setup ()
713 {
714         wxFont subheading_font (*wxNORMAL_FONT);
715         subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
716
717         wxSizer* sizer = _panel->GetSizer();
718
719         {
720                 wxStaticText* m = new wxStaticText (_panel, wxID_ANY, _("Decrypting KDMs"));
721                 m->SetFont (subheading_font);
722                 sizer->Add (m, 0, wxALL, _border);
723         }
724
725         wxButton* export_decryption_certificate = new wxButton (_panel, wxID_ANY, _("Export KDM decryption certificate..."));
726         sizer->Add (export_decryption_certificate, 0, wxLEFT, _border);
727         wxButton* export_decryption_chain = new wxButton (_panel, wxID_ANY, _("Export KDM decryption chain..."));
728         sizer->Add (export_decryption_chain, 0, wxLEFT, _border);
729         wxButton* export_settings = new wxButton (_panel, wxID_ANY, _("Export all KDM decryption settings..."));
730         sizer->Add (export_settings, 0, wxLEFT, _border);
731         wxButton* import_settings = new wxButton (_panel, wxID_ANY, _("Import all KDM decryption settings..."));
732         sizer->Add (import_settings, 0, wxLEFT, _border);
733         wxButton* decryption_advanced = new wxButton (_panel, wxID_ANY, _("Advanced..."));
734         sizer->Add (decryption_advanced, 0, wxALL, _border);
735
736         export_decryption_certificate->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_certificate, this));
737         export_decryption_chain->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain, this));
738         export_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain_and_key, this));
739         import_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::import_decryption_chain_and_key, this));
740         decryption_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::decryption_advanced, this));
741
742         {
743                 wxStaticText* m = new wxStaticText (_panel, wxID_ANY, _("Signing DCPs and KDMs"));
744                 m->SetFont (subheading_font);
745                 sizer->Add (m, 0, wxALL, _border);
746         }
747
748         wxButton* signing_advanced = new wxButton (_panel, wxID_ANY, _("Advanced..."));
749         sizer->Add (signing_advanced, 0, wxLEFT, _border);
750         signing_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::signing_advanced, this));
751 }
752
753 void
754 KeysPage::decryption_advanced ()
755 {
756         CertificateChainEditor* c = new CertificateChainEditor (
757                 _panel, _("Decrypting KDMs"), _border,
758                 bind (&Config::set_decryption_chain, Config::instance (), _1),
759                 bind (&Config::decryption_chain, Config::instance ()),
760                 bind (&KeysPage::nag_remake_decryption_chain, this)
761                 );
762
763         c->ShowModal();
764 }
765
766 void
767 KeysPage::signing_advanced ()
768 {
769         CertificateChainEditor* c = new CertificateChainEditor (
770                 _panel, _("Signing DCPs and KDMs"), _border,
771                 bind (&Config::set_signer_chain, Config::instance (), _1),
772                 bind (&Config::signer_chain, Config::instance ()),
773                 bind (&do_nothing)
774                 );
775
776         c->ShowModal();
777 }
778
779 void
780 KeysPage::export_decryption_chain_and_key ()
781 {
782         wxFileDialog* d = new wxFileDialog (
783                 _panel, _("Select Export File"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom"),
784                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
785                 );
786
787         if (d->ShowModal () == wxID_OK) {
788                 FILE* f = fopen_boost (path_from_file_dialog (d, "dom"), "w");
789                 if (!f) {
790                         throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
791                 }
792
793                 string const chain = Config::instance()->decryption_chain()->chain();
794                 fwrite (chain.c_str(), 1, chain.length(), f);
795                 optional<string> const key = Config::instance()->decryption_chain()->key();
796                 DCPOMATIC_ASSERT (key);
797                 fwrite (key->c_str(), 1, key->length(), f);
798                 fclose (f);
799         }
800         d->Destroy ();
801
802 }
803
804 void
805 KeysPage::import_decryption_chain_and_key ()
806 {
807         wxFileDialog* d = new wxFileDialog (
808                 _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom")
809                 );
810
811         if (d->ShowModal () == wxID_OK) {
812                 shared_ptr<dcp::CertificateChain> new_chain(new dcp::CertificateChain());
813
814                 FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "r");
815                 if (!f) {
816                         throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
817                 }
818
819                 string current;
820                 while (!feof (f)) {
821                         char buffer[128];
822                         if (fgets (buffer, 128, f) == 0) {
823                                 break;
824                         }
825                         current += buffer;
826                         if (strncmp (buffer, "-----END CERTIFICATE-----", 25) == 0) {
827                                 new_chain->add (dcp::Certificate (current));
828                                 current = "";
829                         } else if (strncmp (buffer, "-----END RSA PRIVATE KEY-----", 29) == 0) {
830                                 std::cout << "the key is " << current << "\n";
831                                 new_chain->set_key (current);
832                                 current = "";
833                         }
834                 }
835                 fclose (f);
836
837                 if (new_chain->chain_valid() && new_chain->private_key_valid()) {
838                         Config::instance()->set_decryption_chain (new_chain);
839                 } else {
840                         error_dialog (_panel, _("Invalid DCP-o-matic export file"));
841                 }
842         }
843         d->Destroy ();
844 }
845
846 void
847 KeysPage::nag_remake_decryption_chain ()
848 {
849         NagDialog::maybe_nag (
850                 _panel,
851                 Config::NAG_REMAKE_DECRYPTION_CHAIN,
852                 _("If you continue with this operation you will no longer be able to use any DKDMs that you have created.  Also, any KDMs that have been sent to you will become useless.  Proceed with caution!")
853                 );
854 }
855
856 void
857 KeysPage::export_decryption_chain ()
858 {
859         wxFileDialog* d = new wxFileDialog (
860                 _panel, _("Select Chain File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
861                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
862                 );
863
864         if (d->ShowModal () == wxID_OK) {
865                 FILE* f = fopen_boost (path_from_file_dialog (d, "pem"), "w");
866                 if (!f) {
867                         throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
868                 }
869
870                 std::string const s = Config::instance()->decryption_chain()->chain();
871                 fwrite (s.c_str(), 1, s.length(), f);
872                 fclose (f);
873         }
874         d->Destroy ();
875 }
876
877 void
878 KeysPage::export_decryption_certificate ()
879 {
880         wxFileDialog* d = new wxFileDialog (
881                 _panel, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
882                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
883                 );
884
885         if (d->ShowModal () == wxID_OK) {
886                 FILE* f = fopen_boost (path_from_file_dialog (d, "pem"), "w");
887                 if (!f) {
888                         throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
889                 }
890
891                 std::string const s = Config::instance()->decryption_chain()->leaf().certificate (true);
892                 fwrite (s.c_str(), 1, s.length(), f);
893                 fclose (f);
894         }
895         d->Destroy ();
896 }