90115270d2
- cpp/functions/infra/secret_store.cpp - cpp/functions/infra/secret_store.h - cpp/functions/infra/secret_store.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
168 lines
5.3 KiB
C++
168 lines
5.3 KiB
C++
// 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 <algorithm>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#ifdef _WIN32
|
|
# define WIN32_LEAN_AND_MEAN
|
|
# include <windows.h>
|
|
# include <wincrypt.h>
|
|
# 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> encrypt(const std::string& plaintext) {
|
|
#ifdef _WIN32
|
|
DATA_BLOB in_blob { (DWORD)plaintext.size(),
|
|
(BYTE*)const_cast<char*>(plaintext.data()) };
|
|
DATA_BLOB out_blob {};
|
|
if (!CryptProtectData(&in_blob, L"fn_agents_dashboard", nullptr,
|
|
nullptr, nullptr, 0, &out_blob)) {
|
|
return {};
|
|
}
|
|
std::vector<uint8_t> result(out_blob.pbData,
|
|
out_blob.pbData + out_blob.cbData);
|
|
LocalFree(out_blob.pbData);
|
|
return result;
|
|
#else
|
|
// Linux: 1-byte magic + XOR
|
|
std::vector<uint8_t> key = linux_key();
|
|
std::vector<uint8_t> 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<uint8_t>& blob) {
|
|
if (blob.empty()) return {};
|
|
#ifdef _WIN32
|
|
DATA_BLOB in_blob { (DWORD)blob.size(),
|
|
(BYTE*)const_cast<uint8_t*>(blob.data()) };
|
|
DATA_BLOB out_blob {};
|
|
if (!CryptUnprotectData(&in_blob, nullptr, nullptr,
|
|
nullptr, nullptr, 0, &out_blob)) {
|
|
return {};
|
|
}
|
|
std::string result(reinterpret_cast<char*>(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<uint8_t> 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
|