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 9e4a65fadd
8 changed files with 209 additions and 95 deletions

View File

@ -14,42 +14,60 @@ This library provides a simple C++ interface for interacting with PrivateBin ser
- Cross-platform compatibility (Windows and Linux)
- Support for PrivateBin API versions 1.3 and later
## Building
## Entwicklung & Build
### Prerequisites
### Voraussetzungen
- CMake 3.10 or later
- C++17 compatible compiler
- For Windows: Windows SDK
- For Linux: libcurl development headers
- CMake 3.10+
- C++17-fähiger Compiler (MSVC 2022 bzw. GCC/Clang)
- Git
- vcpkg (wird bei Makefile-Nutzung automatisch gebootstrapped)
### Dependencies
### Abhängigkeiten
The library depends on the following components:
- cryptopp (Crypto++)
- nlohmann-json
- Windows: WinHTTP (SDK)
- Linux: libcurl (wird automatisch über vcpkg genutzt)
1. **HTTP Client**:
- Windows: WinHTTP
- Linux: libcurl
### Schnellstart mit Makefile (Windows & Linux)
2. **JSON Processing**:
- nlohmann/json
1) Abhängigkeiten installieren, konfigurieren und bauen:
### Building on Windows
```cmd
mkdir build
cd build
cmake ..
cmake --build .
```
make
```
### Building on Linux
2) Beispiel bauen und ausführen:
```bash
mkdir build
cd build
cmake ..
make
```
make example
```
Das Makefile erledigt:
- vcpkg klonen & bootstrappen
- Pakete aus `vcpkg.json` installieren
- CMake mit vcpkg-Toolchain konfigurieren
- Bibliothek und Beispiel bauen
### Manuell mit CMake
```
# vcpkg klonen & bootstrappen
git clone https://github.com/microsoft/vcpkg.git "$HOME/vcpkg"
"$HOME/vcpkg/bootstrap-vcpkg.sh" # Linux/macOS
# Windows (PowerShell):
# powershell -NoProfile -ExecutionPolicy Bypass -Command "& '$env:USERPROFILE\vcpkg\bootstrap-vcpkg.bat'"
# Konfigurieren
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE="$HOME/vcpkg/scripts/buildsystems/vcpkg.cmake"
# Bauen
cmake --build build --config Release
# Beispiel
cmake -S example -B example/build -DCMAKE_BUILD_TYPE=Release
cmake --build example/build --config Release
```
## Usage

View File

@ -5,7 +5,7 @@ REM Create build directory
if not exist "build" mkdir build
cd build
REM Generate build files with CMake and vcpkg toolchain
REM Generate build files with CMake and vcpkg manifest
cmake .. -G "Visual Studio 17 2022" -DCMAKE_TOOLCHAIN_FILE=../vcpkg/scripts/buildsystems/vcpkg.cmake
REM Build the project

View File

@ -3,23 +3,25 @@ project(PrivateBinAPIExample)
set(CMAKE_CXX_STANDARD 17)
# Find the privatebinapi library
find_library(PRIVATEBINAPI_LIB privatebinapi
PATHS ${CMAKE_CURRENT_SOURCE_DIR}/../build)
# Use the prebuilt library from the parent build directory
set(PRIVATEBINAPI_BUILD_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../build")
set(PRIVATEBINAPI_RELEASE_LIB "${PRIVATEBINAPI_BUILD_DIR}/Release/privatebinapi.lib")
# If not found, build it as part of the project
if(NOT PRIVATEBINAPI_LIB)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_CURRENT_BINARY_DIR}/privatebinapi)
set(PRIVATEBINAPI_LIB privatebinapi)
if(EXISTS "${PRIVATEBINAPI_RELEASE_LIB}")
set(PRIVATEBINAPI_LIB "${PRIVATEBINAPI_RELEASE_LIB}")
else()
# Fallback: try the build root (multi-config generators may place libs differently)
find_library(PRIVATEBINAPI_LIB privatebinapi PATHS "${PRIVATEBINAPI_BUILD_DIR}")
endif()
if(NOT PRIVATEBINAPI_LIB)
message(FATAL_ERROR "privatebinapi library not found. Please run build.bat in the project root first.")
endif()
# Create example executable
add_executable(example example.cpp)
# Link with the privatebinapi library
target_link_libraries(example ${PRIVATEBINAPI_LIB})
target_link_libraries(example PRIVATE ${PRIVATEBINAPI_LIB} winhttp)
# Include directories
target_include_directories(example PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/../include
)

View File

@ -32,6 +32,40 @@ int main() {
std::cout << "URL: " << paste_url << std::endl;
std::cout << "Delete token: " << delete_token << std::endl;
// Parse paste_id and key from URL (format: "/?{pasteID}#{key}")
std::string full_url = paste_url ? paste_url : "";
std::string paste_id;
std::string key;
auto qpos = full_url.find('?');
auto hpos = full_url.find('#');
if (qpos != std::string::npos) {
if (hpos != std::string::npos && hpos > qpos + 1) {
paste_id = full_url.substr(qpos + 1, hpos - (qpos + 1));
key = full_url.substr(hpos + 1);
} else if (qpos + 1 < full_url.size()) {
paste_id = full_url.substr(qpos + 1);
}
}
// Try to fetch paste content back
if (!paste_id.empty() && !key.empty()) {
std::cout << "Fetching paste..." << std::endl;
char* content = nullptr;
int gr = get_paste("https://privatebin.medisoftware.org", paste_id.c_str(), key.c_str(), &content);
std::cout << "get_paste returned: " << gr << std::endl;
if (gr == 0 && content) {
std::cout << "Content: " << content << std::endl;
free_string(content);
}
}
// Try to delete paste
if (!paste_id.empty() && delete_token) {
std::cout << "Deleting paste..." << std::endl;
int dr = delete_paste("https://privatebin.medisoftware.org", paste_id.c_str(), delete_token);
std::cout << "delete_paste returned: " << dr << std::endl;
}
// Clean up allocated memory
free_string(paste_url);
free_string(delete_token);

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;