Commit 12c748a8 authored by Maxim Georgiev's avatar Maxim Georgiev Committed by Facebook GitHub Bot

Adding cert extension parsing methods to folly::OpenSSLCertUtils

Summary:
Adding methods to `folly::OpenSSLCertUtils` that allow to read extensions from X509 cert structures.
- `folly::OpenSSLCertUtils::getExtension()` allows to query extensions by name
- `folly::OpenSSLCertUtils::getAllExtensions()` returns everything including custom extensions.
No extension name translation mechanism is provided for custom extensions.

Reviewed By: mingtaoy

Differential Revision: D31852457

fbshipit-source-id: 22d34d2cf304ba135419c902d9c5c303f78cf781
parent e4fd0ee9
......@@ -30,6 +30,48 @@ std::string getOpenSSLErrorString(unsigned long err) {
ERR_error_string_n(err, errBuff.data(), errBuff.size());
return std::string(errBuff.data());
}
std::string asn1ToString(ASN1_STRING* a) {
auto strType = ASN1_STRING_type(a);
if (strType == V_ASN1_UTF8STRING || strType == V_ASN1_OCTET_STRING) {
long len = ASN1_STRING_length(a);
int type, xclass;
const unsigned char* data = ASN1_STRING_get0_data(a);
ASN1_get_object(&data, &len, &type, &xclass, len);
return std::string(reinterpret_cast<const char*>(data), len);
} else {
unsigned char* data = const_cast<unsigned char*>(ASN1_STRING_get0_data(a));
int len = ASN1_STRING_length(a);
if (len <= 0) {
return std::string();
}
return std::string(reinterpret_cast<char*>(data), len);
}
}
std::string getExtOid(X509_EXTENSION* extension) {
CHECK_NOTNULL(extension);
ASN1_OBJECT* object = X509_EXTENSION_get_object(extension);
// Query for extension OID
constexpr int buf_size = 256;
std::string ret(buf_size, '\0');
auto length = OBJ_obj2txt(ret.data(), ret.size(), object, 1);
if (length > buf_size) {
// Reserve one byte for the terminating zero
ret.resize(length, '\0');
OBJ_obj2txt(ret.data(), ret.size(), object, 1);
}
ret.resize(length);
return ret;
}
std::string getExtData(X509_EXTENSION* extension) {
CHECK_NOTNULL(extension);
auto asnValue = X509_EXTENSION_get_data(extension);
return asnValue ? asn1ToString(asnValue) : std::string();
}
} // namespace
Optional<std::string> OpenSSLCertUtils::getCommonName(X509& x509) {
......@@ -127,6 +169,31 @@ Optional<std::string> OpenSSLCertUtils::getIssuer(X509& x509) {
return std::string(bioData, bioLen);
}
std::vector<std::string> OpenSSLCertUtils::getExtension(
X509& x509, folly::StringPiece oid) {
std::vector<std::string> extValues;
for (int i = 0; i < X509_get_ext_count(&x509); i++) {
X509_EXTENSION* extension = X509_get_ext(&x509, i);
std::string extensionOid = getExtOid(extension);
if (extensionOid == oid) {
extValues.push_back(getExtData(extension));
}
}
return extValues;
}
std::vector<std::pair<std::string, std::string>>
OpenSSLCertUtils::getAllExtensions(X509& x509) {
std::vector<std::pair<std::string, std::string>> extensions;
for (int i = 0; i < X509_get_ext_count(&x509); i++) {
X509_EXTENSION* extension = X509_get_ext(&x509, i);
std::string oid = getExtOid(extension);
std::string value = getExtData(extension);
extensions.push_back(std::make_pair(oid, value));
}
return extensions;
}
folly::Optional<std::string> OpenSSLCertUtils::toString(X509& x509) {
auto in = BioUniquePtr(BIO_new(BIO_s_mem()));
if (in == nullptr) {
......
......@@ -17,6 +17,8 @@
#pragma once
#include <chrono>
#include <map>
#include <set>
#include <string>
#include <vector>
......@@ -59,6 +61,26 @@ class OpenSSLCertUtils {
*/
static std::string getNotAfterTime(X509& x509);
/*
* Get a set of strings containing data for a given cert extension
* @param x509 Reference to an X509
* @param oid extension OID string like "1.2.3.4"
* @return a std::vector<std::string> containing raw bytes from the extension
* entries with the requested name
*/
static std::vector<std::string> getExtension(
X509& x509, folly::StringPiece oid);
/*
* return a vector of name <-> value pairs for all extensions contaiend
* in the cert
* @param x509 Reference to an X509
* @return a vector of string pairs where first value in every pair is
* extension oid, and the second value is the extension value.
*/
static std::vector<std::pair<std::string, std::string>> getAllExtensions(
X509& x509);
/*
* Summarize the CN, Subject, Issuer, Validity, and extensions as a string
*/
......
......@@ -129,6 +129,25 @@ const std::string kTestCertBundle = folly::stripLeftMargin(R"(
-----END CERTIFICATE-----
)");
const std::map<std::string, std::string> testCertWithSanExts{
{"1.3.6.1.5.5.7.1.1",
"0h\x6\b+\x6\x1\x5\x5\a0\x2\x86\\https://phabricator.fb.com/diffusion/"
"FBCODE/browse/master/ti/test_certs/ca_cert.pem?view=raw"},
{"2.5.29.35",
"\x80\x14\x17\xDF\x29\x9\x29\xBF\x7B\x9F\x1A\x7F\xE9\x46\x49\xC8\x3B\xED"
"\xB3\xB9\xE8\x7B"},
{"2.5.29.19", "\x0"},
{"2.16.840.1.113730.1.13", "OpenSSL Generated Certificate"},
{"2.5.29.17",
"\x82\x12"
"anotherexample.com\x82\x12*.thirdexample.com"},
{"2.5.29.14",
"\x71\xD6\x49\x9D\x64\x47\xD7\x1E\x65\x8B\x1E\x94\x83\x23\x42\xE1\xF2\x19"
"\x9F\xC3"},
{"1.2.3.4.1", "Custom Extension 1"},
{"1.2.3.4.2", "Custom Extension 2"},
};
class OpenSSLCertUtilsTest : public TestWithParam<bool> {
public:
void SetUp() override {
......@@ -188,6 +207,26 @@ static void validateTestCertWithSAN(X509* x509) {
EXPECT_EQ("*.thirdexample.com", altNames[1]);
}
static void addCustomExt(
X509* x509, const std::string& oid, const std::string& data) {
std::string extValue("\x0C");
extValue.push_back(static_cast<char>(data.length()));
extValue.append(data);
folly::ssl::ASN1StrUniquePtr asn1String(ASN1_UTF8STRING_new());
ASN1_STRING_set(asn1String.get(), extValue.c_str(), extValue.size());
folly::ssl::ASN1ObjUniquePtr object(OBJ_txt2obj(oid.c_str(), 1));
folly::ssl::X509ExtensionUniquePtr ext(X509_EXTENSION_create_by_OBJ(
nullptr, object.get(), false, asn1String.get()));
if (!ext) {
throw std::runtime_error(
folly::to<std::string>("Could not create extension ", oid));
}
if (!X509_add_ext(x509, ext.get(), -1)) {
throw std::runtime_error(
folly::to<std::string>("Could not add extension ", oid));
}
}
TEST_P(OpenSSLCertUtilsTest, TestX509CN) {
auto x509 = readCertFromFile(kTestCertWithoutSan);
EXPECT_NE(x509, nullptr);
......@@ -374,3 +413,34 @@ TEST_P(OpenSSLCertUtilsTest, TestReadStoreDuplicate) {
EXPECT_NE(store, nullptr);
EXPECT_EQ(ERR_get_error(), 0);
}
TEST_P(OpenSSLCertUtilsTest, TestAllExtensions) {
auto x509 = readCertFromData(kTestCertWithSan);
EXPECT_NE(x509, nullptr);
// Adding a couple of curtom extensions
addCustomExt(x509.get(), "1.2.3.4.1", "Custom Extension 1");
addCustomExt(x509.get(), "1.2.3.4.2", "Custom Extension 2");
std::vector<std::pair<std::string, std::string>> extensions =
folly::ssl::OpenSSLCertUtils::getAllExtensions(*x509);
for (auto const& pair : extensions) {
std::string name = pair.first;
std::string value = pair.second;
if (testCertWithSanExts.find(name) != testCertWithSanExts.end()) {
EXPECT_EQ(value, testCertWithSanExts.find(name)->second);
}
}
}
TEST_P(OpenSSLCertUtilsTest, TestGetExtension) {
auto x509 = readCertFromData(kTestCertWithSan);
EXPECT_NE(x509, nullptr);
addCustomExt(x509.get(), "1.2.3.4.1", "Custom Extension 1");
addCustomExt(x509.get(), "1.2.3.4.2", "Custom Extension 2");
for (const auto& [name, value] : testCertWithSanExts) {
std::vector<std::string> extensionValues =
folly::ssl::OpenSSLCertUtils::getExtension(*x509, name);
EXPECT_EQ(extensionValues.size(), 1);
EXPECT_EQ(extensionValues[0], value);
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment