使用OpenSSL接口读取pem编码格式文件中的证书
一. 概述
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 filevoid 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 existif (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 tagstd::ifstream fs(cert_file);std::string line;bool begin_find = false;int line_index = 1; // indicate current read line numberint cert_index = 0; // indicate which certificate in this pem fileint 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 bufferread_size = 0; // reset read sizeread_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 bufferread_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 certificatestd::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 indexcert_index++;// reset begin flagbegin_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 contentBIO* 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_notAfterprint_validity(x509);// Subject X509_get_subject_nameprint_subject(x509);// Issuer X509_get_issuer_name// Public Key X509_get_pubkeyextract_public_key(x509);// free x509X509_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证书生成