From 3ca76fd569c94de72c4daf6eef27fbf9bf51233b Mon Sep 17 00:00:00 2001 From: Sara Golemon Date: Mon, 13 Jan 2020 20:38:27 +0000 Subject: SERVER-44435 Allow selective whitelisting of X509 based role authorizations (cherry picked from commit b99fbe5f80f4368e1916e1bfbf3d195276ace5c7) create mode 100644 jstests/libs/client_roles.pem create mode 100644 jstests/ssl/tlsCATrusts.js create mode 100644 jstests/ssl/x509/root-and-trusted-ca.pem create mode 100644 jstests/ssl/x509/trusted-client-testdb-roles.pem create mode 100644 src/mongo/db/auth/auth_types.idl create mode 100644 src/mongo/util/net/ssl_parameters.cpp create mode 100644 src/mongo/util/net/ssl_parameters.idl --- jstests/libs/client_roles.pem | 54 +++++++ jstests/ssl/tlsCATrusts.js | 188 +++++++++++++++++++++++ jstests/ssl/x509/root-and-trusted-ca.pem | 49 ++++++ jstests/ssl/x509/trusted-client-testdb-roles.pem | 55 +++++++ src/mongo/bson/json.cpp | 7 +- src/mongo/bson/oid.cpp | 2 +- src/mongo/client/mongo_uri.cpp | 6 +- src/mongo/crypto/sha1_block.idl | 7 + src/mongo/crypto/sha256_block.idl | 7 + src/mongo/crypto/sha_block.h | 27 ++++ src/mongo/db/auth/auth_types.idl | 44 ++++++ src/mongo/db/auth/role_name.cpp | 34 ++++ src/mongo/db/auth/role_name.h | 9 ++ src/mongo/db/storage/key_string_test.cpp | 2 +- src/mongo/platform/decimal128_bson_test.cpp | 2 +- src/mongo/scripting/mozjs/bindata.cpp | 2 +- src/mongo/util/hex.h | 47 +++++- src/mongo/util/net/SConscript | 5 + src/mongo/util/net/ssl_manager.cpp | 71 +++++++++ src/mongo/util/net/ssl_options.h | 20 ++- src/mongo/util/net/ssl_parameters.cpp | 151 ++++++++++++++++++ src/mongo/util/net/ssl_parameters.idl | 44 ++++++ src/mongo/util/uuid.cpp | 6 +- 23 files changed, 816 insertions(+), 23 deletions(-) create mode 100644 jstests/libs/client_roles.pem create mode 100644 jstests/ssl/tlsCATrusts.js create mode 100644 jstests/ssl/x509/root-and-trusted-ca.pem create mode 100644 jstests/ssl/x509/trusted-client-testdb-roles.pem create mode 100644 src/mongo/db/auth/auth_types.idl create mode 100644 src/mongo/util/net/ssl_parameters.cpp create mode 100644 src/mongo/util/net/ssl_parameters.idl diff --git a/jstests/libs/client_roles.pem b/jstests/libs/client_roles.pem new file mode 100644 index 00000000000..b0ab47283d1 --- /dev/null +++ b/jstests/libs/client_roles.pem @@ -0,0 +1,54 @@ +# Autogenerated file, do not edit. +# Generate using jstests/ssl/x509/mkcert.py --config jstests/ssl/x509/certs.yml client_roles.pem +# +# General purpose client certificate with roles. +-----BEGIN CERTIFICATE----- +MIIDsTCCApkCBD/sHwgwDQYJKoZIhvcNAQELBQAwdDELMAkGA1UEBhMCVVMxETAP +BgNVBAgMCE5ldyBZb3JrMRYwFAYDVQQHDA1OZXcgWW9yayBDaXR5MRAwDgYDVQQK +DAdNb25nb0RCMQ8wDQYDVQQLDAZLZXJuZWwxFzAVBgNVBAMMDktlcm5lbCBUZXN0 +IENBMB4XDTE5MDkyNTIzMjc0MFoXDTM5MDkyNzIzMjc0MFowgYMxCzAJBgNVBAYT +AlVTMREwDwYDVQQIDAhOZXcgWW9yazEWMBQGA1UEBwwNTmV3IFlvcmsgQ2l0eTEQ +MA4GA1UECgwHTW9uZ29EQjEVMBMGA1UECwwMS2VybmVsIFVzZXJzMSAwHgYDVQQD +DBdLZXJuZWwgQ2xpZW50IFBlZXIgUm9sZTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAOUvxhWWvoP0GgTpOL0c/dj/WSm/uYQdSsgZQUuGr9OJ19iCX6mQ +1njZ4sO25MdBnSviw9/UMI80REA4nNvRxWQFRe90CDyBpqxuekpUWzhZ/sYNxKNe +WFr1Oh3DpipNnFVzS3L2pdMkY2T3RnMxJ9aHWXDY4pb1823Lp9OnTZM6ERTDlfZ0 +XJLoO9Xx3VWEemNdiw8B9gw55508XnbNTA/iqt1z/32WgvlgLplpvTnAnCf8kkpE +Qt+03yZAqSQCWtfzrwiO99hIGwglCfHQI31+Bvr88Iji85MlYvDmpQc0L3jPz5Xu +dbCo8DfHVttAbTDqlJrGyF3WPC0xKlN3e40CAwEAAaNAMD4wPAYLKwYBBAGCjikC +AQEELTErMA8MBmJhY2t1cAwFYWRtaW4wGAwPcmVhZEFueURhdGFiYXNlDAVhZG1p +bjANBgkqhkiG9w0BAQsFAAOCAQEALhDSFBz3WBTYmrLU8I8pIlRCgcRTtJWwnzZo +QsC8nilyN5MVDS+RyR6qCUoGhyQqYBFdQ8xML+m9JGUnZK7Xi+ibZ9EiDX7Q7XnZ +ROps+LzKqIipafWd9BsJk4UV1hpeAhRZSA3HnZGqBSzTbd5uS0tR+dARCEduT9JX +qz0+PziACM7xetV5uEaWcWn4df4g4Hzx8k8QXvRymktB/vIeHq+ixGe5SUzTDu59 +Sx7GLdG0MzLVrfBiYHjmoCTAOvi+FE/s1LK4txvi6j5ZNbD6hYzN5vzHWfftd6+3 +ozPqSM7WIltsShB0JujiUVY24RVafiRgfdUixmiegly1tHLDww== +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDlL8YVlr6D9BoE +6Ti9HP3Y/1kpv7mEHUrIGUFLhq/TidfYgl+pkNZ42eLDtuTHQZ0r4sPf1DCPNERA +OJzb0cVkBUXvdAg8gaasbnpKVFs4Wf7GDcSjXlha9Todw6YqTZxVc0ty9qXTJGNk +90ZzMSfWh1lw2OKW9fNty6fTp02TOhEUw5X2dFyS6DvV8d1VhHpjXYsPAfYMOeed +PF52zUwP4qrdc/99loL5YC6Zab05wJwn/JJKRELftN8mQKkkAlrX868IjvfYSBsI +JQnx0CN9fgb6/PCI4vOTJWLw5qUHNC94z8+V7nWwqPA3x1bbQG0w6pSaxshd1jwt +MSpTd3uNAgMBAAECggEBANXNlYr6T8ylYciHEYlKjH1s6O8LjrNy6Bp3hPRCN7Ct +/RBbv4ZeOdYP3X4bFp3T1h3ktDoQpyRQ7ALFTX+719sRGEbkBfL8OFLP1YGV5GtT +eVul6HVHREHGV2sA4/i+4kLNBeu/sL6iSZ8dFznK95EoPwYJLokJ9QfOX0gR4Uqf +suRG3frdWsj5KbNOsG+0A9b/vWfhzCxyUjZCiPlKju03QvuYaoZTekrJDOEddXVo +YO5WhB0AV2rd9zApEd7triX/OPrlqZafLiQzaqQCGSAHb4Ufqt8AHThh++KJesCU +vSmeNQXr/NQeHtteikg6y2c325yxVeWmEbXMzOvFGHkCgYEA9kjdJ9ZnhnsdQihB +SvofGXsK+6WsEuR3E/UZGGxFNwoDCG1mCbFZDZGAueHiXWCXY9iBFQQVZ0Zk+ZMM +Pva5+pc0vBNJ7Y9a01yrKk84/SiO+iyLXzcF98kXcHzs+Yr7+y79zIpBgE8xeTFJ +PEtfZix7CKTEEm5IxBzTgm3ixY8CgYEA7jo+VhD+3n2+hQUPKahMEHl/BMo9ebYE +PLqq3CNScUtJ+z7j77kjSSU7O8LiGe8x62356tCJRGJHCUzTC1o7A5+7aTJ1nlyN +sLSSl3DkoDazEuJLadkVBDH39Fw4MD7yCzW/1AOOdr27nvLfqORKGGti8rBJ94EC +Z6AWOgvPdyMCgYBAY4BeX/Gk+R8HItsfLRFgagjPDdtU/SIct6Gd/wCBiT6Nv3xD +4Hf10iqjbmjuHMi3s+zbf7vQyliv3z/+Xib1Wv7QhQJ491e5lqqxXxD5LtdilK7D +b/FBfPOB10dlXm7OltSH20WQtCOtGbOZ7eK9Jbfs5JbWcrrXeunZWb1GBQKBgHoI +1A36vtTyzToPEWlhkTWPR4YIo91JZHpHlWcXoQ/wZYGTAei3il/Z9rHybyih44ya +1b93/BlPztvwwtu56Sk+Fh8Zfi8Vcm/m0IFLj5KS8sDls4QRdDal8kL3SsFMPWmI +qy/McppZL1eJSxGVgG9p81InBH0JVVYIJ9qsWibJAoGACbAjCkDEFi9K4yU5Y28H +sVsy8fFpugU/dxI3lzxEKEEUyHHQN/MrNzsdLLq3U6vroO5NwCWlNGqiz+MF32D/ +XjTwuc+iwK6s9Xzchz5+5biUBuIbgCP9GED0P4GGZNLElxLNce9gzTp7MTrYA8Tm +IZ0p2RYAcbEtUXlezAP94ak= +-----END PRIVATE KEY----- diff --git a/jstests/ssl/tlsCATrusts.js b/jstests/ssl/tlsCATrusts.js new file mode 100644 index 00000000000..a96a3d76591 --- /dev/null +++ b/jstests/ssl/tlsCATrusts.js @@ -0,0 +1,188 @@ +// Test restricting role authorization via X509 extensions. + +(function() { + "use strict"; + + const SERVER_CERT = 'jstests/libs/server.pem'; + const COMBINED_CA_CERT = 'jstests/ssl/x509/root-and-trusted-ca.pem'; + const CA_HASH = '539D91F8202641BF85C0C36C88FF69F3062D4AB370CECBF9B950A8B97DE72EAE'; + const TRUSTED_CA_HASH = 'AEAEBB1BA947A7C1428D39EF6166B83409D0245D28013C9FDD71DF9E69BEA52B'; + + // Common suffix, keep the lines short. + const RDN_SUFFIX = ',O=MongoDB,L=New York City,ST=New York,C=US'; + const USERS = []; + + const CLIENT = { + cert: 'jstests/libs/client.pem', + roles: [], + }; + USERS.push('CN=client,OU=KernelUser'); + + const CLIENT_ROLES = { + cert: 'jstests/libs/client_roles.pem', + roles: [{role: 'backup', db: 'admin'}, {role: 'readAnyDatabase', db: 'admin'}], + }; + USERS.push('CN=Kernel Client Peer Role,OU=Kernel Users'); + + const TRUSTED_CLIENT_TESTDB_ROLES = { + cert: 'jstests/ssl/x509/trusted-client-testdb-roles.pem', + roles: [{role: 'role1', db: 'testDB'}, {role: 'role2', db: 'testDB'}], + }; + USERS.push('CN=Trusted Kernel Test Client With Roles,OU=Kernel Users'); + + function test(tlsCATrusts, success, failure) { + const options = { + auth: '', + sslMode: 'requireSSL', + sslPEMKeyFile: SERVER_CERT, + sslCAFile: COMBINED_CA_CERT, + }; + + if (tlsCATrusts !== null) { + options.setParameter = { + tlsCATrusts: tojson(tlsCATrusts), + }; + } + + const mongod = MongoRunner.runMongod(options); + + const admin = mongod.getDB('admin'); + admin.createUser({user: 'admin', pwd: 'pwd', roles: ['root']}); + admin.auth({user: 'admin', pwd: 'pwd'}); + + const external = mongod.getDB('$external'); + USERS.forEach((u) => external.createUser({user: u + RDN_SUFFIX, roles: []})); + + const testDB = mongod.getDB('test'); + testDB.createRole({role: 'role1', privileges: [], roles: []}); + testDB.createRole({role: 'role2', privileges: [], roles: []}); + + // Sorting JS arrays of objects with arbitrary order is... complex. + const serverTrusts = + assert.commandWorked(admin.runCommand({getParameter: 1, tlsCATrusts: 1})).tlsCATrusts; + function sortAndNormalizeRoles(roles) { + return roles.map((r) => r.role + '.' + r.db).sort().join('/'); + } + function sortAndNormalizeTrusts(trusts) { + if (trusts === null) { + return "(unconfigured)"; + } + return trusts.map((t) => t.sha256 + '/' + sortAndNormalizeRoles(t.roles)).sort(); + } + assert.eq(sortAndNormalizeTrusts(tlsCATrusts), sortAndNormalizeTrusts(serverTrusts)); + + function impl(user, expect) { + const snRoles = tojson(sortAndNormalizeRoles(user.roles)); + const uri = 'mongodb://localhost:' + mongod.port + '/admin'; + const script = tojson(sortAndNormalizeRoles) + + 'assert(db.getSiblingDB("$external").auth({mechanism: "MONGODB-X509"}));' + + 'const status = assert.commandWorked(db.runCommand({connectionStatus: 1}));' + + 'const roles = status.authInfo.authenticatedUserRoles;' + + 'assert.eq(' + snRoles + ', sortAndNormalizeRoles(roles));'; + const mongo = runMongoProgram('mongo', + '--ssl', + '--sslPEMKeyFile', + user.cert, + '--sslCAFile', + COMBINED_CA_CERT, + uri, + '--eval', + script); + expect(mongo, 0); + } + + success.forEach((u) => impl(u, assert.eq)); + failure.forEach((u) => impl(u, assert.neq)); + + MongoRunner.stopMongod(mongod); + } + + // Positive tests. + const unconfigured = null; + test(unconfigured, [CLIENT, CLIENT_ROLES, TRUSTED_CLIENT_TESTDB_ROLES], []); + + const allRoles = [ + {sha256: CA_HASH, roles: [{role: '', db: ''}]}, + {sha256: TRUSTED_CA_HASH, roles: [{role: '', db: ''}]} + ]; + test(allRoles, [CLIENT, CLIENT_ROLES, TRUSTED_CLIENT_TESTDB_ROLES], []); + + const allRolesOnAdmin = [{sha256: CA_HASH, roles: [{role: '', db: 'admin'}]}]; + test(allRolesOnAdmin, [CLIENT, CLIENT_ROLES], [TRUSTED_CLIENT_TESTDB_ROLES]); + + const specificRolesOnAnyDB = + [{sha256: CA_HASH, roles: [{role: 'backup', db: ''}, {role: 'readAnyDatabase', db: ''}]}]; + test(specificRolesOnAnyDB, [CLIENT, CLIENT_ROLES], [TRUSTED_CLIENT_TESTDB_ROLES]); + + const exactRoles = [{ + sha256: CA_HASH, + roles: [{role: 'backup', db: 'admin'}, {role: 'readAnyDatabase', db: 'admin'}] + }]; + test(exactRoles, [CLIENT, CLIENT_ROLES], [TRUSTED_CLIENT_TESTDB_ROLES]); + + const extraRoles = [{ + sha256: CA_HASH, + roles: [ + {role: 'backup', db: 'admin'}, + {role: 'readAnyDatabase', db: 'admin'}, + {role: 'readWrite', db: 'admin'} + ] + }]; + test(extraRoles, [CLIENT, CLIENT_ROLES], [TRUSTED_CLIENT_TESTDB_ROLES]); + + const similarRoles = [ + { + sha256: CA_HASH, + roles: [ + {role: 'backup', db: 'test'}, + {role: 'readAnyDatabase', db: ''}, + {role: 'backup', db: 'admin'} + ] + }, + { + sha256: TRUSTED_CA_HASH, + roles: [ + {role: 'role1', db: 'admin'}, + {role: 'role2', db: 'testDB'}, + {role: 'role1', db: 'testDB'}, + ] + } + ]; + test(similarRoles, [CLIENT, CLIENT_ROLES, TRUSTED_CLIENT_TESTDB_ROLES], []); + + const withUntrusted = + [{sha256: CA_HASH, roles: [{role: '', db: ''}]}, {sha256: TRUSTED_CA_HASH, roles: []}]; + test(withUntrusted, [CLIENT, CLIENT_ROLES], [TRUSTED_CLIENT_TESTDB_ROLES]); + + const customRoles = [{ + sha256: TRUSTED_CA_HASH, + roles: [ + {role: 'role1', db: 'testDB'}, + {role: 'role2', db: 'testDB'}, + ] + }]; + test(customRoles, [CLIENT, TRUSTED_CLIENT_TESTDB_ROLES], [CLIENT_ROLES]); + + // Negative tests. CLIENT_CERT is okay because it doesn't ask for roles. + const noTrustedCAs = []; + test(noTrustedCAs, [CLIENT], [CLIENT_ROLES, TRUSTED_CLIENT_TESTDB_ROLES]); + + const noRoles = [{sha256: CA_HASH, roles: []}]; + test(noRoles, [CLIENT], [CLIENT_ROLES, TRUSTED_CLIENT_TESTDB_ROLES]); + + const insufficientRoles1 = [ + {sha256: CA_HASH, roles: [{role: 'backup', db: ''}]}, + {sha256: TRUSTED_CA_HASH, roles: [{role: 'role1', db: 'testDB'}]} + ]; + test(insufficientRoles1, [CLIENT], [CLIENT_ROLES, TRUSTED_CLIENT_TESTDB_ROLES]); + + const insufficientRoles2 = [ + {sha256: CA_HASH, roles: [{role: 'readWriteAnyDatabase', db: ''}]}, + {sha256: TRUSTED_CA_HASH, roles: [{role: 'role2', db: 'testDB'}]} + ]; + test(insufficientRoles2, [CLIENT], [CLIENT_ROLES, TRUSTED_CLIENT_TESTDB_ROLES]); + + const withTrusted = + [{sha256: CA_HASH, roles: []}, {sha256: TRUSTED_CA_HASH, roles: [{role: '', db: ''}]}]; + test(withTrusted, [CLIENT, TRUSTED_CLIENT_TESTDB_ROLES], [CLIENT_ROLES]); +})(); diff --git a/jstests/ssl/x509/root-and-trusted-ca.pem b/jstests/ssl/x509/root-and-trusted-ca.pem new file mode 100644 index 00000000000..219ecf6397d --- /dev/null +++ b/jstests/ssl/x509/root-and-trusted-ca.pem @@ -0,0 +1,49 @@ +# Autogenerated file, do not edit. +# Generate using jstests/ssl/x509/mkcert.py --config jstests/ssl/x509/certs.yml root-and-trusted-ca.pem +# +# Combined ca.pem and trusted-ca.pem +# Certificate from ca.pem +-----BEGIN CERTIFICATE----- +MIIDdDCCAlwCBBmRIxIwDQYJKoZIhvcNAQELBQAwdDELMAkGA1UEBhMCVVMxETAP +BgNVBAgMCE5ldyBZb3JrMRYwFAYDVQQHDA1OZXcgWW9yayBDaXR5MRAwDgYDVQQK +DAdNb25nb0RCMQ8wDQYDVQQLDAZLZXJuZWwxFzAVBgNVBAMMDktlcm5lbCBUZXN0 +IENBMB4XDTE5MDkyNTIzMjczOVoXDTM5MDkyNzIzMjczOVowdDELMAkGA1UEBhMC +VVMxETAPBgNVBAgMCE5ldyBZb3JrMRYwFAYDVQQHDA1OZXcgWW9yayBDaXR5MRAw +DgYDVQQKDAdNb25nb0RCMQ8wDQYDVQQLDAZLZXJuZWwxFzAVBgNVBAMMDktlcm5l +bCBUZXN0IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAupVkx8+n +AqzsANKwNPeCYlf2q0WgF4kSUMNJdpmMelrr7hh7EOnAU0hTAQx9BKTEbExeCzH6 +OArFNGjewjWVXwaOpCjK8FMvK6/lGVEpmoHNF9XuiQVmaQ4bJD6rC73YjpgNIPeL +5PyoFLEZv+X2cRBPpTcSRcf87tk8HL7v0eyk1JBhkeKK68SYdWwZlHaa1jqwmliW +WvVMkHVH3lx0VOgQwWtOgs0K1zpcZ0sH5MGpYRQOiidIRZj3PkKeTPQe2D6VQQtv +2yDs9dWfCxJJP9QiWclL2rF/xqlFSNEIfNZpZhk6I1DHQpA2uyJfzRH62pFasJuB +CVh5Tr0EDoVreQIDAQABoxMwETAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB +CwUAA4IBAQARdNCYYWxi2fyhJwzGHwIT261d/pTlOSYLlm84c72aEneFUnfp8/H5 +JjuFbnhiX+5+h3M7eDQhra9s+H3vKr7o38EIVf5OKXvpNLwv1UUmomBvKqccioYh +bxrfwCzfBRuUmW05kcAVn8iKovqyxL7npEZbckwtT+BqZ4kOL4Uzre+S1HMx0zOu +xulSYA/sBoJ2BB93ZIAqB+f/+InS9yggzyhhaQqS7QEl1L4nZE4Oy0jKcxdCzysm +TqiyH+OI5SVRTfXh4XvHmdWBBaQyaTmQzXYUxUi7jg1jEAiebCGrEJv9plwq4KfC +cze9NLBjaXR3GzonT8kICyVT/0UvhuJg +-----END CERTIFICATE----- +# Certificate from trusted-ca.pem +-----BEGIN CERTIFICATE----- +MIIDojCCAooCBG585gswDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxETAP +BgNVBAgMCE5ldyBZb3JrMRYwFAYDVQQHDA1OZXcgWW9yayBDaXR5MRAwDgYDVQQK +DAdNb25nb0RCMQ8wDQYDVQQLDAZLZXJuZWwxHzAdBgNVBAMMFlRydXN0ZWQgS2Vy +bmVsIFRlc3QgQ0EwHhcNMTkwOTI1MjMyNzQxWhcNMzkwOTI3MjMyNzQxWjB8MQsw +CQYDVQQGEwJVUzERMA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3Jr +IENpdHkxEDAOBgNVBAoMB01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEfMB0GA1UE +AwwWVHJ1c3RlZCBLZXJuZWwgVGVzdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBANlRxtpMeCGhkotkjHQqgqvO6O6hoRoAGGJlDaTVtqrjmC8nwySz +1nAFndqUHttxS3A5j4enOabvffdOcV7+Z6vDQmREF6QZmQAk81pmazSc3wOnRiRs +AhXjld7i+rhB50CW01oYzQB50rlBFu+ONKYj32nBjD+1YN4AZ2tuRlbxfx2uf8Bo +Zowfr4n9nHVcWXBLFmaQLn+88WFO/wuwYUOn6Di1Bvtkvqum0or5QeAF0qkJxfhg +3a4vBnomPdwEXCgAGLvHlB41CWG09EuAjrnE3HPPi5vII8pjY2dKKMomOEYmA+KJ +AC1NlTWdN0TtsoaKnyhMMhLWs3eTyXL7kbkCAwEAAaMxMC8wDAYDVR0TBAUwAwEB +/zAfBgNVHREEGDAWgglsb2NhbGhvc3SCCTEyNy4wLjAuMTANBgkqhkiG9w0BAQsF +AAOCAQEAQk56MO9xAhtO077COCqIYe6pYv3uzOplqjXpJ7Cph7GXwQqdFWfKls7B +cLfF/fhIUZIu5itStEkY+AIwht4mBr1F5+hZUp9KZOed30/ewoBXAUgobLipJV66 +FKg8NRtmJbiZrrC00BSO+pKfQThU8k0zZjBmNmpjxnbKZZSFWUKtbhHV1vujver6 +SXZC7R6692vLwRBMoZxhgy/FkYRdiN0U9wpluKd63eo/O02Nt6OEMyeiyl+Z3JWi +8g5iHNrBYGBbGSnDOnqV6tjEY3eq600JDWiodpA1OQheLi78pkc/VQZwof9dyBCm +6BoCskTjip/UB+vIhdPFT9sgUdgDTg== +-----END CERTIFICATE----- diff --git a/jstests/ssl/x509/trusted-client-testdb-roles.pem b/jstests/ssl/x509/trusted-client-testdb-roles.pem new file mode 100644 index 00000000000..858ae8a773a --- /dev/null +++ b/jstests/ssl/x509/trusted-client-testdb-roles.pem @@ -0,0 +1,55 @@ +# Autogenerated file, do not edit. +# Generate using jstests/ssl/x509/mkcert.py --config jstests/ssl/x509/certs.yml trusted-client-testdb-roles.pem +# +# Client certificate with X509 role grants via trusted chain. +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIEQvQH6zANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJV +UzERMA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3JrIENpdHkxEDAO +BgNVBAoMB01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEfMB0GA1UEAwwWVHJ1c3Rl +ZCBLZXJuZWwgVGVzdCBDQTAeFw0yMDAxMDcxNzMxNDhaFw00MDAxMDkxNzMxNDha +MIGRMQswCQYDVQQGEwJVUzERMA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5l +dyBZb3JrIENpdHkxEDAOBgNVBAoMB01vbmdvREIxFTATBgNVBAsMDEtlcm5lbCBV +c2VyczEuMCwGA1UEAwwlVHJ1c3RlZCBLZXJuZWwgVGVzdCBDbGllbnQgV2l0aCBS +b2xlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPgWO/0KXHIk0KH/ +jePx+uC8M34bx8ncAWvUXKZtaNGkv2+LI0k/1U5ybOTD8kg8tnIMYkquuuG2zeIB +99vq2Ve+3j62PlqR4HDzXTt3M3eYp6muRzNn78yxVRn+eiIrdwbnvr28l3ikUaVV +/u9fsHGZOXto+I6tWSWB7MNEVcPtIu2d8XU2gMrqKfpnG0paUKVWkaKyjUX1DsBL +FUybBbjQj0zK5cUeKoZjSmMtRfqV6ngKmOK4xTBsQ2VKi7AntpALq/knAYU8BaqS +wWbVuj5sJX86tdRGGhZ6QKIODTQENPprFaJhy34qrhRkD+YHy7tQ+7vc1JpGodiu +C7/5K+kCAwEAAaM3MDUwMwYLKwYBBAGCjikCAQEEJDEiMA8MBXJvbGUxDAZ0ZXN0 +REIwDwwFcm9sZTIMBnRlc3REQjANBgkqhkiG9w0BAQsFAAOCAQEAlYR0WB/0yHxM +gvS+hjxQWyRFOJdWcFn0xresIBd4PmQO8cnOz8iuFrg8DKnYroBRFp5tR9VSLFpq +EH5xoEUMYEAGryYNp8jjOqxy6lIFUZIOf5Li0CtnnV2qHqsiq0kLpSEt+SbpGXtt +zS1CkgKwj0VMXwl+3HY73Xj6EVUPqqMf+Frc68S0ey1S7+pgr1fHzFN309tcGt4r +uxDsSAvYJcYTYDj4KaycXovUsIq+kB+E+k5DnbwJYqHErx2r86QCasK9QIE2Eujl +t+sBpj8JIObPdpsxEiQ9r1+lurWhyEB4qrtI8fzys/0yHP+EYvra3+HftHXY/t32 +jZ79J4C3YQ== +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQD4Fjv9ClxyJNCh +/43j8frgvDN+G8fJ3AFr1FymbWjRpL9viyNJP9VOcmzkw/JIPLZyDGJKrrrhts3i +Affb6tlXvt4+tj5akeBw8107dzN3mKeprkczZ+/MsVUZ/noiK3cG5769vJd4pFGl +Vf7vX7BxmTl7aPiOrVklgezDRFXD7SLtnfF1NoDK6in6ZxtKWlClVpGiso1F9Q7A +SxVMmwW40I9MyuXFHiqGY0pjLUX6lep4CpjiuMUwbENlSouwJ7aQC6v5JwGFPAWq +ksFm1bo+bCV/OrXURhoWekCiDg00BDT6axWiYct+Kq4UZA/mB8u7UPu73NSaRqHY +rgu/+SvpAgMBAAECggEALVaa5fajyHRz8HcsrjDF4ZZjbrOTApADbnpj6EJseou6 +NJ9f9n4E9I4y2mf4+jymNxeOSwm9u4xV+ezUKEu2JrQKF7nkkVbBhsLjEgAJ1tx+ +H6Nq/bkL+QObguGf3mjFGuz1TeWOZQzaovWhXovFSi1vdN9NNX32ocUpyNHPPrvW +nc3Gun/hws4qWwBFpR+8fzMHPJc/NCwZpDRoJl0yXEAkGTKtIEGEJ7tlWLSb5Bz2 +5N0Dkn2S3t8uozPIuv0rjdYd1t+FfOUUAZGI09LCIu9ndBYO6Vj+Vh99xedZ2oBa +9lHQp3vhLaCXg7O3bY3ac9BIOwdAqcbWAJV/oQl5gQKBgQD90geToF81xZYgLZoU +iU8RRUSmdurZtNirDkMU+/u5Fwu7yn8M53l++TPP76Hi1yGP9803LwIXNXfyg+sb +BRAPJg+bJ8N6m1vFdfg509oqlrzoxnmulwBshqt5HbpiOjYAc1cSOpYSXGJjHoFL ++Au4MRsfDh5RhT1zrUT11+6ZEQKBgQD6N5nGaPOsLHdYXk2//yZF1Ol9kl3L0VWM +XT0F9m/KSCg1kSf+2XCt1U/b1JsrjMTOZWVHNV3yebPs9/pR2ffmyeXtySFuEVeb +ZVNSxaCSVVbTJL9W+mpXdqzcTj9IL9tMN6J5PE8eQ6pjG299sBmdj5S92a3uoxQr +5RmGn36lWQKBgQCix4XQaXNmGteSv2wna3/nxZKnZ3BqOo8R9M2UsZ3YMC14O/+L +GRBUHCHcYwRhZDLED9nuYBlpJQNN5shqxa5s6K3thWzaPrR2SJfvDizGT3HLny3+ +iBzffOaPgD8+K7LiSxY2PJhuIg1/H9swC14IvIV2Pym2gkrM2vx05gzA4QKBgQCW +FmngEK4xVY7U6+Q5SYQcmSThVL18d3mYM4laHUNbE8NCtmpGPQmQzAYV98aH7e1T +XJDOkN1kh8n8V5bIKDXCMtL/ugiabD6fkLzVRoQVoqjtB/rZ4mWNRztS/oCI/WPO +qQSFMj7HCZGX1yoeO1ZyI2D2LC9fmGSOG+Me1Gb0KQKBgQCZazY6Wb7HPO50HnN3 +e3QrT9VE1PKLW6dWpokdYzq2ISnX8ZBeKvMBX+TpKASduNVXK5shsuNqjMAeXtVk +V90P2QkgswCoUlgiaxKby7jBqDIO9CsLt0erQ328WUsf9mgk18CmCc42EWBPuQv7 +WTykB3JVLPGKjKcZVI4PP91yAw== +-----END PRIVATE KEY----- diff --git a/src/mongo/bson/json.cpp b/src/mongo/bson/json.cpp index 55ac254fa09..8f6653f3b29 100644 --- a/src/mongo/bson/json.cpp +++ b/src/mongo/bson/json.cpp @@ -403,7 +403,8 @@ Status JParse::binaryObject(StringData fieldName, BSONObjBuilder& builder) { // unsigned char. If we don't coerce it to an unsigned char before // wrapping it in a BinDataType (currently implicitly a signed // integer), we get undefined behavior. - const auto binDataTypeNumeric = static_cast(fromHex(binDataType)); + const auto binDataTypeSigned = uassertStatusOK(fromHex(binDataType)); + const auto binDataTypeNumeric = static_cast(binDataTypeSigned); builder.appendBinData( fieldName, binData.length(), BinDataType(binDataTypeNumeric), binData.data()); @@ -1153,8 +1154,8 @@ Status JParse::chars(std::string* result, const char* terminalSet, const char* a if (!isHexString(StringData(q, 4))) { return parseError("Expecting 4 hex digits"); } - unsigned char first = fromHex(q); - unsigned char second = fromHex(q += 2); + unsigned char first = uassertStatusOK(fromHex(q)); + unsigned char second = uassertStatusOK(fromHex(q += 2)); const std::string& utf8str = encodeUTF8(first, second); for (unsigned int i = 0; i < utf8str.size(); i++) { result->push_back(utf8str[i]); diff --git a/src/mongo/bson/oid.cpp b/src/mongo/bson/oid.cpp index 55e5cffdf21..b309f59639c 100644 --- a/src/mongo/bson/oid.cpp +++ b/src/mongo/bson/oid.cpp @@ -154,7 +154,7 @@ void OID::init(const std::string& s) { verify(s.size() == 24); const char* p = s.c_str(); for (std::size_t i = 0; i < kOIDSize; i++) { - _data[i] = fromHex(p); + _data[i] = uassertStatusOK(fromHex(p)); p += 2; } } diff --git a/src/mongo/client/mongo_uri.cpp b/src/mongo/client/mongo_uri.cpp index f85ea5482a3..6ddd6f7b940 100644 --- a/src/mongo/client/mongo_uri.cpp +++ b/src/mongo/client/mongo_uri.cpp @@ -92,7 +92,11 @@ mongo::StatusWith mongo::uriDecode(StringData toDecode) { return Status(ErrorCodes::FailedToParse, "Encountered partial escape sequence at end of string"); } - out << fromHex(toDecode.substr(i + 1, 2)); + auto swHex = fromHex(toDecode.substr(i + 1, 2)); + if (!swHex.isOK()) { + return swHex.getStatus(); + } + out << swHex.getValue(); i += 2; } else { out << c; diff --git a/src/mongo/crypto/sha1_block.idl b/src/mongo/crypto/sha1_block.idl index d6054d9e3f6..59ddbbeca61 100644 --- a/src/mongo/crypto/sha1_block.idl +++ b/src/mongo/crypto/sha1_block.idl @@ -44,3 +44,10 @@ types: cpp_type: mongo::SHA1Block serializer: "mongo::SHA1Block::toCDR" deserializer: "mongo::SHA1Block::fromBinData" + + sha1BlockHex: + bson_serialization_type: string + description: "A fixed size hex string representing a SHA1 computation" + cpp_type: mongo::SHA1Block + serializer: "mongo::SHA1Block::toHexString" + deserializer: "mongo::SHA1Block::fromHexString" diff --git a/src/mongo/crypto/sha256_block.idl b/src/mongo/crypto/sha256_block.idl index 734983f286a..a5b4a1cef41 100644 --- a/src/mongo/crypto/sha256_block.idl +++ b/src/mongo/crypto/sha256_block.idl @@ -44,3 +44,10 @@ types: cpp_type: mongo::SHA256Block serializer: "mongo::SHA256Block::toCDR" deserializer: "mongo::SHA256Block::fromBinData" + + sha256BlockHex: + bson_serialization_type: string + description: "A fixed size hex string representing a SHA256 computation" + cpp_type: mongo::SHA256Block + serializer: "mongo::SHA256Block::toHexString" + deserializer: "mongo::SHA256Block::fromHexString" diff --git a/src/mongo/crypto/sha_block.h b/src/mongo/crypto/sha_block.h index 2860fe7fb1d..267d71cb163 100644 --- a/src/mongo/crypto/sha_block.h +++ b/src/mongo/crypto/sha_block.h @@ -40,7 +40,9 @@ #include "mongo/base/status_with.h" #include "mongo/bson/bsonmisc.h" #include "mongo/bson/bsonobjbuilder.h" +#include "mongo/bson/util/builder.h" #include "mongo/util/base64.h" +#include "mongo/util/hex.h" #include "mongo/util/secure_compare_memory.h" namespace mongo { @@ -77,6 +79,20 @@ public: return SHABlock(newHash); } + static StatusWith fromHexStringNoThrow(StringData hex) { + if (!isValidHex(hex)) { + return {ErrorCodes::BadValue, "Hash input is not a hex string"}; + } + + BufBuilder buf; + mongo::fromHexString(hex, &buf); + return fromBuffer(reinterpret_cast(buf.buf()), buf.len()); + } + + static SHABlock fromHexString(StringData hex) { + return uassertStatusOK(fromHexStringNoThrow(hex)); + } + /** * Computes a hash of 'input' from multiple contigous buffers. */ @@ -187,6 +203,13 @@ public: return base64::encode(reinterpret_cast(_hash.data()), _hash.size()); } + /** + * Hex encoded hash block. + */ + std::string toHexString() const { + return toHex(_hash.data(), _hash.size()); + } + bool operator==(const SHABlock& other) const { return consttimeMemEqual(this->_hash.data(), other._hash.data(), kHashLength); } @@ -195,6 +218,10 @@ public: return !(*this == other); } + bool operator<(const SHABlock& other) const { + return this->_hash < other._hash; + } + /** * Custom hasher so SHABlocks can be used in unordered data structures. * diff --git a/src/mongo/db/auth/auth_types.idl b/src/mongo/db/auth/auth_types.idl new file mode 100644 index 00000000000..aae786f5b53 --- /dev/null +++ b/src/mongo/db/auth/auth_types.idl @@ -0,0 +1,44 @@ +# Copyright (C) 2020-present MongoDB, Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the Server Side Public License, version 1, +# as published by MongoDB, Inc. +# +# 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 +# Server Side Public License for more details. +# +# You should have received a copy of the Server Side 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 Server Side 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. +# + +global: + cpp_namespace: "mongo" + cpp_includes: + - "mongo/db/auth/role_name.h" + +imports: + - "mongo/idl/basic_types.idl" + +types: + + RoleName: + bson_serialization_type: any + description: "A struct representing a Role" + cpp_type: "RoleName" + deserializer: "mongo::RoleName::parseFromBSON" + serializer: "mongo::RoleName::serializeToBSON" diff --git a/src/mongo/db/auth/role_name.cpp b/src/mongo/db/auth/role_name.cpp index d05c623463a..d3c16e771bb 100644 --- a/src/mongo/db/auth/role_name.cpp +++ b/src/mongo/db/auth/role_name.cpp @@ -35,6 +35,7 @@ #include #include "mongo/base/string_data.h" +#include "mongo/db/auth/authorization_manager.h" #include "mongo/util/assert_util.h" namespace mongo { @@ -54,4 +55,37 @@ std::ostream& operator<<(std::ostream& os, const RoleName& name) { return os << name.getFullName(); } +RoleName RoleName::parseFromBSON(const BSONElement& elem) { + auto obj = elem.embeddedObjectUserCheck(); + std::array fields; + obj.getFields({"role", "db"}, &fields); + const auto& nameField = fields[0]; + uassert(ErrorCodes::BadValue, + "user name must contain a string field named: role", + nameField.type() == String); + + const auto& dbField = fields[1]; + uassert(ErrorCodes::BadValue, + "role name must contain a string field named: db", + nameField.type() == String); + + return RoleName(nameField.valueStringData(), dbField.valueStringData()); +} + +void RoleName::serializeToBSON(StringData fieldName, BSONObjBuilder* bob) const { + BSONObjBuilder sub(bob->subobjStart(fieldName)); + _serializeToSubObj(&sub); +} + +void RoleName::serializeToBSON(BSONArrayBuilder* bob) const { + BSONObjBuilder sub(bob->subobjStart()); + _serializeToSubObj(&sub); +} + +void RoleName::_serializeToSubObj(BSONObjBuilder* sub) const { + sub->append("role", getRole()); + sub->append("db", getDB()); +} + + } // namespace mongo diff --git a/src/mongo/db/auth/role_name.h b/src/mongo/db/auth/role_name.h index 99fdeee9489..d335998c9e0 100644 --- a/src/mongo/db/auth/role_name.h +++ b/src/mongo/db/auth/role_name.h @@ -38,6 +38,8 @@ #include "mongo/base/disallow_copying.h" #include "mongo/base/string_data.h" +#include "mongo/bson/bsonelement.h" +#include "mongo/bson/bsonobjbuilder.h" #include "mongo/platform/hash_namespace.h" #include "mongo/util/assert_util.h" @@ -53,6 +55,11 @@ public: RoleName() : _splitPoint(0) {} RoleName(StringData role, StringData dbname); + // Added for IDL support + static RoleName parseFromBSON(const BSONElement& elem); + void serializeToBSON(StringData fieldName, BSONObjBuilder* bob) const; + void serializeToBSON(BSONArrayBuilder* bob) const; + /** * Gets the name of the role excluding the "@dbname" component. */ @@ -90,6 +97,8 @@ public: private: std::string _fullName; // The full name, stored as a string. "role@db". size_t _splitPoint; // The index of the "@" separating the role and db name parts. + + void _serializeToSubObj(BSONObjBuilder* sub) const; }; static inline bool operator==(const RoleName& lhs, const RoleName& rhs) { diff --git a/src/mongo/db/storage/key_string_test.cpp b/src/mongo/db/storage/key_string_test.cpp index 6ac51bfd191..444608e2477 100644 --- a/src/mongo/db/storage/key_string_test.cpp +++ b/src/mongo/db/storage/key_string_test.cpp @@ -167,7 +167,7 @@ TEST_F(KeyStringTest, ActualBytesDouble) { // last byte (kEnd) doesn't get flipped string hexFlipped; for (size_t i = 0; i < hex.size() - 2; i += 2) { - char c = fromHex(hex.c_str() + i); + char c = uassertStatusOK(fromHex(hex.c_str() + i)); c = ~c; hexFlipped += toHex(&c, 1); } diff --git a/src/mongo/platform/decimal128_bson_test.cpp b/src/mongo/platform/decimal128_bson_test.cpp index aac4ab22283..24bbef73836 100644 --- a/src/mongo/platform/decimal128_bson_test.cpp +++ b/src/mongo/platform/decimal128_bson_test.cpp @@ -58,7 +58,7 @@ BSONObj convertHexStringToBsonObj(StringData hexString) { auto buffer = SharedBuffer::allocate(bufferSize); for (unsigned int i = 0; i < bufferSize; i++) { - buffer.get()[i] = fromHex(p); + buffer.get()[i] = uassertStatusOK(fromHex(p)); p += 2; } diff --git a/src/mongo/scripting/mozjs/bindata.cpp b/src/mongo/scripting/mozjs/bindata.cpp index 26d2371609f..ddf5df2ba1d 100644 --- a/src/mongo/scripting/mozjs/bindata.cpp +++ b/src/mongo/scripting/mozjs/bindata.cpp @@ -88,7 +88,7 @@ void hexToBinData(JSContext* cx, int src_index = i * 2; if (!std::isxdigit(src[src_index]) || !std::isxdigit(src[src_index + 1])) uasserted(ErrorCodes::BadValue, "Invalid hex character in string"); - data[i] = fromHex(src + src_index); + data[i] = uassertStatusOK(fromHex(src + src_index)); } std::string encoded = base64::encode(data.get(), len); diff --git a/src/mongo/util/hex.h b/src/mongo/util/hex.h index 322d02d5be3..6a89388a245 100644 --- a/src/mongo/util/hex.h +++ b/src/mongo/util/hex.h @@ -32,28 +32,61 @@ #pragma once +#include #include #include "mongo/base/string_data.h" #include "mongo/bson/util/builder.h" +#include "mongo/util/mongoutils/str.h" namespace mongo { // can't use hex namespace because it conflicts with hex iostream function -inline int fromHex(char c) { +inline StatusWith fromHex(char c) { if ('0' <= c && c <= '9') return c - '0'; if ('a' <= c && c <= 'f') return c - 'a' + 10; if ('A' <= c && c <= 'F') return c - 'A' + 10; - verify(false); - return 0xff; + return Status(ErrorCodes::FailedToParse, + str::stream() << "The character " << c << " failed to parse from hex."); } -inline char fromHex(const char* c) { - return (char)((fromHex(c[0]) << 4) | fromHex(c[1])); +inline StatusWith fromHex(const char* c) { + if (fromHex(c[0]).isOK() && fromHex(c[1]).isOK()) { + return (char)((fromHex(c[0]).getValue() << 4) | fromHex(c[1]).getValue()); + } + return Status(ErrorCodes::FailedToParse, + str::stream() << "The character " << c[0] << c[1] + << " failed to parse from hex."); +} +inline StatusWith fromHex(StringData c) { + if (fromHex(c[0]).isOK() && fromHex(c[1]).isOK()) { + return (char)((fromHex(c[0]).getValue() << 4) | fromHex(c[1]).getValue()); + } + return Status(ErrorCodes::FailedToParse, + str::stream() << "The character " << c[0] << c[1] + << " failed to parse from hex."); } -inline char fromHex(StringData c) { - return (char)((fromHex(c[0]) << 4) | fromHex(c[1])); + +/** + * Decodes 'hexString' into raw bytes, appended to the out parameter 'buf'. Callers must first + * ensure that 'hexString' is a valid hex encoding. + */ +inline void fromHexString(StringData hexString, BufBuilder* buf) { + invariant(hexString.size() % 2 == 0); + // Combine every pair of two characters into one byte. + for (std::size_t i = 0; i < hexString.size(); i += 2) { + buf->appendChar(uassertStatusOK(fromHex(StringData(&hexString.rawData()[i], 2)))); + } +} + +/** + * Returns true if 'hexString' is a valid hexadecimal encoding. + */ +inline bool isValidHex(StringData hexString) { + // There must be an even number of characters, since each pair encodes a single byte. + return hexString.size() % 2 == 0 && + std::all_of(hexString.begin(), hexString.end(), [](char c) { return std::isxdigit(c); }); } inline std::string toHex(const void* inRaw, int len) { diff --git a/src/mongo/util/net/SConscript b/src/mongo/util/net/SConscript index c120b65990f..b80beed6dc6 100644 --- a/src/mongo/util/net/SConscript +++ b/src/mongo/util/net/SConscript @@ -22,7 +22,9 @@ env.Library( "socket_exception.cpp", "ssl_manager.cpp", "ssl_options.cpp", + "ssl_parameters.cpp", "thread_idle_callback.cpp", + env.Idlc('ssl_parameters.idl')[0], ], LIBDEPS=[ '$BUILD_DIR/mongo/base', @@ -33,6 +35,9 @@ env.Library( LIBDEPS_PRIVATE=[ '$BUILD_DIR/mongo/db/bson/dotted_path_support', '$BUILD_DIR/mongo/db/server_options_core', + '$BUILD_DIR/mongo/db/server_parameters', + '$BUILD_DIR/mongo/crypto/sha256_block', + '$BUILD_DIR/mongo/idl/idl_parser', '$BUILD_DIR/mongo/util/background_job', '$BUILD_DIR/mongo/util/fail_point', '$BUILD_DIR/mongo/util/options_parser/options_parser', diff --git a/src/mongo/util/net/ssl_manager.cpp b/src/mongo/util/net/ssl_manager.cpp index 70854fd0a50..3ad394a05f1 100644 --- a/src/mongo/util/net/ssl_manager.cpp +++ b/src/mongo/util/net/ssl_manager.cpp @@ -88,6 +88,7 @@ std::string removeFQDNRoot(std::string name) { return name; }; +#ifdef MONGO_CONFIG_SSL struct UniqueX509StoreCtxDeleter { void operator()(X509_STORE_CTX* ctx) { if (ctx) { @@ -96,6 +97,7 @@ struct UniqueX509StoreCtxDeleter { } }; using UniqueX509StoreCtx = std::unique_ptr; +#endif // Because the hostname having a slash is used by `mongo::SockAddr` to determine if a hostname is a // Unix Domain Socket endpoint, this function uses the same logic. (See @@ -1582,6 +1584,68 @@ StatusWith mapTLSVersion(const SSL* conn) { } } +namespace { +Status _validatePeerRoles(const stdx::unordered_set& embeddedRoles, SSL* conn) { + if (embeddedRoles.empty()) { + // Nothing offered, nothing to restrict. + return Status::OK(); + } + + if (!sslGlobalParams.tlsCATrusts) { + // Nothing restricted. + return Status::OK(); + } + + const auto& tlsCATrusts = sslGlobalParams.tlsCATrusts.get(); + if (tlsCATrusts.empty()) { + // Nothing permitted. + return {ErrorCodes::BadValue, + "tlsCATrusts parameter prohibits role based authorization via X509 certificates"}; + } + + auto stack = SSLgetVerifiedChain(conn); + if (!stack || !sk_X509_num(stack.get())) { + return {ErrorCodes::BadValue, "Unable to obtain certificate chain"}; + } + + auto root = sk_X509_value(stack.get(), sk_X509_num(stack.get()) - 1); + SHA256Block::HashType digest; + if (!X509_digest(root, EVP_sha256(), digest.data(), nullptr)) { + return {ErrorCodes::BadValue, "Unable to digest root certificate"}; + } + + SHA256Block sha256(digest); + auto it = tlsCATrusts.find(sha256); + if (it == tlsCATrusts.end()) { + return { + ErrorCodes::BadValue, + str::stream() << "CA: " << sha256.toHexString() + << " is not authorized to grant any roles due to tlsCATrusts parameter"}; + } + + auto allowedRoles = it->second; + // See TLSCATrustsSetParameter::set() for a description of tlsCATrusts format. + if (allowedRoles.count(RoleName("", ""))) { + // CA is authorized for all role assignments. + return Status::OK(); + } + + for (const auto& role : embeddedRoles) { + // Check for exact match or wildcard matches. + if (!allowedRoles.count(role) && !allowedRoles.count(RoleName(role.getRole(), "")) && + !allowedRoles.count(RoleName("", role.getDB()))) { + return {ErrorCodes::BadValue, + str::stream() << "CA: " << sha256.toHexString() + << " is not authorized to grant role " + << role.toString() + << " due to tlsCATrusts parameter"}; + } + } + + return Status::OK(); +} +} // namespace + StatusWith SSLManager::parseAndValidatePeerCertificate( SSL* conn, const std::string& remoteHost, const HostAndPort& hostForLogging) { auto sniName = getRawSNIServerName(conn); @@ -1639,6 +1703,13 @@ StatusWith SSLManager::parseAndValidatePeerCertificate( return swPeerCertificateRoles.getStatus(); } + { + auto status = _validatePeerRoles(swPeerCertificateRoles.getValue(), conn); + if (!status.isOK()) { + return status; + } + } + // If this is an SSL client context (on a MongoDB server or client) // perform hostname validation of the remote server if (remoteHost.empty()) { diff --git a/src/mongo/util/net/ssl_options.h b/src/mongo/util/net/ssl_options.h index 5684e37636e..c95a3e728ad 100644 --- a/src/mongo/util/net/ssl_options.h +++ b/src/mongo/util/net/ssl_options.h @@ -32,9 +32,14 @@ #include "mongo/util/net/ssl_manager.h" +#include +#include +#include #include #include "mongo/base/status.h" +#include "mongo/crypto/sha256_block.h" +#include "mongo/db/auth/role_name.h" namespace mongo { @@ -44,6 +49,8 @@ class Environment; } // namespace optionenvironment struct SSLParams { + using TLSCATrusts = std::map>; + enum class Protocols { TLS1_0, TLS1_1, TLS1_2, TLS1_3 }; AtomicInt32 sslMode; // --sslMode - the TLS operation mode, see enum SSLModes std::string sslPEMTempDHParam; // --setParameter OpenSSLDiffieHellmanParameters=file : PEM file @@ -51,11 +58,14 @@ struct SSLParams { std::string sslPEMKeyFile; // --sslPEMKeyFile std::string sslPEMKeyPassword; // --sslPEMKeyPassword std::string sslClusterFile; // --sslInternalKeyFile - std::string sslClusterPassword; // --sslInternalKeyPassword - std::string sslCAFile; // --sslCAFile - std::string sslClusterCAFile; // --sslClusterCAFile - std::string sslCRLFile; // --sslCRLFile - std::string sslCipherConfig; // --sslCipherConfig + std::string sslClusterPassword; // --sslInternalKeyPassword + std::string sslCAFile; // --sslCAFile + std::string sslClusterCAFile; // --sslClusterCAFile + std::string sslCRLFile; // --sslCRLFile + std::string sslCipherConfig; // --sslCipherConfig + + boost::optional tlsCATrusts; // --setParameter tlsCATrusts + std::vector sslDisabledProtocols; // --sslDisabledProtocols std::vector tlsLogVersions; // --tlsLogVersion bool sslWeakCertificateValidation = false; // --sslWeakCertificateValidation diff --git a/src/mongo/util/net/ssl_parameters.cpp b/src/mongo/util/net/ssl_parameters.cpp new file mode 100644 index 00000000000..ed1d4c64fe8 --- /dev/null +++ b/src/mongo/util/net/ssl_parameters.cpp @@ -0,0 +1,151 @@ + +/** + * Copyright (C) 2018-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * 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 + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side 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 Server Side 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. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/bson/json.h" +#include "mongo/config.h" +#include "mongo/db/server_options.h" +#include "mongo/db/server_parameters.h" +#include "mongo/util/net/ssl_manager.h" +#include "mongo/util/net/ssl_options.h" +#include "mongo/util/net/ssl_parameters_gen.h" + +namespace mongo { + +namespace { + +class TLSCATrustsSetParameter : public ServerParameter { +public: + TLSCATrustsSetParameter() + : ServerParameter(ServerParameterSet::getGlobal(), + "tlsCATrusts", + true, // allowedToChangeAtStartup + false // allowedToChangeAtRuntime + ) {} + + void append(OperationContext*, BSONObjBuilder&, const std::string&) final; + Status set(const BSONElement&) final; + Status setFromString(const std::string&) final; +} tlsCATrustsSetParameter; + +void TLSCATrustsSetParameter::append(OperationContext*, + BSONObjBuilder& b, + const std::string& name) { + if (!sslGlobalParams.tlsCATrusts) { + b.appendNull(name); + return; + } + + BSONArrayBuilder trusts; + + for (const auto& cait : sslGlobalParams.tlsCATrusts.get()) { + BSONArrayBuilder roles; + + for (const auto& rolename : cait.second) { + BSONObjBuilder role; + role.append("role", rolename.getRole()); + role.append("db", rolename.getDB()); + roles.append(role.obj()); + } + + BSONObjBuilder ca; + ca.append("sha256", cait.first.toHexString()); + ca.append("roles", roles.arr()); + + trusts.append(ca.obj()); + } + + b.append(name, trusts.arr()); +} + +/** + * tlsCATrusts takes the form of an array of documents describing + * a set of roles which a given certificate authority may grant. + * + * [ + * { + * "sha256": "0123456789abcdef...", // SHA256 digest of a CA, as hex. + * "roles": [ // Array of grantable RoleNames + * { role: "read", db: "foo" }, + * { role: "readWrite", "db: "bar" }, + * // etc... + * ], + * }, + * // { "sha256": "...", roles: [...]}, // Additional documents... + * ] + * + * If this list has been set, and a client connects with a certificate + * containing roles which it has not been authorized to grant, + * then the connection will be refused. + * + * Wilcard roles may be defined by omitting the role and/or db portions: + * + * { role: "", db: "foo" } // May grant any role on the 'foo' DB. + * { role: "read", db: "" } // May grant 'read' role on any DB. + * { role: "", db: "" } // May grant any role on any DB. + */ +Status TLSCATrustsSetParameter::set(const BSONElement& element) try { + if ((element.type() != Object) || !element.Obj().couldBeArray()) { + return {ErrorCodes::BadValue, "Value must be an array"}; + } + + SSLParams::TLSCATrusts trusts; + for (const auto& trustElement : BSONArray(element.Obj())) { + if (trustElement.type() != Object) { + return {ErrorCodes::BadValue, "Value must be an array of trust definitions"}; + } + + IDLParserErrorContext ctx("tlsCATrusts"); + auto trust = TLSCATrust::parse(ctx, trustElement.Obj()); + + if (trusts.find(trust.getSha256()) != trusts.end()) { + return {ErrorCodes::BadValue, + str::stream() << "Duplicate thumbprint: " << trust.getSha256().toString()}; + } + + const auto& roles = trust.getRoles(); + trusts[std::move(trust.getSha256())] = std::set(roles.begin(), roles.end()); + } + + sslGlobalParams.tlsCATrusts = std::move(trusts); + return Status::OK(); +} catch (...) { + return exceptionToStatus(); +} + +Status TLSCATrustsSetParameter::setFromString(const std::string& json) try { + return set(BSON("" << fromjson(json)).firstElement()); +} catch (...) { + return exceptionToStatus(); +} + +} // namespace +} // namespace mongo diff --git a/src/mongo/util/net/ssl_parameters.idl b/src/mongo/util/net/ssl_parameters.idl new file mode 100644 index 00000000000..21d376a7bc2 --- /dev/null +++ b/src/mongo/util/net/ssl_parameters.idl @@ -0,0 +1,44 @@ +# Copyright (C) 2020-present MongoDB, Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the Server Side Public License, version 1, +# as published by MongoDB, Inc. +# +# 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 +# Server Side Public License for more details. +# +# You should have received a copy of the Server Side 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 Server Side 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. +# + +global: + cpp_namespace: "mongo" + cpp_includes: + - "mongo/util/net/ssl_options.h" + +imports: + - "mongo/crypto/sha256_block.idl" + - "mongo/db/auth/auth_types.idl" + +structs: + TLSCATrust: + description: + strict: true + fields: + sha256: sha256BlockHex + roles: array diff --git a/src/mongo/util/uuid.cpp b/src/mongo/util/uuid.cpp index 208688ddcce..99109a630d7 100644 --- a/src/mongo/util/uuid.cpp +++ b/src/mongo/util/uuid.cpp @@ -74,10 +74,10 @@ StatusWith UUID::parse(const std::string& s) { if (s[j] == '-') j++; - char high = s[j++]; - char low = s[j++]; + auto high = uassertStatusOK(fromHex(s[j++])); + auto low = uassertStatusOK(fromHex(s[j++])); - uuid[i] = ((fromHex(high) << 4) | fromHex(low)); + uuid[i] = ((high << 4) | low); } return UUID{std::move(uuid)}; -- cgit v1.2.1