// secret_store.cpp — implementation of fn_secret (issue 0129). // // See secret_store.h for API docs and platform notes. #include "infra/secret_store.h" #include #include #include #include #include #ifdef _WIN32 # define WIN32_LEAN_AND_MEAN # include # include # pragma comment(lib, "crypt32.lib") #endif namespace fn_secret { // --------------------------------------------------------------------------- // Base64 helpers (no external deps, RFC 4648 alphabet) // --------------------------------------------------------------------------- static const char kB64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static std::string base64_encode(const uint8_t* data, size_t len) { std::string out; out.reserve(((len + 2) / 3) * 4); for (size_t i = 0; i < len; i += 3) { uint32_t b = (uint32_t)data[i] << 16; if (i + 1 < len) b |= (uint32_t)data[i + 1] << 8; if (i + 2 < len) b |= (uint32_t)data[i + 2]; out += kB64Chars[(b >> 18) & 63]; out += kB64Chars[(b >> 12) & 63]; out += (i + 1 < len) ? kB64Chars[(b >> 6) & 63] : '='; out += (i + 2 < len) ? kB64Chars[(b) & 63] : '='; } return out; } static std::vector base64_decode(const std::string& s) { auto decode_char = [](char c) -> int { if (c >= 'A' && c <= 'Z') return c - 'A'; if (c >= 'a' && c <= 'z') return c - 'a' + 26; if (c >= '0' && c <= '9') return c - '0' + 52; if (c == '+') return 62; if (c == '/') return 63; return -1; }; std::vector out; out.reserve(s.size() * 3 / 4); for (size_t i = 0; i + 3 < s.size(); i += 4) { int a = decode_char(s[i]); int b = decode_char(s[i + 1]); int c = decode_char(s[i + 2]); int d = decode_char(s[i + 3]); if (a < 0 || b < 0) break; out.push_back((uint8_t)((a << 2) | (b >> 4))); if (c >= 0) out.push_back((uint8_t)((b << 4) | (c >> 2))); if (d >= 0) out.push_back((uint8_t)((c << 2) | d)); } return out; } // --------------------------------------------------------------------------- // Linux fallback: XOR with a stable per-user key // --------------------------------------------------------------------------- #ifndef _WIN32 static std::vector linux_key() { // Key = first 32 bytes of SHA-256-like mixing of LOGNAME + HOSTNAME. // Good enough to prevent casual plaintext inspection; NOT crypto-secure. const char* user = getenv("LOGNAME"); const char* host = getenv("HOSTNAME"); if (!user) user = "user"; if (!host) host = "localhost"; std::string seed = std::string(user) + "@" + host + ":fn_agents_dashboard_key_v1"; std::vector key(32, 0); for (size_t i = 0; i < seed.size(); i++) { key[i % 32] ^= (uint8_t)seed[i]; key[(i + 7) % 32] += (uint8_t)(seed[i] * 31 + i); key[(i + 13) % 32] ^= (uint8_t)(seed[i] + i * 7); } return key; } #endif // --------------------------------------------------------------------------- // Public API // --------------------------------------------------------------------------- bool is_strong() { #ifdef _WIN32 return true; #else return false; #endif } std::vector encrypt(const std::string& plaintext) { #ifdef _WIN32 DATA_BLOB in_blob { (DWORD)plaintext.size(), (BYTE*)const_cast(plaintext.data()) }; DATA_BLOB out_blob {}; if (!CryptProtectData(&in_blob, L"fn_agents_dashboard", nullptr, nullptr, nullptr, 0, &out_blob)) { return {}; } std::vector result(out_blob.pbData, out_blob.pbData + out_blob.cbData); LocalFree(out_blob.pbData); return result; #else // Linux: 1-byte magic + XOR std::vector key = linux_key(); std::vector out; out.reserve(1 + plaintext.size()); out.push_back(0xAF); // magic marker for (size_t i = 0; i < plaintext.size(); i++) { out.push_back((uint8_t)plaintext[i] ^ key[i % key.size()]); } return out; #endif } std::string decrypt(const std::vector& blob) { if (blob.empty()) return {}; #ifdef _WIN32 DATA_BLOB in_blob { (DWORD)blob.size(), (BYTE*)const_cast(blob.data()) }; DATA_BLOB out_blob {}; if (!CryptUnprotectData(&in_blob, nullptr, nullptr, nullptr, nullptr, 0, &out_blob)) { return {}; } std::string result(reinterpret_cast(out_blob.pbData), out_blob.cbData); LocalFree(out_blob.pbData); return result; #else // Linux: check magic, XOR decode if (blob[0] != 0xAF) return {}; std::vector key = linux_key(); std::string out; out.reserve(blob.size() - 1); for (size_t i = 1; i < blob.size(); i++) { out += (char)(blob[i] ^ key[(i - 1) % key.size()]); } return out; #endif } std::string encrypt_b64(const std::string& plaintext) { auto blob = encrypt(plaintext); if (blob.empty()) return {}; return base64_encode(blob.data(), blob.size()); } std::string decrypt_b64(const std::string& b64) { auto blob = base64_decode(b64); return decrypt(blob); } } // namespace fn_secret