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