← All tasks
cppcodex/cpp-t1 #12Not a task: repair changed code

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;
    }
}