← All tasks
javaclaude-code/java-t1 #16Not a task: repair not recorded

X.509 Certificate Parser (java, written by Claude Code)

envgap__claude-code__java-t1-16

Written by a coding agent; not on GitHubWritten 2026-02-27

01 / FAILURE SIGNATURE

As the study recorded it

SecurityException: Invalid signature file digest - BouncyCastle signed jars conflict with shade plugin
Not a benchmark task.
  • It was made to work, but its repair cannot be rebuilt from the saved files (the saved copy shows no change, or not all of the changes the study's notes describe), so there is no fix to score against.

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

claude-code/java-t1 #16 · read the task the agent was given
Claude Code wrote this java project from the task below. It does not run 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.

CertParser.java
import java.io.*;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.*;
import java.security.cert.*;
import java.security.cert.Certificate;
import java.security.interfaces.*;
import java.util.*;
import java.util.stream.Collectors;

import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x509.*;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.util.encoders.Hex;

/**
 * X.509 Certificate Parser using Bouncy Castle.
 *
 * Parses X.509 certificates in PEM and DER formats, extracts all fields
 * and extensions, and validates certificate chains.
 */
public class CertParser {

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * Load an X.509 certificate from a file (PEM or DER format).
     */
    public static X509Certificate loadCertificate(String filePath) throws Exception {
        byte[] data = Files.readAllBytes(Paths.get(filePath));
        String content = new String(data);

        if (content.contains("-----BEGIN CERTIFICATE-----")) {
            return loadPemCertificate(new ByteArrayInputStream(data));
        } else {
            return loadDerCertificate(data);
        }
    }

    private static X509Certificate loadPemCertificate(InputStream input) throws Exception {
        try (PEMParser parser = new PEMParser(new InputStreamReader(input))) {
            Object obj = parser.readObject();
            if (obj instanceof X509CertificateHolder) {
                return new JcaX509CertificateConverter()
                        .setProvider("BC")
                        .getCertificate((X509CertificateHolder) obj);
            }
            throw new IllegalArgumentException("Not a valid PEM certificate");
        }
    }

    private static X509Certificate loadDerCertificate(byte[] data) throws Exception {
        CertificateFactory cf = CertificateFactory.getInstance("X.509", "BC");
        return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(data));
    }

    /**
     * Load a chain of certificates from a PEM file.
     */
    public static List<X509Certificate> loadCertificateChain(String filePath) throws Exception {
        List<X509Certificate> chain = new ArrayList<>();
        byte[] data = Files.readAllBytes(Paths.get(filePath));

        try (PEMParser parser = new PEMParser(new InputStreamReader(new ByteArrayInputStream(data)))) {
            Object obj;
            JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider("BC");
            while ((obj = parser.readObject()) != null) {
                if (obj instanceof X509CertificateHolder) {
                    chain.add(converter.getCertificate((X509CertificateHolder) obj));
                }
            }
        }
        return chain;
    }

    /**
     * Parse a certificate and extract all fields as a formatted string.
     */
    public static String parseCertificate(X509Certificate cert) throws Exception {
        StringBuilder sb = new StringBuilder();
        sb.append("{\n");

        // Version
        sb.append(jsonField("version", cert.getVersion(), 1));

        // Serial Number
        sb.append(jsonField("serialNumber", cert.getSerialNumber().toString(16), 1));

        // Signature Algorithm
        sb.append(jsonField("signatureAlgorithm", cert.getSigAlgName(), 1));

        // Issuer
        sb.append("  \"issuer\": ").append(nameToJson(cert.getIssuerX500Principal().getName())).append(",\n");

        // Subject
        sb.append("  \"subject\": ").append(nameToJson(cert.getSubjectX500Principal().getName())).append(",\n");

        // Validity
        sb.append("  \"validity\": {\n");
        sb.append(jsonField("notBefore", cert.getNotBefore().toInstant().toString(), 2));
        sb.append(jsonFieldLast("notAfter", cert.getNotAfter().toInstant().toString(), 2));
        sb.append("  },\n");

        // Public Key Info
        sb.append("  \"publicKeyInfo\": ").append(publicKeyToJson(cert.getPublicKey())).append(",\n");

        // Extensions
        sb.append("  \"extensions\": [\n");
        sb.append(extractExtensions(cert));
        sb.append("  ],\n");

        // Fingerprints
        sb.append("  \"fingerprints\": {\n");
        sb.append(jsonField("sha256", fingerprint(cert, "SHA-256"), 2));
        sb.append(jsonField("sha1", fingerprint(cert, "SHA-1"), 2));
        sb.append(jsonFieldLast("md5", fingerprint(cert, "MD5"), 2));
        sb.append("  },\n");

        // Signature
        sb.append(jsonFieldLast("signatureValue", Hex.toHexString(cert.getSignature()), 1));

        sb.append("}");
        return sb.toString();
    }

    private static String fingerprint(X509Certificate cert, String algorithm) throws Exception {
        MessageDigest md = MessageDigest.getInstance(algorithm);
        byte[] digest = md.digest(cert.getEncoded());
        return Hex.toHexString(digest);
    }

    private static String nameToJson(String dnString) {
        StringBuilder sb = new StringBuilder();
        sb.append("{ ");
        String[] parts = dnString.split(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)");
        List<String> entries = new ArrayList<>();
        for (String part : parts) {
            String trimmed = part.trim();
            int eq = trimmed.indexOf('=');
            if (eq > 0) {
                String key = trimmed.substring(0, eq).trim();
                String value = trimmed.substring(eq + 1).trim();
                entries.add("\"" + escapeJson(key) + "\": \"" + escapeJson(value) + "\"");
            }
        }
        sb.append(String.join(", ", entries));
        sb.append(" }");
        return sb.toString();
    }

    private static String publicKeyToJson(PublicKey key) {
        StringBuilder sb = new StringBuilder();
        sb.append("{\n");

        if (key instanceof RSAPublicKey) {
            RSAPublicKey rsaKey = (RSAPublicKey) key;
            sb.append(jsonField("algorithm", "RSA", 2));
            sb.append(jsonField("keySize", rsaKey.getModulus().bitLength(), 2));
            sb.append(jsonFieldLast("exponent", rsaKey.getPublicExponent().intValue(), 2));
        } else if (key instanceof ECPublicKey) {
            ECPublicKey ecKey = (ECPublicKey) key;
            sb.append(jsonField("algorithm", "EC", 2));
            sb.append(jsonFieldLast("keySize", ecKey.getParams().getOrder().bitLength(), 2));
        } else if (key instanceof DSAPublicKey) {
            DSAPublicKey dsaKey = (DSAPublicKey) key;
            sb.append(jsonField("algorithm", "DSA", 2));
            sb.append(jsonFieldLast("keySize", dsaKey.getParams().getP().bitLength(), 2));
        } else {
            sb.append(jsonFieldLast("algorithm", key.getAlgorithm(), 2));
        }

        sb.append("    }");
        return sb.toString();
    }

    private static String extractExtensions(X509Certificate cert) throws Exception {
        StringBuilder sb = new StringBuilder();
        Set<String> criticalOids = cert.getCriticalExtensionOIDs();
        Set<String> nonCriticalOids = cert.getNonCriticalExtensionOIDs();

        if (criticalOids == null) criticalOids = new HashSet<>();
        if (nonCriticalOids == null) nonCriticalOids = new HashSet<>();

        Set<String> allOids = new LinkedHashSet<>();
        allOids.addAll(criticalOids);
        allOids.addAll(nonCriticalOids);

        List<String> extEntries = new ArrayList<>();
        for (String oid : allOids) {
            boolean critical = criticalOids.contains(oid);
            String extValue = parseExtension(cert, oid);
            StringBuilder entry = new StringBuilder();
            entry.append("    {\n");
            entry.append(jsonField("oid", oid, 3));
            entry.append(jsonField("name", getExtensionName(oid), 3));
            entry.append(jsonField("critical", critical, 3));
            entry.append("      \"value\": ").append(extValue).append("\n");
            entry.append("    }");
            extEntries.add(entry.toString());
        }

        return String.join(",\n", extEntries) + "\n";
    }

    private static String parseExtension(X509Certificate cert, String oidStr) {
        try {
            byte[] extValue = cert.getExtensionValue(oidStr);
            if (extValue == null) return "null";

            ASN1OctetString octetString = ASN1OctetString.getInstance(extValue);
            byte[] octets = octetString.getOctets();

            if (oidStr.equals(Extension.basicConstraints.getId())) {
                BasicConstraints bc = BasicConstraints.getInstance(ASN1Sequence.getInstance(octets));
                return String.format("{ \"ca\": %s, \"pathLength\": %s }",
                        bc.isCA(), bc.getPathLenConstraint());
            }

            if (oidStr.equals(Extension.keyUsage.getId())) {
                org.bouncycastle.asn1.x509.KeyUsage ku =
                        org.bouncycastle.asn1.x509.KeyUsage.getInstance(ASN1BitString.getInstance(octets));
                return formatKeyUsage(ku);
            }

            if (oidStr.equals(Extension.extendedKeyUsage.getId())) {
                ExtendedKeyUsage eku = ExtendedKeyUsage.getInstance(ASN1Sequence.getInstance(octets));
                List<String> usages = new ArrayList<>();
                for (KeyPurposeId kp : eku.getUsages()) {
                    usages.add("\"" + getEkuName(kp) + "\"");
                }
                return "[ " + String.join(", ", usages) + " ]";
            }

            if (oidStr.equals(Extension.subjectAlternativeName.getId())) {
                GeneralNames gns = GeneralNames.getInstance(ASN1Sequence.getInstance(octets));
                return formatGeneralNames(gns);
            }

            if (oidStr.equals(Extension.subjectKeyIdentifier.getId())) {
                SubjectKeyIdentifier ski = SubjectKeyIdentifier.getInstance(octets);
                return "{ \"keyIdentifier\": \"" + Hex.toHexString(ski.getKeyIdentifier()) + "\" }";
            }

            if (oidStr.equals(Extension.authorityKeyIdentifier.getId())) {
                AuthorityKeyIdentifier aki = AuthorityKeyIdentifier.getInstance(ASN1Sequence.getInstance(octets));
                StringBuilder akiSb = new StringBuilder("{ ");
                if (aki.getKeyIdentifier() != null) {
                    akiSb.append("\"keyIdentifier\": \"").append(Hex.toHexString(aki.getKeyIdentifier())).append("\"");
                }
                if (aki.getAuthorityCertSerialNumber() != null) {
                    akiSb.append(", \"authorityCertSerialNumber\": ")
                            .append(aki.getAuthorityCertSerialNumber());
                }
                akiSb.append(" }");
                return akiSb.toString();
            }

            if (oidStr.equals(Extension.cRLDistributionPoints.getId())) {
                CRLDistPoint cdp = CRLDistPoint.getInstance(ASN1Sequence.getInstance(octets));
                List<String> points = new ArrayList<>();
                for (DistributionPoint dp : cdp.getDistributionPoints()) {
                    StringBuilder dpSb = new StringBuilder("{ ");
                    if (dp.getDistributionPoint() != null) {
                        DistributionPointName dpName = dp.getDistributionPoint();
                        if (dpName.getType() == DistributionPointName.FULL_NAME) {
                            GeneralNames gns = GeneralNames.getInstance(dpName.getName());
                            List<String> names = new ArrayList<>();
                            for (GeneralName gn : gns.getNames()) {
                                names.add("\"" + gn.getName().toString() + "\"");
                            }
                            dpSb.append("\"fullName\": [").append(String.join(", ", names)).append("]");
                        }
                    }
                    dpSb.append(" }");
                    points.add(dpSb.toString());
                }
                return "[ " + String.join(", ", points) + " ]";
            }

            if (oidStr.equals(Extension.authorityInfoAccess.getId())) {
                AuthorityInformationAccess aia = AuthorityInformationAccess.getInstance(ASN1Sequence.getInstance(octets));
                List<String> descriptions = new ArrayList<>();
                for (AccessDescription ad : aia.getAccessDescriptions()) {
                    String method;
                    if (ad.getAccessMethod().equals(AccessDescription.id_ad_ocsp)) {
                        method = "ocsp";
                    } else if (ad.getAccessMethod().equals(AccessDescription.id_ad_caIssuers)) {
                        method = "caIssuers";
                    } else {
                        method = ad.getAccessMethod().getId();
                    }
                    descriptions.add(String.format("{ \"accessMethod\": \"%s\", \"accessLocation\": \"%s\" }",
                            method, ad.getAccessLocation().getName().toString()));
                }
                return "[ " + String.join(", ", descriptions) + " ]";
            }

            if (oidStr.equals(Extension.certificatePolicies.getId())) {
                ASN1Sequence seq = ASN1Sequence.getInstance(octets);
                List<String> policies = new ArrayList<>();
                for (ASN1Encodable enc : seq) {
                    PolicyInformation pi = PolicyInformation.getInstance(enc);
                    policies.add("{ \"policyIdentifier\": \"" + pi.getPolicyIdentifier().getId() + "\" }");
                }
                return "[ " + String.join(", ", policies) + " ]";
            }

            // Fallback: hex encode the extension value
            return "\"" + Hex.toHexString(octets) + "\"";

        } catch (Exception e) {
            return "\"Error parsing: " + escapeJson(e.getMessage()) + "\"";
        }
    }

    private static String formatKeyUsage(org.bouncycastle.asn1.x509.KeyUsage ku) {
        StringBuilder sb = new StringBuilder("{\n");
        sb.append(jsonField("digitalSignature", ku.hasUsages(org.bouncycastle.asn1.x509.KeyUsage.digitalSignature), 4));
        sb.append(jsonField("nonRepudiation", ku.hasUsages(org.bouncycastle.asn1.x509.KeyUsage.nonRepudiation), 4));
        sb.append(jsonField("keyEncipherment", ku.hasUsages(org.bouncycastle.asn1.x509.KeyUsage.keyEncipherment), 4));
        sb.append(jsonField("dataEncipherment", ku.hasUsages(org.bouncycastle.asn1.x509.KeyUsage.dataEncipherment), 4));
        sb.append(jsonField("keyAgreement", ku.hasUsages(org.bouncycastle.asn1.x509.KeyUsage.keyAgreement), 4));
        sb.append(jsonField("keyCertSign", ku.hasUsages(org.bouncycastle.asn1.x509.KeyUsage.keyCertSign), 4));
        sb.append(jsonField("cRLSign", ku.hasUsages(org.bouncycastle.asn1.x509.KeyUsage.cRLSign), 4));
        sb.append(jsonField("encipherOnly", ku.hasUsages(org.bouncycastle.asn1.x509.KeyUsage.encipherOnly), 4));
        sb.append(jsonFieldLast("decipherOnly", ku.hasUsages(org.bouncycastle.asn1.x509.KeyUsage.decipherOnly), 4));
        sb.append("      }");
        return sb.toString();
    }

    private static String formatGeneralNames(GeneralNames gns) {
        List<String> entries = new ArrayList<>();
        for (GeneralName gn : gns.getNames()) {
            String type;
            switch (gn.getTagNo()) {
                case GeneralName.dNSName: type = "DNS"; break;
                case GeneralName.iPAddress: type = "IP"; break;
                case GeneralName.rfc822Name: type = "email"; break;
                case GeneralName.uniformResourceIdentifier: type = "URI"; break;
                case GeneralName.directoryName: type = "directoryName"; break;
                default: type = "other(" + gn.getTagNo() + ")"; break;
            }
            entries.add(String.format("{ \"type\": \"%s\", \"value\": \"%s\" }",
                    type, escapeJson(gn.getName().toString())));
        }
        return "[ " + String.join(", ", entries) + " ]";
    }

    private static String getExtensionName(String oid) {
        Map<String, String> names = new HashMap<>();
        names.put(Extension.basicConstraints.getId(), "basicConstraints");
        names.put(Extension.keyUsage.getId(), "keyUsage");
        names.put(Extension.extendedKeyUsage.getId(), "extendedKeyUsage");
        names.put(Extension.subjectAlternativeName.getId(), "subjectAlternativeName");
        names.put(Extension.subjectKeyIdentifier.getId(), "subjectKeyIdentifier");
        names.put(Extension.authorityKeyIdentifier.getId(), "authorityKeyIdentifier");
        names.put(Extension.cRLDistributionPoints.getId(), "cRLDistributionPoints");
        names.put(Extension.authorityInfoAccess.getId(), "authorityInfoAccess");
        names.put(Extension.certificatePolicies.getId(), "certificatePolicies");
        names.put(Extension.nameConstraints.getId(), "nameConstraints");
        names.put(Extension.policyConstraints.getId(), "policyConstraints");
        names.put(Extension.inhibitAnyPolicy.getId(), "inhibitAnyPolicy");
        return names.getOrDefault(oid, oid);
    }

    private static String getEkuName(KeyPurposeId kp) {
        if (kp.equals(KeyPurposeId.id_kp_serverAuth)) return "serverAuth";
        if (kp.equals(KeyPurposeId.id_kp_clientAuth)) return "clientAuth";
        if (kp.equals(KeyPurposeId.id_kp_codeSigning)) return "codeSigning";
        if (kp.equals(KeyPurposeId.id_kp_emailProtection)) return "emailProtection";
        if (kp.equals(KeyPurposeId.id_kp_timeStamping)) return "timeStamping";
        if (kp.equals(KeyPurposeId.id_kp_OCSPSigning)) return "ocspSigning";
        return kp.getId();
    }

    /**
     * Validate a certificate chain.
     * The chain should be ordered from end-entity to root.
     */
    public static String validateChain(List<X509Certificate> chain) {
        StringBuilder sb = new StringBuilder();
        sb.append("{\n");
        sb.append(jsonField("chainLength", chain.size(), 1));

        boolean valid = true;
        List<String> errors = new ArrayList<>();
        List<String> certResults = new ArrayList<>();

        Date now = new Date();

        for (int i = 0; i < chain.size(); i++) {
            X509Certificate cert = chain.get(i);
            StringBuilder certSb = new StringBuilder();
            certSb.append("    {\n");
            certSb.append(jsonField("index", i, 3));
            certSb.append(jsonField("subject", cert.getSubjectX500Principal().getName(), 3));
            certSb.append(jsonField("issuer", cert.getIssuerX500Principal().getName(), 3));

            // Validity check
            boolean validPeriod = true;
            try {
                cert.checkValidity(now);
            } catch (CertificateExpiredException e) {
                validPeriod = false;
                errors.add("Certificate at index " + i + " has expired");
                valid = false;
            } catch (CertificateNotYetValidException e) {
                validPeriod = false;
                errors.add("Certificate at index " + i + " is not yet valid");
                valid = false;
            }
            certSb.append(jsonField("validityCheck", validPeriod, 3));

            // Signature verification
            boolean sigOk = false;
            if (i < chain.size() - 1) {
                X509Certificate issuerCert = chain.get(i + 1);
                try {
                    cert.verify(issuerCert.getPublicKey(), "BC");
                    sigOk = true;
                } catch (Exception e) {
                    errors.add("Signature verification failed at index " + i + ": " + e.getMessage());
                    valid = false;
                }
            } else {
                // Root: check self-signed
                try {
                    cert.verify(cert.getPublicKey(), "BC");
                    sigOk = true;
                    certSb.append(jsonField("selfSigned", true, 3));
                } catch (Exception e) {
                    certSb.append(jsonField("selfSigned", false, 3));
                    errors.add("Root certificate at index " + i + " is not self-signed");
                    valid = false;
                }
            }
            certSb.append(jsonFieldLast("signatureCheck", sigOk, 3));

            // Check CA basic constraints for issuer certs
            if (i < chain.size() - 1) {
                X509Certificate issuerCert = chain.get(i + 1);
                int bc = issuerCert.getBasicConstraints();
                if (bc < 0) {
                    errors.add("Certificate at index " + (i + 1) + " is not a CA");
                    valid = false;
                }
            }

            certSb.append("    }");
            certResults.add(certSb.toString());
        }

        sb.append("  \"certificates\": [\n");
        sb.append(String.join(",\n", certResults)).append("\n");
        sb.append("  ],\n");

        sb.append(jsonField("valid", valid, 1));

        sb.append("  \"errors\": [");
        if (!errors.isEmpty()) {
            sb.append("\n");
            List<String> quotedErrors = errors.stream()
                    .map(e -> "    \"" + escapeJson(e) + "\"")
                    .collect(Collectors.toList());
            sb.append(String.join(",\n", quotedErrors)).append("\n  ");
        }
        sb.append("]\n");

        sb.append("}");
        return sb.toString();
    }

    // JSON formatting helpers
    private static String jsonField(String key, Object value, int indent) {
        String pad = "  ".repeat(indent);
        if (value instanceof String) {
            return pad + "\"" + key + "\": \"" + escapeJson((String) value) + "\",\n";
        } else {
            return pad + "\"" + key + "\": " + value + ",\n";
        }
    }

    private static String jsonFieldLast(String key, Object value, int indent) {
        String pad = "  ".repeat(indent);
        if (value instanceof String) {
            return pad + "\"" + key + "\": \"" + escapeJson((String) value) + "\"\n";
        } else {
            return pad + "\"" + key + "\": " + value + "\n";
        }
    }

    private static String escapeJson(String s) {
        if (s == null) return "null";
        return s.replace("\\", "\\\\")
                .replace("\"", "\\\"")
                .replace("\n", "\\n")
                .replace("\r", "\\r")
                .replace("\t", "\\t");
    }

    public static void main(String[] args) {
        if (args.length < 1) {
            System.out.println("Usage: java CertParser <certificate_file> [chain_file]");
            System.out.println();
            System.out.println("Options:");
            System.out.println("  certificate_file  Path to a PEM or DER encoded X.509 certificate");
            System.out.println("  chain_file        Optional PEM file with certificate chain for validation");
            System.exit(1);
        }

        try {
            String certFile = args[0];
            System.out.println("Parsing certificate: " + certFile);
            X509Certificate cert = loadCertificate(certFile);
            System.out.println(parseCertificate(cert));

            if (args.length > 1) {
                String chainFile = args[1];
                System.out.println("\nValidating certificate chain: " + chainFile);
                List<X509Certificate> chain = loadCertificateChain(chainFile);
                System.out.println(validateChain(chain));
            }
        } catch (Exception e) {
            System.err.println("Error: " + e.getMessage());
            e.printStackTrace();
            System.exit(1);
        }
    }
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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>com.certparser</groupId>
    <artifactId>x509-cert-parser</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <name>X.509 Certificate Parser</name>
    <description>Parses X.509 certificates, extracts fields/extensions, and validates chains</description>

    <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.77</version>
        </dependency>
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcpkix-jdk18on</artifactId>
            <version>1.77</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.3.0</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>CertParser</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.5.1</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
README.md
# X.509 Certificate Parser (Java - Trial 1)

An X.509 certificate parser using Bouncy Castle that reads PEM and DER encoded certificates, extracts all fields and extensions, and validates certificate chains.

## Dependencies

- **Bouncy Castle bcprov-jdk18on** (1.77): Cryptographic provider offering comprehensive X.509 certificate handling and ASN.1 parsing.
- **Bouncy Castle bcpkix-jdk18on** (1.77): PKIX utilities for PEM parsing, certificate holder conversion, and certificate path validation.

## Build

```bash
mvn clean package
```

## Usage

### Parse a single certificate

```bash
java -jar target/x509-cert-parser-1.0.0.jar certificate.pem
```

### Parse and validate a chain

```bash
java -jar target/x509-cert-parser-1.0.0.jar certificate.pem chain.pem
```

## Features

- Parses PEM and DER encoded X.509 certificates
- Extracts subject, issuer, validity, serial number, and signature algorithm
- Extracts public key information (RSA, EC, DSA)
- Extracts all standard extensions (Basic Constraints, Key Usage, EKU, SAN, SKI, AKI, CRL DP, AIA, Certificate Policies)
- Computes SHA-256, SHA-1, and MD5 fingerprints
- Validates certificate chains with signature verification
- Checks validity periods and CA constraints
- JSON formatted output