feat: v1.3-konforme API, HTTP-Fixes, Base64, Example erweitert; Makefile & README Setup
This commit is contained in:
70
README.md
70
README.md
@ -14,42 +14,60 @@ This library provides a simple C++ interface for interacting with PrivateBin ser
|
|||||||
- Cross-platform compatibility (Windows and Linux)
|
- Cross-platform compatibility (Windows and Linux)
|
||||||
- Support for PrivateBin API versions 1.3 and later
|
- Support for PrivateBin API versions 1.3 and later
|
||||||
|
|
||||||
## Building
|
## Entwicklung & Build
|
||||||
|
|
||||||
### Prerequisites
|
### Voraussetzungen
|
||||||
|
|
||||||
- CMake 3.10 or later
|
- CMake 3.10+
|
||||||
- C++17 compatible compiler
|
- C++17-fähiger Compiler (MSVC 2022 bzw. GCC/Clang)
|
||||||
- For Windows: Windows SDK
|
- Git
|
||||||
- For Linux: libcurl development headers
|
- 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**:
|
### Schnellstart mit Makefile (Windows & Linux)
|
||||||
- Windows: WinHTTP
|
|
||||||
- Linux: libcurl
|
|
||||||
|
|
||||||
2. **JSON Processing**:
|
1) Abhängigkeiten installieren, konfigurieren und bauen:
|
||||||
- nlohmann/json
|
|
||||||
|
|
||||||
### Building on Windows
|
```
|
||||||
|
make
|
||||||
```cmd
|
|
||||||
mkdir build
|
|
||||||
cd build
|
|
||||||
cmake ..
|
|
||||||
cmake --build .
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Building on Linux
|
2) Beispiel bauen und ausführen:
|
||||||
|
|
||||||
```bash
|
```
|
||||||
mkdir build
|
make example
|
||||||
cd build
|
```
|
||||||
cmake ..
|
|
||||||
make
|
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
|
## Usage
|
||||||
|
|||||||
@ -5,7 +5,7 @@ REM Create build directory
|
|||||||
if not exist "build" mkdir build
|
if not exist "build" mkdir build
|
||||||
cd 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
|
cmake .. -G "Visual Studio 17 2022" -DCMAKE_TOOLCHAIN_FILE=../vcpkg/scripts/buildsystems/vcpkg.cmake
|
||||||
|
|
||||||
REM Build the project
|
REM Build the project
|
||||||
|
|||||||
@ -3,23 +3,25 @@ project(PrivateBinAPIExample)
|
|||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
|
||||||
# Find the privatebinapi library
|
# Use the prebuilt library from the parent build directory
|
||||||
find_library(PRIVATEBINAPI_LIB privatebinapi
|
set(PRIVATEBINAPI_BUILD_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../build")
|
||||||
PATHS ${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(EXISTS "${PRIVATEBINAPI_RELEASE_LIB}")
|
||||||
if(NOT PRIVATEBINAPI_LIB)
|
set(PRIVATEBINAPI_LIB "${PRIVATEBINAPI_RELEASE_LIB}")
|
||||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_CURRENT_BINARY_DIR}/privatebinapi)
|
else()
|
||||||
set(PRIVATEBINAPI_LIB privatebinapi)
|
# 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()
|
endif()
|
||||||
|
|
||||||
# Create example executable
|
|
||||||
add_executable(example example.cpp)
|
add_executable(example example.cpp)
|
||||||
|
|
||||||
# Link with the privatebinapi library
|
target_link_libraries(example PRIVATE ${PRIVATEBINAPI_LIB} winhttp)
|
||||||
target_link_libraries(example ${PRIVATEBINAPI_LIB})
|
|
||||||
|
|
||||||
# Include directories
|
|
||||||
target_include_directories(example PRIVATE
|
target_include_directories(example PRIVATE
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../include
|
${CMAKE_CURRENT_SOURCE_DIR}/../include
|
||||||
)
|
)
|
||||||
@ -31,7 +31,41 @@ int main() {
|
|||||||
std::cout << "Paste created successfully!" << std::endl;
|
std::cout << "Paste created successfully!" << std::endl;
|
||||||
std::cout << "URL: " << paste_url << std::endl;
|
std::cout << "URL: " << paste_url << std::endl;
|
||||||
std::cout << "Delete token: " << delete_token << 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
|
// Clean up allocated memory
|
||||||
free_string(paste_url);
|
free_string(paste_url);
|
||||||
free_string(delete_token);
|
free_string(delete_token);
|
||||||
|
|||||||
@ -4,13 +4,13 @@
|
|||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
// Crypto++ includes
|
// Crypto++ includes
|
||||||
#include "cryptlib.h"
|
#include <cryptopp/cryptlib.h>
|
||||||
#include "osrng.h" // AutoSeededRandomPool
|
#include <cryptopp/osrng.h> // AutoSeededRandomPool
|
||||||
#include "aes.h" // AES encryption
|
#include <cryptopp/aes.h> // AES encryption
|
||||||
#include "gcm.h" // GCM mode
|
#include <cryptopp/gcm.h> // GCM mode
|
||||||
#include "pwdbased.h" // PBKDF2
|
#include <cryptopp/pwdbased.h> // PBKDF2
|
||||||
#include "sha.h" // SHA256
|
#include <cryptopp/sha.h> // SHA256
|
||||||
#include "zlib.h" // Zlib compression
|
#include <cryptopp/zlib.h> // Zlib compression
|
||||||
|
|
||||||
using namespace CryptoPP;
|
using namespace CryptoPP;
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,14 @@
|
|||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <winhttp.h>
|
#include <winhttp.h>
|
||||||
#pragma comment(lib, "winhttp.lib")
|
#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
|
#elif LINUX
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
#endif
|
#endif
|
||||||
@ -36,7 +44,8 @@ bool HttpClient::get(const std::string& url, std::string& response) {
|
|||||||
urlComp.dwExtraInfoLength = (DWORD)-1;
|
urlComp.dwExtraInfoLength = (DWORD)-1;
|
||||||
|
|
||||||
// Parse the URL
|
// 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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,13 +68,25 @@ bool HttpClient::get(const std::string& url, std::string& response) {
|
|||||||
return false;
|
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
|
// Create an HTTP request handle
|
||||||
HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET",
|
HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET",
|
||||||
urlComp.lpszUrlPath,
|
objectName.c_str(),
|
||||||
NULL, WINHTTP_NO_REFERER,
|
NULL, WINHTTP_NO_REFERER,
|
||||||
WINHTTP_DEFAULT_ACCEPT_TYPES,
|
WINHTTP_DEFAULT_ACCEPT_TYPES,
|
||||||
(urlComp.nScheme == INTERNET_SCHEME_HTTPS) ?
|
(urlComp.nScheme == INTERNET_SCHEME_HTTPS) ?
|
||||||
WINHTTP_FLAG_SECURE : 0);
|
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) {
|
if (!hRequest) {
|
||||||
WinHttpCloseHandle(hConnect);
|
WinHttpCloseHandle(hConnect);
|
||||||
@ -156,7 +177,8 @@ bool HttpClient::post(const std::string& url, const std::string& data, std::stri
|
|||||||
urlComp.dwExtraInfoLength = (DWORD)-1;
|
urlComp.dwExtraInfoLength = (DWORD)-1;
|
||||||
|
|
||||||
// Parse the URL
|
// 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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,9 +201,10 @@ bool HttpClient::post(const std::string& url, const std::string& data, std::stri
|
|||||||
return false;
|
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",
|
HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"POST",
|
||||||
urlComp.lpszUrlPath,
|
postPath,
|
||||||
NULL, WINHTTP_NO_REFERER,
|
NULL, WINHTTP_NO_REFERER,
|
||||||
WINHTTP_DEFAULT_ACCEPT_TYPES,
|
WINHTTP_DEFAULT_ACCEPT_TYPES,
|
||||||
(urlComp.nScheme == INTERNET_SCHEME_HTTPS) ?
|
(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 a request
|
||||||
|
// Send UTF-8 bytes as body (raw)
|
||||||
if (!WinHttpSendRequest(hRequest,
|
if (!WinHttpSendRequest(hRequest,
|
||||||
WINHTTP_NO_ADDITIONAL_HEADERS, 0,
|
WINHTTP_NO_ADDITIONAL_HEADERS, 0,
|
||||||
(LPVOID)data.c_str(), (DWORD)data.length(),
|
(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;
|
urlComp.dwExtraInfoLength = (DWORD)-1;
|
||||||
|
|
||||||
// Parse the URL
|
// 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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -314,8 +339,9 @@ bool HttpClient::delete_req(const std::string& url, const std::string& data, std
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create an HTTP request handle
|
// Create an HTTP request handle
|
||||||
|
LPCWSTR delPath = (urlComp.lpszUrlPath && urlComp.dwUrlPathLength > 0) ? urlComp.lpszUrlPath : L"/";
|
||||||
HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"POST",
|
HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"POST",
|
||||||
urlComp.lpszUrlPath,
|
delPath,
|
||||||
NULL, WINHTTP_NO_REFERER,
|
NULL, WINHTTP_NO_REFERER,
|
||||||
WINHTTP_DEFAULT_ACCEPT_TYPES,
|
WINHTTP_DEFAULT_ACCEPT_TYPES,
|
||||||
(urlComp.nScheme == INTERNET_SCHEME_HTTPS) ?
|
(urlComp.nScheme == INTERNET_SCHEME_HTTPS) ?
|
||||||
@ -341,9 +367,9 @@ bool HttpClient::delete_req(const std::string& url, const std::string& data, std
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send a request with DELETE method
|
// Send a request body with DELETE method
|
||||||
if (!WinHttpSendRequest(hRequest,
|
if (!WinHttpSendRequest(hRequest,
|
||||||
L"DELETE", 6,
|
WINHTTP_NO_ADDITIONAL_HEADERS, 0,
|
||||||
(LPVOID)data.c_str(), (DWORD)data.length(),
|
(LPVOID)data.c_str(), (DWORD)data.length(),
|
||||||
(DWORD)data.length(), 0)) {
|
(DWORD)data.length(), 0)) {
|
||||||
WinHttpCloseHandle(hRequest);
|
WinHttpCloseHandle(hRequest);
|
||||||
|
|||||||
@ -1,6 +1,28 @@
|
|||||||
#include "json_parser.h"
|
#include "json_parser.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <chrono>
|
#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,
|
json JsonParser::create_paste_json(const std::vector<unsigned char>& cipher_text,
|
||||||
const std::vector<unsigned char>& auth_tag,
|
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 burn_after_reading,
|
||||||
bool open_discussion) {
|
bool open_discussion) {
|
||||||
|
|
||||||
// Convert binary data to base64 strings (stub implementation)
|
// Convert binary data to base64 strings
|
||||||
std::string cipher_text_b64(cipher_text.begin(), cipher_text.end());
|
std::string cipher_text_b64 = encode_base64(cipher_text);
|
||||||
std::string auth_tag_b64(auth_tag.begin(), auth_tag.end());
|
std::string iv_b64 = encode_base64(iv);
|
||||||
std::string iv_b64(iv.begin(), iv.end());
|
std::string salt_b64 = encode_base64(salt);
|
||||||
std::string salt_b64(salt.begin(), salt.end());
|
|
||||||
|
|
||||||
// Get current timestamp
|
// Get current timestamp
|
||||||
auto now = std::chrono::duration_cast<std::chrono::seconds>(
|
auto now = std::chrono::duration_cast<std::chrono::seconds>(
|
||||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||||
|
|
||||||
// Create the metadata array
|
// Create the metadata array according to PrivateBin v2
|
||||||
json metadata = {
|
json metadata = {
|
||||||
{
|
{
|
||||||
iv_b64, // base64(cipher_iv)
|
iv_b64, // base64(cipher_iv)
|
||||||
@ -33,21 +54,19 @@ json JsonParser::create_paste_json(const std::vector<unsigned char>& cipher_text
|
|||||||
"gcm", // mode
|
"gcm", // mode
|
||||||
"zlib" // compression
|
"zlib" // compression
|
||||||
},
|
},
|
||||||
format, // format
|
format, // formatter key
|
||||||
burn_after_reading ? 1 : 0, // burn after reading
|
burn_after_reading ? 1 : 0, // burn after reading
|
||||||
open_discussion ? 1 : 0 // open discussion
|
open_discussion ? 1 : 0 // open discussion
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create the main JSON structure
|
// Create the main JSON structure
|
||||||
|
// JSON close to v1.3 JSON-LD: meta.expire used by server
|
||||||
json paste_json = {
|
json paste_json = {
|
||||||
{"v", 2}, // version
|
{"v", 2}, // version
|
||||||
{"adata", metadata}, // metadata
|
{"adata", metadata}, // metadata
|
||||||
{"ct", cipher_text_b64}, // cipher text
|
{"ct", cipher_text_b64}, // ciphertext (base64)
|
||||||
{"meta", {
|
{"meta", {
|
||||||
{"expire", expiration},
|
{"expire", expiration}
|
||||||
{"created", now},
|
|
||||||
{"time_to_live", 300}, // This would be calculated based on expiration
|
|
||||||
{"icon", "data:image/png;base64,..."} // Placeholder
|
|
||||||
}}
|
}}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -62,29 +81,32 @@ bool JsonParser::parse_paste_json(const json& json_data,
|
|||||||
std::string& expiration) {
|
std::string& expiration) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Extract cipher text
|
// Extract and decode cipher text
|
||||||
std::string ct = json_data["ct"];
|
std::string ct_b64 = json_data.at("ct");
|
||||||
cipher_text = std::vector<unsigned char>(ct.begin(), ct.end());
|
cipher_text = decode_base64(ct_b64);
|
||||||
|
|
||||||
// Extract metadata
|
// Optional explicit tag field
|
||||||
json adata = json_data["adata"];
|
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) {
|
if (adata.size() >= 1) {
|
||||||
json first_element = adata[0];
|
json first_element = adata[0];
|
||||||
if (first_element.is_array() && first_element.size() >= 2) {
|
if (first_element.is_array() && first_element.size() >= 2) {
|
||||||
// Extract IV and salt (from base64 in real implementation)
|
std::string iv_b64 = first_element[0];
|
||||||
std::string iv_str = first_element[0];
|
std::string salt_b64 = first_element[1];
|
||||||
std::string salt_str = first_element[1];
|
iv = decode_base64(iv_b64);
|
||||||
|
salt = decode_base64(salt_b64);
|
||||||
iv = std::vector<unsigned char>(iv_str.begin(), iv_str.end());
|
|
||||||
salt = std::vector<unsigned char>(salt_str.begin(), salt_str.end());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract expiration
|
// Extract expiration
|
||||||
expiration = json_data["meta"]["expire"];
|
expiration = json_data.at("meta").at("expire");
|
||||||
|
|
||||||
// Auth tag would be extracted from the ciphertext in a real implementation
|
|
||||||
auth_tag.resize(16, 0);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
@ -101,17 +123,13 @@ bool JsonParser::parse_response(const std::string& response,
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
json json_response = json::parse(response);
|
json json_response = json::parse(response);
|
||||||
|
status = json_response.value("status", 1);
|
||||||
status = json_response["status"];
|
|
||||||
|
|
||||||
if (status == 0) {
|
if (status == 0) {
|
||||||
// Success response
|
paste_id = json_response.value("id", "");
|
||||||
paste_id = json_response["id"];
|
url = json_response.value("url", "");
|
||||||
url = json_response["url"];
|
delete_token = json_response.value("deletetoken", "");
|
||||||
delete_token = json_response["deletetoken"];
|
|
||||||
} else {
|
} else {
|
||||||
// Error response
|
message = json_response.value("message", "");
|
||||||
message = json_response["message"];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
#define PRIVATEBIN_API_VERSION "1.3"
|
#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));
|
std::vector<unsigned char> plaintext(content, content + strlen(content));
|
||||||
|
|
||||||
// Compress the plaintext
|
// Compress the plaintext
|
||||||
|
std::cerr << "[privatebinapi] compress..." << std::endl;
|
||||||
std::vector<unsigned char> compressed_data = Crypto::compress(plaintext);
|
std::vector<unsigned char> compressed_data = Crypto::compress(plaintext);
|
||||||
|
|
||||||
// Generate random salt and IV
|
// 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);
|
std::vector<unsigned char> iv = Crypto::generate_key(16);
|
||||||
|
|
||||||
// Derive key using PBKDF2
|
// Derive key using PBKDF2
|
||||||
|
std::cerr << "[privatebinapi] pbkdf2..." << std::endl;
|
||||||
std::vector<unsigned char> derived_key = Crypto::pbkdf2_hmac_sha256(
|
std::vector<unsigned char> derived_key = Crypto::pbkdf2_hmac_sha256(
|
||||||
paste_passphrase, salt, 100000, 32);
|
paste_passphrase, salt, 100000, 32);
|
||||||
|
|
||||||
// Encrypt the data
|
// Encrypt the data
|
||||||
|
std::cerr << "[privatebinapi] encrypt..." << std::endl;
|
||||||
std::vector<unsigned char> auth_tag;
|
std::vector<unsigned char> auth_tag;
|
||||||
std::vector<unsigned char> cipher_text = Crypto::encrypt(
|
std::vector<unsigned char> cipher_text = Crypto::encrypt(
|
||||||
compressed_data, derived_key, iv, auth_tag);
|
compressed_data, derived_key, iv, auth_tag);
|
||||||
@ -91,8 +95,12 @@ int create_paste(const char* server_url, const char* content,
|
|||||||
int status;
|
int status;
|
||||||
std::string message, paste_id, url, del_token;
|
std::string message, paste_id, url, del_token;
|
||||||
if (!JsonParser::parse_response(response, status, 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;
|
return ERROR_JSON_PARSE;
|
||||||
}
|
}
|
||||||
|
if (status != 0) {
|
||||||
|
std::cerr << "[privatebinapi] server status=" << status << ", message= " << message << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
if (status != 0) {
|
if (status != 0) {
|
||||||
return ERROR_SERVER;
|
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);
|
copy_string_to_output(del_token, delete_token);
|
||||||
|
|
||||||
return ERROR_SUCCESS;
|
return ERROR_SUCCESS;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "[privatebinapi] crypto error: " << e.what() << std::endl;
|
||||||
|
return ERROR_CRYPTO;
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
|
std::cerr << "[privatebinapi] unknown crypto error" << std::endl;
|
||||||
return ERROR_CRYPTO;
|
return ERROR_CRYPTO;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -122,15 +134,17 @@ int get_paste(const char* server_url, const char* paste_id,
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Construct the URL
|
// Construct the URL with query per API: base?pasteID
|
||||||
std::string url = std::string(server_url) + "/" + paste_id;
|
std::string url = std::string(server_url) + "?" + paste_id;
|
||||||
|
|
||||||
// Send GET request
|
// Send GET request
|
||||||
HttpClient client;
|
HttpClient client;
|
||||||
std::string response;
|
std::string response;
|
||||||
|
std::cerr << "[privatebinapi] GET " << url << std::endl;
|
||||||
if (!client.get(url, response)) {
|
if (!client.get(url, response)) {
|
||||||
return ERROR_NETWORK;
|
return ERROR_NETWORK;
|
||||||
}
|
}
|
||||||
|
std::cerr << "[privatebinapi] GET response: " << response << std::endl;
|
||||||
|
|
||||||
// Parse the JSON response
|
// Parse the JSON response
|
||||||
json json_data = json::parse(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
|
// Send DELETE request
|
||||||
HttpClient client;
|
HttpClient client;
|
||||||
std::string response;
|
std::string response;
|
||||||
|
std::cerr << "[privatebinapi] DELETE payload: " << json_data << std::endl;
|
||||||
if (!client.delete_req(server_url, json_data, response)) {
|
if (!client.delete_req(server_url, json_data, response)) {
|
||||||
return ERROR_NETWORK;
|
return ERROR_NETWORK;
|
||||||
}
|
}
|
||||||
|
std::cerr << "[privatebinapi] DELETE response: " << response << std::endl;
|
||||||
|
|
||||||
// Parse response
|
// Parse response
|
||||||
int status;
|
int status;
|
||||||
|
|||||||
Reference in New Issue
Block a user