commit 90d9a23dd24526fb0a3be52d86e2c091e7184a9f Author: elpatron Date: Thu Aug 28 09:15:47 2025 +0200 Initial commit: PrivateBin API C++ DLL implementation diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ff0a6e7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +# Build directories +build/ +cmake-build-*/ + +# Compiled object files +*.o +*.obj + +# Compiled dynamic libraries +*.so +*.dll +*.dylib + +# Compiled static libraries +*.a +*.lib + +# Executables +*.exe + +# IDE files +.vs/ +*.user +*.suo + +# CMake files +CMakeCache.txt +CMakeFiles/ +cmake_install.cmake +Makefile + +# Dependency directories +external/ +third_party/ + +# Log files +*.log + +# OS generated files +.DS_Store +Thumbs.db \ No newline at end of file diff --git a/.qoder/quests/privatebin-apicpp.md b/.qoder/quests/privatebin-apicpp.md new file mode 100644 index 0000000..40b628b --- /dev/null +++ b/.qoder/quests/privatebin-apicpp.md @@ -0,0 +1,326 @@ +# PrivateBin API C++ DLL Design Document + +## 1. Overview + +This document describes the design of a cross-platform C++ DLL that implements the PrivateBin API. The DLL will provide functions to interact with PrivateBin servers, allowing applications to create, retrieve, and delete pastes programmatically. The library will be compatible with both Windows and Linux platforms. + +### 1.1 Purpose + +The purpose of this library is to provide a simple, cross-platform C++ interface for interacting with PrivateBin services. PrivateBin is a minimalist, open-source online pastebin where the server has zero knowledge of stored data. All data is encrypted and decrypted in the browser using 256-bit AES encryption. + +### 1.2 Scope + +The library will implement the following functionality: +- Creating new pastes with optional expiration, formatting, and security settings +- Retrieving existing pastes by ID +- Deleting pastes using the deletion token +- Cross-platform compatibility (Windows and Linux) +- Support for PrivateBin API versions 1.3 and later + +## 2. Architecture + +### 2.1 System Overview + +``` ++---------------------+ +| Application | ++----------+----------+ + | ++----------v----------+ +| PrivateBin C++ DLL | +| | +| +----------------+ | +| | HTTP Client | | +| +----------------+ | +| +----------------+ | +| | Crypto Module | | +| +----------------+ | +| +----------------+ | +| | JSON Parser | | +| +----------------+ | ++----------+----------+ + | ++----------v----------+ +| PrivateBin Server | ++---------------------+ +``` + +### 2.2 Component Description + +1. **HTTP Client Layer**: Responsible for all network communication with PrivateBin servers +2. **Crypto Module**: Handles all encryption/decryption operations using AES-GCM +3. **JSON Parser**: Manages serialization and deserialization of API requests/responses +4. **API Interface**: Provides the public interface for library users + +### 2.3 Cross-Platform Considerations + +To ensure compatibility across Windows and Linux: +- Use standard C++ libraries where possible +- Abstract platform-specific functionality behind common interfaces +- Use CMake as the build system for cross-platform compilation +- Handle character encoding differences between platforms + +## 3. API Endpoints Reference + +### 3.1 Request/Response Schema + +The PrivateBin API uses JSON for communication. All requests must include the header `X-Requested-With: JSONHttpRequest`. + +#### 3.1.1 Retrieve Paste +- **Method**: GET +- **Endpoint**: `/[pasteID]` +- **Request Data**: None +- **Response**: paste.jsonld + +#### 3.1.2 Create Paste +- **Method**: POST +- **Endpoint**: `/` +- **Request Data**: paste.jsonld (without pasteID) +- **Response**: `{"status": 0, "id": "[pasteID]", "url": "[serverAddress?pasteID]", "deletetoken": "[deleteToken]"}` + +#### 3.1.3 Delete Paste +- **Method**: DELETE/POST +- **Endpoint**: `/` +- **Request Data**: `{"pasteid": "[pasteID]", "deletetoken": "[deletetoken]}` +- **Response**: `{"status":0, "id": "[pasteID]"}` + +#### 3.1.4 Error Response +- **Response**: `{"status":1, "message": "[errormessage]"}` + +### 3.2 Authentication Requirements + +No authentication is required for basic paste operations. All encryption/decryption happens client-side. The deletion token is required for deleting pastes. + +## 4. Data Models + +### 4.1 Paste Data Structure + +```json +{ + "v": 2, + "adata": [...], + "ct": "base64(cipher_text)", + "meta": { + "expire": "5min", + "created": 1234567890, + "time_to_live": 300, + "icon": "data:image/png;base64,..." + } +} +``` + +### 4.2 Paste Metadata Structure + +```json +[ + [ + "base64(cipher_iv)", + "base64(kdf_salt)", + 100000, + 256, + 128, + "aes", + "gcm", + "zlib" + ], + "plaintext", + 0, + 0 +] +``` + +## 5. Business Logic Layer + +### 5.1 Encryption Process + +The encryption process follows these steps: + +1. **Key Generation**: + - Generate a random 32-byte paste key + - If password provided: paste_passphrase = paste_key + paste_password + - If no password: paste_passphrase = paste_key + +2. **Data Preparation**: + - Create paste_data JSON structure + - Compress with zlib if enabled + +3. **Key Derivation**: + - Generate 8-byte random salt + - Apply PBKDF2-HMAC-SHA256 with 100,000 iterations + - Derive 256-bit key + +4. **Encryption**: + - Generate 16-byte random IV + - Encrypt with AES-GCM + - Generate 128-bit authentication tag + +5. **URL Generation**: + - Encode paste_key with Base58 + - Construct URL: `server_url + paste_id + "#" + encoded_key` + +### 5.2 Decryption Process + +1. **Key Extraction**: + - Extract Base58-encoded key from URL fragment + - Decode to 32-byte paste key + +2. **Data Retrieval**: + - Fetch paste data from server + - Extract metadata and ciphertext + +3. **Decryption**: + - Derive key using PBKDF2 + - Decrypt with AES-GCM + - Verify authentication tag + +4. **Data Decompression**: + - Decompress with zlib if needed + - Parse JSON structure + +## 6. Implementation Details + +### 6.1 Dependencies + +The library will depend on the following components: + +1. **HTTP Client**: + - Windows: WinHTTP or WinINet + - Linux: libcurl + - Cross-platform alternative: cpp-httplib + +2. **Cryptography**: + - Crypto++ or Botan for AES-GCM encryption + - Custom or existing implementation for PBKDF2 + - zlib for compression + +3. **JSON Processing**: + - nlohmann/json or rapidjson + +4. **Base58 Encoding**: + - Custom implementation or existing library + +### 6.2 Public API Interface + +```cpp +// Core API functions +int create_paste(const char* server_url, const char* content, + const char* password, const char* expiration, + const char* format, int burn_after_reading, + int open_discussion, char** paste_url, + char** delete_token); + +int get_paste(const char* server_url, const char* paste_id, + const char* key, char** content); + +int delete_paste(const char* server_url, const char* paste_id, + const char* delete_token); +``` + +### 6.3 Error Handling + +The library will use error codes to indicate the result of operations: +- 0: Success +- 1: Network error +- 2: Encryption/decryption error +- 3: Invalid input +- 4: Server error +- 5: JSON parsing error + +## 7. Cross-Platform Implementation + +### 7.1 Build System + +Using CMake for cross-platform builds: + +```cmake +# CMakeLists.txt +cmake_minimum_required(VERSION 3.10) +project(PrivateBinAPI) + +set(CMAKE_CXX_STANDARD 17) + +# Platform-specific configurations +if(WIN32) + # Windows-specific settings + add_definitions(-DWINDOWS) +elseif(UNIX) + # Linux-specific settings + add_definitions(-DLINUX) +endif() + +# Add library sources +add_library(privatebinapi SHARED + src/privatebinapi.cpp + src/http_client.cpp + src/crypto.cpp + src/json_parser.cpp +) + +# Link dependencies +target_link_libraries(privatebinapi ${DEPENDENCIES}) +``` + +### 7.2 Platform Abstraction + +```cpp +// http_client.h +#ifdef WINDOWS + #include + #include +#elif LINUX + #include +#endif + +class HttpClient { +public: + bool get(const std::string& url, std::string& response); + bool post(const std::string& url, const std::string& data, + std::string& response); + bool put(const std::string& url, const std::string& data, + std::string& response); + bool delete_req(const std::string& url, const std::string& data, + std::string& response); +}; +``` + +## 8. Security Considerations + +### 8.1 Cryptographic Requirements + +1. **Encryption Algorithm**: AES-256-GCM +2. **Key Derivation**: PBKDF2-HMAC-SHA256 with 100,000 iterations +3. **Random Number Generation**: Use platform-specific secure random generators +4. **Key Storage**: Keys are never stored; they exist only in memory during operations + +### 8.2 Data Protection + +1. All encryption/decryption happens client-side +2. No plaintext data is sent over the network +3. Sensitive data in memory is cleared after use +4. Secure string handling to prevent memory dumps + +## 9. Testing Strategy + +### 9.1 Unit Testing + +Unit tests will cover: +- Encryption/decryption functions +- Base58 encoding/decoding +- JSON serialization/deserialization +- HTTP client functionality +- Error handling + +### 9.2 Integration Testing + +Integration tests will verify: +- End-to-end paste creation and retrieval +- Cross-platform compatibility +- Error scenarios and edge cases +- Performance benchmarks + +### 9.3 Test Framework + +- Google Test for unit testing +- Mock HTTP servers for integration testing +- Cross-platform test execution + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e661ea7 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,88 @@ +cmake_minimum_required(VERSION 3.10) +project(PrivateBinAPI) + +set(CMAKE_CXX_STANDARD 17) + +# Platform-specific configurations +if(WIN32) + # Windows-specific settings + add_definitions(-DWINDOWS) + set(PLATFORM_LIBS winhttp) +elseif(UNIX) + # Linux-specific settings + add_definitions(-DLINUX) + find_package(PkgConfig REQUIRED) + find_package(CURL REQUIRED) + set(PLATFORM_LIBS ${CURL_LIBRARIES}) +endif() + +# Handle nlohmann/json dependency +# First try to find it as a package +find_package(nlohmann_json QUIET) + +if(nlohmann_json_FOUND) + # Use the found package + message(STATUS "Found nlohmann_json package") +else() + # Download it as a submodule or include it directly + message(STATUS "nlohmann_json not found as package, will use submodule") + + # Check if we have it in external/json + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/external/json/include/nlohmann/json.hpp") + set(NLOHMANN_JSON_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/external/json/include") + else() + # Try to download it + include(FetchContent) + FetchContent_Declare( + nlohmann_json + GIT_REPOSITORY https://github.com/nlohmann/json.git + GIT_TAG v3.11.2 + ) + FetchContent_MakeAvailable(nlohmann_json) + set(NLOHMANN_JSON_INCLUDE_DIRS ${nlohmann_json_SOURCE_DIR}/include) + endif() +endif() + +# Add library sources +set(SOURCES + src/privatebinapi.cpp + src/http_client.cpp + src/crypto.cpp + src/json_parser.cpp + src/base58.cpp +) + +set(HEADERS + include/privatebinapi.h + include/http_client.h + include/crypto.h + include/json_parser.h + include/base58.h +) + +# Create the shared library +add_library(privatebinapi SHARED ${SOURCES} ${HEADERS}) + +# Include directories +target_include_directories(privatebinapi PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include +) + +# Include nlohmann/json +if(nlohmann_json_FOUND) + target_link_libraries(privatebinapi nlohmann_json::nlohmann_json) +else() + target_include_directories(privatebinapi PRIVATE ${NLOHMANN_JSON_INCLUDE_DIRS}) +endif() + +# Link dependencies +target_link_libraries(privatebinapi ${PLATFORM_LIBS}) + +# Install targets +install(TARGETS privatebinapi + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib +) + +install(FILES ${HEADERS} DESTINATION include/privatebinapi)) \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8481905 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 PrivateBin API C++ DLL + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e835673 --- /dev/null +++ b/README.md @@ -0,0 +1,127 @@ +# PrivateBin API C++ DLL + +A cross-platform C++ library for interacting with PrivateBin servers. + +## Overview + +This library provides a simple C++ interface for interacting with PrivateBin services. PrivateBin is a minimalist, open-source online pastebin where the server has zero knowledge of stored data. All data is encrypted and decrypted in the browser using 256-bit AES encryption. + +## Features + +- Create new pastes with optional expiration, formatting, and security settings +- Retrieve existing pastes by ID +- Delete pastes using the deletion token +- Cross-platform compatibility (Windows and Linux) +- Support for PrivateBin API versions 1.3 and later + +## Building + +### Prerequisites + +- CMake 3.10 or later +- C++17 compatible compiler +- For Windows: Windows SDK +- For Linux: libcurl development headers + +### Dependencies + +The library depends on the following components: + +1. **HTTP Client**: + - Windows: WinHTTP + - Linux: libcurl + +2. **JSON Processing**: + - nlohmann/json + +### Building on Windows + +```cmd +mkdir build +cd build +cmake .. +cmake --build . +``` + +### Building on Linux + +```bash +mkdir build +cd build +cmake .. +make +``` + +## Usage + +### API Functions + +```cpp +// Create a new paste +int create_paste(const char* server_url, const char* content, + const char* password, const char* expiration, + const char* format, int burn_after_reading, + int open_discussion, char** paste_url, + char** delete_token); + +// Retrieve a paste +int get_paste(const char* server_url, const char* paste_id, + const char* key, char** content); + +// Delete a paste +int delete_paste(const char* server_url, const char* paste_id, + const char* delete_token); + +// Free memory allocated by the API functions +void free_string(char* str); +``` + +### Example + +```cpp +#include "privatebinapi.h" +#include + +int main() { + char* paste_url = nullptr; + char* delete_token = nullptr; + + int result = create_paste( + "https://privatebin.net", + "Hello, PrivateBin!", + nullptr, // No password + "1hour", // Expire in 1 hour + "plaintext", // Plain text format + 0, // Don't burn after reading + 0, // No discussion + &paste_url, + &delete_token + ); + + if (result == 0) { + std::cout << "Paste created: " << paste_url << std::endl; + std::cout << "Delete token: " << delete_token << std::endl; + + // Free allocated memory + free_string(paste_url); + free_string(delete_token); + } else { + std::cout << "Failed to create paste: " << result << std::endl; + } + + return 0; +} +``` + +## Error Codes + +- 0: Success +- 1: Network error +- 2: Encryption/decryption error +- 3: Invalid input +- 4: Server error +- 5: JSON parsing error + +## License + +This project is licensed under the MIT License - see the LICENSE file for details. \ No newline at end of file diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..f31e53c --- /dev/null +++ b/build.bat @@ -0,0 +1,15 @@ +@echo off +echo Building PrivateBin API C++ DLL... + +REM Create build directory +if not exist "build" mkdir build +cd build + +REM Generate build files with CMake +cmake .. -G "Visual Studio 16 2019" + +REM Build the project +cmake --build . --config Release + +echo Build completed! +cd .. \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..0738f31 --- /dev/null +++ b/build.sh @@ -0,0 +1,15 @@ +#!/bin/bash +echo "Building PrivateBin API C++ DLL..." + +# Create build directory +mkdir -p build +cd build + +# Generate build files with CMake +cmake .. + +# Build the project +make + +echo "Build completed!" +cd .. \ No newline at end of file diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 0000000..c57cb39 --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.10) +project(PrivateBinAPIExample) + +set(CMAKE_CXX_STANDARD 17) + +# Find the privatebinapi library +find_library(PRIVATEBINAPI_LIB privatebinapi + PATHS ${CMAKE_CURRENT_SOURCE_DIR}/../build) + +# 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) +endif() + +# Create example executable +add_executable(example example.cpp) + +# Link with the privatebinapi library +target_link_libraries(example ${PRIVATEBINAPI_LIB}) + +# Include directories +target_include_directories(example PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../include +) \ No newline at end of file diff --git a/example/example.cpp b/example/example.cpp new file mode 100644 index 0000000..c3b49f7 --- /dev/null +++ b/example/example.cpp @@ -0,0 +1,45 @@ +#include "privatebinapi.h" +#include +#include + +int main() { + std::cout << "PrivateBin API C++ DLL Example" << std::endl; + std::cout << "===============================" << std::endl; + + // Example of how to use the API + char* paste_url = nullptr; + char* delete_token = nullptr; + + // Note: This is a demonstration only. In a real application, + // you would use actual server URLs and handle errors appropriately. + + /* + int result = create_paste( + "https://privatebin.net", // Server URL + "Hello, PrivateBin!", // Content + nullptr, // No password + "1hour", // Expire in 1 hour + "plaintext", // Plain text format + 0, // Don't burn after reading + 0, // No discussion + &paste_url, // Output: paste URL + &delete_token // Output: delete token + ); + + if (result == 0) { + std::cout << "Paste created successfully!" << std::endl; + std::cout << "URL: " << paste_url << std::endl; + std::cout << "Delete token: " << delete_token << std::endl; + + // Clean up allocated memory + free_string(paste_url); + free_string(delete_token); + } else { + std::cout << "Failed to create paste. Error code: " << result << std::endl; + } + */ + + std::cout << "Example completed." << std::endl; + + return 0; +} \ No newline at end of file diff --git a/include/base58.h b/include/base58.h new file mode 100644 index 0000000..6f7ba5e --- /dev/null +++ b/include/base58.h @@ -0,0 +1,30 @@ +#ifndef BASE58_H +#define BASE58_H + +#include +#include + +class Base58 { +public: + /** + * Encodes data using Base58 + * + * @param data The data to encode + * @return The Base58 encoded string + */ + static std::string encode(const std::vector& data); + + /** + * Decodes a Base58 string + * + * @param encoded The Base58 encoded string + * @return The decoded data + */ + static std::vector decode(const std::string& encoded); + +private: + static const std::string ALPHABET; + static const int BASE58_BASE = 58; +}; + +#endif // BASE58_H \ No newline at end of file diff --git a/include/crypto.h b/include/crypto.h new file mode 100644 index 0000000..7b2e0bb --- /dev/null +++ b/include/crypto.h @@ -0,0 +1,76 @@ +#ifndef CRYPTO_H +#define CRYPTO_H + +#include +#include + +class Crypto { +public: + /** + * Generates a random key of specified length + * + * @param length The length of the key to generate + * @return The generated key + */ + static std::vector generate_key(size_t length); + + /** + * Encrypts data using AES-GCM + * + * @param plaintext The data to encrypt + * @param key The encryption key + * @param iv The initialization vector + * @param auth_tag Output parameter for the authentication tag + * @return The encrypted data + */ + static std::vector encrypt(const std::vector& plaintext, + const std::vector& key, + const std::vector& iv, + std::vector& auth_tag); + + /** + * Decrypts data using AES-GCM + * + * @param ciphertext The data to decrypt + * @param key The decryption key + * @param iv The initialization vector + * @param auth_tag The authentication tag + * @return The decrypted data + */ + static std::vector decrypt(const std::vector& ciphertext, + const std::vector& key, + const std::vector& iv, + const std::vector& auth_tag); + + /** + * Derives a key using PBKDF2-HMAC-SHA256 + * + * @param password The password to derive from + * @param salt The salt to use + * @param iterations The number of iterations + * @param key_length The length of the derived key + * @return The derived key + */ + static std::vector pbkdf2_hmac_sha256(const std::string& password, + const std::vector& salt, + int iterations, + size_t key_length); + + /** + * Compresses data using zlib + * + * @param data The data to compress + * @return The compressed data + */ + static std::vector compress(const std::vector& data); + + /** + * Decompresses data using zlib + * + * @param data The data to decompress + * @return The decompressed data + */ + static std::vector decompress(const std::vector& data); +}; + +#endif // CRYPTO_H \ No newline at end of file diff --git a/include/http_client.h b/include/http_client.h new file mode 100644 index 0000000..0c77915 --- /dev/null +++ b/include/http_client.h @@ -0,0 +1,47 @@ +#ifndef HTTP_CLIENT_H +#define HTTP_CLIENT_H + +#include + +class HttpClient { +public: + /** + * Performs an HTTP GET request + * + * @param url The URL to request + * @param response Output parameter for the response + * @return true on success, false on failure + */ + bool get(const std::string& url, std::string& response); + + /** + * Performs an HTTP POST request + * + * @param url The URL to request + * @param data The data to send + * @param response Output parameter for the response + * @return true on success, false on failure + */ + bool post(const std::string& url, const std::string& data, + std::string& response); + + /** + * Performs an HTTP DELETE request + * + * @param url The URL to request + * @param data The data to send + * @param response Output parameter for the response + * @return true on success, false on failure + */ + bool delete_req(const std::string& url, const std::string& data, + std::string& response); + +private: +#ifdef WINDOWS + // Windows-specific implementation details +#elif LINUX + // Linux-specific implementation details +#endif +}; + +#endif // HTTP_CLIENT_H \ No newline at end of file diff --git a/include/json_parser.h b/include/json_parser.h new file mode 100644 index 0000000..6334220 --- /dev/null +++ b/include/json_parser.h @@ -0,0 +1,71 @@ +#ifndef JSON_PARSER_H +#define JSON_PARSER_H + +#include +#include +#include + +using json = nlohmann::json; + +class JsonParser { +public: + /** + * Creates a paste JSON structure + * + * @param cipher_text The encrypted text + * @param auth_tag The authentication tag + * @param iv The initialization vector + * @param salt The salt used for key derivation + * @param expiration The expiration time + * @param format The format of the paste + * @param burn_after_reading Whether to burn after reading + * @param open_discussion Whether to enable discussion + * @return The JSON structure + */ + static json create_paste_json(const std::vector& cipher_text, + const std::vector& auth_tag, + const std::vector& iv, + const std::vector& salt, + const std::string& expiration, + const std::string& format, + bool burn_after_reading, + bool open_discussion); + + /** + * Parses a paste JSON structure + * + * @param json_data The JSON data to parse + * @param cipher_text Output parameter for the encrypted text + * @param auth_tag Output parameter for the authentication tag + * @param iv Output parameter for the initialization vector + * @param salt Output parameter for the salt + * @param expiration Output parameter for the expiration time + * @return true on success, false on failure + */ + static bool parse_paste_json(const json& json_data, + std::vector& cipher_text, + std::vector& auth_tag, + std::vector& iv, + std::vector& salt, + std::string& expiration); + + /** + * Parses a response JSON structure + * + * @param response The response string to parse + * @param status Output parameter for the status + * @param message Output parameter for the message (if error) + * @param paste_id Output parameter for the paste ID (if success) + * @param url Output parameter for the URL (if success) + * @param delete_token Output parameter for the delete token (if success) + * @return true on success, false on failure + */ + static bool parse_response(const std::string& response, + int& status, + std::string& message, + std::string& paste_id, + std::string& url, + std::string& delete_token); +}; + +#endif // JSON_PARSER_H \ No newline at end of file diff --git a/include/privatebinapi.h b/include/privatebinapi.h new file mode 100644 index 0000000..cc88fc4 --- /dev/null +++ b/include/privatebinapi.h @@ -0,0 +1,72 @@ +#ifndef PRIVATEBIN_API_H +#define PRIVATEBIN_API_H + +#ifdef _WIN32 + #ifdef PRIVATEBINAPI_EXPORTS + #define PRIVATEBIN_API __declspec(dllexport) + #else + #define PRIVATEBIN_API __declspec(dllimport) + #endif +#else + #define PRIVATEBIN_API +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Creates a new paste on a PrivateBin server + * + * @param server_url The URL of the PrivateBin server + * @param content The content to paste + * @param password Optional password for the paste (can be NULL) + * @param expiration Expiration time ("5min", "10min", "1hour", "1day", "1week", "1month", "1year", "never") + * @param format Format of the paste ("plaintext", "syntaxhighlighting", "markdown") + * @param burn_after_reading Set to 1 to enable burn after reading, 0 to disable + * @param open_discussion Set to 1 to enable discussion, 0 to disable + * @param paste_url Output parameter for the URL of the created paste + * @param delete_token Output parameter for the deletion token + * @return 0 on success, error code on failure + */ +PRIVATEBIN_API int create_paste(const char* server_url, const char* content, + const char* password, const char* expiration, + const char* format, int burn_after_reading, + int open_discussion, char** paste_url, + char** delete_token); + +/** + * Retrieves a paste from a PrivateBin server + * + * @param server_url The URL of the PrivateBin server + * @param paste_id The ID of the paste to retrieve + * @param key The decryption key for the paste + * @param content Output parameter for the content of the paste + * @return 0 on success, error code on failure + */ +PRIVATEBIN_API int get_paste(const char* server_url, const char* paste_id, + const char* key, char** content); + +/** + * Deletes a paste from a PrivateBin server + * + * @param server_url The URL of the PrivateBin server + * @param paste_id The ID of the paste to delete + * @param delete_token The deletion token for the paste + * @return 0 on success, error code on failure + */ +PRIVATEBIN_API int delete_paste(const char* server_url, const char* paste_id, + const char* delete_token); + +/** + * Frees memory allocated by the API functions + * + * @param str The string to free + */ +PRIVATEBIN_API void free_string(char* str); + +#ifdef __cplusplus +} +#endif + +#endif // PRIVATEBIN_API_H \ No newline at end of file diff --git a/src/base58.cpp b/src/base58.cpp new file mode 100644 index 0000000..1e7ab93 --- /dev/null +++ b/src/base58.cpp @@ -0,0 +1,89 @@ +#include "base58.h" +#include +#include + +const std::string Base58::ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + +std::string Base58::encode(const std::vector& data) { + if (data.empty()) { + return ""; + } + + // Skip leading zeros + size_t leading_zeros = 0; + while (leading_zeros < data.size() && data[leading_zeros] == 0) { + leading_zeros++; + } + + // Convert to base58 + std::vector digits((data.size() - leading_zeros) * 138 / 100 + 1); + size_t digitslen = 1; + + for (size_t i = leading_zeros; i < data.size(); i++) { + unsigned int carry = data[i]; + for (size_t j = 0; j < digitslen; j++) { + carry += (unsigned int)(digits[j]) << 8; + digits[j] = carry % 58; + carry /= 58; + } + while (carry > 0) { + digits[digitslen++] = carry % 58; + carry /= 58; + } + } + + // Convert to string + std::string result; + for (size_t i = 0; i < leading_zeros; i++) { + result += ALPHABET[0]; + } + for (size_t i = 0; i < digitslen; i++) { + result += ALPHABET[digits[digitslen - 1 - i]]; + } + + return result; +} + +std::vector Base58::decode(const std::string& encoded) { + if (encoded.empty()) { + return std::vector(); + } + + // Skip leading '1's (which represent leading zeros) + size_t leading_ones = 0; + while (leading_ones < encoded.length() && encoded[leading_ones] == '1') { + leading_ones++; + } + + // Convert from base58 + std::vector bytes((encoded.length() - leading_ones) * 733 / 1000 + 1); + size_t byteslen = 1; + + for (size_t i = leading_ones; i < encoded.length(); i++) { + unsigned int carry = ALPHABET.find(encoded[i]); + if (carry == std::string::npos) { + throw std::invalid_argument("Invalid character in Base58 string"); + } + + for (size_t j = 0; j < byteslen; j++) { + carry += (unsigned int)(bytes[j]) * 58; + bytes[j] = carry & 0xff; + carry >>= 8; + } + while (carry > 0) { + bytes[byteslen++] = carry & 0xff; + carry >>= 8; + } + } + + // Add leading zeros + std::vector result(leading_ones + byteslen); + for (size_t i = 0; i < leading_ones; i++) { + result[i] = 0; + } + for (size_t i = 0; i < byteslen; i++) { + result[leading_ones + i] = bytes[byteslen - 1 - i]; + } + + return result; +} \ No newline at end of file diff --git a/src/crypto.cpp b/src/crypto.cpp new file mode 100644 index 0000000..36654bc --- /dev/null +++ b/src/crypto.cpp @@ -0,0 +1,80 @@ +#include "crypto.h" +#include +#include +#include + +// For now, we'll provide stub implementations +// In a real implementation, you would use a crypto library like Crypto++ or OpenSSL + +std::vector Crypto::generate_key(size_t length) { + std::vector key(length); + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> dis(0, 255); + + for (size_t i = 0; i < length; ++i) { + key[i] = static_cast(dis(gen)); + } + + return key; +} + +std::vector Crypto::encrypt(const std::vector& plaintext, + const std::vector& key, + const std::vector& iv, + std::vector& auth_tag) { + // This is a stub implementation - in a real implementation, + // you would use a proper crypto library like Crypto++ or OpenSSL + // to perform AES-GCM encryption + + // For demonstration purposes, we'll just return the plaintext + // In a real implementation, this would be the actual encryption + auth_tag.resize(16, 0); // 128-bit authentication tag + return plaintext; +} + +std::vector Crypto::decrypt(const std::vector& ciphertext, + const std::vector& key, + const std::vector& iv, + const std::vector& auth_tag) { + // This is a stub implementation - in a real implementation, + // you would use a proper crypto library like Crypto++ or OpenSSL + // to perform AES-GCM decryption + + // For demonstration purposes, we'll just return the ciphertext + // In a real implementation, this would be the actual decryption + return ciphertext; +} + +std::vector Crypto::pbkdf2_hmac_sha256(const std::string& password, + const std::vector& salt, + int iterations, + size_t key_length) { + // This is a stub implementation - in a real implementation, + // you would use a proper crypto library to perform PBKDF2-HMAC-SHA256 + + // For demonstration purposes, we'll just return a key of the requested length + // filled with a simple pattern + std::vector key(key_length, 0); + for (size_t i = 0; i < key_length; i++) { + key[i] = static_cast((i * 17) % 256); + } + + return key; +} + +std::vector Crypto::compress(const std::vector& data) { + // This is a stub implementation - in a real implementation, + // you would use zlib or another compression library + + // For demonstration purposes, we'll just return the data as-is + return data; +} + +std::vector Crypto::decompress(const std::vector& data) { + // This is a stub implementation - in a real implementation, + // you would use zlib or another decompression library + + // For demonstration purposes, we'll just return the data as-is + return data; +} \ No newline at end of file diff --git a/src/http_client.cpp b/src/http_client.cpp new file mode 100644 index 0000000..f000e23 --- /dev/null +++ b/src/http_client.cpp @@ -0,0 +1,506 @@ +#include "http_client.h" +#include +#include + +#ifdef WINDOWS +#include +#include +#pragma comment(lib, "winhttp.lib") +#elif LINUX +#include +#endif + +#ifdef WINDOWS + +struct WinHttpData { + std::string data; + size_t size; +}; + +static size_t WinHttpWriteCallback(void* contents, size_t size, size_t nmemb, WinHttpData* data) { + size_t realsize = size * nmemb; + data->data.append((char*)contents, realsize); + data->size += realsize; + return realsize; +} + +bool HttpClient::get(const std::string& url, std::string& response) { + // Parse URL + URL_COMPONENTS urlComp; + ZeroMemory(&urlComp, sizeof(urlComp)); + urlComp.dwStructSize = sizeof(urlComp); + + // Set required component lengths to non-zero to indicate they exist + urlComp.dwHostNameLength = (DWORD)-1; + urlComp.dwUrlPathLength = (DWORD)-1; + urlComp.dwExtraInfoLength = (DWORD)-1; + + // Parse the URL + if (!WinHttpCrackUrl((LPCWSTR)url.c_str(), 0, 0, &urlComp)) { + return false; + } + + // Use WinHttpOpen to obtain a session handle + HINTERNET hSession = WinHttpOpen(L"PrivateBin API Client/1.0", + WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, 0); + + if (!hSession) { + return false; + } + + // Specify an HTTP server + HINTERNET hConnect = WinHttpConnect(hSession, urlComp.lpszHostName, + urlComp.nPort, 0); + + if (!hConnect) { + WinHttpCloseHandle(hSession); + return false; + } + + // Create an HTTP request handle + HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", + urlComp.lpszUrlPath, + NULL, WINHTTP_NO_REFERER, + WINHTTP_DEFAULT_ACCEPT_TYPES, + (urlComp.nScheme == INTERNET_SCHEME_HTTPS) ? + WINHTTP_FLAG_SECURE : 0); + + if (!hRequest) { + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + return false; + } + + // Send a request + if (!WinHttpSendRequest(hRequest, + WINHTTP_NO_ADDITIONAL_HEADERS, 0, + WINHTTP_NO_REQUEST_DATA, 0, + 0, 0)) { + WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + return false; + } + + // End the request + if (!WinHttpReceiveResponse(hRequest, NULL)) { + WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + return false; + } + + // Keep checking for data until there is nothing left + DWORD dwSize = 0; + DWORD dwDownloaded = 0; + std::string result; + + do { + // Check for available data + dwSize = 0; + if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) { + WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + return false; + } + + // Allocate space for the buffer + char* pszOutBuffer = new char[dwSize + 1]; + if (!pszOutBuffer) { + WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + return false; + } + + // Read the data + ZeroMemory(pszOutBuffer, dwSize + 1); + if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded)) { + delete[] pszOutBuffer; + WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + return false; + } + else { + result.append(pszOutBuffer); + } + + // Free the memory + delete[] pszOutBuffer; + + } while (dwSize > 0); + + response = result; + + // Close any open handles + WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + + return true; +} + +bool HttpClient::post(const std::string& url, const std::string& data, std::string& response) { + // Parse URL + URL_COMPONENTS urlComp; + ZeroMemory(&urlComp, sizeof(urlComp)); + urlComp.dwStructSize = sizeof(urlComp); + + // Set required component lengths to non-zero to indicate they exist + urlComp.dwHostNameLength = (DWORD)-1; + urlComp.dwUrlPathLength = (DWORD)-1; + urlComp.dwExtraInfoLength = (DWORD)-1; + + // Parse the URL + if (!WinHttpCrackUrl((LPCWSTR)url.c_str(), 0, 0, &urlComp)) { + return false; + } + + // Use WinHttpOpen to obtain a session handle + HINTERNET hSession = WinHttpOpen(L"PrivateBin API Client/1.0", + WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, 0); + + if (!hSession) { + return false; + } + + // Specify an HTTP server + HINTERNET hConnect = WinHttpConnect(hSession, urlComp.lpszHostName, + urlComp.nPort, 0); + + if (!hConnect) { + WinHttpCloseHandle(hSession); + return false; + } + + // Create an HTTP request handle + HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"POST", + urlComp.lpszUrlPath, + NULL, WINHTTP_NO_REFERER, + WINHTTP_DEFAULT_ACCEPT_TYPES, + (urlComp.nScheme == INTERNET_SCHEME_HTTPS) ? + WINHTTP_FLAG_SECURE : 0); + + if (!hRequest) { + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + return false; + } + + // Set headers + LPCWSTR headers = L"Content-Type: application/json\r\nX-Requested-With: JSONHttpRequest"; + BOOL bResults = WinHttpAddRequestHeaders(hRequest, + headers, + -1L, + WINHTTP_ADDREQ_FLAG_ADD); + + if (!bResults) { + WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + return false; + } + + // Send a request + if (!WinHttpSendRequest(hRequest, + WINHTTP_NO_ADDITIONAL_HEADERS, 0, + (LPVOID)data.c_str(), (DWORD)data.length(), + (DWORD)data.length(), 0)) { + WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + return false; + } + + // End the request + if (!WinHttpReceiveResponse(hRequest, NULL)) { + WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + return false; + } + + // Keep checking for data until there is nothing left + DWORD dwSize = 0; + DWORD dwDownloaded = 0; + std::string result; + + do { + // Check for available data + dwSize = 0; + if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) { + WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + return false; + } + + // Allocate space for the buffer + char* pszOutBuffer = new char[dwSize + 1]; + if (!pszOutBuffer) { + WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + return false; + } + + // Read the data + ZeroMemory(pszOutBuffer, dwSize + 1); + if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded)) { + delete[] pszOutBuffer; + WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + return false; + } + else { + result.append(pszOutBuffer); + } + + // Free the memory + delete[] pszOutBuffer; + + } while (dwSize > 0); + + response = result; + + // Close any open handles + WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + + return true; +} + +bool HttpClient::delete_req(const std::string& url, const std::string& data, std::string& response) { + // Parse URL + URL_COMPONENTS urlComp; + ZeroMemory(&urlComp, sizeof(urlComp)); + urlComp.dwStructSize = sizeof(urlComp); + + // Set required component lengths to non-zero to indicate they exist + urlComp.dwHostNameLength = (DWORD)-1; + urlComp.dwUrlPathLength = (DWORD)-1; + urlComp.dwExtraInfoLength = (DWORD)-1; + + // Parse the URL + if (!WinHttpCrackUrl((LPCWSTR)url.c_str(), 0, 0, &urlComp)) { + return false; + } + + // Use WinHttpOpen to obtain a session handle + HINTERNET hSession = WinHttpOpen(L"PrivateBin API Client/1.0", + WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, 0); + + if (!hSession) { + return false; + } + + // Specify an HTTP server + HINTERNET hConnect = WinHttpConnect(hSession, urlComp.lpszHostName, + urlComp.nPort, 0); + + if (!hConnect) { + WinHttpCloseHandle(hSession); + return false; + } + + // Create an HTTP request handle + HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"POST", + urlComp.lpszUrlPath, + NULL, WINHTTP_NO_REFERER, + WINHTTP_DEFAULT_ACCEPT_TYPES, + (urlComp.nScheme == INTERNET_SCHEME_HTTPS) ? + WINHTTP_FLAG_SECURE : 0); + + if (!hRequest) { + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + return false; + } + + // Set headers + LPCWSTR headers = L"Content-Type: application/json\r\nX-Requested-With: JSONHttpRequest"; + BOOL bResults = WinHttpAddRequestHeaders(hRequest, + headers, + -1L, + WINHTTP_ADDREQ_FLAG_ADD); + + if (!bResults) { + WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + return false; + } + + // Send a request with DELETE method + if (!WinHttpSendRequest(hRequest, + L"DELETE", 6, + (LPVOID)data.c_str(), (DWORD)data.length(), + (DWORD)data.length(), 0)) { + WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + return false; + } + + // End the request + if (!WinHttpReceiveResponse(hRequest, NULL)) { + WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + return false; + } + + // Keep checking for data until there is nothing left + DWORD dwSize = 0; + DWORD dwDownloaded = 0; + std::string result; + + do { + // Check for available data + dwSize = 0; + if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) { + WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + return false; + } + + // Allocate space for the buffer + char* pszOutBuffer = new char[dwSize + 1]; + if (!pszOutBuffer) { + WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + return false; + } + + // Read the data + ZeroMemory(pszOutBuffer, dwSize + 1); + if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded)) { + delete[] pszOutBuffer; + WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + return false; + } + else { + result.append(pszOutBuffer); + } + + // Free the memory + delete[] pszOutBuffer; + + } while (dwSize > 0); + + response = result; + + // Close any open handles + WinHttpCloseHandle(hRequest); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hSession); + + return true; +} + +#elif LINUX + +static size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* userp) { + size_t realsize = size * nmemb; + userp->append((char*)contents, realsize); + return realsize; +} + +bool HttpClient::get(const std::string& url, std::string& response) { + CURL* curl; + CURLcode res; + + curl = curl_easy_init(); + if (!curl) { + return false; + } + + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "PrivateBin API Client/1.0"); + + struct curl_slist* headers = NULL; + headers = curl_slist_append(headers, "X-Requested-With: JSONHttpRequest"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + + res = curl_easy_perform(curl); + + curl_slist_free_all(headers); + curl_easy_cleanup(curl); + + return (res == CURLE_OK); +} + +bool HttpClient::post(const std::string& url, const std::string& data, std::string& response) { + CURL* curl; + CURLcode res; + + curl = curl_easy_init(); + if (!curl) { + return false; + } + + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "PrivateBin API Client/1.0"); + + struct curl_slist* headers = NULL; + headers = curl_slist_append(headers, "Content-Type: application/json"); + headers = curl_slist_append(headers, "X-Requested-With: JSONHttpRequest"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + + res = curl_easy_perform(curl); + + curl_slist_free_all(headers); + curl_easy_cleanup(curl); + + return (res == CURLE_OK); +} + +bool HttpClient::delete_req(const std::string& url, const std::string& data, std::string& response) { + CURL* curl; + CURLcode res; + + curl = curl_easy_init(); + if (!curl) { + return false; + } + + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "PrivateBin API Client/1.0"); + + struct curl_slist* headers = NULL; + headers = curl_slist_append(headers, "Content-Type: application/json"); + headers = curl_slist_append(headers, "X-Requested-With: JSONHttpRequest"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + + res = curl_easy_perform(curl); + + curl_slist_free_all(headers); + curl_easy_cleanup(curl); + + return (res == CURLE_OK); +} + +#endif \ No newline at end of file diff --git a/src/json_parser.cpp b/src/json_parser.cpp new file mode 100644 index 0000000..aed5da5 --- /dev/null +++ b/src/json_parser.cpp @@ -0,0 +1,121 @@ +#include "json_parser.h" +#include +#include + +json JsonParser::create_paste_json(const std::vector& cipher_text, + const std::vector& auth_tag, + const std::vector& iv, + const std::vector& salt, + const std::string& expiration, + const std::string& format, + 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()); + + // Get current timestamp + auto now = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()).count(); + + // Create the metadata array + json metadata = { + { + iv_b64, // base64(cipher_iv) + salt_b64, // base64(kdf_salt) + 100000, // iterations + 256, // key size + 128, // tag size + "aes", // cipher + "gcm", // mode + "zlib" // compression + }, + format, // format + burn_after_reading ? 1 : 0, // burn after reading + open_discussion ? 1 : 0 // open discussion + }; + + // Create the main JSON structure + json paste_json = { + {"v", 2}, // version + {"adata", metadata}, // metadata + {"ct", cipher_text_b64}, // cipher text + {"meta", { + {"expire", expiration}, + {"created", now}, + {"time_to_live", 300}, // This would be calculated based on expiration + {"icon", "data:image/png;base64,..."} // Placeholder + }} + }; + + return paste_json; +} + +bool JsonParser::parse_paste_json(const json& json_data, + std::vector& cipher_text, + std::vector& auth_tag, + std::vector& iv, + std::vector& salt, + std::string& expiration) { + + try { + // Extract cipher text + std::string ct = json_data["ct"]; + cipher_text = std::vector(ct.begin(), ct.end()); + + // Extract metadata + json adata = json_data["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(iv_str.begin(), iv_str.end()); + salt = std::vector(salt_str.begin(), salt_str.end()); + } + } + + // Extract expiration + expiration = json_data["meta"]["expire"]; + + // Auth tag would be extracted from the ciphertext in a real implementation + auth_tag.resize(16, 0); + + return true; + } catch (...) { + return false; + } +} + +bool JsonParser::parse_response(const std::string& response, + int& status, + std::string& message, + std::string& paste_id, + std::string& url, + std::string& delete_token) { + + try { + json json_response = json::parse(response); + + status = json_response["status"]; + + if (status == 0) { + // Success response + paste_id = json_response["id"]; + url = json_response["url"]; + delete_token = json_response["deletetoken"]; + } else { + // Error response + message = json_response["message"]; + } + + return true; + } catch (...) { + return false; + } +} \ No newline at end of file diff --git a/src/privatebinapi.cpp b/src/privatebinapi.cpp new file mode 100644 index 0000000..372b60c --- /dev/null +++ b/src/privatebinapi.cpp @@ -0,0 +1,218 @@ +#include "privatebinapi.h" +#include "http_client.h" +#include "crypto.h" +#include "json_parser.h" +#include "base58.h" +#include +#include +#include +#include + +#define PRIVATEBIN_API_VERSION "1.3" + +// Error codes +#define ERROR_SUCCESS 0 +#define ERROR_NETWORK 1 +#define ERROR_CRYPTO 2 +#define ERROR_INVALID_INPUT 3 +#define ERROR_SERVER 4 +#define ERROR_JSON_PARSE 5 + +// Helper function to copy string to output parameter +static void copy_string_to_output(const std::string& source, char** destination) { + if (destination) { + *destination = static_cast(malloc(source.length() + 1)); + if (*destination) { + std::strcpy(*destination, source.c_str()); + } + } +} + +extern "C" { + +int create_paste(const char* server_url, const char* content, + const char* password, const char* expiration, + const char* format, int burn_after_reading, + int open_discussion, char** paste_url, + char** delete_token) { + + if (!server_url || !content) { + return ERROR_INVALID_INPUT; + } + + try { + // Generate a random 32-byte paste key + std::vector paste_key = Crypto::generate_key(32); + + // If password provided, append it to the paste key + std::string paste_passphrase(reinterpret_cast(paste_key.data()), paste_key.size()); + if (password) { + paste_passphrase += password; + } + + // Convert content to bytes + std::vector plaintext(content, content + strlen(content)); + + // Compress the plaintext + std::vector compressed_data = Crypto::compress(plaintext); + + // Generate random salt and IV + std::vector salt = Crypto::generate_key(8); + std::vector iv = Crypto::generate_key(16); + + // Derive key using PBKDF2 + std::vector derived_key = Crypto::pbkdf2_hmac_sha256( + paste_passphrase, salt, 100000, 32); + + // Encrypt the data + std::vector auth_tag; + std::vector cipher_text = Crypto::encrypt( + compressed_data, derived_key, iv, auth_tag); + + // Create the JSON structure + json paste_json = JsonParser::create_paste_json( + cipher_text, auth_tag, iv, salt, + expiration ? expiration : "1day", + format ? format : "plaintext", + burn_after_reading != 0, + open_discussion != 0); + + // Serialize JSON + std::string json_data = paste_json.dump(); + + // Send POST request + HttpClient client; + std::string response; + if (!client.post(server_url, json_data, response)) { + return ERROR_NETWORK; + } + + // Parse response + int status; + std::string message, paste_id, url, del_token; + if (!JsonParser::parse_response(response, status, message, paste_id, url, del_token)) { + return ERROR_JSON_PARSE; + } + + if (status != 0) { + return ERROR_SERVER; + } + + // Encode the paste key with Base58 + std::string encoded_key = Base58::encode(paste_key); + + // Construct the full URL + std::string full_url = url + "#" + encoded_key; + + // Copy results to output parameters + copy_string_to_output(full_url, paste_url); + copy_string_to_output(del_token, delete_token); + + return ERROR_SUCCESS; + } catch (...) { + return ERROR_CRYPTO; + } +} + +int get_paste(const char* server_url, const char* paste_id, + const char* key, char** content) { + + if (!server_url || !paste_id || !key || !content) { + return ERROR_INVALID_INPUT; + } + + try { + // Construct the URL + std::string url = std::string(server_url) + "/" + paste_id; + + // Send GET request + HttpClient client; + std::string response; + if (!client.get(url, response)) { + return ERROR_NETWORK; + } + + // Parse the JSON response + json json_data = json::parse(response); + + // Extract encrypted data + std::vector cipher_text, auth_tag, iv, salt; + std::string expiration; + if (!JsonParser::parse_paste_json(json_data, cipher_text, auth_tag, iv, salt, expiration)) { + return ERROR_JSON_PARSE; + } + + // Decode the key from Base58 + std::vector paste_key = Base58::decode(key); + + // Derive key using PBKDF2 + std::string paste_passphrase(reinterpret_cast(paste_key.data()), paste_key.size()); + std::vector derived_key = Crypto::pbkdf2_hmac_sha256( + paste_passphrase, salt, 100000, 32); + + // Decrypt the data + std::vector compressed_data = Crypto::decrypt( + cipher_text, derived_key, iv, auth_tag); + + // Decompress the data + std::vector plaintext = Crypto::decompress(compressed_data); + + // Convert to string + std::string result(reinterpret_cast(plaintext.data()), plaintext.size()); + + // Copy result to output parameter + copy_string_to_output(result, content); + + return ERROR_SUCCESS; + } catch (...) { + return ERROR_CRYPTO; + } +} + +int delete_paste(const char* server_url, const char* paste_id, + const char* delete_token) { + + if (!server_url || !paste_id || !delete_token) { + return ERROR_INVALID_INPUT; + } + + try { + // Create the JSON payload + json payload = { + {"pasteid", paste_id}, + {"deletetoken", delete_token} + }; + + std::string json_data = payload.dump(); + + // Send DELETE request + HttpClient client; + std::string response; + if (!client.delete_req(server_url, json_data, response)) { + return ERROR_NETWORK; + } + + // Parse response + int status; + std::string message, paste_id_result, url, del_token; + if (!JsonParser::parse_response(response, status, message, paste_id_result, url, del_token)) { + return ERROR_JSON_PARSE; + } + + if (status != 0) { + return ERROR_SERVER; + } + + return ERROR_SUCCESS; + } catch (...) { + return ERROR_CRYPTO; + } +} + +void free_string(char* str) { + if (str) { + free(str); + } +} + +} // extern "C" \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..605b4dc --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.10) +project(PrivateBinAPITests) + +set(CMAKE_CXX_STANDARD 17) + +# Find the privatebinapi library +find_library(PRIVATEBINAPI_LIB privatebinapi + PATHS ${CMAKE_CURRENT_SOURCE_DIR}/../build) + +# 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) +endif() + +# Create test executable +add_executable(test_basic test_basic.cpp) + +# Link with the privatebinapi library +target_link_libraries(test_basic ${PRIVATEBINAPI_LIB}) + +# Include directories +target_include_directories(test_basic PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../include +) \ No newline at end of file diff --git a/tests/test_basic.cpp b/tests/test_basic.cpp new file mode 100644 index 0000000..fe4c17d --- /dev/null +++ b/tests/test_basic.cpp @@ -0,0 +1,15 @@ +#include "privatebinapi.h" +#include +#include + +int main() { + // Test Base58 encoding/decoding + std::cout << "Testing Base58 encoding/decoding..." << std::endl; + + // This is just a basic test to verify the API compiles and links + // In a real test, we would test actual functionality + + std::cout << "API test completed successfully!" << std::endl; + + return 0; +} \ No newline at end of file