--- /dev/null
+/*
+ * Copyright 2017-present Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <folly/ssl/OpenSSLCertUtils.h>
+
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+
+#include <folly/ScopeGuard.h>
+
+namespace folly {
+namespace ssl {
+
+Optional<std::string> OpenSSLCertUtils::getCommonName(X509& x509) {
+ auto subject = X509_get_subject_name(&x509);
+ if (!subject) {
+ return none;
+ }
+
+ auto cnLoc = X509_NAME_get_index_by_NID(subject, NID_commonName, -1);
+ if (cnLoc < 0) {
+ return none;
+ }
+
+ auto cnEntry = X509_NAME_get_entry(subject, cnLoc);
+ if (!cnEntry) {
+ return none;
+ }
+
+ auto cnAsn = X509_NAME_ENTRY_get_data(cnEntry);
+ if (!cnAsn) {
+ return none;
+ }
+
+ auto cnData = reinterpret_cast<const char*>(ASN1_STRING_data(cnAsn));
+ auto cnLen = ASN1_STRING_length(cnAsn);
+ if (!cnData || cnLen <= 0) {
+ return none;
+ }
+
+ return Optional<std::string>(std::string(cnData, cnLen));
+}
+
+std::vector<std::string> OpenSSLCertUtils::getSubjectAltNames(X509& x509) {
+ auto names = reinterpret_cast<STACK_OF(GENERAL_NAME)*>(
+ X509_get_ext_d2i(&x509, NID_subject_alt_name, nullptr, nullptr));
+ if (!names) {
+ return {};
+ }
+ SCOPE_EXIT {
+ sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
+ };
+
+ std::vector<std::string> ret;
+ auto count = sk_GENERAL_NAME_num(names);
+ for (int i = 0; i < count; i++) {
+ auto genName = sk_GENERAL_NAME_value(names, i);
+ if (!genName || genName->type != GEN_DNS) {
+ continue;
+ }
+ auto nameData =
+ reinterpret_cast<const char*>(ASN1_STRING_data(genName->d.dNSName));
+ auto nameLen = ASN1_STRING_length(genName->d.dNSName);
+ if (!nameData || nameLen <= 0) {
+ continue;
+ }
+ ret.emplace_back(nameData, nameLen);
+ }
+ return ret;
+}
+}
+}
--- /dev/null
+/*
+ * Copyright 2017 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include <openssl/x509.h>
+
+#include <folly/Optional.h>
+
+namespace folly {
+namespace ssl {
+
+class OpenSSLCertUtils {
+ public:
+ // Note: non-const until OpenSSL 1.1.0
+ static Optional<std::string> getCommonName(X509& x509);
+
+ static std::vector<std::string> getSubjectAltNames(X509& x509);
+};
+}
+}
--- /dev/null
+/*
+ * Copyright 2017 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/ssl/OpenSSLCertUtils.h>
+
+#include <openssl/bio.h>
+#include <openssl/evp.h>
+
+#include <folly/Range.h>
+#include <folly/String.h>
+#include <folly/io/async/ssl/OpenSSLPtrTypes.h>
+#include <folly/portability/GTest.h>
+#include <folly/portability/OpenSSL.h>
+
+using namespace testing;
+using namespace folly;
+
+const char* kTestCertWithoutSan = "folly/io/async/test/certs/tests-cert.pem";
+
+// Test key
+// -----BEGIN EC PRIVATE KEY-----
+// MHcCAQEEIBskFwVZ9miFN+SKCFZPe9WEuFGmP+fsecLUnsTN6bOcoAoGCCqGSM49
+// AwEHoUQDQgAE7/f4YYOYunAM/VkmjDYDg3AWUgyyTIraWmmQZsnu0bYNV/lLLfNz
+// CtHggxGSwEtEe40nNb9C8wQmHUvb7VBBlw==
+// -----END EC PRIVATE KEY-----
+const std::string kTestCertWithSan = folly::stripLeftMargin(R"(
+ -----BEGIN CERTIFICATE-----
+ MIIDXDCCAkSgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBQMQswCQYDVQQGEwJVUzEL
+ MAkGA1UECAwCQ0ExDTALBgNVBAoMBEFzb3gxJTAjBgNVBAMMHEFzb3ggQ2VydGlm
+ aWNhdGlvbiBBdXRob3JpdHkwHhcNMTcwMjEzMjMyMTAzWhcNNDQwNzAxMjMyMTAz
+ WjAwMQswCQYDVQQGEwJVUzENMAsGA1UECgwEQXNveDESMBAGA1UEAwwJMTI3LjAu
+ MC4xMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7/f4YYOYunAM/VkmjDYDg3AW
+ UgyyTIraWmmQZsnu0bYNV/lLLfNzCtHggxGSwEtEe40nNb9C8wQmHUvb7VBBl6OC
+ ASowggEmMAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJh
+ dGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBRx1kmdZEfXHmWLHpSDI0Lh8hmfwzAf
+ BgNVHSMEGDAWgBQX3ykJKb97nxp/6UZJyDvts7noezAxBgNVHREEKjAoghJhbm90
+ aGVyZXhhbXBsZS5jb22CEioudGhpcmRleGFtcGxlLmNvbTB4BggrBgEFBQcBAQRs
+ MGowaAYIKwYBBQUHMAKGXGh0dHBzOi8vcGhhYnJpY2F0b3IuZmIuY29tL2RpZmZ1
+ c2lvbi9GQkNPREUvYnJvd3NlL21hc3Rlci90aS90ZXN0X2NlcnRzL2NhX2NlcnQu
+ cGVtP3ZpZXc9cmF3MA0GCSqGSIb3DQEBCwUAA4IBAQCj3FLjLMLudaFDiYo9pAPQ
+ NBYNpG27aajQCvnEsYaMAGnNBxUUhv/E4xpnJEhatiCJWlPgGebdjXkpXYkLxnFj
+ 38UmpfZbNcvPPKxXmjIlkpYeFwcHTAUpFmMXVHdr8FjkDSN+qWHLllMFNAAqp0U6
+ 4VWjDlq9xCjzNw+8fdcEpwylpPrbNyQHqSO1k+DhM2qPuQfiWPmHe2PbJv8JB3no
+ HWGi9SNe0FjtJM3066L0Gj8g/bFDo/pnyKguQyGkS7PaepK5/u5Y2fMMBO/m4+U0
+ b9Yb0TvatsqL688CoZcSn73A0yAjptwbD/4HmcVlG2j/y8eTVpXisugu6Xz+QQGu
+ -----END CERTIFICATE-----
+)");
+
+static folly::ssl::X509UniquePtr readCertFromFile(const std::string& filename) {
+ folly::ssl::BioUniquePtr bio(BIO_new(BIO_s_file()));
+ if (!bio) {
+ throw std::runtime_error("Couldn't create BIO");
+ }
+
+ if (BIO_read_filename(bio.get(), filename.c_str()) != 1) {
+ throw std::runtime_error("Couldn't read cert file: " + filename);
+ }
+ return folly::ssl::X509UniquePtr(
+ PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr));
+}
+
+static folly::ssl::X509UniquePtr readCertFromData(
+ const folly::StringPiece data) {
+ folly::ssl::BioUniquePtr bio(BIO_new_mem_buf(data.data(), data.size()));
+ if (!bio) {
+ throw std::runtime_error("Couldn't create BIO");
+ }
+ return folly::ssl::X509UniquePtr(
+ PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr));
+}
+
+TEST(OpenSSLCertUtilsTest, TestX509CN) {
+ OpenSSL_add_all_algorithms();
+
+ auto x509 = readCertFromFile(kTestCertWithoutSan);
+ EXPECT_NE(x509, nullptr);
+ auto identity = folly::ssl::OpenSSLCertUtils::getCommonName(*x509);
+ EXPECT_EQ(identity.value(), "Asox Company");
+ auto sans = folly::ssl::OpenSSLCertUtils::getSubjectAltNames(*x509);
+ EXPECT_EQ(sans.size(), 0);
+}
+
+TEST(OpenSSLCertUtilsTest, TestX509Sans) {
+ OpenSSL_add_all_algorithms();
+
+ auto x509 = readCertFromData(kTestCertWithSan);
+ EXPECT_NE(x509, nullptr);
+ auto identity = folly::ssl::OpenSSLCertUtils::getCommonName(*x509);
+ EXPECT_EQ(identity.value(), "127.0.0.1");
+ auto altNames = folly::ssl::OpenSSLCertUtils::getSubjectAltNames(*x509);
+ EXPECT_EQ(altNames.size(), 2);
+ EXPECT_EQ(altNames[0], "anotherexample.com");
+ EXPECT_EQ(altNames[1], "*.thirdexample.com");
+}