← All tasks
pythonclaude-code/python-t1 #16Not a task: already works

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

envgap__claude-code__python-t1-16

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

01 / FAILURE SIGNATURE

As the study recorded it

No identifying execution failure has been captured.
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
requirements.txt
Reproduce
Awaiting issue-specific recipe
Run under trace
Awaiting a meaningful runtime command

03 / TASK AND FAILURE

claude-code/python-t1 #16 · read the task the agent was given
Claude Code wrote this python 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 Python project for a clean Ubuntu 22.04 machine with only Python 3.10+ installed. Include:
- Source code
- requirements.txt 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.

cert_parser.py
"""
X.509 Certificate Parser using the cryptography library.

Parses X.509 certificates in PEM and DER formats, extracts all fields
and extensions, and validates certificate chains.
"""

import sys
import json
import datetime
from pathlib import Path
from typing import List, Dict, Any, Optional, Union

from cryptography import x509
from cryptography.x509 import (
    Certificate,
    ExtensionNotFound,
    ExtensionOID,
    NameOID,
    AuthorityKeyIdentifier,
    SubjectKeyIdentifier,
    BasicConstraints,
    KeyUsage,
    ExtendedKeyUsage,
    SubjectAlternativeName,
    CRLDistributionPoints,
    AuthorityInformationAccess,
    CertificatePolicies,
    NameConstraints,
    InhibitAnyPolicy,
    PolicyConstraints,
)
from cryptography.x509.oid import ExtensionOID as ExtOID
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa, ec, ed25519, ed448, dsa, padding
from cryptography.exceptions import InvalidSignature


def load_certificate(file_path: str) -> Certificate:
    """Load an X.509 certificate from a PEM or DER file."""
    path = Path(file_path)
    data = path.read_bytes()

    # Try PEM first
    if b"-----BEGIN CERTIFICATE-----" in data:
        return x509.load_pem_x509_certificate(data)

    # Fall back to DER
    try:
        return x509.load_der_x509_certificate(data)
    except Exception:
        raise ValueError(f"Unable to parse certificate from {file_path}. "
                         "File is neither valid PEM nor DER format.")


def load_certificate_from_bytes(data: bytes) -> Certificate:
    """Load an X.509 certificate from raw bytes (PEM or DER)."""
    if b"-----BEGIN CERTIFICATE-----" in data:
        return x509.load_pem_x509_certificate(data)
    return x509.load_der_x509_certificate(data)


def _name_to_dict(name: x509.Name) -> Dict[str, str]:
    """Convert an x509.Name to a dictionary of OID-name to value."""
    result = {}
    oid_names = {
        NameOID.COMMON_NAME: "commonName",
        NameOID.COUNTRY_NAME: "countryName",
        NameOID.STATE_OR_PROVINCE_NAME: "stateOrProvinceName",
        NameOID.LOCALITY_NAME: "localityName",
        NameOID.ORGANIZATION_NAME: "organizationName",
        NameOID.ORGANIZATIONAL_UNIT_NAME: "organizationalUnitName",
        NameOID.EMAIL_ADDRESS: "emailAddress",
        NameOID.SERIAL_NUMBER: "serialNumber",
        NameOID.DOMAIN_COMPONENT: "domainComponent",
    }
    for attr in name:
        label = oid_names.get(attr.oid, attr.oid.dotted_string)
        if label in result:
            existing = result[label]
            if isinstance(existing, list):
                existing.append(attr.value)
            else:
                result[label] = [existing, attr.value]
        else:
            result[label] = attr.value
    return result


def _get_public_key_info(cert: Certificate) -> Dict[str, Any]:
    """Extract public key algorithm and size information."""
    pub_key = cert.public_key()
    info: Dict[str, Any] = {}

    if isinstance(pub_key, rsa.RSAPublicKey):
        info["algorithm"] = "RSA"
        info["keySize"] = pub_key.key_size
        numbers = pub_key.public_numbers()
        info["exponent"] = numbers.e
    elif isinstance(pub_key, ec.EllipticCurvePublicKey):
        info["algorithm"] = "EC"
        info["curve"] = pub_key.curve.name
        info["keySize"] = pub_key.curve.key_size
    elif isinstance(pub_key, dsa.DSAPublicKey):
        info["algorithm"] = "DSA"
        info["keySize"] = pub_key.key_size
    elif isinstance(pub_key, (ed25519.Ed25519PublicKey,)):
        info["algorithm"] = "Ed25519"
        info["keySize"] = 256
    elif isinstance(pub_key, (ed448.Ed448PublicKey,)):
        info["algorithm"] = "Ed448"
        info["keySize"] = 448
    else:
        info["algorithm"] = type(pub_key).__name__

    # Fingerprint of the public key in DER format
    pub_der = pub_key.public_bytes(
        serialization.Encoding.DER,
        serialization.PublicFormat.SubjectPublicKeyInfo
    )
    info["derHex"] = pub_der.hex()
    return info


def _extract_extension_value(ext) -> Any:
    """Convert an extension value object into a JSON-serializable structure."""
    val = ext.value

    if isinstance(val, BasicConstraints):
        return {"ca": val.ca, "pathLength": val.path_length}

    if isinstance(val, KeyUsage):
        return {
            "digitalSignature": val.digital_signature,
            "contentCommitment": val.content_commitment,
            "keyEncipherment": val.key_encipherment,
            "dataEncipherment": val.data_encipherment,
            "keyAgreement": val.key_agreement,
            "keyCertSign": val.key_cert_sign,
            "crlSign": val.crl_sign,
            "encipherOnly": val.encipher_only if val.key_agreement else None,
            "decipherOnly": val.decipher_only if val.key_agreement else None,
        }

    if isinstance(val, ExtendedKeyUsage):
        eku_names = {
            x509.oid.ExtendedKeyUsageOID.SERVER_AUTH: "serverAuth",
            x509.oid.ExtendedKeyUsageOID.CLIENT_AUTH: "clientAuth",
            x509.oid.ExtendedKeyUsageOID.CODE_SIGNING: "codeSigning",
            x509.oid.ExtendedKeyUsageOID.EMAIL_PROTECTION: "emailProtection",
            x509.oid.ExtendedKeyUsageOID.TIME_STAMPING: "timeStamping",
            x509.oid.ExtendedKeyUsageOID.OCSP_SIGNING: "ocspSigning",
        }
        return [eku_names.get(usage, usage.dotted_string) for usage in val]

    if isinstance(val, SubjectAlternativeName):
        san_entries = []
        for name in val:
            if isinstance(name, x509.DNSName):
                san_entries.append({"type": "DNS", "value": name.value})
            elif isinstance(name, x509.IPAddress):
                san_entries.append({"type": "IP", "value": str(name.value)})
            elif isinstance(name, x509.RFC822Name):
                san_entries.append({"type": "email", "value": name.value})
            elif isinstance(name, x509.UniformResourceIdentifier):
                san_entries.append({"type": "URI", "value": name.value})
            elif isinstance(name, x509.DirectoryName):
                san_entries.append({"type": "directoryName", "value": _name_to_dict(name.value)})
            else:
                san_entries.append({"type": type(name).__name__, "value": str(name.value)})
        return san_entries

    if isinstance(val, SubjectKeyIdentifier):
        return {"keyIdentifier": val.digest.hex()}

    if isinstance(val, AuthorityKeyIdentifier):
        result = {}
        if val.key_identifier is not None:
            result["keyIdentifier"] = val.key_identifier.hex()
        if val.authority_cert_serial_number is not None:
            result["authorityCertSerialNumber"] = val.authority_cert_serial_number
        if val.authority_cert_issuer is not None:
            result["authorityCertIssuer"] = [str(n) for n in val.authority_cert_issuer]
        return result

    if isinstance(val, CRLDistributionPoints):
        points = []
        for dp in val:
            entry = {}
            if dp.full_name:
                entry["fullName"] = [str(n.value) for n in dp.full_name]
            if dp.relative_name:
                entry["relativeName"] = _name_to_dict(dp.relative_name)
            if dp.crl_issuer:
                entry["crlIssuer"] = [str(n) for n in dp.crl_issuer]
            if dp.reasons:
                entry["reasons"] = [r.value for r in dp.reasons]
            points.append(entry)
        return points

    if isinstance(val, AuthorityInformationAccess):
        descriptions = []
        for desc in val:
            entry = {
                "accessMethod": "ocsp" if desc.access_method == x509.oid.AuthorityInformationAccessOID.OCSP
                else "caIssuers" if desc.access_method == x509.oid.AuthorityInformationAccessOID.CA_ISSUERS
                else desc.access_method.dotted_string,
                "accessLocation": str(desc.access_location.value)
            }
            descriptions.append(entry)
        return descriptions

    if isinstance(val, CertificatePolicies):
        policies = []
        for policy in val:
            entry = {"policyIdentifier": policy.policy_identifier.dotted_string}
            if policy.policy_qualifiers:
                qualifiers = []
                for q in policy.policy_qualifiers:
                    if isinstance(q, str):
                        qualifiers.append({"type": "cps", "value": q})
                    else:
                        qualifiers.append({"type": "userNotice", "value": str(q)})
                entry["policyQualifiers"] = qualifiers
            policies.append(entry)
        return policies

    if isinstance(val, NameConstraints):
        result = {}
        if val.permitted_subtrees:
            result["permitted"] = [str(s.value) for s in val.permitted_subtrees]
        if val.excluded_subtrees:
            result["excluded"] = [str(s.value) for s in val.excluded_subtrees]
        return result

    if isinstance(val, InhibitAnyPolicy):
        return {"skipCerts": val.skip_certs}

    if isinstance(val, PolicyConstraints):
        return {
            "requireExplicitPolicy": val.require_explicit_policy,
            "inhibitPolicyMapping": val.inhibit_policy_mapping,
        }

    # Fallback: return string representation
    return str(val)


def extract_extensions(cert: Certificate) -> List[Dict[str, Any]]:
    """Extract all extensions from the certificate."""
    extensions = []
    for ext in cert.extensions:
        ext_info = {
            "oid": ext.oid.dotted_string,
            "name": ext.oid._name if hasattr(ext.oid, '_name') else ext.oid.dotted_string,
            "critical": ext.critical,
            "value": _extract_extension_value(ext),
        }
        extensions.append(ext_info)
    return extensions


def extract_fingerprints(cert: Certificate) -> Dict[str, str]:
    """Compute certificate fingerprints."""
    return {
        "sha256": cert.fingerprint(hashes.SHA256()).hex(),
        "sha1": cert.fingerprint(hashes.SHA1()).hex(),
        "md5": cert.fingerprint(hashes.MD5()).hex(),
    }


def parse_certificate(cert: Certificate) -> Dict[str, Any]:
    """Parse a certificate and extract all fields into a dictionary."""
    result = {
        "version": cert.version.value,
        "serialNumber": format(cert.serial_number, 'x'),
        "signatureAlgorithm": cert.signature_algorithm_oid.dotted_string,
        "issuer": _name_to_dict(cert.issuer),
        "subject": _name_to_dict(cert.subject),
        "validity": {
            "notBefore": cert.not_valid_before_utc.isoformat(),
            "notAfter": cert.not_valid_after_utc.isoformat(),
        },
        "publicKeyInfo": _get_public_key_info(cert),
        "extensions": extract_extensions(cert),
        "fingerprints": extract_fingerprints(cert),
        "signatureValue": cert.signature.hex(),
    }
    return result


def validate_certificate_chain(chain: List[Certificate]) -> Dict[str, Any]:
    """
    Validate a certificate chain.

    The chain should be ordered from end-entity to root CA.
    Each certificate's signature is verified against the next certificate's public key.
    Validity periods are also checked.

    Returns a dictionary with validation results.
    """
    if not chain:
        return {"valid": False, "error": "Empty certificate chain"}

    results = {
        "chainLength": len(chain),
        "certificates": [],
        "valid": True,
        "errors": [],
    }

    now = datetime.datetime.now(datetime.timezone.utc)

    for i, cert in enumerate(chain):
        cert_result = {
            "index": i,
            "subject": _name_to_dict(cert.subject),
            "issuer": _name_to_dict(cert.issuer),
            "validityCheck": True,
            "signatureCheck": None,
        }

        # Check validity period
        if now < cert.not_valid_before_utc:
            cert_result["validityCheck"] = False
            msg = f"Certificate at index {i} is not yet valid"
            results["errors"].append(msg)
            results["valid"] = False
        elif now > cert.not_valid_after_utc:
            cert_result["validityCheck"] = False
            msg = f"Certificate at index {i} has expired"
            results["errors"].append(msg)
            results["valid"] = False

        # Verify signature against issuer (next cert in chain)
        if i < len(chain) - 1:
            issuer_cert = chain[i + 1]
            issuer_pub_key = issuer_cert.public_key()
            try:
                if isinstance(issuer_pub_key, rsa.RSAPublicKey):
                    issuer_pub_key.verify(
                        cert.signature,
                        cert.tbs_certificate_bytes,
                        padding.PKCS1v15(),
                        cert.signature_hash_algorithm,
                    )
                elif isinstance(issuer_pub_key, ec.EllipticCurvePublicKey):
                    issuer_pub_key.verify(
                        cert.signature,
                        cert.tbs_certificate_bytes,
                        ec.ECDSA(cert.signature_hash_algorithm),
                    )
                elif isinstance(issuer_pub_key, ed25519.Ed25519PublicKey):
                    issuer_pub_key.verify(
                        cert.signature,
                        cert.tbs_certificate_bytes,
                    )
                elif isinstance(issuer_pub_key, ed448.Ed448PublicKey):
                    issuer_pub_key.verify(
                        cert.signature,
                        cert.tbs_certificate_bytes,
                    )
                elif isinstance(issuer_pub_key, dsa.DSAPublicKey):
                    issuer_pub_key.verify(
                        cert.signature,
                        cert.tbs_certificate_bytes,
                        cert.signature_hash_algorithm,
                    )
                cert_result["signatureCheck"] = True
            except (InvalidSignature, Exception) as e:
                cert_result["signatureCheck"] = False
                msg = f"Signature verification failed for certificate at index {i}: {e}"
                results["errors"].append(msg)
                results["valid"] = False
        else:
            # Root certificate: check self-signature
            pub_key = cert.public_key()
            try:
                if isinstance(pub_key, rsa.RSAPublicKey):
                    pub_key.verify(
                        cert.signature,
                        cert.tbs_certificate_bytes,
                        padding.PKCS1v15(),
                        cert.signature_hash_algorithm,
                    )
                elif isinstance(pub_key, ec.EllipticCurvePublicKey):
                    pub_key.verify(
                        cert.signature,
                        cert.tbs_certificate_bytes,
                        ec.ECDSA(cert.signature_hash_algorithm),
                    )
                elif isinstance(pub_key, (ed25519.Ed25519PublicKey,)):
                    pub_key.verify(cert.signature, cert.tbs_certificate_bytes)
                elif isinstance(pub_key, (ed448.Ed448PublicKey,)):
                    pub_key.verify(cert.signature, cert.tbs_certificate_bytes)
                cert_result["signatureCheck"] = True
                cert_result["selfSigned"] = True
            except (InvalidSignature, Exception):
                cert_result["signatureCheck"] = False
                cert_result["selfSigned"] = False
                msg = f"Root certificate at index {i} is not self-signed or has invalid signature"
                results["errors"].append(msg)
                results["valid"] = False

        # Check basic constraints for CA certificates
        if i < len(chain) - 1:
            issuer_cert = chain[i + 1]
            try:
                bc = issuer_cert.extensions.get_extension_for_oid(ExtensionOID.BASIC_CONSTRAINTS)
                if not bc.value.ca:
                    msg = f"Certificate at index {i+1} is not a CA but signed certificate at index {i}"
                    results["errors"].append(msg)
                    results["valid"] = False
            except ExtensionNotFound:
                pass  # No basic constraints extension; may be valid for v1 certs

        results["certificates"].append(cert_result)

    return results


def load_chain_from_pem(file_path: str) -> List[Certificate]:
    """Load a chain of certificates from a PEM file containing multiple certs."""
    path = Path(file_path)
    data = path.read_bytes()

    certs = []
    # Split on certificate boundaries
    pem_certs = data.split(b"-----END CERTIFICATE-----")
    for pem_block in pem_certs:
        pem_block = pem_block.strip()
        if b"-----BEGIN CERTIFICATE-----" in pem_block:
            pem_block += b"\n-----END CERTIFICATE-----\n"
            cert = x509.load_pem_x509_certificate(pem_block)
            certs.append(cert)
    return certs


def main():
    """Main entry point for the certificate parser."""
    if len(sys.argv) < 2:
        print("Usage: python cert_parser.py <certificate_file> [chain_file]")
        print()
        print("Options:")
        print("  certificate_file  Path to a PEM or DER encoded X.509 certificate")
        print("  chain_file        Optional path to a PEM file with certificate chain")
        sys.exit(1)

    cert_file = sys.argv[1]

    # Parse the main certificate
    print(f"Parsing certificate: {cert_file}")
    cert = load_certificate(cert_file)
    parsed = parse_certificate(cert)
    print(json.dumps(parsed, indent=2, default=str))

    # If a chain file is provided, validate the chain
    if len(sys.argv) > 2:
        chain_file = sys.argv[2]
        print(f"\nValidating certificate chain: {chain_file}")
        chain = load_chain_from_pem(chain_file)
        validation = validate_certificate_chain(chain)
        print(json.dumps(validation, indent=2, default=str))


if __name__ == "__main__":
    main()
README.md
# X.509 Certificate Parser (Python - Trial 1)

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

## Dependencies

- **cryptography** (41.0.7): Comprehensive cryptographic library for Python providing X.509 certificate parsing, public key operations, and signature verification.

## Setup

```bash
pip install -r requirements.txt
```

## Usage

### Parse a single certificate

```bash
python cert_parser.py certificate.pem
```

### Parse a certificate and validate a chain

```bash
python cert_parser.py certificate.pem chain.pem
```

## Features

- Parses PEM and DER encoded X.509 certificates
- Extracts subject, issuer, validity period, serial number, and signature algorithm
- Extracts public key information (RSA, EC, DSA, Ed25519, Ed448)
- Extracts all extensions including:
  - Basic Constraints
  - Key Usage / Extended Key Usage
  - Subject Alternative Name
  - Authority/Subject Key Identifier
  - CRL Distribution Points
  - Authority Information Access
  - Certificate Policies
  - Name Constraints
  - Policy Constraints
- Computes SHA-256, SHA-1, and MD5 fingerprints
- Validates certificate chains with signature verification
- Checks certificate validity periods
- Verifies CA basic constraints in chain
- Outputs results as structured JSON
requirements.txt
cryptography==41.0.7