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

X.509 Certificate Parser (java, written by Codex)

envgap__codex__java-t1-16

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 #16 · 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: X.509 Certificate Parser

Write a program that parses X.509 digital certificates in PEM and DER formats, extracts all fields, validates the certificate chain, and checks expiration status.

FUNCTIONAL REQUIREMENTS:
- Accept a certificate file path as a command-line argument (support both PEM and DER formats, auto-detected)
- Extract and display all certificate fields: version, serial number, issuer, subject, validity period (not before/not after), public key algorithm and size, signature algorithm, and fingerprints (SHA-1, SHA-256)
- Parse all X.509 v3 extensions: Subject Alternative Names (SANs), Key Usage, Extended Key Usage, Basic Constraints, Authority/Subject Key Identifiers, CRL Distribution Points
- Check certificate expiration: report if expired, days until expiration, or days since expiration
- Validate a certificate chain when multiple certificates are provided: verify that each certificate is signed by the next one in the chain
- Support reading certificate bundles (multiple PEM certificates concatenated in one file) and parsing each individually
- Support a --format flag to choose output format: text (default human-readable), json, or csv
- Support fetching and parsing a remote server's certificate via --host flag (given a hostname and optional port)
- Print the parsed certificate details to console in a structured, readable format
- Save the output to a file via --output flag
- If no input is given, generate a self-signed CA certificate and a leaf certificate signed by it, then parse both and demonstrate chain validation
- Handle errors: invalid certificate data, unsupported formats, incomplete chains, and encoding issues

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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.tmlr</groupId>
    <artifactId>x509-certificate-parser</artifactId>
    <version>1.0.0</version>

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

    <dependencies>
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk18on</artifactId>
            <version>1.78.1</version>
        </dependency>
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcpkix-jdk18on</artifactId>
            <version>1.78.1</version>
        </dependency>
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcutil-jdk18on</artifactId>
            <version>1.78.1</version>
        </dependency>
        <dependency>
            <groupId>org.json</groupId>
            <artifactId>json</artifactId>
            <version>20240303</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.13.0</version>
                <configuration>
                    <release>17</release>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.4.2</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>X509CertificateParser</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

README.md
# X.509 Certificate Parser (Java)

Parses X.509 certificates (PEM/DER/bundles/remote host), extracts fields/extensions, checks expiration, and validates chain signatures.

## Requirements

- Ubuntu 22.04
- JDK 17+
- Maven 3.8+

## Dependencies (Pinned)

- `org.bouncycastle:bcprov-jdk18on:1.78.1`
- `org.bouncycastle:bcpkix-jdk18on:1.78.1`
- `org.bouncycastle:bcutil-jdk18on:1.78.1`
- `org.json:json:20240303`

## Build

```bash
mvn clean package
```

## Run

```bash
java -jar target/x509-certificate-parser-1.0.0.jar ./cert.pem
java -jar target/x509-certificate-parser-1.0.0.jar ./bundle.pem --format json
java -jar target/x509-certificate-parser-1.0.0.jar ./cert.der --format csv --output ./report.csv
java -jar target/x509-certificate-parser-1.0.0.jar --host example.com:443 --format text
java -jar target/x509-certificate-parser-1.0.0.jar
```

## Features

- PEM/DER auto-detection
- PEM bundle parsing (multiple certs in one file)
- Full field extraction (version, serial, issuer, subject, validity, key info, signature algorithm, SHA-1/SHA-256 fingerprints)
- Extension parsing (SAN, Key Usage, EKU, Basic Constraints, SKID/AKID, CRL DP)
- Expiration status and day counts
- Chain signature validation
- Remote server cert-chain fetch with `--host`
- Output formats: text/json/csv
- Output file support via `--output`
- No-args demo chain generation and validation

src/main/java/X509CertificateParser.java
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.x509.CRLDistPoint;
import org.bouncycastle.asn1.x509.DistributionPoint;
import org.bouncycastle.asn1.x509.DistributionPointName;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.json.JSONArray;
import org.json.JSONObject;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.ByteArrayInputStream;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPublicKey;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public final class X509CertificateParser {
    private record ParsedArgs(Map<String, String> options, List<String> positional) {}

    private X509CertificateParser() {}

    public static void main(String[] args) {
        try {
            Security.addProvider(new BouncyCastleProvider());
            ParsedArgs parsed = parseArgs(args);
            String format = parsed.options.getOrDefault("format", "text").toLowerCase();
            if (!List.of("text", "json", "csv").contains(format)) throw new IllegalArgumentException("Invalid --format");

            List<X509Certificate> certs;
            String source;
            if (parsed.options.containsKey("host")) {
                String[] hp = parsed.options.get("host").split(":", 2);
                String host = hp[0];
                int port = hp.length > 1 ? Integer.parseInt(hp[1]) : Integer.parseInt(parsed.options.getOrDefault("port", "443"));
                certs = fetchHostChain(host, port);
                source = "host:" + host + ":" + port;
            } else if (!parsed.positional.isEmpty()) {
                Path p = Paths.get(parsed.positional.get(0)).toAbsolutePath();
                certs = loadFromFile(p);
                source = p.toString();
            } else {
                certs = generateDemoChain();
                source = "demo-generated";
            }

            JSONObject result = new JSONObject();
            result.put("source", source);
            result.put("certificate_count", certs.size());
            JSONArray arr = new JSONArray();
            for (X509Certificate cert : certs) arr.put(analyze(cert));
            result.put("certificates", arr);
            result.put("chain_validation", validateChain(certs));

            String rendered = switch (format) {
                case "json" -> result.toString(2) + "\n";
                case "csv" -> renderCsv(result);
                default -> renderText(result);
            };

            if (parsed.options.containsKey("output")) {
                Path out = Paths.get(parsed.options.get("output")).toAbsolutePath();
                Files.createDirectories(out.getParent());
                Files.writeString(out, rendered, StandardCharsets.UTF_8);
            }
            System.out.print(rendered);
        } 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 t = args[i];
            if (t.startsWith("--")) {
                String k = t.substring(2);
                if (i + 1 < args.length && !args[i + 1].startsWith("--")) options.put(k, args[++i]);
                else options.put(k, "true");
            } else positional.add(t);
        }
        return new ParsedArgs(options, positional);
    }

    private static List<X509Certificate> loadFromFile(Path path) throws Exception {
        byte[] data = Files.readAllBytes(path);
        String text = new String(data, StandardCharsets.UTF_8);
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        if (text.contains("BEGIN CERTIFICATE")) {
            List<X509Certificate> certs = new ArrayList<>();
            Matcher m = Pattern.compile("-----BEGIN CERTIFICATE-----([\\s\\S]*?)-----END CERTIFICATE-----").matcher(text);
            while (m.find()) {
                String b64 = m.group(1).replaceAll("\\s+", "");
                byte[] der = Base64.getDecoder().decode(b64);
                certs.add((X509Certificate) cf.generateCertificate(new ByteArrayInputStream(der)));
            }
            if (certs.isEmpty()) throw new IllegalArgumentException("No PEM certificate blocks found.");
            return certs;
        }
        return List.of((X509Certificate) cf.generateCertificate(new ByteArrayInputStream(data)));
    }

    private static List<X509Certificate> fetchHostChain(String host, int port) throws Exception {
        TrustManager[] trustAll = new TrustManager[]{new X509TrustManager() {
            @Override public void checkClientTrusted(java.security.cert.X509Certificate[] xcs, String s) {}
            @Override public void checkServerTrusted(java.security.cert.X509Certificate[] xcs, String s) {}
            @Override public java.security.cert.X509Certificate[] getAcceptedIssuers() { return new java.security.cert.X509Certificate[0]; }
        }};
        SSLContext ctx = SSLContext.getInstance("TLS");
        ctx.init(null, trustAll, null);
        try (SSLSocket socket = (SSLSocket) ctx.getSocketFactory().createSocket(host, port)) {
            socket.startHandshake();
            java.security.cert.Certificate[] chain = socket.getSession().getPeerCertificates();
            List<X509Certificate> out = new ArrayList<>();
            for (java.security.cert.Certificate c : chain) out.add((X509Certificate) c);
            return out;
        }
    }

    private static List<X509Certificate> generateDemoChain() throws Exception {
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
        kpg.initialize(2048);
        KeyPair caKeys = kpg.generateKeyPair();
        KeyPair leafKeys = kpg.generateKeyPair();

        org.bouncycastle.asn1.x500.X500Name caName = new org.bouncycastle.asn1.x500.X500Name("CN=Demo Root CA,O=TMLR Demo");
        org.bouncycastle.asn1.x500.X500Name leafName = new org.bouncycastle.asn1.x500.X500Name("CN=demo.local,O=TMLR Demo");
        Date notBefore = Date.from(Instant.now().minus(1, ChronoUnit.MINUTES));
        Date caNotAfter = Date.from(Instant.now().plus(365, ChronoUnit.DAYS));
        Date leafNotAfter = Date.from(Instant.now().plus(180, ChronoUnit.DAYS));

        JcaX509v3CertificateBuilder caBuilder = new JcaX509v3CertificateBuilder(
                caName, BigInteger.valueOf(1001), notBefore, caNotAfter, caName, caKeys.getPublic()
        );
        caBuilder.addExtension(org.bouncycastle.asn1.x509.Extension.basicConstraints, true, new org.bouncycastle.asn1.x509.BasicConstraints(1));
        caBuilder.addExtension(org.bouncycastle.asn1.x509.Extension.keyUsage, true, new org.bouncycastle.asn1.x509.KeyUsage(org.bouncycastle.asn1.x509.KeyUsage.keyCertSign | org.bouncycastle.asn1.x509.KeyUsage.cRLSign));
        ContentSigner caSigner = new JcaContentSignerBuilder("SHA256withRSA").build(caKeys.getPrivate());
        X509Certificate caCert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(caBuilder.build(caSigner));

        JcaX509v3CertificateBuilder leafBuilder = new JcaX509v3CertificateBuilder(
                caName, BigInteger.valueOf(1002), notBefore, leafNotAfter, leafName, leafKeys.getPublic()
        );
        leafBuilder.addExtension(org.bouncycastle.asn1.x509.Extension.basicConstraints, true, new org.bouncycastle.asn1.x509.BasicConstraints(false));
        leafBuilder.addExtension(org.bouncycastle.asn1.x509.Extension.keyUsage, true,
                new org.bouncycastle.asn1.x509.KeyUsage(org.bouncycastle.asn1.x509.KeyUsage.digitalSignature | org.bouncycastle.asn1.x509.KeyUsage.keyEncipherment));
        leafBuilder.addExtension(org.bouncycastle.asn1.x509.Extension.extendedKeyUsage, false,
                new org.bouncycastle.asn1.x509.ExtendedKeyUsage(org.bouncycastle.asn1.x509.KeyPurposeId.id_kp_serverAuth));
        GeneralName[] sans = new GeneralName[] { new GeneralName(GeneralName.dNSName, "demo.local"), new GeneralName(GeneralName.dNSName, "www.demo.local") };
        leafBuilder.addExtension(org.bouncycastle.asn1.x509.Extension.subjectAlternativeName, false, new GeneralNames(sans));
        ContentSigner leafSigner = new JcaContentSignerBuilder("SHA256withRSA").build(caKeys.getPrivate());
        X509Certificate leafCert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(leafBuilder.build(leafSigner));

        return List.of(leafCert, caCert);
    }

    private static JSONObject analyze(X509Certificate cert) throws Exception {
        JSONObject out = new JSONObject();
        out.put("version", cert.getVersion());
        out.put("serial_number", cert.getSerialNumber().toString(16));
        out.put("issuer", cert.getIssuerX500Principal().getName());
        out.put("subject", cert.getSubjectX500Principal().getName());
        out.put("validity", new JSONObject()
                .put("not_before", cert.getNotBefore().toInstant().toString())
                .put("not_after", cert.getNotAfter().toInstant().toString()));
        out.put("expiration", expiration(cert));
        out.put("public_key", keyInfo(cert.getPublicKey()));
        out.put("signature_algorithm", cert.getSigAlgName());
        byte[] der = cert.getEncoded();
        out.put("fingerprints", new JSONObject()
                .put("sha1", hex(MessageDigest.getInstance("SHA-1").digest(der)))
                .put("sha256", hex(MessageDigest.getInstance("SHA-256").digest(der))));
        out.put("extensions", parseExtensions(cert));
        return out;
    }

    private static JSONObject keyInfo(PublicKey key) {
        JSONObject out = new JSONObject();
        if (key instanceof RSAPublicKey rsa) {
            out.put("algorithm", "RSA");
            out.put("size", rsa.getModulus().bitLength());
        } else if (key instanceof ECPublicKey ec) {
            out.put("algorithm", "EC");
            out.put("size", ec.getParams().getOrder().bitLength());
        } else {
            out.put("algorithm", key.getAlgorithm());
            out.put("size", JSONObject.NULL);
        }
        return out;
    }

    private static JSONObject expiration(X509Certificate cert) {
        long days = ChronoUnit.DAYS.between(Instant.now(), cert.getNotAfter().toInstant());
        if (days >= 0) return new JSONObject().put("expired", false).put("days_until_expiration", days).put("days_since_expiration", 0);
        return new JSONObject().put("expired", true).put("days_until_expiration", 0).put("days_since_expiration", Math.abs(days));
    }

    private static JSONObject parseExtensions(X509Certificate cert) throws Exception {
        JSONObject out = new JSONObject();
        out.put("subject_alt_names", new JSONArray());
        out.put("key_usage", new JSONObject());
        out.put("extended_key_usage", new JSONArray());
        out.put("basic_constraints", new JSONObject());
        out.put("subject_key_identifier", JSONObject.NULL);
        out.put("authority_key_identifier", JSONObject.NULL);
        out.put("crl_distribution_points", new JSONArray());

        var sans = cert.getSubjectAlternativeNames();
        if (sans != null) {
            JSONArray arr = new JSONArray();
            for (List<?> item : sans) arr.put(String.valueOf(item.get(1)));
            out.put("subject_alt_names", arr);
        }
        boolean[] ku = cert.getKeyUsage();
        if (ku != null) {
            String[] names = {"digital_signature", "non_repudiation", "key_encipherment", "data_encipherment", "key_agreement", "key_cert_sign", "crl_sign", "encipher_only", "decipher_only"};
            JSONObject usage = new JSONObject();
            for (int i = 0; i < Math.min(ku.length, names.length); i++) if (ku[i]) usage.put(names[i], true);
            out.put("key_usage", usage);
        }
        List<String> eku = cert.getExtendedKeyUsage();
        if (eku != null) out.put("extended_key_usage", new JSONArray(eku));
        out.put("basic_constraints", new JSONObject()
                .put("ca", cert.getBasicConstraints() >= 0)
                .put("path_length", cert.getBasicConstraints() >= 0 ? cert.getBasicConstraints() : JSONObject.NULL));

        byte[] skid = cert.getExtensionValue("2.5.29.14");
        if (skid != null) out.put("subject_key_identifier", hex(extractOctets(skid)));
        byte[] akid = cert.getExtensionValue("2.5.29.35");
        if (akid != null) out.put("authority_key_identifier", hex(extractOctets(akid)));

        byte[] crlExt = cert.getExtensionValue("2.5.29.31");
        if (crlExt != null) out.put("crl_distribution_points", parseCrlDp(crlExt));
        return out;
    }

    private static byte[] extractOctets(byte[] extValue) throws Exception {
        try (ASN1InputStream ais = new ASN1InputStream(extValue)) {
            ASN1Primitive p = ais.readObject();
            return ASN1OctetString.getInstance(p).getOctets();
        }
    }

    private static JSONArray parseCrlDp(byte[] extValue) throws Exception {
        byte[] octets = extractOctets(extValue);
        CRLDistPoint dp = CRLDistPoint.getInstance(ASN1Primitive.fromByteArray(octets));
        JSONArray arr = new JSONArray();
        for (DistributionPoint point : dp.getDistributionPoints()) {
            DistributionPointName n = point.getDistributionPoint();
            if (n != null && n.getType() == DistributionPointName.FULL_NAME) {
                GeneralNames gns = GeneralNames.getInstance(n.getName());
                for (GeneralName gn : gns.getNames()) arr.put(gn.getName().toString());
            }
        }
        return arr;
    }

    private static JSONObject validateChain(List<X509Certificate> certs) {
        JSONArray steps = new JSONArray();
        boolean ok = true;
        for (int i = 0; i < certs.size() - 1; i++) {
            boolean valid;
            try {
                certs.get(i).verify(certs.get(i + 1).getPublicKey());
                valid = true;
            } catch (Exception ex) {
                valid = false;
                ok = false;
            }
            steps.put(new JSONObject()
                    .put("index", i)
                    .put("child_subject", certs.get(i).getSubjectX500Principal().getName())
                    .put("issuer_subject", certs.get(i + 1).getSubjectX500Principal().getName())
                    .put("valid_signature", valid));
        }
        return new JSONObject().put("valid", ok).put("steps", steps);
    }

    private static String renderText(JSONObject result) {
        StringBuilder sb = new StringBuilder();
        JSONArray certs = result.getJSONArray("certificates");
        for (int i = 0; i < certs.length(); i++) {
            JSONObject c = certs.getJSONObject(i);
            sb.append("Certificate #").append(i + 1).append('\n');
            sb.append("  Subject: ").append(c.getString("subject")).append('\n');
            sb.append("  Issuer : ").append(c.getString("issuer")).append('\n');
            sb.append("  Version: ").append(c.get("version")).append('\n');
            sb.append("  Serial : ").append(c.getString("serial_number")).append('\n');
            sb.append("  Validity: ").append(c.getJSONObject("validity").getString("not_before"))
                    .append(" -> ").append(c.getJSONObject("validity").getString("not_after")).append('\n');
            sb.append("  Signature Algorithm: ").append(c.getString("signature_algorithm")).append('\n');
            sb.append("  Public Key: ").append(c.getJSONObject("public_key").getString("algorithm")).append(' ')
                    .append(c.getJSONObject("public_key").opt("size")).append('\n');
            sb.append("  SHA-1: ").append(c.getJSONObject("fingerprints").getString("sha1")).append('\n');
            sb.append("  SHA-256: ").append(c.getJSONObject("fingerprints").getString("sha256")).append('\n');
            sb.append('\n');
        }
        sb.append("Chain validation: ").append(result.getJSONObject("chain_validation").getBoolean("valid")).append('\n');
        return sb.toString();
    }

    private static String renderCsv(JSONObject result) {
        StringBuilder sb = new StringBuilder();
        sb.append("index,subject,issuer,serial_number,not_before,not_after,expired,days_until_expiration,days_since_expiration,public_key_algorithm,public_key_size,signature_algorithm,fingerprint_sha1,fingerprint_sha256\n");
        JSONArray certs = result.getJSONArray("certificates");
        for (int i = 0; i < certs.length(); i++) {
            JSONObject c = certs.getJSONObject(i);
            sb.append(i + 1).append(',')
                    .append(quote(c.getString("subject"))).append(',')
                    .append(quote(c.getString("issuer"))).append(',')
                    .append(c.getString("serial_number")).append(',')
                    .append(c.getJSONObject("validity").getString("not_before")).append(',')
                    .append(c.getJSONObject("validity").getString("not_after")).append(',')
                    .append(c.getJSONObject("expiration").getBoolean("expired")).append(',')
                    .append(c.getJSONObject("expiration").get("days_until_expiration")).append(',')
                    .append(c.getJSONObject("expiration").get("days_since_expiration")).append(',')
                    .append(c.getJSONObject("public_key").getString("algorithm")).append(',')
                    .append(c.getJSONObject("public_key").opt("size")).append(',')
                    .append(c.getString("signature_algorithm")).append(',')
                    .append(c.getJSONObject("fingerprints").getString("sha1")).append(',')
                    .append(c.getJSONObject("fingerprints").getString("sha256")).append('\n');
        }
        return sb.toString();
    }

    private static String quote(String s) {
        return "\"" + s.replace("\"", "\"\"") + "\"";
    }

    private static String hex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) sb.append(String.format("%02x", b));
        return sb.toString();
    }
}