X.509 Certificate Parser (python, written by Codex)
envgap__codex__python-t1-16
Written by a coding agent; not on GitHubWritten 2026-03-02
01 / FAILURE SIGNATURE
As the study recorded it
ValueError: encipher_only is undefined unless key_agreement is true
Not a benchmark task.
- Its repair changed source code, so it is not an environment task.
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
codex/python-t1 #16 · read the task the agent was given
Codex wrote this python 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 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.
README.md
# X.509 Certificate Parser (Python) Parses X.509 certificates (PEM/DER/bundles/remote host), extracts fields/extensions, checks expiration, and validates chain signatures. ## Requirements - Ubuntu 22.04 - Python 3.10+ ## Dependencies (Pinned) - `cryptography==44.0.0` - `cffi==1.17.1` - `pycparser==2.22` ## Setup ```bash python -m venv .venv source .venv/bin/activate pip install -r requirements.txt ``` ## Run ```bash python src/main.py ./cert.pem python src/main.py ./bundle.pem --format json python src/main.py ./cert.der --format csv --output ./report.csv python src/main.py --host example.com:443 --format text python src/main.py ``` ## Features - PEM/DER auto-detection - PEM bundle parsing - Full field extraction - X.509 extension parsing: - SAN - Key Usage - Extended Key Usage - Basic Constraints - SKID/AKID - CRL Distribution Points - Expiration status reporting - Chain signature validation across cert list - Remote host certificate fetch (`--host`) - Output formats: text/json/csv - Output file support (`--output`) - No-args demo generates CA + signed leaf and validates chain
requirements.txt
cryptography==44.0.0 cffi==1.17.1 pycparser==2.22
src/main.py
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import csv
import hashlib
import json
import re
import socket
import ssl
from datetime import datetime, timezone, timedelta
from pathlib import Path
from typing import Any
from cryptography import x509
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding, rsa, ec
from cryptography.x509.oid import ExtensionOID, NameOID
def parse_args() -> argparse.Namespace:
p = argparse.ArgumentParser(description="X.509 Certificate Parser")
p.add_argument("path", nargs="?")
p.add_argument("--host", help="hostname[:port]")
p.add_argument("--port", type=int, default=443)
p.add_argument("--format", default="text", choices=["text", "json", "csv"])
p.add_argument("--output")
return p.parse_args()
def load_certs_from_file(path: Path) -> list[x509.Certificate]:
data = path.read_bytes()
text = data.decode("utf-8", errors="ignore")
if "BEGIN CERTIFICATE" in text:
certs = []
for block in re.findall(r"-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----", text):
certs.append(x509.load_pem_x509_certificate(block.encode("utf-8")))
if not certs:
raise ValueError("No PEM certificates found.")
return certs
return [x509.load_der_x509_certificate(data)]
def fetch_host_cert(host: str, port: int) -> list[x509.Certificate]:
ctx = ssl.create_default_context()
with socket.create_connection((host, port), timeout=10) as sock:
with ctx.wrap_socket(sock, server_hostname=host) as ssock:
leaf_der = ssock.getpeercert(binary_form=True)
if not leaf_der:
raise ValueError("Could not fetch certificate from host.")
return [x509.load_der_x509_certificate(leaf_der)]
def name_to_str(name: x509.Name) -> str:
return ", ".join([f"{attr.oid._name or attr.oid.dotted_string}={attr.value}" for attr in name])
def key_info(pub) -> dict[str, Any]:
if isinstance(pub, rsa.RSAPublicKey):
return {"algorithm": "RSA", "size": pub.key_size}
if isinstance(pub, ec.EllipticCurvePublicKey):
return {"algorithm": "EC", "size": pub.key_size}
return {"algorithm": type(pub).__name__, "size": None}
def parse_extensions(cert: x509.Certificate) -> dict[str, Any]:
out: dict[str, Any] = {
"subject_alt_names": [],
"key_usage": {},
"extended_key_usage": [],
"basic_constraints": {},
"subject_key_identifier": None,
"authority_key_identifier": None,
"crl_distribution_points": [],
}
try:
san = cert.extensions.get_extension_for_oid(ExtensionOID.SUBJECT_ALTERNATIVE_NAME).value
out["subject_alt_names"] = [str(x.value) for x in san]
except x509.ExtensionNotFound:
pass
try:
ku = cert.extensions.get_extension_for_oid(ExtensionOID.KEY_USAGE).value
out["key_usage"] = {
"digital_signature": ku.digital_signature,
"content_commitment": ku.content_commitment,
"key_encipherment": ku.key_encipherment,
"data_encipherment": ku.data_encipherment,
"key_agreement": ku.key_agreement,
"key_cert_sign": ku.key_cert_sign,
"crl_sign": ku.crl_sign,
"encipher_only": ku.encipher_only,
"decipher_only": ku.decipher_only,
}
except x509.ExtensionNotFound:
pass
try:
eku = cert.extensions.get_extension_for_oid(ExtensionOID.EXTENDED_KEY_USAGE).value
out["extended_key_usage"] = [oid.dotted_string for oid in eku]
except x509.ExtensionNotFound:
pass
try:
bc = cert.extensions.get_extension_for_oid(ExtensionOID.BASIC_CONSTRAINTS).value
out["basic_constraints"] = {"ca": bc.ca, "path_length": bc.path_length}
except x509.ExtensionNotFound:
pass
try:
skid = cert.extensions.get_extension_for_oid(ExtensionOID.SUBJECT_KEY_IDENTIFIER).value
out["subject_key_identifier"] = skid.digest.hex()
except x509.ExtensionNotFound:
pass
try:
akid = cert.extensions.get_extension_for_oid(ExtensionOID.AUTHORITY_KEY_IDENTIFIER).value
out["authority_key_identifier"] = akid.key_identifier.hex() if akid.key_identifier else None
except x509.ExtensionNotFound:
pass
try:
crl = cert.extensions.get_extension_for_oid(ExtensionOID.CRL_DISTRIBUTION_POINTS).value
uris: list[str] = []
for dp in crl:
if dp.full_name:
for n in dp.full_name:
uris.append(str(n.value))
out["crl_distribution_points"] = uris
except x509.ExtensionNotFound:
pass
return out
def expiration_info(cert: x509.Certificate) -> dict[str, Any]:
now = datetime.now(timezone.utc)
na = cert.not_valid_after.replace(tzinfo=timezone.utc)
days = (na - now).days
if days >= 0:
return {"expired": False, "days_until_expiration": days, "days_since_expiration": 0}
return {"expired": True, "days_until_expiration": 0, "days_since_expiration": abs(days)}
def analyze_cert(cert: x509.Certificate) -> dict[str, Any]:
der = cert.public_bytes(serialization.Encoding.DER)
sig_hash = cert.signature_hash_algorithm.name if cert.signature_hash_algorithm else "unknown"
return {
"version": cert.version.name,
"serial_number": format(cert.serial_number, "x"),
"issuer": name_to_str(cert.issuer),
"subject": name_to_str(cert.subject),
"validity": {
"not_before": cert.not_valid_before.replace(tzinfo=timezone.utc).isoformat(),
"not_after": cert.not_valid_after.replace(tzinfo=timezone.utc).isoformat(),
},
"expiration": expiration_info(cert),
"public_key": key_info(cert.public_key()),
"signature_algorithm": sig_hash,
"fingerprints": {
"sha1": hashlib.sha1(der).hexdigest(),
"sha256": hashlib.sha256(der).hexdigest(),
},
"extensions": parse_extensions(cert),
}
def verify_child_signed_by_issuer(child: x509.Certificate, issuer: x509.Certificate) -> bool:
pub = issuer.public_key()
try:
if isinstance(pub, rsa.RSAPublicKey):
pub.verify(child.signature, child.tbs_certificate_bytes, padding.PKCS1v15(), child.signature_hash_algorithm)
elif isinstance(pub, ec.EllipticCurvePublicKey):
pub.verify(child.signature, child.tbs_certificate_bytes, ec.ECDSA(child.signature_hash_algorithm))
else:
return False
return True
except InvalidSignature:
return False
def validate_chain(certs: list[x509.Certificate]) -> dict[str, Any]:
steps = []
ok = True
for i in range(len(certs) - 1):
valid = verify_child_signed_by_issuer(certs[i], certs[i + 1])
steps.append({
"index": i,
"child_subject": name_to_str(certs[i].subject),
"issuer_subject": name_to_str(certs[i + 1].subject),
"valid_signature": valid,
})
if not valid:
ok = False
return {"valid": ok, "steps": steps}
def generate_demo_chain() -> list[x509.Certificate]:
ca_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
leaf_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
now = datetime.now(timezone.utc)
ca_subject = x509.Name([
x509.NameAttribute(NameOID.COMMON_NAME, "Demo Root CA"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "TMLR Demo"),
])
ca_cert = (
x509.CertificateBuilder()
.subject_name(ca_subject)
.issuer_name(ca_subject)
.public_key(ca_key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(now - timedelta(minutes=1))
.not_valid_after(now + timedelta(days=365))
.add_extension(x509.BasicConstraints(ca=True, path_length=1), critical=True)
.add_extension(x509.KeyUsage(
digital_signature=False, content_commitment=False, key_encipherment=False, data_encipherment=False,
key_agreement=False, key_cert_sign=True, crl_sign=True, encipher_only=False, decipher_only=False
), critical=True)
.add_extension(x509.SubjectKeyIdentifier.from_public_key(ca_key.public_key()), critical=False)
.sign(private_key=ca_key, algorithm=hashes.SHA256())
)
leaf_subject = x509.Name([
x509.NameAttribute(NameOID.COMMON_NAME, "demo.local"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "TMLR Demo"),
])
leaf_cert = (
x509.CertificateBuilder()
.subject_name(leaf_subject)
.issuer_name(ca_subject)
.public_key(leaf_key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(now - timedelta(minutes=1))
.not_valid_after(now + timedelta(days=180))
.add_extension(x509.BasicConstraints(ca=False, path_length=None), critical=True)
.add_extension(x509.KeyUsage(
digital_signature=True, content_commitment=False, key_encipherment=True, data_encipherment=False,
key_agreement=False, key_cert_sign=False, crl_sign=False, encipher_only=False, decipher_only=False
), critical=True)
.add_extension(x509.ExtendedKeyUsage([x509.oid.ExtendedKeyUsageOID.SERVER_AUTH]), critical=False)
.add_extension(x509.SubjectAlternativeName([x509.DNSName("demo.local"), x509.DNSName("www.demo.local")]), critical=False)
.add_extension(x509.AuthorityKeyIdentifier.from_issuer_public_key(ca_key.public_key()), critical=False)
.add_extension(x509.SubjectKeyIdentifier.from_public_key(leaf_key.public_key()), critical=False)
.add_extension(x509.CRLDistributionPoints([
x509.DistributionPoint(
full_name=[x509.UniformResourceIdentifier("http://example.com/demo.crl")],
relative_name=None,
reasons=None,
crl_issuer=None,
)
]), critical=False)
.sign(private_key=ca_key, algorithm=hashes.SHA256())
)
return [leaf_cert, ca_cert]
def render_text(result: dict[str, Any]) -> str:
lines = []
for i, cert in enumerate(result["certificates"], start=1):
lines.append(f"Certificate #{i}")
lines.append(f" Subject: {cert['subject']}")
lines.append(f" Issuer : {cert['issuer']}")
lines.append(f" Version: {cert['version']}")
lines.append(f" Serial : {cert['serial_number']}")
lines.append(f" Validity: {cert['validity']['not_before']} -> {cert['validity']['not_after']}")
lines.append(f" Signature Algorithm: {cert['signature_algorithm']}")
lines.append(f" Public Key: {cert['public_key']['algorithm']} {cert['public_key']['size']}")
lines.append(f" SHA-1 : {cert['fingerprints']['sha1']}")
lines.append(f" SHA-256: {cert['fingerprints']['sha256']}")
lines.append(
f" Expired: {cert['expiration']['expired']} | Days until: {cert['expiration']['days_until_expiration']} | Days since: {cert['expiration']['days_since_expiration']}"
)
lines.append(f" SANs: {'; '.join(cert['extensions']['subject_alt_names'])}")
lines.append(f" Key Usage: {', '.join([k for k, v in cert['extensions']['key_usage'].items() if v])}")
lines.append(f" Extended Key Usage: {', '.join(cert['extensions']['extended_key_usage'])}")
lines.append(f" Basic Constraints: {cert['extensions']['basic_constraints']}")
lines.append(f" Subject Key ID: {cert['extensions']['subject_key_identifier']}")
lines.append(f" Authority Key ID: {cert['extensions']['authority_key_identifier']}")
lines.append(f" CRL DP: {'; '.join(cert['extensions']['crl_distribution_points'])}")
lines.append("")
lines.append(f"Chain validation: {result['chain_validation']['valid']}")
for s in result["chain_validation"]["steps"]:
lines.append(f" [{s['index']}] {'OK' if s['valid_signature'] else 'FAIL'} :: {s['child_subject']} <- {s['issuer_subject']}")
return "\n".join(lines) + "\n"
def render_csv(result: dict[str, Any]) -> str:
from io import StringIO
buf = StringIO()
writer = csv.writer(buf)
writer.writerow([
"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",
])
for i, cert in enumerate(result["certificates"], start=1):
writer.writerow([
i, cert["subject"], cert["issuer"], cert["serial_number"], cert["validity"]["not_before"], cert["validity"]["not_after"],
cert["expiration"]["expired"], cert["expiration"]["days_until_expiration"], cert["expiration"]["days_since_expiration"],
cert["public_key"]["algorithm"], cert["public_key"]["size"], cert["signature_algorithm"],
cert["fingerprints"]["sha1"], cert["fingerprints"]["sha256"],
])
return buf.getvalue()
def main() -> int:
args = parse_args()
if args.host:
host, *port = args.host.split(":")
p = int(port[0]) if port else args.port
certs = fetch_host_cert(host, p)
source = f"host:{host}:{p}"
elif args.path:
certs = load_certs_from_file(Path(args.path).resolve())
source = str(Path(args.path).resolve())
else:
certs = generate_demo_chain()
source = "demo-generated"
result = {
"source": source,
"certificate_count": len(certs),
"certificates": [analyze_cert(c) for c in certs],
"chain_validation": validate_chain(certs),
}
if args.format == "json":
out = json.dumps(result, indent=2)
elif args.format == "csv":
out = render_csv(result)
else:
out = render_text(result)
if args.output:
out_path = Path(args.output).resolve()
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(out + ("" if out.endswith("\n") else "\n"), encoding="utf-8")
print(out, end="" if out.endswith("\n") else "\n")
return 0
if __name__ == "__main__":
raise SystemExit(main())