test: Add 8 and update 14 files
Some checks failed
Build-Test-Publish / build (linux/amd64) (push) Failing after 22s
Build-Test-Publish / build (linux/arm64) (push) Failing after 32s
Build-Test-Publish / create-manifest (push) Has been skipped

This commit is contained in:
Your Name
2025-08-10 21:18:40 +12:00
parent 1fed086348
commit 8ab6028597
22 changed files with 1392 additions and 81 deletions

187
src/bcrypt.hpp Normal file
View File

@@ -0,0 +1,187 @@
// Simplified BCrypt implementation for token hashing
// Based on OpenBSD's bcrypt with modifications for C++ header-only use
// This implementation uses SHA-256 as the core hashing function with salt and multiple rounds
#ifndef BCRYPT_HPP
#define BCRYPT_HPP
#include <string>
#include <vector>
#include <random>
#include <sstream>
#include <iomanip>
#include <cstring>
#include <openssl/sha.h>
#include <openssl/rand.h>
namespace simple_object_storage {
class BCrypt {
public:
// Default cost factor (number of rounds = 2^cost)
static constexpr int DEFAULT_COST = 12;
static constexpr int MIN_COST = 4;
static constexpr int MAX_COST = 31;
static constexpr size_t SALT_LENGTH = 16; // 128 bits
static constexpr size_t HASH_LENGTH = 32; // 256 bits (SHA-256)
// Generate a hash from a password/token
static std::string hashPassword(const std::string& password, int cost = DEFAULT_COST) {
if (cost < MIN_COST || cost > MAX_COST) {
cost = DEFAULT_COST;
}
// Generate random salt
std::vector<unsigned char> salt(SALT_LENGTH);
if (RAND_bytes(salt.data(), SALT_LENGTH) != 1) {
throw std::runtime_error("Failed to generate random salt");
}
// Perform the hashing
std::vector<unsigned char> hash = hashWithSalt(password, salt, cost);
// Format: $2b$<cost>$<salt><hash>
// We use a simplified format for header-only implementation
return formatHash(cost, salt, hash);
}
// Verify a password against a hash
static bool verifyPassword(const std::string& password, const std::string& hash) {
// Parse the hash to extract cost, salt, and expected hash
int cost;
std::vector<unsigned char> salt;
std::vector<unsigned char> expectedHash;
if (!parseHash(hash, cost, salt, expectedHash)) {
return false;
}
// Hash the password with the extracted salt and cost
std::vector<unsigned char> computedHash = hashWithSalt(password, salt, cost);
// Constant-time comparison to prevent timing attacks
return constantTimeCompare(computedHash, expectedHash);
}
private:
// Perform the actual hashing with salt and cost
static std::vector<unsigned char> hashWithSalt(const std::string& password,
const std::vector<unsigned char>& salt,
int cost) {
// Number of iterations = 2^cost
size_t rounds = 1ULL << cost;
// Initial hash: SHA256(salt + password)
std::vector<unsigned char> data;
data.insert(data.end(), salt.begin(), salt.end());
data.insert(data.end(), password.begin(), password.end());
std::vector<unsigned char> hash(SHA256_DIGEST_LENGTH);
SHA256(data.data(), data.size(), hash.data());
// Iterate to increase computational cost
for (size_t i = 0; i < rounds; ++i) {
// SHA256(hash + salt + password)
data.clear();
data.insert(data.end(), hash.begin(), hash.end());
data.insert(data.end(), salt.begin(), salt.end());
data.insert(data.end(), password.begin(), password.end());
SHA256(data.data(), data.size(), hash.data());
}
return hash;
}
// Format hash for storage
static std::string formatHash(int cost, const std::vector<unsigned char>& salt,
const std::vector<unsigned char>& hash) {
std::stringstream ss;
ss << "$2b$" << std::setfill('0') << std::setw(2) << cost << "$";
// Encode salt as hex
for (unsigned char byte : salt) {
ss << std::hex << std::setw(2) << static_cast<int>(byte);
}
// Encode hash as hex
for (unsigned char byte : hash) {
ss << std::hex << std::setw(2) << static_cast<int>(byte);
}
return ss.str();
}
// Parse a formatted hash
static bool parseHash(const std::string& hashStr, int& cost,
std::vector<unsigned char>& salt,
std::vector<unsigned char>& hash) {
// Expected format: $2b$<cost>$<salt><hash>
if (hashStr.size() < 10 || hashStr.substr(0, 4) != "$2b$") {
return false;
}
// Find the second $
size_t secondDollar = hashStr.find('$', 4);
if (secondDollar == std::string::npos) {
return false;
}
// Parse cost
try {
cost = std::stoi(hashStr.substr(4, secondDollar - 4));
} catch (...) {
return false;
}
// Parse salt and hash (both in hex)
std::string hexData = hashStr.substr(secondDollar + 1);
if (hexData.size() != (SALT_LENGTH + HASH_LENGTH) * 2) {
return false;
}
// Decode salt
salt.clear();
for (size_t i = 0; i < SALT_LENGTH * 2; i += 2) {
try {
unsigned char byte = static_cast<unsigned char>(
std::stoi(hexData.substr(i, 2), nullptr, 16));
salt.push_back(byte);
} catch (...) {
return false;
}
}
// Decode hash
hash.clear();
for (size_t i = SALT_LENGTH * 2; i < hexData.size(); i += 2) {
try {
unsigned char byte = static_cast<unsigned char>(
std::stoi(hexData.substr(i, 2), nullptr, 16));
hash.push_back(byte);
} catch (...) {
return false;
}
}
return true;
}
// Constant-time comparison to prevent timing attacks
static bool constantTimeCompare(const std::vector<unsigned char>& a,
const std::vector<unsigned char>& b) {
if (a.size() != b.size()) {
return false;
}
unsigned char result = 0;
for (size_t i = 0; i < a.size(); ++i) {
result |= a[i] ^ b[i];
}
return result == 0;
}
};
} // namespace simple_object_storage
#endif // BCRYPT_HPP

156
src/hash_token.cpp Normal file
View File

@@ -0,0 +1,156 @@
// Utility to generate bcrypt hashes from tokens
#include <iostream>
#include <string>
#include <cstdlib>
#include <termios.h>
#include <unistd.h>
#include "bcrypt.hpp"
using namespace simple_object_storage;
// Function to read password without echoing to terminal
std::string readPasswordFromStdin() {
struct termios oldt, newt;
tcgetattr(STDIN_FILENO, &oldt);
newt = oldt;
newt.c_lflag &= ~ECHO;
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
std::string password;
std::getline(std::cin, password);
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
std::cout << std::endl;
return password;
}
int main(int argc, char* argv[]) {
std::string token;
int cost = BCrypt::DEFAULT_COST;
bool verify_mode = false;
bool quiet_mode = false;
// Parse command line arguments
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
if (arg == "--help" || arg == "-h") {
std::cout << "Usage: " << argv[0] << " [OPTIONS] [TOKEN]\n"
<< "\nGenerate bcrypt hashes for authentication tokens.\n"
<< "\nOptions:\n"
<< " -h, --help Show this help message\n"
<< " -c, --cost N Set bcrypt cost factor (4-31, default: " << BCrypt::DEFAULT_COST << ")\n"
<< " -v, --verify Verify a token against a hash\n"
<< " -q, --quiet Quiet mode (only output hash)\n"
<< " -g, --generate Generate a random token and hash it\n"
<< "\nExamples:\n"
<< " " << argv[0] << " mytoken123 # Hash a token\n"
<< " " << argv[0] << " -c 14 mytoken123 # Hash with cost 14\n"
<< " " << argv[0] << " # Read token from stdin\n"
<< " " << argv[0] << " -v # Verify token against hash\n"
<< " " << argv[0] << " -g # Generate random token\n";
return 0;
} else if (arg == "--cost" || arg == "-c") {
if (i + 1 < argc) {
cost = std::atoi(argv[++i]);
if (cost < BCrypt::MIN_COST || cost > BCrypt::MAX_COST) {
std::cerr << "Error: Cost must be between " << BCrypt::MIN_COST
<< " and " << BCrypt::MAX_COST << std::endl;
return 1;
}
} else {
std::cerr << "Error: --cost requires an argument\n";
return 1;
}
} else if (arg == "--verify" || arg == "-v") {
verify_mode = true;
} else if (arg == "--quiet" || arg == "-q") {
quiet_mode = true;
} else if (arg == "--generate" || arg == "-g") {
// Generate a random token
const char* charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
const size_t charset_size = 62;
const size_t token_length = 32;
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(0, charset_size - 1);
token = "";
for (size_t i = 0; i < token_length; ++i) {
token += charset[dis(gen)];
}
if (!quiet_mode) {
std::cout << "Generated token: " << token << std::endl;
}
} else if (!arg.empty() && arg[0] != '-') {
token = arg;
} else {
std::cerr << "Error: Unknown option: " << arg << "\n";
return 1;
}
}
if (verify_mode) {
// Verification mode
if (!quiet_mode) {
std::cout << "Enter token to verify: ";
}
std::string plaintext = readPasswordFromStdin();
if (!quiet_mode) {
std::cout << "Enter hash to verify against: ";
}
std::string hash;
std::getline(std::cin, hash);
try {
bool valid = BCrypt::verifyPassword(plaintext, hash);
if (!quiet_mode) {
std::cout << "Verification result: ";
}
std::cout << (valid ? "VALID" : "INVALID") << std::endl;
return valid ? 0 : 1;
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
} else {
// Hash generation mode
if (token.empty()) {
if (!quiet_mode) {
std::cout << "Enter token to hash: ";
}
token = readPasswordFromStdin();
}
if (token.empty()) {
std::cerr << "Error: Token cannot be empty\n";
return 1;
}
try {
std::string hash = BCrypt::hashPassword(token, cost);
if (!quiet_mode) {
std::cout << "Bcrypt hash (cost=" << cost << "):\n";
}
std::cout << hash << std::endl;
// Verify the hash works
if (!quiet_mode) {
bool verified = BCrypt::verifyPassword(token, hash);
if (!verified) {
std::cerr << "Warning: Generated hash failed verification!\n";
return 1;
}
std::cout << "Hash verified successfully.\n";
}
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
}
return 0;
}

View File

@@ -19,6 +19,7 @@
#include "welcome_page.hpp"
#include "rate_limiter.hpp"
#include "HttpController.hpp"
#include "bcrypt.hpp" // For secure token hashing
namespace simple_object_storage {
@@ -67,7 +68,16 @@ bool Server::validate_write_request(const drogon::HttpRequestPtr &req, drogon::H
return false;
}
bool write_token_valid = std::find(config_.write_tokens.begin(), config_.write_tokens.end(), token) != config_.write_tokens.end();
// Check if token is valid by comparing against stored bcrypt hashes
bool write_token_valid = false;
for (const auto& stored_hash : config_.write_tokens) {
// Verify the token against the stored bcrypt hash
if (BCrypt::verifyPassword(token, stored_hash)) {
write_token_valid = true;
break;
}
}
if (!write_token_valid) {
// Only count failed attempt (increment the limiter)
auth_rate_limiter_->is_allowed(client_ip); // This will increment the count