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