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