X.509 Certificate Parser (javascript, written by Claude Code)
envgap__claude-code__javascript-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
package.json- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / TASK AND FAILURE
claude-code/javascript-t1 #16 · read the task the agent was given
Claude Code wrote this javascript 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 JavaScript project for a clean Ubuntu 22.04 machine with only Node.js 20+ (LTS) installed. Include: - Source code - package.json 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.
package.json
{
"name": "x509-cert-parser",
"version": "1.0.0",
"description": "X.509 certificate parser that extracts fields/extensions and validates chains",
"main": "parser.js",
"scripts": {
"start": "node parser.js",
"parse": "node parser.js"
},
"keywords": ["x509", "certificate", "parser", "pem", "der", "ssl", "tls"],
"license": "MIT",
"dependencies": {
"node-forge": "1.3.1"
}
}
parser.js
/**
* X.509 Certificate Parser using node-forge.
*
* Parses X.509 certificates in PEM and DER formats, extracts all fields
* and extensions, and validates certificate chains.
*/
const forge = require("node-forge");
const fs = require("fs");
const path = require("path");
/**
* Load an X.509 certificate from a file (PEM or DER).
* @param {string} filePath - Path to the certificate file.
* @returns {forge.pki.Certificate} The parsed certificate.
*/
function loadCertificate(filePath) {
const data = fs.readFileSync(filePath);
const text = data.toString("utf8");
if (text.includes("-----BEGIN CERTIFICATE-----")) {
return forge.pki.certificateFromPem(text);
}
// DER format: convert binary to forge ASN.1
const asn1 = forge.asn1.fromDer(forge.util.createBuffer(data));
return forge.pki.certificateFromAsn1(asn1);
}
/**
* Load a certificate chain from a PEM file with multiple certificates.
* @param {string} filePath - Path to the PEM chain file.
* @returns {forge.pki.Certificate[]} Array of certificates, end-entity first.
*/
function loadCertificateChain(filePath) {
const text = fs.readFileSync(filePath, "utf8");
const certs = [];
const regex = /-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g;
let match;
while ((match = regex.exec(text)) !== null) {
certs.push(forge.pki.certificateFromPem(match[0]));
}
return certs;
}
/**
* Convert a forge distinguished name to a plain object.
* @param {Object} dn - forge distinguished name.
* @returns {Object} Key-value pairs of the DN attributes.
*/
function dnToObject(dn) {
const result = {};
const nameMap = {
"2.5.4.3": "commonName",
"2.5.4.6": "countryName",
"2.5.4.8": "stateOrProvinceName",
"2.5.4.7": "localityName",
"2.5.4.10": "organizationName",
"2.5.4.11": "organizationalUnitName",
"1.2.840.113549.1.9.1": "emailAddress",
"2.5.4.5": "serialNumber",
"0.9.2342.19200300.100.1.25": "domainComponent",
};
for (const attr of dn.attributes) {
const name =
attr.shortName || nameMap[attr.type] || attr.type;
if (result[name]) {
if (Array.isArray(result[name])) {
result[name].push(attr.value);
} else {
result[name] = [result[name], attr.value];
}
} else {
result[name] = attr.value;
}
}
return result;
}
/**
* Extract public key information from a certificate.
* @param {forge.pki.Certificate} cert - The certificate.
* @returns {Object} Public key details.
*/
function getPublicKeyInfo(cert) {
const pubKey = cert.publicKey;
const info = {};
if (pubKey.n && pubKey.e) {
// RSA key
info.algorithm = "RSA";
info.keySize = pubKey.n.bitLength();
info.exponent = pubKey.e.intValue();
} else if (pubKey.type === "EC" || pubKey.curve) {
info.algorithm = "EC";
if (pubKey.curve) {
info.curve = pubKey.curve;
}
} else {
info.algorithm = pubKey.type || "Unknown";
}
// Compute public key fingerprint
const pubKeyAsn1 = forge.pki.publicKeyToAsn1(pubKey);
const pubKeyDer = forge.asn1.toDer(pubKeyAsn1).getBytes();
const md = forge.md.sha256.create();
md.update(pubKeyDer);
info.publicKeyFingerprint = md.digest().toHex();
return info;
}
/**
* Parse an extension value into a readable structure.
* @param {Object} ext - forge extension object.
* @returns {*} Parsed extension value.
*/
function parseExtensionValue(ext) {
const name = ext.name;
if (name === "basicConstraints") {
return {
cA: ext.cA || false,
pathLenConstraint: ext.pathLenConstraint !== undefined ? ext.pathLenConstraint : null,
};
}
if (name === "keyUsage") {
return {
digitalSignature: ext.digitalSignature || false,
nonRepudiation: ext.nonRepudiation || false,
keyEncipherment: ext.keyEncipherment || false,
dataEncipherment: ext.dataEncipherment || false,
keyAgreement: ext.keyAgreement || false,
keyCertSign: ext.keyCertSign || false,
cRLSign: ext.cRLSign || false,
encipherOnly: ext.encipherOnly || false,
decipherOnly: ext.decipherOnly || false,
};
}
if (name === "extKeyUsage") {
const usages = [];
if (ext.serverAuth) usages.push("serverAuth");
if (ext.clientAuth) usages.push("clientAuth");
if (ext.codeSigning) usages.push("codeSigning");
if (ext.emailProtection) usages.push("emailProtection");
if (ext.timeStamping) usages.push("timeStamping");
return usages;
}
if (name === "subjectAltName") {
return (ext.altNames || []).map((alt) => {
const typeMap = {
1: "email",
2: "DNS",
6: "URI",
7: "IP",
};
return {
type: typeMap[alt.type] || `type${alt.type}`,
value: alt.value || alt.ip || String(alt),
};
});
}
if (name === "subjectKeyIdentifier") {
return {
keyIdentifier: ext.subjectKeyIdentifier,
};
}
if (name === "authorityKeyIdentifier") {
const result = {};
if (ext.keyIdentifier) {
result.keyIdentifier = forge.util.bytesToHex(ext.keyIdentifier);
}
if (ext.serialNumber) {
result.authorityCertSerialNumber = ext.serialNumber;
}
return result;
}
if (name === "cRLDistributionPoints") {
// node-forge stores CRL DPs in ext value as array
if (ext.value && Array.isArray(ext.value)) {
return ext.value;
}
return ext;
}
if (name === "authorityInfoAccess") {
if (ext.value && Array.isArray(ext.value)) {
return ext.value;
}
return ext;
}
if (name === "certificatePolicies") {
if (ext.value && Array.isArray(ext.value)) {
return ext.value;
}
return ext;
}
// For unrecognized extensions, return whatever forge gives us
if (ext.value !== undefined) {
return ext.value;
}
return String(ext);
}
/**
* Extract all extensions from a certificate.
* @param {forge.pki.Certificate} cert - The certificate.
* @returns {Array} Array of extension objects.
*/
function extractExtensions(cert) {
const extensions = [];
for (const ext of cert.extensions) {
extensions.push({
oid: ext.id,
name: ext.name || ext.id,
critical: ext.critical || false,
value: parseExtensionValue(ext),
});
}
return extensions;
}
/**
* Compute certificate fingerprints.
* @param {forge.pki.Certificate} cert - The certificate.
* @returns {Object} Fingerprints in hex format.
*/
function computeFingerprints(cert) {
const certAsn1 = forge.pki.certificateToAsn1(cert);
const certDer = forge.asn1.toDer(certAsn1).getBytes();
const sha256 = forge.md.sha256.create();
sha256.update(certDer);
const sha1 = forge.md.sha1.create();
sha1.update(certDer);
const md5 = forge.md.md5.create();
md5.update(certDer);
return {
sha256: sha256.digest().toHex(),
sha1: sha1.digest().toHex(),
md5: md5.digest().toHex(),
};
}
/**
* Parse a certificate and return all fields as a structured object.
* @param {forge.pki.Certificate} cert - The certificate.
* @returns {Object} Parsed certificate data.
*/
function parseCertificate(cert) {
return {
version: cert.version + 1, // forge uses 0-indexed
serialNumber: cert.serialNumber,
signatureAlgorithm: forge.pki.oids[cert.signatureOid] || cert.signatureOid,
issuer: dnToObject(cert.issuer),
subject: dnToObject(cert.subject),
validity: {
notBefore: cert.validity.notBefore.toISOString(),
notAfter: cert.validity.notAfter.toISOString(),
},
publicKeyInfo: getPublicKeyInfo(cert),
extensions: extractExtensions(cert),
fingerprints: computeFingerprints(cert),
signatureValue: forge.util.bytesToHex(
forge.util.binary.raw.decode(cert.signature)
),
};
}
/**
* Validate a certificate chain.
* Chain should be ordered from end-entity to root CA.
* @param {forge.pki.Certificate[]} chain - Array of certificates.
* @returns {Object} Validation results.
*/
function validateCertificateChain(chain) {
if (!chain || chain.length === 0) {
return { valid: false, error: "Empty certificate chain" };
}
const now = new Date();
const results = {
chainLength: chain.length,
certificates: [],
valid: true,
errors: [],
};
for (let i = 0; i < chain.length; i++) {
const cert = chain[i];
const certResult = {
index: i,
subject: dnToObject(cert.subject),
issuer: dnToObject(cert.issuer),
validityCheck: true,
signatureCheck: null,
};
// Check validity period
if (now < cert.validity.notBefore) {
certResult.validityCheck = false;
results.errors.push(`Certificate at index ${i} is not yet valid`);
results.valid = false;
} else if (now > cert.validity.notAfter) {
certResult.validityCheck = false;
results.errors.push(`Certificate at index ${i} has expired`);
results.valid = false;
}
// Verify signature
if (i < chain.length - 1) {
const issuerCert = chain[i + 1];
try {
const verified = issuerCert.verify(cert);
certResult.signatureCheck = verified;
if (!verified) {
results.errors.push(
`Signature verification failed for certificate at index ${i}`
);
results.valid = false;
}
} catch (e) {
certResult.signatureCheck = false;
results.errors.push(
`Signature verification error at index ${i}: ${e.message}`
);
results.valid = false;
}
} else {
// Root: check self-signed
try {
const selfSigned = cert.verify(cert);
certResult.signatureCheck = selfSigned;
certResult.selfSigned = selfSigned;
if (!selfSigned) {
results.errors.push(
`Root certificate at index ${i} is not self-signed`
);
results.valid = false;
}
} catch (e) {
certResult.signatureCheck = false;
certResult.selfSigned = false;
results.errors.push(
`Root certificate self-signature check failed: ${e.message}`
);
results.valid = false;
}
}
// Check basic constraints for CA certs
if (i < chain.length - 1) {
const issuerCert = chain[i + 1];
const bcExt = issuerCert.extensions.find(
(ext) => ext.name === "basicConstraints"
);
if (bcExt && !bcExt.cA) {
results.errors.push(
`Certificate at index ${i + 1} is not a CA but signed certificate at index ${i}`
);
results.valid = false;
}
}
results.certificates.push(certResult);
}
return results;
}
// CLI interface
function main() {
const args = process.argv.slice(2);
if (args.length < 1) {
console.log("Usage: node parser.js <certificate_file> [chain_file]");
console.log();
console.log("Options:");
console.log(
" certificate_file Path to a PEM or DER encoded X.509 certificate"
);
console.log(
" chain_file Optional PEM file with certificate chain for validation"
);
process.exit(1);
}
const certFile = args[0];
console.log(`Parsing certificate: ${certFile}`);
const cert = loadCertificate(certFile);
const parsed = parseCertificate(cert);
console.log(JSON.stringify(parsed, null, 2));
if (args.length > 1) {
const chainFile = args[1];
console.log(`\nValidating certificate chain: ${chainFile}`);
const chain = loadCertificateChain(chainFile);
const validation = validateCertificateChain(chain);
console.log(JSON.stringify(validation, null, 2));
}
}
// Export for use as module
module.exports = {
loadCertificate,
loadCertificateChain,
parseCertificate,
validateCertificateChain,
extractExtensions,
computeFingerprints,
};
if (require.main === module) {
main();
}
README.md
# X.509 Certificate Parser (JavaScript - Trial 1) An X.509 certificate parser using node-forge that reads PEM and DER encoded certificates, extracts all fields and extensions, and validates certificate chains. ## Dependencies - **node-forge** (1.3.1): Pure JavaScript implementation of TLS and cryptographic tools, providing X.509 certificate parsing, ASN.1 encoding/decoding, and PKI operations. ## Setup ```bash npm install ``` ## Usage ### Parse a single certificate ```bash node parser.js certificate.pem ``` ### Parse and validate a chain ```bash node parser.js 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) - Extracts all standard extensions (Basic Constraints, Key Usage, Extended Key Usage, 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 - Can be used as a module or CLI tool