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