RSA Digital Signature Tool (javascript, written by Codex)
envgap__codex__javascript-t1-12
Written by a coding agent; not on GitHubWritten 2026-03-02
01 / FAILURE SIGNATURE
As the study recorded it
None
Not a benchmark task.
- The project already builds and runs before the fix, so there is nothing to repair.
02 / ENVIRONMENT RECIPE
- Base commit
Not freshly verified- Manifest
package.json- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / TASK AND FAILURE
codex/javascript-t1 #12 · read the task the agent was given
Codex wrote this javascript project from the task below. It installed and ran 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 JavaScript project for a clean Ubuntu 22.04 machine with only Node.js 20+ (LTS) installed. Include: - Source code - package.json 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
4 files, exactly as written, before any repair.
package-lock.json
{
"name": "rsa-digital-signature-tool",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "rsa-digital-signature-tool",
"version": "1.0.0",
"dependencies": {}
}
}
}
package.json
{
"name": "rsa-digital-signature-tool",
"version": "1.0.0",
"description": "RSA key generation, signing, and signature verification tool using RSA-PSS.",
"type": "module",
"main": "src/index.js",
"scripts": {
"start": "node src/index.js"
},
"engines": {
"node": ">=20.0.0"
},
"dependencies": {}
}
README.md
# RSA Digital Signature Tool (JavaScript) Supports `keygen`, `sign`, and `verify` subcommands using RSA-PSS signatures with SHA-256. ## Requirements - Ubuntu 22.04 - Node.js 20+ (LTS) ## Dependencies - Direct: none - Transitive: none Pinned in `package.json` and `package-lock.json`. ## Usage ```bash node src/index.js keygen [--bits 2048|4096] [--output dir] [--format pem|der|both] node src/index.js sign <file-or-dir> <private-key.pem> [--batch] [--output file-or-dir] node src/index.js verify <file> <signature.sig> <public-key.pem> ``` Examples: ```bash node src/index.js keygen --bits 4096 --output ./keys --format both node src/index.js sign ./document.txt ./keys/private_key.pem node src/index.js verify ./document.txt ./document.txt.sig ./keys/public_key.pem node src/index.js sign ./docs ./keys/private_key.pem --batch --output ./sigs ``` No args runs an end-to-end demo including tamper detection.
src/index.js
import fs from "fs";
import path from "path";
import crypto from "crypto";
function parseArgs(argv) {
const options = {};
const positional = [];
for (let i = 0; i < argv.length; i += 1) {
const token = argv[i];
if (token.startsWith("--")) {
const key = token.slice(2);
const next = argv[i + 1];
if (next && !next.startsWith("--")) {
options[key] = next;
i += 1;
} else {
options[key] = true;
}
} else {
positional.push(token);
}
}
return { options, positional };
}
function sha256(data) {
return crypto.createHash("sha256").update(data).digest();
}
function fingerprintPublicKey(publicPem) {
return sha256(Buffer.isBuffer(publicPem) ? publicPem : Buffer.from(publicPem)).toString("hex");
}
function keyInfoFromPublicKey(publicPem) {
const keyObj = crypto.createPublicKey(publicPem);
const details = keyObj.asymmetricKeyDetails || {};
const bits = details.modulusLength || null;
return { bits, fingerprint: fingerprintPublicKey(publicPem), created_at: new Date().toISOString() };
}
function writeFileEnsure(p, data) {
fs.mkdirSync(path.dirname(p), { recursive: true });
fs.writeFileSync(p, data);
}
function keygen(options) {
const bits = Number.parseInt(options.bits || "2048", 10);
if (![2048, 4096].includes(bits)) throw new Error("Unsupported key size. Use 2048 or 4096.");
const outDir = path.resolve(options.output || ".");
const fmt = String(options.format || "pem").toLowerCase(); // pem|der|both
if (!["pem", "der", "both"].includes(fmt)) throw new Error("Unsupported --format. Use pem|der|both.");
const { privateKey, publicKey } = crypto.generateKeyPairSync("rsa", {
modulusLength: bits,
publicExponent: 0x10001,
privateKeyEncoding: { type: "pkcs8", format: "pem" },
publicKeyEncoding: { type: "spki", format: "pem" },
});
const privPath = path.join(outDir, "private_key.pem");
const pubPemPath = path.join(outDir, "public_key.pem");
writeFileEnsure(privPath, privateKey);
if (fmt === "pem" || fmt === "both") writeFileEnsure(pubPemPath, publicKey);
if (fmt === "der" || fmt === "both") {
const pubDer = crypto.createPublicKey(publicKey).export({ type: "spki", format: "der" });
writeFileEnsure(path.join(outDir, "public_key.der"), pubDer);
}
const info = keyInfoFromPublicKey(publicKey);
console.log("Key generation complete");
console.log(` Private key : ${privPath}`);
if (fmt === "pem" || fmt === "both") console.log(` Public PEM : ${pubPemPath}`);
if (fmt === "der" || fmt === "both") console.log(` Public DER : ${path.join(outDir, "public_key.der")}`);
console.log(` Key size : ${info.bits}`);
console.log(` Fingerprint : ${info.fingerprint}`);
console.log(` Created at : ${info.created_at}`);
}
function signOneFile(filePath, privateKeyPem, outputPath) {
const data = fs.readFileSync(filePath);
const fileHash = sha256(data).toString("hex");
const signature = crypto.sign("sha256", data, {
key: privateKeyPem,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
saltLength: 32,
});
writeFileEnsure(outputPath, signature);
console.log(`Signed: ${filePath}`);
console.log(` SHA-256 hash : ${fileHash}`);
console.log(` Signature size: ${signature.length} bytes`);
console.log(` Signature file: ${outputPath}`);
}
function listFilesRecursive(root) {
const out = [];
function walk(dir) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const e of entries) {
const p = path.join(dir, e.name);
if (e.isDirectory()) walk(p);
else if (e.isFile()) out.push(p);
}
}
walk(root);
return out;
}
function signCommand(positional, options) {
if (positional.length < 3) throw new Error("Usage: sign <file-or-dir> <private-key-path> [--batch] [--output path]");
const inputPath = path.resolve(positional[1]);
const privateKeyPath = path.resolve(positional[2]);
const privateKeyPem = fs.readFileSync(privateKeyPath, "utf8");
const out = options.output ? path.resolve(options.output) : null;
if (options.batch) {
if (!fs.statSync(inputPath).isDirectory()) throw new Error("--batch requires a directory input path.");
const outDir = out || path.resolve("signatures");
for (const file of listFilesRecursive(inputPath)) {
const rel = path.relative(inputPath, file);
const sigPath = path.join(outDir, `${rel}.sig`);
signOneFile(file, privateKeyPem, sigPath);
}
} else {
const sigOut = out || path.resolve(`${inputPath}.sig`);
signOneFile(inputPath, privateKeyPem, sigOut);
}
}
function verifyOneFile(filePath, sigPath, publicKeyPem) {
const data = fs.readFileSync(filePath);
const signature = fs.readFileSync(sigPath);
const fileHash = sha256(data).toString("hex");
const valid = crypto.verify("sha256", data, {
key: publicKeyPem,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
saltLength: 32,
}, signature);
const info = keyInfoFromPublicKey(publicKeyPem);
console.log(`Verify: ${filePath}`);
console.log(` SHA-256 hash : ${fileHash}`);
console.log(` Signature file: ${sigPath}`);
console.log(` Status : ${valid ? "VALID" : "INVALID"}`);
console.log(` Key size : ${info.bits}`);
console.log(` Fingerprint : ${info.fingerprint}`);
return valid;
}
function verifyCommand(positional) {
if (positional.length < 4) throw new Error("Usage: verify <file-path> <signature-path> <public-key-path>");
const filePath = path.resolve(positional[1]);
const sigPath = path.resolve(positional[2]);
const pubPath = path.resolve(positional[3]);
const publicKeyPem = fs.readFileSync(pubPath, "utf8");
const ok = verifyOneFile(filePath, sigPath, publicKeyPem);
if (!ok) process.exitCode = 2;
}
function selfTest() {
const root = path.resolve("rsa_sample");
fs.mkdirSync(root, { recursive: true });
const sampleFile = path.join(root, "sample.txt");
fs.writeFileSync(sampleFile, "RSA digital signature demo.\n");
keygen({ bits: "2048", output: root, format: "both" });
const privateKeyPath = path.join(root, "private_key.pem");
const publicKeyPath = path.join(root, "public_key.pem");
const sigPath = path.join(root, "sample.txt.sig");
signOneFile(sampleFile, fs.readFileSync(privateKeyPath, "utf8"), sigPath);
const valid1 = verifyOneFile(sampleFile, sigPath, fs.readFileSync(publicKeyPath, "utf8"));
fs.appendFileSync(sampleFile, "tampered\n");
const valid2 = verifyOneFile(sampleFile, sigPath, fs.readFileSync(publicKeyPath, "utf8"));
console.log(`Self-test result: initial verify=${valid1}, after tamper verify=${valid2}`);
}
function main() {
const { options, positional } = parseArgs(process.argv.slice(2));
if (positional.length === 0) {
selfTest();
return;
}
const cmd = positional[0].toLowerCase();
if (cmd === "keygen") keygen(options);
else if (cmd === "sign") signCommand(positional, options);
else if (cmd === "verify") verifyCommand(positional);
else throw new Error("Unknown subcommand. Use keygen, sign, or verify.");
}
try {
main();
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
console.error(`Error: ${message}`);
process.exit(1);
}