/** * Copyright (C) 2018 MongoDB Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the GNU Affero General Public License in all respects * for all of the code used other than as permitted herein. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you do not * wish to do so, delete this exception statement from your version. If you * delete this exception statement from all source files in the program, * then also delete it in the license file. */ #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kNetwork #include "mongo/platform/basic.h" #include #include #include #include "mongo/base/checked_cast.h" #include "mongo/base/init.h" #include "mongo/base/initializer_context.h" #include "mongo/base/status.h" #include "mongo/base/status_with.h" #include "mongo/crypto/sha1_block.h" #include "mongo/crypto/sha256_block.h" #include "mongo/platform/random.h" #include "mongo/stdx/memory.h" #include "mongo/util/base64.h" #include "mongo/util/concurrency/mutex.h" #include "mongo/util/log.h" #include "mongo/util/net/cidr.h" #include "mongo/util/net/private/ssl_expiration.h" #include "mongo/util/net/socket_exception.h" #include "mongo/util/net/ssl/apple.hpp" #include "mongo/util/net/ssl_manager.h" #include "mongo/util/net/ssl_options.h" using asio::ssl::apple::CFUniquePtr; /* This API appears in the Security framework even though * the SecIdentity.h header doesn't reference it. * * Use it explicitly for turning Cert/Key pairs into Identities * because it's way cheaper than going via keychain and has the * handy property of not causing difficult to diagnose heisenbugs. */ extern "C" SecIdentityRef SecIdentityCreate(CFAllocatorRef, SecCertificateRef, SecKeyRef); namespace mongo { namespace { // CFAbsoluteTime and X.509 is relative to Jan 1 2001 00:00:00 GMT // Unix Epoch (and thereby Date_t) is relative to Jan 1, 1970 00:00:00 GMT static const ::CFAbsoluteTime k20010101_000000_GMT = 978307200; ::CFStringRef kMongoDBRolesOID = nullptr; StatusWith toString(::CFStringRef str) { const auto len = ::CFStringGetMaximumSizeForEncoding(::CFStringGetLength(str), ::kCFStringEncodingUTF8); if (len == 0) { return std::string(); } std::string ret; ret.resize(len + 1); if (!::CFStringGetCString(str, &ret[0], len, ::kCFStringEncodingUTF8)) { return Status(ErrorCodes::InternalError, "Unable to convert CoreFoundation string"); } ret.resize(strlen(ret.c_str())); return ret; } // Never actually errors, but use StatusWith to be // consistent with other toString() signatures. StatusWith toString(::CFDataRef data) { const auto len = ::CFDataGetLength(data); const auto* p = (const char*)::CFDataGetBytePtr(data); return std::string(p, len); } StatusWith toString(::OSStatus status) { CFUniquePtr<::CFStringRef> errstr(::SecCopyErrorMessageString(status, nullptr)); if (!errstr) { return Status(ErrorCodes::InternalError, "Unable to convert OSStatus"); } auto ret = toString(errstr.get()); return ret; } // Ideally we'd use an operator<< overload, // but OSStatus is just a uint32_t. // Be explicit about conversion to provide meaningful // output in error streams. std::string stringFromOSStatus(::OSStatus status) { static_assert(std::is_same::value, "CoreFoundation OSStatus has changed type"); auto ret = toString(status); if (!ret.isOK()) { return str::stream() << "Unknown error: " << static_cast(status); } return ret.getValue(); } // CFTypeRef is actually just `void*`. // So while we could be polymorphic with the other toString() methods, // it's basically asking for a hard to diagnose type error. StatusWith stringFromCFType(::CFTypeRef val) { const auto type = val ? ::CFGetTypeID(val) : ((CFTypeID)-1); if (type == ::CFStringGetTypeID()) { return toString(static_cast<::CFStringRef>(val)); } else if (type == ::CFDataGetTypeID()) { return toString(static_cast<::CFDataRef>(val)); } else { return Status(ErrorCodes::BadValue, "Value is not translatable to string"); } } std::ostringstream& operator<<(std::ostringstream& ss, ::CFStringRef str) { auto swStr = toString(str); if (swStr.isOK()) { ss << swStr.getValue(); } else { ss << "Unknown error"; } return ss; } std::ostringstream& operator<<(std::ostringstream& ss, ::CFErrorRef error) { std::string comma; CFUniquePtr<::CFStringRef> desc(::CFErrorCopyDescription(error)); if (desc) { ss << comma << desc.get(); comma = ", "; } CFUniquePtr<::CFStringRef> reason(::CFErrorCopyFailureReason(error)); if (reason) { ss << comma << reason.get(); comma = ", "; } CFUniquePtr<::CFStringRef> suggest(::CFErrorCopyRecoverySuggestion(error)); if (suggest) { ss << comma << suggest.get(); comma = ", "; } auto code = ::CFErrorGetCode(error); if (code) { ss << comma << "Code: " << code; } return ss; } void uassertOSStatusOK(::OSStatus status, ErrorCodes::Error code = ErrorCodes::InvalidSSLConfiguration) { if (status == ::errSecSuccess) { return; } auto swMsg = toString(status); if (!swMsg.isOK()) { uasserted(code, str::stream() << "Unknown SSL error" << static_cast(status)); } uasserted(code, swMsg.getValue()); } void uassertOSStatusOK(::OSStatus status, SocketErrorKind kind) { if (status == ::errSecSuccess) { return; } auto swMsg = toString(status); if (!swMsg.isOK()) { throwSocketError(kind, str::stream() << "Unknown SSL error" << static_cast(status)); } throwSocketError(kind, swMsg.getValue()); } bool isUnixDomainSocket(const std::string& hostname) { return end(hostname) != std::find(begin(hostname), end(hostname), '/'); } ::OSStatus posixErrno(int err) { switch (err) { case EAGAIN: return ::errSSLWouldBlock; case ENOENT: return ::errSSLClosedGraceful; case ECONNRESET: return ::errSSLClosedAbort; default: return ::errSSLInternal; } } namespace detail { template struct CFTypeMap; template <> struct CFTypeMap<::CFStringRef> { static constexpr StringData typeName() { return "string"_sd; } static ::CFTypeID type() { return ::CFStringGetTypeID(); } }; template <> struct CFTypeMap<::CFDataRef> { static constexpr StringData typeName() { return "data"_sd; } static ::CFTypeID type() { return ::CFDataGetTypeID(); } }; template <> struct CFTypeMap<::CFNumberRef> { static constexpr StringData typeName() { return "number"_sd; } static ::CFTypeID type() { return ::CFNumberGetTypeID(); } }; template <> struct CFTypeMap<::CFArrayRef> { static constexpr StringData typeName() { return "array"_sd; } static ::CFTypeID type() { return ::CFArrayGetTypeID(); } }; template <> struct CFTypeMap<::CFDictionaryRef> { static constexpr StringData typeName() { return "dictionary"_sd; } static ::CFTypeID type() { return ::CFDictionaryGetTypeID(); } }; } // namespace detail template StatusWith extractDictionaryValue(::CFDictionaryRef dict, ::CFStringRef key) { const auto badValue = [key](StringData msg) -> Status { auto swKey = toString(key); if (!swKey.isOK()) { return {ErrorCodes::InvalidSSLConfiguration, msg}; } return {ErrorCodes::InvalidSSLConfiguration, str::stream() << msg << " for key'" << swKey.getValue() << "'"}; }; auto val = ::CFDictionaryGetValue(dict, key); if (!val) { return badValue("Missing value"); } if (::CFGetTypeID(val) != detail::CFTypeMap::type()) { return badValue(str::stream() << "Value is not a " << detail::CFTypeMap::typeName()); } return reinterpret_cast(val); } StatusWith 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())); } // Translate a raw DER subject sequence into a structured subject name. StatusWith extractSubjectName(::CFDictionaryRef dict) { auto swSubject = extractDictionaryValue<::CFDictionaryRef>(dict, ::kSecOIDX509V1SubjectName); if (!swSubject.isOK()) { return swSubject.getStatus(); } auto swElems = extractDictionaryValue<::CFArrayRef>(swSubject.getValue(), ::kSecPropertyKeyValue); if (!swElems.isOK()) { return swElems.getStatus(); } auto elems = swElems.getValue(); const auto nElems = ::CFArrayGetCount(elems); std::vector> ret; for (auto i = nElems; i; --i) { auto elem = reinterpret_cast<::CFDictionaryRef>(::CFArrayGetValueAtIndex(elems, i - 1)); invariant(elem); if (::CFGetTypeID(elem) != ::CFDictionaryGetTypeID()) { return {ErrorCodes::InvalidSSLConfiguration, "Subject name element is not a dictionary"}; } auto swType = extractDictionaryValue<::CFStringRef>(elem, ::kSecPropertyKeyType); if (!swType.isOK()) { return swType.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 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)); } else { // Single Value RDN. auto swEntry = extractSingleOIDEntry(elem); if (!swEntry.isOK()) { return swEntry.getStatus(); } ret.push_back(std::vector({std::move(swEntry.getValue())})); } } return SSLX509Name(std::move(ret)); } StatusWith extractValidityDate(::CFDictionaryRef dict, ::CFStringRef oid, StringData name) { auto swVal = extractDictionaryValue<::CFDictionaryRef>(dict, oid); if (!swVal.isOK()) { return swVal.getStatus(); } auto swNum = extractDictionaryValue<::CFNumberRef>(swVal.getValue(), ::kSecPropertyKeyValue); if (!swNum.isOK()) { return swNum.getStatus(); } int64_t dateval = 0; if (!::CFNumberGetValue(swNum.getValue(), ::kCFNumberSInt64Type, &dateval)) { return {ErrorCodes::InvalidSSLConfiguration, str::stream() << "Certificate contains invalid " << name << ": OID contains invalid numeric value"}; } return Date_t::fromMillisSinceEpoch((k20010101_000000_GMT + dateval) * 1000); } StatusWith> parsePeerRoles(::CFDictionaryRef dict) { if (!::CFDictionaryContainsKey(dict, kMongoDBRolesOID)) { return stdx::unordered_set(); } auto swRolesKey = extractDictionaryValue<::CFDictionaryRef>(dict, kMongoDBRolesOID); if (!swRolesKey.isOK()) { return swRolesKey.getStatus(); } auto swRolesList = extractDictionaryValue<::CFArrayRef>(swRolesKey.getValue(), ::kSecPropertyKeyValue); if (!swRolesList.isOK()) { return swRolesList.getStatus(); } auto rolesList = swRolesList.getValue(); const auto count = ::CFArrayGetCount(rolesList); for (::CFIndex i = 0; i < count; ++i) { auto elemval = ::CFArrayGetValueAtIndex(rolesList, i); invariant(elemval); if (::CFGetTypeID(elemval) != ::CFDictionaryGetTypeID()) { return {ErrorCodes::InvalidSSLConfiguration, "Invalid list element in Certificate Roles OID"}; } auto elem = reinterpret_cast<::CFDictionaryRef>(elemval); auto swType = extractDictionaryValue<::CFStringRef>(elem, ::kSecPropertyKeyType); if (!swType.isOK()) { return swType.getStatus(); } if (::CFStringCompare(swType.getValue(), ::kSecPropertyTypeData, 0)) { // Non data, ignore. continue; } auto swData = extractDictionaryValue<::CFDataRef>(elem, ::kSecPropertyKeyValue); if (!swData.isOK()) { return swData.getStatus(); } ConstDataRange rolesData( reinterpret_cast(::CFDataGetBytePtr(swData.getValue())), ::CFDataGetLength(swData.getValue())); return parsePeerRoles(rolesData); } return {ErrorCodes::InvalidSSLConfiguration, "Unable to extract role data from certificate"}; } StatusWith> extractSubjectAlternateNames(::CFDictionaryRef dict) { const auto badValue = [](StringData msg) -> Status { return {ErrorCodes::InvalidSSLConfiguration, str::stream() << "Certificate contains invalid SAN: " << msg}; }; auto swSANDict = extractDictionaryValue<::CFDictionaryRef>(dict, ::kSecOIDSubjectAltName); if (!swSANDict.isOK()) { return swSANDict.getStatus(); } auto sanDict = swSANDict.getValue(); auto swList = extractDictionaryValue<::CFArrayRef>(sanDict, ::kSecPropertyKeyValue); if (!swList.isOK()) { return swList.getStatus(); } std::vector ret; auto list = swList.getValue(); const auto count = ::CFArrayGetCount(list); for (::CFIndex i = 0; i < count; ++i) { auto elemval = ::CFArrayGetValueAtIndex(list, i); invariant(elemval); if (::CFGetTypeID(elemval) != ::CFDictionaryGetTypeID()) { return badValue("Invalid list element"); } auto elem = reinterpret_cast<::CFDictionaryRef>(elemval); auto swLabel = extractDictionaryValue<::CFStringRef>(elem, ::kSecPropertyKeyLabel); if (!swLabel.isOK()) { return swLabel.getStatus(); } if ((::CFStringCompare(swLabel.getValue(), CFSTR("DNS Name"), ::kCFCompareCaseInsensitive) != ::kCFCompareEqualTo) && (::CFStringCompare(swLabel.getValue(), CFSTR("IP Address"), ::kCFCompareCaseInsensitive) != ::kCFCompareEqualTo)) { // Skip other elements, e.g. 'Critical' continue; } bool dnsFlag = false; if (::CFStringCompare(swLabel.getValue(), CFSTR("DNS Name"), ::kCFCompareCaseInsensitive) == ::kCFCompareEqualTo) { dnsFlag = true; } auto swName = extractDictionaryValue<::CFStringRef>(elem, ::kSecPropertyKeyValue); if (!swName.isOK()) { return swName.getStatus(); } auto swNameStr = toString(swName.getValue()); if (!swNameStr.isOK()) { return swNameStr.getStatus(); } auto swCIDRValue = CIDR::parse(swNameStr.getValue()); if (swCIDRValue.isOK()) { swNameStr = swCIDRValue.getValue().toString(); if (dnsFlag) { warning() << "You have an IP Address in the DNS Name field on your certificate. We will not allow this in MongoDB version 4.2."; } } ret.push_back(swNameStr.getValue()); } return ret; } enum LoadPEMMode { kLoadPEMBindIdentities = true, kLoadPEMStripKeys = false, }; /** * Load a PEM encoded file from disk. * This file may contain multiple PEM blocks (e.g. a private key and one or more certificates). * Because SecItemImport loads all items as-is, we must manually attempt to pair up * corresponding Certificates and Keys. * This is done using a temporary Keychain and looping through the results. * * Depending on the value passed for any SecKey instances present will be * either discarded, or combined with matching SecCertificates to make SecIdentities. * Unbound certificates will remain in the CFArray as-is. */ StatusWith> loadPEM(const std::string& keyfilepath, const std::string& passphrase = "", const LoadPEMMode mode = kLoadPEMBindIdentities) { const auto retFail = [&keyfilepath, &passphrase](const std::string& msg = "") { return Status(ErrorCodes::InvalidSSLConfiguration, str::stream() << "Unable to load PEM from '" << keyfilepath << "'" << (passphrase.empty() ? "" : " with passphrase: ") << msg); }; std::ifstream pemFile(keyfilepath, std::ios::binary); if (!pemFile.is_open()) { return retFail("Failed opening file"); } std::vector pemdata((std::istreambuf_iterator(pemFile)), std::istreambuf_iterator()); CFUniquePtr cfdata(::CFDataCreate(nullptr, pemdata.data(), pemdata.size())); invariant(cfdata); pemdata.clear(); CFUniquePtr cfpass; if (!passphrase.empty()) { cfpass.reset(::CFDataCreate( nullptr, reinterpret_cast(passphrase.c_str()), passphrase.size())); } ::SecItemImportExportKeyParameters params = { SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION, 0, cfpass.get(), }; CFUniquePtr cfkeyfile( ::CFStringCreateWithCString(nullptr, keyfilepath.c_str(), ::kCFStringEncodingUTF8)); auto format = ::kSecFormatUnknown; auto type = ::kSecItemTypeUnknown; ::CFArrayRef certs = nullptr; auto status = ::SecItemImport(cfdata.get(), cfkeyfile.get(), &format, &type, ::kSecItemPemArmour, ¶ms, nullptr, &certs); CFUniquePtr<::CFArrayRef> cfcerts(certs); if ((status == ::errSecUnknownFormat) && !passphrase.empty()) { // The Security framework in OSX doesn't support PKCS#8 encrypted keys // using modern encryption algorithms. Give the user a hint about that. return Status(ErrorCodes::InvalidSSLConfiguration, "Unable to import PEM key file, possibly due to presence of PKCS#8 encrypted " "key. Consider using a certificate selector or PKCS#12 instead"); } if (status != ::errSecSuccess) { return retFail(str::stream() << "Failing importing certificate(s): " << stringFromOSStatus(status)); } auto count = ::CFArrayGetCount(cfcerts.get()); if ((count > 0) && (mode == kLoadPEMBindIdentities)) { // Turn Certificate/Key pairs into identities. CFUniquePtr<::CFMutableArrayRef> bind( ::CFArrayCreateMutable(nullptr, count, &kCFTypeArrayCallBacks)); for (::CFIndex i = 0; i < count; ++i) { auto elem = ::CFArrayGetValueAtIndex(cfcerts.get(), i); invariant(elem); const auto type = ::CFGetTypeID(elem); if (type == ::SecIdentityGetTypeID()) { // Our import had a proper identity in it, ready to go. ::CFArrayAppendValue(bind.get(), elem); continue; } if (type != ::SecCertificateGetTypeID()) { continue; } // Attempt to match the certificate to a private key in the aggregate we just imported. CFUniquePtr<::SecIdentityRef> cfid; for (::CFIndex j = 0; j < count; ++j) { auto key = ::CFArrayGetValueAtIndex(cfcerts.get(), j); invariant(key); if (::CFGetTypeID(key) != ::SecKeyGetTypeID()) { continue; } auto id = ::SecIdentityCreate(nullptr, static_cast<::SecCertificateRef>(const_cast(elem)), static_cast<::SecKeyRef>(const_cast(key))); if (id) { cfid.reset(id); break; } } if (cfid) { ::CFArrayAppendValue(bind.get(), cfid.get()); } else { ::CFArrayAppendValue(bind.get(), elem); } } // Reencapsulate to allow the inner type to change. cfcerts.reset(bind.release()); count = ::CFArrayGetCount(cfcerts.get()); } if ((count > 0) && (mode == kLoadPEMStripKeys)) { // Strip unpaired keys and identities. CFUniquePtr<::CFMutableArrayRef> strip( ::CFArrayCreateMutable(nullptr, count, &kCFTypeArrayCallBacks)); for (::CFIndex i = 0; i < count; ++i) { auto elem = ::CFArrayGetValueAtIndex(cfcerts.get(), i); if (!elem) { continue; } const auto type = ::CFGetTypeID(elem); if (type == ::SecCertificateGetTypeID()) { // Preserve Certificates. ::CFArrayAppendValue(strip.get(), elem); continue; } if (type != ::SecIdentityGetTypeID()) { continue; } // Extract public certificate from Identity. ::SecCertificateRef cert = nullptr; const auto status = ::SecIdentityCopyCertificate( static_cast<::SecIdentityRef>(const_cast(elem)), &cert); CFUniquePtr<::SecCertificateRef> cfcert(cert); if (status != ::errSecSuccess) { return {ErrorCodes::InternalError, str::stream() << "Unable to extract certificate from identity: " << stringFromOSStatus(status)}; } ::CFArrayAppendValue(strip.get(), cfcert.get()); } // Reencapsulate to allow the inner type to change. cfcerts.reset(strip.release()); count = ::CFArrayGetCount(cfcerts.get()); } if (count <= 0) { return {ErrorCodes::InvalidSSLConfiguration, str::stream() << "PEM file '" << keyfilepath << "' has no certificates"}; } // Rewrap the return to be the non-mutable type. return std::move(cfcerts); } StatusWith certificateGetSubject(::SecCertificateRef cert, Date_t* expire = nullptr) { // Fetch expiry range and full subject name. CFUniquePtr<::CFMutableArrayRef> oids( ::CFArrayCreateMutable(nullptr, expire ? 3 : 1, &::kCFTypeArrayCallBacks)); ::CFArrayAppendValue(oids.get(), ::kSecOIDX509V1SubjectName); if (expire) { ::CFArrayAppendValue(oids.get(), ::kSecOIDX509V1ValidityNotBefore); ::CFArrayAppendValue(oids.get(), ::kSecOIDX509V1ValidityNotAfter); } ::CFErrorRef cferror = nullptr; CFUniquePtr<::CFDictionaryRef> cfdict(::SecCertificateCopyValues(cert, oids.get(), &cferror)); if (cferror) { CFUniquePtr<::CFErrorRef> deleter(cferror); return {ErrorCodes::InvalidSSLConfiguration, str::stream() << "Unable to determine certificate validity: " << cferror}; } auto swSubjectName = extractSubjectName(cfdict.get()); if (!swSubjectName.isOK()) { return swSubjectName.getStatus(); } auto subject = swSubjectName.getValue(); if (!expire) { return subject; } // Marshal expiration. auto swValidFrom = extractValidityDate(cfdict.get(), ::kSecOIDX509V1ValidityNotBefore, "valid-from"); auto swValidUntil = extractValidityDate(cfdict.get(), ::kSecOIDX509V1ValidityNotAfter, "valid-until"); if (!swValidFrom.isOK() || !swValidUntil.isOK()) { return swValidUntil.getStatus(); } *expire = swValidUntil.getValue(); const auto now = Date_t::now(); if ((now < swValidFrom.getValue()) || (*expire < now)) { return {ErrorCodes::InvalidSSLConfiguration, "The provided SSL certificate is expired or not yet valid"}; } return subject; } StatusWith certificateGetSubject(::CFArrayRef certs, Date_t* expire = nullptr) { if (::CFArrayGetCount(certs) <= 0) { return {ErrorCodes::InvalidSSLConfiguration, "No certificates in certificate list"}; } auto root = ::CFArrayGetValueAtIndex(certs, 0); if (!root || (::CFGetTypeID(root) != ::SecIdentityGetTypeID())) { return {ErrorCodes::InvalidSSLConfiguration, "Root certificate not an identity pair"}; } ::SecCertificateRef idcert = nullptr; auto status = ::SecIdentityCopyCertificate( static_cast<::SecIdentityRef>(const_cast(root)), &idcert); if (status != ::errSecSuccess) { return {ErrorCodes::InvalidSSLConfiguration, str::stream() << "Unable to get certificate from identity: " << stringFromOSStatus(status)}; } CFUniquePtr<::SecCertificateRef> cert(idcert); return certificateGetSubject(cert.get(), expire); } StatusWith> copyMatchingCertificate( const SSLParams::CertificateSelector& selector, SSLManagerInterface::ConnectionDirection direction) { if (selector.subject.empty() && selector.thumbprint.empty()) { // In practice, this should never occur, thanks to the selector.empty() // checks at the callsites of this function. return {ErrorCodes::InvalidSSLConfiguration, "Certificate selector has no values"}; } if (!selector.subject.empty() && !selector.thumbprint.empty()) { // This can only happen if the parsing logic in ssl_options.cpp changes. // Guard against it to play it safe. return {ErrorCodes::InvalidSSLConfiguration, "Certificate selector has multiple values"}; } const bool isServer = (direction == SSLManagerInterface::ConnectionDirection::kIncoming); CFUniquePtr<::SecPolicyRef> cfpolicy(::SecPolicyCreateSSL(isServer, nullptr)); CFUniquePtr<::CFMutableDictionaryRef> cfquery(::CFDictionaryCreateMutable( nullptr, 5, &::kCFTypeDictionaryKeyCallBacks, &::kCFTypeDictionaryValueCallBacks)); ::CFDictionaryAddValue(cfquery.get(), ::kSecClass, ::kSecClassIdentity); ::CFDictionaryAddValue(cfquery.get(), ::kSecReturnRef, ::kCFBooleanTrue); ::CFDictionaryAddValue(cfquery.get(), ::kSecMatchLimit, ::kSecMatchLimitAll); ::CFDictionaryAddValue(cfquery.get(), ::kSecMatchPolicy, cfpolicy.get()); // Note: These search terms don't ACTUALLY work. // We should be able to specify kSecMatchLimitOne, but instead we have to get // extra (sometimes duplicate) results, and manually filter them below. if (!selector.subject.empty()) { invariant(selector.thumbprint.empty()); CFUniquePtr<::CFStringRef> cfsubject(::CFStringCreateWithCString( nullptr, selector.subject.c_str(), ::kCFStringEncodingUTF8)); if (!cfsubject) { return {ErrorCodes::InvalidSSLConfiguration, str::stream() << "Certificate subject name specified is not UTF-8" << selector.subject}; } ::CFDictionaryAddValue(cfquery.get(), ::kSecAttrLabel, cfsubject.get()); } else { invariant(!selector.thumbprint.empty()); CFUniquePtr<::CFDataRef> cfdigest( ::CFDataCreate(nullptr, static_cast(selector.thumbprint.data()), selector.thumbprint.size())); if (!cfdigest) { return {ErrorCodes::InvalidSSLConfiguration, "Unable to create Public Key Hash from certificate thumbprint selector value"}; } // Don't be fooled by the name. // "Public Key Hash" is actually referring to the digest of the entire Certificate. ::CFDictionaryAddValue(cfquery.get(), ::kSecAttrPublicKeyHash, cfdigest.get()); } ::CFTypeRef identities = nullptr; auto status = ::SecItemCopyMatching(cfquery.get(), &identities); CFUniquePtr<::CFArrayRef> cfident(static_cast<::CFArrayRef>(identities)); if (status != ::errSecSuccess) { return {ErrorCodes::InvalidSSLConfiguration, str::stream() << "Failure querying system keychain for certificate selector: " << stringFromOSStatus(status)}; } if (::CFGetTypeID(cfident.get()) != ::CFArrayGetTypeID()) { return {ErrorCodes::InvalidSSLConfiguration, "System keychain returned invalid result"}; } // We should be able to return the results at this point, // but the search criteria above will return non-matching results in OSX 10.12 and later. CFUniquePtr<::CFMutableArrayRef> cfresult( ::CFArrayCreateMutable(nullptr, 1, &::kCFTypeArrayCallBacks)); for (::CFIndex i = 0; i < ::CFArrayGetCount(cfident.get()); ++i) { auto ident = static_cast<::SecIdentityRef>( const_cast(::CFArrayGetValueAtIndex(cfident.get(), i))); if (::CFGetTypeID(ident) != ::SecIdentityGetTypeID()) { continue; } ::SecCertificateRef cert = nullptr; status = ::SecIdentityCopyCertificate(ident, &cert); CFUniquePtr<::SecCertificateRef> cfcert(cert); if (status != ::errSecSuccess) { return {ErrorCodes::InvalidSSLConfiguration, "Unable to retreive certificate from identity"}; } if (!selector.subject.empty()) { // Try matching subject name to short (common name) portion of subject. CFUniquePtr<::CFStringRef> certSubject( ::SecCertificateCopySubjectSummary(cfcert.get())); if (!certSubject) { return {ErrorCodes::InvalidSSLConfiguration, "Unable to retreive subject summary from identity"}; } auto swSubjectSummary = toString(certSubject.get()); if (!swSubjectSummary.isOK()) { return swSubjectSummary.getStatus(); } if (swSubjectSummary.getValue() == selector.subject) { ::CFArrayAppendValue(cfresult.get(), ident); break; } // Try matching full subject name instead. auto swCertSubject = certificateGetSubject(cfcert.get()); if (!swCertSubject.isOK()) { return swCertSubject.getStatus(); } if (swCertSubject.getValue().toString() == selector.subject) { ::CFArrayAppendValue(cfresult.get(), ident); break; } } if (!selector.thumbprint.empty()) { CFUniquePtr<::CFDataRef> cfCertData(::SecCertificateCopyData(cfcert.get())); ConstDataRange certData( reinterpret_cast(::CFDataGetBytePtr(cfCertData.get())), ::CFDataGetLength(cfCertData.get())); // Attempt to match SHA1 digest. if (SHA1Block::kHashLength == selector.thumbprint.size()) { const auto certSha1 = SHA1Block::computeHash({certData}); if (!memcmp(certSha1.data(), selector.thumbprint.data(), certSha1.size())) { ::CFArrayAppendValue(cfresult.get(), ident); break; } } // Attempt to match SHA256 digest. if (SHA256Block::kHashLength == selector.thumbprint.size()) { const auto certSha256 = SHA256Block::computeHash({certData}); if (!memcmp(certSha256.data(), selector.thumbprint.data(), certSha256.size())) { ::CFArrayAppendValue(cfresult.get(), ident); break; } } } } if (::CFArrayGetCount(cfresult.get()) == 0) { return {ErrorCodes::InvalidSSLConfiguration, "Certificate selector returned no results"}; } return CFUniquePtr<::CFArrayRef>(cfresult.release()); } std::string explainTrustFailure(::SecTrustRef trust, ::SecTrustResultType result) { const auto ret = [result](auto reason) -> std::string { auto ss = str::stream(); if (result == ::kSecTrustResultDeny) { ss << "Certificate trust denied"; } else if (result == ::kSecTrustResultRecoverableTrustFailure) { ss << "Certificate trust failure"; } else if (result == ::kSecTrustResultFatalTrustFailure) { ss << "Certificate trust fatal failure"; } else { static_assert(std::is_signed::value == std::is_signed<::SecTrustResultType>::value, "Must cast status to same signedness"); static_assert(sizeof(uint) >= sizeof(::SecTrustResultType), "Must cast result to same or wider type"); ss << "Certificate trust failure #" << static_cast(result); } ss << ": " << reason; return ss; }; CFUniquePtr<::CFArrayRef> cfprops(::SecTrustCopyProperties(trust)); if (!cfprops) { return ret("Unable to retreive cause for trust failure"); } const auto count = ::CFArrayGetCount(cfprops.get()); for (::CFIndex i = 0; i < count; ++i) { auto elem = ::CFArrayGetValueAtIndex(cfprops.get(), i); if (::CFGetTypeID(elem) != ::CFDictionaryGetTypeID()) { return ret("Unable to parse cause for trust failure"); } auto dict = static_cast<::CFDictionaryRef>(elem); auto reason = ::CFDictionaryGetValue(dict, ::kSecPropertyTypeError); if (!reason) { continue; } if (::CFGetTypeID(reason) != ::CFStringGetTypeID()) { return ret("Unable to parse trust failure error"); } auto swReason = toString(static_cast<::CFStringRef>(reason)); if (!swReason.isOK()) { return ret("Unable to express trust failure error"); } return ret(swReason.getValue()); } return ret("No trust failure reason available"); } } // namespace ///////////////////////////////////////////////////////////////////////////// // SSLConnection namespace { class SSLConnectionApple : public SSLConnectionInterface { public: SSLConnectionApple(asio::ssl::apple::Context* ctx, Socket* socket, ::SSLProtocolSide side, std::string hostname = "", std::vector init = {}) : _sock(socket), _init(std::move(init)) { _ssl.reset(::SSLCreateContext(nullptr, side, ::kSSLStreamType)); uassert(ErrorCodes::InternalError, "Failed creating SSL context", _ssl); auto certs = ctx->certs.get(); if (certs) { uassertOSStatusOK(::SSLSetCertificate(_ssl.get(), certs)); } uassertOSStatusOK(::SSLSetConnection(_ssl.get(), static_cast(this))); uassertOSStatusOK(::SSLSetPeerID(_ssl.get(), _ssl.get(), sizeof(_ssl))); uassertOSStatusOK(::SSLSetIOFuncs(_ssl.get(), read_func, write_func)); uassertOSStatusOK(::SSLSetProtocolVersionMin(_ssl.get(), ctx->protoMin)); uassertOSStatusOK(::SSLSetProtocolVersionMax(_ssl.get(), ctx->protoMax)); if (!hostname.empty()) { uassertOSStatusOK( ::SSLSetPeerDomainName(_ssl.get(), hostname.c_str(), hostname.size())); } // SSLHandshake will return errSSLServerAuthCompleted and let us do our own verify. // We'll pretend to have done that, and let our caller invoke verifyPeer later. uassertOSStatusOK(::SSLSetClientSideAuthenticate(_ssl.get(), ::kTryAuthenticate)); uassertOSStatusOK( ::SSLSetSessionOption(_ssl.get(), ::kSSLSessionOptionBreakOnServerAuth, true)); uassertOSStatusOK( ::SSLSetSessionOption(_ssl.get(), ::kSSLSessionOptionBreakOnClientAuth, true)); ::OSStatus status; do { status = ::SSLHandshake(_ssl.get()); } while ((status == ::errSSLServerAuthCompleted) || (status == ::errSSLClientAuthCompleted)); uassertOSStatusOK(status, ErrorCodes::SSLHandshakeFailed); } std::string getSNIServerName() const final { size_t len = 0; auto status = ::SSLCopyRequestedPeerNameLength(_ssl.get(), &len); if (status != ::errSecSuccess) { return ""; } std::string ret; ret.resize(len + 1); status = ::SSLCopyRequestedPeerName(_ssl.get(), &ret[0], &len); if (status != ::errSecSuccess) { return ""; } ret.resize(len); return ret; } ::SSLContextRef get() const { return const_cast<::SSLContextRef>(_ssl.get()); } private: static ::OSStatus write_func(::SSLConnectionRef ctx, const void* data, size_t* data_len) { const auto* conn = reinterpret_cast(ctx); size_t len = *data_len; *data_len = 0; while (len > 0) { auto wrote = ::write(conn->_sock->rawFD(), static_cast(data) + *data_len, len); if (wrote > 0) { *data_len += wrote; len -= wrote; continue; } return posixErrno(errno); } return ::errSecSuccess; } static ::OSStatus read_func(::SSLConnectionRef ctx, void* data, size_t* data_len) { auto* conn = const_cast(reinterpret_cast(ctx)); auto* dest = static_cast(data); size_t len = *data_len; *data_len = 0; while (len > 0) { if (conn->_init.size()) { // Consume any initial bytes first. auto& init = conn->_init; const auto mvlen = std::max(len, init.size()); std::copy(init.begin(), init.begin() + mvlen, dest + *data_len); init.erase(init.begin(), init.begin() + mvlen); *data_len += mvlen; len -= mvlen; continue; } // Then go to the network. auto didread = ::read(conn->_sock->rawFD(), dest + *data_len, len); if (didread > 0) { *data_len += didread; len -= didread; continue; } return posixErrno(errno); } return ::errSecSuccess; } Socket* _sock; // When in server mode, _init contains any bytes read prior to // starting the SSL handshake process. // Once exhausted, this is never refilled. std::vector _init; CFUniquePtr<::SSLContextRef> _ssl; }; } // namespace ///////////////////////////////////////////////////////////////////////////// // SSLManager namespace { class SSLManagerApple : public SSLManagerInterface { public: explicit SSLManagerApple(const SSLParams& params, bool isServer); Status initSSLContext(asio::ssl::apple::Context* context, const SSLParams& params, ConnectionDirection direction) final; SSLConnectionInterface* connect(Socket* socket) final; SSLConnectionInterface* accept(Socket* socket, const char* initialBytes, int len) final; SSLPeerInfo parseAndValidatePeerCertificateDeprecated(const SSLConnectionInterface* conn, const std::string& remoteHost) final; StatusWith> parseAndValidatePeerCertificate( ::SSLContextRef conn, const std::string& remoteHost) final; const SSLConfiguration& getSSLConfiguration() const final { return _sslConfiguration; } int SSL_read(SSLConnectionInterface* conn, void* buf, int num) final; int SSL_write(SSLConnectionInterface* conn, const void* buf, int num) final; int SSL_shutdown(SSLConnectionInterface* conn) final; private: bool _weakValidation; bool _allowInvalidCertificates; bool _allowInvalidHostnames; bool _suppressNoCertificateWarning; asio::ssl::apple::Context _clientCtx; asio::ssl::apple::Context _serverCtx; /* _clientCA represents the CA to use when acting as a client * and validating remotes during outbound connections. * This comes from, in order, --tlsCAFile, or the system CA. */ CFUniquePtr<::CFArrayRef> _clientCA; /* _serverCA represents the CA to use when acting as a server * and validating remotes during inbound connections. * This comes from --tlsClusterCAFile, if available, * otherwise it inherits from _clientCA. */ CFUniquePtr<::CFArrayRef> _serverCA; SSLConfiguration _sslConfiguration; }; SSLManagerApple::SSLManagerApple(const SSLParams& params, bool isServer) : _weakValidation(params.sslWeakCertificateValidation), _allowInvalidCertificates(params.sslAllowInvalidCertificates), _allowInvalidHostnames(params.sslAllowInvalidHostnames), _suppressNoCertificateWarning(params.suppressNoTLSPeerCertificateWarning) { uassertStatusOK(initSSLContext(&_clientCtx, params, ConnectionDirection::kOutgoing)); if (_clientCtx.certs) { _sslConfiguration.clientSubjectName = uassertStatusOK(certificateGetSubject(_clientCtx.certs.get())); } if (isServer) { uassertStatusOK(initSSLContext(&_serverCtx, params, ConnectionDirection::kIncoming)); if (_serverCtx.certs) { _sslConfiguration.serverSubjectName = uassertStatusOK(certificateGetSubject( _serverCtx.certs.get(), &_sslConfiguration.serverCertificateExpirationDate)); static auto task = CertificateExpirationMonitor(_sslConfiguration.serverCertificateExpirationDate); } } if (!params.sslCAFile.empty()) { auto ca = uassertStatusOK(loadPEM(params.sslCAFile, "", kLoadPEMStripKeys)); _clientCA = std::move(ca); _sslConfiguration.hasCA = _clientCA && ::CFArrayGetCount(_clientCA.get()); } if (!params.sslCertificateSelector.empty() || !params.sslClusterCertificateSelector.empty()) { // By using the system keychain, we acknowledge it exists. _sslConfiguration.hasCA = true; } if (!_clientCA) { // No explicit CA was specified, use the Keychain CA explicitly on client connects, // even though we're going to pretend it doesn't exist on server. ::CFArrayRef certs = nullptr; uassertOSStatusOK(SecTrustCopyAnchorCertificates(&certs)); _clientCA.reset(certs); } if (!params.sslClusterCAFile.empty()) { auto ca = uassertStatusOK(loadPEM(params.sslClusterCAFile, "", kLoadPEMStripKeys)); _serverCA = std::move(ca); } else { // No inbound CA specified, share a reference with outbound CA. auto ca = _clientCA.get(); ::CFRetain(ca); _serverCA.reset(ca); } } StatusWith> parseProtocolRange(const SSLParams& params) { // Map disabled protocols to range. bool tls10 = true, tls11 = true, tls12 = true; for (const SSLParams::Protocols& protocol : params.sslDisabledProtocols) { if (protocol == SSLParams::Protocols::TLS1_0) { tls10 = false; } else if (protocol == SSLParams::Protocols::TLS1_1) { tls11 = false; } else if (protocol == SSLParams::Protocols::TLS1_2) { tls12 = false; } else { return {ErrorCodes::InvalidSSLConfiguration, "Unknown disabled TLS protocol version"}; } } // Throw out the invalid cases. if (tls10 && !tls11 && tls12) { return {ErrorCodes::InvalidSSLConfiguration, "Can not disable TLS 1.1 while leaving 1.0 and 1.2 enabled"}; } if (!tls10 && !tls11 && !tls12) { return {ErrorCodes::InvalidSSLConfiguration, "All valid TLS modes disabled"}; } auto protoMin = tls10 ? ::kTLSProtocol1 : tls11 ? ::kTLSProtocol11 : ::kTLSProtocol12; auto protoMax = tls12 ? ::kTLSProtocol12 : tls11 ? ::kTLSProtocol11 : ::kTLSProtocol1; return std::pair<::SSLProtocol, ::SSLProtocol>(protoMin, protoMax); } Status SSLManagerApple::initSSLContext(asio::ssl::apple::Context* context, const SSLParams& params, ConnectionDirection direction) { // Options. context->allowInvalidHostnames = _allowInvalidHostnames; // Protocol Version. const auto swProto = parseProtocolRange(params); if (!swProto.isOK()) { return swProto.getStatus(); } const auto proto = swProto.getValue(); context->protoMin = proto.first; context->protoMax = proto.second; // Certificate. const auto selectCertificate = [&context, direction](const SSLParams::CertificateSelector& selector, const std::string& PEMFile, const std::string& PEMPass) -> Status { if (!selector.empty()) { auto swCerts = copyMatchingCertificate(selector, direction); if (!swCerts.isOK()) { return swCerts.getStatus(); } context->certs = std::move(swCerts.getValue()); return Status::OK(); } if (!PEMFile.empty()) { auto swCerts = loadPEM(PEMFile, PEMPass); if (!swCerts.isOK()) { return swCerts.getStatus(); } context->certs = std::move(swCerts.getValue()); return Status::OK(); } return Status::OK(); }; if (direction == ConnectionDirection::kOutgoing) { const auto status = selectCertificate( params.sslClusterCertificateSelector, params.sslClusterFile, params.sslClusterPassword); if (context->certs || !status.isOK()) { return status; } // Fallthrough... } return selectCertificate( params.sslCertificateSelector, params.sslPEMKeyFile, params.sslPEMKeyPassword); } SSLConnectionInterface* SSLManagerApple::connect(Socket* socket) { return new SSLConnectionApple( &_clientCtx, socket, ::kSSLClientSide, socket->remoteAddr().hostOrIp()); } SSLConnectionInterface* SSLManagerApple::accept(Socket* socket, const char* initialBytes, int len) { std::vector init; const auto* p = reinterpret_cast(initialBytes); init.insert(init.end(), p, p + len); return new SSLConnectionApple(&_serverCtx, socket, ::kSSLServerSide, "", init); } SSLPeerInfo SSLManagerApple::parseAndValidatePeerCertificateDeprecated( const SSLConnectionInterface* conn, const std::string& remoteHost) { auto ssl = checked_cast(conn)->get(); auto swPeerSubjectName = parseAndValidatePeerCertificate(ssl, remoteHost); // We can't use uassertStatusOK here because we need to throw a NetworkException. if (!swPeerSubjectName.isOK()) { throwSocketError(SocketErrorKind::CONNECT_ERROR, swPeerSubjectName.getStatus().reason()); } return swPeerSubjectName.getValue().get_value_or(SSLPeerInfo()); } void recordTLSVersion(::SSLContextRef ssl) { ::SSLProtocol protocol; uassertOSStatusOK(::SSLGetNegotiatedProtocolVersion(ssl, &protocol)); auto& counts = mongo::TLSVersionCounts::get(getGlobalServiceContext()); switch (protocol) { case kTLSProtocol1: counts.tls10.addAndFetch(1); break; case kTLSProtocol11: counts.tls11.addAndFetch(1); break; case kTLSProtocol12: counts.tls12.addAndFetch(1); break; // case kTLSProtocol13: // counts.tls13.addAndFetch(1); // break; case kSSLProtocolUnknown: case kSSLProtocol2: case kSSLProtocol3: case kSSLProtocol3Only: case kTLSProtocol1Only: case kSSLProtocolAll: case kDTLSProtocol1: default: // Some system headers may define additional protocols, so suppress warnings. // Do nothing break; } } StatusWith> SSLManagerApple::parseAndValidatePeerCertificate( ::SSLContextRef ssl, const std::string& remoteHost) { // Record TLS version stats recordTLSVersion(ssl); /* While we always have a system CA via the Keychain, * we'll pretend not to in terms of validation if the server * was started using a PEM file (legacy mode). * * When a certificate selector is used, we'll override hasCA to true * so that the validation path runs anyway. */ if (!_sslConfiguration.hasCA && isSSLServer) { return {boost::none}; } const auto badCert = [](StringData msg, bool warn = false) -> StatusWith> { constexpr StringData prefix = "SSL peer certificate validation failed: "_sd; if (warn) { warning() << prefix << msg; return {boost::none}; } else { std::string m = str::stream() << prefix << msg << "; connection rejected"; error() << m; return Status(ErrorCodes::SSLHandshakeFailed, m); } }; ::SecTrustRef trust = nullptr; const auto status = ::SSLCopyPeerTrust(ssl, &trust); CFUniquePtr<::SecTrustRef> cftrust(trust); if ((status != ::errSecSuccess) || (!cftrust)) { if (_weakValidation && _suppressNoCertificateWarning) { return {boost::none}; } else { if (status == ::errSecSuccess) { return badCert(str::stream() << "no SSL certificate provided by peer: " << stringFromOSStatus(status), _weakValidation); } else { return badCert(str::stream() << "Unable to retreive SSL trust from peer: " << stringFromOSStatus(status), _weakValidation); } } } // When remoteHost is empty, it means we're handling an Inbound connection. // In that case, we in a server role, so use the _serverCA, // otherwise we're in a client role, so use that. auto ca = remoteHost.empty() ? _serverCA.get() : _clientCA.get(); if (ca) { auto status = ::SecTrustSetAnchorCertificates(cftrust.get(), ca); if (status == ::errSecSuccess) { status = ::SecTrustSetAnchorCertificatesOnly(cftrust.get(), true); } if (status != ::errSecSuccess) { return badCert(str::stream() << "Unable to bind CA to trust chain: " << stringFromOSStatus(status), _weakValidation); } } bool ipv6 = false; auto remoteHostName = remoteHost; if (!remoteHost.empty()) { auto swCIDRRemoteHost = CIDR::parse(remoteHost); if (swCIDRRemoteHost.isOK()) { remoteHostName = swCIDRRemoteHost.getValue().toString(); if (remoteHostName.find(':') != std::string::npos) { ipv6 = true; } } } auto result = ::kSecTrustResultInvalid; uassertOSStatusOK(::SecTrustEvaluate(cftrust.get(), &result), ErrorCodes::SSLHandshakeFailed); if ((result != ::kSecTrustResultProceed) && (result != ::kSecTrustResultUnspecified) && (!ipv6)) { return badCert(explainTrustFailure(cftrust.get(), result), _allowInvalidCertificates); } auto cert = ::SecTrustGetCertificateAtIndex(cftrust.get(), 0); if (!cert) { return badCert("no SSL certificate found in trust container", _weakValidation); } CFUniquePtr<::CFMutableArrayRef> oids( ::CFArrayCreateMutable(nullptr, remoteHost.empty() ? 3 : 2, &::kCFTypeArrayCallBacks)); ::CFArrayAppendValue(oids.get(), ::kSecOIDX509V1SubjectName); ::CFArrayAppendValue(oids.get(), ::kSecOIDSubjectAltName); if (remoteHost.empty()) { ::CFArrayAppendValue(oids.get(), kMongoDBRolesOID); } ::CFErrorRef err = nullptr; CFUniquePtr<::CFDictionaryRef> cfdict(::SecCertificateCopyValues(cert, oids.get(), &err)); CFUniquePtr<::CFErrorRef> cferror(err); if (cferror) { return badCert(str::stream() << cferror.get(), _weakValidation); } // Extract SubjectName into a human readable string. auto swPeerSubjectName = extractSubjectName(cfdict.get()); if (!swPeerSubjectName.isOK()) { return swPeerSubjectName.getStatus(); } const auto peerSubjectName = std::move(swPeerSubjectName.getValue()); LOG(2) << "Accepted TLS connection from peer: " << peerSubjectName; if (remoteHost.empty()) { // If this is an SSL server context (on a mongod/mongos) // parse any client roles out of the client certificate. auto swPeerCertificateRoles = parsePeerRoles(cfdict.get()); if (!swPeerCertificateRoles.isOK()) { return swPeerCertificateRoles.getStatus(); } return boost::make_optional( SSLPeerInfo(peerSubjectName, std::move(swPeerCertificateRoles.getValue()))); } // If this is an SSL client context (on a MongoDB server or client) // perform hostname validation of the remote server bool sanMatch = false; bool cnMatch = false; StringBuilder certErr; certErr << "The server certificate does not match the host name. " << "Hostname: " << remoteHost << " does not match "; // Attempt to retreive "Subject Alternative Name" std::vector sans; auto swSANs = extractSubjectAlternateNames(cfdict.get()); if (swSANs.isOK()) { sans = std::move(swSANs.getValue()); } if (!sans.empty()) { certErr << "SAN(s): "; for (auto& san : sans) { if (hostNameMatchForX509Certificates(remoteHostName, san)) { sanMatch = true; break; } certErr << san << " "; } } else { auto swCN = peerSubjectName.getOID(kOID_CommonName); if (swCN.isOK()) { auto commonName = std::move(swCN.getValue()); if (hostNameMatchForX509Certificates(remoteHostName, commonName)) { cnMatch = true; } certErr << "CN: " << commonName; } else { certErr << "No Common Name (CN) or Subject Alternate Names (SAN) found"; } } if (!sanMatch && !cnMatch) { const auto msg = certErr.str(); if (_allowInvalidCertificates || _allowInvalidHostnames || isUnixDomainSocket(remoteHostName)) { warning() << msg; } else { error() << msg; return Status(ErrorCodes::SSLHandshakeFailed, msg); } } return boost::make_optional(SSLPeerInfo(peerSubjectName, stdx::unordered_set())); } int SSLManagerApple::SSL_read(SSLConnectionInterface* conn, void* buf, int num) { auto ssl = checked_cast(conn)->get(); size_t read = 0; uassertOSStatusOK(::SSLRead(ssl, static_cast(buf), num, &read), SocketErrorKind::RECV_ERROR); return read; } int SSLManagerApple::SSL_write(SSLConnectionInterface* conn, const void* buf, int num) { auto ssl = checked_cast(conn)->get(); size_t written = 0; uassertOSStatusOK(::SSLWrite(ssl, static_cast(buf), num, &written), SocketErrorKind::SEND_ERROR); return written; } int SSLManagerApple::SSL_shutdown(SSLConnectionInterface* conn) { auto ssl = checked_cast(conn)->get(); const auto status = ::SSLClose(ssl); if (status == ::errSSLWouldBlock) { return 0; } uassertOSStatusOK(status, ErrorCodes::SocketException); return 1; } } // namespace ///////////////////////////////////////////////////////////////////////////// // Global variable indicating if this is a server or a client instance bool isSSLServer = false; namespace { SimpleMutex sslManagerMtx; SSLManagerInterface* theSSLManager = nullptr; } // namespace std::unique_ptr SSLManagerInterface::create(const SSLParams& params, bool isServer) { return stdx::make_unique(params, isServer); } MONGO_INITIALIZER(SSLManager)(InitializerContext*) { kMongoDBRolesOID = ::CFStringCreateWithCString( nullptr, mongodbRolesOID.identifier.c_str(), ::kCFStringEncodingUTF8); stdx::lock_guard lck(sslManagerMtx); if (!isSSLServer || (sslGlobalParams.sslMode.load() != SSLParams::SSLMode_disabled)) { theSSLManager = new SSLManagerApple(sslGlobalParams, isSSLServer); } return Status::OK(); } } // namespace mongo mongo::SSLManagerInterface* mongo::getSSLManager() { stdx::lock_guard lck(sslManagerMtx); if (theSSLManager) { return theSSLManager; } return nullptr; }