#ifdef __APPLE__ #include "local_certificate.hpp" #include "config.hpp" #include "encoding.hpp" #include "platform.hpp" #include #include #include 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& 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