feat: v1.3-konforme API, HTTP-Fixes, Base64, Example erweitert; Makefile & README Setup

This commit is contained in:
2025-08-28 10:32:16 +02:00
parent 29818a5708
commit 23f98c22f5
8 changed files with 209 additions and 95 deletions

View File

@ -4,13 +4,13 @@
#include <cstring>
// Crypto++ includes
#include "cryptlib.h"
#include "osrng.h" // AutoSeededRandomPool
#include "aes.h" // AES encryption
#include "gcm.h" // GCM mode
#include "pwdbased.h" // PBKDF2
#include "sha.h" // SHA256
#include "zlib.h" // Zlib compression
#include <cryptopp/cryptlib.h>
#include <cryptopp/osrng.h> // AutoSeededRandomPool
#include <cryptopp/aes.h> // AES encryption
#include <cryptopp/gcm.h> // GCM mode
#include <cryptopp/pwdbased.h> // PBKDF2
#include <cryptopp/sha.h> // SHA256
#include <cryptopp/zlib.h> // Zlib compression
using namespace CryptoPP;

View File

@ -6,6 +6,14 @@
#include <windows.h>
#include <winhttp.h>
#pragma comment(lib, "winhttp.lib")
static std::wstring utf8_to_wide(const std::string& s) {
if (s.empty()) return std::wstring();
int needed = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), (int)s.size(), nullptr, 0);
std::wstring ws(needed, L'\0');
MultiByteToWideChar(CP_UTF8, 0, s.c_str(), (int)s.size(), &ws[0], needed);
return ws;
}
#elif LINUX
#include <curl/curl.h>
#endif
@ -36,7 +44,8 @@ bool HttpClient::get(const std::string& url, std::string& response) {
urlComp.dwExtraInfoLength = (DWORD)-1;
// Parse the URL
if (!WinHttpCrackUrl((LPCWSTR)url.c_str(), 0, 0, &urlComp)) {
std::wstring wurl = utf8_to_wide(url);
if (!WinHttpCrackUrl(wurl.c_str(), 0, 0, &urlComp)) {
return false;
}
@ -59,13 +68,25 @@ bool HttpClient::get(const std::string& url, std::string& response) {
return false;
}
// Build object name = path + extra info (query)
std::wstring pathPart = (urlComp.lpszUrlPath && urlComp.dwUrlPathLength > 0)
? std::wstring(urlComp.lpszUrlPath, urlComp.dwUrlPathLength)
: std::wstring(L"/");
std::wstring extraPart = (urlComp.lpszExtraInfo && urlComp.dwExtraInfoLength > 0)
? std::wstring(urlComp.lpszExtraInfo, urlComp.dwExtraInfoLength)
: std::wstring();
std::wstring objectName = pathPart + extraPart;
// Create an HTTP request handle
HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET",
urlComp.lpszUrlPath,
objectName.c_str(),
NULL, WINHTTP_NO_REFERER,
WINHTTP_DEFAULT_ACCEPT_TYPES,
(urlComp.nScheme == INTERNET_SCHEME_HTTPS) ?
WINHTTP_FLAG_SECURE : 0);
// Set headers per API requirement
LPCWSTR headers = L"X-Requested-With: JSONHttpRequest\r\nAccept: application/json";
WinHttpAddRequestHeaders(hRequest, headers, -1L, WINHTTP_ADDREQ_FLAG_ADD);
if (!hRequest) {
WinHttpCloseHandle(hConnect);
@ -156,7 +177,8 @@ bool HttpClient::post(const std::string& url, const std::string& data, std::stri
urlComp.dwExtraInfoLength = (DWORD)-1;
// Parse the URL
if (!WinHttpCrackUrl((LPCWSTR)url.c_str(), 0, 0, &urlComp)) {
std::wstring wurl = utf8_to_wide(url);
if (!WinHttpCrackUrl(wurl.c_str(), 0, 0, &urlComp)) {
return false;
}
@ -179,9 +201,10 @@ bool HttpClient::post(const std::string& url, const std::string& data, std::stri
return false;
}
// Create an HTTP request handle
// Create an HTTP request handle (POST per API 1.3)
LPCWSTR postPath = (urlComp.lpszUrlPath && urlComp.dwUrlPathLength > 0) ? urlComp.lpszUrlPath : L"/";
HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"POST",
urlComp.lpszUrlPath,
postPath,
NULL, WINHTTP_NO_REFERER,
WINHTTP_DEFAULT_ACCEPT_TYPES,
(urlComp.nScheme == INTERNET_SCHEME_HTTPS) ?
@ -208,6 +231,7 @@ bool HttpClient::post(const std::string& url, const std::string& data, std::stri
}
// Send a request
// Send UTF-8 bytes as body (raw)
if (!WinHttpSendRequest(hRequest,
WINHTTP_NO_ADDITIONAL_HEADERS, 0,
(LPVOID)data.c_str(), (DWORD)data.length(),
@ -290,7 +314,8 @@ bool HttpClient::delete_req(const std::string& url, const std::string& data, std
urlComp.dwExtraInfoLength = (DWORD)-1;
// Parse the URL
if (!WinHttpCrackUrl((LPCWSTR)url.c_str(), 0, 0, &urlComp)) {
std::wstring wurlDel = utf8_to_wide(url);
if (!WinHttpCrackUrl(wurlDel.c_str(), 0, 0, &urlComp)) {
return false;
}
@ -314,8 +339,9 @@ bool HttpClient::delete_req(const std::string& url, const std::string& data, std
}
// Create an HTTP request handle
LPCWSTR delPath = (urlComp.lpszUrlPath && urlComp.dwUrlPathLength > 0) ? urlComp.lpszUrlPath : L"/";
HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"POST",
urlComp.lpszUrlPath,
delPath,
NULL, WINHTTP_NO_REFERER,
WINHTTP_DEFAULT_ACCEPT_TYPES,
(urlComp.nScheme == INTERNET_SCHEME_HTTPS) ?
@ -341,9 +367,9 @@ bool HttpClient::delete_req(const std::string& url, const std::string& data, std
return false;
}
// Send a request with DELETE method
// Send a request body with DELETE method
if (!WinHttpSendRequest(hRequest,
L"DELETE", 6,
WINHTTP_NO_ADDITIONAL_HEADERS, 0,
(LPVOID)data.c_str(), (DWORD)data.length(),
(DWORD)data.length(), 0)) {
WinHttpCloseHandle(hRequest);

View File

@ -1,6 +1,28 @@
#include "json_parser.h"
#include <iostream>
#include <chrono>
#include <cryptopp/base64.h>
#include <cryptopp/filters.h>
#include <cryptopp/queue.h>
// Helper: Base64 encode without line breaks
static std::string encode_base64(const std::vector<unsigned char>& data) {
std::string result;
CryptoPP::StringSource ss(
data.data(), data.size(), true,
new CryptoPP::Base64Encoder(new CryptoPP::StringSink(result), false /* insertLineBreaks */)
);
return result;
}
// Helper: Base64 decode
static std::vector<unsigned char> decode_base64(const std::string& b64) {
std::string decoded;
CryptoPP::StringSource ss(
b64, true, new CryptoPP::Base64Decoder(new CryptoPP::StringSink(decoded))
);
return std::vector<unsigned char>(decoded.begin(), decoded.end());
}
json JsonParser::create_paste_json(const std::vector<unsigned char>& cipher_text,
const std::vector<unsigned char>& auth_tag,
@ -11,17 +33,16 @@ json JsonParser::create_paste_json(const std::vector<unsigned char>& cipher_text
bool burn_after_reading,
bool open_discussion) {
// Convert binary data to base64 strings (stub implementation)
std::string cipher_text_b64(cipher_text.begin(), cipher_text.end());
std::string auth_tag_b64(auth_tag.begin(), auth_tag.end());
std::string iv_b64(iv.begin(), iv.end());
std::string salt_b64(salt.begin(), salt.end());
// Convert binary data to base64 strings
std::string cipher_text_b64 = encode_base64(cipher_text);
std::string iv_b64 = encode_base64(iv);
std::string salt_b64 = encode_base64(salt);
// Get current timestamp
auto now = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
// Create the metadata array
// Create the metadata array according to PrivateBin v2
json metadata = {
{
iv_b64, // base64(cipher_iv)
@ -33,21 +54,19 @@ json JsonParser::create_paste_json(const std::vector<unsigned char>& cipher_text
"gcm", // mode
"zlib" // compression
},
format, // format
format, // formatter key
burn_after_reading ? 1 : 0, // burn after reading
open_discussion ? 1 : 0 // open discussion
};
// Create the main JSON structure
// JSON close to v1.3 JSON-LD: meta.expire used by server
json paste_json = {
{"v", 2}, // version
{"adata", metadata}, // metadata
{"ct", cipher_text_b64}, // cipher text
{"v", 2}, // version
{"adata", metadata}, // metadata
{"ct", cipher_text_b64}, // ciphertext (base64)
{"meta", {
{"expire", expiration},
{"created", now},
{"time_to_live", 300}, // This would be calculated based on expiration
{"icon", "data:image/png;base64,..."} // Placeholder
{"expire", expiration}
}}
};
@ -62,29 +81,32 @@ bool JsonParser::parse_paste_json(const json& json_data,
std::string& expiration) {
try {
// Extract cipher text
std::string ct = json_data["ct"];
cipher_text = std::vector<unsigned char>(ct.begin(), ct.end());
// Extract and decode cipher text
std::string ct_b64 = json_data.at("ct");
cipher_text = decode_base64(ct_b64);
// Extract metadata
json adata = json_data["adata"];
// Optional explicit tag field
if (json_data.contains("tag")) {
std::string tag_b64 = json_data.at("tag");
auth_tag = decode_base64(tag_b64);
} else {
auth_tag.clear();
}
// Extract metadata and decode IV & salt
json adata = json_data.at("adata");
if (adata.size() >= 1) {
json first_element = adata[0];
if (first_element.is_array() && first_element.size() >= 2) {
// Extract IV and salt (from base64 in real implementation)
std::string iv_str = first_element[0];
std::string salt_str = first_element[1];
iv = std::vector<unsigned char>(iv_str.begin(), iv_str.end());
salt = std::vector<unsigned char>(salt_str.begin(), salt_str.end());
std::string iv_b64 = first_element[0];
std::string salt_b64 = first_element[1];
iv = decode_base64(iv_b64);
salt = decode_base64(salt_b64);
}
}
// Extract expiration
expiration = json_data["meta"]["expire"];
// Auth tag would be extracted from the ciphertext in a real implementation
auth_tag.resize(16, 0);
expiration = json_data.at("meta").at("expire");
return true;
} catch (...) {
@ -101,17 +123,13 @@ bool JsonParser::parse_response(const std::string& response,
try {
json json_response = json::parse(response);
status = json_response["status"];
status = json_response.value("status", 1);
if (status == 0) {
// Success response
paste_id = json_response["id"];
url = json_response["url"];
delete_token = json_response["deletetoken"];
paste_id = json_response.value("id", "");
url = json_response.value("url", "");
delete_token = json_response.value("deletetoken", "");
} else {
// Error response
message = json_response["message"];
message = json_response.value("message", "");
}
return true;

View File

@ -7,6 +7,7 @@
#include <vector>
#include <cstring>
#include <cstdlib>
#include <iostream>
#define PRIVATEBIN_API_VERSION "1.3"
@ -54,6 +55,7 @@ int create_paste(const char* server_url, const char* content,
std::vector<unsigned char> plaintext(content, content + strlen(content));
// Compress the plaintext
std::cerr << "[privatebinapi] compress..." << std::endl;
std::vector<unsigned char> compressed_data = Crypto::compress(plaintext);
// Generate random salt and IV
@ -61,10 +63,12 @@ int create_paste(const char* server_url, const char* content,
std::vector<unsigned char> iv = Crypto::generate_key(16);
// Derive key using PBKDF2
std::cerr << "[privatebinapi] pbkdf2..." << std::endl;
std::vector<unsigned char> derived_key = Crypto::pbkdf2_hmac_sha256(
paste_passphrase, salt, 100000, 32);
// Encrypt the data
std::cerr << "[privatebinapi] encrypt..." << std::endl;
std::vector<unsigned char> auth_tag;
std::vector<unsigned char> cipher_text = Crypto::encrypt(
compressed_data, derived_key, iv, auth_tag);
@ -91,8 +95,12 @@ int create_paste(const char* server_url, const char* content,
int status;
std::string message, paste_id, url, del_token;
if (!JsonParser::parse_response(response, status, message, paste_id, url, del_token)) {
std::cerr << "[privatebinapi] raw response (unparsed): " << response << std::endl;
return ERROR_JSON_PARSE;
}
if (status != 0) {
std::cerr << "[privatebinapi] server status=" << status << ", message= " << message << std::endl;
}
if (status != 0) {
return ERROR_SERVER;
@ -109,7 +117,11 @@ int create_paste(const char* server_url, const char* content,
copy_string_to_output(del_token, delete_token);
return ERROR_SUCCESS;
} catch (const std::exception& e) {
std::cerr << "[privatebinapi] crypto error: " << e.what() << std::endl;
return ERROR_CRYPTO;
} catch (...) {
std::cerr << "[privatebinapi] unknown crypto error" << std::endl;
return ERROR_CRYPTO;
}
}
@ -122,15 +134,17 @@ int get_paste(const char* server_url, const char* paste_id,
}
try {
// Construct the URL
std::string url = std::string(server_url) + "/" + paste_id;
// Construct the URL with query per API: base?pasteID
std::string url = std::string(server_url) + "?" + paste_id;
// Send GET request
HttpClient client;
std::string response;
std::cerr << "[privatebinapi] GET " << url << std::endl;
if (!client.get(url, response)) {
return ERROR_NETWORK;
}
std::cerr << "[privatebinapi] GET response: " << response << std::endl;
// Parse the JSON response
json json_data = json::parse(response);
@ -188,9 +202,11 @@ int delete_paste(const char* server_url, const char* paste_id,
// Send DELETE request
HttpClient client;
std::string response;
std::cerr << "[privatebinapi] DELETE payload: " << json_data << std::endl;
if (!client.delete_req(server_url, json_data, response)) {
return ERROR_NETWORK;
}
std::cerr << "[privatebinapi] DELETE response: " << response << std::endl;
// Parse response
int status;