一. 概述
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
二. 代码
cpp
#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编译
四. 运行结果
其中使用的证书是在下面这个在线网站上生成的: