summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSara Golemon <sara.golemon@mongodb.com>2020-01-13 20:38:27 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-02-04 19:03:40 +0000
commit3ca76fd569c94de72c4daf6eef27fbf9bf51233b (patch)
treedf08c2e3416475ea70867eb64673ce210ffef150
parentd3262e58c914fd0b5689069c7e8950c508cf1b4a (diff)
downloadmongo-3ca76fd569c94de72c4daf6eef27fbf9bf51233b.tar.gz
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
-rw-r--r--jstests/libs/client_roles.pem54
-rw-r--r--jstests/ssl/tlsCATrusts.js188
-rw-r--r--jstests/ssl/x509/root-and-trusted-ca.pem49
-rw-r--r--jstests/ssl/x509/trusted-client-testdb-roles.pem55
-rw-r--r--src/mongo/bson/json.cpp7
-rw-r--r--src/mongo/bson/oid.cpp2
-rw-r--r--src/mongo/client/mongo_uri.cpp6
-rw-r--r--src/mongo/crypto/sha1_block.idl7
-rw-r--r--src/mongo/crypto/sha256_block.idl7
-rw-r--r--src/mongo/crypto/sha_block.h27
-rw-r--r--src/mongo/db/auth/auth_types.idl44
-rw-r--r--src/mongo/db/auth/role_name.cpp34
-rw-r--r--src/mongo/db/auth/role_name.h9
-rw-r--r--src/mongo/db/storage/key_string_test.cpp2
-rw-r--r--src/mongo/platform/decimal128_bson_test.cpp2
-rw-r--r--src/mongo/scripting/mozjs/bindata.cpp2
-rw-r--r--src/mongo/util/hex.h47
-rw-r--r--src/mongo/util/net/SConscript5
-rw-r--r--src/mongo/util/net/ssl_manager.cpp71
-rw-r--r--src/mongo/util/net/ssl_options.h20
-rw-r--r--src/mongo/util/net/ssl_parameters.cpp151
-rw-r--r--src/mongo/util/net/ssl_parameters.idl44
-rw-r--r--src/mongo/util/uuid.cpp6
23 files changed, 816 insertions, 23 deletions
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<unsigned char>(fromHex(binDataType));
+ const auto binDataTypeSigned = uassertStatusOK(fromHex(binDataType));
+ const auto binDataTypeNumeric = static_cast<unsigned char>(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<std::string> 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<SHABlock> 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<const uint8_t*>(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<const char*>(_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
+# <http://www.mongodb.com/licensing/server-side-public-license>.
+#
+# 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 <string>
#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<BSONElement, 2> 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 <cctype>
#include <string>
#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<char> 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<char> 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<char> 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<X509_STORE_CTX, UniqueX509StoreCtxDeleter>;
+#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<TLSVersion> mapTLSVersion(const SSL* conn) {
}
}
+namespace {
+Status _validatePeerRoles(const stdx::unordered_set<RoleName>& 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<SSLPeerInfo> SSLManager::parseAndValidatePeerCertificate(
SSL* conn, const std::string& remoteHost, const HostAndPort& hostForLogging) {
auto sniName = getRawSNIServerName(conn);
@@ -1639,6 +1703,13 @@ StatusWith<SSLPeerInfo> 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 <boost/optional.hpp>
+#include <map>
+#include <set>
#include <vector>
#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<SHA256Block, std::set<RoleName>>;
+
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> tlsCATrusts; // --setParameter tlsCATrusts
+
std::vector<Protocols> sslDisabledProtocols; // --sslDisabledProtocols
std::vector<Protocols> 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
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * 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<RoleName>(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
+# <http://www.mongodb.com/licensing/server-side-public-license>.
+#
+# 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<RoleName>
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> 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)};