Skip to content
API Documentation
BastionGuard™
ca_installer.hpp
Go to the documentation of this file.
1/*
2 * BastionGuard™
3 * Copyright (C) 2025–2026 Calogero Scarnà
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 *
17 * BastionGuard™ is a trademark of Calogero Scarnà.
18 * The BastionGuard™ name and branding are not licensed under the GPL.
19 */
20
21#pragma once
22
23#include <string>
24#include <vector>
25#include <filesystem>
26#include <iostream>
27#include <fstream>
28#include <cstdlib>
29#include <pwd.h>
30#include <unistd.h>
31#include <glob.h>
32
33#include "../DistroUtils.hpp"
34#include "../SafeProcess.hpp"
35
36#include <glib/gi18n.h>
37
38namespace fs = std::filesystem;
39
40namespace CaInstaller {
41
42static constexpr const char* CA_NAME = "BastionGuard-CA";
43
44static constexpr const char* HELPER =
45 "/usr/share/BastionGuard/data/scripts/install-ca-system.sh";
46
47static inline fs::path ca_src_path() {
48 const char* h = std::getenv("HOME");
49 return fs::path(h ? h : "/tmp")
50 / ".local/share/BastionGuard/certs/intercept-ca.crt.pem";
51}
52
53static inline fs::path stamp_path() {
54 const char* h = std::getenv("HOME");
55 return fs::path(h ? h : "/tmp")
56 / ".local/share/BastionGuard/ca-installed.stamp";
57}
58
59static inline std::string ca_fingerprint(const fs::path& ca_path) {
60 const auto result = bg_process::run({"openssl", "x509", "-in", ca_path.string(),
61 "-noout", "-fingerprint", "-sha1"});
62 if (!result.ok()) return {};
63 const auto eq = result.output.find('=');
64 if (eq == std::string::npos) return {};
65 std::string fingerprint = result.output.substr(eq + 1);
66 while (!fingerprint.empty() && (fingerprint.back() == '\n' || fingerprint.back() == '\r'))
67 fingerprint.pop_back();
68 return fingerprint;
69}
70
71static inline bool stamp_is_valid() {
72 if (!fs::exists(stamp_path())) return false;
73 std::ifstream f(stamp_path());
74 std::string stamped_fp;
75 std::getline(f, stamped_fp);
76 const std::string current_fp = ca_fingerprint(ca_src_path());
77 return !current_fp.empty() && (stamped_fp == current_fp);
78}
79
80static inline void write_stamp() {
81 try {
82 fs::create_directories(stamp_path().parent_path());
83 std::ofstream f(stamp_path());
84 f << ca_fingerprint(ca_src_path()) << "\n";
85 } catch (...) {}
86}
87
88static inline bool run(const std::vector<std::string>& args) {
89 return bg_process::run(args).ok();
90}
91
92static inline std::string current_home() {
93 const char* h = std::getenv("HOME");
94 if (h && *h) return h;
95 const passwd* pw = getpwuid(getuid());
96 return pw ? pw->pw_dir : "";
97}
98
99static inline std::vector<std::string> glob_expand(const std::string& pat) {
100 std::vector<std::string> out;
101 glob_t g{};
102 if (glob(pat.c_str(), GLOB_NOSORT | GLOB_TILDE, nullptr, &g) == 0)
103 for (size_t i = 0; i < g.gl_pathc; ++i)
104 out.emplace_back(g.gl_pathv[i]);
105 globfree(&g);
106 return out;
107}
108
109static inline bool is_system_trusted() {
110 const std::string ca = ca_src_path().string();
111 if (!fs::exists(ca)) return false;
112 for (const auto& bundle : {
113 std::string("/etc/ssl/certs/ca-certificates.crt"),
114 std::string("/etc/pki/tls/certs/ca-bundle.crt"),
115 std::string("/etc/ssl/ca-bundle.pem"),
116 std::string("/etc/pki/tls/cacert.pem"),
117 std::string("/usr/share/ca-certificates/ca-bundle.crt"),
118 std::string("/etc/ssl/certs/ca-bundle.crt"),
119 })
120 {
121 if (fs::exists(bundle) &&
122 run({"openssl", "verify", "-CAfile", bundle, ca}))
123 return true;
124 }
125 return false;
126}
127
128static inline void nss_add(const std::string& nssdb) {
129 if (!fs::exists(nssdb)) return;
130 const std::string ca = ca_src_path().string();
131 if (!fs::exists(ca)) return;
132 const std::string prefix = fs::exists(nssdb + "/cert9.db") ? "sql:" : "dbm:";
133 run({"certutil", "-A", "-d", prefix + nssdb,
134 "-n", CA_NAME, "-t", "CT,,", "-i", ca});
135}
136
137static inline void install_nss_user(const std::string& home) {
138 if (home.empty() || !fs::exists(ca_src_path())) return;
139
140 for (const auto& pat : std::vector<std::string>{
141 home + "/.mozilla/firefox/*.default",
142 home + "/.mozilla/firefox/*.default-release",
143 home + "/.mozilla/firefox/*.default-esr",
144 home + "/.var/app/org.mozilla.firefox/.mozilla/firefox/*.default",
145 home + "/.var/app/org.mozilla.firefox/.mozilla/firefox/*.default-release",
146 home + "/.pki/nssdb",
147 home + "/snap/chromium/current/.pki/nssdb",
148 })
149 {
150 for (const auto& match : glob_expand(pat))
151 nss_add(match);
152 }
153
154 std::cerr << _("[ca-installer] NSS utente aggiornato\n");
155}
156
157static inline bool install_system_via_pkexec() {
159 std::cerr << _("[ca-installer] CA di sistema disabilitata su Fedora/RHEL/Alma/Rocky\n");
160 return false;
161 }
162 if (!fs::exists(HELPER)) {
163 std::cerr << _("[ca-installer] ❌ helper non trovato: ") << HELPER << "\n";
164 return false;
165 }
166
167 std::cerr << _("[ca-installer] 🔐 Richiesta autorizzazione installazione CA...\n");
168 if (!run({"pkexec", HELPER, ca_src_path().string()})) {
169 std::cerr << _("[ca-installer] ❌ pkexec fallito o rifiutato dall'utente\n");
170 return false;
171 }
172
173 write_stamp();
174
175 std::cerr << _("[ca-installer] ✅ CA installata nel sistema via pkexec\n");
176 return true;
177}
178
179static inline bool ensure_trusted() {
181 std::cerr << _("[ca-installer] CEF/CA disabilitati su Fedora/RHEL/Alma/Rocky\n");
182 return false;
183 }
184 const fs::path ca_path = ca_src_path();
185
186 if (!fs::exists(ca_path)) {
187 std::cerr << _("[ca-installer] ❌ CA intercept non trovata: ") << ca_path << "\n";
188 std::cerr << _("[ca-installer] ℹ️ Verrà generata al primo avvio del proxy TLS\n");
189 return false;
190 }
191
192 const std::string home = current_home();
194 std::cerr << _("[ca-installer] ✅ CA già trusted (sentinel valido)\n");
195 install_nss_user(home);
196 return true;
197 }
198
199 std::cerr << _("[ca-installer] 🔄 CA nuova o non ancora trusted — installazione...\n");
201 std::cerr << _("[ca-installer] ⚠️ Fallback sudo...\n");
202 const bool sudo_ok =
203 run({"sudo", HELPER, ca_path.string()});
204 if (!sudo_ok) {
205 std::cerr << _("[ca-installer] ❌ Impossibile installare CA nel sistema\n");
206 install_nss_user(home);
207 return false;
208 }
209 write_stamp();
210 }
211
212 install_nss_user(home);
213 return true;
214}
215
216} // namespace CaInstaller
#define _(s)
static void write_stamp()
static constexpr const char * HELPER
static bool is_system_trusted()
static fs::path stamp_path()
static fs::path ca_src_path()
static constexpr const char * CA_NAME
static bool install_system_via_pkexec()
static std::vector< std::string > glob_expand(const std::string &pat)
static std::string current_home()
static bool ensure_trusted()
static void nss_add(const std::string &nssdb)
static bool run(const std::vector< std::string > &args)
static void install_nss_user(const std::string &home)
static bool stamp_is_valid()
static std::string ca_fingerprint(const fs::path &ca_path)
Result run(const std::vector< std::string > &args, bool merge_stderr=false, std::size_t limit=8U *1024U *1024U, const std::map< std::string, std::string > &environment={}, const std::function< bool(const std::string &)> &on_line={})