diff --git a/Base/res/apps/CertificatesSettings.af b/Base/res/apps/CertificatesSettings.af new file mode 100644 index 0000000000..1ba704c966 --- /dev/null +++ b/Base/res/apps/CertificatesSettings.af @@ -0,0 +1,6 @@ +[App] +Name=Certificate Settings +Executable=/bin/CertificateSettings +Category=Settings +Description=Access and change the system's certificates +ExcludeFromSystemMenu=true diff --git a/Base/res/icons/16x16/certificate.png b/Base/res/icons/16x16/certificate.png new file mode 100644 index 0000000000..720f00d66c Binary files /dev/null and b/Base/res/icons/16x16/certificate.png differ diff --git a/Base/res/icons/32x32/certificate.png b/Base/res/icons/32x32/certificate.png new file mode 100644 index 0000000000..6346e7eaf5 Binary files /dev/null and b/Base/res/icons/32x32/certificate.png differ diff --git a/Userland/Applications/CMakeLists.txt b/Userland/Applications/CMakeLists.txt index 1750029d35..fd5e5a0742 100644 --- a/Userland/Applications/CMakeLists.txt +++ b/Userland/Applications/CMakeLists.txt @@ -7,6 +7,7 @@ add_subdirectory(BrowserSettings) add_subdirectory(Calculator) add_subdirectory(Calendar) add_subdirectory(CalendarSettings) +add_subdirectory(CertificateSettings) add_subdirectory(CharacterMap) add_subdirectory(ClockSettings) add_subdirectory(CrashReporter) diff --git a/Userland/Applications/CertificateSettings/CMakeLists.txt b/Userland/Applications/CertificateSettings/CMakeLists.txt new file mode 100644 index 0000000000..26d4e6fec9 --- /dev/null +++ b/Userland/Applications/CertificateSettings/CMakeLists.txt @@ -0,0 +1,19 @@ +serenity_component( + CertificateSettings + REQUIRED + TARGETS CertificateSettings +) + +compile_gml(CertificateStore.gml CertificateStoreGML.h certificate_store_gml) + +set(SOURCES + CertificateStore.cpp + main.cpp +) + +set(GENERATED_SOURCES + CertificateStoreGML.h +) + +serenity_app(CertificateSettings ICON certificate) +target_link_libraries(CertificateSettings PRIVATE LibCore LibCrypto LibGfx LibGUI LibMain LibTLS) diff --git a/Userland/Applications/CertificateSettings/CertificateStore.cpp b/Userland/Applications/CertificateSettings/CertificateStore.cpp new file mode 100644 index 0000000000..4101bbed03 --- /dev/null +++ b/Userland/Applications/CertificateSettings/CertificateStore.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2023, Fabian Dellwing + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "CertificateStore.h" +#include +#include + +namespace CertificateSettings { + +NonnullRefPtr CertificateStoreModel::create() { return adopt_ref(*new CertificateStoreModel); } + +ErrorOr CertificateStoreModel::load() +{ + // FIXME: In the future, we will allow users to import their own certificates. To support this, we would need to change this logic + auto cacert_file = TRY(Core::File::open("/etc/cacert.pem"sv, Core::File::OpenMode::Read)); + auto data = TRY(cacert_file->read_until_eof()); + m_certificates = TRY(DefaultRootCACertificates::the().reload_certificates(data)); + + return {}; +} + +DeprecatedString CertificateStoreModel::column_name(int column) const +{ + switch (column) { + case Column::IssuedTo: + return "Issued To"; + case Column::IssuedBy: + return "Issued By"; + case Column::Expire: + return "Expiration Date"; + default: + VERIFY_NOT_REACHED(); + } +} + +GUI::Variant CertificateStoreModel::data(GUI::ModelIndex const& index, GUI::ModelRole role) const +{ + if (role != GUI::ModelRole::Display) + return {}; + if (m_certificates.is_empty()) + return {}; + + auto cert = m_certificates.at(index.row()); + + switch (index.column()) { + case Column::IssuedTo: + return cert.subject.subject.is_empty() ? cert.subject.unit : cert.subject.subject; + case Column::IssuedBy: + return cert.issuer.subject.is_empty() ? cert.issuer.unit : cert.issuer.subject; + case Column::Expire: + return cert.not_after.to_deprecated_string("%Y-%m-%d"sv); + default: + VERIFY_NOT_REACHED(); + } + + return {}; +} + +ErrorOr> CertificateStoreWidget::try_create() +{ + auto widget = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) CertificateStoreWidget())); + TRY(widget->initialize()); + return widget; +} + +ErrorOr CertificateStoreWidget::initialize() +{ + TRY(load_from_gml(certificate_store_gml)); + + m_root_ca_tableview = find_descendant_of_type_named("root_ca_tableview"); + m_root_ca_tableview->set_highlight_selected_rows(true); + m_root_ca_tableview->set_alternating_row_colors(false); + + m_root_ca_model = CertificateStoreModel::create(); + TRY(m_root_ca_model->load()); + m_root_ca_tableview->set_model(m_root_ca_model); + m_root_ca_tableview->set_column_width(CertificateStoreModel::Column::IssuedTo, 150); + m_root_ca_tableview->set_column_width(CertificateStoreModel::Column::IssuedBy, 150); + + return {}; +} +} diff --git a/Userland/Applications/CertificateSettings/CertificateStore.gml b/Userland/Applications/CertificateSettings/CertificateStore.gml new file mode 100644 index 0000000000..4385b34109 --- /dev/null +++ b/Userland/Applications/CertificateSettings/CertificateStore.gml @@ -0,0 +1,40 @@ +@GUI::Widget { + fill_with_background_color: true + layout: @GUI::VerticalBoxLayout { + margins: [8] + } + + @GUI::GroupBox { + title: "Trusted Root Certification Authorities" + fixed_height: 500 + fixed_width: 465 + layout: @GUI::VerticalBoxLayout { + margins: [8] + } + + @GUI::TableView { + name: "root_ca_tableview" + } + + @GUI::Widget { + layout: @GUI::HorizontalBoxLayout { + spacing: 6 + } + preferred_height: "fit" + + @GUI::Button { + name: "import_button" + text: "Import" + fixed_width: 80 + enabled: false + } + + @GUI::Button { + name: "export_button" + text: "Export" + fixed_width: 80 + enabled: false + } + } + } +} diff --git a/Userland/Applications/CertificateSettings/CertificateStore.h b/Userland/Applications/CertificateSettings/CertificateStore.h new file mode 100644 index 0000000000..ffcc3fd4f1 --- /dev/null +++ b/Userland/Applications/CertificateSettings/CertificateStore.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023, Fabian Dellwing + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace CertificateSettings { + +class CertificateStoreModel : public GUI::Model { +public: + enum Column { + IssuedTo, + IssuedBy, + Expire, + __Count + }; + + static NonnullRefPtr create(); + virtual ~CertificateStoreModel() override = default; + + virtual int row_count(GUI::ModelIndex const& = GUI::ModelIndex()) const override { return m_certificates.size(); } + virtual int column_count(GUI::ModelIndex const& = GUI::ModelIndex()) const override { return Column::__Count; } + virtual DeprecatedString column_name(int) const override; + virtual GUI::Variant data(GUI::ModelIndex const&, GUI::ModelRole) const override; + virtual ErrorOr load(); + +private: + Vector m_certificates; +}; + +class CertificateStoreWidget : public GUI::SettingsWindow::Tab { + C_OBJECT_ABSTRACT(CertStoreWidget) +public: + virtual ~CertificateStoreWidget() override = default; + static ErrorOr> try_create(); + virtual void apply_settings() override {}; + +private: + CertificateStoreWidget() = default; + ErrorOr initialize(); + + RefPtr m_root_ca_model; + RefPtr m_root_ca_tableview; +}; +} diff --git a/Userland/Applications/CertificateSettings/main.cpp b/Userland/Applications/CertificateSettings/main.cpp new file mode 100644 index 0000000000..e434f6c88c --- /dev/null +++ b/Userland/Applications/CertificateSettings/main.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2023, Fabian Dellwing + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "CertificateStore.h" + +#include +#include +#include +#include +#include +#include + +ErrorOr serenity_main(Main::Arguments args) +{ + TRY(Core::System::pledge("stdio rpath wpath cpath recvfd sendfd unix")); + + auto app = TRY(GUI::Application::try_create(args)); + + TRY(Core::System::unveil("/res", "r")); + TRY(Core::System::unveil("/etc/cacert.pem", "r")); + TRY(Core::System::unveil("/etc/timezone", "r")); + TRY(Core::System::unveil(nullptr, nullptr)); + + auto app_icon = GUI::Icon::default_icon("certificate"sv); + auto window = TRY(GUI::SettingsWindow::create("Certificates", GUI::SettingsWindow::ShowDefaultsButton::No)); + auto cert_store_widget = TRY(window->add_tab(TRY("Certificate Store"_string), "certificate"sv)); + window->set_icon(app_icon.bitmap_for_size(16)); + + window->show(); + return app->exec(); +}