mirror of
https://github.com/SerenityOS/serenity
synced 2024-07-22 18:46:18 +00:00
![Timothy Flynn](/assets/img/avatar_default.png)
We are currently using Core::DateTime, which is meant to represent local time. However, we are doing no conversion between the parsed time in UTC and local time, so we end up comparing time stamps from different time zones. Instead, store the parsed times as UnixDateTime, which is UTC. Then we can always compare the parsed times against the current UTC time. This also lets us store parsed milliseconds.
192 lines
6.2 KiB
C++
192 lines
6.2 KiB
C++
/*
|
|
* Copyright (c) 2023, Fabian Dellwing <fabian@dellwing.net>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include "CertificateStoreWidget.h"
|
|
#include <AK/String.h>
|
|
#include <LibCore/DateTime.h>
|
|
#include <LibCrypto/ASN1/PEM.h>
|
|
#include <LibFileSystem/FileSystem.h>
|
|
#include <LibFileSystemAccessClient/Client.h>
|
|
#include <LibGUI/MessageBox.h>
|
|
#include <LibGUI/SortingProxyModel.h>
|
|
|
|
namespace CertificateSettings {
|
|
|
|
NonnullRefPtr<CertificateStoreModel> CertificateStoreModel::create()
|
|
{
|
|
return adopt_ref(*new CertificateStoreModel);
|
|
}
|
|
|
|
void CertificateStoreProxyModel::sort(int column, GUI::SortOrder sort_order)
|
|
{
|
|
SortingProxyModel::sort(column, sort_order);
|
|
m_parent_table_view->set_column_width(CertificateStoreModel::Column::IssuedTo, 150);
|
|
m_parent_table_view->set_column_width(CertificateStoreModel::Column::IssuedBy, 150);
|
|
}
|
|
|
|
ErrorOr<void> CertificateStoreModel::load()
|
|
{
|
|
m_certificates = TRY(DefaultRootCACertificates::load_certificates());
|
|
|
|
return {};
|
|
}
|
|
|
|
ErrorOr<String> CertificateStoreModel::column_name(int column) const
|
|
{
|
|
switch (column) {
|
|
case Column::IssuedTo:
|
|
return "Issued To"_string;
|
|
case Column::IssuedBy:
|
|
return "Issued By"_string;
|
|
case Column::Expire:
|
|
return "Expiration Date"_string;
|
|
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: {
|
|
auto issued_to = cert.subject.common_name();
|
|
if (issued_to.is_empty()) {
|
|
issued_to = cert.subject.organizational_unit();
|
|
}
|
|
|
|
return issued_to;
|
|
}
|
|
case Column::IssuedBy: {
|
|
auto issued_by = cert.issuer.common_name();
|
|
if (issued_by.is_empty()) {
|
|
issued_by = cert.issuer.organizational_unit();
|
|
}
|
|
|
|
return issued_by;
|
|
}
|
|
case Column::Expire:
|
|
return Core::DateTime::from_timestamp(cert.validity.not_after.seconds_since_epoch()).to_byte_string("%Y-%m-%d"sv);
|
|
default:
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
ErrorOr<size_t> CertificateStoreModel::add(Vector<Certificate> const& certificates)
|
|
{
|
|
auto size = m_certificates.size();
|
|
TRY(m_certificates.try_extend(certificates));
|
|
return m_certificates.size() - size;
|
|
}
|
|
|
|
ErrorOr<void> CertificateStoreWidget::import_pem()
|
|
{
|
|
FileSystemAccessClient::OpenFileOptions options {
|
|
.window_title = "Import"sv,
|
|
.allowed_file_types = Vector {
|
|
GUI::FileTypeFilter { "Certificate Files", { { "pem", "crt" } } },
|
|
},
|
|
};
|
|
auto fsac_result = FileSystemAccessClient::Client::the().open_file(window(), options);
|
|
if (fsac_result.is_error())
|
|
return {};
|
|
|
|
auto fsac_file = fsac_result.release_value();
|
|
auto data = TRY(fsac_file.release_stream()->read_until_eof());
|
|
auto count = TRY(m_root_ca_model->add(TRY(DefaultRootCACertificates::parse_pem_root_certificate_authorities(data))));
|
|
|
|
if (count == 0) {
|
|
return Error::from_string_view("No valid CA found to import."sv);
|
|
}
|
|
|
|
auto cert_file = TRY(Core::File::open(TRY(String::formatted("{}/.config/certs.pem", Core::StandardPaths::home_directory())), Core::File::OpenMode::Write | Core::File::OpenMode::Append));
|
|
TRY(cert_file->write_until_depleted(data.bytes()));
|
|
cert_file->close();
|
|
|
|
m_root_ca_model->invalidate();
|
|
m_root_ca_tableview->set_column_width(CertificateStoreModel::Column::IssuedTo, 150);
|
|
m_root_ca_tableview->set_column_width(CertificateStoreModel::Column::IssuedBy, 150);
|
|
|
|
GUI::MessageBox::show(window(), TRY(String::formatted("Successfully imported {} CAs.", count)), "Success"sv);
|
|
|
|
return {};
|
|
}
|
|
|
|
Certificate CertificateStoreModel::get(int index)
|
|
{
|
|
return m_certificates.at(index);
|
|
}
|
|
|
|
ErrorOr<void> CertificateStoreWidget::export_pem()
|
|
{
|
|
auto index = m_root_ca_tableview->selection().first();
|
|
auto real_index = m_root_ca_proxy_model->map_to_source(index);
|
|
auto cert = m_root_ca_model->get(real_index.row());
|
|
|
|
String filename = cert.subject.common_name();
|
|
if (filename.is_empty()) {
|
|
filename = cert.subject.organizational_unit();
|
|
}
|
|
|
|
filename = TRY(filename.replace(" "sv, "_"sv, ReplaceMode::All));
|
|
|
|
auto file = FileSystemAccessClient::Client::the().save_file(window(), filename.to_byte_string(), "pem"sv);
|
|
if (file.is_error())
|
|
return {};
|
|
|
|
auto data = TRY(Crypto::encode_pem(cert.original_asn1));
|
|
|
|
TRY(file.release_value().release_stream()->write_until_depleted(data));
|
|
|
|
return {};
|
|
}
|
|
|
|
ErrorOr<void> CertificateStoreWidget::initialize()
|
|
{
|
|
m_root_ca_tableview = find_descendant_of_type_named<GUI::TableView>("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();
|
|
m_root_ca_proxy_model = TRY(CertificateStoreProxyModel::create(*m_root_ca_model, *m_root_ca_tableview));
|
|
m_root_ca_proxy_model->set_sort_role(GUI::ModelRole::Display);
|
|
TRY(m_root_ca_model->load());
|
|
m_root_ca_tableview->set_model(m_root_ca_proxy_model);
|
|
m_root_ca_tableview->set_column_width(CertificateStoreModel::Column::IssuedTo, 150);
|
|
m_root_ca_tableview->set_column_width(CertificateStoreModel::Column::IssuedBy, 150);
|
|
|
|
m_root_ca_tableview->on_selection_change = [&]() {
|
|
m_export_ca_button->set_enabled(m_root_ca_tableview->selection().size() == 1);
|
|
};
|
|
|
|
m_import_ca_button = find_descendant_of_type_named<GUI::Button>("import_button");
|
|
m_import_ca_button->on_click = [&](auto) {
|
|
auto import_result = import_pem();
|
|
if (import_result.is_error()) {
|
|
GUI::MessageBox::show_error(window(), ByteString::formatted("{}", import_result.release_error()));
|
|
}
|
|
};
|
|
|
|
m_export_ca_button = find_descendant_of_type_named<GUI::Button>("export_button");
|
|
m_export_ca_button->on_click = [&](auto) {
|
|
auto export_result = export_pem();
|
|
if (export_result.is_error()) {
|
|
GUI::MessageBox::show_error(window(), ByteString::formatted("{}", export_result.release_error()));
|
|
}
|
|
};
|
|
|
|
return {};
|
|
}
|
|
}
|