179 lines
5.7 KiB
Plaintext
179 lines
5.7 KiB
Plaintext
#ifdef __APPLE__
|
|
|
|
#include "local_certificate.hpp"
|
|
|
|
#include "config.hpp"
|
|
#include "encoding.hpp"
|
|
#include "platform.hpp"
|
|
|
|
#include <cstdlib>
|
|
#include <fstream>
|
|
#include <sys/stat.h>
|
|
|
|
namespace connector {
|
|
namespace {
|
|
|
|
struct CertificatePaths {
|
|
std::filesystem::path directory;
|
|
std::filesystem::path ca_key;
|
|
std::filesystem::path ca;
|
|
std::filesystem::path server_key;
|
|
std::filesystem::path server_certificate;
|
|
std::filesystem::path identity;
|
|
std::filesystem::path request;
|
|
std::filesystem::path extensions;
|
|
};
|
|
|
|
CertificatePaths paths() {
|
|
const auto directory = config_path().parent_path() / "tls";
|
|
return {
|
|
directory,
|
|
directory / "ca-key.pem",
|
|
directory / "ca.pem",
|
|
directory / "server-key.pem",
|
|
directory / "server.pem",
|
|
directory / "identity.p12",
|
|
directory / "server.csr",
|
|
directory / "server.ext",
|
|
};
|
|
}
|
|
|
|
std::string process_error(const ProcessResult& result) {
|
|
std::string message = trim(result.output);
|
|
return message.empty() ? "OpenSSL 执行失败" : message;
|
|
}
|
|
|
|
bool run_openssl(const std::vector<std::string>& arguments, std::string& error) {
|
|
const ProcessResult result = run_process("/usr/bin/openssl", arguments, {}, 30);
|
|
if (result.timed_out) {
|
|
error = "生成本机 HTTPS 证书超时";
|
|
return false;
|
|
}
|
|
if (result.exit_code != 0) {
|
|
error = process_error(result);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool certificate_is_usable(const std::filesystem::path& certificate) {
|
|
std::error_code file_error;
|
|
if (!std::filesystem::is_regular_file(certificate, file_error)) return false;
|
|
const ProcessResult result = run_process(
|
|
"/usr/bin/openssl", {"x509", "-checkend", "2592000", "-noout", "-in", certificate.string()}, {}, 10);
|
|
return !result.timed_out && result.exit_code == 0;
|
|
}
|
|
|
|
bool identity_is_usable(const std::filesystem::path& identity) {
|
|
std::error_code error;
|
|
return std::filesystem::is_regular_file(identity, error);
|
|
}
|
|
|
|
void set_private_permissions(const std::filesystem::path& path) {
|
|
chmod(path.c_str(), 0600);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::filesystem::path local_identity_path() {
|
|
return paths().identity;
|
|
}
|
|
|
|
bool ensure_local_certificate(std::string& error) {
|
|
const CertificatePaths files = paths();
|
|
if (certificate_is_usable(files.server_certificate) && identity_is_usable(files.identity)) {
|
|
error.clear();
|
|
return true;
|
|
}
|
|
|
|
try {
|
|
std::filesystem::create_directories(files.directory);
|
|
chmod(files.directory.c_str(), 0700);
|
|
for (const auto& path : {files.ca_key, files.ca, files.server_key, files.server_certificate,
|
|
files.identity, files.request, files.extensions}) {
|
|
std::error_code ignored;
|
|
std::filesystem::remove(path, ignored);
|
|
}
|
|
} catch (const std::exception& exception) {
|
|
error = exception.what();
|
|
return false;
|
|
}
|
|
|
|
const std::string dir = files.directory.string();
|
|
if (!run_openssl({"genrsa", "-out", files.ca_key.string(), "2048"}, error)
|
|
|| !run_openssl({"req", "-x509", "-new", "-key", files.ca_key.string(), "-sha256", "-days", "3650",
|
|
"-subj", "/CN=Spine Particle Connector Local CA", "-out", files.ca.string()}, error)
|
|
|| !run_openssl({"genrsa", "-out", files.server_key.string(), "2048"}, error)
|
|
|| !run_openssl({"req", "-new", "-key", files.server_key.string(), "-subj", "/CN=127.0.0.1",
|
|
"-out", files.request.string()}, error)) {
|
|
return false;
|
|
}
|
|
|
|
{
|
|
std::ofstream extensions(files.extensions, std::ios::binary | std::ios::trunc);
|
|
if (!extensions) {
|
|
error = "无法写入证书配置";
|
|
return false;
|
|
}
|
|
extensions << "subjectAltName=IP:127.0.0.1,DNS:localhost\n"
|
|
<< "extendedKeyUsage=serverAuth\n"
|
|
<< "basicConstraints=CA:FALSE\n";
|
|
}
|
|
|
|
if (!run_openssl({"x509", "-req", "-in", files.request.string(), "-CA", files.ca.string(),
|
|
"-CAkey", files.ca_key.string(), "-CAcreateserial", "-out", files.server_certificate.string(),
|
|
"-days", "825", "-sha256", "-extfile", files.extensions.string()}, error)
|
|
|| !run_openssl({"pkcs12", "-export", "-out", files.identity.string(), "-inkey", files.server_key.string(),
|
|
"-in", files.server_certificate.string(), "-passout", "pass:spine-connector-local"}, error)) {
|
|
return false;
|
|
}
|
|
|
|
set_private_permissions(files.server_key);
|
|
set_private_permissions(files.identity);
|
|
{
|
|
std::error_code ignored;
|
|
std::filesystem::remove(files.ca_key, ignored);
|
|
}
|
|
(void)dir;
|
|
error.clear();
|
|
return true;
|
|
}
|
|
|
|
bool local_certificate_trusted() {
|
|
const CertificatePaths files = paths();
|
|
const ProcessResult result = run_process(
|
|
"/usr/bin/security",
|
|
{"verify-cert", "-c", files.server_certificate.string(), "-n", "127.0.0.1", "-p", "ssl", "-L", "-q"},
|
|
{}, 10);
|
|
return !result.timed_out && result.exit_code == 0;
|
|
}
|
|
|
|
bool trust_local_certificate(std::string& error) {
|
|
const CertificatePaths files = paths();
|
|
if (!ensure_local_certificate(error)) return false;
|
|
const char* home = std::getenv("HOME");
|
|
if (!home || !*home) {
|
|
error = "无法确定当前用户主目录";
|
|
return false;
|
|
}
|
|
const auto keychain = std::filesystem::path(home) / "Library" / "Keychains" / "login.keychain-db";
|
|
const ProcessResult result = run_process(
|
|
"/usr/bin/security",
|
|
{"add-trusted-cert", "-r", "trustRoot", "-p", "ssl", "-k", keychain.string(), files.ca.string()},
|
|
{}, 120);
|
|
if (result.timed_out || result.exit_code != 0) {
|
|
error = result.timed_out ? "等待系统证书授权超时" : process_error(result);
|
|
return false;
|
|
}
|
|
if (!local_certificate_trusted()) {
|
|
error = "证书已安装,但系统仍未信任它";
|
|
return false;
|
|
}
|
|
error.clear();
|
|
return true;
|
|
}
|
|
|
|
} // namespace connector
|
|
|
|
#endif
|