← All tasks
javascriptcodex/javascript-t1 #12Not a task: already works

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