Skip to content

Commit 4c44668

Browse files
authored
Merge pull request #2644 from nkumar04/nkumar/BC-20423
Added utility to parse subject field from tls cert bundle
2 parents 2c76dbb + 8526f05 commit 4c44668

File tree

6 files changed

+253
-77
lines changed

6 files changed

+253
-77
lines changed

client/clientservice/src/configuration.cpp

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "secrets_manager_enc.h"
2121
#include "secrets_manager_plain.h"
2222
#include "client/clientservice/client_service.hpp"
23+
#include "crypto_utils.hpp"
2324

2425
using concord::client::concordclient::ConcordClientConfig;
2526
using concord::client::concordclient::StateSnapshotConfig;
@@ -293,32 +294,8 @@ void readCert(const std::string& input_filename, std::string& out_data) {
293294
}
294295

295296
std::string getClientIdFromClientCert(const std::string& client_cert_path, bool use_unified_certs) {
296-
std::array<char, 128> buffer;
297-
std::string client_id;
298-
std::string delimiter;
299-
300-
// check if client cert can be opened
301-
std::ifstream input_file(client_cert_path.c_str(), std::ios::in);
302-
303-
if (!input_file.is_open()) {
304-
throw std::runtime_error("Could not open the input file (" + client_cert_path + ") at the concord client.");
305-
}
306-
307-
// The cmd string is used to get the subject in the client cert.
308-
std::string cmd =
309-
"openssl crl2pkcs7 -nocrl -certfile " + client_cert_path + " | openssl pkcs7 -print_certs -noout | grep .";
310-
std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd.c_str(), "r"), pclose);
311-
if (!pipe) {
312-
throw std::runtime_error("Failed to read subject fields from client cert - popen() failed!");
313-
}
314-
315-
// parse the O field i.e., the client id from the subject field when
316-
// unified certificates are used, else parse OU field.
317-
delimiter = (use_unified_certs) ? "O = " : "OU = ";
318-
if (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
319-
client_id = parseClientIdFromSubject(buffer.data(), delimiter);
320-
}
321-
return client_id;
297+
auto field_name = (use_unified_certs ? "O" : "OU");
298+
return util::crypto::CertificateUtils::getSubjectFieldByName(client_cert_path, field_name);
322299
}
323300

324301
// Parses the value of the OU field i.e., the client id from the subject

thin-replica-server/include/thin-replica-server/thin_replica_impl.hpp

Lines changed: 8 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
#include "kv_types.hpp"
3737
#include "kvbc_app_filter/kvbc_app_filter.h"
3838
#include "kvbc_app_filter/kvbc_key_types.h"
39+
#include "crypto_utils.hpp"
40+
3941
#include "thin_replica.grpc.pb.h"
4042
#include "subscription_buffer.hpp"
4143
#include "trs_metrics.hpp"
@@ -118,9 +120,6 @@ class ThinReplicaImpl {
118120
// last timestamp when subscription status for live updates was not ok
119121
std::optional<std::chrono::steady_clock::time_point> last_failed_subscribe_status_time;
120122

121-
// Max Subject length in certificates should be 555 bytes with ascii characters
122-
static const uint16_t certSubjectLength{555};
123-
124123
public:
125124
ThinReplicaImpl(std::unique_ptr<ThinReplicaServerConfig> config,
126125
std::shared_ptr<concordMetrics::Aggregator> aggregator)
@@ -700,29 +699,10 @@ class ThinReplicaImpl {
700699
const std::string& root_cert_path,
701700
std::unordered_set<std::string>& cert_ou_field_set,
702701
bool use_unified_certs) {
703-
std::array<char, 128> buffer;
704-
std::string result;
705-
std::string delimiter;
706-
// Openssl doesn't provide a method to fetch all the x509 certificates
707-
// directly from a bundled cert, due to the assumption of one certificate
708-
// per file. But for some reason openssl supports displaying multiple certs
709-
// from a pkcs7 file. So we generate an intermediate pkcs7 file using
710-
// crl2pkcs7 openssl command to get the subject fields of all the certs from
711-
// the bundled root cert.
712-
std::string cmd =
713-
"openssl crl2pkcs7 -nocrl -certfile " + root_cert_path + " | openssl pkcs7 -print_certs -noout | grep .";
714-
std::unique_ptr<FILE, decltype(&pclose)> pipe_ptr(popen(cmd.c_str(), "r"), pclose);
715-
if (!pipe_ptr) {
716-
LOG_ERROR(logger_, "Failed to read from root cert - popen() failed, error: " << strerror(errno));
717-
throw std::runtime_error("Failed to read from root cert - popen() failed!");
718-
}
719-
720-
delimiter = (use_unified_certs) ? "O = " : "OU = ";
721-
722-
while (fgets(buffer.data(), buffer.size(), pipe_ptr.get()) != nullptr) {
723-
result = buffer.data();
724-
// parse the client id from the subject field
725-
cert_ou_field_set.insert(parseClientIdFromSubject(result, delimiter));
702+
auto field_name = (use_unified_certs ? "O" : "OU");
703+
auto attribute_list = util::crypto::CertificateUtils::getSubjectFieldListByName(root_cert_path, field_name);
704+
for (auto& val : attribute_list) {
705+
cert_ou_field_set.emplace(std::move(val));
726706
}
727707
}
728708

@@ -737,30 +717,8 @@ class ThinReplicaImpl {
737717
}
738718

739719
static std::string getClientIdFromCertificate(const std::string& client_cert_path, bool use_unified_certs = false) {
740-
std::array<char, certSubjectLength> buffer;
741-
std::string client_id;
742-
std::string delimiter;
743-
// check if client cert can be opened
744-
std::ifstream input_file(client_cert_path.c_str(), std::ios::in);
745-
746-
if (!input_file.is_open()) {
747-
throw std::runtime_error("Could not open the input file at path (" + client_cert_path + ")");
748-
}
749-
750-
// The cmd string is used to get the subject in the client cert.
751-
const std::string cmd =
752-
"openssl crl2pkcs7 -nocrl -certfile " + client_cert_path + " | openssl pkcs7 -print_certs -noout | grep .";
753-
std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd.c_str(), "r"), pclose);
754-
if (!pipe) {
755-
throw std::runtime_error("Failed to read subject fields from client cert - popen() failed!");
756-
}
757-
758-
delimiter = (use_unified_certs) ? "O = " : "OU = ";
759-
if (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
760-
// parse the client id from the subject field
761-
client_id = parseClientIdFromSubject(buffer.data(), delimiter);
762-
}
763-
return client_id;
720+
auto field_name = (use_unified_certs ? "O" : "OU");
721+
return util::crypto::CertificateUtils::getSubjectFieldByName(client_cert_path, field_name);
764722
}
765723

766724
private:

util/include/crypto_utils.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <utility>
1616
#include <string>
1717
#include <memory>
18+
#include <vector>
1819

1920
#include <openssl/bio.h>
2021
#include <openssl/ec.h>
@@ -37,6 +38,11 @@ class CertificateUtils {
3738
uint32_t& remote_peer_id,
3839
std::string& conn_type,
3940
bool use_unified_certs);
41+
// valid field_name: "C"/"L"/"ST"/"O"/"OU"/"CN"
42+
static std::string getSubjectFieldByName(const std::string& cert_path, const std::string& attribute_name);
43+
// This function accepts path to a cert bundle
44+
static std::vector<std::string> getSubjectFieldListByName(const std::string& cert_bundle_path,
45+
const std::string& attribute_name);
4046
};
4147
class IVerifier {
4248
public:

util/src/crypto_utils.cpp

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,17 @@
2828
#include <openssl/evp.h>
2929
#include <regex>
3030
#include "Logger.hpp"
31+
#include <unordered_map>
3132

3233
using namespace CryptoPP;
3334
namespace concord::util::crypto {
35+
36+
const std::unordered_map<std::string, int> name_to_id_map = {{"C", NID_countryName},
37+
{"L", NID_localityName},
38+
{"ST", NID_stateOrProvinceName},
39+
{"O", NID_organizationName},
40+
{"OU", NID_organizationalUnitName},
41+
{"CN", NID_commonName}};
3442
class ECDSAVerifier::Impl {
3543
std::unique_ptr<ECDSA<ECP, CryptoPP::SHA256>::Verifier> verifier_;
3644

@@ -449,4 +457,76 @@ bool CertificateUtils::verifyCertificate(X509* cert, const std::string& public_k
449457
BIO_free(pub_bio);
450458
return (bool)r;
451459
}
460+
461+
std::string CertificateUtils::getSubjectFieldByName(const std::string& cert_path, const std::string& attribute_name) {
462+
if (name_to_id_map.find(attribute_name) == name_to_id_map.end()) {
463+
LOG_ERROR(GL, "Invalid attribute name: " << attribute_name);
464+
return std::string{};
465+
}
466+
const auto& nid = name_to_id_map.at(attribute_name);
467+
auto deleter = [](FILE* fp) {
468+
if (fp) fclose(fp);
469+
};
470+
char buf[1024];
471+
std::unique_ptr<FILE, decltype(deleter)> fp(fopen(cert_path.c_str(), "r"), deleter);
472+
if (!fp) {
473+
LOG_ERROR(GL, "Certificate file not found, path: " << cert_path);
474+
return std::string();
475+
}
476+
477+
X509* cert = PEM_read_X509(fp.get(), NULL, NULL, NULL);
478+
if (!cert) {
479+
LOG_ERROR(GL, "Cannot parse certificate, path: " << cert_path);
480+
return std::string();
481+
}
482+
// The returned value of X509_get_subject_name is an internal pointer
483+
// which MUST NOT be freed.
484+
X509_NAME* name = X509_get_subject_name(cert);
485+
auto name_len = X509_NAME_get_text_by_NID(name, nid, buf, sizeof(buf));
486+
if (name_len == -1 || name_len == -2) {
487+
LOG_ERROR(GL, "name entry not found or invalid. error_code:" << name_len);
488+
X509_free(cert);
489+
return std::string{};
490+
}
491+
X509_free(cert);
492+
return std::string(buf);
493+
}
494+
495+
std::vector<std::string> CertificateUtils::getSubjectFieldListByName(const std::string& cert_bundle_path,
496+
const std::string& attribute_name) {
497+
auto attribute_list = std::vector<std::string>{};
498+
X509_STORE* store = NULL;
499+
if (name_to_id_map.find(attribute_name) == name_to_id_map.end()) {
500+
LOG_ERROR(GL, "Invalid attribute name: " << attribute_name);
501+
return {};
502+
}
503+
const auto& nid = name_to_id_map.at(attribute_name);
504+
char buf[1024];
505+
if (!(store = X509_STORE_new())) {
506+
LOG_ERROR(GL, "Error creating X509_STORE_CTX object\n");
507+
return {};
508+
}
509+
auto ret = X509_STORE_load_locations(store, cert_bundle_path.c_str(), NULL);
510+
if (ret != 1) {
511+
LOG_ERROR(GL, "Error loading CA cert or chain file\n");
512+
return {};
513+
}
514+
auto objs = X509_STORE_get0_objects(store);
515+
for (int i = 0; i < sk_X509_OBJECT_num(objs); i++) {
516+
X509_OBJECT* x509_obj = sk_X509_OBJECT_value(objs, i);
517+
if (x509_obj) {
518+
X509* cert = X509_OBJECT_get0_X509(x509_obj);
519+
if (cert) {
520+
X509_NAME* name = X509_get_subject_name(cert);
521+
auto name_len = X509_NAME_get_text_by_NID(name, nid, buf, sizeof(buf));
522+
if (name_len == -1 || name_len == -2) {
523+
continue;
524+
}
525+
attribute_list.emplace_back(std::string(buf));
526+
}
527+
}
528+
}
529+
X509_STORE_free(store);
530+
return attribute_list;
531+
}
452532
} // namespace concord::util::crypto

util/test/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ target_link_libraries(RawMemoryPool_test GTest::Main util)
7777

7878
add_executable(crypto_utils_test crypto_utils_test.cpp )
7979
add_test(crypto_utils_test crypto_utils_test)
80-
target_link_libraries(crypto_utils_test GTest::Main util)
80+
target_link_libraries(crypto_utils_test stdc++fs GTest::Main util)
8181

8282
add_executable(synchronized_value_test synchronized_value_test.cpp)
8383
add_test(synchronized_value_test synchronized_value_test)

0 commit comments

Comments
 (0)