← All tasks
cppclaude-code/cpp-t1 #16Not a task: not reproduced

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

envgap__claude-code__cpp-t1-16

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

01 / FAILURE SIGNATURE

As the study recorded it

Could NOT find OpenSSL - libssl-dev not in Docker image
Not a benchmark task.
  • In a clean container the reported failure did not reproduce, or the known fix did not make the project run.

02 / ENVIRONMENT RECIPE

Base commit
Not freshly verified
Manifest
CMakeLists.txt
Reproduce
Awaiting issue-specific recipe
Run under trace
Awaiting a meaningful runtime command

03 / TASK AND FAILURE

claude-code/cpp-t1 #16 · read the task the agent was given
Claude Code wrote this cpp 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 C++ project for a clean Ubuntu 22.04 machine with only G++ 12+ and CMake 3.22+ installed. Include:
- Source code
- CMakeLists.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.

CMakeLists.txt
cmake_minimum_required(VERSION 3.22)
project(x509_cert_parser VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# ---------------------------------------------------------------------------
# Dependencies
# ---------------------------------------------------------------------------
# OpenSSL (system) — X.509 certificate parsing, cryptographic operations,
#                     and signature verification.
# nlohmann/json v3.11.3 — header-only JSON library for structured output.
# ---------------------------------------------------------------------------

find_package(OpenSSL REQUIRED)

include(FetchContent)

FetchContent_Declare(
    nlohmann_json
    GIT_REPOSITORY https://github.com/nlohmann/json.git
    GIT_TAG        v3.11.3
    GIT_SHALLOW    TRUE
)

set(JSON_BuildTests   OFF CACHE BOOL "" FORCE)
set(JSON_Install      OFF CACHE BOOL "" FORCE)
set(JSON_MultipleHeaders OFF CACHE BOOL "" FORCE)

FetchContent_MakeAvailable(nlohmann_json)

# ---------------------------------------------------------------------------
# Executable
# ---------------------------------------------------------------------------

add_executable(x509_cert_parser main.cpp)

target_link_libraries(x509_cert_parser PRIVATE
    OpenSSL::SSL
    OpenSSL::Crypto
    nlohmann_json::nlohmann_json
)

if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
    target_compile_options(x509_cert_parser PRIVATE -Wall -Wextra -Wpedantic)
endif()
main.cpp
/**
 * X.509 Certificate Parser using OpenSSL and nlohmann/json.
 *
 * Parses X.509 certificates in PEM and DER formats, extracts all fields
 * and extensions, and validates certificate chains.
 */

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <memory>
#include <cstring>
#include <iomanip>

#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/pem.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/asn1.h>
#include <openssl/objects.h>

#include <nlohmann/json.hpp>

using json = nlohmann::json;

// RAII wrappers for OpenSSL types
struct X509Deleter { void operator()(X509* p) { if (p) X509_free(p); } };
struct BIODeleter { void operator()(BIO* p) { if (p) BIO_free_all(p); } };
struct X509StoreDeleter { void operator()(X509_STORE* p) { if (p) X509_STORE_free(p); } };
struct X509StoreCtxDeleter { void operator()(X509_STORE_CTX* p) { if (p) X509_STORE_CTX_free(p); } };
struct EVP_PKEYDeleter { void operator()(EVP_PKEY* p) { if (p) EVP_PKEY_free(p); } };

using X509Ptr = std::unique_ptr<X509, X509Deleter>;
using BIOPtr = std::unique_ptr<BIO, BIODeleter>;
using X509StorePtr = std::unique_ptr<X509_STORE, X509StoreDeleter>;
using X509StoreCtxPtr = std::unique_ptr<X509_STORE_CTX, X509StoreCtxDeleter>;

/**
 * Convert a byte array to a hex string.
 */
std::string bytesToHex(const unsigned char* data, size_t len) {
    std::ostringstream ss;
    for (size_t i = 0; i < len; i++) {
        ss << std::hex << std::setfill('0') << std::setw(2) << (int)data[i];
    }
    return ss.str();
}

/**
 * Get the string representation of an ASN1_INTEGER.
 */
std::string asn1IntegerToHex(const ASN1_INTEGER* ai) {
    BIGNUM* bn = ASN1_INTEGER_to_BN(ai, nullptr);
    if (!bn) return "";
    char* hex = BN_bn2hex(bn);
    std::string result(hex);
    OPENSSL_free(hex);
    BN_free(bn);
    // Convert to lowercase
    for (auto& c : result) c = std::tolower(c);
    return result;
}

/**
 * Get string representation of an ASN1_TIME.
 */
std::string asn1TimeToString(const ASN1_TIME* t) {
    if (!t) return "";
    BIOPtr bio(BIO_new(BIO_s_mem()));
    ASN1_TIME_print(bio.get(), t);
    char* buf = nullptr;
    long len = BIO_get_mem_data(bio.get(), &buf);
    return std::string(buf, len);
}

/**
 * Convert X509_NAME to JSON object.
 */
json nameToJson(X509_NAME* name) {
    json result = json::object();
    if (!name) return result;

    int count = X509_NAME_entry_count(name);
    for (int i = 0; i < count; i++) {
        X509_NAME_ENTRY* entry = X509_NAME_get_entry(name, i);
        ASN1_OBJECT* obj = X509_NAME_ENTRY_get_object(entry);
        ASN1_STRING* val = X509_NAME_ENTRY_get_data(entry);

        char oid_buf[256];
        OBJ_obj2txt(oid_buf, sizeof(oid_buf), obj, 0);
        std::string key(oid_buf);

        unsigned char* utf8 = nullptr;
        int utf8_len = ASN1_STRING_to_UTF8(&utf8, val);
        std::string value;
        if (utf8_len > 0) {
            value = std::string(reinterpret_cast<char*>(utf8), utf8_len);
            OPENSSL_free(utf8);
        }

        if (result.contains(key)) {
            if (result[key].is_array()) {
                result[key].push_back(value);
            } else {
                json arr = json::array();
                arr.push_back(result[key]);
                arr.push_back(value);
                result[key] = arr;
            }
        } else {
            result[key] = value;
        }
    }
    return result;
}

/**
 * Get public key information.
 */
json getPublicKeyInfo(X509* cert) {
    json info = json::object();
    EVP_PKEY* pkey = X509_get0_pubkey(cert);
    if (!pkey) return info;

    int id = EVP_PKEY_id(pkey);
    int bits = EVP_PKEY_bits(pkey);

    switch (id) {
        case EVP_PKEY_RSA:
            info["algorithm"] = "RSA";
            info["keySize"] = bits;
            break;
        case EVP_PKEY_EC:
            info["algorithm"] = "EC";
            info["keySize"] = bits;
            break;
        case EVP_PKEY_DSA:
            info["algorithm"] = "DSA";
            info["keySize"] = bits;
            break;
        case EVP_PKEY_ED25519:
            info["algorithm"] = "Ed25519";
            info["keySize"] = 256;
            break;
        case EVP_PKEY_ED448:
            info["algorithm"] = "Ed448";
            info["keySize"] = 448;
            break;
        default:
            info["algorithm"] = "Unknown";
            info["keySize"] = bits;
            break;
    }

    // Compute public key fingerprint (SHA-256 of DER-encoded public key)
    unsigned char* der = nullptr;
    int der_len = i2d_PUBKEY(pkey, &der);
    if (der_len > 0) {
        unsigned char hash[EVP_MAX_MD_SIZE];
        unsigned int hash_len = 0;
        EVP_Digest(der, der_len, hash, &hash_len, EVP_sha256(), nullptr);
        info["publicKeyFingerprint"] = bytesToHex(hash, hash_len);
        OPENSSL_free(der);
    }

    return info;
}

/**
 * Parse a single extension into JSON.
 */
json parseExtension(X509* cert, int nid, X509_EXTENSION* ext) {
    json result;

    BIOPtr bio(BIO_new(BIO_s_mem()));
    if (!X509V3_EXT_print(bio.get(), ext, 0, 0)) {
        // Fallback to hex dump
        ASN1_STRING* data = X509_EXTENSION_get_data(ext);
        result = bytesToHex(ASN1_STRING_get0_data(data), ASN1_STRING_length(data));
        return result;
    }

    char* buf = nullptr;
    long len = BIO_get_mem_data(bio.get(), &buf);
    std::string text(buf, len);

    // Try to structure well-known extensions
    switch (nid) {
        case NID_basic_constraints: {
            BASIC_CONSTRAINTS* bc = (BASIC_CONSTRAINTS*)X509V3_EXT_d2i(ext);
            if (bc) {
                result["ca"] = bc->ca ? true : false;
                result["pathLength"] = bc->pathlen ? ASN1_INTEGER_get(bc->pathlen) : nullptr;
                BASIC_CONSTRAINTS_free(bc);
            } else {
                result = text;
            }
            break;
        }
        case NID_key_usage: {
            ASN1_BIT_STRING* usage = (ASN1_BIT_STRING*)X509V3_EXT_d2i(ext);
            if (usage) {
                result["digitalSignature"] = (ASN1_BIT_STRING_get_bit(usage, 0) != 0);
                result["nonRepudiation"] = (ASN1_BIT_STRING_get_bit(usage, 1) != 0);
                result["keyEncipherment"] = (ASN1_BIT_STRING_get_bit(usage, 2) != 0);
                result["dataEncipherment"] = (ASN1_BIT_STRING_get_bit(usage, 3) != 0);
                result["keyAgreement"] = (ASN1_BIT_STRING_get_bit(usage, 4) != 0);
                result["keyCertSign"] = (ASN1_BIT_STRING_get_bit(usage, 5) != 0);
                result["crlSign"] = (ASN1_BIT_STRING_get_bit(usage, 6) != 0);
                result["encipherOnly"] = (ASN1_BIT_STRING_get_bit(usage, 7) != 0);
                result["decipherOnly"] = (ASN1_BIT_STRING_get_bit(usage, 8) != 0);
                ASN1_BIT_STRING_free(usage);
            } else {
                result = text;
            }
            break;
        }
        case NID_ext_key_usage: {
            EXTENDED_KEY_USAGE* eku = (EXTENDED_KEY_USAGE*)X509V3_EXT_d2i(ext);
            if (eku) {
                result = json::array();
                for (int i = 0; i < sk_ASN1_OBJECT_num(eku); i++) {
                    ASN1_OBJECT* obj = sk_ASN1_OBJECT_value(eku, i);
                    char buf2[256];
                    OBJ_obj2txt(buf2, sizeof(buf2), obj, 0);
                    result.push_back(std::string(buf2));
                }
                EXTENDED_KEY_USAGE_free(eku);
            } else {
                result = text;
            }
            break;
        }
        case NID_subject_alt_name:
        case NID_issuer_alt_name: {
            GENERAL_NAMES* gens = (GENERAL_NAMES*)X509V3_EXT_d2i(ext);
            if (gens) {
                result = json::array();
                for (int i = 0; i < sk_GENERAL_NAME_num(gens); i++) {
                    GENERAL_NAME* gen = sk_GENERAL_NAME_value(gens, i);
                    json entry;
                    switch (gen->type) {
                        case GEN_DNS:
                            entry["type"] = "DNS";
                            entry["value"] = std::string(
                                reinterpret_cast<const char*>(ASN1_STRING_get0_data(gen->d.dNSName)),
                                ASN1_STRING_length(gen->d.dNSName));
                            break;
                        case GEN_EMAIL:
                            entry["type"] = "email";
                            entry["value"] = std::string(
                                reinterpret_cast<const char*>(ASN1_STRING_get0_data(gen->d.rfc822Name)),
                                ASN1_STRING_length(gen->d.rfc822Name));
                            break;
                        case GEN_URI:
                            entry["type"] = "URI";
                            entry["value"] = std::string(
                                reinterpret_cast<const char*>(ASN1_STRING_get0_data(gen->d.uniformResourceIdentifier)),
                                ASN1_STRING_length(gen->d.uniformResourceIdentifier));
                            break;
                        case GEN_IPADD: {
                            entry["type"] = "IP";
                            const unsigned char* ip_data = ASN1_STRING_get0_data(gen->d.iPAddress);
                            int ip_len = ASN1_STRING_length(gen->d.iPAddress);
                            if (ip_len == 4) {
                                std::ostringstream oss;
                                oss << (int)ip_data[0] << "." << (int)ip_data[1]
                                    << "." << (int)ip_data[2] << "." << (int)ip_data[3];
                                entry["value"] = oss.str();
                            } else if (ip_len == 16) {
                                std::ostringstream oss;
                                for (int j = 0; j < 16; j += 2) {
                                    if (j > 0) oss << ":";
                                    oss << std::hex << std::setfill('0') << std::setw(2) << (int)ip_data[j]
                                        << std::setfill('0') << std::setw(2) << (int)ip_data[j+1];
                                }
                                entry["value"] = oss.str();
                            }
                            break;
                        }
                        case GEN_DIRNAME:
                            entry["type"] = "directoryName";
                            entry["value"] = nameToJson(gen->d.directoryName);
                            break;
                        default:
                            entry["type"] = "other";
                            entry["value"] = gen->type;
                            break;
                    }
                    result.push_back(entry);
                }
                GENERAL_NAMES_free(gens);
            } else {
                result = text;
            }
            break;
        }
        case NID_subject_key_identifier: {
            ASN1_OCTET_STRING* ski = (ASN1_OCTET_STRING*)X509V3_EXT_d2i(ext);
            if (ski) {
                result["keyIdentifier"] = bytesToHex(ASN1_STRING_get0_data(ski), ASN1_STRING_length(ski));
                ASN1_OCTET_STRING_free(ski);
            } else {
                result = text;
            }
            break;
        }
        case NID_authority_key_identifier: {
            AUTHORITY_KEYID* aki = (AUTHORITY_KEYID*)X509V3_EXT_d2i(ext);
            if (aki) {
                if (aki->keyid) {
                    result["keyIdentifier"] = bytesToHex(
                        ASN1_STRING_get0_data(aki->keyid), ASN1_STRING_length(aki->keyid));
                }
                if (aki->serial) {
                    result["authorityCertSerialNumber"] = asn1IntegerToHex(aki->serial);
                }
                AUTHORITY_KEYID_free(aki);
            } else {
                result = text;
            }
            break;
        }
        case NID_crl_distribution_points: {
            CRL_DIST_POINTS* cdps = (CRL_DIST_POINTS*)X509V3_EXT_d2i(ext);
            if (cdps) {
                result = json::array();
                for (int i = 0; i < sk_DIST_POINT_num(cdps); i++) {
                    DIST_POINT* dp = sk_DIST_POINT_value(cdps, i);
                    json point;
                    if (dp->distpoint && dp->distpoint->type == 0) {
                        GENERAL_NAMES* gns = dp->distpoint->name.fullname;
                        json names = json::array();
                        for (int j = 0; j < sk_GENERAL_NAME_num(gns); j++) {
                            GENERAL_NAME* gn = sk_GENERAL_NAME_value(gns, j);
                            if (gn->type == GEN_URI) {
                                names.push_back(std::string(
                                    reinterpret_cast<const char*>(ASN1_STRING_get0_data(gn->d.uniformResourceIdentifier)),
                                    ASN1_STRING_length(gn->d.uniformResourceIdentifier)));
                            }
                        }
                        point["fullName"] = names;
                    }
                    result.push_back(point);
                }
                CRL_DIST_POINTS_free(cdps);
            } else {
                result = text;
            }
            break;
        }
        case NID_info_access: {
            AUTHORITY_INFO_ACCESS* aia = (AUTHORITY_INFO_ACCESS*)X509V3_EXT_d2i(ext);
            if (aia) {
                result = json::array();
                for (int i = 0; i < sk_ACCESS_DESCRIPTION_num(aia); i++) {
                    ACCESS_DESCRIPTION* ad = sk_ACCESS_DESCRIPTION_value(aia, i);
                    json entry;
                    int method_nid = OBJ_obj2nid(ad->method);
                    if (method_nid == NID_ad_OCSP) {
                        entry["accessMethod"] = "ocsp";
                    } else if (method_nid == NID_ad_ca_issuers) {
                        entry["accessMethod"] = "caIssuers";
                    } else {
                        char method_buf[256];
                        OBJ_obj2txt(method_buf, sizeof(method_buf), ad->method, 1);
                        entry["accessMethod"] = std::string(method_buf);
                    }
                    if (ad->location->type == GEN_URI) {
                        entry["accessLocation"] = std::string(
                            reinterpret_cast<const char*>(
                                ASN1_STRING_get0_data(ad->location->d.uniformResourceIdentifier)),
                            ASN1_STRING_length(ad->location->d.uniformResourceIdentifier));
                    }
                    result.push_back(entry);
                }
                AUTHORITY_INFO_ACCESS_free(aia);
            } else {
                result = text;
            }
            break;
        }
        default:
            result = text;
            break;
    }

    return result;
}

/**
 * Extract all extensions from a certificate.
 */
json extractExtensions(X509* cert) {
    json extensions = json::array();
    int ext_count = X509_get_ext_count(cert);

    for (int i = 0; i < ext_count; i++) {
        X509_EXTENSION* ext = X509_get_ext(cert, i);
        ASN1_OBJECT* obj = X509_EXTENSION_get_object(ext);

        char oid_buf[256];
        OBJ_obj2txt(oid_buf, sizeof(oid_buf), obj, 1);
        char name_buf[256];
        OBJ_obj2txt(name_buf, sizeof(name_buf), obj, 0);

        int nid = OBJ_obj2nid(obj);
        bool critical = X509_EXTENSION_get_critical(ext) != 0;

        json ext_json;
        ext_json["oid"] = std::string(oid_buf);
        ext_json["name"] = std::string(name_buf);
        ext_json["critical"] = critical;
        ext_json["value"] = parseExtension(cert, nid, ext);

        extensions.push_back(ext_json);
    }

    return extensions;
}

/**
 * Compute certificate fingerprints.
 */
json computeFingerprints(X509* cert) {
    json fps;
    unsigned char md[EVP_MAX_MD_SIZE];
    unsigned int md_len;

    X509_digest(cert, EVP_sha256(), md, &md_len);
    fps["sha256"] = bytesToHex(md, md_len);

    X509_digest(cert, EVP_sha1(), md, &md_len);
    fps["sha1"] = bytesToHex(md, md_len);

    X509_digest(cert, EVP_md5(), md, &md_len);
    fps["md5"] = bytesToHex(md, md_len);

    return fps;
}

/**
 * Load a certificate from a file (PEM or DER).
 */
X509Ptr loadCertificate(const std::string& filePath) {
    // Try PEM first
    BIOPtr bio(BIO_new_file(filePath.c_str(), "r"));
    if (!bio) {
        throw std::runtime_error("Cannot open file: " + filePath);
    }

    X509* cert = PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr);
    if (cert) {
        return X509Ptr(cert);
    }

    // Try DER
    bio.reset(BIO_new_file(filePath.c_str(), "rb"));
    if (!bio) {
        throw std::runtime_error("Cannot open file: " + filePath);
    }

    cert = d2i_X509_bio(bio.get(), nullptr);
    if (cert) {
        return X509Ptr(cert);
    }

    throw std::runtime_error("Unable to parse certificate: " + filePath);
}

/**
 * Load a chain of certificates from a PEM file.
 */
std::vector<X509Ptr> loadCertificateChain(const std::string& filePath) {
    std::vector<X509Ptr> chain;
    BIOPtr bio(BIO_new_file(filePath.c_str(), "r"));
    if (!bio) {
        throw std::runtime_error("Cannot open chain file: " + filePath);
    }

    while (true) {
        X509* cert = PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr);
        if (!cert) break;
        chain.push_back(X509Ptr(cert));
    }
    // Clear any errors from reading past end
    ERR_clear_error();

    if (chain.empty()) {
        throw std::runtime_error("No certificates found in chain file: " + filePath);
    }

    return chain;
}

/**
 * Parse a certificate into JSON.
 */
json parseCertificate(X509* cert) {
    json result;

    // Version
    result["version"] = X509_get_version(cert) + 1;

    // Serial number
    const ASN1_INTEGER* serial = X509_get0_serialNumber(cert);
    result["serialNumber"] = asn1IntegerToHex(serial);

    // Signature algorithm
    int sig_nid = X509_get_signature_nid(cert);
    result["signatureAlgorithm"] = std::string(OBJ_nid2ln(sig_nid));

    // Issuer and Subject
    result["issuer"] = nameToJson(X509_get_issuer_name(cert));
    result["subject"] = nameToJson(X509_get_subject_name(cert));

    // Validity
    result["validity"]["notBefore"] = asn1TimeToString(X509_get0_notBefore(cert));
    result["validity"]["notAfter"] = asn1TimeToString(X509_get0_notAfter(cert));

    // Public key info
    result["publicKeyInfo"] = getPublicKeyInfo(cert);

    // Extensions
    result["extensions"] = extractExtensions(cert);

    // Fingerprints
    result["fingerprints"] = computeFingerprints(cert);

    // Signature value
    const ASN1_BIT_STRING* sig = nullptr;
    const X509_ALGOR* alg = nullptr;
    X509_get0_signature(&sig, &alg, cert);
    if (sig) {
        result["signatureValue"] = bytesToHex(sig->data, sig->length);
    }

    return result;
}

/**
 * Validate a certificate chain.
 */
json validateCertificateChain(const std::vector<X509Ptr>& chain) {
    json result;
    result["chainLength"] = chain.size();
    result["certificates"] = json::array();
    result["valid"] = true;
    result["errors"] = json::array();

    for (size_t i = 0; i < chain.size(); i++) {
        X509* cert = chain[i].get();
        json certResult;
        certResult["index"] = i;
        certResult["subject"] = nameToJson(X509_get_subject_name(cert));
        certResult["issuer"] = nameToJson(X509_get_issuer_name(cert));

        // Check validity
        int notBefore = X509_cmp_current_time(X509_get0_notBefore(cert));
        int notAfter = X509_cmp_current_time(X509_get0_notAfter(cert));

        if (notBefore > 0) {
            certResult["validityCheck"] = false;
            result["errors"].push_back("Certificate at index " + std::to_string(i) + " is not yet valid");
            result["valid"] = false;
        } else if (notAfter < 0) {
            certResult["validityCheck"] = false;
            result["errors"].push_back("Certificate at index " + std::to_string(i) + " has expired");
            result["valid"] = false;
        } else {
            certResult["validityCheck"] = true;
        }

        // Verify signature
        if (i < chain.size() - 1) {
            EVP_PKEY* issuer_key = X509_get0_pubkey(chain[i + 1].get());
            int verify_result = X509_verify(cert, issuer_key);
            certResult["signatureCheck"] = (verify_result == 1);
            if (verify_result != 1) {
                result["errors"].push_back("Signature verification failed at index " + std::to_string(i));
                result["valid"] = false;
            }

            // Check CA constraint on issuer
            int bc = X509_check_ca(chain[i + 1].get());
            if (bc == 0) {
                result["errors"].push_back("Certificate at index " + std::to_string(i + 1) + " is not a CA");
                result["valid"] = false;
            }
        } else {
            // Root: check self-signed
            EVP_PKEY* self_key = X509_get0_pubkey(cert);
            int verify_result = X509_verify(cert, self_key);
            certResult["signatureCheck"] = (verify_result == 1);
            certResult["selfSigned"] = (verify_result == 1);
            if (verify_result != 1) {
                result["errors"].push_back("Root at index " + std::to_string(i) + " is not self-signed");
                result["valid"] = false;
            }
        }

        result["certificates"].push_back(certResult);
    }

    return result;
}

void printUsage(const char* progName) {
    std::cout << "Usage: " << progName << " <certificate_file> [chain_file]" << std::endl;
    std::cout << std::endl;
    std::cout << "Options:" << std::endl;
    std::cout << "  certificate_file  Path to a PEM or DER encoded X.509 certificate" << std::endl;
    std::cout << "  chain_file        Optional PEM file with certificate chain for validation" << std::endl;
}

int main(int argc, char* argv[]) {
    if (argc < 2) {
        printUsage(argv[0]);
        return 1;
    }

    try {
        OpenSSL_add_all_algorithms();
        ERR_load_crypto_strings();

        std::string certFile = argv[1];
        std::cout << "Parsing certificate: " << certFile << std::endl;

        X509Ptr cert = loadCertificate(certFile);
        json parsed = parseCertificate(cert.get());
        std::cout << parsed.dump(2) << std::endl;

        if (argc > 2) {
            std::string chainFile = argv[2];
            std::cout << "\nValidating certificate chain: " << chainFile << std::endl;

            auto chain = loadCertificateChain(chainFile);
            json validation = validateCertificateChain(chain);
            std::cout << validation.dump(2) << std::endl;
        }

        EVP_cleanup();
        ERR_free_strings();

    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return 1;
    }

    return 0;
}
README.md
# X.509 Certificate Parser (C++ - Trial 1)

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

## Dependencies

- **OpenSSL** (system): Industry-standard cryptographic library providing X.509 certificate parsing, ASN.1 decoding, public key operations, and signature verification.
- **nlohmann/json** (3.11.3): Header-only JSON library for C++ used to produce structured JSON output of parsed certificate data.

## Build

```bash
mkdir build && cd build
cmake ..
cmake --build .
```

## Usage

### Parse a single certificate

```bash
./x509_cert_parser certificate.pem
```

### Parse and validate a chain

```bash
./x509_cert_parser 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, Ed25519, Ed448)
- 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
- RAII wrappers for safe OpenSSL resource management
- JSON formatted output