diff --git a/src/Security/TlsKeyUtil.h b/src/Security/TlsKeyUtil.h index 18c0ae9..46001f3 100644 --- a/src/Security/TlsKeyUtil.h +++ b/src/Security/TlsKeyUtil.h @@ -21,7 +21,9 @@ namespace snoop auto b = s.find_first_not_of(" \t\r\n"); auto e = s.find_last_not_of(" \t\r\n"); if (b == std::string::npos) + { return ""; + } return s.substr(b, e - b + 1); } @@ -31,13 +33,19 @@ namespace snoop std::string out; FILE *pipe = popen((cmd + " 2>&1").c_str(), "r"); if (!pipe) + { throw std::runtime_error("popen failed: " + cmd); + } while (fgets(buf.data(), (int)buf.size(), pipe) != nullptr) + { out.append(buf.data()); + } int rc = pclose(pipe); int exitCode = WIFEXITED(rc) ? WEXITSTATUS(rc) : rc; if (exitCode != 0) + { spdlog::warn("Command '{}' exited with code {}", cmd, exitCode); + } return out; } @@ -46,12 +54,16 @@ namespace snoop { std::string id = Trim(Exec("keyctl search @s user iot-client-key | tail -n1")); if (id.empty()) + { throw std::runtime_error("iot-client-key not found in keyring"); + } // Create a secure temp file char tmpl[] = "/run/iot/iot-keyXXXXXX"; int fd = mkstemp(tmpl); if (fd < 0) + { throw std::runtime_error("mkstemp failed for client key"); + } close(fd); std::filesystem::path p(tmpl); @@ -80,7 +92,9 @@ namespace snoop name.push_back('\0'); fd = mkstemp(name.data()); if (fd < 0) + { throw std::runtime_error("mkstemp failed"); + } fchmod(fd, S_IRUSR | S_IWUSR); path = name.data(); } @@ -92,7 +106,9 @@ namespace snoop { ssize_t w = ::write(fd, p + off, n - off); if (w <= 0) + { throw std::runtime_error("write failed"); + } off += (size_t)w; } fsync(fd); @@ -100,7 +116,9 @@ namespace snoop ~TempFile() { if (fd >= 0) + { ::close(fd); + } std::error_code ec; std::filesystem::remove(path, ec); } @@ -111,12 +129,16 @@ namespace snoop // 1) get key id std::string id = Trim(Exec("keyctl search @s user iot-client-key | tail -n1")); if (id.empty()) + { throw std::runtime_error("iot-client-key not found in keyring"); - + } + // 2) capture payload (no redirection to file) std::string bytes = Exec("keyctl pipe " + id); if (bytes.empty()) + { throw std::runtime_error("keyctl pipe returned empty payload"); + } return std::vector(bytes.begin(), bytes.end()); } diff --git a/src/Services/AudioWriterService.h b/src/Services/AudioWriterService.h index 670cacb..a79c4be 100644 --- a/src/Services/AudioWriterService.h +++ b/src/Services/AudioWriterService.h @@ -207,7 +207,9 @@ namespace snoop // Setup CA/CRT (from enrollment) std::filesystem::path ca = "/etc/iot/keys/issuing_ca.pem"; if (!std::filesystem::exists(ca)) + { ca = "/etc/iot/keys/ca_chain.pem"; + } const std::filesystem::path crt = "/etc/iot/keys/device.crt.pem"; if (!needReinit) @@ -337,7 +339,9 @@ namespace snoop // certs from enrollment std::filesystem::path ca = "/etc/iot/keys/issuing_ca.pem"; if (!std::filesystem::exists(ca)) + { ca = "/etc/iot/keys/ca_chain.pem"; + } const std::filesystem::path crt = "/etc/iot/keys/device.crt.pem"; while (!m_isIntermission) diff --git a/src/Services/EnrollmentService.h b/src/Services/EnrollmentService.h index 5e48bad..ee5b0d3 100644 --- a/src/Services/EnrollmentService.h +++ b/src/Services/EnrollmentService.h @@ -36,7 +36,9 @@ namespace snoop std::string out; FILE *pipe = popen((cmd + " 2>&1").c_str(), "r"); if (!pipe) + { throw std::runtime_error("popen failed: " + cmd); + } while (fgets(buf.data(), (int)buf.size(), pipe) != nullptr) { out.append(buf.data()); @@ -44,17 +46,23 @@ namespace snoop auto rc = pclose(pipe); int exitCode = WIFEXITED(rc) ? WEXITSTATUS(rc) : rc; if (exitCode != 0) + { spdlog::warn("Command '{}' exited with code {}", cmd, exitCode); + } return out; } static void WriteFile(const std::filesystem::path &p, const std::string &data, bool createParents = true) { if (createParents) + { std::filesystem::create_directories(p.parent_path()); + } std::ofstream f(p, std::ios::binary); if (!f) + { throw std::runtime_error("Cannot open file for write: " + p.string()); + } f.write(data.data(), (std::streamsize)data.size()); } @@ -62,7 +70,9 @@ namespace snoop { std::ifstream f(p, std::ios::binary); if (!f) + { throw std::runtime_error("Cannot open file for read: " + p.string()); + } std::ostringstream ss; ss << f.rdbuf(); return ss.str(); @@ -97,7 +107,9 @@ namespace snoop { auto serial = Trim(m[1].str()); if (!serial.empty()) + { return serial; + } } } spdlog::warn("CPU Serial not found, using fallback"); @@ -221,7 +233,9 @@ namespace snoop std::filesystem::path(csrName)); if (!std::filesystem::exists(keyName) || !std::filesystem::exists(csrName)) + { throw std::runtime_error("CSR or key was not generated"); + } std::filesystem::create_directories(keystoreDir); @@ -232,8 +246,9 @@ namespace snoop kek); if (!std::filesystem::exists(encKeyPath)) + { throw std::runtime_error("Encrypted key not created: " + encKeyPath.string()); - + } // shred plaintext key const std::string shredCmd = "shred -u " + keyName; spdlog::info("Shredding plaintext private key with '{}'", shredCmd); @@ -259,8 +274,9 @@ namespace snoop std::filesystem::path(csrName)); if (!std::filesystem::exists(csrName)) + { throw std::runtime_error("CSR was not generated from existing key"); - + } // TempFile destructor will securely delete the decrypted key file } @@ -320,10 +336,15 @@ namespace snoop spdlog::info("POST {} (multipart)", path); auto res = cli.Post(path.c_str(), items); if (!res) + { throw std::runtime_error("Enroll request failed (no response)"); + } spdlog::info("Enroll response status: {}", res->status); if (res->status != 200 && res->status != 201) + { + spdlog::info("Enroll error: " + res->body); throw std::runtime_error("Enroll failed: HTTP " + std::to_string(res->status)); + } // 6) Expect JSON: { "certificate": "...", "issuing_ca":"...", "ca_chain":"..." } nlohmann::json j; @@ -336,12 +357,16 @@ namespace snoop throw std::runtime_error(std::string("Enroll: invalid JSON: ") + e.what()); } if (!j.contains("certificate") || !j["certificate"].is_string()) + { throw std::runtime_error("Enroll response missing or invalid 'certificate'"); + } WriteFile(certPath, j["certificate"].get()); if (j.contains("issuing_ca")) { if (!j["issuing_ca"].is_string()) + { throw std::runtime_error("'issuing_ca' must be a string PEM"); + } WriteFile(caPath, j["issuing_ca"].get()); } @@ -455,15 +480,21 @@ namespace snoop auto res = cli->Post(path.c_str(), items); if (!res) + { throw std::runtime_error("Renew request failed (no response)"); + } spdlog::info("Renew response status: {}", res->status); if (res->status != 200) + { throw std::runtime_error("Renew failed: HTTP " + std::to_string(res->status)); + } nlohmann::json j = nlohmann::json::parse(res->body); if (!j.contains("certificate") || !j["certificate"].is_string()) + { throw std::runtime_error("Renew response missing or invalid 'certificate'"); + } WriteFile(certPath, j["certificate"].get()); if (j.contains("issuing_ca") && j["issuing_ca"].is_string()) @@ -475,11 +506,17 @@ namespace snoop { std::string chainPem; if (j["ca_chain"].is_string()) + { chainPem = j["ca_chain"].get(); + } else if (j["ca_chain"].is_array()) + { chainPem = JoinPemArray(j["ca_chain"]); + } else + { throw std::runtime_error("'ca_chain' must be string or array"); + } WriteFile(chainPath, chainPem); } @@ -525,7 +562,9 @@ namespace snoop { std::ifstream f(path, std::ios::binary); if (!f) + { throw std::runtime_error("Failed to open file: " + path); + } std::ostringstream ss; ss << f.rdbuf(); return ss.str(); diff --git a/src/Services/WhipClient.h b/src/Services/WhipClient.h index 5c287c1..c0900e1 100644 --- a/src/Services/WhipClient.h +++ b/src/Services/WhipClient.h @@ -49,7 +49,9 @@ namespace snoop { std::lock_guard lk(m_mtx); if (m_started) + { return; + } // Parse & normalize the provided URL now (extract token, add trailing /whip) m_endpoint = ParseWhipUrl(m_p.whipUrl); @@ -97,11 +99,12 @@ namespace snoop spdlog::warn("WHIP PATCH candidate failed: {}", e.what()); } }); - // 2) When ICE gathering completes, send end-of-candidates - m_pc->onGatheringStateChange([this](rtc::PeerConnection::GatheringState s) + // 2) When ICE gathering completes, send end-of-candidates + m_pc->onGatheringStateChange([this](rtc::PeerConnection::GatheringState s) { spdlog::info("WHIP gathering state: {}", (int)s); - if (s == rtc::PeerConnection::GatheringState::Complete) { + if (s == rtc::PeerConnection::GatheringState::Complete) + { PatchSdpFrag("a=end-of-candidates"); } }); @@ -136,7 +139,9 @@ namespace snoop { std::lock_guard lk(m_mtx); if (!m_started) + { return; + } m_trackOpen = false; m_track.reset(); @@ -150,7 +155,9 @@ namespace snoop { std::lock_guard lk(m_mtx); if (!m_track || !m_started || !m_trackOpen.load()) + { return; + } std::array rtp{}; rtp[0] = 0x80; // V=2 @@ -233,7 +240,9 @@ namespace snoop { size_t amp = query.find('&', pos); if (amp == std::string::npos) + { amp = query.size(); + } auto kv = query.substr(pos, amp - pos); auto eq = kv.find('='); if (eq != std::string::npos) @@ -252,9 +261,13 @@ namespace snoop // MediaMTX wants: /whip//whip // Your incoming URLs already start with /whip/... — we just ensure they end with /whip. if (rawPath.empty()) + { rawPath = "/"; + } if (rawPath.back() == '/') + { rawPath.pop_back(); + } if (rawPath.rfind("/whip", std::string::npos) != rawPath.size() - 5) { rawPath += "/whip"; @@ -266,7 +279,9 @@ namespace snoop static std::tuple ExtractAnswerAndLocation(const httplib::Result &r) { if (!r) + { throw std::runtime_error("No HTTP result"); + } if (r->status != 201 && r->status != 200) { throw std::runtime_error("Unexpected WHIP status: " + std::to_string(r->status)); @@ -274,7 +289,9 @@ namespace snoop std::string answer = r->body; std::string resourceUrl; if (r->has_header("Location")) + { resourceUrl = r->get_header_value("Location"); + } return {answer, resourceUrl}; } @@ -294,7 +311,9 @@ namespace snoop auto res = cli->Post(m_endpoint.path.c_str(), hs, sdpOffer, "application/sdp"); if (!res) + { throw std::runtime_error("No HTTP result (network?)"); + } const auto ctype = res->get_header_value("Content-Type"); const bool has_loc = res->has_header("Location"); @@ -313,7 +332,9 @@ namespace snoop std::string answer = res->body; std::string resourceUrl; if (res->has_header("Location")) + { resourceUrl = res->get_header_value("Location"); + } if (answer.find("a=ice-ufrag:") == std::string::npos) { @@ -334,7 +355,9 @@ namespace snoop // If MediaMTX returns an absolute Location for the resource, we parse it; // otherwise we reuse the same host/port and use Location as path. if (!m_resourceUrl) + { return; + } ParsedUrl target; try @@ -393,7 +416,9 @@ namespace snoop void PatchSdpFrag(const std::string &sdpfrag) { if (!m_resourceUrl) + { return; + } // Reuse the same host/port and just set path = Location ParsedUrl target = m_endpoint; @@ -411,7 +436,9 @@ namespace snoop // MUST include CRLF at end of body std::string body = sdpfrag; if (body.empty() || body.back() != '\n') + { body += "\r\n"; + } auto res = cli->Patch(target.path.c_str(), hs, body, "application/trickle-ice-sdpfrag"); if (!res || !(res->status == 200 || res->status == 201 || res->status == 204))