diff options
-rw-r--r-- | jstests/libs/README.ssl | 5 | ||||
-rw-r--r-- | jstests/libs/client_escape.pem | 48 | ||||
-rw-r--r-- | jstests/libs/client_utf8.cnf | 14 | ||||
-rw-r--r-- | jstests/libs/client_utf8.pem | 49 | ||||
-rw-r--r-- | jstests/ssl/libs/ssl_x509_role_auth_escape.js | 13 | ||||
-rw-r--r-- | jstests/ssl/libs/ssl_x509_role_auth_utf8.js | 12 | ||||
-rw-r--r-- | jstests/ssl/ssl_x509_roles.js | 57 | ||||
-rw-r--r-- | src/mongo/util/hex.cpp | 4 | ||||
-rw-r--r-- | src/mongo/util/net/ssl/impl/error.ipp | 3 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_manager.cpp | 49 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_manager.h | 7 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_manager_apple.cpp | 2 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_manager_test.cpp | 15 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_manager_windows.cpp | 127 |
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 |