Skip to content

Commit 067e752

Browse files
authored
Export/import X509 certificates to PEM format (#1913)
* Export/import X509 certificate from/to PEM * - Add tests - Add doxygen documentation * Fix typos
1 parent b5ed25f commit 067e752

File tree

5 files changed

+121
-3
lines changed

5 files changed

+121
-3
lines changed

Packet++/header/X509Decoder.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,6 +766,19 @@ namespace pcpp
766766
/// @throws An exception if the file doesn't exist, cannot be read or contains invalid data
767767
static std::unique_ptr<X509Certificate> fromDERFile(const std::string& derFileName);
768768

769+
/// Creates an X509Certificate from PEM-encoded data
770+
/// @param[in] pemData PEM-encoded certificate data
771+
/// @return A unique pointer to the created X509Certificate
772+
/// @throws std::invalid_argument exception if the data is not a valid PEM-encoded certificate
773+
static std::unique_ptr<X509Certificate> fromPEM(const std::string& pemData);
774+
775+
/// Creates an X509Certificate from a file containing PEM-encoded data
776+
/// @param[in] pemFileName Path to the file containing PEM-encoded certificate
777+
/// @return A unique pointer to the created X509Certificate
778+
/// @throws std::runtime_error exception if the file doesn't exist or cannot be read
779+
/// @throws std::invalid_argument exception if the data is not a valid PEM-encoded certificate
780+
static std::unique_ptr<X509Certificate> fromPEMFile(const std::string& pemFileName);
781+
769782
/// Gets the version of the certificate
770783
/// @return The X509Version of the certificate
771784
X509Version getVersion() const;
@@ -824,6 +837,10 @@ namespace pcpp
824837
/// @return A byte vector containing the DER-encoded data
825838
std::vector<uint8_t> toDER() const;
826839

840+
/// Converts the certificate to PEM-encoded format
841+
/// @return A string containing the PEM-encoded data
842+
std::string toPEM() const;
843+
827844
/// Converts the certificate to a JSON string representation
828845
/// @param[in] indent Number of spaces to use for indentation (-1 for no pretty printing)
829846
/// @return A JSON string representation of the certificate
@@ -847,5 +864,7 @@ namespace pcpp
847864
mutable std::vector<X509Extension> m_Extensions;
848865
mutable bool m_ExtensionsParsed = false;
849866
std::unique_ptr<uint8_t[]> m_DerData;
867+
868+
static constexpr const char* certificatePemLabel = "CERTIFICATE";
850869
};
851870
} // namespace pcpp

Packet++/src/X509Decoder.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "X509Decoder.h"
22
#include "Asn1Codec.h"
33
#include "GeneralUtils.h"
4+
#include "PemCodec.h"
45
#include "json.hpp"
56
#include <fstream>
67
#include <unordered_map>
@@ -971,6 +972,40 @@ namespace pcpp
971972
return std::unique_ptr<X509Certificate>(new X509Certificate(std::move(derData), derDataLen));
972973
}
973974

975+
std::unique_ptr<X509Certificate> X509Certificate::fromPEM(const std::string& pemData)
976+
{
977+
auto derData = PemCodec::decode(pemData, certificatePemLabel);
978+
std::unique_ptr<uint8_t[]> derDataBuffer(new uint8_t[derData.size()]);
979+
std::copy(derData.begin(), derData.end(), derDataBuffer.get());
980+
return std::unique_ptr<X509Certificate>(new X509Certificate(std::move(derDataBuffer), derData.size()));
981+
}
982+
983+
std::unique_ptr<X509Certificate> X509Certificate::fromPEMFile(const std::string& pemFileName)
984+
{
985+
std::ifstream pemFile(pemFileName, std::ios::in | std::ios::binary);
986+
if (!pemFile.good())
987+
{
988+
throw std::runtime_error("PEM file doesn't exist or cannot be opened");
989+
}
990+
991+
pemFile.seekg(0, std::ios::end);
992+
std::streamsize pemContentLen = pemFile.tellg();
993+
if (pemContentLen < 0)
994+
{
995+
throw std::runtime_error("Failed to determine PEM file size");
996+
}
997+
pemFile.seekg(0, std::ios::beg);
998+
999+
std::string pemContent;
1000+
pemContent.resize(static_cast<std::size_t>(pemContentLen));
1001+
if (!pemFile.read(&pemContent[0], pemContentLen))
1002+
{
1003+
throw std::runtime_error("Failed to read PEM file");
1004+
}
1005+
1006+
return fromPEM(pemContent);
1007+
}
1008+
9741009
X509Version X509Certificate::getVersion() const
9751010
{
9761011
return m_TBSCertificate.getVersion();
@@ -1068,6 +1103,11 @@ namespace pcpp
10681103
return m_X509Internal->encode();
10691104
}
10701105

1106+
std::string X509Certificate::toPEM() const
1107+
{
1108+
return PemCodec::encode(m_X509Internal->encode(), certificatePemLabel);
1109+
}
1110+
10711111
std::string X509Certificate::toJson(int indent) const
10721112
{
10731113
auto extensions = nlohmann::ordered_json::array();
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDpzCCA0ygAwIBAgIRAI98/hCe+ph/EWgS4ZgAhJcwCgYIKoZIzj0EAwIwOzEL
3+
MAkGA1UEBhMCVVMxHjAcBgNVBAoTFUdvb2dsZSBUcnVzdCBTZXJ2aWNlczEMMAoG
4+
A1UEAxMDV0UxMB4XDTI1MDYwMTAwNDU0M1oXDTI1MDgzMDAxNDUzOVowFjEUMBIG
5+
A1UEAxMLY2hhdGdwdC5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQbb9p/
6+
fexDlq6DFwH3ks4KGFAYc5/kJ1tL1LZCuJwBMpQx02n0LcqeuYY1UyBq2UXmAxMM
7+
/ZRRVSGEBm/fW7mZo4ICVDCCAlAwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoG
8+
CCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFJudysWJUhKe/wXlhjfw
9+
nWrOLQqWMB8GA1UdIwQYMBaAFJB3kjVnxP+ozKnme9mAeXvMk/k4MF4GCCsGAQUF
10+
BwEBBFIwUDAnBggrBgEFBQcwAYYbaHR0cDovL28ucGtpLmdvb2cvcy93ZTEvajN3
11+
MCUGCCsGAQUFBzAChhlodHRwOi8vaS5wa2kuZ29vZy93ZTEuY3J0MCUGA1UdEQQe
12+
MByCC2NoYXRncHQuY29tgg0qLmNoYXRncHQuY29tMBMGA1UdIAQMMAowCAYGZ4EM
13+
AQIBMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jLnBraS5nb29nL3dlMS9EUDJQ
14+
Uzh6UW5Wcy5jcmwwggEFBgorBgEEAdZ5AgQCBIH2BIHzAPEAdgAS8U40vVNyTIQG
15+
GcOPP3oT+Oe1YoeInG0wBYTr5YYmOgAAAZcpKo0ZAAAEAwBHMEUCIQDG0omkHJ1j
16+
58VJxETXJNXHgob8t1O3HKDQcDd8dFJuPAIgQ/OkVI9FMLAEHNB6N/BeU/eWDbgA
17+
udw0RytVoruF45oAdwAN4fIwK9MNwUBiEgnqVS78R3R8sdfpMO8OQh60fk6qNAAA
18+
AZcpKo0mAAAEAwBIMEYCIQC3HRsfCw3UUGrK+o/dPf9lsONitH7V7cqYx5v8HqSc
19+
IQIhAJ7aj827Kpha2y+NA436TmDrurPa8Cfvojbic/xBICGOMAoGCCqGSM49BAMC
20+
A0kAMEYCIQDfBtqCdYOeZduS2KF7XjyN6NZh6TnOLuVZkvOPCk9D9QIhAI/fE7Gl
21+
4BD14XuGllB9qGPdGGfUfpTyG6ZAxulgdhFg
22+
-----END CERTIFICATE-----

Tests/Packet++Test/Tests/X509Tests.cpp

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,16 +175,46 @@ PTF_TEST_CASE(X509ParsingTest)
175175
"308203a73082034ca0030201020211008f7cfe109efa987f116812e198008497300a06082a8648ce3d040302303b310b3009060355040613025553311e301c060355040a1315476f6f676c65205472757374205365727669636573310c300a06035504031303574531301e170d3235303630313030343534335a170d3235303833303031343533395a3016311430120603550403130b636861746770742e636f6d3059301306072a8648ce3d020106082a8648ce3d030107034200041b6fda7f7dec4396ae831701f792ce0a185018739fe4275b4bd4b642b89c01329431d369f42dca9eb9863553206ad945e603130cfd9451552184066fdf5bb999a382025430820250300e0603551d0f0101ff04040302078030130603551d25040c300a06082b06010505070301300c0603551d130101ff04023000301d0603551d0e041604149b9dcac58952129eff05e58637f09d6ace2d0a96301f0603551d230418301680149077923567c4ffa8cca9e67bd980797bcc93f938305e06082b0601050507010104523050302706082b06010505073001861b687474703a2f2f6f2e706b692e676f6f672f732f7765312f6a3377302506082b060105050730028619687474703a2f2f692e706b692e676f6f672f7765312e63727430250603551d11041e301c820b636861746770742e636f6d820d2a2e636861746770742e636f6d30130603551d20040c300a3008060667810c01020130360603551d1f042f302d302ba029a0278625687474703a2f2f632e706b692e676f6f672f7765312f4450325053387a516e56732e63726c30820105060a2b06010401d6790204020481f60481f300f100760012f14e34bd53724c840619c38f3f7a13f8e7b56287889c6d300584ebe586263a00000197292a8d190000040300473045022100c6d289a41c9d63e7c549c444d724d5c78286fcb753b71ca0d070377c74526e3c022043f3a4548f4530b0041cd07a37f05e53f7960db800b9dc34472b55a2bb85e39a0077000de1f2302bd30dc140621209ea552efc47747cb1d7e930ef0e421eb47e4eaa3400000197292a8d260000040300483046022100b71d1b1f0b0dd4506acafa8fdd3dff65b0e362b47ed5edca98c79bfc1ea49c210221009eda8fcdbb2a985adb2f8d038dfa4e60ebbab3daf027efa236e273fc4120218e300a06082a8648ce3d0403020349003046022100df06da8275839e65db92d8a17b5e3c8de8d661e939ce2ee55992f38f0a4f43f50221008fdf13b1a5e010f5e17b8696507da863dd1867d47e94f21ba640c6e960761160";
176176
uint8_t derData[5000];
177177
auto derDataLen = pcpp::hexStringToByteArray(derDataString, derData, 5000);
178-
179-
auto x509CertFromString = pcpp::X509Certificate::fromDER(derDataString);
178+
std::string pemDataString = R"(-----BEGIN CERTIFICATE-----
179+
MIIDpzCCA0ygAwIBAgIRAI98/hCe+ph/EWgS4ZgAhJcwCgYIKoZIzj0EAwIwOzEL
180+
MAkGA1UEBhMCVVMxHjAcBgNVBAoTFUdvb2dsZSBUcnVzdCBTZXJ2aWNlczEMMAoG
181+
A1UEAxMDV0UxMB4XDTI1MDYwMTAwNDU0M1oXDTI1MDgzMDAxNDUzOVowFjEUMBIG
182+
A1UEAxMLY2hhdGdwdC5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQbb9p/
183+
fexDlq6DFwH3ks4KGFAYc5/kJ1tL1LZCuJwBMpQx02n0LcqeuYY1UyBq2UXmAxMM
184+
/ZRRVSGEBm/fW7mZo4ICVDCCAlAwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoG
185+
CCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFJudysWJUhKe/wXlhjfw
186+
nWrOLQqWMB8GA1UdIwQYMBaAFJB3kjVnxP+ozKnme9mAeXvMk/k4MF4GCCsGAQUF
187+
BwEBBFIwUDAnBggrBgEFBQcwAYYbaHR0cDovL28ucGtpLmdvb2cvcy93ZTEvajN3
188+
MCUGCCsGAQUFBzAChhlodHRwOi8vaS5wa2kuZ29vZy93ZTEuY3J0MCUGA1UdEQQe
189+
MByCC2NoYXRncHQuY29tgg0qLmNoYXRncHQuY29tMBMGA1UdIAQMMAowCAYGZ4EM
190+
AQIBMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jLnBraS5nb29nL3dlMS9EUDJQ
191+
Uzh6UW5Wcy5jcmwwggEFBgorBgEEAdZ5AgQCBIH2BIHzAPEAdgAS8U40vVNyTIQG
192+
GcOPP3oT+Oe1YoeInG0wBYTr5YYmOgAAAZcpKo0ZAAAEAwBHMEUCIQDG0omkHJ1j
193+
58VJxETXJNXHgob8t1O3HKDQcDd8dFJuPAIgQ/OkVI9FMLAEHNB6N/BeU/eWDbgA
194+
udw0RytVoruF45oAdwAN4fIwK9MNwUBiEgnqVS78R3R8sdfpMO8OQh60fk6qNAAA
195+
AZcpKo0mAAAEAwBIMEYCIQC3HRsfCw3UUGrK+o/dPf9lsONitH7V7cqYx5v8HqSc
196+
IQIhAJ7aj827Kpha2y+NA436TmDrurPa8Cfvojbic/xBICGOMAoGCCqGSM49BAMC
197+
A0kAMEYCIQDfBtqCdYOeZduS2KF7XjyN6NZh6TnOLuVZkvOPCk9D9QIhAI/fE7Gl
198+
4BD14XuGllB9qGPdGGfUfpTyG6ZAxulgdhFg
199+
-----END CERTIFICATE-----
200+
)";
201+
202+
auto x509CertFromDerString = pcpp::X509Certificate::fromDER(derDataString);
180203
auto x509CertFromDerData = pcpp::X509Certificate::fromDER(derData, derDataLen);
204+
auto x509CertFromPemData = pcpp::X509Certificate::fromPEM(pemDataString);
205+
auto x509CertFromPemFile = pcpp::X509Certificate::fromPEMFile("PacketExamples/x509_cert_chatgpt.pem");
181206

182-
PTF_ASSERT_EQUAL(x509CertFromString->toJson(), expectedJson);
207+
PTF_ASSERT_EQUAL(x509CertFromDerString->toJson(), expectedJson);
183208
PTF_ASSERT_EQUAL(x509CertFromDerData->toJson(), expectedJson);
209+
PTF_ASSERT_EQUAL(x509CertFromPemData->toJson(), expectedJson);
210+
PTF_ASSERT_EQUAL(x509CertFromPemFile->toJson(), expectedJson);
184211

185212
auto certDerData = x509Certificate->toDER();
186213
PTF_ASSERT_EQUAL(certDerData.size(), derDataLen)
187214
PTF_ASSERT_BUF_COMPARE(certDerData.data(), derData, derDataLen);
215+
216+
auto certPemData = x509Certificate->toPEM();
217+
PTF_ASSERT_EQUAL(certPemData, pemDataString);
188218
}
189219

190220
PTF_TEST_CASE(X509VariantsParsingTest)
@@ -348,6 +378,12 @@ PTF_TEST_CASE(X509InvalidDataTest)
348378
PTF_ASSERT_RAISES(pcpp::X509Certificate::fromDERFile("PacketExamples/missing_file.der"), std::runtime_error,
349379
"DER file doesn't exist or cannot be opened");
350380
}
381+
382+
// PEM file doesn't exist
383+
{
384+
PTF_ASSERT_RAISES(pcpp::X509Certificate::fromPEMFile("PacketExamples/missing_file.pem"), std::runtime_error,
385+
"PEM file doesn't exist or cannot be opened");
386+
}
351387
}
352388

353389
PTF_TEST_CASE(X509ExtensionDataTest)

typos-config.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ pinter = "pinter"
3838
helo = "helo"
3939
hom = "hom"
4040
pn = "pn"
41+
ND = "ND"
4142

4243
[type.make]
4344
extend-glob = []

0 commit comments

Comments
 (0)