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