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