RSA Digital Signature Tool (cpp, written by Codex)
envgap__codex__cpp-t1-12
Written by a coding agent; not on GitHubWritten 2026-03-02
01 / FAILURE SIGNATURE
As the study recorded it
mbedtls ZIP missing submodule + API mismatch (nullptr args + missing set_padding)
Not a benchmark task.
- Its repair changed source code, so it is not an environment task.
02 / ENVIRONMENT RECIPE
- Base commit
Not freshly verified- Manifest
CMakeLists.txt- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / TASK AND FAILURE
codex/cpp-t1 #12 · read the task the agent was given
Codex wrote this cpp project from the task below. It does not run on a clean Ubuntu 22.04 machine as written. Task given to the agent: TASK: RSA Digital Signature Tool Write a program that generates RSA key pairs and uses them to create and verify digital signatures on files, ensuring document authenticity and integrity. FUNCTIONAL REQUIREMENTS: - Support three subcommands: keygen, sign, and verify - keygen: Generate an RSA key pair (2048 or 4096 bit, selectable via --bits flag, default 2048) and save the private key and public key as separate PEM files - sign: Accept a file path and private key path, compute a SHA-256 hash of the file, sign it with RSA-PSS padding, and save the signature as a separate .sig file - verify: Accept a file path, signature file path, and public key path, then verify the signature and print whether it is valid or invalid with details - Support signing multiple files at once by accepting a directory path via --batch flag - Support a --output flag to specify where to save generated keys or signatures - Display key information: key size, fingerprint (SHA-256 hash of public key), and creation timestamp - Support exporting the public key in both PEM and DER formats via --format flag - Print detailed results to console: for signing show the file hash and signature size, for verification show match/mismatch status and key details - If no arguments are given, generate a sample key pair, create a sample text file, sign it, verify the signature, then tamper with the file and show that verification fails - Handle errors gracefully: invalid key format, mismatched key/signature, corrupted files, unsupported key sizes Create a complete C++ project for a clean Ubuntu 22.04 machine with only G++ 12+ and CMake 3.22+ installed. Include: - Source code - CMakeLists.txt with all dependencies (direct and transitive) pinned to exact versions - README.md with setup instructions, dependency explanations, build steps, run commands, and expected output
04 / LABELS
Labels from the report text only; not yet run
No supported category has been assigned.
Label rules and the text that matched
[]
05 / FILES
The project as the agent wrote it
3 files, exactly as written, before any repair.
CMakeLists.txt
cmake_minimum_required(VERSION 3.22)
project(rsa_digital_signature_tool_cpp VERSION 1.0.0 LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
include(FetchContent)
# Pinned dependency
set(ENABLE_TESTING OFF CACHE BOOL "" FORCE)
set(ENABLE_PROGRAMS OFF CACHE BOOL "" FORCE)
set(ENABLE_EXAMPLES OFF CACHE BOOL "" FORCE)
FetchContent_Declare(
mbedtls
URL https://github.com/Mbed-TLS/mbedtls/archive/refs/tags/v3.6.0.zip
)
FetchContent_MakeAvailable(mbedtls)
add_executable(rsa_tool src/main.cpp)
target_link_libraries(rsa_tool PRIVATE mbedtls mbedx509 mbedcrypto)
target_include_directories(rsa_tool PRIVATE ${mbedtls_SOURCE_DIR}/include)
README.md
# RSA Digital Signature Tool (C++) Implements `keygen`, `sign`, and `verify` subcommands using RSA-PSS with SHA-256. ## Requirements - Ubuntu 22.04 - G++ 12+ - CMake 3.22+ ## Dependencies - Direct: - `mbedTLS v3.6.0` (pinned in `CMakeLists.txt` via `FetchContent`) - Transitive: - `mbedcrypto` and `mbedx509` (built from the same pinned mbedTLS source) ## Build ```bash cmake -S . -B build cmake --build build --config Release ``` ## Usage ```bash ./build/rsa_tool keygen [--bits 2048|4096] [--output dir] [--format pem|der|both] ./build/rsa_tool sign <file-or-dir> <private-key.pem> [--batch] [--output path] ./build/rsa_tool verify <file> <signature.sig> <public-key.pem|public-key.der> ``` Examples: ```bash ./build/rsa_tool keygen --bits 4096 --output ./keys --format both ./build/rsa_tool sign ./document.txt ./keys/private_key.pem ./build/rsa_tool verify ./document.txt ./document.txt.sig ./keys/public_key.pem ./build/rsa_tool sign ./docs ./keys/private_key.pem --batch --output ./sigs ``` No arguments runs an end-to-end demo: 1. Generate sample keypair 2. Sign a sample file 3. Verify signature (valid) 4. Tamper file 5. Verify signature again (invalid)
src/main.cpp
#include <algorithm>
#include <chrono>
#include <cstring>
#include <ctime>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <map>
#include <optional>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/entropy.h>
#include <mbedtls/error.h>
#include <mbedtls/pk.h>
#include <mbedtls/rsa.h>
#include <mbedtls/sha256.h>
namespace {
struct ParsedArgs {
std::map<std::string, std::string> options;
std::vector<std::string> positional;
};
ParsedArgs parseArgs(int argc, char** argv) {
ParsedArgs parsed;
for (int i = 1; i < argc; i++) {
std::string token = argv[i];
if (token.rfind("--", 0) == 0) {
std::string key = token.substr(2);
if (i + 1 < argc && std::string(argv[i + 1]).rfind("--", 0) != 0) parsed.options[key] = argv[++i];
else parsed.options[key] = "true";
} else {
parsed.positional.push_back(token);
}
}
return parsed;
}
[[noreturn]] void throwMbed(int code, const std::string& where) {
char buffer[256];
mbedtls_strerror(code, buffer, sizeof(buffer));
throw std::runtime_error(where + ": " + std::string(buffer));
}
struct Rng {
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctrDrbg;
Rng() {
mbedtls_entropy_init(&entropy);
mbedtls_ctr_drbg_init(&ctrDrbg);
const char* pers = "rsa-digital-signature-tool";
int rc = mbedtls_ctr_drbg_seed(
&ctrDrbg,
mbedtls_entropy_func,
&entropy,
reinterpret_cast<const unsigned char*>(pers),
std::strlen(pers)
);
if (rc != 0) throwMbed(rc, "mbedtls_ctr_drbg_seed");
}
~Rng() {
mbedtls_ctr_drbg_free(&ctrDrbg);
mbedtls_entropy_free(&entropy);
}
};
std::string nowIsoUtc() {
const auto now = std::chrono::system_clock::now();
const std::time_t t = std::chrono::system_clock::to_time_t(now);
std::tm tmUtc {};
#if defined(_WIN32)
gmtime_s(&tmUtc, &t);
#else
gmtime_r(&t, &tmUtc);
#endif
std::ostringstream out;
out << std::put_time(&tmUtc, "%Y-%m-%dT%H:%M:%SZ");
return out.str();
}
std::vector<unsigned char> readBinary(const std::filesystem::path& path) {
std::ifstream in(path, std::ios::binary);
if (!in.is_open()) throw std::runtime_error("Failed to open file: " + path.string());
return std::vector<unsigned char>(std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>());
}
void writeBinary(const std::filesystem::path& path, const std::vector<unsigned char>& bytes) {
if (!path.parent_path().empty()) std::filesystem::create_directories(path.parent_path());
std::ofstream out(path, std::ios::binary);
if (!out.is_open()) throw std::runtime_error("Failed to write file: " + path.string());
out.write(reinterpret_cast<const char*>(bytes.data()), static_cast<std::streamsize>(bytes.size()));
}
void writeText(const std::filesystem::path& path, const std::string& content) {
if (!path.parent_path().empty()) std::filesystem::create_directories(path.parent_path());
std::ofstream out(path, std::ios::binary);
if (!out.is_open()) throw std::runtime_error("Failed to write file: " + path.string());
out << content;
}
std::string toHex(const std::vector<unsigned char>& bytes) {
std::ostringstream out;
for (unsigned char b : bytes) {
out << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(b);
}
return out.str();
}
std::vector<unsigned char> sha256(const std::vector<unsigned char>& bytes) {
std::vector<unsigned char> digest(32);
mbedtls_sha256(bytes.data(), bytes.size(), digest.data(), 0);
return digest;
}
std::vector<unsigned char> exportPublicDer(mbedtls_pk_context* pk) {
std::vector<unsigned char> buffer(8192);
int written = mbedtls_pk_write_pubkey_der(pk, buffer.data(), buffer.size());
if (written < 0) throwMbed(written, "mbedtls_pk_write_pubkey_der");
return std::vector<unsigned char>(buffer.end() - written, buffer.end());
}
std::vector<std::filesystem::path> listFilesRecursive(const std::filesystem::path& root) {
std::vector<std::filesystem::path> files;
for (const auto& entry : std::filesystem::recursive_directory_iterator(root)) {
if (entry.is_regular_file()) files.push_back(entry.path());
}
std::sort(files.begin(), files.end());
return files;
}
void keygen(int bits, const std::filesystem::path& outDir, const std::string& format, Rng& rng) {
if (bits != 2048 && bits != 4096) throw std::runtime_error("Unsupported key size. Use 2048 or 4096.");
if (format != "pem" && format != "der" && format != "both") throw std::runtime_error("Unsupported --format. Use pem|der|both.");
std::filesystem::create_directories(outDir);
mbedtls_pk_context pk;
mbedtls_pk_init(&pk);
int rc = mbedtls_pk_setup(&pk, mbedtls_pk_info_from_type(MBEDTLS_PK_RSA));
if (rc != 0) {
mbedtls_pk_free(&pk);
throwMbed(rc, "mbedtls_pk_setup");
}
rc = mbedtls_rsa_gen_key(mbedtls_pk_rsa(pk), mbedtls_ctr_drbg_random, &rng.ctrDrbg, bits, 65537);
if (rc != 0) {
mbedtls_pk_free(&pk);
throwMbed(rc, "mbedtls_rsa_gen_key");
}
std::vector<unsigned char> privatePem(16384);
rc = mbedtls_pk_write_key_pem(&pk, privatePem.data(), privatePem.size());
if (rc != 0) {
mbedtls_pk_free(&pk);
throwMbed(rc, "mbedtls_pk_write_key_pem");
}
std::vector<unsigned char> publicPem(8192);
rc = mbedtls_pk_write_pubkey_pem(&pk, publicPem.data(), publicPem.size());
if (rc != 0) {
mbedtls_pk_free(&pk);
throwMbed(rc, "mbedtls_pk_write_pubkey_pem");
}
const auto publicDer = exportPublicDer(&pk);
const auto fingerprint = toHex(sha256(publicDer));
const auto privatePath = outDir / "private_key.pem";
const auto publicPemPath = outDir / "public_key.pem";
const auto publicDerPath = outDir / "public_key.der";
writeText(privatePath, reinterpret_cast<const char*>(privatePem.data()));
if (format == "pem" || format == "both") writeText(publicPemPath, reinterpret_cast<const char*>(publicPem.data()));
if (format == "der" || format == "both") writeBinary(publicDerPath, publicDer);
std::cout << "Key generation complete\n";
std::cout << " Private key : " << privatePath << "\n";
if (format == "pem" || format == "both") std::cout << " Public PEM : " << publicPemPath << "\n";
if (format == "der" || format == "both") std::cout << " Public DER : " << publicDerPath << "\n";
std::cout << " Key size : " << bits << "\n";
std::cout << " Fingerprint : " << fingerprint << "\n";
std::cout << " Created at : " << nowIsoUtc() << "\n";
mbedtls_pk_free(&pk);
}
mbedtls_pk_context loadPrivateKey(const std::filesystem::path& path, Rng& rng) {
mbedtls_pk_context pk;
mbedtls_pk_init(&pk);
int rc = mbedtls_pk_parse_keyfile(&pk, path.string().c_str(), nullptr, mbedtls_ctr_drbg_random, &rng.ctrDrbg);
if (rc != 0) {
mbedtls_pk_free(&pk);
throwMbed(rc, "mbedtls_pk_parse_keyfile");
}
if (!mbedtls_pk_can_do(&pk, MBEDTLS_PK_RSA)) {
mbedtls_pk_free(&pk);
throw std::runtime_error("Private key is not RSA.");
}
return pk;
}
mbedtls_pk_context loadPublicKey(const std::filesystem::path& path) {
mbedtls_pk_context pk;
mbedtls_pk_init(&pk);
int rc = mbedtls_pk_parse_public_keyfile(&pk, path.string().c_str());
if (rc != 0) {
mbedtls_pk_free(&pk);
throwMbed(rc, "mbedtls_pk_parse_public_keyfile");
}
if (!mbedtls_pk_can_do(&pk, MBEDTLS_PK_RSA)) {
mbedtls_pk_free(&pk);
throw std::runtime_error("Public key is not RSA.");
}
return pk;
}
std::vector<unsigned char> signDataPssSha256(
const std::vector<unsigned char>& data,
mbedtls_pk_context* privateKey,
Rng& rng
) {
auto digest = sha256(data);
mbedtls_rsa_context* rsa = mbedtls_pk_rsa(*privateKey);
std::vector<unsigned char> sig(mbedtls_rsa_get_len(rsa));
int rc = mbedtls_rsa_rsassa_pss_sign(
rsa,
mbedtls_ctr_drbg_random,
&rng.ctrDrbg,
MBEDTLS_MD_SHA256,
static_cast<unsigned int>(digest.size()),
digest.data(),
sig.data()
);
if (rc != 0) throwMbed(rc, "mbedtls_rsa_rsassa_pss_sign");
return sig;
}
bool verifyDataPssSha256(
const std::vector<unsigned char>& data,
const std::vector<unsigned char>& signature,
mbedtls_pk_context* publicKey
) {
auto digest = sha256(data);
mbedtls_rsa_context* rsa = mbedtls_pk_rsa(*publicKey);
int rc = mbedtls_rsa_rsassa_pss_verify(
rsa,
nullptr,
nullptr,
MBEDTLS_MD_SHA256,
static_cast<unsigned int>(digest.size()),
digest.data(),
signature.data()
);
return rc == 0;
}
void signOneFile(
const std::filesystem::path& filePath,
mbedtls_pk_context* privateKey,
Rng& rng,
const std::filesystem::path& outSignaturePath
) {
const auto bytes = readBinary(filePath);
const auto digest = sha256(bytes);
const auto signature = signDataPssSha256(bytes, privateKey, rng);
writeBinary(outSignaturePath, signature);
std::cout << "Signed: " << filePath << "\n";
std::cout << " SHA-256 hash : " << toHex(digest) << "\n";
std::cout << " Signature size: " << signature.size() << " bytes\n";
std::cout << " Signature file: " << outSignaturePath << "\n";
}
bool verifyOneFile(
const std::filesystem::path& filePath,
const std::filesystem::path& signaturePath,
mbedtls_pk_context* publicKey
) {
const auto bytes = readBinary(filePath);
const auto sig = readBinary(signaturePath);
const auto digest = sha256(bytes);
const bool valid = verifyDataPssSha256(bytes, sig, publicKey);
const auto fingerprint = toHex(sha256(exportPublicDer(publicKey)));
const auto keyBits = mbedtls_pk_get_bitlen(publicKey);
std::cout << "Verify: " << filePath << "\n";
std::cout << " SHA-256 hash : " << toHex(digest) << "\n";
std::cout << " Signature file: " << signaturePath << "\n";
std::cout << " Status : " << (valid ? "VALID" : "INVALID") << "\n";
std::cout << " Key size : " << keyBits << "\n";
std::cout << " Fingerprint : " << fingerprint << "\n";
return valid;
}
void signCommand(const ParsedArgs& args, Rng& rng) {
if (args.positional.size() < 3) throw std::runtime_error("Usage: sign <file-or-dir> <private-key-path> [--batch] [--output path]");
const std::filesystem::path inputPath = std::filesystem::absolute(args.positional[1]);
const std::filesystem::path privateKeyPath = std::filesystem::absolute(args.positional[2]);
const bool batch = args.options.count("batch") > 0;
const std::optional<std::filesystem::path> output =
args.options.count("output") ? std::optional<std::filesystem::path>(std::filesystem::absolute(args.options.at("output"))) : std::nullopt;
mbedtls_pk_context privateKey = loadPrivateKey(privateKeyPath, rng);
if (batch) {
if (!std::filesystem::is_directory(inputPath)) {
mbedtls_pk_free(&privateKey);
throw std::runtime_error("--batch requires a directory input path.");
}
const std::filesystem::path outDir = output.has_value() ? *output : std::filesystem::absolute("signatures");
for (const auto& file : listFilesRecursive(inputPath)) {
const auto rel = std::filesystem::relative(file, inputPath);
const auto sigPath = outDir / (rel.string() + ".sig");
signOneFile(file, &privateKey, rng, sigPath);
}
} else {
if (!std::filesystem::is_regular_file(inputPath)) {
mbedtls_pk_free(&privateKey);
throw std::runtime_error("Input path must be a file when --batch is not used.");
}
const std::filesystem::path sigPath = output.has_value() ? *output : std::filesystem::path(inputPath.string() + ".sig");
signOneFile(inputPath, &privateKey, rng, sigPath);
}
mbedtls_pk_free(&privateKey);
}
void verifyCommand(const ParsedArgs& args) {
if (args.positional.size() < 4) throw std::runtime_error("Usage: verify <file-path> <signature-path> <public-key-path>");
const std::filesystem::path filePath = std::filesystem::absolute(args.positional[1]);
const std::filesystem::path signaturePath = std::filesystem::absolute(args.positional[2]);
const std::filesystem::path publicKeyPath = std::filesystem::absolute(args.positional[3]);
mbedtls_pk_context publicKey = loadPublicKey(publicKeyPath);
const bool valid = verifyOneFile(filePath, signaturePath, &publicKey);
mbedtls_pk_free(&publicKey);
if (!valid) std::exit(2);
}
void runDemo(Rng& rng) {
const auto root = std::filesystem::absolute("rsa_sample_cpp");
std::filesystem::create_directories(root);
const auto sampleFile = root / "sample.txt";
writeText(sampleFile, "RSA digital signature demo.\n");
keygen(2048, root, "both", rng);
const auto privateKey = root / "private_key.pem";
const auto publicKey = root / "public_key.pem";
const auto signature = root / "sample.txt.sig";
mbedtls_pk_context priv = loadPrivateKey(privateKey, rng);
signOneFile(sampleFile, &priv, rng, signature);
mbedtls_pk_free(&priv);
mbedtls_pk_context pub = loadPublicKey(publicKey);
const bool validBefore = verifyOneFile(sampleFile, signature, &pub);
writeText(sampleFile, "RSA digital signature demo.\nThis file was tampered.\n");
const bool validAfter = verifyOneFile(sampleFile, signature, &pub);
mbedtls_pk_free(&pub);
std::cout << "Demo result: initial verify=" << (validBefore ? "true" : "false")
<< ", after tamper verify=" << (validAfter ? "true" : "false") << "\n";
}
void keygenCommand(const ParsedArgs& args, Rng& rng) {
const int bits = args.options.count("bits") ? std::stoi(args.options.at("bits")) : 2048;
const auto outDir = std::filesystem::absolute(args.options.count("output") ? args.options.at("output") : ".");
const auto format = args.options.count("format") ? args.options.at("format") : "pem";
keygen(bits, outDir, format, rng);
}
} // namespace
int main(int argc, char** argv) {
try {
Rng rng;
ParsedArgs args = parseArgs(argc, argv);
if (args.positional.empty()) {
runDemo(rng);
return 0;
}
const std::string cmd = args.positional[0];
if (cmd == "keygen") keygenCommand(args, rng);
else if (cmd == "sign") signCommand(args, rng);
else if (cmd == "verify") verifyCommand(args);
else throw std::runtime_error("Unknown subcommand. Use keygen, sign, or verify.");
return 0;
} catch (const std::exception& ex) {
std::cerr << "Error: " << ex.what() << "\n";
return 1;
}
}