summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Benvenuto <mark.benvenuto@mongodb.com>2018-04-17 12:07:46 -0400
committerMark Benvenuto <mark.benvenuto@mongodb.com>2018-04-17 12:07:46 -0400
commitaa0995f8cd3a2f42346439296d1e103be5926a4b (patch)
tree36c3d2eb13ee3b21a4276cec3f3ffcef450e4cb3
parentfcf41ce8ddf70894ed6803420e94a1685cc60903 (diff)
downloadmongo-aa0995f8cd3a2f42346439296d1e103be5926a4b.tar.gz
SERVER-34413 Converting Certificate Subject Names to strings need to obey RFC 2253
-rw-r--r--jstests/libs/README.ssl5
-rw-r--r--jstests/libs/client_escape.pem48
-rw-r--r--jstests/libs/client_utf8.cnf14
-rw-r--r--jstests/libs/client_utf8.pem49
-rw-r--r--jstests/ssl/libs/ssl_x509_role_auth_escape.js13
-rw-r--r--jstests/ssl/libs/ssl_x509_role_auth_utf8.js12
-rw-r--r--jstests/ssl/ssl_x509_roles.js57
-rw-r--r--src/mongo/util/hex.cpp4
-rw-r--r--src/mongo/util/net/ssl/impl/error.ipp3
-rw-r--r--src/mongo/util/net/ssl_manager.cpp49
-rw-r--r--src/mongo/util/net/ssl_manager.h7
-rw-r--r--src/mongo/util/net/ssl_manager_apple.cpp2
-rw-r--r--src/mongo/util/net/ssl_manager_test.cpp15
-rw-r--r--src/mongo/util/net/ssl_manager_windows.cpp127
14 files changed, 355 insertions, 50 deletions
diff --git a/jstests/libs/README.ssl b/jstests/libs/README.ssl
index fd8a4d77ea6..e199e3d297e 100644
--- a/jstests/libs/README.ssl
+++ b/jstests/libs/README.ssl
@@ -23,3 +23,8 @@ openssl x509 -req -sha256 -in roles.csr -days 3650 -out roles.pem -extfile opens
openssl rsa -in roles.key -out roles2.key
cat roles.pem roles2.key > roles_final.pem
+
+
+Example Commands for UTF-8
+--------------------------
+openssl req -new -utf8 -nameopt multiline,utf8 -config .\jstests\libs\client_utf8.cnf -newkey rsa:2048 -nodes -keyout roles.key -out roles.csr \ No newline at end of file
diff --git a/jstests/libs/client_escape.pem b/jstests/libs/client_escape.pem
new file mode 100644
index 00000000000..8858efba637
--- /dev/null
+++ b/jstests/libs/client_escape.pem
@@ -0,0 +1,48 @@
+-----BEGIN CERTIFICATE-----
+MIIDjDCCAnSgAwIBAgIJAOdcrxT4uC2qMA0GCSqGSIb3DQEBCwUAMHQxFzAVBgNV
+BAMTDktlcm5lbCBUZXN0IENBMQ8wDQYDVQQLEwZLZXJuZWwxEDAOBgNVBAoTB01v
+bmdvREIxFjAUBgNVBAcTDU5ldyBZb3JrIENpdHkxETAPBgNVBAgTCE5ldyBZb3Jr
+MQswCQYDVQQGEwJVUzAeFw0xODA0MTEyMDM0MDhaFw0yODA0MDgyMDM0MDhaMFUx
+CzAJBgNVBAYTAiwrMQwwCgYDVQQIDAMiXDwxCzAJBgNVBAcMAiA+MQswCQYDVQQK
+DAI7IDEPMA0GA1UECwwGRXNjYXBlMQ0wCwYDVQQDDARUZXN0MIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu68gQgunDPYW5eBzGVYequBxYIC7S3djIQ0a
+AGHWnJHOhAv8+hk0SqyJdGA9aZA1Fy616DUanOJhRE2lPokT82IMqWPvF79HmY3f
+7eOG6dNostcDs4NFxSdLplgLivOp1Lvb7E52DAMkA8xygGIxRmFCXpqve2qQehpS
+ccB2fY9WxSQ4uGgR8Fl/uqyyv+P6M2jyOh+7SPS14FE4xabHuoC4BI8CQEtKBiMt
+mBDjL9D6SuCC72h7tfR2ACXY3s9GzU1d8H03Ps9Uteo0ksSZ+eDXL1pD2hSp/e2u
+03pNQhApiGea4XKK8VqjsS0QcluQLc9JP0FzPGQHjbDOOa2wFwIDAQABo0AwPjA8
+BgsrBgEEAYKOKQIBAQQtMSswDwwGYmFja3VwDAVhZG1pbjAYDA9yZWFkQW55RGF0
+YWJhc2UMBWFkbWluMA0GCSqGSIb3DQEBCwUAA4IBAQAocD4YHZA1ylZUJZicC/tH
+gf02OQe1CaYgekWDq/CEPeLUrMXPn20w/xpWdAL42HFEkVX+rIkI+vgafbMD9mII
+M6DdRJZmielVhKzFYuuhIUt3mEIwjiucbEct0ltsj/mNAXhRU1LTm+NlpmdfStRJ
+lmHHzMZdEFkT/aNT7tFpCO1KNI5uB7yPN4JFSLM5lHd1qACPs4Be965zXNeAwvv5
+qo83Pq0Hr+/k+73bq9He4ah1zEqUZVvCjG814lzv0GQB5LVV468/IzC07HSAF1Jd
+nCCUy7xVxZhI9MEZ1d8QnpJSAJtQSuGwwJcY2ncjR/I3u12Vz4J+8dG4GbD5meYl
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAu68gQgunDPYW5eBzGVYequBxYIC7S3djIQ0aAGHWnJHOhAv8
++hk0SqyJdGA9aZA1Fy616DUanOJhRE2lPokT82IMqWPvF79HmY3f7eOG6dNostcD
+s4NFxSdLplgLivOp1Lvb7E52DAMkA8xygGIxRmFCXpqve2qQehpSccB2fY9WxSQ4
+uGgR8Fl/uqyyv+P6M2jyOh+7SPS14FE4xabHuoC4BI8CQEtKBiMtmBDjL9D6SuCC
+72h7tfR2ACXY3s9GzU1d8H03Ps9Uteo0ksSZ+eDXL1pD2hSp/e2u03pNQhApiGea
+4XKK8VqjsS0QcluQLc9JP0FzPGQHjbDOOa2wFwIDAQABAoIBAB832QDDWW0BffuM
+oviC56T6okEwCHdcC2QS/tEzdPQkGJrEN7cN/DGzk0WFQJHPh9UsUtumAaA40379
+/09wk533IC4Izls/2NQE0kuKfgVkwdSFiTsRycgqah3oJTtenlAhU0pyHpaO6y2q
+kx0huBqB0P1Vc3zu08tLl40OO2dU5/NDxFrQBF29YoeSvKDEmvxpAsGeV8zcKaR3
+CP2dXzTQUm/g02rmq/Rm00h4dYX+TtJKOPPF8UJn+E7btgrej8EOkFXb7Wr/Bspf
+zo/7lYYBD08jPvrfBxBJszblVgwxeCnrF4MBavjgwm4bK5J910YgR7Jj64hXKlrX
+hYPzwgkCgYEA9USMp6kBNG/wyBTGae650E6U2N8BDPezV6aPBIzEY+jai3n4b+gc
+CI7612IneU7bpZLe3sQH222LENgV1G03dgjYLvitldoIJSXh+O9Lzok62TfJd1LN
+tLgnc05SPk2L5R5bJblyZX0qaG1djotPQrLImF0+lFEhERx264AF6+UCgYEAw+WI
+hbQoTKhQebi0PDG5zE82JCTSgyAaoYos+bVFYSkvdMAOJUpxvhgBVkwuROF0mHTR
+QiWgVnDSikPwDs9f0JaoBG2r55bh9nCesm0OSFCILvNBR3D9mGi5Zf0lsBcqwtHp
+qui2bidUz/4khwGrV6ZjYwRyqCXii07HIk8aBEsCgYEAlpDGaAZwHui5P5rBUzai
+CyayaZA87OECz8QH+BsSVOMVhpoR0WEEFJzxkFdJNEccv8Ax4T8mM6ZDwMtmW0nQ
+yj0TuYIfzeZTKbrfxhngx6Gm5sJAg2fpsA986GQkSOsB/l1sRvRZLcRwzDqXPHJ4
+7dEjI6XaTOKhEggPWPL2r6ECgYARUZ+qQm01qcPDnyLAF51XhMFCnMbHtPTlxzOG
+uBH0LxH0/m3Buj0oEzM7D+0fRddrCxjdAjqUqdDTTIJSrN7JsCgWjCOgi+RaR0ag
+e6fBmO0RSYxB5qlk6g1KCQFl+gZbxvcjlwNZ5bxHzy4niWw+iYVL4b7JQuAudL6C
+OAAS1QKBgQDKiGC1b3xbxOtrCcxuPBENrysiu2blLZIlVLuK3cDQk3GGM0tmOZrP
+013lQG0P2wbDDTt5CbMshbvXYava7usw0zypc5CyWJbz524TAaL0jy5voXlM3sTF
+yXbwalewqoZRo1YvBkeVurfRSbFFudAjvxLgxWFe7QizRRMkeG/NQg==
+-----END RSA PRIVATE KEY-----
diff --git a/jstests/libs/client_utf8.cnf b/jstests/libs/client_utf8.cnf
new file mode 100644
index 00000000000..22e63c4334b
--- /dev/null
+++ b/jstests/libs/client_utf8.cnf
@@ -0,0 +1,14 @@
+[req]
+prompt = no
+distinguished_name = dn
+
+[dn]
+CN=Калоян
+OU=Kernel Users
+O=MongoDB
+L=New York City
+ST=New York
+C=US
+
+[MongoDBAuthorizationGrant]
+1.3.6.1.4.1.34601.2.1.1 = DER:312B300F0C066261636B75700C0561646D696E30180C0F72656164416E7944617461626173650C0561646D696E
diff --git a/jstests/libs/client_utf8.pem b/jstests/libs/client_utf8.pem
new file mode 100644
index 00000000000..7a4f1e7886c
--- /dev/null
+++ b/jstests/libs/client_utf8.pem
@@ -0,0 +1,49 @@
+-----BEGIN CERTIFICATE-----
+MIIDrzCCApegAwIBAgIJAOdcrxT4uC2xMA0GCSqGSIb3DQEBCwUAMHQxFzAVBgNV
+BAMTDktlcm5lbCBUZXN0IENBMQ8wDQYDVQQLEwZLZXJuZWwxEDAOBgNVBAoTB01v
+bmdvREIxFjAUBgNVBAcTDU5ldyBZb3JrIENpdHkxETAPBgNVBAgTCE5ldyBZb3Jr
+MQswCQYDVQQGEwJVUzAeFw0xODA0MTMxNjIzNTBaFw0yODA0MTAxNjIzNTBaMHgx
+FTATBgNVBAMMDNCa0LDQu9C+0Y/QvTEVMBMGA1UECwwMS2VybmVsIFVzZXJzMRAw
+DgYDVQQKDAdNb25nb0RCMRYwFAYDVQQHDA1OZXcgWW9yayBDaXR5MREwDwYDVQQI
+DAhOZXcgWW9yazELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQChzghecwgQofjdBirHXI9XUd10DSljQNFCGrieMAra4U6mOp8IDuNX
+qfnCwaFebFDYEpCMjjq8dbxm8r0bNTg7wFvGaK1YP9MBQlgNQ8/cYgIlnlGyKWB7
+97f79TfF+hMKkgFHgkZY/3vFaxmJpk5zHPHfDN6iZl4i5ayKNJjiwEje+NVd8L9L
+PuB8zSSYVKyUNP9TmNwjLSAEX9uPndSemLLFhDVg/E8kwY+joFmNORH7u9tfL7G2
+EmVZkimxIzEkjloqrKieqR8iD8dnKmgF1V4VL3qSvJoAsA3AX6syM91z6BGyIuMU
+LkroS0uidCR9jimQH+BdZ9S3T1HagvVtAgMBAAGjQDA+MDwGCysGAQQBgo4pAgEB
+BC0xKzAPDAZiYWNrdXAMBWFkbWluMBgMD3JlYWRBbnlEYXRhYmFzZQwFYWRtaW4w
+DQYJKoZIhvcNAQELBQADggEBAEv4ZEpGpfE7Qo3lJ26SBidYk8vTMhA16rBVOjDI
+fvjexZcOuHrHqjGQHAF+Fa2DsNoKMxsOUoLQkEj33u+CL+JoCMU4tq5ycyQGLk1U
+oRM8dhIa4/GmT70U7GzCgcZcU2SATMacr4x8QvY59Ys0isKz6Yxilc29ZG5LwTzc
+GgFoERezIxYHERiVit9eKnwMSNVvma8lw+JgksE6pBw8fck+gjU06KPtquMv+b5W
+oKnfRf8JEGF+beWBVs/J9MgBziC17urpce4D3wGO57vUjqAEAxhQh6eepszTGCTz
+RMFLJCw3pmWD9D0StJc1URMflpvUkKUNf4wd7IFnbLxerNM=
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEAoc4IXnMIEKH43QYqx1yPV1HddA0pY0DRQhq4njAK2uFOpjqf
+CA7jV6n5wsGhXmxQ2BKQjI46vHW8ZvK9GzU4O8BbxmitWD/TAUJYDUPP3GICJZ5R
+silge/e3+/U3xfoTCpIBR4JGWP97xWsZiaZOcxzx3wzeomZeIuWsijSY4sBI3vjV
+XfC/Sz7gfM0kmFSslDT/U5jcIy0gBF/bj53UnpiyxYQ1YPxPJMGPo6BZjTkR+7vb
+Xy+xthJlWZIpsSMxJI5aKqyonqkfIg/HZypoBdVeFS96kryaALANwF+rMjPdc+gR
+siLjFC5K6EtLonQkfY4pkB/gXWfUt09R2oL1bQIDAQABAoIBAHSpzkTzYcxETOBo
+Mu39oeiOwUKMvtuuf3LPO2/rKdh5fTDDsaiou2iYyNmQrR4csQmqEQkW/+ikZ0w/
+FvSIIdyLLGDZVqr7kdMll8EwtK2tCOlAxpzT0ppvmPuU6jrXvWTwsvVxS/GdfAty
+1j32aDf9lbOCXv/vGU7GFOqtU9KhNhuPDIJYZJwLrBFfTimQtjdI3b1ka6VNvf0t
+4YrIo5TbpG7g/sFDa7WhNeUaCyPXLdoR9Pf/I/8kuHJfAuocACcM18aoCjFrNfH4
+rvNM/+zN3EpmsqE/SxKDeRY27Qn2RbbYeOV4qC37/HnvyKOq61++IrkRLzBvxm8A
+prV0JMECgYEAz8mEerKeTVr+TKFGnxBYDXbslxj1+rbd+75LGi47iBaZlwRMwnDC
+Vve/UktPfSK2QzLY8OPyiJWpk/xeVAF9doa9pdzTb48DZQFCdpBGRwAaSxgMxiC/
+hq0ZWMU1o/RK/JEoy1+eW1QgGhAIweb5gZdu1+dP8OQKmDrymakYtZ0CgYEAx1kt
+/UtBw67WjFfPRbOB5vf4mCFmZdOP2/sqpOmZTshDzBMaG+cK5q0yMv7e8V9EgwzT
+H5BRgQKWOxKavGVZqe9tO1udopE5dBUYs+ax1fTgrBAeeptWImprzomvpG4U6jKx
+wO9OiNSMqyz1PckMFsuGPEfBcR1ur+0aWXhonhECgYEAkR8hk8Bkp4pByatp83Yq
+yVyh6PXZ9nz5lPVW5rYbhfSi2MJ8CeFPGgZ2bTeEENXJji1ZLwVFQvekNGMyTmGs
+CrgYhFsyoNu1dznl/YDOVJBRzNjDMy10Fs+WwFUcHaJlLPkFEOV7aDXdJdbFBwgW
+gtgM5mX7gzcB30hsX72mMFECgYEAivN1Y/W9/Fj1AVP/jPUHmefEZr35MwbZYpta
+6SJOkyRyZV/3NfXAmnOk5vjSi2o9Rh99VaXxubwE3V4lbyYx7oqOF+7PHM6fPnOq
+CVPHJ3yN0kFWDV4TFuAZDxjXzgRRHcGCNxpXIncNKg+nTY7Cl2z7vy7A/khmq8fY
+Mv8D5RECgYEAtj/IO6PpkOx0lYOufesc8a8nj4BaQuUD4bjXxnIMAddDlwdE95Ez
+Z1iBgF5tjI1Li92EM3Vmn8zKgkscyRcCuzczo+67zSl+h6fFUTAci9QuW/6jsekT
+ivFqBTrp///bRlS/qrjGW9F7m13O+xWDGH+SRsldMBbZx9FNbe55ldM=
+-----END RSA PRIVATE KEY-----
diff --git a/jstests/ssl/libs/ssl_x509_role_auth_escape.js b/jstests/ssl/libs/ssl_x509_role_auth_escape.js
new file mode 100644
index 00000000000..a9a0595667c
--- /dev/null
+++ b/jstests/ssl/libs/ssl_x509_role_auth_escape.js
@@ -0,0 +1,13 @@
+// Helper script used to validate login as x509 auth with a certificate with roles works.
+(function() {
+ "use strict";
+
+ // Auth as user in certificate with a subject name with lots of RFC 2253 escaping
+ // Ex: CN=Test,OU=Escape,O=\;\ ,L=\ \>,ST=\"\\\<,C=\,\+
+ // It validates leading space, and the 7 magic characters
+ const ret = db.getSiblingDB("$external").auth({
+ mechanism: "MONGODB-X509",
+ user: "CN=Test,OU=Escape,O=\\;\\ ,L=\\ \\>,ST=\\\"\\\\\\<,C=\\,\\+"
+ });
+ assert.eq(ret, 1, "Auth failed");
+}());
diff --git a/jstests/ssl/libs/ssl_x509_role_auth_utf8.js b/jstests/ssl/libs/ssl_x509_role_auth_utf8.js
new file mode 100644
index 00000000000..f94db33d27f
--- /dev/null
+++ b/jstests/ssl/libs/ssl_x509_role_auth_utf8.js
@@ -0,0 +1,12 @@
+// Helper script used to validate login as x509 auth with a certificate with roles works.
+(function() {
+ "use strict";
+
+ // Authenticate against a certificate with a RDN in the subject name of type UTF8STRING
+ const retutf8 = db.getSiblingDB("$external").auth({
+ mechanism: "MONGODB-X509",
+ user:
+ "C=US,ST=New York,L=New York City,O=MongoDB,OU=Kernel Users,CN=\\D0\\9A\\D0\\B0\\D0\\BB\\D0\\BE\\D1\\8F\\D0\\BD"
+ });
+ assert.eq(retutf8, 1, "Auth failed");
+}());
diff --git a/jstests/ssl/ssl_x509_roles.js b/jstests/ssl/ssl_x509_roles.js
index 9217f9863ba..6bde3f57fae 100644
--- a/jstests/ssl/ssl_x509_roles.js
+++ b/jstests/ssl/ssl_x509_roles.js
@@ -1,32 +1,65 @@
// Test that a client can authenicate against the server with roles.
+// Also validates RFC2253
(function() {
"use strict";
const SERVER_CERT = "jstests/libs/server.pem";
const CA_CERT = "jstests/libs/ca.pem";
const CLIENT_CERT = "jstests/libs/client_roles.pem";
+ const CLIENT_ESCAPE_CERT = "jstests/libs/client_escape.pem";
+ const CLIENT_UTF8_CERT = "jstests/libs/client_utf8.pem";
const CLIENT_USER =
"C=US,ST=New York,L=New York City,O=MongoDB,OU=Kernel Users,CN=Kernel Client Peer Role";
function authAndTest(port) {
- let mongo = runMongoProgram("mongo",
- "--host",
- "localhost",
- "--port",
- port,
- "--ssl",
- "--sslCAFile",
- CA_CERT,
- "--sslPEMKeyFile",
- CLIENT_CERT,
- "jstests/ssl/libs/ssl_x509_role_auth.js");
+ const mongo = runMongoProgram("mongo",
+ "--host",
+ "localhost",
+ "--port",
+ port,
+ "--ssl",
+ "--sslCAFile",
+ CA_CERT,
+ "--sslPEMKeyFile",
+ CLIENT_CERT,
+ "jstests/ssl/libs/ssl_x509_role_auth.js");
// runMongoProgram returns 0 on success
assert.eq(0, mongo, "Connection attempt failed");
+
+ const escaped = runMongoProgram("mongo",
+ "--host",
+ "localhost",
+ "--port",
+ port,
+ "--ssl",
+ "--sslCAFile",
+ CA_CERT,
+ "--sslPEMKeyFile",
+ CLIENT_ESCAPE_CERT,
+ "jstests/ssl/libs/ssl_x509_role_auth_escape.js");
+
+ // runMongoProgram returns 0 on success
+ assert.eq(0, escaped, "Connection attempt failed");
+
+ const utf8 = runMongoProgram("mongo",
+ "--host",
+ "localhost",
+ "--port",
+ port,
+ "--ssl",
+ "--sslCAFile",
+ CA_CERT,
+ "--sslPEMKeyFile",
+ CLIENT_UTF8_CERT,
+ "jstests/ssl/libs/ssl_x509_role_auth_utf8.js");
+
+ // runMongoProgram returns 0 on success
+ assert.eq(0, utf8, "Connection attempt failed");
}
- let x509_options = {sslMode: "requireSSL", sslPEMKeyFile: SERVER_CERT, sslCAFile: CA_CERT};
+ const x509_options = {sslMode: "requireSSL", sslPEMKeyFile: SERVER_CERT, sslCAFile: CA_CERT};
print("1. Testing x.509 auth to mongod");
{
diff --git a/src/mongo/util/hex.cpp b/src/mongo/util/hex.cpp
index adbe6ac560d..2d4a563c05b 100644
--- a/src/mongo/util/hex.cpp
+++ b/src/mongo/util/hex.cpp
@@ -62,6 +62,10 @@ std::string integerToHexDef(T inInt) {
}
template <>
+std::string integerToHex<char>(char val) {
+ return integerToHexDef(val);
+}
+template <>
std::string integerToHex<int>(int val) {
return integerToHexDef(val);
}
diff --git a/src/mongo/util/net/ssl/impl/error.ipp b/src/mongo/util/net/ssl/impl/error.ipp
index e2c6bea061e..5a4031b4cab 100644
--- a/src/mongo/util/net/ssl/impl/error.ipp
+++ b/src/mongo/util/net/ssl/impl/error.ipp
@@ -46,8 +46,7 @@ public:
}
#elif MONGO_CONFIG_SSL_PROVIDER == SSL_PROVIDER_APPLE
std::string message(int value) const {
- // engine_apple produces osstatus_errorcategory messages.
- ASIO_ASSERT(false);
+ // engine_apple produces osstatus_errorcategory messages except for stream_truncated
return "asio.ssl error";
}
#else
diff --git a/src/mongo/util/net/ssl_manager.cpp b/src/mongo/util/net/ssl_manager.cpp
index 3cb37f98983..79a99543475 100644
--- a/src/mongo/util/net/ssl_manager.cpp
+++ b/src/mongo/util/net/ssl_manager.cpp
@@ -38,6 +38,7 @@
#include "mongo/db/server_parameters.h"
#include "mongo/platform/overflow_arithmetic.h"
#include "mongo/transport/session.h"
+#include "mongo/util/hex.h"
#include "mongo/util/mongoutils/str.h"
#include "mongo/util/net/ssl_options.h"
#include "mongo/util/net/ssl_types.h"
@@ -469,6 +470,54 @@ std::string removeFQDNRoot(std::string name) {
return name;
};
+namespace {
+
+// Characters that need to be escaped in RFC 2253
+const std::array<char, 7> rfc2253EscapeChars = {',', '+', '"', '\\', '<', '>', ';'};
+
+} // namespace
+
+// See section "2.4 Converting an AttributeValue from ASN.1 to a String" in RFC 2243
+std::string escapeRfc2253(StringData str) {
+ std::string ret;
+
+ if (str.size() > 0) {
+ size_t pos = 0;
+
+ // a space or "#" character occurring at the beginning of the string
+ if (str[0] == ' ') {
+ ret = "\\ ";
+ pos = 1;
+ } else if (str[0] == '#') {
+ ret = "\\#";
+ pos = 1;
+ }
+
+ while (pos < str.size()) {
+ if (static_cast<signed char>(str[pos]) < 0) {
+ ret += '\\';
+ ret += integerToHex(str[pos]);
+ } else {
+ if (std::find(rfc2253EscapeChars.cbegin(), rfc2253EscapeChars.cend(), str[pos]) !=
+ rfc2253EscapeChars.cend()) {
+ ret += '\\';
+ }
+
+ ret += str[pos];
+ }
+ ++pos;
+ }
+
+ // a space character occurring at the end of the string
+ if (ret.size() > 2 && ret[ret.size() - 1] == ' ') {
+ ret[ret.size() - 1] = '\\';
+ ret += ' ';
+ }
+ }
+
+ return ret;
+}
+
#endif
} // namespace mongo
diff --git a/src/mongo/util/net/ssl_manager.h b/src/mongo/util/net/ssl_manager.h
index 40b9d24276f..71f8b5a5d66 100644
--- a/src/mongo/util/net/ssl_manager.h
+++ b/src/mongo/util/net/ssl_manager.h
@@ -226,5 +226,12 @@ StatusWith<stdx::unordered_set<RoleName>> parsePeerRoles(ConstDataRange cdrExten
*/
std::string removeFQDNRoot(std::string name);
+/**
+ * Escape a string per RGC 2253
+ *
+ * See "2.4 Converting an AttributeValue from ASN.1 to a String" in RFC 2243
+ */
+std::string escapeRfc2253(StringData str);
+
} // namespace mongo
#endif // #ifdef MONGO_CONFIG_SSL
diff --git a/src/mongo/util/net/ssl_manager_apple.cpp b/src/mongo/util/net/ssl_manager_apple.cpp
index 064ff530c38..56d2f5b2b35 100644
--- a/src/mongo/util/net/ssl_manager_apple.cpp
+++ b/src/mongo/util/net/ssl_manager_apple.cpp
@@ -342,7 +342,7 @@ StatusWith<std::string> extractSubjectName(::CFDictionaryRef dict) {
return swValueStr.getStatus();
}
- ret << swLabelStr.getValue() << '=' << swValueStr.getValue();
+ ret << swLabelStr.getValue() << '=' << escapeRfc2253(swValueStr.getValue());
if (i > 1) {
ret << ",";
}
diff --git a/src/mongo/util/net/ssl_manager_test.cpp b/src/mongo/util/net/ssl_manager_test.cpp
index c2b0101637d..a0e26155234 100644
--- a/src/mongo/util/net/ssl_manager_test.cpp
+++ b/src/mongo/util/net/ssl_manager_test.cpp
@@ -245,6 +245,21 @@ TEST(SSLManager, MongoDBRolesParser) {
ASSERT_EQ(roles[1].getDB(), "admin");
}
}
+
+TEST(SSLManager, EscapeRFC2253) {
+ ASSERT_EQ(escapeRfc2253("abc"), "abc");
+ ASSERT_EQ(escapeRfc2253(" abc"), "\\ abc");
+ ASSERT_EQ(escapeRfc2253("#abc"), "\\#abc");
+ ASSERT_EQ(escapeRfc2253("a,c"), "a\\,c");
+ ASSERT_EQ(escapeRfc2253("a+c"), "a\\+c");
+ ASSERT_EQ(escapeRfc2253("a\"c"), "a\\\"c");
+ ASSERT_EQ(escapeRfc2253("a\\c"), "a\\\\c");
+ ASSERT_EQ(escapeRfc2253("a<c"), "a\\<c");
+ ASSERT_EQ(escapeRfc2253("a>c"), "a\\>c");
+ ASSERT_EQ(escapeRfc2253("a;c"), "a\\;c");
+ ASSERT_EQ(escapeRfc2253("abc "), "abc\\ ");
+}
+
#endif
// // namespace
diff --git a/src/mongo/util/net/ssl_manager_windows.cpp b/src/mongo/util/net/ssl_manager_windows.cpp
index f28ced45f8a..8e32993c752 100644
--- a/src/mongo/util/net/ssl_manager_windows.cpp
+++ b/src/mongo/util/net/ssl_manager_windows.cpp
@@ -217,39 +217,6 @@ using UniqueCertChainEngine = AutoHandle<HCERTCHAINENGINE, CertChainEngineFree>;
*/
using UniqueCertificateWithPrivateKey = std::tuple<UniqueCertificate, UniqueCryptProvider>;
-// MongoDB wants RFC 2253 (LDAP) formatted DN names for auth purposes
-std::string getCertificateSubjectName(PCCERT_CONTEXT cert) {
- DWORD needed =
- CertNameToStrW(cert->dwCertEncodingType,
- &(cert->pCertInfo->Subject),
- CERT_X500_NAME_STR | CERT_NAME_STR_CRLF_FLAG | CERT_NAME_STR_REVERSE_FLAG,
- NULL,
- 0);
- uassert(
- 50753, str::stream() << "CertNameToStr size query failed with: " << needed, needed != 0);
-
- auto nameBuf = std::make_unique<wchar_t[]>(needed);
- DWORD cbConverted =
- CertNameToStrW(cert->dwCertEncodingType,
- &(cert->pCertInfo->Subject),
- CERT_X500_NAME_STR | CERT_NAME_STR_CRLF_FLAG | CERT_NAME_STR_REVERSE_FLAG,
- nameBuf.get(),
- needed);
- uassert(50754,
- str::stream() << "CertNameToStr retrieval failed with unexpected return: "
- << cbConverted,
- needed == cbConverted);
-
- // Windows converts the names as RFC 1799 (x.509) instead of RFC 2253 (LDAP)
- std::wstring str(nameBuf.get());
-
- // Windows uses "S" instead of "ST" for stateOrProvinceName (2.5.4.8) OID so we massage the
- // string here.
- boost::replace_all(str, L"\r\nS=", L",ST=");
- boost::replace_all(str, L"\r\n", L",");
-
- return toUtf8String(str.c_str());
-}
StatusWith<stdx::unordered_set<RoleName>> parsePeerRoles(PCCERT_CONTEXT cert) {
PCERT_EXTENSION extension = CertFindExtension(mongodbRolesOID.identifier.c_str(),
@@ -1390,11 +1357,95 @@ 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, "0.9.2342.19200300.100.1.1") == 0) {
+ return {"UID"};
+ }
+
+ // RFC 2253 specifies #hexstring encoding for unknown OIDs,
+ // however for backward compatibility purposes, we omit these.
+ return {ErrorCodes::InvalidSSLConfiguration, str::stream() << "Unknown OID: " << label};
+}
+
+// MongoDB wants RFC 2253 (LDAP) formatted DN names for auth purposes
+StatusWith<std::string> getCertificateSubjectName(PCCERT_CONTEXT cert) {
+
+ auto swBlob =
+ decodeObject(X509_NAME, cert->pCertInfo->Subject.pbData, cert->pCertInfo->Subject.cbData);
+
+ if (!swBlob.isOK()) {
+ return swBlob.getStatus();
+ }
+
+ PCERT_NAME_INFO nameInfo = reinterpret_cast<PCERT_NAME_INFO>(swBlob.getValue().data());
+
+ StringBuilder output;
+
+ bool addComma = false;
+
+ // 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];
+
+ DWORD needed =
+ CertRDNValueToStrW(rdnAttribute.dwValueType, &rdnAttribute.Value, NULL, 0);
+
+ std::wstring wstr;
+ wstr.resize(needed - 1);
+ DWORD converted = CertRDNValueToStrW(rdnAttribute.dwValueType,
+ &rdnAttribute.Value,
+ 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;
+ }
+ }
+
+ return output.str();
+}
+
Status SSLManagerWindows::_validateCertificate(PCCERT_CONTEXT cert,
std::string* subjectName,
Date_t* serverCertificateExpirationDate) {
- *subjectName = getCertificateSubjectName(cert);
+ auto swCert = getCertificateSubjectName(cert);
+
+ if (!swCert.isOK()) {
+ return swCert.getStatus();
+ }
+
+ *subjectName = swCert.getValue();
if (serverCertificateExpirationDate != nullptr) {
FILETIME currentTime;
@@ -1629,7 +1680,13 @@ StatusWith<boost::optional<SSLPeerInfo>> SSLManagerWindows::parseAndValidatePeer
}
}
- std::string peerSubjectName = getCertificateSubjectName(cert);
+ auto swCert = getCertificateSubjectName(cert);
+
+ if (!swCert.isOK()) {
+ return swCert.getStatus();
+ }
+
+ std::string peerSubjectName = swCert.getValue();
LOG(2) << "Accepted TLS connection from peer: " << peerSubjectName;
// On the server side, parse the certificate for roles