summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/mongo/client/sasl_scram_client_conversation.h2
-rw-r--r--src/mongo/db/auth/sasl_scram_server_conversation.h2
-rw-r--r--src/mongo/db/auth/security_key.cpp2
-rw-r--r--src/mongo/db/commands/user_management_commands.cpp2
-rw-r--r--src/mongo/util/icu.cpp11
-rw-r--r--src/mongo/util/icu.h8
-rw-r--r--src/mongo/util/icu_test.cpp4
-rw-r--r--src/mongo/util/net/SConscript1
-rw-r--r--src/mongo/util/net/ssl_manager.cpp535
-rw-r--r--src/mongo/util/net/ssl_manager.h31
-rw-r--r--src/mongo/util/net/ssl_manager_apple.cpp7
-rw-r--r--src/mongo/util/net/ssl_manager_openssl.cpp8
-rw-r--r--src/mongo/util/net/ssl_manager_test.cpp117
-rw-r--r--src/mongo/util/net/ssl_manager_windows.cpp6
-rw-r--r--src/mongo/util/net/ssl_types.h13
-rw-r--r--src/mongo/util/text.cpp9
-rw-r--r--src/mongo/util/text.h4
-rw-r--r--src/third_party/icu4c-57.1/source/mongo_sources/icudt57b.datbin3358832 -> 3372768 bytes
-rw-r--r--src/third_party/icu4c-57.1/source/mongo_sources/icudt57l.datbin3358832 -> 3372768 bytes
-rwxr-xr-xsrc/third_party/scripts/icu_get_sources.sh14
20 files changed, 648 insertions, 128 deletions
diff --git a/src/mongo/client/sasl_scram_client_conversation.h b/src/mongo/client/sasl_scram_client_conversation.h
index ba1631115e3..ab35eac775f 100644
--- a/src/mongo/client/sasl_scram_client_conversation.h
+++ b/src/mongo/client/sasl_scram_client_conversation.h
@@ -140,7 +140,7 @@ public:
if (std::is_same<SHA1Block, HashBlock>::value) {
return val.toString();
} else {
- return mongo::saslPrep(val);
+ return icuSaslPrep(val);
}
}
diff --git a/src/mongo/db/auth/sasl_scram_server_conversation.h b/src/mongo/db/auth/sasl_scram_server_conversation.h
index 5c1e56f8e64..d5d77a870df 100644
--- a/src/mongo/db/auth/sasl_scram_server_conversation.h
+++ b/src/mongo/db/auth/sasl_scram_server_conversation.h
@@ -64,7 +64,7 @@ public:
if (std::is_same<SHA1Block, HashBlock>::value) {
return str.toString();
} else {
- return mongo::saslPrep(str);
+ return icuSaslPrep(str);
}
}
diff --git a/src/mongo/db/auth/security_key.cpp b/src/mongo/db/auth/security_key.cpp
index 00344494374..9aa4b3c7626 100644
--- a/src/mongo/db/auth/security_key.cpp
+++ b/src/mongo/db/auth/security_key.cpp
@@ -76,7 +76,7 @@ public:
return boost::none;
}
- auto swSaslPassword = saslPrep(password);
+ auto swSaslPassword = icuSaslPrep(password);
if (!swSaslPassword.isOK()) {
error() << "Could not prep security key file for SCRAM-SHA-256: "
<< swSaslPassword.getStatus();
diff --git a/src/mongo/db/commands/user_management_commands.cpp b/src/mongo/db/commands/user_management_commands.cpp
index 967af4dfdbe..df1d33ba54f 100644
--- a/src/mongo/db/commands/user_management_commands.cpp
+++ b/src/mongo/db/commands/user_management_commands.cpp
@@ -704,7 +704,7 @@ Status buildCredentials(BSONObjBuilder* builder, const auth::CreateOrUpdateUserA
if (!args.digestPassword) {
return {ErrorCodes::BadValue, "Use of SCRAM-SHA-256 requires undigested passwords"};
}
- const auto swPwd = saslPrep(args.password);
+ const auto swPwd = icuSaslPrep(args.password);
if (!swPwd.isOK()) {
return swPwd.getStatus();
}
diff --git a/src/mongo/util/icu.cpp b/src/mongo/util/icu.cpp
index 4b9d797a5f5..9825da56fde 100644
--- a/src/mongo/util/icu.cpp
+++ b/src/mongo/util/icu.cpp
@@ -172,11 +172,18 @@ private:
};
} // namespace
-} // namespace mongo
-mongo::StatusWith<std::string> mongo::saslPrep(StringData str, UStringPrepOptions options) try {
+StatusWith<std::string> icuSaslPrep(StringData str, UStringPrepOptions options) try {
const auto opts = (options == kUStringPrepDefault) ? USPREP_DEFAULT : USPREP_ALLOW_UNASSIGNED;
return USPrep(USPREP_RFC4013_SASLPREP).prepare(UString::fromUTF8(str), opts).toUTF8();
} catch (const DBException& e) {
return e.toStatus();
}
+
+StatusWith<std::string> icuX509DNPrep(StringData str) try {
+ return USPrep(USPREP_RFC4518_LDAP).prepare(UString::fromUTF8(str), USPREP_DEFAULT).toUTF8();
+} catch (const DBException& e) {
+ return e.toStatus();
+}
+
+} // namespace mongo
diff --git a/src/mongo/util/icu.h b/src/mongo/util/icu.h
index 52056e8b806..99baf411db7 100644
--- a/src/mongo/util/icu.h
+++ b/src/mongo/util/icu.h
@@ -52,6 +52,12 @@ enum UStringPrepOptions {
* Attempt to apply RFC4013 saslPrep to the target string.
* Normalizes unicode sequences for SCRAM authentication.
*/
-StatusWith<std::string> saslPrep(StringData str, UStringPrepOptions = kUStringPrepDefault);
+StatusWith<std::string> icuSaslPrep(StringData str, UStringPrepOptions = kUStringPrepDefault);
+
+/**
+ * Attempt to apply RFC4518 string prep to the target string, this normalizes an X509 DN
+ * so it can be compared against other X509 DNs
+ */
+StatusWith<std::string> icuX509DNPrep(StringData str);
} // namespace mongo
diff --git a/src/mongo/util/icu_test.cpp b/src/mongo/util/icu_test.cpp
index a14faeb42c0..38a5bd2a265 100644
--- a/src/mongo/util/icu_test.cpp
+++ b/src/mongo/util/icu_test.cpp
@@ -42,7 +42,7 @@ struct testCases {
bool success;
};
-TEST(ICUTest, saslPrep) {
+TEST(ICUTest, icuSaslPrep) {
const testCases tests[] = {
// U+0065 LATIN SMALL LETTER E + U+0301 COMBINING ACUTE ACCENT
// U+00E9 LATIN SMALL LETTER E WITH ACUTE
@@ -66,7 +66,7 @@ TEST(ICUTest, saslPrep) {
};
for (const auto test : tests) {
- auto ret = saslPrep(test.original);
+ auto ret = icuSaslPrep(test.original);
ASSERT_EQ(ret.isOK(), test.success);
if (test.success) {
ASSERT_OK(ret);
diff --git a/src/mongo/util/net/SConscript b/src/mongo/util/net/SConscript
index 0918e6a9d9f..caca867099f 100644
--- a/src/mongo/util/net/SConscript
+++ b/src/mongo/util/net/SConscript
@@ -130,6 +130,7 @@ env.Library(
'$BUILD_DIR/mongo/db/service_context',
'$BUILD_DIR/mongo/idl/server_parameter',
'$BUILD_DIR/mongo/util/background_job',
+ '$BUILD_DIR/mongo/util/icu',
'$BUILD_DIR/mongo/util/winutil',
],
)
diff --git a/src/mongo/util/net/ssl_manager.cpp b/src/mongo/util/net/ssl_manager.cpp
index c6e869e7f7a..bce07236b3e 100644
--- a/src/mongo/util/net/ssl_manager.cpp
+++ b/src/mongo/util/net/ssl_manager.cpp
@@ -47,6 +47,7 @@
#include "mongo/platform/overflow_arithmetic.h"
#include "mongo/transport/session.h"
#include "mongo/util/hex.h"
+#include "mongo/util/icu.h"
#include "mongo/util/log.h"
#include "mongo/util/mongoutils/str.h"
#include "mongo/util/net/ssl_options.h"
@@ -54,101 +55,411 @@
namespace mongo {
namespace {
+
+// Some of these duplicate the std::isalpha/std::isxdigit because we don't want them to be
+// affected by the current locale.
+inline bool isAlpha(char ch) {
+ return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z');
+}
+
+inline bool isDigit(char ch) {
+ return (ch >= '0' && ch <= '9');
+}
+
+inline bool isHex(char ch) {
+ return isDigit(ch) || (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f');
+}
+
+// This function returns true if the character is supposed to be escaped according to the rules
+// in RFC4514. The exception to the RFC the space character ' ' and the '#', because we've not
+// required users to escape spaces or sharps in DNs in the past.
+inline bool isEscaped(char ch) {
+ switch (ch) {
+ case '"':
+ case '+':
+ case ',':
+ case ';':
+ case '<':
+ case '>':
+ case '\\':
+ return true;
+ default:
+ return false;
+ }
+}
+
+// These characters may appear escaped in a string or not, but must not appear as the first
+// character.
+inline bool isMayBeEscaped(char ch) {
+ switch (ch) {
+ case ' ':
+ case '#':
+ case '=':
+ return true;
+ default:
+ return false;
+ }
+}
+
+/*
+ * This class parses out the components of a DN according to RFC4514.
+ *
+ * It takes in a StringData to the DN to be parsed, the buffer containing the StringData
+ * must remain in scope for the duration that it is being parsed.
+ */
+class RFC4514Parser {
+public:
+ explicit RFC4514Parser(StringData sd) : _str(sd), _it(_str.begin()) {}
+
+ std::string extractAttributeName();
+
+ enum ValueTerminator {
+ NewRDN, // The value ended in ','
+ MultiValue, // The value ended in '+'
+ Done // The value ended with the end of the string
+ };
+
+ // Returns a decoded string representing one value in an RDN, and the way the value was
+ // terminated.
+ std::pair<std::string, ValueTerminator> extractValue();
+
+ bool done() const {
+ return _it == _str.end();
+ }
+
+ void skipSpaces() {
+ while (!done() && _cur() == ' ') {
+ _advance();
+ }
+ }
+
+private:
+ char _cur() const {
+ uassert(51036, "Overflowed string while parsing DN string", !done());
+ return *_it;
+ }
+
+ char _advance() {
+ invariant(!done());
+ ++_it;
+ return done() ? '\0' : _cur();
+ }
+
+ StringData _str;
+ StringData::const_iterator _it;
+};
+
+// Parses an attribute name according to the rules for the "descr" type defined in
+// https://tools.ietf.org/html/rfc4512
+std::string RFC4514Parser::extractAttributeName() {
+ StringBuilder sb;
+
+ auto ch = _cur();
+ stdx::function<bool(char ch)> characterCheck;
+ // If the first character is a digit, then this is an OID and can only contain
+ // numbers and '.'
+ if (isDigit(ch)) {
+ characterCheck = [](char ch) { return (isDigit(ch) || ch == '.'); };
+ // If the first character is an alpha, then this is a short name and can only
+ // contain alpha/digit/hyphen characters.
+ } else if (isAlpha(ch)) {
+ characterCheck = [](char ch) { return (isAlpha(ch) || isDigit(ch) || ch == '-'); };
+ // Otherwise this is an invalid attribute name
+ } else {
+ uasserted(ErrorCodes::BadValue,
+ str::stream() << "DN attribute names must begin with either a digit or an alpha"
+ << " not \'"
+ << ch
+ << "\'");
+ }
+
+ for (; ch != '=' && !done(); ch = _advance()) {
+ if (ch == ' ') {
+ continue;
+ }
+ uassert(ErrorCodes::BadValue,
+ str::stream() << "DN attribute name contains an invalid character \'" << ch << "\'",
+ characterCheck(ch));
+ sb << ch;
+ }
+
+ if (!done()) {
+ _advance();
+ }
+
+ return sb.str();
+}
+
+std::pair<std::string, RFC4514Parser::ValueTerminator> RFC4514Parser::extractValue() {
+ StringBuilder sb;
+
+ // The RFC states the spaces at the beginning and end of the value must be escaped, which
+ // means we should skip any leading unescaped spaces.
+ skipSpaces();
+
+ // Every time we see an escaped space ("\ "), we increment this counter. Every time we see
+ // anything non-space character we reset this counter to zero. That way we'll know the number
+ // of consecutive escaped spaces at the end of the string there are.
+ int trailingSpaces = 0;
+
+ char ch = _cur();
+ uassert(ErrorCodes::BadValue, "Raw DER sequences are not supported in DN strings", ch != '#');
+ for (; ch != ',' && ch != '+' && !done(); ch = _advance()) {
+ if (ch == '\\') {
+ ch = _advance();
+ if (isEscaped(ch)) {
+ sb << ch;
+ trailingSpaces = 0;
+ } else if (isHex(ch)) {
+ const std::array<char, 2> hexValStr = {ch, _advance()};
+
+ uassert(ErrorCodes::BadValue,
+ str::stream() << "Escaped hex value contains invalid character \'"
+ << hexValStr[1]
+ << "\'",
+ isHex(hexValStr[1]));
+ const char hexVal = uassertStatusOK(fromHex(StringData(hexValStr.data(), 2)));
+ sb << hexVal;
+ if (hexVal != ' ') {
+ trailingSpaces = 0;
+ } else {
+ trailingSpaces++;
+ }
+ } else if (isMayBeEscaped(ch)) {
+ // It is legal to escape whitespace, but we don't count it as an "escaped"
+ // character because we don't require it to be escaped within the value, that is
+ // "C=New York" is legal, and so is "C=New\ York"
+ //
+ // The exception is that leading and trailing whitespace must be escaped or else
+ // it will be trimmed.
+ sb << ch;
+ if (ch == ' ') {
+ trailingSpaces++;
+ } else {
+ trailingSpaces = 0;
+ }
+ } else {
+ uasserted(ErrorCodes::BadValue,
+ str::stream() << "Invalid escaped character \'" << ch << "\'");
+ }
+ } else if (isEscaped(ch)) {
+ uasserted(ErrorCodes::BadValue,
+ str::stream() << "Found unescaped character that should be escaped: \'" << ch
+ << "\'");
+ } else {
+ if (ch != ' ') {
+ trailingSpaces = 0;
+ }
+ sb << ch;
+ }
+ }
+
+ std::string val = sb.str();
+ // It's legal to have trailing spaces as long as they are escaped, so if we have some trailing
+ // escaped spaces, trim the size of the string to the last non-space character + the number of
+ // escaped trailing spaces.
+ if (trailingSpaces > 0) {
+ auto lastNonSpace = val.find_last_not_of(' ');
+ lastNonSpace += trailingSpaces + 1;
+ val.erase(lastNonSpace);
+ }
+
+ // Consume the + or , character
+ if (!done()) {
+ _advance();
+ }
+
+ switch (ch) {
+ case '+':
+ return {std::move(val), MultiValue};
+ case ',':
+ return {std::move(val), NewRDN};
+ default:
+ invariant(done());
+ return {std::move(val), Done};
+ }
+}
+
+const auto getTLSVersionCounts = ServiceContext::declareDecoration<TLSVersionCounts>();
+
+// These represent the ASN.1 type bytes for strings used in an X509 DirectoryString
+constexpr int kASN1UTF8String = 12;
+constexpr int kASN1PrintableString = 19;
+constexpr int kASN1TeletexString = 20;
+constexpr int kASN1UniversalString = 28;
+constexpr int kASN1BMPString = 30;
+constexpr int kASN1OctetString = 4;
+} // namespace
+
+StatusWith<SSLX509Name> parseDN(StringData sd) try {
+ uassert(ErrorCodes::BadValue, "DN strings must be valid UTF-8 strings", isValidUTF8(sd));
+ RFC4514Parser parser(sd);
+
+ std::vector<std::vector<SSLX509Name::Entry>> entries;
+ auto curRDN = entries.emplace(entries.end());
+ while (!parser.done()) {
+ // Allow spaces to separate RDNs for readability, e.g. "CN=foo, OU=bar, DC=bizz"
+ parser.skipSpaces();
+ auto attributeName = parser.extractAttributeName();
+ auto oid = x509ShortNameToOid(attributeName);
+ uassert(ErrorCodes::BadValue, str::stream() << "DN contained an unknown OID " << oid, oid);
+ std::string value;
+ char terminator;
+ std::tie(value, terminator) = parser.extractValue();
+ curRDN->emplace_back(std::move(*oid), kASN1UTF8String, std::move(value));
+ if (terminator == RFC4514Parser::NewRDN) {
+ curRDN = entries.emplace(entries.end());
+ }
+ }
+
+ uassert(ErrorCodes::BadValue,
+ "Cannot parse empty DN",
+ entries.size() > 1 || !entries.front().empty());
+
+ return SSLX509Name(std::move(entries));
+} catch (const DBException& e) {
+ return e.toStatus();
+}
#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 nid = OBJ_txt2nid(name.c_str());
+std::string x509OidToShortName(StringData name) {
+ const auto nid = OBJ_txt2nid(name.rawData());
if (nid == 0) {
- return name;
+ return name.toString();
}
const auto* sn = OBJ_nid2sn(nid);
if (!sn) {
- return name;
+ return name.toString();
}
return sn;
}
+
+boost::optional<std::string> x509ShortNameToOid(StringData name) {
+ // Converts the OID to an ASN1_OBJECT
+ const auto obj = OBJ_txt2obj(name.rawData(), 0);
+ if (!obj) {
+ return boost::none;
+ }
+
+ // OBJ_obj2txt doesn't let you pass in a NULL buffer and a negative size to discover how
+ // big the buffer should be, but the man page gives 80 as a good guess for buffer size.
+ constexpr auto kDefaultBufferSize = 80;
+ std::vector<char> buffer(kDefaultBufferSize);
+ size_t realSize = OBJ_obj2txt(buffer.data(), buffer.size(), obj, 1);
+
+ // Resize the buffer down or up to the real size.
+ buffer.resize(realSize);
+
+ // If the real size is greater than the default buffer size we picked, then just call
+ // OBJ_obj2txt again now that the buffer is correctly sized.
+ if (realSize > kDefaultBufferSize) {
+ OBJ_obj2txt(buffer.data(), buffer.size(), obj, 1);
+ }
+
+ return std::string(buffer.data(), buffer.size());
+}
#else
// On Apple/Windows we have to provide our own mapping.
// Generate the 2.5.4.* portions of this list from OpenSSL sources with:
// grep -E '^X509 ' "$OPENSSL/crypto/objects/objects.txt" | tr -d '\t' |
// sed -e 's/^X509 *\([0-9]\+\) *\(: *\)\+\([[:alnum:]]\+\).*/{"2.5.4.\1", "\3"},/g'
-std::string x509OidToShortName(const std::string& name) {
- static const StringMap<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.29.17", "subjectAltName"},
+static const std::initializer_list<std::pair<StringData, StringData>> kX509OidToShortNameMappings =
+ {
+ {"0.9.2342.19200300.100.1.1"_sd, "UID"_sd},
+ {"0.9.2342.19200300.100.1.25"_sd, "DC"_sd},
+ {"1.2.840.113549.1.9.1"_sd, "emailAddress"_sd},
+ {"2.5.29.17"_sd, "subjectAltName"_sd},
// X509 OIDs Generated from objects.txt
- {"2.5.4.3", "CN"},
- {"2.5.4.4", "SN"},
- {"2.5.4.5", "serialNumber"},
- {"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"},
- {"2.5.4.12", "title"},
- {"2.5.4.13", "description"},
- {"2.5.4.14", "searchGuide"},
- {"2.5.4.15", "businessCategory"},
- {"2.5.4.16", "postalAddress"},
- {"2.5.4.17", "postalCode"},
- {"2.5.4.18", "postOfficeBox"},
- {"2.5.4.19", "physicalDeliveryOfficeName"},
- {"2.5.4.20", "telephoneNumber"},
- {"2.5.4.21", "telexNumber"},
- {"2.5.4.22", "teletexTerminalIdentifier"},
- {"2.5.4.23", "facsimileTelephoneNumber"},
- {"2.5.4.24", "x121Address"},
- {"2.5.4.25", "internationaliSDNNumber"},
- {"2.5.4.26", "registeredAddress"},
- {"2.5.4.27", "destinationIndicator"},
- {"2.5.4.28", "preferredDeliveryMethod"},
- {"2.5.4.29", "presentationAddress"},
- {"2.5.4.30", "supportedApplicationContext"},
- {"2.5.4.31", "member"},
- {"2.5.4.32", "owner"},
- {"2.5.4.33", "roleOccupant"},
- {"2.5.4.34", "seeAlso"},
- {"2.5.4.35", "userPassword"},
- {"2.5.4.36", "userCertificate"},
- {"2.5.4.37", "cACertificate"},
- {"2.5.4.38", "authorityRevocationList"},
- {"2.5.4.39", "certificateRevocationList"},
- {"2.5.4.40", "crossCertificatePair"},
- {"2.5.4.41", "name"},
- {"2.5.4.42", "GN"},
- {"2.5.4.43", "initials"},
- {"2.5.4.44", "generationQualifier"},
- {"2.5.4.45", "x500UniqueIdentifier"},
- {"2.5.4.46", "dnQualifier"},
- {"2.5.4.47", "enhancedSearchGuide"},
- {"2.5.4.48", "protocolInformation"},
- {"2.5.4.49", "distinguishedName"},
- {"2.5.4.50", "uniqueMember"},
- {"2.5.4.51", "houseIdentifier"},
- {"2.5.4.52", "supportedAlgorithms"},
- {"2.5.4.53", "deltaRevocationList"},
- {"2.5.4.54", "dmdName"},
- {"2.5.4.65", "pseudonym"},
- {"2.5.4.72", "role"},
- };
+ {"2.5.4.3"_sd, "CN"_sd},
+ {"2.5.4.4"_sd, "SN"_sd},
+ {"2.5.4.5"_sd, "serialNumber"_sd},
+ {"2.5.4.6"_sd, "C"_sd},
+ {"2.5.4.7"_sd, "L"_sd},
+ {"2.5.4.8"_sd, "ST"_sd},
+ {"2.5.4.9"_sd, "street"_sd},
+ {"2.5.4.10"_sd, "O"_sd},
+ {"2.5.4.11"_sd, "OU"_sd},
+ {"2.5.4.12"_sd, "title"_sd},
+ {"2.5.4.13"_sd, "description"_sd},
+ {"2.5.4.14"_sd, "searchGuide"_sd},
+ {"2.5.4.15"_sd, "businessCategory"_sd},
+ {"2.5.4.16"_sd, "postalAddress"_sd},
+ {"2.5.4.17"_sd, "postalCode"_sd},
+ {"2.5.4.18"_sd, "postOfficeBox"_sd},
+ {"2.5.4.19"_sd, "physicalDeliveryOfficeName"_sd},
+ {"2.5.4.20"_sd, "telephoneNumber"_sd},
+ {"2.5.4.21"_sd, "telexNumber"_sd},
+ {"2.5.4.22"_sd, "teletexTerminalIdentifier"_sd},
+ {"2.5.4.23"_sd, "facsimileTelephoneNumber"_sd},
+ {"2.5.4.24"_sd, "x121Address"_sd},
+ {"2.5.4.25"_sd, "internationaliSDNNumber"_sd},
+ {"2.5.4.26"_sd, "registeredAddress"_sd},
+ {"2.5.4.27"_sd, "destinationIndicator"_sd},
+ {"2.5.4.28"_sd, "preferredDeliveryMethod"_sd},
+ {"2.5.4.29"_sd, "presentationAddress"_sd},
+ {"2.5.4.30"_sd, "supportedApplicationContext"_sd},
+ {"2.5.4.31"_sd, "member"_sd},
+ {"2.5.4.32"_sd, "owner"_sd},
+ {"2.5.4.33"_sd, "roleOccupant"_sd},
+ {"2.5.4.34"_sd, "seeAlso"_sd},
+ {"2.5.4.35"_sd, "userPassword"_sd},
+ {"2.5.4.36"_sd, "userCertificate"_sd},
+ {"2.5.4.37"_sd, "cACertificate"_sd},
+ {"2.5.4.38"_sd, "authorityRevocationList"_sd},
+ {"2.5.4.39"_sd, "certificateRevocationList"_sd},
+ {"2.5.4.40"_sd, "crossCertificatePair"_sd},
+ {"2.5.4.41"_sd, "name"_sd},
+ {"2.5.4.42"_sd, "GN"_sd},
+ {"2.5.4.43"_sd, "initials"_sd},
+ {"2.5.4.44"_sd, "generationQualifier"_sd},
+ {"2.5.4.45"_sd, "x500UniqueIdentifier"_sd},
+ {"2.5.4.46"_sd, "dnQualifier"_sd},
+ {"2.5.4.47"_sd, "enhancedSearchGuide"_sd},
+ {"2.5.4.48"_sd, "protocolInformation"_sd},
+ {"2.5.4.49"_sd, "distinguishedName"_sd},
+ {"2.5.4.50"_sd, "uniqueMember"_sd},
+ {"2.5.4.51"_sd, "houseIdentifier"_sd},
+ {"2.5.4.52"_sd, "supportedAlgorithms"_sd},
+ {"2.5.4.53"_sd, "deltaRevocationList"_sd},
+ {"2.5.4.54"_sd, "dmdName"_sd},
+ {"2.5.4.65"_sd, "pseudonym"_sd},
+ {"2.5.4.72"_sd, "role"_sd},
+};
+
+std::string x509OidToShortName(StringData oid) {
+ auto it = std::find_if(
+ kX509OidToShortNameMappings.begin(),
+ kX509OidToShortNameMappings.end(),
+ [&](const std::pair<StringData, StringData>& entry) { return entry.first == oid; });
- auto it = kX509OidToShortNameMappings.find(name);
if (it == kX509OidToShortNameMappings.end()) {
- return name;
+ return oid.toString();
}
- return it->second;
+ return it->second.toString();
}
-#endif
-const auto getTLSVersionCounts = ServiceContext::declareDecoration<TLSVersionCounts>();
+boost::optional<std::string> x509ShortNameToOid(StringData name) {
+ auto it = std::find_if(
+ kX509OidToShortNameMappings.begin(),
+ kX509OidToShortNameMappings.end(),
+ [&](const std::pair<StringData, StringData>& entry) { return entry.second == name; });
-} // namespace
+ if (it == kX509OidToShortNameMappings.end()) {
+ // If the name is a known oid in our mapping list then just return it.
+ if (std::find_if(kX509OidToShortNameMappings.begin(),
+ kX509OidToShortNameMappings.end(),
+ [&](const auto& entry) { return entry.first == name; }) !=
+ kX509OidToShortNameMappings.end()) {
+ return name.toString();
+ }
+ return boost::none;
+ }
+ return it->first.toString();
+}
+#endif
TLSVersionCounts& TLSVersionCounts::get(ServiceContext* serviceContext) {
return getTLSVersionCounts(serviceContext);
@@ -161,8 +472,8 @@ MONGO_INITIALIZER_WITH_PREREQUISITES(SSLManagerLogger, ("SSLManager", "GlobalLog
if (!config.clientSubjectName.empty()) {
LOG(1) << "Client Certificate Name: " << config.clientSubjectName;
}
- if (!config.serverSubjectName.empty()) {
- LOG(1) << "Server Certificate Name: " << config.serverSubjectName;
+ if (!config.serverSubjectName().empty()) {
+ LOG(1) << "Server Certificate Name: " << config.serverSubjectName();
LOG(1) << "Server Certificate Expiration: " << config.serverCertificateExpirationDate;
}
}
@@ -170,6 +481,32 @@ MONGO_INITIALIZER_WITH_PREREQUISITES(SSLManagerLogger, ("SSLManager", "GlobalLog
return Status::OK();
}
+Status SSLX509Name::normalizeStrings() {
+ for (auto& rdn : _entries) {
+ for (auto& entry : rdn) {
+ switch (entry.type) {
+ // For each type of valid DirectoryString, do the string prep algorithm.
+ case kASN1UTF8String:
+ case kASN1PrintableString:
+ case kASN1TeletexString:
+ case kASN1UniversalString:
+ case kASN1BMPString:
+ case kASN1OctetString: {
+ auto res = icuX509DNPrep(entry.value);
+ if (!res.isOK()) {
+ return res.getStatus();
+ }
+ entry.value = std::move(res.getValue());
+ entry.type = kASN1UTF8String;
+ break;
+ }
+ }
+ }
+ }
+
+ return Status::OK();
+}
+
StatusWith<std::string> SSLX509Name::getOID(StringData oid) const {
for (const auto& rdn : _entries) {
for (const auto& entry : rdn) {
@@ -238,40 +575,58 @@ std::vector<SSLX509Name::Entry> canonicalizeClusterDN(
}
} // namespace
+Status SSLConfiguration::setServerSubjectName(SSLX509Name name) {
+ auto status = name.normalizeStrings();
+ if (!status.isOK()) {
+ return status;
+ }
+ _serverSubjectName = std::move(name);
+ _canonicalServerSubjectName = canonicalizeClusterDN(_serverSubjectName.entries());
+ return Status::OK();
+}
+
/**
* 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.
+ * names in their normalized, unescaped forms and provides a more reliable match.
*
- * 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.
+ * The StringData version attempts to canonicalize the stringified subject name
+ * according to RFC4514 and compare that to the normalized/unescaped version of
+ * the server's distinguished name.
*/
-bool SSLConfiguration::isClusterMember(const SSLX509Name& subject) const {
- auto client = canonicalizeClusterDN(subject._entries);
- auto server = canonicalizeClusterDN(serverSubjectName._entries);
+bool SSLConfiguration::isClusterMember(SSLX509Name subject) const {
+ if (!subject.normalizeStrings().isOK()) {
+ return false;
+ }
+
+ auto client = canonicalizeClusterDN(subject.entries());
- return !client.empty() && (client == server);
+ return !client.empty() && (client == _canonicalServerSubjectName);
}
bool SSLConfiguration::isClusterMember(StringData subjectName) const {
- std::vector<std::string> clientRDN = StringSplitter::split(subjectName.toString(), ",");
- std::vector<std::string> serverRDN = StringSplitter::split(serverSubjectName.toString(), ",");
+ auto swClient = parseDN(subjectName);
+ if (!swClient.isOK()) {
+ warning() << "Unable to parse client subject name: " << swClient.getStatus();
+ return false;
+ }
+ auto& client = swClient.getValue();
+ auto status = client.normalizeStrings();
+ if (!status.isOK()) {
+ warning() << "Unable to normalize client subject name: " << status;
+ return false;
+ }
- canonicalizeClusterDN(&clientRDN);
- canonicalizeClusterDN(&serverRDN);
+ auto canonicalClient = canonicalizeClusterDN(client.entries());
- return !clientRDN.empty() && (clientRDN == serverRDN);
+ return !canonicalClient.empty() && (canonicalClient == _canonicalServerSubjectName);
}
BSONObj SSLConfiguration::getServerStatusBSON() const {
BSONObjBuilder security;
- security.append("SSLServerSubjectName", serverSubjectName.toString());
+ 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 d93728aa466..fa2abc656a5 100644
--- a/src/mongo/util/net/ssl_manager.h
+++ b/src/mongo/util/net/ssl_manager.h
@@ -101,14 +101,24 @@ public:
virtual std::string getSNIServerName() const = 0;
};
-struct SSLConfiguration {
+class SSLConfiguration {
+public:
bool isClusterMember(StringData subjectName) const;
- bool isClusterMember(const SSLX509Name& subjectName) const;
+ bool isClusterMember(SSLX509Name subjectName) const;
BSONObj getServerStatusBSON() const;
- SSLX509Name serverSubjectName;
+ Status setServerSubjectName(SSLX509Name name);
+
+ const SSLX509Name& serverSubjectName() const {
+ return _serverSubjectName;
+ }
+
SSLX509Name clientSubjectName;
Date_t serverCertificateExpirationDate;
bool hasCA = false;
+
+private:
+ SSLX509Name _serverSubjectName;
+ std::vector<SSLX509Name::Entry> _canonicalServerSubjectName;
};
/**
@@ -244,13 +254,26 @@ StatusWith<stdx::unordered_set<RoleName>> parsePeerRoles(ConstDataRange cdrExten
std::string removeFQDNRoot(std::string name);
/**
- * Escape a string per RGC 2253
+ * Escape a string per RFC 2253
*
* See "2.4 Converting an AttributeValue from ASN.1 to a String" in RFC 2243
*/
std::string escapeRfc2253(StringData str);
/**
+ * Parse a DN from a string per RFC 4514
+ */
+StatusWith<SSLX509Name> parseDN(StringData str);
+
+/**
+ * These functions map short names for RDN components to numeric OID's and the other way around.
+ *
+ * The x509ShortNameToOid returns boost::none if no mapping exists for that oid.
+ */
+std::string x509OidToShortName(StringData name);
+boost::optional<std::string> x509ShortNameToOid(StringData name);
+
+/**
* Platform neutral TLS version enum
*/
enum class TLSVersion {
diff --git a/src/mongo/util/net/ssl_manager_apple.cpp b/src/mongo/util/net/ssl_manager_apple.cpp
index 23c03910983..30de09c63c1 100644
--- a/src/mongo/util/net/ssl_manager_apple.cpp
+++ b/src/mongo/util/net/ssl_manager_apple.cpp
@@ -1153,8 +1153,9 @@ SSLManagerApple::SSLManagerApple(const SSLParams& params, bool isServer)
if (isServer) {
uassertStatusOK(initSSLContext(&_serverCtx, params, ConnectionDirection::kIncoming));
if (_serverCtx.certs) {
- _sslConfiguration.serverSubjectName = uassertStatusOK(certificateGetSubject(
- _serverCtx.certs.get(), &_sslConfiguration.serverCertificateExpirationDate));
+ uassertStatusOK(
+ _sslConfiguration.setServerSubjectName(uassertStatusOK(certificateGetSubject(
+ _serverCtx.certs.get(), &_sslConfiguration.serverCertificateExpirationDate))));
static auto task =
CertificateExpirationMonitor(_sslConfiguration.serverCertificateExpirationDate);
}
@@ -1558,7 +1559,7 @@ std::unique_ptr<SSLManagerInterface> SSLManagerInterface::create(const SSLParams
return stdx::make_unique<SSLManagerApple>(params, isServer);
}
-MONGO_INITIALIZER(SSLManager)(InitializerContext*) {
+MONGO_INITIALIZER_WITH_PREREQUISITES(SSLManager, ("LoadICUData"))(InitializerContext*) {
kMongoDBRolesOID = ::CFStringCreateWithCString(
nullptr, mongodbRolesOID.identifier.c_str(), ::kCFStringEncodingUTF8);
diff --git a/src/mongo/util/net/ssl_manager_openssl.cpp b/src/mongo/util/net/ssl_manager_openssl.cpp
index 52f4a37e126..11a0ebc4dcb 100644
--- a/src/mongo/util/net/ssl_manager_openssl.cpp
+++ b/src/mongo/util/net/ssl_manager_openssl.cpp
@@ -662,7 +662,8 @@ MONGO_INITIALIZER(SetupOpenSSL)(InitializerContext*) {
return Status::OK();
}
-MONGO_INITIALIZER_WITH_PREREQUISITES(SSLManager, ("SetupOpenSSL"))(InitializerContext*) {
+MONGO_INITIALIZER_WITH_PREREQUISITES(SSLManager, ("SetupOpenSSL", "LoadICUData"))
+(InitializerContext*) {
stdx::lock_guard<SimpleMutex> lck(sslManagerMtx);
if (!isSSLServer || (sslGlobalParams.sslMode.load() != SSLParams::SSLMode_disabled)) {
theSSLManager = new SSLManagerOpenSSL(sslGlobalParams, isSSLServer);
@@ -846,13 +847,16 @@ SSLManagerOpenSSL::SSLManagerOpenSSL(const SSLParams& params, bool isServer)
uasserted(16562, "ssl initialization problem");
}
+ SSLX509Name serverSubjectName;
if (!_parseAndValidateCertificate(params.sslPEMKeyFile,
&_serverPEMPassword,
- &_sslConfiguration.serverSubjectName,
+ &serverSubjectName,
&_sslConfiguration.serverCertificateExpirationDate)) {
uasserted(16942, "ssl initialization problem");
}
+ uassertStatusOK(_sslConfiguration.setServerSubjectName(std::move(serverSubjectName)));
+
static CertificateExpirationMonitor task =
CertificateExpirationMonitor(_sslConfiguration.serverCertificateExpirationDate);
}
diff --git a/src/mongo/util/net/ssl_manager_test.cpp b/src/mongo/util/net/ssl_manager_test.cpp
index 3abc6fc72da..1c7d866815a 100644
--- a/src/mongo/util/net/ssl_manager_test.cpp
+++ b/src/mongo/util/net/ssl_manager_test.cpp
@@ -46,7 +46,6 @@
namespace mongo {
namespace {
TEST(SSLManager, matchHostname) {
-#ifdef MONGO_CONFIG_SSL
enum Expected : bool { match = true, mismatch = false };
const struct {
Expected expected;
@@ -87,12 +86,8 @@ TEST(SSLManager, matchHostname) {
}
}
ASSERT_FALSE(failure);
-#endif
-}
}
-#ifdef MONGO_CONFIG_SSL
-
std::vector<RoleName> getSortedRoles(const stdx::unordered_set<RoleName>& roles) {
std::vector<RoleName> vec;
vec.reserve(roles.size());
@@ -273,7 +268,115 @@ TEST(SSLManager, DHCheckRFC7919) {
}
#endif
-#endif
+struct FlattenedX509Name {
+ using EntryVector = std::vector<std::pair<std::string, std::string>>;
+
+ FlattenedX509Name(std::initializer_list<EntryVector::value_type> forVector)
+ : value(forVector) {}
+
+ FlattenedX509Name() = default;
+
+ void addPair(std::string oid, std::string val) {
+ value.emplace_back(std::move(oid), std::move(val));
+ }
+
+ std::string toString() const {
+ bool first = true;
+ StringBuilder sb;
+ for (const auto& entry : value) {
+ sb << (first ? "\"" : ",\"") << entry.first << "\"=\"" << entry.second << "\"";
+ first = false;
+ }
+
+ return sb.str();
+ }
+
+ EntryVector value;
+
+ bool operator==(const FlattenedX509Name& other) const {
+ return value == other.value;
+ }
+};
+
+std::ostream& operator<<(std::ostream& o, const FlattenedX509Name& name) {
+ o << name.toString();
+ return o;
+}
+
+FlattenedX509Name flattenX509Name(const SSLX509Name& name) {
+ FlattenedX509Name ret;
+ for (const auto& entry : name.entries()) {
+ for (const auto& rdn : entry) {
+ ret.addPair(rdn.oid, rdn.value);
+ }
+ }
+
+ return ret;
+}
+
+TEST(SSLManager, DNParsingAndNormalization) {
+ std::vector<std::pair<std::string, FlattenedX509Name>> tests = {
+ // Basic DN parsing
+ {"UID=jsmith,DC=example,DC=net",
+ {{"0.9.2342.19200300.100.1.1", "jsmith"},
+ {"0.9.2342.19200300.100.1.25", "example"},
+ {"0.9.2342.19200300.100.1.25", "net"}}},
+ {"OU=Sales+CN=J. Smith,DC=example,DC=net",
+ {{"2.5.4.11", "Sales"},
+ {"2.5.4.3", "J. Smith"},
+ {"0.9.2342.19200300.100.1.25", "example"},
+ {"0.9.2342.19200300.100.1.25", "net"}}},
+ {R"(CN=James \"Jim\" Smith\, III,DC=example,DC=net)",
+ {{"2.5.4.3", R"(James "Jim" Smith, III)"},
+ {"0.9.2342.19200300.100.1.25", "example"},
+ {"0.9.2342.19200300.100.1.25", "net"}}},
+ // Per RFC4518, control sequences are mapped to nothing and whitepace is mapped to ' '
+ {"CN=Before\\0aAfter,O=tabs\tare\tspaces\u200B,DC=\\07\\08example,DC=net",
+ {{"2.5.4.3", "Before After"},
+ {"2.5.4.10", "tabs are spaces"},
+ {"0.9.2342.19200300.100.1.25", "example"},
+ {"0.9.2342.19200300.100.1.25", "net"}}},
+ // Check that you can't fake a cluster dn with poor comma escaping
+ {R"(CN=evil\,OU\=Kernel,O=MongoDB Inc.,L=New York City,ST=New York,C=US)",
+ {{"2.5.4.3", "evil,OU=Kernel"},
+ {"2.5.4.10", "MongoDB Inc."},
+ {"2.5.4.7", "New York City"},
+ {"2.5.4.8", "New York"},
+ {"2.5.4.6", "US"}}},
+ // check space handling (must be escaped at the beginning and end of strings)
+ {R"(CN= \ escaped spaces\20\ )", {{"2.5.4.3", " escaped spaces "}}},
+ {"CN=server, O=MongoDB Inc.", {{"2.5.4.3", "server"}, {"2.5.4.10", "MongoDB Inc."}}},
+ // Check that escaped #'s work correctly at the beginning of the string and throughout.
+ {R"(CN=\#1 = \\#1)", {{"2.5.4.3", "#1 = \\#1"}}},
+ {R"(CN== \#1)", {{"2.5.4.3", "= #1"}}},
+ // check that escaped utf8 string properly parse to utf8
+ {R"(CN=Lu\C4\8Di\C4\87)", {{"2.5.4.3", "Lučić"}}},
+ // check that unescaped utf8 strings round trip correctly
+ {"CN = Калоян, O=مُنظّمة الدُّول المُصدِّرة للنّفْط, L=大田区\\, 東京都",
+ {{"2.5.4.3", "Калоян"},
+ {"2.5.4.10", "مُنظّمة الدُّول المُصدِّرة للنّفْط"},
+ {"2.5.4.7", "大田区, 東京都"}}}};
+
+ for (const auto& test : tests) {
+ log() << "Testing DN \"" << test.first << "\"";
+ auto swDN = parseDN(test.first);
+ ASSERT_OK(swDN.getStatus());
+ ASSERT_OK(swDN.getValue().normalizeStrings());
+ auto decoded = flattenX509Name(swDN.getValue());
+ ASSERT_EQ(decoded, test.second);
+ }
+}
+
+TEST(SSLManager, BadDNParsing) {
+ std::vector<std::string> tests = {"CN=#12345",
+ R"(CN=\B)",
+ R"(CN=<", "\)"};
+ for (const auto& test : tests) {
+ log() << "Testing bad DN: \"" << test << "\"";
+ auto swDN = parseDN(test);
+ ASSERT_NOT_OK(swDN.getStatus());
+ }
+}
-// // namespace
+} // namespace
} // namespace mongo
diff --git a/src/mongo/util/net/ssl_manager_windows.cpp b/src/mongo/util/net/ssl_manager_windows.cpp
index 90eb6de52d0..7feee26e213 100644
--- a/src/mongo/util/net/ssl_manager_windows.cpp
+++ b/src/mongo/util/net/ssl_manager_windows.cpp
@@ -344,7 +344,7 @@ private:
UniqueCertificate _sslClusterCertificate;
};
-MONGO_INITIALIZER(SSLManager)(InitializerContext*) {
+MONGO_INITIALIZER_WITH_PREREQUISITES(SSLManager, ("LoadICUData"))(InitializerContext*) {
stdx::lock_guard<SimpleMutex> lck(sslManagerMtx);
if (!isSSLServer || (sslGlobalParams.sslMode.load() != SSLParams::SSLMode_disabled)) {
theSSLManager = new SSLManagerWindows(sslGlobalParams, isSSLServer);
@@ -417,10 +417,12 @@ SSLManagerWindows::SSLManagerWindows(const SSLParams& params, bool isServer)
uassertStatusOK(initSSLContext(&_serverCred, params, ConnectionDirection::kIncoming));
if (_serverCertificates[0] != nullptr) {
+ SSLX509Name subjectName;
uassertStatusOK(
_validateCertificate(_serverCertificates[0],
- &_sslConfiguration.serverSubjectName,
+ &subjectName,
&_sslConfiguration.serverCertificateExpirationDate));
+ uassertStatusOK(_sslConfiguration.setServerSubjectName(std::move(subjectName)));
}
// Monitor the server certificate's expiration
diff --git a/src/mongo/util/net/ssl_types.h b/src/mongo/util/net/ssl_types.h
index 76b15e952f1..7bcca37700e 100644
--- a/src/mongo/util/net/ssl_types.h
+++ b/src/mongo/util/net/ssl_types.h
@@ -87,8 +87,19 @@ public:
return !(lhs._entries == rhs._entries);
}
+ const std::vector<std::vector<Entry>>& entries() const {
+ return _entries;
+ }
+
+ /*
+ * This will go through every entry, verify that it's type is a valid DirectoryString
+ * according to https://tools.ietf.org/html/rfc5280#section-4.1.2.4, and perform
+ * the RFC 4518 string prep algorithm on it to normalize the values so they can be
+ * directly compared. After this, all entries should have the type 12 (utf8String).
+ */
+ Status normalizeStrings();
+
private:
- friend struct SSLConfiguration;
std::vector<std::vector<Entry>> _entries;
};
diff --git a/src/mongo/util/text.cpp b/src/mongo/util/text.cpp
index e522ac5aeab..4d10fc88f83 100644
--- a/src/mongo/util/text.cpp
+++ b/src/mongo/util/text.cpp
@@ -121,14 +121,9 @@ inline int leadingOnes(unsigned char c) {
return _leadingOnes[c & 0x7f];
}
-bool isValidUTF8(const std::string& s) {
- return isValidUTF8(s.c_str());
-}
-
-bool isValidUTF8(const char* s) {
+bool isValidUTF8(StringData s) {
int left = 0; // how many bytes are left in the current codepoint
- while (*s) {
- const unsigned char c = (unsigned char)*(s++);
+ for (unsigned char c : s) {
const int ones = leadingOnes(c);
if (left) {
if (ones != 1)
diff --git a/src/mongo/util/text.h b/src/mongo/util/text.h
index 3f747ff1751..94979610b34 100644
--- a/src/mongo/util/text.h
+++ b/src/mongo/util/text.h
@@ -36,6 +36,7 @@
#include <vector>
#include "mongo/base/disallow_copying.h"
+#include "mongo/base/string_data.h"
#include "mongo/config.h"
namespace mongo {
@@ -72,8 +73,7 @@ private:
* std::string can be converted to sequence of codepoints. However, it doesn't
* guarantee that the codepoints are valid.
*/
-bool isValidUTF8(const char* s);
-bool isValidUTF8(const std::string& s);
+bool isValidUTF8(StringData s);
#if defined(_WIN32)
diff --git a/src/third_party/icu4c-57.1/source/mongo_sources/icudt57b.dat b/src/third_party/icu4c-57.1/source/mongo_sources/icudt57b.dat
index 59eab7860da..fbd961fba72 100644
--- a/src/third_party/icu4c-57.1/source/mongo_sources/icudt57b.dat
+++ b/src/third_party/icu4c-57.1/source/mongo_sources/icudt57b.dat
Binary files differ
diff --git a/src/third_party/icu4c-57.1/source/mongo_sources/icudt57l.dat b/src/third_party/icu4c-57.1/source/mongo_sources/icudt57l.dat
index d29853e2481..b088f9b9c7b 100644
--- a/src/third_party/icu4c-57.1/source/mongo_sources/icudt57l.dat
+++ b/src/third_party/icu4c-57.1/source/mongo_sources/icudt57l.dat
Binary files differ
diff --git a/src/third_party/scripts/icu_get_sources.sh b/src/third_party/scripts/icu_get_sources.sh
index 71c102b0370..df06b11a421 100755
--- a/src/third_party/scripts/icu_get_sources.sh
+++ b/src/third_party/scripts/icu_get_sources.sh
@@ -11,7 +11,16 @@
#
# The script accepts a single optional argument, which is the path to the .dat archive to use as the
# source of the trimmed-down ICU .dat files generated as output. If omitted, the .dat archive which
-# is included in the ICU source code is used by default.
+# is included in the ICU source code is used by default. The included .dat file included with some
+# versions of ICU does not contain all the collations needed by mongodb. If this script fails while
+# checking the data list for collations, it must be invoked with the path to our custom .dat
+# archive:
+# ./src/third_party/scripts/icu_get_sources.sh \
+# $(pwd)/src/third_party/icu4c-57.1/source/mongo_sources/icudt57l.dat
+#
+# That archive can be generated by the ICU tool here: http://apps.icu-project.org/datacustom/, you
+# should check all the boxes and download the zipfile for the major version of ICU. This script
+# will strip out anything that isn't needed.
#
# This script returns a zero exit code on success.
@@ -99,11 +108,13 @@ BASE_FILES="root.res
ucadata.icu"
for DESIRED_DATA_DIRECTORY in $DESIRED_DATA_DIRECTORIES; do
for BASE_FILE in $BASE_FILES; do
+ echo "Checking $ORIGINAL_DATA_LIST for $DESIRED_DATA_DIRECTORY/$BASE_FILE"
# Using grep to sanity-check that the file indeed appears in the original data list.
grep -E "^${DESIRED_DATA_DIRECTORY}/${BASE_FILE}$" "$ORIGINAL_DATA_LIST" >> "$NEW_DATA_LIST"
done
for LANGUAGE in $(grep -Ev "^#" "$LANGUAGE_FILE_IN"); do
# Ditto above.
+ echo "Checking $ORIGINAL_DATA_LIST for $DESIRED_DATA_DIRECTORY/$LANGUAGE"
grep -E "^${DESIRED_DATA_DIRECTORY}/${LANGUAGE}.res$" "$ORIGINAL_DATA_LIST" \
>> "$NEW_DATA_LIST"
done
@@ -111,6 +122,7 @@ done
# UStringPrepProfile: USPREP_RFC4013_SASLPREP and NFKC normalization.
grep -E "^rfc4013.spp$" "$ORIGINAL_DATA_LIST" >> "$NEW_DATA_LIST"
+grep -E "^rfc4518.spp$" "$ORIGINAL_DATA_LIST" >> "$NEW_DATA_LIST"
grep -E "^nfkc.nrm$" "$ORIGINAL_DATA_LIST" >> "$NEW_DATA_LIST"
#