summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSara Golemon <sara.golemon@mongodb.com>2018-05-07 18:51:11 -0400
committerSara Golemon <sara.golemon@mongodb.com>2018-05-15 22:11:45 -0400
commitfa3fae04c144048bebf3f4c58e08b7b0c32743e1 (patch)
tree32cc44010126bb4e5d94e0c181aeb210f34edab8 /src
parentcf339b8a8d8708e8b28747fe0cafee7cc79fe9a6 (diff)
downloadmongo-fa3fae04c144048bebf3f4c58e08b7b0c32743e1.tar.gz
SERVER-34735 Extract structured data from X509 subject names
Diffstat (limited to 'src')
-rw-r--r--src/mongo/client/async_client.cpp2
-rw-r--r--src/mongo/client/dbclient.cpp2
-rw-r--r--src/mongo/db/auth/authorization_manager_test.cpp13
-rw-r--r--src/mongo/db/auth/authz_manager_external_state.cpp13
-rw-r--r--src/mongo/db/commands/authentication_commands.cpp14
-rw-r--r--src/mongo/db/db.cpp2
-rw-r--r--src/mongo/db/initialize_server_global_state.cpp2
-rw-r--r--src/mongo/executor/network_interface_asio_auth.cpp2
-rw-r--r--src/mongo/util/net/ssl_manager.cpp123
-rw-r--r--src/mongo/util/net/ssl_manager.h13
-rw-r--r--src/mongo/util/net/ssl_manager_apple.cpp134
-rw-r--r--src/mongo/util/net/ssl_manager_openssl.cpp82
-rw-r--r--src/mongo/util/net/ssl_manager_windows.cpp66
-rw-r--r--src/mongo/util/net/ssl_parameters.cpp8
-rw-r--r--src/mongo/util/net/ssl_types.h66
15 files changed, 351 insertions, 191 deletions
diff --git a/src/mongo/client/async_client.cpp b/src/mongo/client/async_client.cpp
index 184f69689a3..374dd2cf7a7 100644
--- a/src/mongo/client/async_client.cpp
+++ b/src/mongo/client/async_client.cpp
@@ -135,7 +135,7 @@ Future<void> AsyncDBClient::authenticate(const BSONObj& params) {
std::string clientName;
#ifdef MONGO_CONFIG_SSL
if (getSSLManager()) {
- clientName = getSSLManager()->getSSLConfiguration().clientSubjectName;
+ clientName = getSSLManager()->getSSLConfiguration().clientSubjectName.toString();
}
#endif
diff --git a/src/mongo/client/dbclient.cpp b/src/mongo/client/dbclient.cpp
index c93aa177fdd..3a7e95e819c 100644
--- a/src/mongo/client/dbclient.cpp
+++ b/src/mongo/client/dbclient.cpp
@@ -466,7 +466,7 @@ void DBClientBase::_auth(const BSONObj& params) {
std::string clientName = "";
#ifdef MONGO_CONFIG_SSL
if (sslManager() != nullptr) {
- clientName = sslManager()->getSSLConfiguration().clientSubjectName;
+ clientName = sslManager()->getSSLConfiguration().clientSubjectName.toString();
}
#endif
diff --git a/src/mongo/db/auth/authorization_manager_test.cpp b/src/mongo/db/auth/authorization_manager_test.cpp
index c382e0e8e00..75d078bfbc7 100644
--- a/src/mongo/db/auth/authorization_manager_test.cpp
+++ b/src/mongo/db/auth/authorization_manager_test.cpp
@@ -32,6 +32,7 @@
*/
#include "mongo/base/status.h"
#include "mongo/bson/mutable/document.h"
+#include "mongo/config.h"
#include "mongo/crypto/mechanism_scram.h"
#include "mongo/crypto/sha1_block.h"
#include "mongo/crypto/sha256_block.h"
@@ -59,6 +60,12 @@
namespace mongo {
namespace {
+// Construct a simple, structured X509 name equivalent to "CN=mongodb.com"
+SSLX509Name buildX509Name() {
+ return SSLX509Name(std::vector<std::vector<SSLX509Name::Entry>>(
+ {{{kOID_CommonName.toString(), 19 /* Printable String */, "mongodb.com"}}}));
+}
+
void setX509PeerInfo(const transport::SessionHandle& session, SSLPeerInfo info) {
auto& sslPeerInfo = SSLPeerInfo::forSession(session);
sslPeerInfo = info;
@@ -257,13 +264,14 @@ TEST_F(AuthorizationManagerTest, testAcquireV2User) {
authzManager->releaseUser(v2cluster);
}
+#ifdef MONGO_CONFIG_SSL
TEST_F(AuthorizationManagerTest, testLocalX509Authorization) {
ServiceContextNoop serviceContext;
transport::TransportLayerMock transportLayer{};
transport::SessionHandle session = transportLayer.createSession();
setX509PeerInfo(
session,
- SSLPeerInfo("CN=mongodb.com", {RoleName("read", "test"), RoleName("readWrite", "test")}));
+ SSLPeerInfo(buildX509Name(), {RoleName("read", "test"), RoleName("readWrite", "test")}));
ServiceContext::UniqueClient client = serviceContext.makeClient("testClient", session);
ServiceContext::UniqueOperationContext opCtx = client->makeOperationContext();
@@ -290,6 +298,7 @@ TEST_F(AuthorizationManagerTest, testLocalX509Authorization) {
authzManager->releaseUser(x509User);
}
+#endif
TEST_F(AuthorizationManagerTest, testLocalX509AuthorizationInvalidUser) {
ServiceContextNoop serviceContext;
@@ -297,7 +306,7 @@ TEST_F(AuthorizationManagerTest, testLocalX509AuthorizationInvalidUser) {
transport::SessionHandle session = transportLayer.createSession();
setX509PeerInfo(
session,
- SSLPeerInfo("CN=mongodb.com", {RoleName("read", "test"), RoleName("write", "test")}));
+ SSLPeerInfo(buildX509Name(), {RoleName("read", "test"), RoleName("write", "test")}));
ServiceContext::UniqueClient client = serviceContext.makeClient("testClient", session);
ServiceContext::UniqueOperationContext opCtx = client->makeOperationContext();
diff --git a/src/mongo/db/auth/authz_manager_external_state.cpp b/src/mongo/db/auth/authz_manager_external_state.cpp
index 1eef2c854d2..49e52a6455b 100644
--- a/src/mongo/db/auth/authz_manager_external_state.cpp
+++ b/src/mongo/db/auth/authz_manager_external_state.cpp
@@ -28,6 +28,7 @@
#include "mongo/platform/basic.h"
+#include "mongo/config.h"
#include "mongo/db/auth/authz_manager_external_state.h"
#include "mongo/db/auth/user_name.h"
#include "mongo/db/operation_context.h"
@@ -42,11 +43,17 @@ AuthzManagerExternalState::~AuthzManagerExternalState() = default;
bool AuthzManagerExternalState::shouldUseRolesFromConnection(OperationContext* opCtx,
const UserName& userName) {
- if (!opCtx || !opCtx->getClient() || !opCtx->getClient()->session())
+#ifdef MONGO_CONFIG_SSL
+ if (!opCtx || !opCtx->getClient() || !opCtx->getClient()->session()) {
return false;
+ }
+
auto& sslPeerInfo = SSLPeerInfo::forSession(opCtx->getClient()->session());
- return sslPeerInfo.subjectName == userName.getUser() && userName.getDB() == "$external" &&
- !sslPeerInfo.roles.empty();
+ return sslPeerInfo.subjectName.toString() == userName.getUser() &&
+ userName.getDB() == "$external" && !sslPeerInfo.roles.empty();
+#else
+ return false;
+#endif
}
diff --git a/src/mongo/db/commands/authentication_commands.cpp b/src/mongo/db/commands/authentication_commands.cpp
index ffe3fff78ee..00bf6dd23cb 100644
--- a/src/mongo/db/commands/authentication_commands.cpp
+++ b/src/mongo/db/commands/authentication_commands.cpp
@@ -88,7 +88,7 @@ Status _authenticateX509(OperationContext* opCtx, const UserName& user, const BS
if (!getSSLManager()->getSSLConfiguration().hasCA) {
return Status(ErrorCodes::AuthenticationFailed,
"Unable to verify x.509 certificate, as no CA has been provided.");
- } else if (user.getUser() != clientName) {
+ } else if (user.getUser() != clientName.toString()) {
return Status(ErrorCodes::AuthenticationFailed,
"There is no x.509 client certificate matching the user.");
} else {
@@ -236,14 +236,14 @@ bool CmdAuthenticate::run(OperationContext* opCtx,
if (mechanism.empty()) {
uasserted(ErrorCodes::BadValue, "Auth mechanism not specified");
}
- UserName user;
- auto& sslPeerInfo = SSLPeerInfo::forSession(opCtx->getClient()->session());
- if (mechanism == kX509AuthMechanism && !cmdObj.hasField("user")) {
- user = UserName(sslPeerInfo.subjectName, dbname);
- } else {
- user = UserName(cmdObj.getStringField("user"), dbname);
+ UserName user(cmdObj.getStringField("user"), dbname);
+#ifdef MONGO_CONFIG_SSL
+ if (mechanism == kX509AuthMechanism && user.getUser().empty()) {
+ auto& sslPeerInfo = SSLPeerInfo::forSession(opCtx->getClient()->session());
+ user = UserName(sslPeerInfo.subjectName.toString(), dbname);
}
+#endif
uassert(ErrorCodes::AuthenticationFailed, "No user name provided", !user.getUser().empty());
if (getTestCommandsEnabled() && user.getDB() == "admin" &&
diff --git a/src/mongo/db/db.cpp b/src/mongo/db/db.cpp
index f9b47ed91de..5ddf80abdd9 100644
--- a/src/mongo/db/db.cpp
+++ b/src/mongo/db/db.cpp
@@ -373,7 +373,7 @@ ExitCode _initAndListen(int listenPort) {
logMongodStartupWarnings(storageGlobalParams, serverGlobalParams, serviceContext);
-#if MONGO_CONFIG_SSL
+#ifdef MONGO_CONFIG_SSL
if (sslGlobalParams.sslAllowInvalidCertificates &&
((serverGlobalParams.clusterAuthMode.load() == ServerGlobalParams::ClusterAuthMode_x509) ||
sequenceContains(saslGlobalParams.authenticationMechanisms, "MONGODB-X509"))) {
diff --git a/src/mongo/db/initialize_server_global_state.cpp b/src/mongo/db/initialize_server_global_state.cpp
index 17b17180073..847873010ee 100644
--- a/src/mongo/db/initialize_server_global_state.cpp
+++ b/src/mongo/db/initialize_server_global_state.cpp
@@ -406,7 +406,7 @@ bool initializeServerGlobalState() {
<< saslCommandUserDBFieldName
<< "$external"
<< saslCommandUserFieldName
- << getSSLManager()->getSSLConfiguration().clientSubjectName));
+ << getSSLManager()->getSSLConfiguration().clientSubjectName.toString()));
}
#endif
return true;
diff --git a/src/mongo/executor/network_interface_asio_auth.cpp b/src/mongo/executor/network_interface_asio_auth.cpp
index b9eefb60646..1a981b3d017 100644
--- a/src/mongo/executor/network_interface_asio_auth.cpp
+++ b/src/mongo/executor/network_interface_asio_auth.cpp
@@ -191,7 +191,7 @@ void NetworkInterfaceASIO::_authenticate(AsyncOp* op) {
std::string clientName;
#ifdef MONGO_CONFIG_SSL
if (getSSLManager()) {
- clientName = getSSLManager()->getSSLConfiguration().clientSubjectName;
+ clientName = getSSLManager()->getSSLConfiguration().clientSubjectName.toString();
}
#endif
diff --git a/src/mongo/util/net/ssl_manager.cpp b/src/mongo/util/net/ssl_manager.cpp
index 79a99543475..ae3aa75d222 100644
--- a/src/mongo/util/net/ssl_manager.cpp
+++ b/src/mongo/util/net/ssl_manager.cpp
@@ -44,7 +44,6 @@
#include "mongo/util/net/ssl_types.h"
#include "mongo/util/text.h"
-
namespace mongo {
namespace {
@@ -107,6 +106,72 @@ public:
#ifdef MONGO_CONFIG_SSL
namespace {
+#if MONGO_CONFIG_SSL_PROVIDER == MONGO_CONFIG_SSL_PROVIDER_OPENSSL
+// OpenSSL has a more complete library of OID to SN mappings.
+std::string x509OidToShortName(const std::string& name) {
+ const auto* sn = OBJ_nid2sn(OBJ_txt2nid(entry.oid.c_str()));
+ if (!sn) {
+ return name;
+ }
+ return sn;
+}
+#else
+// On Apple/Windows we have to provide our own mapping.
+std::string x509OidToShortName(const std::string& name) {
+ static const std::map<std::string, std::string> kX509OidToShortNameMappings = {
+ {"0.9.2342.19200300.100.1.1", "UID"},
+ {"0.9.2342.19200300.100.1.25", "DC"},
+ {"1.2.840.113549.1.9.1", "emailAddress"},
+ {"2.5.4.3", "CN"},
+ {"2.5.4.6", "C"},
+ {"2.5.4.7", "L"},
+ {"2.5.4.8", "ST"},
+ {"2.5.4.9", "STREET"},
+ {"2.5.4.10", "O"},
+ {"2.5.4.11", "OU"},
+ };
+
+ auto it = kX509OidToShortNameMappings.find(name);
+ if (it == kX509OidToShortNameMappings.end()) {
+ return name;
+ }
+ return it->second;
+}
+#endif
+} // namespace
+
+StatusWith<std::string> SSLX509Name::getOID(StringData oid) const {
+ for (const auto& rdn : _entries) {
+ for (const auto& entry : rdn) {
+ if (entry.oid == oid) {
+ return entry.value;
+ }
+ }
+ }
+ return {ErrorCodes::KeyNotFound, "OID does not exist"};
+}
+
+StringBuilder& operator<<(StringBuilder& os, const SSLX509Name& name) {
+ std::string comma;
+ for (const auto& rdn : name._entries) {
+ std::string plus;
+ os << comma;
+ for (const auto& entry : rdn) {
+ os << plus << x509OidToShortName(entry.oid) << "=" << escapeRfc2253(entry.value);
+ plus = "+";
+ }
+ comma = ",";
+ }
+ return os;
+}
+
+std::string SSLX509Name::toString() const {
+ StringBuilder os;
+ os << *this;
+ return os.str();
+}
+
+namespace {
void canonicalizeClusterDN(std::vector<std::string>* dn) {
// remove all RDNs we don't care about
for (size_t i = 0; i < dn->size(); i++) {
@@ -121,30 +186,62 @@ void canonicalizeClusterDN(std::vector<std::string>* dn) {
}
std::stable_sort(dn->begin(), dn->end());
}
+
+constexpr StringData kOID_DC = "0.9.2342.19200300.100.1.25"_sd;
+constexpr StringData kOID_O = "2.5.4.10"_sd;
+constexpr StringData kOID_OU = "2.5.4.11"_sd;
+
+std::vector<SSLX509Name::Entry> canonicalizeClusterDN(
+ const std::vector<std::vector<SSLX509Name::Entry>>& entries) {
+ std::vector<SSLX509Name::Entry> ret;
+
+ for (const auto& rdn : entries) {
+ for (const auto& entry : rdn) {
+ if ((entry.oid != kOID_DC) && (entry.oid != kOID_O) && (entry.oid != kOID_OU)) {
+ continue;
+ }
+ ret.push_back(entry);
+ }
+ }
+ std::stable_sort(ret.begin(), ret.end());
+ return ret;
+}
} // namespace
+/**
+ * The behavior of isClusterMember() is subtly different when passed
+ * an SSLX509Name versus a StringData.
+ *
+ * The SSLX509Name version (immediately below) compares distinguished
+ * names in their raw, unescaped forms and provides a more reliable match.
+ *
+ * The StringData version attempts to do a simplified string compare
+ * with the serialized version of the server subject name.
+ *
+ * Because escaping is not checked in the StringData version,
+ * some not-strictly matching RDNs will appear to share O/OU/DC with the
+ * server subject name. Therefore, that variant should be called with care.
+ */
+bool SSLConfiguration::isClusterMember(const SSLX509Name& subject) const {
+ auto client = canonicalizeClusterDN(subject._entries);
+ auto server = canonicalizeClusterDN(serverSubjectName._entries);
+
+ return !client.empty() && (client == server);
+}
+
bool SSLConfiguration::isClusterMember(StringData subjectName) const {
std::vector<std::string> clientRDN = StringSplitter::split(subjectName.toString(), ",");
- std::vector<std::string> serverRDN = StringSplitter::split(serverSubjectName, ",");
+ std::vector<std::string> serverRDN = StringSplitter::split(serverSubjectName.toString(), ",");
canonicalizeClusterDN(&clientRDN);
canonicalizeClusterDN(&serverRDN);
- if (clientRDN.size() == 0 || clientRDN.size() != serverRDN.size()) {
- return false;
- }
-
- for (size_t i = 0; i < serverRDN.size(); i++) {
- if (clientRDN[i] != serverRDN[i]) {
- return false;
- }
- }
- return true;
+ return !clientRDN.empty() && (clientRDN == serverRDN);
}
BSONObj SSLConfiguration::getServerStatusBSON() const {
BSONObjBuilder security;
- security.append("SSLServerSubjectName", serverSubjectName);
+ security.append("SSLServerSubjectName", serverSubjectName.toString());
security.appendBool("SSLServerHasCertificateAuthority", hasCA);
security.appendDate("SSLServerCertificateExpirationDate", serverCertificateExpirationDate);
return security.obj();
diff --git a/src/mongo/util/net/ssl_manager.h b/src/mongo/util/net/ssl_manager.h
index 71f8b5a5d66..90abfb18721 100644
--- a/src/mongo/util/net/ssl_manager.h
+++ b/src/mongo/util/net/ssl_manager.h
@@ -87,18 +87,11 @@ public:
};
struct SSLConfiguration {
- SSLConfiguration() : serverSubjectName(""), clientSubjectName("") {}
- SSLConfiguration(const std::string& serverSubjectName,
- const std::string& clientSubjectName,
- const Date_t& serverCertificateExpirationDate)
- : serverSubjectName(serverSubjectName),
- clientSubjectName(clientSubjectName),
- serverCertificateExpirationDate(serverCertificateExpirationDate) {}
-
bool isClusterMember(StringData subjectName) const;
+ bool isClusterMember(const SSLX509Name& subjectName) const;
BSONObj getServerStatusBSON() const;
- std::string serverSubjectName;
- std::string clientSubjectName;
+ SSLX509Name serverSubjectName;
+ SSLX509Name clientSubjectName;
Date_t serverCertificateExpirationDate;
bool hasCA = false;
};
diff --git a/src/mongo/util/net/ssl_manager_apple.cpp b/src/mongo/util/net/ssl_manager_apple.cpp
index fbb02049c8f..01569048e24 100644
--- a/src/mongo/util/net/ssl_manager_apple.cpp
+++ b/src/mongo/util/net/ssl_manager_apple.cpp
@@ -279,34 +279,33 @@ StatusWith<T> extractDictionaryValue(::CFDictionaryRef dict, ::CFStringRef key)
return reinterpret_cast<T>(val);
}
-StatusWith<std::string> mapSubjectLabel(::CFStringRef label) {
- if (!::CFStringCompare(label, ::kSecOIDCommonName, 0)) {
- return {"CN"};
- } else if (!::CFStringCompare(label, ::kSecOIDCountryName, 0)) {
- return {"C"};
- } else if (!::CFStringCompare(label, ::kSecOIDStateProvinceName, 0)) {
- return {"ST"};
- } else if (!::CFStringCompare(label, ::kSecOIDLocalityName, 0)) {
- return {"L"};
- } else if (!::CFStringCompare(label, ::kSecOIDOrganizationName, 0)) {
- return {"O"};
- } else if (!::CFStringCompare(label, ::kSecOIDOrganizationalUnitName, 0)) {
- return {"OU"};
- } else if (!::CFStringCompare(label, ::kSecOIDStreetAddress, 0)) {
- return {"STREET"};
- } else if (!::CFStringCompare(label, CFSTR("0.9.2342.19200300.100.1.25"), 0)) {
- return {"DC"};
- } else if (!::CFStringCompare(label, CFSTR("0.9.2342.19200300.100.1.1"), 0)) {
- return {"UID"};
- } else if (!::CFStringCompare(label, ::kSecOIDEmailAddress, 0)) {
- return {"emailAddress"};
- }
-
- return toString(label);
+StatusWith<SSLX509Name::Entry> extractSingleOIDEntry(::CFDictionaryRef entry) {
+ auto swLabel = extractDictionaryValue<::CFStringRef>(entry, ::kSecPropertyKeyLabel);
+ if (!swLabel.isOK()) {
+ return swLabel.getStatus();
+ }
+ auto swLabelStr = toString(swLabel.getValue());
+ if (!swLabelStr.isOK()) {
+ return swLabelStr.getStatus();
+ }
+
+ auto swValue = extractDictionaryValue<::CFStringRef>(entry, ::kSecPropertyKeyValue);
+ if (!swValue.isOK()) {
+ return swValue.getStatus();
+ }
+ auto swValueStr = toString(swValue.getValue());
+ if (!swValueStr.isOK()) {
+ return swValueStr.getStatus();
+ }
+
+ // Secure Transport doesn't give us access to the specific string type,
+ // so regard all strings as ASN1_PRINTABLESTRING on this platform.
+ return SSLX509Name::Entry(
+ std::move(swLabelStr.getValue()), 19, std::move(swValueStr.getValue()));
}
-// Encode a raw DER subject sequence into a human readable subject name (RFC 2253).
-StatusWith<std::string> extractSubjectName(::CFDictionaryRef dict) {
+// Translate a raw DER subject sequence into a structured subject name.
+StatusWith<SSLX509Name> extractSubjectName(::CFDictionaryRef dict) {
auto swSubject = extractDictionaryValue<::CFDictionaryRef>(dict, ::kSecOIDX509V1SubjectName);
if (!swSubject.isOK()) {
return swSubject.getStatus();
@@ -320,7 +319,7 @@ StatusWith<std::string> extractSubjectName(::CFDictionaryRef dict) {
auto elems = swElems.getValue();
const auto nElems = ::CFArrayGetCount(elems);
- StringBuilder ret;
+ std::vector<std::vector<SSLX509Name::Entry>> ret;
for (auto i = nElems; i; --i) {
auto elem = reinterpret_cast<::CFDictionaryRef>(::CFArrayGetValueAtIndex(elems, i - 1));
invariant(elem);
@@ -329,30 +328,46 @@ StatusWith<std::string> extractSubjectName(::CFDictionaryRef dict) {
"Subject name element is not a dictionary"};
}
- auto swLabel = extractDictionaryValue<::CFStringRef>(elem, ::kSecPropertyKeyLabel);
- if (!swLabel.isOK()) {
- return swLabel.getStatus();
- }
- auto swLabelStr = mapSubjectLabel(swLabel.getValue());
- if (!swLabelStr.isOK()) {
- return swLabelStr.getStatus();
+ auto swType = extractDictionaryValue<::CFStringRef>(elem, ::kSecPropertyKeyType);
+ if (!swType.isOK()) {
+ return swType.getStatus();
}
- auto swValue = extractDictionaryValue<::CFStringRef>(elem, ::kSecPropertyKeyValue);
- if (!swValue.isOK()) {
- return swValue.getStatus();
- }
- auto swValueStr = toString(swValue.getValue());
- if (!swValueStr.isOK()) {
- return swValueStr.getStatus();
- }
+ if (!::CFStringCompare(swType.getValue(), CFSTR("section"), 0)) {
+ // Multi-value RDN.
+ auto swList = extractDictionaryValue<::CFArrayRef>(elem, ::kSecPropertyKeyValue);
+ if (!swList.isOK()) {
+ return swList.getStatus();
+ }
+ const auto nRDNAttrs = ::CFArrayGetCount(swList.getValue());
+ std::vector<SSLX509Name::Entry> rdn;
+ for (auto j = nRDNAttrs; j; --j) {
+ auto rdnElem = reinterpret_cast<::CFDictionaryRef>(
+ ::CFArrayGetValueAtIndex(swList.getValue(), j - 1));
+ invariant(rdnElem);
+ if (::CFGetTypeID(rdnElem) != ::CFDictionaryGetTypeID()) {
+ return {ErrorCodes::InvalidSSLConfiguration,
+ "Subject name sub-element is not a dictionary"};
+ }
+ auto swEntry = extractSingleOIDEntry(rdnElem);
+ if (!swEntry.isOK()) {
+ return swEntry.getStatus();
+ }
+ rdn.push_back(std::move(swEntry.getValue()));
+ }
+ ret.push_back(std::move(rdn));
- ret << swLabelStr.getValue() << '=' << escapeRfc2253(swValueStr.getValue());
- if (i > 1) {
- ret << ",";
+ } else {
+ // Single Value RDN.
+ auto swEntry = extractSingleOIDEntry(elem);
+ if (!swEntry.isOK()) {
+ return swEntry.getStatus();
+ }
+ ret.push_back(std::vector<SSLX509Name::Entry>({std::move(swEntry.getValue())}));
}
}
- return ret.str();
+
+ return SSLX509Name(std::move(ret));
}
StatusWith<mongo::Date_t> extractValidityDate(::CFDictionaryRef dict,
@@ -635,7 +650,7 @@ StatusWith<CFUniquePtr<::CFArrayRef>> loadPEM(const std::string& keyfilepath,
return std::move(cfcerts);
}
-StatusWith<std::string> certificateGetSubject(::SecCertificateRef cert, Date_t* expire = nullptr) {
+StatusWith<SSLX509Name> certificateGetSubject(::SecCertificateRef cert, Date_t* expire = nullptr) {
// Fetch expiry range and full subject name.
CFUniquePtr<::CFMutableArrayRef> oids(
::CFArrayCreateMutable(nullptr, expire ? 3 : 1, &::kCFTypeArrayCallBacks));
@@ -682,7 +697,7 @@ StatusWith<std::string> certificateGetSubject(::SecCertificateRef cert, Date_t*
return subject;
}
-StatusWith<std::string> certificateGetSubject(::CFArrayRef certs, Date_t* expire = nullptr) {
+StatusWith<SSLX509Name> certificateGetSubject(::CFArrayRef certs, Date_t* expire = nullptr) {
if (::CFArrayGetCount(certs) <= 0) {
return {ErrorCodes::InvalidSSLConfiguration, "No certificates in certificate list"};
}
@@ -810,7 +825,7 @@ StatusWith<CFUniquePtr<::CFArrayRef>> copyMatchingCertificate(
if (!swCertSubject.isOK()) {
return swCertSubject.getStatus();
}
- if (swCertSubject.getValue() == selector.subject) {
+ if (swCertSubject.getValue().toString() == selector.subject) {
::CFArrayAppendValue(cfresult.get(), ident);
break;
}
@@ -1331,18 +1346,17 @@ StatusWith<boost::optional<SSLPeerInfo>> SSLManagerApple::parseAndValidatePeerCe
certErr << san << " ";
}
- } else if (peerSubjectName.find("CN=") != std::string::npos) {
- const auto cnBegin = peerSubjectName.find("CN=") + 3;
- auto cnEnd = peerSubjectName.find(",", cnBegin);
- if (cnEnd == std::string::npos) {
- cnEnd = peerSubjectName.size();
- }
- const auto commonName = peerSubjectName.substr(cnBegin, cnEnd - cnBegin);
- cnMatch = hostNameMatchForX509Certificates(remoteHost, commonName);
- certErr << "CN: " << commonName;
-
} else {
- certErr << "No Common Name (CN) or Subject Alternate Names (SAN) found";
+ auto swCN = peerSubjectName.getOID(kOID_CommonName);
+ if (swCN.isOK()) {
+ auto commonName = std::move(swCN.getValue());
+ if (hostNameMatchForX509Certificates(remoteHost, commonName)) {
+ cnMatch = true;
+ }
+ certErr << "CN: " << commonName;
+ } else {
+ certErr << "No Common Name (CN) or Subject Alternate Names (SAN) found";
+ }
}
if (!sanMatch && !cnMatch) {
diff --git a/src/mongo/util/net/ssl_manager_openssl.cpp b/src/mongo/util/net/ssl_manager_openssl.cpp
index 20abd7b6421..6b48b228926 100644
--- a/src/mongo/util/net/ssl_manager_openssl.cpp
+++ b/src/mongo/util/net/ssl_manager_openssl.cpp
@@ -176,6 +176,9 @@ IMPLEMENT_ASN1_ENCODE_FUNCTIONS_const_fname(ASN1_SEQUENCE_ANY, ASN1_SET_ANY, ASN
const STACK_OF(X509_EXTENSION) * X509_get0_extensions(const X509* peerCert) {
return peerCert->cert_info->extensions;
}
+inline int X509_NAME_ENTRY_set(const X509_NAME_ENTRY* ne) {
+ return ne->set;
+}
#endif
/**
@@ -394,7 +397,7 @@ private:
*/
bool _parseAndValidateCertificate(const std::string& keyFile,
const std::string& keyPassword,
- std::string* subjectName,
+ SSLX509Name* subjectName,
Date_t* serverNotAfter);
@@ -497,23 +500,42 @@ SSLManagerInterface* getSSLManager() {
return NULL;
}
-std::string getCertificateSubjectName(X509* cert) {
- std::string result;
+SSLX509Name getCertificateSubjectX509Name(X509* cert) {
+ std::vector<std::vector<SSLX509Name::Entry>> entries;
+
+ auto name = X509_get_subject_name(cert);
+ int count = X509_NAME_entry_count(name);
+ int prevSet = -1;
+ std::vector<SSLX509Name::Entry> rdn;
+ for (int i = count - 1; i >= 0; --i) {
+ auto* entry = X509_NAME_get_entry(name, i);
+
+ const auto currentSet = X509_NAME_ENTRY_set(entry);
+ if (currentSet != prevSet) {
+ if (!rdn.empty()) {
+ entries.push_back(std::move(rdn));
+ rdn = std::vector<SSLX509Name::Entry>();
+ }
+ prevSet = currentSet;
+ }
- BIO* out = BIO_new(BIO_s_mem());
- uassert(16884, "unable to allocate BIO memory", NULL != out);
- ON_BLOCK_EXIT(BIO_free, out);
+ char buffer[128];
+ // OBJ_obj2txt can only fail if we pass a nullptr from get_object,
+ // or if OpenSSL's BN library falls over.
+ // In either case, just panic.
+ uassert(ErrorCodes::InvalidSSLConfiguration,
+ "Unable to parse certiciate subject name",
+ OBJ_obj2txt(buffer, sizeof(buffer), X509_NAME_ENTRY_get_object(entry), 1) > 0);
- if (X509_NAME_print_ex(out, X509_get_subject_name(cert), 0, XN_FLAG_RFC2253) >= 0) {
- if (BIO_number_written(out) > 0) {
- result.resize(BIO_number_written(out));
- BIO_read(out, &result[0], result.size());
- }
- } else {
- log() << "failed to convert subject name to RFC2253 format";
+ const auto* str = X509_NAME_ENTRY_get_data(entry);
+ rdn.emplace_back(
+ buffer, str->type, std::string(reinterpret_cast<const char*>(str->data), str->length));
+ }
+ if (!rdn.empty()) {
+ entries.push_back(std::move(rdn));
}
- return result;
+ return SSLX509Name(std::move(entries));
}
SSLConnectionOpenSSL::SSLConnectionOpenSSL(SSL_CTX* context,
@@ -803,7 +825,7 @@ unsigned long long SSLManagerOpenSSL::_convertASN1ToMillis(ASN1_TIME* asn1time)
bool SSLManagerOpenSSL::_parseAndValidateCertificate(const std::string& keyFile,
const std::string& keyPassword,
- std::string* subjectName,
+ SSLX509Name* subjectName,
Date_t* serverCertificateExpirationDate) {
BIO* inBIO = BIO_new(BIO_s_file());
if (inBIO == NULL) {
@@ -830,7 +852,7 @@ bool SSLManagerOpenSSL::_parseAndValidateCertificate(const std::string& keyFile,
}
ON_BLOCK_EXIT(X509_free, x509);
- *subjectName = getCertificateSubjectName(x509);
+ *subjectName = getCertificateSubjectX509Name(x509);
if (serverCertificateExpirationDate != NULL) {
unsigned long long notBeforeMillis = _convertASN1ToMillis(X509_get_notBefore(x509));
if (notBeforeMillis == 0) {
@@ -1246,8 +1268,8 @@ StatusWith<boost::optional<SSLPeerInfo>> SSLManagerOpenSSL::parseAndValidatePeer
}
// TODO: check optional cipher restriction, using cert.
- std::string peerSubjectName = getCertificateSubjectName(peerCert);
- LOG(2) << "Accepted TLS connection from peer: " << peerSubjectName;
+ auto peerSubject = getCertificateSubjectX509Name(peerCert);
+ LOG(2) << "Accepted TLS connection from peer: " << peerSubject;
StatusWith<stdx::unordered_set<RoleName>> swPeerCertificateRoles = _parsePeerRoles(peerCert);
if (!swPeerCertificateRoles.isOK()) {
@@ -1258,7 +1280,7 @@ StatusWith<boost::optional<SSLPeerInfo>> SSLManagerOpenSSL::parseAndValidatePeer
// perform hostname validation of the remote server
if (remoteHost.empty()) {
return boost::make_optional(
- SSLPeerInfo(peerSubjectName, std::move(swPeerCertificateRoles.getValue())));
+ SSLPeerInfo(peerSubject, std::move(swPeerCertificateRoles.getValue())));
}
// Try to match using the Subject Alternate Name, if it exists.
@@ -1288,19 +1310,19 @@ StatusWith<boost::optional<SSLPeerInfo>> SSLManagerOpenSSL::parseAndValidatePeer
}
}
sk_GENERAL_NAME_pop_free(sanNames, GENERAL_NAME_free);
- } else if (peerSubjectName.find("CN=") != std::string::npos) {
+ } else {
// If Subject Alternate Name (SAN) doesn't exist and Common Name (CN) does,
// check Common Name.
- int cnBegin = peerSubjectName.find("CN=") + 3;
- int cnEnd = peerSubjectName.find(",", cnBegin);
- std::string commonName = peerSubjectName.substr(cnBegin, cnEnd - cnBegin);
-
- if (hostNameMatchForX509Certificates(remoteHost, commonName)) {
- cnMatch = true;
+ auto swCN = peerSubject.getOID(kOID_CommonName);
+ if (swCN.isOK()) {
+ auto commonName = std::move(swCN.getValue());
+ if (hostNameMatchForX509Certificates(remoteHost, commonName)) {
+ cnMatch = true;
+ }
+ certificateNames << "CN: " << commonName;
+ } else {
+ certificateNames << "No Common Name (CN) or Subject Alternate Names (SAN) found";
}
- certificateNames << "CN: " << commonName;
- } else {
- certificateNames << "No Common Name (CN) or Subject Alternate Names (SAN) found";
}
if (!sanMatch && !cnMatch) {
@@ -1316,7 +1338,7 @@ StatusWith<boost::optional<SSLPeerInfo>> SSLManagerOpenSSL::parseAndValidatePeer
}
}
- return boost::make_optional(SSLPeerInfo(peerSubjectName, stdx::unordered_set<RoleName>()));
+ return boost::make_optional(SSLPeerInfo(peerSubject, stdx::unordered_set<RoleName>()));
}
diff --git a/src/mongo/util/net/ssl_manager_windows.cpp b/src/mongo/util/net/ssl_manager_windows.cpp
index 14ed8c9e68f..d96595f48a5 100644
--- a/src/mongo/util/net/ssl_manager_windows.cpp
+++ b/src/mongo/util/net/ssl_manager_windows.cpp
@@ -294,7 +294,7 @@ private:
void _handshake(SSLConnectionWindows* conn, bool client);
Status _validateCertificate(PCCERT_CONTEXT cert,
- std::string* subjectName,
+ SSLX509Name* subjectName,
Date_t* serverCertificateExpirationDate);
Status _initChainEngines(bool hasCAFile);
@@ -1356,34 +1356,8 @@ unsigned long long FiletimeToEpocMillis(FILETIME ft) {
return ns100 / 1000;
}
-StatusWith<std::string> mapSubjectLabel(LPSTR label) {
- if (strcmp(label, szOID_COMMON_NAME) == 0) {
- return {"CN"};
- } else if (strcmp(label, szOID_COUNTRY_NAME) == 0) {
- return {"C"};
- } else if (strcmp(label, szOID_STATE_OR_PROVINCE_NAME) == 0) {
- return {"ST"};
- } else if (strcmp(label, szOID_LOCALITY_NAME) == 0) {
- return {"L"};
- } else if (strcmp(label, szOID_ORGANIZATION_NAME) == 0) {
- return {"O"};
- } else if (strcmp(label, szOID_ORGANIZATIONAL_UNIT_NAME) == 0) {
- return {"OU"};
- } else if (strcmp(label, szOID_STREET_ADDRESS) == 0) {
- return {"STREET"};
- } else if (strcmp(label, szOID_DOMAIN_COMPONENT) == 0) {
- return {"DC"};
- } else if (strcmp(label, szOID_RSA_emailAddr) == 0) {
- return {"emailAddress"};
- } else if (strcmp(label, "0.9.2342.19200300.100.1.1") == 0) {
- return {"UID"};
- }
-
- return label;
-}
-
// MongoDB wants RFC 2253 (LDAP) formatted DN names for auth purposes
-StatusWith<std::string> getCertificateSubjectName(PCCERT_CONTEXT cert) {
+StatusWith<SSLX509Name> getCertificateSubjectName(PCCERT_CONTEXT cert) {
auto swBlob =
decodeObject(X509_NAME, cert->pCertInfo->Subject.pbData, cert->pCertInfo->Subject.cbData);
@@ -1394,14 +1368,13 @@ StatusWith<std::string> getCertificateSubjectName(PCCERT_CONTEXT cert) {
PCERT_NAME_INFO nameInfo = reinterpret_cast<PCERT_NAME_INFO>(swBlob.getValue().data());
- StringBuilder output;
-
- bool addComma = false;
+ std::vector<std::vector<SSLX509Name::Entry>> entries;
// Iterate in reverse order
for (int64_t i = nameInfo->cRDN - 1; i >= 0; i--) {
- for (DWORD j = 0; j < nameInfo->rgRDN[i].cRDNAttr; j++) {
- CERT_RDN_ATTR& rdnAttribute = nameInfo->rgRDN[i].rgRDNAttr[j];
+ std::vector<SSLX509Name::Entry> rdn;
+ for (DWORD j = nameInfo->rgRDN[i].cRDNAttr; j > 0; --j) {
+ CERT_RDN_ATTR& rdnAttribute = nameInfo->rgRDN[i].rgRDNAttr[j - 1];
DWORD needed =
CertRDNValueToStrW(rdnAttribute.dwValueType, &rdnAttribute.Value, NULL, 0);
@@ -1413,29 +1386,16 @@ StatusWith<std::string> getCertificateSubjectName(PCCERT_CONTEXT cert) {
const_cast<wchar_t*>(wstr.data()),
needed);
invariant(needed == converted);
-
- auto swLabel = mapSubjectLabel(rdnAttribute.pszObjId);
- if (!swLabel.isOK()) {
- return swLabel.getStatus();
- }
-
- if (addComma) {
- output << ',';
- }
-
- output << swLabel.getValue();
- output << '=';
- output << escapeRfc2253(toUtf8String(wstr));
-
- addComma = true;
+ rdn.emplace_back(rdnAttribute.pszObjId, rdnAttribute.dwValueType, toUtf8String(wstr));
}
+ entries.push_back(std::move(rdn));
}
- return output.str();
+ return SSLX509Name(std::move(entries));
}
Status SSLManagerWindows::_validateCertificate(PCCERT_CONTEXT cert,
- std::string* subjectName,
+ SSLX509Name* subjectName,
Date_t* serverCertificateExpirationDate) {
auto swCert = getCertificateSubjectName(cert);
@@ -1512,7 +1472,7 @@ Status validatePeerCertificate(const std::string& remoteHost,
HCERTCHAINENGINE certChainEngine,
bool allowInvalidCertificates,
bool allowInvalidHostnames,
- std::string* peerSubjectName) {
+ SSLX509Name* peerSubjectName) {
CERT_CHAIN_PARA certChainPara;
memset(&certChainPara, 0, sizeof(certChainPara));
certChainPara.cbSize = sizeof(CERT_CHAIN_PARA);
@@ -1619,7 +1579,7 @@ Status validatePeerCertificate(const std::string& remoteHost,
<< integerToHex(certChainPolicyStatus.dwError)
<< "): " << errnoWithDescription(certChainPolicyStatus.dwError);
warning() << msg.ss.str();
- *peerSubjectName = "";
+ *peerSubjectName = SSLX509Name();
return Status::OK();
} else if (allowInvalidHostnames) {
warning() << msg.ss.str();
@@ -1667,7 +1627,7 @@ StatusWith<boost::optional<SSLPeerInfo>> SSLManagerWindows::parseAndValidatePeer
}
UniqueCertificate certHolder(cert);
- std::string peerSubjectName;
+ SSLX509Name peerSubjectName;
// Validate against the local machine store first since it is easier to manage programmatically.
Status validateCertMachine = validatePeerCertificate(remoteHost,
diff --git a/src/mongo/util/net/ssl_parameters.cpp b/src/mongo/util/net/ssl_parameters.cpp
index f98119ef487..2ebbb5cf48d 100644
--- a/src/mongo/util/net/ssl_parameters.cpp
+++ b/src/mongo/util/net/ssl_parameters.cpp
@@ -177,12 +177,8 @@ public:
serverGlobalParams.clusterAuthMode.store(ServerGlobalParams::ClusterAuthMode_sendX509);
#ifdef MONGO_CONFIG_SSL
setInternalUserAuthParams(
- BSON(saslCommandMechanismFieldName
- << "MONGODB-X509"
- << saslCommandUserDBFieldName
- << "$external"
- << saslCommandUserFieldName
- << getSSLManager()->getSSLConfiguration().clientSubjectName));
+ BSON(saslCommandMechanismFieldName << "MONGODB-X509" << saslCommandUserDBFieldName
+ << "$external"));
#endif
} else if (str == "x509" && oldMode == ServerGlobalParams::ClusterAuthMode_sendX509) {
serverGlobalParams.clusterAuthMode.store(ServerGlobalParams::ClusterAuthMode_x509);
diff --git a/src/mongo/util/net/ssl_types.h b/src/mongo/util/net/ssl_types.h
index 91a9df3ddbf..0a0fb887274 100644
--- a/src/mongo/util/net/ssl_types.h
+++ b/src/mongo/util/net/ssl_types.h
@@ -29,22 +29,84 @@
#include <string>
+#include "mongo/bson/util/builder.h"
#include "mongo/db/auth/role_name.h"
#include "mongo/stdx/unordered_set.h"
#include "mongo/transport/session.h"
namespace mongo {
+constexpr StringData kOID_CommonName = "2.5.4.3"_sd;
+
+/**
+ * Represents a structed X509 certificate subject name.
+ * For example: C=US,O=MongoDB,OU=KernelTeam,CN=server
+ * would be held as a four element vector of Entries.
+ * The first entry of which yould be broken down something like:
+ * {{"2.5.4.6", 19, "US"}}.
+ * Note that _entries is a vector of vectors to accomodate
+ * multi-value RDNs.
+ */
+class SSLX509Name {
+public:
+ struct Entry {
+ Entry(std::string oid, int type, std::string value)
+ : oid(std::move(oid)), type(type), value(std::move(value)) {}
+ std::string oid; // e.g. "2.5.4.8" (ST)
+ int type; // e.g. 19 (PRINTABLESTRING)
+ std::string value;
+ auto equalityLens() const {
+ return std::tie(oid, type, value);
+ }
+ };
+
+ SSLX509Name() = default;
+ explicit SSLX509Name(std::vector<std::vector<Entry>> entries) : _entries(std::move(entries)) {}
+
+ /**
+ * Retreive the first instance of the value for a given OID in this name.
+ * Returns ErrorCodes::KeyNotFound if the OID does not exist.
+ */
+ StatusWith<std::string> getOID(StringData oid) const;
+
+ bool empty() const {
+ return std::all_of(
+ _entries.cbegin(), _entries.cend(), [](const auto& e) { return e.empty(); });
+ }
+
+ friend StringBuilder& operator<<(StringBuilder&, const SSLX509Name&);
+ std::string toString() const;
+
+ friend bool operator==(const SSLX509Name& lhs, const SSLX509Name& rhs) {
+ return lhs._entries == rhs._entries;
+ }
+ friend bool operator!=(const SSLX509Name& lhs, const SSLX509Name& rhs) {
+ return !(lhs._entries == rhs._entries);
+ }
+
+private:
+ friend struct SSLConfiguration;
+ std::vector<std::vector<Entry>> _entries;
+};
+
+std::ostream& operator<<(std::ostream&, const SSLX509Name&);
+inline bool operator==(const SSLX509Name::Entry& lhs, const SSLX509Name::Entry& rhs) {
+ return lhs.equalityLens() == rhs.equalityLens();
+}
+inline bool operator<(const SSLX509Name::Entry& lhs, const SSLX509Name::Entry& rhs) {
+ return lhs.equalityLens() < rhs.equalityLens();
+}
+
/**
* Contains information extracted from the peer certificate which is consumed by subsystems
* outside of the networking stack.
*/
struct SSLPeerInfo {
- SSLPeerInfo(std::string subjectName, stdx::unordered_set<RoleName> roles)
+ SSLPeerInfo(SSLX509Name subjectName, stdx::unordered_set<RoleName> roles)
: subjectName(std::move(subjectName)), roles(std::move(roles)) {}
SSLPeerInfo() = default;
- std::string subjectName;
+ SSLX509Name subjectName;
stdx::unordered_set<RoleName> roles;
static SSLPeerInfo& forSession(const transport::SessionHandle& session);