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