Warn on importing complete certificate chains (part of #1488).
[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 "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_remake
336         )
337         : wxDialog (parent, wxID_ANY, title)
338         , _set (set)
339         , _get (get)
340         , _nag_remake (nag_remake)
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         int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
503         if (i == -1) {
504                 return;
505         }
506
507         _certificates->DeleteItem (i);
508         shared_ptr<dcp::CertificateChain> chain(new dcp::CertificateChain(*_get().get()));
509         chain->remove (i);
510         _set (chain);
511
512         update_sensitivity ();
513 }
514
515 void
516 CertificateChainEditor::export_certificate ()
517 {
518         int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
519         if (i == -1) {
520                 return;
521         }
522
523         wxFileDialog* d = new wxFileDialog (
524                 this, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
525                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
526                 );
527
528         dcp::CertificateChain::List all = _get()->root_to_leaf ();
529         dcp::CertificateChain::List::iterator j = all.begin ();
530         for (int k = 0; k < i; ++k) {
531                 ++j;
532         }
533
534         if (d->ShowModal () == wxID_OK) {
535                 boost::filesystem::path path (wx_to_std(d->GetPath()));
536                 FILE* f = fopen_boost (path, "w");
537                 if (!f) {
538                         throw OpenFileError (path, errno, false);
539                 }
540
541                 string const s = j->certificate (true);
542                 checked_fwrite (s.c_str(), s.length(), f, path);
543                 fclose (f);
544         }
545         d->Destroy ();
546 }
547
548 void
549 CertificateChainEditor::export_chain ()
550 {
551         wxFileDialog* d = new wxFileDialog (
552                 this, _("Select Chain File"), wxEmptyString, wxEmptyString, wxT("PEM files (*.pem)|*.pem"),
553                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
554                 );
555
556         if (d->ShowModal () == wxID_OK) {
557                 boost::filesystem::path path (wx_to_std(d->GetPath()));
558                 FILE* f = fopen_boost (path, "w");
559                 if (!f) {
560                         throw OpenFileError (path, errno, false);
561                 }
562
563                 string const s = _get()->chain();
564                 checked_fwrite (s.c_str(), s.length(), f, path);
565                 fclose (f);
566         }
567
568         d->Destroy ();
569 }
570
571 void
572 CertificateChainEditor::update_certificate_list ()
573 {
574         _certificates->DeleteAllItems ();
575         size_t n = 0;
576         dcp::CertificateChain::List certs = _get()->root_to_leaf ();
577         BOOST_FOREACH (dcp::Certificate const & i, certs) {
578                 wxListItem item;
579                 item.SetId (n);
580                 _certificates->InsertItem (item);
581                 _certificates->SetItem (n, 1, std_to_wx (i.thumbprint ()));
582
583                 if (n == 0) {
584                         _certificates->SetItem (n, 0, _("Root"));
585                 } else if (n == (certs.size() - 1)) {
586                         _certificates->SetItem (n, 0, _("Leaf"));
587                 } else {
588                         _certificates->SetItem (n, 0, _("Intermediate"));
589                 }
590
591                 ++n;
592         }
593
594         static wxColour normal = _private_key_bad->GetForegroundColour ();
595
596         if (_get()->private_key_valid()) {
597                 _private_key_bad->Hide ();
598                 _private_key_bad->SetForegroundColour (normal);
599         } else {
600                 _private_key_bad->Show ();
601                 _private_key_bad->SetForegroundColour (wxColour (255, 0, 0));
602         }
603 }
604
605 void
606 CertificateChainEditor::remake_certificates ()
607 {
608         shared_ptr<const dcp::CertificateChain> chain = _get();
609
610         string subject_organization_name;
611         string subject_organizational_unit_name;
612         string root_common_name;
613         string intermediate_common_name;
614         string leaf_common_name;
615
616         dcp::CertificateChain::List all = chain->root_to_leaf ();
617
618         if (all.size() >= 1) {
619                 /* Have a root */
620                 subject_organization_name = chain->root().subject_organization_name ();
621                 subject_organizational_unit_name = chain->root().subject_organizational_unit_name ();
622                 root_common_name = chain->root().subject_common_name ();
623         }
624
625         if (all.size() >= 2) {
626                 /* Have a leaf */
627                 leaf_common_name = chain->leaf().subject_common_name ();
628         }
629
630         if (all.size() >= 3) {
631                 /* Have an intermediate */
632                 dcp::CertificateChain::List::iterator i = all.begin ();
633                 ++i;
634                 intermediate_common_name = i->subject_common_name ();
635         }
636
637         if (_nag_remake()) {
638                 /* Cancel was clicked */
639                 return;
640         }
641
642         MakeChainDialog* d = new MakeChainDialog (
643                 this,
644                 subject_organization_name,
645                 subject_organizational_unit_name,
646                 root_common_name,
647                 intermediate_common_name,
648                 leaf_common_name
649                 );
650
651         if (d->ShowModal () == wxID_OK) {
652                 _set (
653                         shared_ptr<dcp::CertificateChain> (
654                                 new dcp::CertificateChain (
655                                         openssl_path (),
656                                         d->organisation (),
657                                         d->organisational_unit (),
658                                         d->root_common_name (),
659                                         d->intermediate_common_name (),
660                                         d->leaf_common_name ()
661                                         )
662                                 )
663                         );
664
665                 update_certificate_list ();
666                 update_private_key ();
667         }
668
669         d->Destroy ();
670 }
671
672 void
673 CertificateChainEditor::update_sensitivity ()
674 {
675         /* We can only remove the leaf certificate */
676         _remove_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == (_certificates->GetItemCount() - 1));
677         _export_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
678 }
679
680 void
681 CertificateChainEditor::update_private_key ()
682 {
683         checked_set (_private_key, dcp::private_key_fingerprint (_get()->key().get()));
684         _sizer->Layout ();
685 }
686
687 void
688 CertificateChainEditor::import_private_key ()
689 {
690         wxFileDialog* d = new wxFileDialog (this, _("Select Key File"));
691
692         if (d->ShowModal() == wxID_OK) {
693                 try {
694                         boost::filesystem::path p (wx_to_std (d->GetPath ()));
695                         if (boost::filesystem::file_size (p) > 8192) {
696                                 error_dialog (
697                                         this,
698                                         wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
699                                         );
700                                 return;
701                         }
702
703                         shared_ptr<dcp::CertificateChain> chain(new dcp::CertificateChain(*_get().get()));
704                         chain->set_key (dcp::file_to_string (p));
705                         _set (chain);
706                         update_private_key ();
707                 } catch (dcp::MiscError& e) {
708                         error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
709                 }
710         }
711
712         d->Destroy ();
713
714         update_sensitivity ();
715 }
716
717 void
718 CertificateChainEditor::export_private_key ()
719 {
720         optional<string> key = _get()->key();
721         if (!key) {
722                 return;
723         }
724
725         wxFileDialog* d = new wxFileDialog (
726                 this, _("Select Key File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
727                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
728                 );
729
730         if (d->ShowModal () == wxID_OK) {
731                 boost::filesystem::path path (wx_to_std(d->GetPath()));
732                 FILE* f = fopen_boost (path, "w");
733                 if (!f) {
734                         throw OpenFileError (path, errno, false);
735                 }
736
737                 string const s = _get()->key().get ();
738                 checked_fwrite (s.c_str(), s.length(), f, path);
739                 fclose (f);
740         }
741         d->Destroy ();
742 }
743
744 wxString
745 KeysPage::GetName () const
746 {
747         return _("Keys");
748 }
749
750 void
751 KeysPage::setup ()
752 {
753         wxFont subheading_font (*wxNORMAL_FONT);
754         subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
755
756         wxSizer* sizer = _panel->GetSizer();
757
758         {
759                 wxStaticText* m = new StaticText (_panel, _("Decrypting KDMs"));
760                 m->SetFont (subheading_font);
761                 sizer->Add (m, 0, wxALL, _border);
762         }
763
764         wxButton* export_decryption_certificate = new Button (_panel, _("Export KDM decryption certificate..."));
765         sizer->Add (export_decryption_certificate, 0, wxLEFT, _border);
766         wxButton* export_settings = new Button (_panel, _("Export all KDM decryption settings..."));
767         sizer->Add (export_settings, 0, wxLEFT, _border);
768         wxButton* import_settings = new Button (_panel, _("Import all KDM decryption settings..."));
769         sizer->Add (import_settings, 0, wxLEFT, _border);
770         wxButton* decryption_advanced = new Button (_panel, _("Advanced..."));
771         sizer->Add (decryption_advanced, 0, wxALL, _border);
772
773         export_decryption_certificate->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_certificate, this));
774         export_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain_and_key, this));
775         import_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::import_decryption_chain_and_key, this));
776         decryption_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::decryption_advanced, this));
777
778         {
779                 wxStaticText* m = new StaticText (_panel, _("Signing DCPs and KDMs"));
780                 m->SetFont (subheading_font);
781                 sizer->Add (m, 0, wxALL, _border);
782         }
783
784         wxButton* signing_advanced = new Button (_panel, _("Advanced..."));
785         sizer->Add (signing_advanced, 0, wxLEFT, _border);
786         signing_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::signing_advanced, this));
787 }
788
789 void
790 KeysPage::decryption_advanced ()
791 {
792         CertificateChainEditor* c = new CertificateChainEditor (
793                 _panel, _("Decrypting KDMs"), _border,
794                 bind (&Config::set_decryption_chain, Config::instance (), _1),
795                 bind (&Config::decryption_chain, Config::instance ()),
796                 bind (&KeysPage::nag_remake_decryption_chain, this)
797                 );
798
799         c->ShowModal();
800 }
801
802 void
803 KeysPage::signing_advanced ()
804 {
805         CertificateChainEditor* c = new CertificateChainEditor (
806                 _panel, _("Signing DCPs and KDMs"), _border,
807                 bind (&Config::set_signer_chain, Config::instance (), _1),
808                 bind (&Config::signer_chain, Config::instance ()),
809                 bind (&do_nothing)
810                 );
811
812         c->ShowModal();
813 }
814
815 void
816 KeysPage::export_decryption_chain_and_key ()
817 {
818         wxFileDialog* d = new wxFileDialog (
819                 _panel, _("Select Export File"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom"),
820                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
821                 );
822
823         if (d->ShowModal () == wxID_OK) {
824                 boost::filesystem::path path (wx_to_std(d->GetPath()));
825                 FILE* f = fopen_boost (path, "w");
826                 if (!f) {
827                         throw OpenFileError (path, errno, false);
828                 }
829
830                 string const chain = Config::instance()->decryption_chain()->chain();
831                 checked_fwrite (chain.c_str(), chain.length(), f, path);
832                 optional<string> const key = Config::instance()->decryption_chain()->key();
833                 DCPOMATIC_ASSERT (key);
834                 checked_fwrite (key->c_str(), key->length(), f, path);
835                 fclose (f);
836         }
837         d->Destroy ();
838
839 }
840
841 void
842 KeysPage::import_decryption_chain_and_key ()
843 {
844         if (NagDialog::maybe_nag (
845                     _panel,
846                     Config::NAG_IMPORT_DECRYPTION_CHAIN,
847                     /* XXX: this needs to be marked translatable */
848                     wxT("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!"),
849                     true
850                     )) {
851                 return;
852         }
853
854         wxFileDialog* d = new wxFileDialog (
855                 _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom")
856                 );
857
858         if (d->ShowModal () == wxID_OK) {
859                 shared_ptr<dcp::CertificateChain> new_chain(new dcp::CertificateChain());
860
861                 FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "r");
862                 if (!f) {
863                         throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
864                 }
865
866                 string current;
867                 while (!feof (f)) {
868                         char buffer[128];
869                         if (fgets (buffer, 128, f) == 0) {
870                                 break;
871                         }
872                         current += buffer;
873                         if (strncmp (buffer, "-----END CERTIFICATE-----", 25) == 0) {
874                                 new_chain->add (dcp::Certificate (current));
875                                 current = "";
876                         } else if (strncmp (buffer, "-----END RSA PRIVATE KEY-----", 29) == 0) {
877                                 new_chain->set_key (current);
878                                 current = "";
879                         }
880                 }
881                 fclose (f);
882
883                 if (new_chain->chain_valid() && new_chain->private_key_valid()) {
884                         Config::instance()->set_decryption_chain (new_chain);
885                 } else {
886                         error_dialog (_panel, _("Invalid DCP-o-matic export file"));
887                 }
888         }
889         d->Destroy ();
890 }
891
892 bool
893 KeysPage::nag_remake_decryption_chain ()
894 {
895         return NagDialog::maybe_nag (
896                 _panel,
897                 Config::NAG_REMAKE_DECRYPTION_CHAIN,
898                 _("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!"),
899                 true
900                 );
901 }
902
903 void
904 KeysPage::export_decryption_certificate ()
905 {
906         wxFileDialog* d = new wxFileDialog (
907                 _panel, _("Select Certificate File"), wxEmptyString, _("dcpomatic_kdm_decryption_cert.pem"), wxT ("PEM files (*.pem)|*.pem"),
908                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
909                 );
910
911         if (d->ShowModal () == wxID_OK) {
912                 boost::filesystem::path path (wx_to_std(d->GetPath()));
913                 FILE* f = fopen_boost (path, "w");
914                 if (!f) {
915                         throw OpenFileError (path, errno, false);
916                 }
917
918                 string const s = Config::instance()->decryption_chain()->leaf().certificate (true);
919                 checked_fwrite (s.c_str(), s.length(), f, path);
920                 fclose (f);
921         }
922
923         d->Destroy ();
924 }