Be a bit more careful with fwrite.
[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         table->Add (_button_sizer, wxGBPosition (r, 0), wxGBSpan (1, 4));
412         ++r;
413
414         _private_key_bad = new StaticText (this, _("Leaf private key does not match leaf certificate!"));
415         font = *wxSMALL_FONT;
416         font.SetWeight (wxFONTWEIGHT_BOLD);
417         _private_key_bad->SetFont (font);
418         table->Add (_private_key_bad, wxGBPosition (r, 0), wxGBSpan (1, 3));
419         ++r;
420
421         _add_certificate->Bind     (wxEVT_BUTTON,       bind (&CertificateChainEditor::add_certificate, this));
422         _remove_certificate->Bind  (wxEVT_BUTTON,       bind (&CertificateChainEditor::remove_certificate, this));
423         _export_certificate->Bind  (wxEVT_BUTTON,       bind (&CertificateChainEditor::export_certificate, this));
424         _certificates->Bind        (wxEVT_LIST_ITEM_SELECTED,   bind (&CertificateChainEditor::update_sensitivity, this));
425         _certificates->Bind        (wxEVT_LIST_ITEM_DESELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
426         _remake_certificates->Bind (wxEVT_BUTTON,       bind (&CertificateChainEditor::remake_certificates, this));
427         _import_private_key->Bind  (wxEVT_BUTTON,       bind (&CertificateChainEditor::import_private_key, this));
428         _export_private_key->Bind  (wxEVT_BUTTON,       bind (&CertificateChainEditor::export_private_key, this));
429
430         wxSizer* buttons = CreateSeparatedButtonSizer (wxCLOSE);
431         if (buttons) {
432                 _sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
433         }
434
435         SetSizerAndFit (_sizer);
436
437         update_certificate_list ();
438         update_private_key ();
439         update_sensitivity ();
440 }
441
442 void
443 CertificateChainEditor::add_button (wxWindow* button)
444 {
445         _button_sizer->Add (button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
446         _sizer->Layout ();
447 }
448
449 void
450 CertificateChainEditor::add_certificate ()
451 {
452         wxFileDialog* d = new wxFileDialog (this, _("Select Certificate File"));
453
454         if (d->ShowModal() == wxID_OK) {
455                 try {
456                         dcp::Certificate c;
457                         string extra;
458                         try {
459                                 extra = c.read_string (dcp::file_to_string (wx_to_std (d->GetPath ())));
460                         } catch (boost::filesystem::filesystem_error& e) {
461                                 error_dialog (this, _("Could not import certificate (%s)"), d->GetPath());
462                                 d->Destroy ();
463                                 return;
464                         }
465
466                         if (!extra.empty ()) {
467                                 message_dialog (
468                                         this,
469                                         _("This file contains other certificates (or other data) after its first certificate. "
470                                           "Only the first certificate will be used.")
471                                         );
472                         }
473                         shared_ptr<dcp::CertificateChain> chain(new dcp::CertificateChain(*_get().get()));
474                         chain->add (c);
475                         if (!chain->chain_valid ()) {
476                                 error_dialog (
477                                         this,
478                                         _("Adding this certificate would make the chain inconsistent, so it will not be added. "
479                                           "Add certificates in order from root to intermediate to leaf.")
480                                         );
481                                 chain->remove (c);
482                         } else {
483                                 _set (chain);
484                                 update_certificate_list ();
485                         }
486                 } catch (dcp::MiscError& e) {
487                         error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
488                 }
489         }
490
491         d->Destroy ();
492
493         update_sensitivity ();
494 }
495
496 void
497 CertificateChainEditor::remove_certificate ()
498 {
499         int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
500         if (i == -1) {
501                 return;
502         }
503
504         _certificates->DeleteItem (i);
505         shared_ptr<dcp::CertificateChain> chain(new dcp::CertificateChain(*_get().get()));
506         chain->remove (i);
507         _set (chain);
508
509         update_sensitivity ();
510 }
511
512 void
513 CertificateChainEditor::export_certificate ()
514 {
515         int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
516         if (i == -1) {
517                 return;
518         }
519
520         wxFileDialog* d = new wxFileDialog (
521                 this, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
522                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
523                 );
524
525         dcp::CertificateChain::List all = _get()->root_to_leaf ();
526         dcp::CertificateChain::List::iterator j = all.begin ();
527         for (int k = 0; k < i; ++k) {
528                 ++j;
529         }
530
531         if (d->ShowModal () == wxID_OK) {
532                 boost::filesystem::path path (wx_to_std(d->GetPath()));
533                 FILE* f = fopen_boost (path, "w");
534                 if (!f) {
535                         throw OpenFileError (path, errno, false);
536                 }
537
538                 string const s = j->certificate (true);
539                 checked_fwrite (s.c_str(), s.length(), f, path);
540                 fclose (f);
541         }
542         d->Destroy ();
543 }
544
545 void
546 CertificateChainEditor::update_certificate_list ()
547 {
548         _certificates->DeleteAllItems ();
549         size_t n = 0;
550         dcp::CertificateChain::List certs = _get()->root_to_leaf ();
551         BOOST_FOREACH (dcp::Certificate const & i, certs) {
552                 wxListItem item;
553                 item.SetId (n);
554                 _certificates->InsertItem (item);
555                 _certificates->SetItem (n, 1, std_to_wx (i.thumbprint ()));
556
557                 if (n == 0) {
558                         _certificates->SetItem (n, 0, _("Root"));
559                 } else if (n == (certs.size() - 1)) {
560                         _certificates->SetItem (n, 0, _("Leaf"));
561                 } else {
562                         _certificates->SetItem (n, 0, _("Intermediate"));
563                 }
564
565                 ++n;
566         }
567
568         static wxColour normal = _private_key_bad->GetForegroundColour ();
569
570         if (_get()->private_key_valid()) {
571                 _private_key_bad->Hide ();
572                 _private_key_bad->SetForegroundColour (normal);
573         } else {
574                 _private_key_bad->Show ();
575                 _private_key_bad->SetForegroundColour (wxColour (255, 0, 0));
576         }
577 }
578
579 void
580 CertificateChainEditor::remake_certificates ()
581 {
582         shared_ptr<const dcp::CertificateChain> chain = _get();
583
584         string subject_organization_name;
585         string subject_organizational_unit_name;
586         string root_common_name;
587         string intermediate_common_name;
588         string leaf_common_name;
589
590         dcp::CertificateChain::List all = chain->root_to_leaf ();
591
592         if (all.size() >= 1) {
593                 /* Have a root */
594                 subject_organization_name = chain->root().subject_organization_name ();
595                 subject_organizational_unit_name = chain->root().subject_organizational_unit_name ();
596                 root_common_name = chain->root().subject_common_name ();
597         }
598
599         if (all.size() >= 2) {
600                 /* Have a leaf */
601                 leaf_common_name = chain->leaf().subject_common_name ();
602         }
603
604         if (all.size() >= 3) {
605                 /* Have an intermediate */
606                 dcp::CertificateChain::List::iterator i = all.begin ();
607                 ++i;
608                 intermediate_common_name = i->subject_common_name ();
609         }
610
611         if (_nag_remake()) {
612                 /* Cancel was clicked */
613                 return;
614         }
615
616         MakeChainDialog* d = new MakeChainDialog (
617                 this,
618                 subject_organization_name,
619                 subject_organizational_unit_name,
620                 root_common_name,
621                 intermediate_common_name,
622                 leaf_common_name
623                 );
624
625         if (d->ShowModal () == wxID_OK) {
626                 _set (
627                         shared_ptr<dcp::CertificateChain> (
628                                 new dcp::CertificateChain (
629                                         openssl_path (),
630                                         d->organisation (),
631                                         d->organisational_unit (),
632                                         d->root_common_name (),
633                                         d->intermediate_common_name (),
634                                         d->leaf_common_name ()
635                                         )
636                                 )
637                         );
638
639                 update_certificate_list ();
640                 update_private_key ();
641         }
642
643         d->Destroy ();
644 }
645
646 void
647 CertificateChainEditor::update_sensitivity ()
648 {
649         /* We can only remove the leaf certificate */
650         _remove_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == (_certificates->GetItemCount() - 1));
651         _export_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
652 }
653
654 void
655 CertificateChainEditor::update_private_key ()
656 {
657         checked_set (_private_key, dcp::private_key_fingerprint (_get()->key().get()));
658         _sizer->Layout ();
659 }
660
661 void
662 CertificateChainEditor::import_private_key ()
663 {
664         wxFileDialog* d = new wxFileDialog (this, _("Select Key File"));
665
666         if (d->ShowModal() == wxID_OK) {
667                 try {
668                         boost::filesystem::path p (wx_to_std (d->GetPath ()));
669                         if (boost::filesystem::file_size (p) > 8192) {
670                                 error_dialog (
671                                         this,
672                                         wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
673                                         );
674                                 return;
675                         }
676
677                         shared_ptr<dcp::CertificateChain> chain(new dcp::CertificateChain(*_get().get()));
678                         chain->set_key (dcp::file_to_string (p));
679                         _set (chain);
680                         update_private_key ();
681                 } catch (dcp::MiscError& e) {
682                         error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
683                 }
684         }
685
686         d->Destroy ();
687
688         update_sensitivity ();
689 }
690
691 void
692 CertificateChainEditor::export_private_key ()
693 {
694         optional<string> key = _get()->key();
695         if (!key) {
696                 return;
697         }
698
699         wxFileDialog* d = new wxFileDialog (
700                 this, _("Select Key File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
701                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
702                 );
703
704         if (d->ShowModal () == wxID_OK) {
705                 boost::filesystem::path path (wx_to_std(d->GetPath()));
706                 FILE* f = fopen_boost (path, "w");
707                 if (!f) {
708                         throw OpenFileError (path, errno, false);
709                 }
710
711                 string const s = _get()->key().get ();
712                 checked_fwrite (s.c_str(), s.length(), f, path);
713                 fclose (f);
714         }
715         d->Destroy ();
716 }
717
718 wxString
719 KeysPage::GetName () const
720 {
721         return _("Keys");
722 }
723
724 void
725 KeysPage::setup ()
726 {
727         wxFont subheading_font (*wxNORMAL_FONT);
728         subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
729
730         wxSizer* sizer = _panel->GetSizer();
731
732         {
733                 wxStaticText* m = new StaticText (_panel, _("Decrypting KDMs"));
734                 m->SetFont (subheading_font);
735                 sizer->Add (m, 0, wxALL, _border);
736         }
737
738         wxButton* export_decryption_certificate = new Button (_panel, _("Export KDM decryption certificate..."));
739         sizer->Add (export_decryption_certificate, 0, wxLEFT, _border);
740         wxButton* export_decryption_chain = new Button (_panel, _("Export KDM decryption chain..."));
741         sizer->Add (export_decryption_chain, 0, wxLEFT, _border);
742         wxButton* export_settings = new Button (_panel, _("Export all KDM decryption settings..."));
743         sizer->Add (export_settings, 0, wxLEFT, _border);
744         wxButton* import_settings = new Button (_panel, _("Import all KDM decryption settings..."));
745         sizer->Add (import_settings, 0, wxLEFT, _border);
746         wxButton* decryption_advanced = new Button (_panel, _("Advanced..."));
747         sizer->Add (decryption_advanced, 0, wxALL, _border);
748
749         export_decryption_certificate->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_certificate, this));
750         export_decryption_chain->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain, this));
751         export_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain_and_key, this));
752         import_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::import_decryption_chain_and_key, this));
753         decryption_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::decryption_advanced, this));
754
755         {
756                 wxStaticText* m = new StaticText (_panel, _("Signing DCPs and KDMs"));
757                 m->SetFont (subheading_font);
758                 sizer->Add (m, 0, wxALL, _border);
759         }
760
761         wxButton* signing_advanced = new Button (_panel, _("Advanced..."));
762         sizer->Add (signing_advanced, 0, wxLEFT, _border);
763         signing_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::signing_advanced, this));
764 }
765
766 void
767 KeysPage::decryption_advanced ()
768 {
769         CertificateChainEditor* c = new CertificateChainEditor (
770                 _panel, _("Decrypting KDMs"), _border,
771                 bind (&Config::set_decryption_chain, Config::instance (), _1),
772                 bind (&Config::decryption_chain, Config::instance ()),
773                 bind (&KeysPage::nag_remake_decryption_chain, this)
774                 );
775
776         c->ShowModal();
777 }
778
779 void
780 KeysPage::signing_advanced ()
781 {
782         CertificateChainEditor* c = new CertificateChainEditor (
783                 _panel, _("Signing DCPs and KDMs"), _border,
784                 bind (&Config::set_signer_chain, Config::instance (), _1),
785                 bind (&Config::signer_chain, Config::instance ()),
786                 bind (&do_nothing)
787                 );
788
789         c->ShowModal();
790 }
791
792 void
793 KeysPage::export_decryption_chain_and_key ()
794 {
795         wxFileDialog* d = new wxFileDialog (
796                 _panel, _("Select Export File"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom"),
797                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
798                 );
799
800         if (d->ShowModal () == wxID_OK) {
801                 boost::filesystem::path path (wx_to_std(d->GetPath()));
802                 FILE* f = fopen_boost (path, "w");
803                 if (!f) {
804                         throw OpenFileError (path, errno, false);
805                 }
806
807                 string const chain = Config::instance()->decryption_chain()->chain();
808                 checked_fwrite (chain.c_str(), chain.length(), f, path);
809                 optional<string> const key = Config::instance()->decryption_chain()->key();
810                 DCPOMATIC_ASSERT (key);
811                 checked_fwrite (key->c_str(), key->length(), f, path);
812                 fclose (f);
813         }
814         d->Destroy ();
815
816 }
817
818 void
819 KeysPage::import_decryption_chain_and_key ()
820 {
821         wxFileDialog* d = new wxFileDialog (
822                 _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom")
823                 );
824
825         if (d->ShowModal () == wxID_OK) {
826                 shared_ptr<dcp::CertificateChain> new_chain(new dcp::CertificateChain());
827
828                 FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "r");
829                 if (!f) {
830                         throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
831                 }
832
833                 string current;
834                 while (!feof (f)) {
835                         char buffer[128];
836                         if (fgets (buffer, 128, f) == 0) {
837                                 break;
838                         }
839                         current += buffer;
840                         if (strncmp (buffer, "-----END CERTIFICATE-----", 25) == 0) {
841                                 new_chain->add (dcp::Certificate (current));
842                                 current = "";
843                         } else if (strncmp (buffer, "-----END RSA PRIVATE KEY-----", 29) == 0) {
844                                 new_chain->set_key (current);
845                                 current = "";
846                         }
847                 }
848                 fclose (f);
849
850                 if (new_chain->chain_valid() && new_chain->private_key_valid()) {
851                         Config::instance()->set_decryption_chain (new_chain);
852                 } else {
853                         error_dialog (_panel, _("Invalid DCP-o-matic export file"));
854                 }
855         }
856         d->Destroy ();
857 }
858
859 bool
860 KeysPage::nag_remake_decryption_chain ()
861 {
862         return NagDialog::maybe_nag (
863                 _panel,
864                 Config::NAG_REMAKE_DECRYPTION_CHAIN,
865                 _("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!"),
866                 true
867                 );
868 }
869
870 void
871 KeysPage::export_decryption_chain ()
872 {
873         wxFileDialog* d = new wxFileDialog (
874                 _panel, _("Select Chain File"), wxEmptyString, _("dcpomatic_kdm_decryption_chain.pem"), wxT ("PEM files (*.pem)|*.pem"),
875                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
876                 );
877
878         if (d->ShowModal () == wxID_OK) {
879                 boost::filesystem::path path (wx_to_std(d->GetPath()));
880                 FILE* f = fopen_boost (path, "w");
881                 if (!f) {
882                         throw OpenFileError (path, errno, false);
883                 }
884
885                 string const s = Config::instance()->decryption_chain()->chain();
886                 checked_fwrite (s.c_str(), s.length(), f, path);
887                 fclose (f);
888         }
889         d->Destroy ();
890 }
891
892 void
893 KeysPage::export_decryption_certificate ()
894 {
895         wxFileDialog* d = new wxFileDialog (
896                 _panel, _("Select Certificate File"), wxEmptyString, _("dcpomatic_kdm_decryption_cert.pem"), wxT ("PEM files (*.pem)|*.pem"),
897                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
898                 );
899
900         if (d->ShowModal () == wxID_OK) {
901                 boost::filesystem::path path (wx_to_std(d->GetPath()));
902                 FILE* f = fopen_boost (path, "w");
903                 if (!f) {
904                         throw OpenFileError (path, errno, false);
905                 }
906
907                 string const s = Config::instance()->decryption_chain()->leaf().certificate (true);
908                 checked_fwrite (s.c_str(), s.length(), f, path);
909                 fclose (f);
910         }
911
912         d->Destroy ();
913 }