summaryrefslogtreecommitdiff
path: root/src/wx/certificate_chain_editor.cc
diff options
context:
space:
mode:
authorCarl Hetherington <cth@carlh.net>2025-03-01 22:38:03 +0100
committerCarl Hetherington <cth@carlh.net>2025-03-01 22:38:03 +0100
commit88b1b8b47233d8215bb796d6f8836810ed767836 (patch)
tree42f45c7b97a62975c052db86fe03cca6cacc8aaa /src/wx/certificate_chain_editor.cc
parent921b3573e25c42ef3e490c00beee07605c9a0a46 (diff)
Move CertificateChainEditor to its own files and format white space.
Diffstat (limited to 'src/wx/certificate_chain_editor.cc')
-rw-r--r--src/wx/certificate_chain_editor.cc416
1 files changed, 416 insertions, 0 deletions
diff --git a/src/wx/certificate_chain_editor.cc b/src/wx/certificate_chain_editor.cc
new file mode 100644
index 000000000..97275cc79
--- /dev/null
+++ b/src/wx/certificate_chain_editor.cc
@@ -0,0 +1,416 @@
+/*
+ Copyright (C) 2025 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ DCP-o-matic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "certificate_chain_editor.h"
+#include "dcpomatic_button.h"
+#include "make_chain_dialog.h"
+#include "static_text.h"
+#include "wx_util.h"
+#include <dcp/certificate_chain.h>
+#include <dcp/exceptions.h>
+#include <dcp/file.h>
+#include <dcp/filesystem.h>
+#include <wx/listctrl.h>
+
+
+using std::function;
+using std::make_shared;
+using std::shared_ptr;
+using std::string;
+using boost::bind;
+#if BOOST_VERSION >= 106100
+using namespace boost::placeholders;
+#endif
+
+
+CertificateChainEditor::CertificateChainEditor(
+ wxWindow* parent,
+ wxString title,
+ int border,
+ function<void (shared_ptr<dcp::CertificateChain>)> set,
+ function<shared_ptr<const dcp::CertificateChain> (void)> get,
+ function<bool (void)> nag_alter
+ )
+ : wxDialog(parent, wxID_ANY, title)
+ , _set(set)
+ , _get(get)
+ , _nag_alter(nag_alter)
+{
+ _sizer = new wxBoxSizer(wxVERTICAL);
+
+ auto certificates_sizer = new wxBoxSizer(wxHORIZONTAL);
+ _sizer->Add(certificates_sizer, 0, wxALL, border);
+
+ _certificates = new wxListCtrl(this, wxID_ANY, wxDefaultPosition, wxSize(440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
+
+ {
+ wxListItem ip;
+ ip.SetId(0);
+ ip.SetText(_("Type"));
+ ip.SetWidth(100);
+ _certificates->InsertColumn(0, ip);
+ }
+
+ {
+ wxListItem ip;
+ ip.SetId(1);
+ ip.SetText(_("Thumbprint"));
+ ip.SetWidth(340);
+
+ auto font = ip.GetFont();
+ font.SetFamily(wxFONTFAMILY_TELETYPE);
+ ip.SetFont(font);
+
+ _certificates->InsertColumn(1, ip);
+ }
+
+ certificates_sizer->Add(_certificates, 1, wxEXPAND);
+
+ {
+ auto s = new wxBoxSizer(wxVERTICAL);
+ _add_certificate = new Button(this, _("Add..."));
+ s->Add(_add_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
+ _remove_certificate = new Button(this, _("Remove"));
+ s->Add(_remove_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
+ _export_certificate = new Button(this, _("Export certificate..."));
+ s->Add(_export_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
+ _export_chain = new Button(this, _("Export chain..."));
+ s->Add(_export_chain, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
+ certificates_sizer->Add(s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
+ }
+
+ auto table = new wxGridBagSizer(DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+ _sizer->Add(table, 1, wxALL | wxEXPAND, border);
+ int r = 0;
+
+ add_label_to_sizer(table, this, _("Leaf private key"), true, wxGBPosition(r, 0));
+ _private_key = new StaticText(this, {});
+ wxFont font = _private_key->GetFont();
+ font.SetFamily(wxFONTFAMILY_TELETYPE);
+ _private_key->SetFont(font);
+ table->Add(_private_key, wxGBPosition(r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
+ _import_private_key = new Button(this, _("Import..."));
+ table->Add(_import_private_key, wxGBPosition(r, 2));
+ _export_private_key = new Button(this, _("Export..."));
+ table->Add(_export_private_key, wxGBPosition(r, 3));
+ ++r;
+
+ _button_sizer = new wxBoxSizer(wxHORIZONTAL);
+ _remake_certificates = new Button(this, _("Re-make certificates and key..."));
+ _button_sizer->Add(_remake_certificates, 1, wxRIGHT, border);
+ table->Add(_button_sizer, wxGBPosition(r, 0), wxGBSpan(1, 4));
+ ++r;
+
+ _private_key_bad = new StaticText(this, _("Leaf private key does not match leaf certificate!"));
+ font = *wxSMALL_FONT;
+ font.SetWeight(wxFONTWEIGHT_BOLD);
+ _private_key_bad->SetFont(font);
+ table->Add(_private_key_bad, wxGBPosition(r, 0), wxGBSpan(1, 3));
+ ++r;
+
+ _add_certificate->Bind(wxEVT_BUTTON, bind(&CertificateChainEditor::add_certificate, this));
+ _remove_certificate->Bind(wxEVT_BUTTON, bind(&CertificateChainEditor::remove_certificate, this));
+ _export_certificate->Bind(wxEVT_BUTTON, bind(&CertificateChainEditor::export_certificate, this));
+ _certificates->Bind(wxEVT_LIST_ITEM_SELECTED, bind(&CertificateChainEditor::update_sensitivity, this));
+ _certificates->Bind(wxEVT_LIST_ITEM_DESELECTED, bind(&CertificateChainEditor::update_sensitivity, this));
+ _remake_certificates->Bind(wxEVT_BUTTON, bind(&CertificateChainEditor::remake_certificates, this));
+ _export_chain->Bind(wxEVT_BUTTON, bind(&CertificateChainEditor::export_chain, this));
+ _import_private_key->Bind(wxEVT_BUTTON, bind(&CertificateChainEditor::import_private_key, this));
+ _export_private_key->Bind(wxEVT_BUTTON, bind(&CertificateChainEditor::export_private_key, this));
+
+ auto buttons = CreateSeparatedButtonSizer(wxCLOSE);
+ if (buttons) {
+ _sizer->Add(buttons, wxSizerFlags().Expand().DoubleBorder());
+ }
+
+ SetSizerAndFit(_sizer);
+
+ update_certificate_list();
+ update_private_key();
+ update_sensitivity();
+}
+
+void
+CertificateChainEditor::add_button(wxWindow* button)
+{
+ _button_sizer->Add(button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
+ _sizer->Layout();
+}
+
+void
+CertificateChainEditor::add_certificate()
+{
+ wxFileDialog dialog(this, _("Select Certificate file"), {}, {}, char_to_wx("PEM files (*.pem)|*.pem|KEY files (*.key)|*.key|All files (*.*)|*.*"));
+
+ if (dialog.ShowModal() == wxID_OK) {
+ try {
+ dcp::Certificate c;
+ string extra;
+ try {
+ extra = c.read_string(dcp::file_to_string(wx_to_std(dialog.GetPath())));
+ } catch (boost::filesystem::filesystem_error& e) {
+ error_dialog(this, _("Could not import certificate (%s)"), dialog.GetPath());
+ return;
+ }
+
+ if (!extra.empty()) {
+ message_dialog(
+ this,
+ _("This file contains other certificates (or other data) after its first certificate. "
+ "Only the first certificate will be used.")
+ );
+ }
+ auto chain = make_shared<dcp::CertificateChain>(*_get().get());
+ chain->add(c);
+ if (!chain->chain_valid()) {
+ error_dialog(
+ this,
+ _("Adding this certificate would make the chain inconsistent, so it will not be added. "
+ "Add certificates in order from root to intermediate to leaf.")
+ );
+ chain->remove(c);
+ } else {
+ _set(chain);
+ update_certificate_list();
+ }
+ } catch (dcp::MiscError& e) {
+ error_dialog(this, _("Could not read certificate file."), std_to_wx(e.what()));
+ }
+ }
+
+ update_sensitivity();
+}
+
+void
+CertificateChainEditor::remove_certificate()
+{
+ if (_nag_alter()) {
+ /* Cancel was clicked */
+ return;
+ }
+
+ int i = _certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
+ if (i == -1) {
+ return;
+ }
+
+ _certificates->DeleteItem(i);
+ auto chain = make_shared<dcp::CertificateChain>(*_get().get());
+ chain->remove(i);
+ _set(chain);
+
+ update_sensitivity();
+ update_certificate_list();
+}
+
+void
+CertificateChainEditor::export_certificate()
+{
+ int i = _certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
+ if (i == -1) {
+ return;
+ }
+
+ auto all = _get()->root_to_leaf();
+
+ wxString default_name;
+ if (i == 0) {
+ default_name = char_to_wx("root.pem");
+ } else if (i == static_cast<int>(all.size() - 1)) {
+ default_name = char_to_wx("leaf.pem");
+ } else {
+ default_name = char_to_wx("intermediate.pem");
+ }
+
+ wxFileDialog dialog(
+ this, _("Select Certificate File"), wxEmptyString, default_name, char_to_wx("PEM files (*.pem)|*.pem"),
+ wxFD_SAVE | wxFD_OVERWRITE_PROMPT
+ );
+
+ auto j = all.begin();
+ for (int k = 0; k < i; ++k) {
+ ++j;
+ }
+
+ if (dialog.ShowModal() != wxID_OK) {
+ return;
+ }
+
+ boost::filesystem::path path(wx_to_std(dialog.GetPath()));
+ if (path.extension() != ".pem") {
+ path += ".pem";
+ }
+ dcp::File f(path, "w");
+ if (!f) {
+ throw OpenFileError(path, f.open_error(), OpenFileError::WRITE);
+ }
+
+ string const s = j->certificate(true);
+ f.checked_write(s.c_str(), s.length());
+}
+
+void
+CertificateChainEditor::export_chain()
+{
+ wxFileDialog dialog(
+ this, _("Select Chain File"), wxEmptyString, char_to_wx("certificate_chain.pem"), char_to_wx("PEM files (*.pem)|*.pem"),
+ wxFD_SAVE | wxFD_OVERWRITE_PROMPT
+ );
+
+ if (dialog.ShowModal() != wxID_OK) {
+ return;
+ }
+
+ boost::filesystem::path path(wx_to_std(dialog.GetPath()));
+ if (path.extension() != ".pem") {
+ path += ".pem";
+ }
+ dcp::File f(path, "w");
+ if (!f) {
+ throw OpenFileError(path, f.open_error(), OpenFileError::WRITE);
+ }
+
+ auto const s = _get()->chain();
+ f.checked_write(s.c_str(), s.length());
+}
+
+void
+CertificateChainEditor::update_certificate_list()
+{
+ _certificates->DeleteAllItems();
+ size_t n = 0;
+ auto certs = _get()->root_to_leaf();
+ for (auto const& i: certs) {
+ wxListItem item;
+ item.SetId(n);
+ _certificates->InsertItem(item);
+ _certificates->SetItem(n, 1, std_to_wx(i.thumbprint()));
+
+ if (n == 0) {
+ _certificates->SetItem(n, 0, _("Root"));
+ } else if (n == (certs.size() - 1)) {
+ _certificates->SetItem(n, 0, _("Leaf"));
+ } else {
+ _certificates->SetItem(n, 0, _("Intermediate"));
+ }
+
+ ++n;
+ }
+
+ static wxColour normal = _private_key_bad->GetForegroundColour();
+
+ if (_get()->private_key_valid()) {
+ _private_key_bad->Hide();
+ _private_key_bad->SetForegroundColour(normal);
+ } else {
+ _private_key_bad->Show();
+ _private_key_bad->SetForegroundColour(wxColour(255, 0, 0));
+ }
+}
+
+void
+CertificateChainEditor::remake_certificates()
+{
+ if (_nag_alter()) {
+ /* Cancel was clicked */
+ return;
+ }
+
+ MakeChainDialog dialog(this, _get());
+
+ if (dialog.ShowModal() == wxID_OK) {
+ _set(dialog.get());
+ update_certificate_list();
+ update_private_key();
+ }
+}
+
+void
+CertificateChainEditor::update_sensitivity()
+{
+ /* We can only remove the leaf certificate */
+ _remove_certificate->Enable(_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == (_certificates->GetItemCount() - 1));
+ _export_certificate->Enable(_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
+}
+
+void
+CertificateChainEditor::update_private_key()
+{
+ checked_set(_private_key, dcp::private_key_fingerprint(_get()->key().get()));
+ _sizer->Layout();
+}
+
+void
+CertificateChainEditor::import_private_key()
+{
+ wxFileDialog dialog(this, _("Select Key file"), {}, {}, char_to_wx("PEM files (*.pem)|*.pem|KEY files (*.key)|*.key|All files (*.*)|*.*"));
+
+ if (dialog.ShowModal() == wxID_OK) {
+ try {
+ boost::filesystem::path p(wx_to_std(dialog.GetPath()));
+ if (dcp::filesystem::file_size(p) > 8192) {
+ error_dialog(
+ this,
+ wxString::Format(_("Could not read key file; file is too long (%s)"), std_to_wx(p.string()))
+ );
+ return;
+ }
+
+ auto chain = make_shared<dcp::CertificateChain>(*_get().get());
+ chain->set_key(dcp::file_to_string(p));
+ _set(chain);
+ update_private_key();
+ } catch (std::exception& e) {
+ error_dialog(this, _("Could not read certificate file."), std_to_wx(e.what()));
+ }
+ }
+
+ update_sensitivity();
+}
+
+void
+CertificateChainEditor::export_private_key()
+{
+ auto key = _get()->key();
+ if (!key) {
+ return;
+ }
+
+ wxFileDialog dialog(
+ this, _("Select Key File"), wxEmptyString, char_to_wx("private_key.pem"), char_to_wx("PEM files (*.pem)|*.pem"),
+ wxFD_SAVE | wxFD_OVERWRITE_PROMPT
+ );
+
+ if (dialog.ShowModal() == wxID_OK) {
+ boost::filesystem::path path(wx_to_std(dialog.GetPath()));
+ if (path.extension() != ".pem") {
+ path += ".pem";
+ }
+ dcp::File f(path, "w");
+ if (!f) {
+ throw OpenFileError(path, f.open_error(), OpenFileError::WRITE);
+ }
+
+ auto const s = _get()->key().get();
+ f.checked_write(s.c_str(), s.length());
+ }
+}
+