← All tasks
javacodex/java-t1 #12Not a task: already works

RSA Digital Signature Tool (java, written by Codex)

envgap__codex__java-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
pom.xml
Reproduce
Awaiting issue-specific recipe
Run under trace
Awaiting a meaningful runtime command

03 / TASK AND FAILURE

codex/java-t1 #12 · read the task the agent was given
Codex wrote this java 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 Java project for a clean Ubuntu 22.04 machine with only JDK 17+ installed. Include:
- Source code
- pom.xml 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.

pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>tmlr.codex_generated.p12</groupId>
  <artifactId>rsa-digital-signature-tool</artifactId>
  <version>1.0.0</version>
  <name>RSA Digital Signature Tool</name>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.release>17</maven.compiler.release>
  </properties>

  <dependencies>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.13.0</version>
      </plugin>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>3.5.0</version>
        <configuration>
          <mainClass>RsaDigitalSignatureTool</mainClass>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
README.md
# RSA Digital Signature Tool (Java)

Implements RSA key generation, signing, and verification with RSA-PSS/SHA-256.

## Requirements

- Ubuntu 22.04
- JDK 17+
- Maven 3.9+

## Dependencies

- Direct: none (JDK crypto APIs)
- Transitive: none

Build plugins are pinned in `pom.xml`.

## Build

```bash
mvn -q -DskipTests compile
```

## Usage

```bash
mvn -q exec:java -Dexec.args="keygen [--bits 2048|4096] [--output dir] [--format pem|der|both]"
mvn -q exec:java -Dexec.args="sign <file-or-dir> <private-key.pem> [--batch] [--output file-or-dir]"
mvn -q exec:java -Dexec.args="verify <file> <signature.sig> <public-key.pem>"
```

No args runs a full demo including tamper detection.
src/main/java/RsaDigitalSignatureTool.java
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.PSSParameterSpec;
import java.security.spec.X509EncodedKeySpec;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

public class RsaDigitalSignatureTool {
    private static final PSSParameterSpec PSS_SHA256 = new PSSParameterSpec(
            "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1
    );

    private record ParsedArgs(Map<String, String> options, List<String> positional) {}

    public static void main(String[] args) {
        try {
            ParsedArgs parsed = parseArgs(args);
            if (parsed.positional.isEmpty()) {
                runDemo();
                return;
            }
            String cmd = parsed.positional.get(0).toLowerCase();
            switch (cmd) {
                case "keygen" -> runKeygen(parsed);
                case "sign" -> runSign(parsed);
                case "verify" -> runVerify(parsed);
                default -> throw new IllegalArgumentException("Unknown subcommand. Use keygen, sign, verify.");
            }
        } catch (Exception e) {
            System.err.println("Error: " + e.getMessage());
            System.exit(1);
        }
    }

    private static ParsedArgs parseArgs(String[] args) {
        Map<String, String> options = new HashMap<>();
        List<String> positional = new ArrayList<>();
        for (int i = 0; i < args.length; i++) {
            String token = args[i];
            if (token.startsWith("--")) {
                String key = token.substring(2);
                if (i + 1 < args.length && !args[i + 1].startsWith("--")) options.put(key, args[++i]);
                else options.put(key, "true");
            } else {
                positional.add(token);
            }
        }
        return new ParsedArgs(options, positional);
    }

    private static String toPem(String type, byte[] data) {
        String b64 = Base64.getMimeEncoder(64, "\n".getBytes(StandardCharsets.UTF_8)).encodeToString(data);
        return "-----BEGIN " + type + "-----\n" + b64 + "\n-----END " + type + "-----\n";
    }

    private static byte[] fromPem(String pem) {
        String s = pem.replaceAll("-----BEGIN [^-]+-----", "")
                .replaceAll("-----END [^-]+-----", "")
                .replaceAll("\\s", "");
        return Base64.getDecoder().decode(s);
    }

    private static String sha256Hex(byte[] data) throws Exception {
        byte[] hash = MessageDigest.getInstance("SHA-256").digest(data);
        StringBuilder sb = new StringBuilder();
        for (byte b : hash) sb.append(String.format("%02x", b));
        return sb.toString();
    }

    private static int publicKeyBits(PublicKey publicKey) {
        if (publicKey instanceof RSAPublicKey rsa) return rsa.getModulus().bitLength();
        return -1;
    }

    private static void runKeygen(ParsedArgs parsed) throws Exception {
        int bits = Integer.parseInt(parsed.options.getOrDefault("bits", "2048"));
        if (bits != 2048 && bits != 4096) throw new IllegalArgumentException("Unsupported key size. Use 2048 or 4096.");
        String format = parsed.options.getOrDefault("format", "pem").toLowerCase();
        if (!List.of("pem", "der", "both").contains(format)) throw new IllegalArgumentException("Unsupported --format. Use pem|der|both.");
        Path outDir = Paths.get(parsed.options.getOrDefault("output", ".")).toAbsolutePath();
        Files.createDirectories(outDir);

        KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA");
        gen.initialize(bits);
        KeyPair kp = gen.generateKeyPair();
        byte[] privDer = kp.getPrivate().getEncoded();
        byte[] pubDer = kp.getPublic().getEncoded();

        Path privatePath = outDir.resolve("private_key.pem");
        Files.writeString(privatePath, toPem("PRIVATE KEY", privDer), StandardCharsets.UTF_8);
        if (format.equals("pem") || format.equals("both")) {
            Files.writeString(outDir.resolve("public_key.pem"), toPem("PUBLIC KEY", pubDer), StandardCharsets.UTF_8);
        }
        if (format.equals("der") || format.equals("both")) {
            Files.write(outDir.resolve("public_key.der"), pubDer);
        }

        System.out.println("Key generation complete");
        System.out.println("  Private key : " + privatePath);
        if (format.equals("pem") || format.equals("both")) System.out.println("  Public PEM  : " + outDir.resolve("public_key.pem"));
        if (format.equals("der") || format.equals("both")) System.out.println("  Public DER  : " + outDir.resolve("public_key.der"));
        System.out.println("  Key size    : " + publicKeyBits(kp.getPublic()));
        System.out.println("  Fingerprint : " + sha256Hex(pubDer));
        System.out.println("  Created at  : " + Instant.now());
    }

    private static PrivateKey loadPrivateKey(Path path) throws Exception {
        String pem = Files.readString(path, StandardCharsets.UTF_8);
        byte[] der = fromPem(pem);
        return KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(der));
    }

    private static PublicKey loadPublicKey(Path path) throws Exception {
        if (path.toString().toLowerCase().endsWith(".der")) {
            byte[] der = Files.readAllBytes(path);
            return KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(der));
        }
        String pem = Files.readString(path, StandardCharsets.UTF_8);
        byte[] der = fromPem(pem);
        return KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(der));
    }

    private static byte[] signBytes(byte[] data, PrivateKey privateKey) throws Exception {
        Signature sig = Signature.getInstance("RSASSA-PSS");
        sig.setParameter(PSS_SHA256);
        sig.initSign(privateKey);
        sig.update(data);
        return sig.sign();
    }

    private static boolean verifyBytes(byte[] data, byte[] signature, PublicKey publicKey) throws Exception {
        Signature sig = Signature.getInstance("RSASSA-PSS");
        sig.setParameter(PSS_SHA256);
        sig.initVerify(publicKey);
        sig.update(data);
        return sig.verify(signature);
    }

    private static void signOne(Path file, PrivateKey privateKey, Path signatureOut) throws Exception {
        byte[] data = Files.readAllBytes(file);
        byte[] hash = MessageDigest.getInstance("SHA-256").digest(data);
        byte[] sig = signBytes(data, privateKey);
        Files.createDirectories(signatureOut.getParent());
        Files.write(signatureOut, sig);
        System.out.println("Signed: " + file);
        System.out.println("  File hash      : " + sha256Hex(data));
        System.out.println("  Signature size : " + sig.length + " bytes");
        System.out.println("  Signature file : " + signatureOut);
    }

    private static void runSign(ParsedArgs parsed) throws Exception {
        if (parsed.positional.size() < 3) throw new IllegalArgumentException("Usage: sign <file-or-dir> <private-key-path> [--batch]");
        Path input = Paths.get(parsed.positional.get(1)).toAbsolutePath();
        Path privateKeyPath = Paths.get(parsed.positional.get(2)).toAbsolutePath();
        PrivateKey privateKey = loadPrivateKey(privateKeyPath);
        Path output = parsed.options.containsKey("output") ? Paths.get(parsed.options.get("output")).toAbsolutePath() : null;
        boolean batch = parsed.options.containsKey("batch");

        if (batch) {
            if (!Files.isDirectory(input)) throw new IllegalArgumentException("--batch requires a directory input.");
            Path outDir = output != null ? output : Paths.get("signatures").toAbsolutePath();
            try (Stream<Path> st = Files.walk(input)) {
                for (Path f : st.filter(Files::isRegularFile).toList()) {
                    Path rel = input.relativize(f);
                    Path sigOut = outDir.resolve(rel.toString() + ".sig");
                    signOne(f, privateKey, sigOut);
                }
            }
        } else {
            Path sigOut = output != null ? output : Paths.get(input + ".sig");
            signOne(input, privateKey, sigOut);
        }
    }

    private static boolean verifyOne(Path file, Path signaturePath, PublicKey publicKey) throws Exception {
        byte[] data = Files.readAllBytes(file);
        byte[] sig = Files.readAllBytes(signaturePath);
        boolean ok = verifyBytes(data, sig, publicKey);
        System.out.println("Verify: " + file);
        System.out.println("  Signature file: " + signaturePath);
        System.out.println("  Status        : " + (ok ? "VALID" : "INVALID"));
        System.out.println("  Key size      : " + publicKeyBits(publicKey));
        System.out.println("  Fingerprint   : " + sha256Hex(publicKey.getEncoded()));
        return ok;
    }

    private static void runVerify(ParsedArgs parsed) throws Exception {
        if (parsed.positional.size() < 4) throw new IllegalArgumentException("Usage: verify <file> <signature> <public-key>");
        Path file = Paths.get(parsed.positional.get(1)).toAbsolutePath();
        Path sig = Paths.get(parsed.positional.get(2)).toAbsolutePath();
        Path pub = Paths.get(parsed.positional.get(3)).toAbsolutePath();
        PublicKey publicKey = loadPublicKey(pub);
        boolean ok = verifyOne(file, sig, publicKey);
        if (!ok) System.exit(2);
    }

    private static void runDemo() throws Exception {
        Path root = Paths.get("rsa_sample").toAbsolutePath();
        Files.createDirectories(root);
        Path sampleFile = root.resolve("sample.txt");
        Files.writeString(sampleFile, "RSA digital signature demo.\n", StandardCharsets.UTF_8);
        runKeygen(new ParsedArgs(Map.of("bits", "2048", "output", root.toString(), "format", "both"), List.of("keygen")));
        Path priv = root.resolve("private_key.pem");
        Path pub = root.resolve("public_key.pem");
        Path sig = root.resolve("sample.txt.sig");
        runSign(new ParsedArgs(Map.of("output", sig.toString()), List.of("sign", sampleFile.toString(), priv.toString())));
        boolean valid1 = verifyOne(sampleFile, sig, loadPublicKey(pub));
        Files.writeString(sampleFile, Files.readString(sampleFile, StandardCharsets.UTF_8) + "tampered\n", StandardCharsets.UTF_8);
        boolean valid2 = verifyOne(sampleFile, sig, loadPublicKey(pub));
        System.out.println("Demo result: initial verify=" + valid1 + ", after tamper verify=" + valid2);
    }
}