使用OpenSSL接口读取pem编码格式文件中的证书

发布于:2025-07-02 ⋅ 阅读:(18) ⋅ 点赞:(0)

一. 概述

        pem格式的全称是Private Enhance Mail(加强邮件文本格式),是一种常见的文本文件格式,用于存储和传输加密的数据,最初为了安全电子邮件而设计,后来被广泛应用在数字证书,SSL/TLS和秘钥管理。

        pem文件中,每一段文本前后都有清晰的边界标记,如下

-----BEGIN XXX-----
HKK091IJ23KJJQkjc2k
k0.......
-----END XXX-----

        解析pem文件的代码流程如下:
a. 读取pem文件中的标签获取不同的对象(证书,秘钥..)的BASE64编码的字符串
b. 使用BIO_new_mem_buf包装读取到的字符串作为一个BIO
c. 根据不同的对象(证书,秘钥)调用不同的SSL函数解析BIO,例如PEM_read_bio_X509,PEM_read_bio_PrivateKey

二. 代码

#include <iostream>
#include <fstream>
#include <vector>

#include <unistd.h>
#include <string.h>

#include <openssl/evp.h>
#include <openssl/ssl.h>
#include <openssl/asn1.h>
#include <openssl/x509.h>
#include <openssl/err.h>

const char* CERTIFICATE_BEGIN = "-----BEGIN CERTIFICATE-----";
const char* CERTIFICATE_END = "-----END CERTIFICATE-----";

const int CERTFILE_CONTENT_LINE_MAXLEN = 14000;   // 14000 byte limit
char cert_content_tmp_buf[CERTFILE_CONTENT_LINE_MAXLEN] = {0};  // store every certificate content in pem file

void print_serial_number(X509 *cert) {
    ASN1_INTEGER *serial = X509_get_serialNumber(cert);
    if (!serial) {
        std::cout << "无法获取序列号" << std::endl;
        return;
    }

    // 将序列号转换为 BIGNUM(方便打印)
    BIGNUM *bn = ASN1_INTEGER_to_BN(serial, NULL);
    if (!bn) {
        std::cout << "序列号转换失败" << std::endl;
        return;
    }

    // 打印序列号(十进制)
    std::cout << "Serial Number (decimal): ";
    BN_print_fp(stdout, bn);
    std::cout << std::endl;

    // 打印序列号(十六进制)
    char *hex = BN_bn2hex(bn);
    std::cout << "Serial Number (hex): " << hex << std::endl;
    OPENSSL_free(hex);

    BN_free(bn);
}

void extract_public_key(X509 *cert) {
    EVP_PKEY *pkey = X509_get_pubkey(cert);
    if (!pkey) {
        std::cout << "无法提取公钥" << std::endl;
        return;
    }

    // 打印公钥类型(如 RSA、ECC)
    int type = EVP_PKEY_id(pkey);
    std::cout << "Public Key Type: " << OBJ_nid2sn(type) << std::endl;

    // 如果是 RSA 公钥,可进一步提取模数(n)和指数(e)
    if (type == EVP_PKEY_RSA) {
        RSA *rsa = EVP_PKEY_get1_RSA(pkey);
        const BIGNUM *n, *e;
        RSA_get0_key(rsa, &n, &e, NULL);

        std::cout << "RSA Modulus (n): ";
        BN_print_fp(stdout, n);
        std::cout << "\nRSA Exponent (e): ";
        BN_print_fp(stdout, e);
        std::cout << std::endl;

        RSA_free(rsa);
    }

    EVP_PKEY_free(pkey);
}

void print_validity(X509 *cert) {
    ASN1_TIME *not_before = X509_get_notBefore(cert);
    ASN1_TIME *not_after = X509_get_notAfter(cert);

    char* s_notBefore = (char*)(ASN1_STRING_data(not_before));
    char* s_notAfter = (char*)(ASN1_STRING_data(not_after));
    std::cout << "Valid From: " << s_notBefore;
    std::cout << "\nValid Until: " << s_notAfter;
    std::cout << std::endl;
}

void print_subject(X509 *cert) {
    X509_NAME *subject = X509_get_subject_name(cert);
    if (!subject) {
        std::cout << "Error get subject from cert" << std::endl;
    } else {
        X509_NAME_print_ex_fp(stdout, subject, 0, XN_FLAG_ONELINE);
        std::cout << std::endl;
    }
}

bool read_cert_from_file(const std::string& cert_file, std::vector<std::string>& cert_content_buf) {
    bool success = true;
    // file exist
    if (access(cert_file.c_str(), F_OK)) {
        std::cout << "cert file [" << cert_file << "] not exists" << std::endl;
        success = false;
    } else {
        // loop read content between cert begin-end tag
        std::ifstream fs(cert_file);
        std::string line;
        bool begin_find = false;
        int line_index = 1;  // indicate current read line number
        int cert_index = 0;  // indicate which certificate in this pem file
        int read_size = 0;
        while (std::getline(fs, line)) {
            if (!begin_find) {
                if (0 == line.compare(CERTIFICATE_BEGIN)) {
                    if (line.length() > CERTFILE_CONTENT_LINE_MAXLEN) {
                        std::cout << "data line " << line_index << " exceed max line length [" << CERTFILE_CONTENT_LINE_MAXLEN << "]" << std::endl;
                        success = false;
                        break;
                    }
                    memset(cert_content_tmp_buf, 0, CERTFILE_CONTENT_LINE_MAXLEN);
                    memcpy(cert_content_tmp_buf + read_size, line.c_str(), line.length());  // append current line to cert file buffer
                    read_size = 0;   // reset read size
                    read_size += line.length();
                    cert_content_tmp_buf[read_size] = '\n';
                    read_size++;
                    begin_find = true;
                }
            } else {
                memcpy(cert_content_tmp_buf + read_size, line.c_str(), line.length());  // append current line to cert file buffer
                read_size += line.length();
                if (0 == line.compare(CERTIFICATE_BEGIN)) {
                    std::cout << "data line " << line_index << " has duplicated [" << CERTIFICATE_BEGIN << "]" << std::endl;
                    success = false;
                    break;
                } else
                {
                    if (0 == line.compare(CERTIFICATE_END)) {
                        // store current certificate
                        std::cout << "read " << (cert_index + 1) << " complete certificate content, size is " << read_size << std::endl;
                        cert_content_buf.emplace_back(std::string(cert_content_tmp_buf, read_size));
                        // increase cert index
                        cert_index++;
                        // reset begin flag
                        begin_find = false;
                    } else {
                        cert_content_tmp_buf[read_size] = '\n';
                        read_size++;
                    }
                }
            }
        }
        fs.close();
        if (success) {
            std::cout << "total " << cert_index << " certificate in " << cert_file << std::endl;
        }
    }
    return success;
}

void parse_certificate(const std::string& certificate) {
    X509* x509;  // store parsed x509 cert content
    BIO* bio = BIO_new_mem_buf(certificate.c_str(), certificate.length());
    std::cout << "allocate BIO fro certificate buffer, size is " << certificate.length() << ",\n content is:\n " << certificate << std::endl;
    if (bio) {
        x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
        if (x509) {
            // cert serial number 
            print_serial_number(x509);
            // before and after date X509_get_notBefore & X509_get_notAfter
            print_validity(x509);
            // Subject X509_get_subject_name
            print_subject(x509);
            // Issuer X509_get_issuer_name
            
            // Public Key  X509_get_pubkey
            extract_public_key(x509);
            // free x509
            X509_free(x509);
        } else {
            std::cout << "Read X509 certificate content from  BIO fail" << std::endl;
            ERR_print_errors_fp(stderr);
        }
    } else {
        std::cout << "allocate BIO fro certificate buffer fail" << std::endl;
    }
}

int main(int argc, char** argv) {
    if (argc > 1) {
        std::string filePath = argv[1];
        std::vector<std::string> vec_cert;
        if (read_cert_from_file(std::string(filePath), vec_cert)) {
            for(auto cert : vec_cert) {
                std::cout << "##### parse certificate begin ######\n" << std::endl;
                parse_certificate(cert);
                std::cout << "\n##### parse certificate end ######" << std::endl;
            }
        }
    } else {
        std::cout << "usage: ./cert_read $PEM_FILE_NAME" << std::endl;
    }
}

三. 编译

CMakeLists.txt如下

cmake_minimum_required(VERSION 3.10)

project(certRead)

add_executable(${PROJECT_NAME} main.cpp)

target_link_libraries(${PROJECT_NAME} ssl crypto)

然后cmake + make编译

四. 运行结果

其中使用的证书是在下面这个在线网站上生成的:

SSL证书生成


网站公告

今日签到

点亮在社区的每一天
去签到