diff options
author | Sara Golemon <sara.golemon@mongodb.com> | 2018-02-20 12:39:37 -0500 |
---|---|---|
committer | Sara Golemon <sara.golemon@mongodb.com> | 2018-03-17 12:45:29 -0400 |
commit | 65191bbe5c00bb419a7466fb7db43e220035f776 (patch) | |
tree | 1f2c00082b35eafbcdaa1539ed680ac84949a640 | |
parent | 9eea82ee2a0e37da90cbb549d55c5eac8aa69a55 (diff) | |
download | mongo-65191bbe5c00bb419a7466fb7db43e220035f776.tar.gz |
SERVER-22412 Implement a secure transport ASIO backend
24 files changed, 2173 insertions, 265 deletions
diff --git a/SConstruct b/SConstruct index fbbf8f433bb..fcd14983ed4 100644 --- a/SConstruct +++ b/SConstruct @@ -2901,11 +2901,13 @@ def doConfigure(myenv): conf.env.Append( MONGO_CRYPTO=["windows"] ) elif conf.env.TargetOSIs('darwin', 'macOS'): + ssl_provider = 'apple' + env.SetConfigHeaderDefine("MONGO_CONFIG_SSL_PROVIDER", "SSL_PROVIDER_APPLE") conf.env.Append( MONGO_CRYPTO=["apple"] ) - if has_option("ssl"): - # TODO: Replace SSL implementation as well. - # For now, let openssl fill that role. - checkOpenSSL(conf) + conf.env.AppendUnique(FRAMEWORKS=[ + 'CoreFoundation', + 'Security', + ]) if ssl_provider == 'openssl': if has_option("ssl"): diff --git a/jstests/ssl/dh_params.js b/jstests/ssl/dh_params.js index 2da7867aff9..852fc8c311b 100644 --- a/jstests/ssl/dh_params.js +++ b/jstests/ssl/dh_params.js @@ -1,9 +1,8 @@ -(function() { +load("jstests/ssl/libs/ssl_helpers.js"); +requireSSLProvider('openssl', function() { "use strict"; - load("jstests/ssl/libs/ssl_helpers.js"); - // Verify that requireSSL with Diffie-Hellman parameters allows ssl connections print("=== Testing that DHParams files can be loaded ==="); replShouldSucceed("dhparam-dhparam", dhparamSSL, dhparamSSL); -}()); +}); diff --git a/jstests/ssl/libs/ssl_helpers.js b/jstests/ssl/libs/ssl_helpers.js index 23703683aa4..05d0a5b9333 100644 --- a/jstests/ssl/libs/ssl_helpers.js +++ b/jstests/ssl/libs/ssl_helpers.js @@ -176,3 +176,34 @@ function mixedShardTest(options1, options2, shouldSucceed) { } } } + +function determineSSLProvider() { + 'use strict'; + const info = getBuildInfo(); + const ssl = (info.openssl === undefined) ? '' : info.openssl.running; + if (/OpenSSL/.test(ssl)) { + return 'openssl'; + } else if (/Apple/.test(ssl)) { + return 'apple'; + } else if (/Windows/.test(ssl)) { + return 'windows'; + } else { + return null; + } +} + +function requireSSLProvider(required, fn) { + 'use strict'; + if ((typeof required) === 'string') { + required = [required]; + } + + const provider = determineSSLProvider(); + if (!required.includes(provider)) { + print("*****************************************************"); + print("Skipping " + tojson(required) + " test because SSL provider is " + provider); + print("*****************************************************"); + return; + } + fn(); +} diff --git a/jstests/ssl/ssl_cert_password.js b/jstests/ssl/ssl_cert_password.js index 2595510ff4a..356c0603c84 100644 --- a/jstests/ssl/ssl_cert_password.js +++ b/jstests/ssl/ssl_cert_password.js @@ -4,172 +4,179 @@ // does not return error statuses to indicate an error. // This test requires ssl support in mongo-tools // @tags: [requires_ssl_mongo_tools] -var baseName = "jstests_ssl_ssl_cert_password"; -var dbpath = MongoRunner.dataPath + baseName; -var external_scratch_dir = MongoRunner.dataPath + baseName + "/external/"; -resetDbpath(dbpath); -mkdir(external_scratch_dir); - -// Password is correct -var md = MongoRunner.runMongod({ - nopreallocj: "", - dbpath: dbpath, - sslMode: "requireSSL", - sslPEMKeyFile: "jstests/libs/password_protected.pem", - sslPEMKeyPassword: "qwerty" -}); -// MongoRunner.runMongod connects a Mongo shell, so if we get here, the test is successful. - -// Password incorrect; error logged is: -// error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt -var exit_code = runMongoProgram("mongo", - "--port", - md.port, - "--ssl", - "--sslAllowInvalidCertificates", - "--sslCAFile", - "jstests/libs/ca.pem", - "--sslPEMKeyFile", - "jstests/libs/password_protected.pem", - "--sslPEMKeyPassword", - "barf"); - -// 1 is the exit code for failure -assert(exit_code == 1); - -// Test that mongodump and mongorestore support ssl -c = md.getDB("dumprestore_ssl").getCollection("foo"); -assert.eq(0, c.count(), "dumprestore_ssl.foo collection is not initially empty"); -c.save({a: 22}); -assert.eq(1, c.count(), "failed to insert document into dumprestore_ssl.foo collection"); - -exit_code = MongoRunner.runMongoTool("mongodump", { - out: external_scratch_dir, - port: md.port, - ssl: "", - sslPEMKeyFile: "jstests/libs/password_protected.pem", - sslCAFile: "jstests/libs/ca.pem", - sslPEMKeyPassword: "qwerty", -}); - -assert.eq(exit_code, 0, "Failed to start mongodump with ssl"); - -c.drop(); -assert.eq(0, c.count(), "dumprestore_ssl.foo collection is not empty after drop"); - -exit_code = MongoRunner.runMongoTool("mongorestore", { - dir: external_scratch_dir, - port: md.port, - ssl: "", - sslCAFile: "jstests/libs/ca.pem", - sslPEMKeyFile: "jstests/libs/password_protected.pem", - sslPEMKeyPassword: "qwerty", -}); -assert.eq(exit_code, 0, "Failed to start mongorestore with ssl"); - -assert.soon("c.findOne()", "no data after sleep. Expected a document after calling mongorestore"); -assert.eq(1, - c.count(), - "did not find expected document in dumprestore_ssl.foo collection after mongorestore"); -assert.eq(22, c.findOne().a, "did not find correct value in document after mongorestore"); - -// Test that mongoimport and mongoexport support ssl -var exportimport_ssl_dbname = "exportimport_ssl"; -c = md.getDB(exportimport_ssl_dbname).getCollection("foo"); -assert.eq(0, c.count(), "exportimport_ssl.foo collection is not initially empty"); -c.save({a: 22}); -assert.eq(1, c.count(), "failed to insert document into exportimport_ssl.foo collection"); - -var exportimport_file = "data.json"; - -exit_code = MongoRunner.runMongoTool("mongoexport", { - out: external_scratch_dir + exportimport_file, - db: exportimport_ssl_dbname, - collection: "foo", - port: md.port, - ssl: "", - sslCAFile: "jstests/libs/ca.pem", - sslPEMKeyFile: "jstests/libs/password_protected.pem", - sslPEMKeyPassword: "qwerty", +load('jstests/ssl/libs/ssl_helpers.js'); +requireSSLProvider('openssl', function() { + var baseName = "jstests_ssl_ssl_cert_password"; + var dbpath = MongoRunner.dataPath + baseName; + var external_scratch_dir = MongoRunner.dataPath + baseName + "/external/"; + resetDbpath(dbpath); + mkdir(external_scratch_dir); + + // Password is correct + var md = MongoRunner.runMongod({ + nopreallocj: "", + dbpath: dbpath, + sslMode: "requireSSL", + sslPEMKeyFile: "jstests/libs/password_protected.pem", + sslPEMKeyPassword: "qwerty" + }); + // MongoRunner.runMongod connects a Mongo shell, so if we get here, the test is successful. + + // Password incorrect; error logged is: + // error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt + var exit_code = runMongoProgram("mongo", + "--port", + md.port, + "--ssl", + "--sslAllowInvalidCertificates", + "--sslCAFile", + "jstests/libs/ca.pem", + "--sslPEMKeyFile", + "jstests/libs/password_protected.pem", + "--sslPEMKeyPassword", + "barf"); + + // 1 is the exit code for failure + assert(exit_code == 1); + + // Test that mongodump and mongorestore support ssl + c = md.getDB("dumprestore_ssl").getCollection("foo"); + assert.eq(0, c.count(), "dumprestore_ssl.foo collection is not initially empty"); + c.save({a: 22}); + assert.eq(1, c.count(), "failed to insert document into dumprestore_ssl.foo collection"); + + exit_code = MongoRunner.runMongoTool("mongodump", { + out: external_scratch_dir, + port: md.port, + ssl: "", + sslPEMKeyFile: "jstests/libs/password_protected.pem", + sslCAFile: "jstests/libs/ca.pem", + sslPEMKeyPassword: "qwerty", + }); + + assert.eq(exit_code, 0, "Failed to start mongodump with ssl"); + + c.drop(); + assert.eq(0, c.count(), "dumprestore_ssl.foo collection is not empty after drop"); + + exit_code = MongoRunner.runMongoTool("mongorestore", { + dir: external_scratch_dir, + port: md.port, + ssl: "", + sslCAFile: "jstests/libs/ca.pem", + sslPEMKeyFile: "jstests/libs/password_protected.pem", + sslPEMKeyPassword: "qwerty", + }); + + assert.eq(exit_code, 0, "Failed to start mongorestore with ssl"); + + assert.soon("c.findOne()", + "no data after sleep. Expected a document after calling mongorestore"); + assert.eq( + 1, + c.count(), + "did not find expected document in dumprestore_ssl.foo collection after mongorestore"); + assert.eq(22, c.findOne().a, "did not find correct value in document after mongorestore"); + + // Test that mongoimport and mongoexport support ssl + var exportimport_ssl_dbname = "exportimport_ssl"; + c = md.getDB(exportimport_ssl_dbname).getCollection("foo"); + assert.eq(0, c.count(), "exportimport_ssl.foo collection is not initially empty"); + c.save({a: 22}); + assert.eq(1, c.count(), "failed to insert document into exportimport_ssl.foo collection"); + + var exportimport_file = "data.json"; + + exit_code = MongoRunner.runMongoTool("mongoexport", { + out: external_scratch_dir + exportimport_file, + db: exportimport_ssl_dbname, + collection: "foo", + port: md.port, + ssl: "", + sslCAFile: "jstests/libs/ca.pem", + sslPEMKeyFile: "jstests/libs/password_protected.pem", + sslPEMKeyPassword: "qwerty", + }); + + assert.eq(exit_code, 0, "Failed to start mongoexport with ssl"); + + c.drop(); + assert.eq(0, c.count(), "afterdrop", "-d", exportimport_ssl_dbname, "-c", "foo"); + + exit_code = MongoRunner.runMongoTool("mongoimport", { + file: external_scratch_dir + exportimport_file, + db: exportimport_ssl_dbname, + collection: "foo", + port: md.port, + ssl: "", + sslCAFile: "jstests/libs/ca.pem", + sslPEMKeyFile: "jstests/libs/password_protected.pem", + sslPEMKeyPassword: "qwerty", + }); + + assert.eq(exit_code, 0, "Failed to start mongoimport with ssl"); + + assert.soon("c.findOne()", + "no data after sleep. Expected a document after calling mongoimport"); + assert.eq(1, + c.count(), + "did not find expected document in dumprestore_ssl.foo collection after mongoimport"); + assert.eq(22, c.findOne().a, "did not find correct value in document after mongoimport"); + + // Test that mongofiles supports ssl + var mongofiles_ssl_dbname = "mongofiles_ssl"; + mongofiles_db = md.getDB(mongofiles_ssl_dbname); + + source_filename = 'jstests/ssl/ssl_cert_password.js'; + filename = 'ssl_cert_password.js'; + + exit_code = MongoRunner.runMongoTool("mongofiles", + { + db: mongofiles_ssl_dbname, + port: md.port, + ssl: "", + sslCAFile: "jstests/libs/ca.pem", + sslPEMKeyFile: "jstests/libs/password_protected.pem", + sslPEMKeyPassword: "qwerty", + }, + "put", + source_filename); + + assert.eq(exit_code, 0, "Failed to start mongofiles with ssl"); + + md5 = md5sumFile(source_filename); + + file_obj = mongofiles_db.fs.files.findOne(); + assert(file_obj, "failed to find file object in mongofiles_ssl db using gridfs"); + md5_stored = file_obj.md5; + md5_computed = mongofiles_db.runCommand({filemd5: file_obj._id}).md5; + assert.eq(md5, md5_stored, "md5 incorrect for file"); + assert.eq(md5, md5_computed, "md5 computed incorrectly by server"); + + exit_code = MongoRunner.runMongoTool("mongofiles", + { + db: mongofiles_ssl_dbname, + local: external_scratch_dir + filename, + port: md.port, + ssl: "", + sslCAFile: "jstests/libs/ca.pem", + sslPEMKeyFile: "jstests/libs/password_protected.pem", + sslPEMKeyPassword: "qwerty", + }, + "get", + source_filename); + + assert.eq(exit_code, 0, "Failed to start mongofiles with ssl"); + + md5 = md5sumFile(external_scratch_dir + filename); + assert.eq(md5, md5_stored, "hash of stored file does not match the expected value"); + + if (!_isWindows()) { + // Stop the server + var exitCode = MongoRunner.stopMongod(md); + assert(exitCode == 0); + } else { + MongoRunner.stopMongod(md); + } }); - -assert.eq(exit_code, 0, "Failed to start mongoexport with ssl"); - -c.drop(); -assert.eq(0, c.count(), "afterdrop", "-d", exportimport_ssl_dbname, "-c", "foo"); - -exit_code = MongoRunner.runMongoTool("mongoimport", { - file: external_scratch_dir + exportimport_file, - db: exportimport_ssl_dbname, - collection: "foo", - port: md.port, - ssl: "", - sslCAFile: "jstests/libs/ca.pem", - sslPEMKeyFile: "jstests/libs/password_protected.pem", - sslPEMKeyPassword: "qwerty", -}); - -assert.eq(exit_code, 0, "Failed to start mongoimport with ssl"); - -assert.soon("c.findOne()", "no data after sleep. Expected a document after calling mongoimport"); -assert.eq(1, - c.count(), - "did not find expected document in dumprestore_ssl.foo collection after mongoimport"); -assert.eq(22, c.findOne().a, "did not find correct value in document after mongoimport"); - -// Test that mongofiles supports ssl -var mongofiles_ssl_dbname = "mongofiles_ssl"; -mongofiles_db = md.getDB(mongofiles_ssl_dbname); - -source_filename = 'jstests/ssl/ssl_cert_password.js'; -filename = 'ssl_cert_password.js'; - -exit_code = MongoRunner.runMongoTool("mongofiles", - { - db: mongofiles_ssl_dbname, - port: md.port, - ssl: "", - sslCAFile: "jstests/libs/ca.pem", - sslPEMKeyFile: "jstests/libs/password_protected.pem", - sslPEMKeyPassword: "qwerty", - }, - "put", - source_filename); - -assert.eq(exit_code, 0, "Failed to start mongofiles with ssl"); - -md5 = md5sumFile(source_filename); - -file_obj = mongofiles_db.fs.files.findOne(); -assert(file_obj, "failed to find file object in mongofiles_ssl db using gridfs"); -md5_stored = file_obj.md5; -md5_computed = mongofiles_db.runCommand({filemd5: file_obj._id}).md5; -assert.eq(md5, md5_stored, "md5 incorrect for file"); -assert.eq(md5, md5_computed, "md5 computed incorrectly by server"); - -exit_code = MongoRunner.runMongoTool("mongofiles", - { - db: mongofiles_ssl_dbname, - local: external_scratch_dir + filename, - port: md.port, - ssl: "", - sslCAFile: "jstests/libs/ca.pem", - sslPEMKeyFile: "jstests/libs/password_protected.pem", - sslPEMKeyPassword: "qwerty", - }, - "get", - source_filename); - -assert.eq(exit_code, 0, "Failed to start mongofiles with ssl"); - -md5 = md5sumFile(external_scratch_dir + filename); -assert.eq(md5, md5_stored, "hash of stored file does not match the expected value"); - -if (!_isWindows()) { - // Stop the server - var exitCode = MongoRunner.stopMongod(md); - assert(exitCode == 0); -} else { - MongoRunner.stopMongod(md); -} diff --git a/jstests/ssl/ssl_crl.js b/jstests/ssl/ssl_crl.js index 9f70ba91b2b..a01a2726f06 100644 --- a/jstests/ssl/ssl_crl.js +++ b/jstests/ssl/ssl_crl.js @@ -4,16 +4,19 @@ // Note: crl_expired.pem is a CRL with no revoked certificates, but is an expired CRL. // crl.pem is a CRL with no revoked certificates. -load("jstests/libs/ssl_test.js"); +load('jstests/ssl/libs/ssl_helpers.js'); +requireSSLProvider(['openssl', 'windows'], function() { + load("jstests/libs/ssl_test.js"); -var testUnrevoked = new SSLTest( - // Server option overrides - {sslMode: "requireSSL", sslCRLFile: "jstests/libs/crl.pem"}); + var testUnrevoked = new SSLTest( + // Server option overrides + {sslMode: "requireSSL", sslCRLFile: "jstests/libs/crl.pem"}); -assert(testUnrevoked.connectWorked()); + assert(testUnrevoked.connectWorked()); -var testRevoked = new SSLTest( - // Server option overrides - {sslMode: "requireSSL", sslCRLFile: "jstests/libs/crl_expired.pem"}); + var testRevoked = new SSLTest( + // Server option overrides + {sslMode: "requireSSL", sslCRLFile: "jstests/libs/crl_expired.pem"}); -assert(!testRevoked.connectWorked()); + assert(!testRevoked.connectWorked()); +}); diff --git a/jstests/ssl/ssl_crl_revoked.js b/jstests/ssl/ssl_crl_revoked.js index 1fe4f558f2a..1dab6b0ae70 100644 --- a/jstests/ssl/ssl_crl_revoked.js +++ b/jstests/ssl/ssl_crl_revoked.js @@ -2,24 +2,28 @@ // Note: crl_client_revoked.pem is a CRL with the client.pem certificate listed as revoked. // This test should test that the user cannot connect with client.pem certificate. -var md = MongoRunner.runMongod({ - sslMode: "requireSSL", - sslPEMKeyFile: "jstests/libs/server.pem", - sslCAFile: "jstests/libs/ca.pem", - sslCRLFile: "jstests/libs/crl_client_revoked.pem" -}); +load('jstests/ssl/libs/ssl_helpers.js'); + +requireSSLProvider(['openssl', 'windows'], function() { + var md = MongoRunner.runMongod({ + sslMode: "requireSSL", + sslPEMKeyFile: "jstests/libs/server.pem", + sslCAFile: "jstests/libs/ca.pem", + sslCRLFile: "jstests/libs/crl_client_revoked.pem" + }); -var mongo = runMongoProgram("mongo", - "--port", - md.port, - "--ssl", - "--sslAllowInvalidCertificates", - "--sslPEMKeyFile", - "jstests/libs/client_revoked.pem", - "--eval", - ";"); + var mongo = runMongoProgram("mongo", + "--port", + md.port, + "--ssl", + "--sslAllowInvalidCertificates", + "--sslPEMKeyFile", + "jstests/libs/client_revoked.pem", + "--eval", + ";"); -// 1 is the exit code for the shell failing to connect, which is what we want -// for a successful test. -assert(mongo == 1); -MongoRunner.stopMongod(md);
\ No newline at end of file + // 1 is the exit code for the shell failing to connect, which is what we want + // for a successful test. + assert(mongo == 1); + MongoRunner.stopMongod(md); +}); diff --git a/jstests/ssl/ssl_options.js b/jstests/ssl/ssl_options.js index d1c0c04adcd..9a85142804a 100644 --- a/jstests/ssl/ssl_options.js +++ b/jstests/ssl/ssl_options.js @@ -1,41 +1,49 @@ -var baseName = "jstests_ssl_ssl_options"; - -jsTest.log("Testing censorship of ssl options"); - -var mongodConfig = { - sslPEMKeyFile: "jstests/libs/password_protected.pem", - sslMode: "requireSSL", - sslPEMKeyPassword: "qwerty", - sslClusterPassword: "qwerty", - sslCAFile: "jstests/libs/ca.pem" -}; -var mongodSource = MongoRunner.runMongod(mongodConfig); - -var getCmdLineOptsResult = mongodSource.adminCommand("getCmdLineOpts"); - -var i; -var isPassword = false; -for (i = 0; i < getCmdLineOptsResult.argv.length; i++) { - if (isPassword) { - assert.eq(getCmdLineOptsResult.argv[i], - "<password>", - "Password not properly censored: " + tojson(getCmdLineOptsResult)); - isPassword = false; - continue; - } +// Test redaction of passwords in command line SSL option parsing. + +load('jstests/ssl/libs/ssl_helpers.js'); +requireSSLProvider('openssl', function() { + 'use strict'; + + const baseName = "jstests_ssl_ssl_options"; + + jsTest.log("Testing censorship of ssl options"); - if (getCmdLineOptsResult.argv[i] === "--sslPEMKeyPassword" || - getCmdLineOptsResult.argv[i] === "--sslClusterPassword") { - isPassword = true; + const mongodConfig = { + sslPEMKeyFile: "jstests/libs/password_protected.pem", + sslMode: "requireSSL", + sslPEMKeyPassword: "qwerty", + sslClusterPassword: "qwerty", + sslCAFile: "jstests/libs/ca.pem" + }; + const mongodSource = MongoRunner.runMongod(mongodConfig); + + const getCmdLineOptsResult = mongodSource.adminCommand("getCmdLineOpts"); + + let i; + let isPassword = false; + for (i = 0; i < getCmdLineOptsResult.argv.length; i++) { + if (isPassword) { + assert.eq(getCmdLineOptsResult.argv[i], + "<password>", + "Password not properly censored: " + tojson(getCmdLineOptsResult)); + isPassword = false; + continue; + } + + if (getCmdLineOptsResult.argv[i] === "--sslPEMKeyPassword" || + getCmdLineOptsResult.argv[i] === "--sslClusterPassword") { + isPassword = true; + } } -} -assert.eq(getCmdLineOptsResult.parsed.net.ssl.PEMKeyPassword, - "<password>", - "Password not properly censored: " + tojson(getCmdLineOptsResult)); -assert.eq(getCmdLineOptsResult.parsed.net.ssl.clusterPassword, - "<password>", - "Password not properly censored: " + tojson(getCmdLineOptsResult)); -MongoRunner.stopMongod(mongodSource); + assert.eq(getCmdLineOptsResult.parsed.net.ssl.PEMKeyPassword, + "<password>", + "Password not properly censored: " + tojson(getCmdLineOptsResult)); + assert.eq(getCmdLineOptsResult.parsed.net.ssl.clusterPassword, + "<password>", + "Password not properly censored: " + tojson(getCmdLineOptsResult)); + + MongoRunner.stopMongod(mongodSource); -print(baseName + " succeeded."); + print(baseName + " succeeded."); +}); diff --git a/jstests/sslSpecial/SERVER-26369.js b/jstests/sslSpecial/SERVER-26369.js index 065f646c3fc..e2852189822 100644 --- a/jstests/sslSpecial/SERVER-26369.js +++ b/jstests/sslSpecial/SERVER-26369.js @@ -6,15 +6,19 @@ TestData.skipCheckingUUIDsConsistentAcrossCluster = true; load("jstests/ssl/libs/ssl_helpers.js"); - var st = new ShardingTest({shards: {rs0: {nodes: 1}}}); - - st.rs0.restart(0, { + const st = new ShardingTest({shards: {rs0: {nodes: 1}}}); + let opts = { sslMode: "allowSSL", - sslPEMKeyFile: "jstests/libs/password_protected.pem", - sslPEMKeyPassword: "qwerty", + sslPEMKeyFile: "jstests/libs/client.pem", sslCAFile: "jstests/libs/ca.pem", shardsvr: '' + }; + requireSSLProvider('openssl', function() { + // Only the OpenSSL provider supports encrypted PKCS#8 + opts.sslPEMKeyFile = "jstests/libs/password_protected.pem"; + opts.sslPEMKeyPassword = "qwerty"; }); + st.rs0.restart(0, opts); st.stop(); })(); diff --git a/src/mongo/config.h.in b/src/mongo/config.h.in index 5808477e11f..39a6541ce73 100644 --- a/src/mongo/config.h.in +++ b/src/mongo/config.h.in @@ -31,6 +31,7 @@ // List of possible SSL providers #define SSL_PROVIDER_OPENSSL 1 #define SSL_PROVIDER_WINDOWS 2 +#define SSL_PROVIDER_APPLE 3 // Define to target byte order (1234 vs 4321) @mongo_config_byte_order@ diff --git a/src/mongo/util/net/ssl/apple.hpp b/src/mongo/util/net/ssl/apple.hpp new file mode 100644 index 00000000000..e38e2b46c97 --- /dev/null +++ b/src/mongo/util/net/ssl/apple.hpp @@ -0,0 +1,98 @@ +/** + * Copyright (C) 2018 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * 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 GNU Affero General 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. + */ + +#pragma once + +#if MONGO_CONFIG_SSL_PROVIDER == SSL_PROVIDER_APPLE + +#include <CoreFoundation/CoreFoundation.h> +#include <Security/Security.h> +#include <memory> +#include <string> + +namespace asio { +namespace ssl { +namespace apple { + +namespace { +template <typename T> +struct CFReleaser { + void operator()(T ptr) { + if (ptr) { + ::CFRelease(ptr); + } + } +}; +} // namespace + +/** + * CoreFoundation types are internally refcounted using CFRetain/CFRelease. + * Values received from a method using the word "Copy" typically follow "The Copy Rule" + * which requires that the caller explicitly invoke CFRelease on the obtained value. + * Values received from a method using the word "Get" typically follow "The Get Rule" + * which requires that the caller DOES NOT attempt to release any references, + * though it may invoke CFRetain to hold on to the object for longer. + * + * Use of the CFUniquePtr type assumes that a value was wither obtained from a "Copy" + * method, or that it has been explicitly retained. + */ +template <typename T> +using CFUniquePtr = std::unique_ptr<typename std::remove_pointer<T>::type, CFReleaser<T>>; + +/** + * Equivalent of OpenSSL's SSL_CTX type. + * Allows loading SecIdentity and SecCertificate chains + * separate from an SSLContext instance. + * + * Unlike OpenSSL, Secure Transport sets protocol range on + * each connection instance separately, so just stash them aside + * in the same place for now. + */ +struct Context { + Context() = default; + explicit Context(::SSLProtocol p) : protoMin(p), protoMax(p) {} + Context& operator=(const Context& src) { + protoMin = src.protoMin; + protoMax = src.protoMax; + if (src.certs) { + ::CFRetain(src.certs.get()); + } + certs.reset(src.certs.get()); + return *this; + } + + ::SSLProtocol protoMin = kTLSProtocol1; + ::SSLProtocol protoMax = kTLSProtocol12; + CFUniquePtr<::CFArrayRef> certs; +}; + +} // namespace apple +} // namespace ssl +} // namespace asio + +#endif diff --git a/src/mongo/util/net/ssl/context.hpp b/src/mongo/util/net/ssl/context.hpp index 278530f5916..2a23b8454c8 100644 --- a/src/mongo/util/net/ssl/context.hpp +++ b/src/mongo/util/net/ssl/context.hpp @@ -34,6 +34,10 @@ #include "mongo/util/net/ssl/context_openssl.hpp" +#elif MONGO_CONFIG_SSL_PROVIDER == SSL_PROVIDER_APPLE + +#include "mongo/util/net/ssl/context_apple.hpp" + #else #error "Unknown SSL Provider" #endif diff --git a/src/mongo/util/net/ssl/context_apple.hpp b/src/mongo/util/net/ssl/context_apple.hpp new file mode 100644 index 00000000000..3fd7f7eabde --- /dev/null +++ b/src/mongo/util/net/ssl/context_apple.hpp @@ -0,0 +1,82 @@ +/** + * Copyright 2018 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * 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 GNU Affero General 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. + */ + +#pragma once + +#include "asio/detail/config.hpp" +#include "asio/detail/noncopyable.hpp" +#include "mongo/util/net/ssl/apple.hpp" +#include "mongo/util/net/ssl/context_base.hpp" + +#include "asio/detail/push_options.hpp" + +namespace asio { +namespace ssl { + +class context : public context_base, private noncopyable { +public: + using native_handle_type = apple::Context*; + + ASIO_DECL explicit context(method m) { + _context.protoMin = _mapProto(m); + _context.protoMax = _context.protoMax; + } + + ASIO_DECL context(context&& other) = default; + ASIO_DECL context& operator=(context&& other) = default; + + ASIO_DECL native_handle_type native_handle() { + return &_context; + } + +private: + static ::SSLProtocol _mapProto(method m) { + switch (m) { + case context::tlsv1: + case context::tlsv1_client: + case context::tlsv1_server: + return ::kTLSProtocol1; + case context::tlsv11: + case context::tlsv11_client: + case context::tlsv11_server: + return ::kTLSProtocol11; + case context::tlsv12: + case context::tlsv12_client: + case context::tlsv12_server: + default: + return ::kTLSProtocol12; + } + } + + apple::Context _context; +}; + +} // namespace ssl +} // namespace asio + +#include "asio/detail/pop_options.hpp" diff --git a/src/mongo/util/net/ssl/detail/engine.hpp b/src/mongo/util/net/ssl/detail/engine.hpp index 80fc21c5daf..9beb6724b7e 100644 --- a/src/mongo/util/net/ssl/detail/engine.hpp +++ b/src/mongo/util/net/ssl/detail/engine.hpp @@ -34,6 +34,10 @@ #include "mongo/util/net/ssl/detail/engine_openssl.hpp" +#elif MONGO_CONFIG_SSL_PROVIDER == SSL_PROVIDER_APPLE + +#include "mongo/util/net/ssl/detail/engine_apple.hpp" + #else #error "Unknown SSL Provider" #endif diff --git a/src/mongo/util/net/ssl/detail/engine_apple.hpp b/src/mongo/util/net/ssl/detail/engine_apple.hpp new file mode 100644 index 00000000000..69b027ceaa2 --- /dev/null +++ b/src/mongo/util/net/ssl/detail/engine_apple.hpp @@ -0,0 +1,103 @@ +/** + * Copyright 2018 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * 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 GNU Affero General 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. + */ + +#pragma once + +#include <deque> + +#include "asio/buffer.hpp" +#include "asio/detail/config.hpp" +#include "mongo/util/net/ssl/apple.hpp" +#include "mongo/util/net/ssl/context_apple.hpp" +#include "mongo/util/net/ssl/stream_base.hpp" + +#include "asio/detail/push_options.hpp" + +namespace asio { +namespace ssl { +namespace detail { + +class engine { +public: + using native_handle_type = ::SSLContextRef; + enum want { + want_input_and_retry = -2, + want_output_and_retry = -1, + want_nothing = 0, + want_output = 1 + }; + + ASIO_DECL explicit engine(context::native_handle_type context); + + ASIO_DECL native_handle_type native_handle() { + return _ssl.get(); + } + + ASIO_DECL want handshake(stream_base::handshake_type type, asio::error_code& ec); + + ASIO_DECL want shutdown(asio::error_code& ec); + + ASIO_DECL want write(const asio::const_buffer& data, + asio::error_code& ec, + std::size_t& bytes_transferred); + + ASIO_DECL want read(const asio::mutable_buffer& data, + asio::error_code& ec, + std::size_t& bytes_transferred); + + ASIO_DECL asio::mutable_buffer get_output(const asio::mutable_buffer& data); + + ASIO_DECL asio::const_buffer put_input(const asio::const_buffer& data); + + ASIO_DECL const asio::error_code& map_error_code(asio::error_code& ec) const; + +private: + engine(const engine&) = delete; + engine& operator=(const engine&) = delete; + bool _initSSL(stream_base::handshake_type type, asio::error_code& ec); + static ::OSStatus read_func(::SSLConnectionRef ctx, void* data, size_t* data_len); + static ::OSStatus write_func(::SSLConnectionRef ctx, const void* data, size_t* data_len); + want wouldBlock() const; + + apple::CFUniquePtr<native_handle_type> _ssl; + apple::CFUniquePtr<::CFArrayRef> _certs; + apple::CFUniquePtr<::CFArrayRef> _ca; + ::SSLProtocol _protoMin, _protoMax; + std::deque<char> _inbuf; + std::deque<char> _outbuf; +}; + +} // namespace detail +} // namespace ssl +} // namespace asio + +#include "asio/detail/pop_options.hpp" + +#if defined(ASIO_HEADER_ONLY) +#include "mongo/util/net/ssl/detail/impl/engine_apple.ipp" +#endif // defined(ASIO_HEADER_ONLY) diff --git a/src/mongo/util/net/ssl/detail/impl/engine_apple.ipp b/src/mongo/util/net/ssl/detail/impl/engine_apple.ipp new file mode 100644 index 00000000000..25abd1a70bb --- /dev/null +++ b/src/mongo/util/net/ssl/detail/impl/engine_apple.ipp @@ -0,0 +1,347 @@ +/** + * Copyright (C) 2018 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * 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 GNU Affero General 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. + */ + +#pragma once + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kNetwork + +#include "asio/detail/config.hpp" + +#include "asio/detail/push_options.hpp" +#include "asio/detail/throw_error.hpp" +#include "asio/error.hpp" + +#include "mongo/util/log.h" +#include "mongo/util/mongoutils/str.h" +#include "mongo/util/net/ssl/apple.hpp" +#include "mongo/util/net/ssl/detail/engine.hpp" +#include "mongo/util/net/ssl/error.hpp" + +namespace asio { +namespace ssl { +namespace detail { + +namespace { + +std::ostringstream& operator<<(std::ostringstream& ss, ::OSStatus status) { + apple::CFUniquePtr<::CFStringRef> errstr(::SecCopyErrorMessageString(status, nullptr)); + if (!errstr) { + ss << "Unknown Error: " << static_cast<int>(status); + return ss; + } + const auto len = ::CFStringGetMaximumSizeForEncoding(::CFStringGetLength(errstr.get()), + ::kCFStringEncodingUTF8); + std::string ret; + ret.resize(len + 1); + if (!::CFStringGetCString(errstr.get(), &ret[0], len, ::kCFStringEncodingUTF8)) { + ss << "Unknown Error: " << static_cast<int>(status); + return ss; + } + + ret.resize(strlen(ret.c_str())); + ss << ret; + return ss; +} + +const class osstatus_category : public error_category { +public: + const char* name() const noexcept final { + return "Secure.Transport"; + } + + std::string message(int value) const noexcept final { + const auto status = static_cast<::OSStatus>(value); + return mongo::str::stream() << "Secure.Transport: " << status; + } +} OSStatus_category; + +asio::error_code errorCode(::OSStatus status) { + return asio::error_code(static_cast<int>(status), OSStatus_category); +} + +/** + * Verify that an SSL session is ready for I/O (state: Connected). + * In all other states, asio should be speaking to the socket directly. + */ +bool verifyConnected(::SSLContextRef ssl, asio::error_code* ec) { + auto state = ::kSSLAborted; + auto status = ::SSLGetSessionState(ssl, &state); + if (status != ::errSecSuccess) { + // Unable to determine session state. + *ec = errorCode(status); + return false; + } + switch (state) { + case ::kSSLIdle: + *ec = asio::error::not_connected; + return false; + case ::kSSLHandshake: + *ec = asio::error::in_progress; + return false; + case ::kSSLConnected: + return true; + case ::kSSLClosed: + *ec = asio::error::shut_down; + return false; + case ::kSSLAborted: + *ec = asio::error::connection_aborted; + return false; + default: + // Undefined state, call it an internal error. + *ec = errorCode(::errSSLInternal); + return false; + } +} + +} // namespace + +engine::engine(context::native_handle_type context) { + if (context) { + if (context->certs) { + ::CFRetain(context->certs.get()); + _certs.reset(context->certs.get()); + } + _protoMin = context->protoMin; + _protoMax = context->protoMax; + } else { + apple::Context def; + _protoMin = def.protoMin; + _protoMax = def.protoMax; + } +} + +bool engine::_initSSL(stream_base::handshake_type type, asio::error_code& ec) { + if (_ssl) { + return true; + } + + const auto side = (type == stream_base::client) ? ::kSSLClientSide : ::kSSLServerSide; + _ssl.reset(::SSLCreateContext(nullptr, side, ::kSSLStreamType)); + if (!_ssl) { + mongo::error() << "Failed allocating SSLContext"; + ec = errorCode(::errSSLInternal); + return false; + } + + auto status = ::SSLSetConnection(_ssl.get(), static_cast<void*>(this)); + + // TODO: ::SSLSetPeerDomainName() + + if (_certs && (status == ::errSecSuccess)) { + status = ::SSLSetCertificate(_ssl.get(), _certs.get()); + } + + if (status == ::errSecSuccess) { + status = ::SSLSetPeerID(_ssl.get(), _ssl.get(), sizeof(native_handle_type)); + } + + if (status == ::errSecSuccess) { + status = ::SSLSetIOFuncs(_ssl.get(), read_func, write_func); + } + + if (status == ::errSecSuccess) { + status = ::SSLSetProtocolVersionMin(_ssl.get(), _protoMin); + } + + if (status == ::errSecSuccess) { + status = ::SSLSetProtocolVersionMax(_ssl.get(), _protoMax); + } + + if (status == ::errSecSuccess) { + status = ::SSLSetClientSideAuthenticate(_ssl.get(), ::kTryAuthenticate); + } + + if (status == ::errSecSuccess) { + status = ::SSLSetSessionOption(_ssl.get(), ::kSSLSessionOptionBreakOnServerAuth, true); + } + + if (status == ::errSecSuccess) { + status = ::SSLSetSessionOption(_ssl.get(), ::kSSLSessionOptionBreakOnClientAuth, true); + } + + if (status != ::errSecSuccess) { + _ssl.reset(nullptr); + ec = errorCode(status); + return false; + } + + return true; +} + +engine::want engine::handshake(stream_base::handshake_type type, asio::error_code& ec) { + if (!_initSSL(type, ec)) { + // Error happened, ec has been set. + return want::want_nothing; + } + + // We use BreakOnClientAuth and BreakOnServerAuth above to + // convince the OS not to validate the certs for us. + // In practice, we'll be validating the peer in ssl_manager_apple.cpp later. + // As a side effect, we have to call SSLHandshake up to three times. + // Breaking once for client auth, then for server auth, and finally on completion. + ::OSStatus status; + do { + status = ::SSLHandshake(_ssl.get()); + } while ((status == ::errSSLServerAuthCompleted) || (status == ::errSSLClientAuthCompleted)); + + if (status == ::errSSLWouldBlock) { + return wouldBlock(); + } + + if (status != ::errSecSuccess) { + _ssl.reset(nullptr); + ec = errorCode(status); + return want::want_nothing; + } + + return _outbuf.size() ? want::want_output : want::want_nothing; +} + +engine::want engine::shutdown(asio::error_code& ec) { + if (_ssl) { + const auto status = ::SSLClose(_ssl.get()); + if (status == ::errSSLWouldBlock) { + return wouldBlock(); + } + if (status == ::errSecSuccess) { + _ssl.reset(nullptr); + } else { + ec = errorCode(status); + } + } else { + mongo::error() << "SSL connection already shut down"; + ec = errorCode(::errSSLInternal); + } + return want::want_nothing; +} + +const asio::error_code& engine::map_error_code(asio::error_code& ec) const { + if (ec != asio::error::eof) { + return ec; + } + + if (_inbuf.size() || _outbuf.size()) { + ec = asio::ssl::error::stream_truncated; + return ec; + } + + invariant(_ssl); + auto state = ::kSSLAborted; + const auto status = ::SSLGetSessionState(_ssl.get(), &state); + if (status != ::errSecSuccess) { + ec = errorCode(status); + return ec; + } + + if (state == ::kSSLConnected) { + ec = asio::ssl::error::stream_truncated; + return ec; + } + + return ec; +} + +engine::want engine::write(const asio::const_buffer& data, + asio::error_code& ec, + std::size_t& bytes_transferred) { + if (!verifyConnected(_ssl.get(), &ec)) { + return want::want_nothing; + } + const auto status = ::SSLWrite(_ssl.get(), data.data(), data.size(), &bytes_transferred); + if (status == ::errSSLWouldBlock) { + return (bytes_transferred < data.size()) ? want::want_output_and_retry : want::want_nothing; + } + if (status != ::errSecSuccess) { + ec = errorCode(status); + } + return _outbuf.size() ? want::want_output : want::want_nothing; +} + +asio::mutable_buffer engine::get_output(const asio::mutable_buffer& data) { + const auto len = std::min<size_t>(data.size(), _outbuf.size()); + if (len > 0) { + auto* p = const_cast<char*>(static_cast<const char*>(data.data())); + std::copy(_outbuf.begin(), _outbuf.begin() + len, p); + _outbuf.erase(_outbuf.begin(), _outbuf.begin() + len); + } + + return asio::mutable_buffer(data.data(), len); +} + +::OSStatus engine::write_func(::SSLConnectionRef ctx, const void* data, size_t* data_len) { + auto* this_ = const_cast<engine*>(static_cast<const engine*>(ctx)); + const auto* p = static_cast<const char*>(data); + this_->_outbuf.insert(this_->_outbuf.end(), p, p + *data_len); + return ::errSecSuccess; +} + +engine::want engine::read(const asio::mutable_buffer& data, + asio::error_code& ec, + std::size_t& bytes_transferred) { + if (!verifyConnected(_ssl.get(), &ec)) { + return want::want_nothing; + } + const auto status = ::SSLRead(_ssl.get(), data.data(), data.size(), &bytes_transferred); + if ((status != ::errSSLWouldBlock) && (status != ::errSecSuccess)) { + ec = errorCode(status); + } + return bytes_transferred ? want::want_nothing : wouldBlock(); +} + +asio::const_buffer engine::put_input(const asio::const_buffer& data) { + const auto* p = static_cast<const char*>(data.data()); + _inbuf.insert(_inbuf.end(), p, p + data.size()); + return asio::buffer(data + data.size()); +} + +::OSStatus engine::read_func(::SSLConnectionRef ctx, void* data, size_t* data_len) { + ::OSStatus ret = ::errSecSuccess; + auto* this_ = const_cast<engine*>(static_cast<const engine*>(ctx)); + if (*data_len > this_->_inbuf.size()) { + // If we're able to 100% satisfy the read request Secure Transport made, + // then we should ultimately signal that the read is incomplete. + ret = ::errSSLWouldBlock; + } + *data_len = std::min<size_t>(*data_len, this_->_inbuf.size()); + if (*data_len > 0) { + std::copy( + this_->_inbuf.begin(), this_->_inbuf.begin() + *data_len, static_cast<char*>(data)); + this_->_inbuf.erase(this_->_inbuf.begin(), this_->_inbuf.begin() + *data_len); + } + return ret; +} + +engine::want engine::wouldBlock() const { + return _outbuf.empty() ? want::want_input_and_retry : want::want_output_and_retry; +} + +#include "asio/detail/pop_options.hpp" + +} // namespace detail +} // namespace ssl +} // namespace asio diff --git a/src/mongo/util/net/ssl/detail/stream_core.hpp b/src/mongo/util/net/ssl/detail/stream_core.hpp index 21841fec2b9..1740a5bda29 100644 --- a/src/mongo/util/net/ssl/detail/stream_core.hpp +++ b/src/mongo/util/net/ssl/detail/stream_core.hpp @@ -22,7 +22,9 @@ #else // defined(ASIO_HAS_BOOST_DATE_TIME) #include "asio/steady_timer.hpp" #endif // defined(ASIO_HAS_BOOST_DATE_TIME) + #include "asio/buffer.hpp" +#include "mongo/util/net/ssl/apple.hpp" #include "mongo/util/net/ssl/detail/engine.hpp" #include "asio/detail/push_options.hpp" @@ -40,6 +42,8 @@ struct stream_core { stream_core(SCHANNEL_CRED* context, asio::io_context& io_context) #elif MONGO_CONFIG_SSL_PROVIDER == SSL_PROVIDER_OPENSSL stream_core(SSL_CTX* context, asio::io_context& io_context) +#elif MONGO_CONFIG_SSL_PROVIDER == SSL_PROVIDER_APPLE + stream_core(apple::Context* context, asio::io_context& io_context) #else #error "Unknown SSL Provider" #endif diff --git a/src/mongo/util/net/ssl/impl/error.ipp b/src/mongo/util/net/ssl/impl/error.ipp index 60b8f0da177..e2c6bea061e 100644 --- a/src/mongo/util/net/ssl/impl/error.ipp +++ b/src/mongo/util/net/ssl/impl/error.ipp @@ -15,8 +15,12 @@ #pragma once #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) +#include <string> + +#include "asio/detail/assert.hpp" #include "asio/detail/config.hpp" #include "mongo/util/errno_util.h" +#include "mongo/util/net/ssl/apple.hpp" #include "mongo/util/net/ssl/error.hpp" #include "asio/detail/push_options.hpp" @@ -40,6 +44,12 @@ public: const char* s = ::ERR_reason_error_string(value); return s ? s : "asio.ssl error"; } +#elif MONGO_CONFIG_SSL_PROVIDER == SSL_PROVIDER_APPLE + std::string message(int value) const { + // engine_apple produces osstatus_errorcategory messages. + ASIO_ASSERT(false); + return "asio.ssl error"; + } #else #error "Unknown SSL Provider" #endif diff --git a/src/mongo/util/net/ssl/impl/src.hpp b/src/mongo/util/net/ssl/impl/src.hpp index f5bb866efee..5e300c2147e 100644 --- a/src/mongo/util/net/ssl/impl/src.hpp +++ b/src/mongo/util/net/ssl/impl/src.hpp @@ -33,6 +33,11 @@ #include "mongo/util/net/ssl/impl/context_openssl.ipp" #include "mongo/util/net/ssl/impl/error.ipp" +#elif MONGO_CONFIG_SSL_PROVIDER == SSL_PROVIDER_APPLE + +#include "mongo/util/net/ssl/detail/impl/engine_apple.ipp" +#include "mongo/util/net/ssl/impl/error.ipp" + #else #error "Unknown SSL Provider" #endif diff --git a/src/mongo/util/net/ssl/stream.hpp b/src/mongo/util/net/ssl/stream.hpp index d44bdc4bd08..df933c0fe55 100644 --- a/src/mongo/util/net/ssl/stream.hpp +++ b/src/mongo/util/net/ssl/stream.hpp @@ -22,6 +22,7 @@ #include "asio/detail/handler_type_requirements.hpp" #include "asio/detail/noncopyable.hpp" #include "asio/detail/type_traits.hpp" +#include "mongo/util/net/ssl/apple.hpp" #include "mongo/util/net/ssl/context.hpp" #include "mongo/util/net/ssl/detail/buffered_handshake_op.hpp" #include "mongo/util/net/ssl/detail/handshake_op.hpp" @@ -67,6 +68,8 @@ public: typedef PCtxtHandle native_handle_type; #elif MONGO_CONFIG_SSL_PROVIDER == SSL_PROVIDER_OPENSSL typedef SSL* native_handle_type; +#elif MONGO_CONFIG_SSL_PROVIDER == SSL_PROVIDER_APPLE + typedef ::SSLContextRef native_handle_type; #else #error "Unknown SSL Provider" #endif diff --git a/src/mongo/util/net/ssl_manager.cpp b/src/mongo/util/net/ssl_manager.cpp index 82207251d6e..1e84d75242a 100644 --- a/src/mongo/util/net/ssl_manager.cpp +++ b/src/mongo/util/net/ssl_manager.cpp @@ -65,6 +65,14 @@ ExportedServerParameter<std::string, ServerParameterType::kStartupOnly> setDiffieHellmanParameterPEMFile(ServerParameterSet::getGlobal(), "opensslDiffieHellmanParameters", &sslGlobalParams.sslPEMTempDHParam); + +std::string removeFQDNRoot(std::string name) { + if (name.back() == '.') { + name.pop_back(); + } + return name; +}; + } // namespace SSLPeerInfo& SSLPeerInfo::forSession(const transport::SessionHandle& session) { @@ -463,3 +471,24 @@ StatusWith<stdx::unordered_set<RoleName>> parsePeerRoles(ConstDataRange cdrExten #endif } // namespace mongo + +#ifdef MONGO_CONFIG_SSL +// TODO SERVER-11601 Use NFC Unicode canonicalization +bool mongo::hostNameMatchForX509Certificates(std::string nameToMatch, std::string certHostName) { + nameToMatch = removeFQDNRoot(std::move(nameToMatch)); + certHostName = removeFQDNRoot(std::move(certHostName)); + + if (certHostName.size() < 2) { + return false; + } + + // match wildcard DNS names + if (certHostName[0] == '*' && certHostName[1] == '.') { + // allow name.example.com if the cert is *.example.com, '*' does not match '.' + const char* subName = strchr(nameToMatch.c_str(), '.'); + return subName && !strcasecmp(certHostName.c_str() + 1, subName); + } else { + return !strcasecmp(nameToMatch.c_str(), certHostName.c_str()); + } +} +#endif diff --git a/src/mongo/util/net/ssl_manager.h b/src/mongo/util/net/ssl_manager.h index 9e0edccb649..fc171f8612c 100644 --- a/src/mongo/util/net/ssl_manager.h +++ b/src/mongo/util/net/ssl_manager.h @@ -40,6 +40,7 @@ #include "mongo/bson/bsonobj.h" #include "mongo/util/decorable.h" #include "mongo/util/net/sock.h" +#include "mongo/util/net/ssl/apple.hpp" #include "mongo/util/net/ssl_types.h" #include "mongo/util/time_support.h" @@ -67,6 +68,9 @@ typedef SSL* SSLConnectionType; #elif MONGO_CONFIG_SSL_PROVIDER == SSL_PROVIDER_WINDOWS typedef SCHANNEL_CRED* SSLContextType; typedef PCtxtHandle SSLConnectionType; +#elif MONGO_CONFIG_SSL_PROVIDER == SSL_PROVIDER_APPLE +typedef asio::ssl::apple::Context* SSLContextType; +typedef SSLContextRef SSLConnectionType; #else #error "Unknown SSL Provider" #endif diff --git a/src/mongo/util/net/ssl_manager_apple.cpp b/src/mongo/util/net/ssl_manager_apple.cpp new file mode 100644 index 00000000000..bca51fa9a40 --- /dev/null +++ b/src/mongo/util/net/ssl_manager_apple.cpp @@ -0,0 +1,1171 @@ +/** + * Copyright (C) 2018 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * 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 GNU Affero General 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. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kNetwork + +#include "mongo/platform/basic.h" + +#include <boost/optional/optional.hpp> +#include <fstream> +#include <stdlib.h> + +#include "mongo/base/checked_cast.h" +#include "mongo/base/init.h" +#include "mongo/base/initializer_context.h" +#include "mongo/base/status.h" +#include "mongo/base/status_with.h" +#include "mongo/platform/random.h" +#include "mongo/stdx/memory.h" +#include "mongo/util/base64.h" +#include "mongo/util/concurrency/mutex.h" +#include "mongo/util/log.h" +#include "mongo/util/net/private/ssl_expiration.h" +#include "mongo/util/net/socket_exception.h" +#include "mongo/util/net/ssl/apple.hpp" +#include "mongo/util/net/ssl_manager.h" +#include "mongo/util/net/ssl_options.h" + +using asio::ssl::apple::CFUniquePtr; + +/* This API appears in the Security framework even though + * the SecIdentity.h header doesn't reference it. + * + * Use it explicitly for turning Cert/Key pairs into Identities + * because it's way cheaper than going via keychain and has the + * handy property of not causing difficult to diagnose heisenbugs. + */ +extern "C" SecIdentityRef SecIdentityCreate(CFAllocatorRef, SecCertificateRef, SecKeyRef); + +namespace mongo { + +namespace { + +// CFAbsoluteTime and X.509 is relative to Jan 1 2001 00:00:00 GMT +// Unix Epoch (and thereby Date_t) is relative to Jan 1, 1970 00:00:00 GMT +static const ::CFAbsoluteTime k20010101_000000_GMT = 978307200; + +StatusWith<std::string> toString(::CFStringRef str) { + const auto len = + ::CFStringGetMaximumSizeForEncoding(::CFStringGetLength(str), ::kCFStringEncodingUTF8); + std::string ret; + ret.resize(len + 1); + if (!::CFStringGetCString(str, &ret[0], len, ::kCFStringEncodingUTF8)) { + return Status(ErrorCodes::InternalError, "Unable to convert CoreFoundation string"); + } + ret.resize(strlen(ret.c_str())); + return ret; +} + +// Never actually errors, but use StatusWith to be +// consistent with other toString() signatures. +StatusWith<std::string> toString(::CFDataRef data) { + const auto len = ::CFDataGetLength(data); + const auto* p = (const char*)::CFDataGetBytePtr(data); + return std::string(p, len); +} + +StatusWith<std::string> toString(::OSStatus status) { + CFUniquePtr<::CFStringRef> errstr(::SecCopyErrorMessageString(status, nullptr)); + if (!errstr) { + return Status(ErrorCodes::InternalError, "Unable to convert OSStatus"); + } + auto ret = toString(errstr.get()); + return ret; +} + +// CFTypeRef is actually just `void*`. +// So while we could be polymorphic with the other toString() methods, +// it's basically asking for a hard to diagnose type error. +StatusWith<std::string> stringFromCFType(::CFTypeRef val) { + const auto type = val ? ::CFGetTypeID(val) : ((CFTypeID)-1); + if (type == ::CFStringGetTypeID()) { + return toString(static_cast<::CFStringRef>(val)); + } else if (type == ::CFDataGetTypeID()) { + return toString(static_cast<::CFDataRef>(val)); + } else { + return Status(ErrorCodes::BadValue, "Value is not translatable to string"); + } +} + +std::ostringstream& operator<<(std::ostringstream& ss, ::CFStringRef str) { + auto swStr = toString(str); + if (swStr.isOK()) { + ss << swStr.getValue(); + } else { + ss << "Unknown error"; + } + return ss; +} + +std::ostringstream& operator<<(std::ostringstream& ss, ::OSStatus status) { + static_assert(std::is_signed<int>::value == std::is_signed<::OSStatus>::value, + "Must cast status to same signedness"); + static_assert(sizeof(int) >= sizeof(::OSStatus), "Must cast status to same or wider type"); + auto swStr = toString(status); + if (swStr.isOK()) { + ss << swStr.getValue(); + } else { + ss << "Unknown error: " << static_cast<int>(status); + } + return ss; +} + +std::ostringstream& operator<<(std::ostringstream& ss, ::CFErrorRef error) { + std::string comma; + + CFUniquePtr<::CFStringRef> desc(::CFErrorCopyDescription(error)); + if (desc) { + ss << comma << desc.get(); + comma = ", "; + } + + CFUniquePtr<::CFStringRef> reason(::CFErrorCopyFailureReason(error)); + if (reason) { + ss << comma << reason.get(); + comma = ", "; + } + + CFUniquePtr<::CFStringRef> suggest(::CFErrorCopyRecoverySuggestion(error)); + if (suggest) { + ss << comma << suggest.get(); + comma = ", "; + } + + auto code = ::CFErrorGetCode(error); + if (code) { + ss << comma << "Code: " << code; + } + + return ss; +} + +void uassertOSStatusOK(::OSStatus status, + ErrorCodes::Error code = ErrorCodes::InvalidSSLConfiguration) { + if (status == ::errSecSuccess) { + return; + } + auto swMsg = toString(status); + if (!swMsg.isOK()) { + uasserted(code, str::stream() << "Unknown SSL error" << static_cast<int>(status)); + } + uasserted(code, swMsg.getValue()); +} + +void uassertOSStatusOK(::OSStatus status, SocketErrorKind kind) { + if (status == ::errSecSuccess) { + return; + } + auto swMsg = toString(status); + if (!swMsg.isOK()) { + throwSocketError(kind, str::stream() << "Unknown SSL error" << static_cast<int>(status)); + } + throwSocketError(kind, swMsg.getValue()); +} + +StatusWith<stdx::unordered_set<RoleName>> parsePeerRoles(::SecCertificateRef peer) { + // TODO: ASN.1 parsing + return stdx::unordered_set<RoleName>(); +} + +bool isUnixDomainSocket(const std::string& hostname) { + return end(hostname) != std::find(begin(hostname), end(hostname), '/'); +} + +::OSStatus posixErrno(int err) { + switch (err) { + case EAGAIN: + return ::errSSLWouldBlock; + case ENOENT: + return ::errSSLClosedGraceful; + case ECONNRESET: + return ::errSSLClosedAbort; + default: + return ::errSSLInternal; + } +} + +namespace detail { +template <typename T> +struct CFTypeMap {}; +template <> +struct CFTypeMap<::CFStringRef> { + static constexpr StringData typeName = "string"_sd; + static ::CFTypeID type() { + return ::CFStringGetTypeID(); + } +}; +constexpr StringData CFTypeMap<::CFStringRef>::typeName; +template <> +struct CFTypeMap<::CFNumberRef> { + static constexpr StringData typeName = "number"_sd; + static ::CFTypeID type() { + return ::CFNumberGetTypeID(); + } +}; +constexpr StringData CFTypeMap<::CFNumberRef>::typeName; +template <> +struct CFTypeMap<::CFArrayRef> { + static constexpr StringData typeName = "array"_sd; + static ::CFTypeID type() { + return ::CFArrayGetTypeID(); + } +}; +constexpr StringData CFTypeMap<::CFArrayRef>::typeName; +template <> +struct CFTypeMap<::CFDictionaryRef> { + static constexpr StringData typeName = "dictionary"_sd; + static ::CFTypeID type() { + return ::CFDictionaryGetTypeID(); + } +}; +constexpr StringData CFTypeMap<::CFDictionaryRef>::typeName; +} // namespace detail + +template <typename T> +StatusWith<T> extractDictionaryValue(::CFDictionaryRef dict, ::CFStringRef key) { + const auto badValue = [key](StringData msg) -> Status { + auto swKey = toString(key); + if (!swKey.isOK()) { + return {ErrorCodes::InvalidSSLConfiguration, msg}; + } + return {ErrorCodes::InvalidSSLConfiguration, + str::stream() << msg << " for key'" << swKey.getValue() << "'"}; + }; + + auto val = ::CFDictionaryGetValue(dict, key); + if (!val) { + return badValue("Missing value"); + } + if (::CFGetTypeID(val) != detail::CFTypeMap<T>::type()) { + return badValue(str::stream() << "Value is not a " << detail::CFTypeMap<T>::typeName); + } + return reinterpret_cast<T>(val); +} + +StatusWith<std::string> mapSubjectLabel(::CFStringRef label) { + if (!::CFStringCompare(label, ::kSecOIDCommonName, 0)) { + return {"CN"}; + } else if (!::CFStringCompare(label, ::kSecOIDCountryName, 0)) { + return {"C"}; + } else if (!::CFStringCompare(label, ::kSecOIDStateProvinceName, 0)) { + return {"ST"}; + } else if (!::CFStringCompare(label, ::kSecOIDLocalityName, 0)) { + return {"L"}; + } else if (!::CFStringCompare(label, ::kSecOIDOrganizationName, 0)) { + return {"O"}; + } else if (!::CFStringCompare(label, ::kSecOIDOrganizationalUnitName, 0)) { + return {"OU"}; + } else if (!::CFStringCompare(label, ::kSecOIDStreetAddress, 0)) { + return {"STREET"}; + } else if (!::CFStringCompare(label, CFSTR("0.9.2342.19200300.100.1.25"), 0)) { + return {"DC"}; + } else if (!::CFStringCompare(label, CFSTR("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}; +} + +// Encode a raw DER subject sequence into a human readable subject name (RFC 2253). +StatusWith<std::string> extractSubjectName(::CFDictionaryRef dict) { + auto swSubject = extractDictionaryValue<::CFDictionaryRef>(dict, ::kSecOIDX509V1SubjectName); + if (!swSubject.isOK()) { + return swSubject.getStatus(); + } + + auto swElems = + extractDictionaryValue<::CFArrayRef>(swSubject.getValue(), ::kSecPropertyKeyValue); + if (!swElems.isOK()) { + return swElems.getStatus(); + } + + auto elems = swElems.getValue(); + const auto nElems = ::CFArrayGetCount(elems); + StringBuilder ret; + for (auto i = nElems; i; --i) { + auto elem = reinterpret_cast<::CFDictionaryRef>(::CFArrayGetValueAtIndex(elems, i - 1)); + invariant(elem); + if (::CFGetTypeID(elem) != ::CFDictionaryGetTypeID()) { + return {ErrorCodes::InvalidSSLConfiguration, + "Subject name element is not a dictionary"}; + } + + auto swLabel = extractDictionaryValue<::CFStringRef>(elem, ::kSecPropertyKeyLabel); + if (!swLabel.isOK()) { + return swLabel.getStatus(); + } + auto swLabelStr = mapSubjectLabel(swLabel.getValue()); + if (!swLabelStr.isOK()) { + return swLabelStr.getStatus(); + } + + auto swValue = extractDictionaryValue<::CFStringRef>(elem, ::kSecPropertyKeyValue); + if (!swValue.isOK()) { + return swValue.getStatus(); + } + auto swValueStr = toString(swValue.getValue()); + if (!swValueStr.isOK()) { + return swValueStr.getStatus(); + } + + ret << swLabelStr.getValue() << '=' << swValueStr.getValue(); + if (i > 1) { + ret << ","; + } + } + return ret.str(); +} + +StatusWith<mongo::Date_t> extractValidityDate(::CFDictionaryRef dict, + ::CFStringRef oid, + StringData name) { + auto swVal = extractDictionaryValue<::CFDictionaryRef>(dict, oid); + if (!swVal.isOK()) { + return swVal.getStatus(); + } + + auto swNum = extractDictionaryValue<::CFNumberRef>(swVal.getValue(), ::kSecPropertyKeyValue); + if (!swNum.isOK()) { + return swNum.getStatus(); + } + + int64_t dateval = 0; + if (!::CFNumberGetValue(swNum.getValue(), ::kCFNumberSInt64Type, &dateval)) { + return {ErrorCodes::InvalidSSLConfiguration, + str::stream() << "Certificate contains invalid " << name + << ": OID contains invalid numeric value"}; + } + + return Date_t::fromMillisSinceEpoch((k20010101_000000_GMT + dateval) * 1000); +} + +StatusWith<std::vector<std::string>> extractSubjectAlternateNames(::CFDictionaryRef dict) { + const auto badValue = [](StringData msg) -> Status { + return {ErrorCodes::InvalidSSLConfiguration, + str::stream() << "Certificate contains invalid SAN: " << msg}; + }; + auto swSANDict = extractDictionaryValue<::CFDictionaryRef>(dict, ::kSecOIDSubjectAltName); + if (!swSANDict.isOK()) { + return swSANDict.getStatus(); + } + + auto sanDict = swSANDict.getValue(); + auto swList = extractDictionaryValue<::CFArrayRef>(sanDict, ::kSecPropertyKeyValue); + if (!swList.isOK()) { + return swList.getStatus(); + } + + std::vector<std::string> ret; + auto list = swList.getValue(); + const auto count = ::CFArrayGetCount(list); + for (::CFIndex i = 0; i < count; ++i) { + auto elemval = ::CFArrayGetValueAtIndex(list, i); + invariant(elemval); + if (::CFGetTypeID(elemval) != ::CFDictionaryGetTypeID()) { + return badValue("Invalid list element"); + } + + auto elem = reinterpret_cast<::CFDictionaryRef>(elemval); + auto swLabel = extractDictionaryValue<::CFStringRef>(elem, ::kSecPropertyKeyLabel); + if (!swLabel.isOK()) { + return swLabel.getStatus(); + } + if (::CFStringCompare(swLabel.getValue(), CFSTR("DNS Name"), ::kCFCompareCaseInsensitive) != + ::kCFCompareEqualTo) { + // Skip other elements, e.g. 'Critical' + continue; + } + auto swName = extractDictionaryValue<::CFStringRef>(elem, ::kSecPropertyKeyValue); + if (!swName.isOK()) { + return swName.getStatus(); + } + auto swNameStr = toString(swName.getValue()); + if (!swNameStr.isOK()) { + return swNameStr.getStatus(); + } + ret.push_back(swNameStr.getValue()); + } + return ret; +} + +enum LoadPEMMode { + kLoadPEMBindIdentities = true, + kLoadPEMStripKeys = false, +}; +/** + * Load a PEM encoded file from disk. + * This file may contain multiple PEM blocks (e.g. a private key and one or more certificates). + * Because SecItemImport loads all items as-is, we must manually attempt to pair up + * corresponding Certificates and Keys. + * This is done using a temporary Keychain and looping through the results. + * + * Depending on the value passed for <mode> any SecKey instances present will be + * either discarded, or combined with matching SecCertificates to make SecIdentities. + * Unbound certificates will remain in the CFArray as-is. + */ +StatusWith<CFUniquePtr<::CFArrayRef>> loadPEM(const std::string& keyfilepath, + const std::string& passphrase = "", + const LoadPEMMode mode = kLoadPEMBindIdentities) { + const auto retFail = [&keyfilepath, &passphrase](const std::string& msg = "") { + return Status(ErrorCodes::InvalidSSLConfiguration, + str::stream() << "Unable to load PEM from '" << keyfilepath << "'" + << (passphrase.empty() ? "" : " with passphrase: ") + << msg); + }; + + std::ifstream pemFile(keyfilepath, std::ios::binary); + if (!pemFile.is_open()) { + return retFail("Failed opening file"); + } + + std::vector<uint8_t> pemdata((std::istreambuf_iterator<char>(pemFile)), + std::istreambuf_iterator<char>()); + CFUniquePtr<CFDataRef> cfdata(::CFDataCreate(nullptr, pemdata.data(), pemdata.size())); + invariant(cfdata); + pemdata.clear(); + + CFUniquePtr<CFDataRef> cfpass; + if (!passphrase.empty()) { + cfpass.reset(::CFDataCreate( + nullptr, reinterpret_cast<const uint8_t*>(passphrase.c_str()), passphrase.size())); + } + ::SecItemImportExportKeyParameters params = { + SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION, 0, cfpass.get(), + }; + + CFUniquePtr<CFStringRef> cfkeyfile( + ::CFStringCreateWithCString(nullptr, keyfilepath.c_str(), ::kCFStringEncodingUTF8)); + auto format = ::kSecFormatUnknown; + auto type = ::kSecItemTypeUnknown; + ::CFArrayRef certs = nullptr; + auto status = ::SecItemImport(cfdata.get(), + cfkeyfile.get(), + &format, + &type, + ::kSecItemPemArmour, + ¶ms, + nullptr, + &certs); + CFUniquePtr<::CFArrayRef> cfcerts(certs); + if ((status == ::errSecUnknownFormat) && !passphrase.empty()) { + // The Security framework in OSX doesn't support PKCS#8 encrypted keys + // using modern encryption algorithms. Give the user a hint about that. + return Status(ErrorCodes::InvalidSSLConfiguration, + "Unable to import PEM key file, possibly due to presence of PKCS#8 encrypted " + "key. Consider using a certificate selector or PKCS#12 instead"); + } + if (status != ::errSecSuccess) { + return retFail(str::stream() << "Failing importing certificate(s): " << status); + } + + auto count = ::CFArrayGetCount(cfcerts.get()); + if ((count > 0) && (mode == kLoadPEMBindIdentities)) { + // Turn Certificate/Key pairs into identities. + CFUniquePtr<::CFMutableArrayRef> bind( + ::CFArrayCreateMutable(nullptr, count, &kCFTypeArrayCallBacks)); + for (::CFIndex i = 0; i < count; ++i) { + auto elem = ::CFArrayGetValueAtIndex(cfcerts.get(), i); + invariant(elem); + const auto type = ::CFGetTypeID(elem); + if (type == ::SecIdentityGetTypeID()) { + // Our import had a proper identity in it, ready to go. + ::CFArrayAppendValue(bind.get(), elem); + continue; + } + if (type != ::SecCertificateGetTypeID()) { + continue; + } + + // Attempt to match the certificate to a private key in the aggregate we just imported. + CFUniquePtr<::SecIdentityRef> cfid; + for (::CFIndex j = 0; j < count; ++j) { + auto key = ::CFArrayGetValueAtIndex(cfcerts.get(), j); + invariant(key); + if (::CFGetTypeID(key) != ::SecKeyGetTypeID()) { + continue; + } + auto id = + ::SecIdentityCreate(nullptr, + static_cast<::SecCertificateRef>(const_cast<void*>(elem)), + static_cast<::SecKeyRef>(const_cast<void*>(key))); + if (id) { + cfid.reset(id); + break; + } + } + if (cfid) { + ::CFArrayAppendValue(bind.get(), cfid.get()); + } else { + ::CFArrayAppendValue(bind.get(), elem); + } + } + // Reencapsulate to allow the inner type to change. + cfcerts.reset(bind.release()); + count = ::CFArrayGetCount(cfcerts.get()); + } + + if ((count > 0) && (mode == kLoadPEMStripKeys)) { + // Strip unpaired keys and identities. + CFUniquePtr<::CFMutableArrayRef> strip( + ::CFArrayCreateMutable(nullptr, count, &kCFTypeArrayCallBacks)); + for (::CFIndex i = 0; i < count; ++i) { + auto elem = ::CFArrayGetValueAtIndex(cfcerts.get(), i); + if (!elem) { + continue; + } + const auto type = ::CFGetTypeID(elem); + if (type == ::SecCertificateGetTypeID()) { + // Preserve Certificates. + ::CFArrayAppendValue(strip.get(), elem); + continue; + } + if (type != ::SecIdentityGetTypeID()) { + continue; + } + + // Extract public certificate from Identity. + ::SecCertificateRef cert = nullptr; + const auto status = ::SecIdentityCopyCertificate( + static_cast<::SecIdentityRef>(const_cast<void*>(elem)), &cert); + CFUniquePtr<::SecCertificateRef> cfcert(cert); + if (status != ::errSecSuccess) { + return {ErrorCodes::InternalError, + str::stream() << "Unable to extract certificate from identity: " << status}; + } + ::CFArrayAppendValue(strip.get(), cfcert.get()); + } + // Reencapsulate to allow the inner type to change. + cfcerts.reset(strip.release()); + count = ::CFArrayGetCount(cfcerts.get()); + } + + if (count <= 0) { + return {ErrorCodes::InvalidSSLConfiguration, + str::stream() << "PEM file '" << keyfilepath << "' has no certificates"}; + } + + // Rewrap the return to be the non-mutable type. + return std::move(cfcerts); +} + +StatusWith<std::string> loadAndValidatePEM(const std::string& key, + const std::string& pass, + Date_t* expire = nullptr) { + auto swCerts = loadPEM(key, pass); + if (!swCerts.isOK()) { + return swCerts.getStatus(); + } + + auto certs = std::move(swCerts.getValue()); + if (::CFArrayGetCount(certs.get()) <= 0) { + return {ErrorCodes::InvalidSSLConfiguration, "No certificates in certificate list"}; + } + + auto root = ::CFArrayGetValueAtIndex(certs.get(), 0); + if (!root || (::CFGetTypeID(root) != ::SecIdentityGetTypeID())) { + return {ErrorCodes::InvalidSSLConfiguration, "Root certificate not an identity pair"}; + } + + ::SecCertificateRef idcert = nullptr; + auto status = ::SecIdentityCopyCertificate( + static_cast<::SecIdentityRef>(const_cast<void*>(root)), &idcert); + if (status != ::errSecSuccess) { + return {ErrorCodes::InvalidSSLConfiguration, + str::stream() << "Unable to get certificate from identity: " << status}; + } + CFUniquePtr<::SecCertificateRef> cert(idcert); + + // Fetch expiry range and full subject name. + CFUniquePtr<::CFMutableArrayRef> oids( + ::CFArrayCreateMutable(nullptr, expire ? 3 : 1, &::kCFTypeArrayCallBacks)); + ::CFArrayAppendValue(oids.get(), ::kSecOIDX509V1SubjectName); + if (expire) { + ::CFArrayAppendValue(oids.get(), ::kSecOIDX509V1ValidityNotBefore); + ::CFArrayAppendValue(oids.get(), ::kSecOIDX509V1ValidityNotAfter); + } + + ::CFErrorRef cferror = nullptr; + CFUniquePtr<::CFDictionaryRef> cfdict( + ::SecCertificateCopyValues(cert.get(), oids.get(), &cferror)); + if (cferror) { + CFUniquePtr<::CFErrorRef> deleter(cferror); + return {ErrorCodes::InvalidSSLConfiguration, + str::stream() << "Unable to determine certificate validity: " << cferror}; + } + + auto swSubjectName = extractSubjectName(cfdict.get()); + if (!swSubjectName.isOK()) { + return swSubjectName.getStatus(); + } + auto subject = swSubjectName.getValue(); + + if (!expire) { + return subject; + } + + // Marshal expiration. + auto swValidFrom = + extractValidityDate(cfdict.get(), ::kSecOIDX509V1ValidityNotBefore, "valid-from"); + auto swValidUntil = + extractValidityDate(cfdict.get(), ::kSecOIDX509V1ValidityNotAfter, "valid-until"); + if (!swValidFrom.isOK() || !swValidUntil.isOK()) { + return swValidUntil.getStatus(); + } + + *expire = swValidUntil.getValue(); + const auto now = Date_t::now(); + if ((now < swValidFrom.getValue()) || (*expire < now)) { + return {ErrorCodes::InvalidSSLConfiguration, + "The provided SSL certificate is expired or not yet valid"}; + } + + return subject; +}; + +} // namespace + +///////////////////////////////////////////////////////////////////////////// +// SSLConnection +namespace { + +class SSLConnectionApple : public SSLConnectionInterface { +public: + SSLConnectionApple(asio::ssl::apple::Context* ctx, + Socket* socket, + ::SSLProtocolSide side, + std::string hostname = "", + std::vector<uint8_t> init = {}) + : _sock(socket), _init(std::move(init)) { + _ssl.reset(::SSLCreateContext(nullptr, side, ::kSSLStreamType)); + uassert(ErrorCodes::InternalError, "Failed creating SSL context", _ssl); + + auto certs = ctx->certs.get(); + if (certs) { + uassertOSStatusOK(::SSLSetCertificate(_ssl.get(), certs)); + } + + uassertOSStatusOK(::SSLSetConnection(_ssl.get(), static_cast<void*>(this))); + uassertOSStatusOK(::SSLSetPeerID(_ssl.get(), _ssl.get(), sizeof(_ssl))); + uassertOSStatusOK(::SSLSetIOFuncs(_ssl.get(), read_func, write_func)); + uassertOSStatusOK(::SSLSetProtocolVersionMin(_ssl.get(), ctx->protoMin)); + uassertOSStatusOK(::SSLSetProtocolVersionMax(_ssl.get(), ctx->protoMax)); + + if (!hostname.empty()) { + uassertOSStatusOK( + ::SSLSetPeerDomainName(_ssl.get(), hostname.c_str(), hostname.size())); + } + + // SSLHandshake will return errSSLServerAuthCompleted and let us do our own verify. + // We'll pretend to have done that, and let our caller invoke verifyPeer later. + uassertOSStatusOK(::SSLSetClientSideAuthenticate(_ssl.get(), ::kTryAuthenticate)); + uassertOSStatusOK( + ::SSLSetSessionOption(_ssl.get(), ::kSSLSessionOptionBreakOnServerAuth, true)); + uassertOSStatusOK( + ::SSLSetSessionOption(_ssl.get(), ::kSSLSessionOptionBreakOnClientAuth, true)); + + ::OSStatus status; + do { + status = ::SSLHandshake(_ssl.get()); + } while ((status == ::errSSLServerAuthCompleted) || + (status == ::errSSLClientAuthCompleted)); + uassertOSStatusOK(status, ErrorCodes::SSLHandshakeFailed); + } + + std::string getSNIServerName() const final { + size_t len = 0; + auto status = ::SSLCopyRequestedPeerNameLength(_ssl.get(), &len); + if (status != ::errSecSuccess) { + return ""; + } + std::string ret; + ret.resize(len + 1); + status = ::SSLCopyRequestedPeerName(_ssl.get(), &ret[0], &len); + if (status != ::errSecSuccess) { + return ""; + } + ret.resize(len); + return ret; + } + + ::SSLContextRef get() const { + return const_cast<::SSLContextRef>(_ssl.get()); + } + +private: + static ::OSStatus write_func(::SSLConnectionRef ctx, const void* data, size_t* data_len) { + const auto* conn = reinterpret_cast<const SSLConnectionApple*>(ctx); + size_t len = *data_len; + *data_len = 0; + while (len > 0) { + auto wrote = + ::write(conn->_sock->rawFD(), static_cast<const char*>(data) + *data_len, len); + if (wrote > 0) { + *data_len += wrote; + len -= wrote; + continue; + } + return posixErrno(errno); + } + return ::errSecSuccess; + } + + static ::OSStatus read_func(::SSLConnectionRef ctx, void* data, size_t* data_len) { + auto* conn = + const_cast<SSLConnectionApple*>(reinterpret_cast<const SSLConnectionApple*>(ctx)); + auto* dest = static_cast<char*>(data); + size_t len = *data_len; + *data_len = 0; + while (len > 0) { + if (conn->_init.size()) { + // Consume any initial bytes first. + auto& init = conn->_init; + const auto mvlen = std::max(len, init.size()); + std::copy(init.begin(), init.begin() + mvlen, dest + *data_len); + init.erase(init.begin(), init.begin() + mvlen); + *data_len += mvlen; + len -= mvlen; + continue; + } + + // Then go to the network. + auto didread = ::read(conn->_sock->rawFD(), dest + *data_len, len); + if (didread > 0) { + *data_len += didread; + len -= didread; + continue; + } + return posixErrno(errno); + } + return ::errSecSuccess; + } + + Socket* _sock; + + // When in server mode, _init contains any bytes read prior to + // starting the SSL handshake process. + // Once exhausted, this is never refilled. + std::vector<uint8_t> _init; + + CFUniquePtr<::SSLContextRef> _ssl; +}; + +} // namespace + +///////////////////////////////////////////////////////////////////////////// +// SSLManager +namespace { + +class SSLManagerApple : public SSLManagerInterface { +public: + explicit SSLManagerApple(const SSLParams& params, bool isServer); + + Status initSSLContext(asio::ssl::apple::Context* context, + const SSLParams& params, + ConnectionDirection direction) final; + + SSLConnectionInterface* connect(Socket* socket) final; + SSLConnectionInterface* accept(Socket* socket, const char* initialBytes, int len) final; + + SSLPeerInfo parseAndValidatePeerCertificateDeprecated(const SSLConnectionInterface* conn, + const std::string& remoteHost) final; + + StatusWith<boost::optional<SSLPeerInfo>> parseAndValidatePeerCertificate( + ::SSLContextRef conn, const std::string& remoteHost) final; + + const SSLConfiguration& getSSLConfiguration() const final { + return _sslConfiguration; + } + + int SSL_read(SSLConnectionInterface* conn, void* buf, int num) final; + int SSL_write(SSLConnectionInterface* conn, const void* buf, int num) final; + int SSL_shutdown(SSLConnectionInterface* conn) final; + +private: + Status _validatePEMs(const SSLParams& params, bool isServer); + + bool _weakValidation; + bool _allowInvalidCertificates; + bool _allowInvalidHostnames; + asio::ssl::apple::Context _clientCtx; + asio::ssl::apple::Context _serverCtx; + CFUniquePtr<::CFArrayRef> _ca; + SSLConfiguration _sslConfiguration; +}; + +SSLManagerApple::SSLManagerApple(const SSLParams& params, bool isServer) + : _weakValidation(params.sslWeakCertificateValidation), + _allowInvalidCertificates(params.sslAllowInvalidCertificates), + _allowInvalidHostnames(params.sslAllowInvalidHostnames) { + uassertStatusOK(initSSLContext(&_clientCtx, params, ConnectionDirection::kOutgoing)); + if (isServer) { + uassertStatusOK(initSSLContext(&_serverCtx, params, ConnectionDirection::kIncoming)); + } + uassertStatusOK(_validatePEMs(params, isServer)); + + if (!params.sslCAFile.empty()) { + auto ca = uassertStatusOK(loadPEM(params.sslCAFile, "", kLoadPEMStripKeys)); + _ca = std::move(ca); + _sslConfiguration.hasCA = _ca && ::CFArrayGetCount(_ca.get()); + } +} + +Status SSLManagerApple::_validatePEMs(const SSLParams& params, bool isServer) { + // pick the certificate for use in outgoing connections, + std::string clientPEM, clientPassword; + if (!isServer || params.sslClusterFile.empty()) { + // We are either a client, or a server without a cluster key, + // so use the PEM key file, if specified. + clientPEM = params.sslPEMKeyFile; + clientPassword = params.sslPEMKeyPassword; + } else { + // We are a server with a cluster key, so use the cluster key file + clientPEM = params.sslClusterFile; + clientPassword = params.sslClusterPassword; + } + if (!clientPEM.empty()) { + auto swSubject = loadAndValidatePEM(clientPEM, clientPassword); + if (!swSubject.isOK()) { + return swSubject.getStatus(); + } + _sslConfiguration.clientSubjectName = swSubject.getValue(); + } + + if (isServer) { + auto swSubject = loadAndValidatePEM(params.sslPEMKeyFile, + params.sslPEMKeyPassword, + &_sslConfiguration.serverCertificateExpirationDate); + if (!swSubject.isOK()) { + return swSubject.getStatus(); + } + _sslConfiguration.serverSubjectName = swSubject.getValue(); + + static auto task = + CertificateExpirationMonitor(_sslConfiguration.serverCertificateExpirationDate); + } + + return Status::OK(); +} + +StatusWith<std::pair<::SSLProtocol, ::SSLProtocol>> parseProtocolRange(const SSLParams& params) { + // Map disabled protocols to range. + bool tls10 = true, tls11 = true, tls12 = true; + for (const SSLParams::Protocols& protocol : params.sslDisabledProtocols) { + if (protocol == SSLParams::Protocols::TLS1_0) { + tls10 = false; + } else if (protocol == SSLParams::Protocols::TLS1_1) { + tls11 = false; + } else if (protocol == SSLParams::Protocols::TLS1_2) { + tls12 = false; + } else { + return {ErrorCodes::InvalidSSLConfiguration, "Unknown disabled TLS protocol version"}; + } + } + // Throw out the invalid cases. + if (tls10 && !tls11 && tls12) { + return {ErrorCodes::InvalidSSLConfiguration, + "Can not disable TLS 1.1 while leaving 1.0 and 1.2 enabled"}; + } + if (!tls10 && !tls11 && !tls12) { + return {ErrorCodes::InvalidSSLConfiguration, "All valid TLS modes disabled"}; + } + + auto protoMin = tls10 ? ::kTLSProtocol1 : tls11 ? ::kTLSProtocol11 : ::kTLSProtocol12; + auto protoMax = tls12 ? ::kTLSProtocol12 : tls11 ? ::kTLSProtocol11 : ::kTLSProtocol1; + + return std::pair<::SSLProtocol, ::SSLProtocol>(protoMin, protoMax); +} + +Status SSLManagerApple::initSSLContext(asio::ssl::apple::Context* context, + const SSLParams& params, + ConnectionDirection direction) { + // Protocol Version. + const auto swProto = parseProtocolRange(params); + if (!swProto.isOK()) { + return swProto.getStatus(); + } + const auto proto = swProto.getValue(); + context->protoMin = proto.first; + context->protoMax = proto.second; + + // Certificate. + if (direction == ConnectionDirection::kOutgoing && !params.sslClusterFile.empty()) { + auto swCertificates = loadPEM(params.sslClusterFile, params.sslClusterPassword); + if (!swCertificates.isOK()) { + return swCertificates.getStatus(); + } + context->certs = std::move(swCertificates.getValue()); + } else if (!params.sslPEMKeyFile.empty()) { + auto swCertificates = loadPEM(params.sslPEMKeyFile, params.sslPEMKeyPassword); + if (!swCertificates.isOK()) { + return swCertificates.getStatus(); + } + context->certs = std::move(swCertificates.getValue()); + } + + return Status::OK(); +} + +SSLConnectionInterface* SSLManagerApple::connect(Socket* socket) { + return new SSLConnectionApple( + &_clientCtx, socket, ::kSSLClientSide, socket->remoteAddr().hostOrIp()); +} + +SSLConnectionInterface* SSLManagerApple::accept(Socket* socket, const char* initialBytes, int len) { + std::vector<uint8_t> init; + const auto* p = reinterpret_cast<const uint8_t*>(initialBytes); + init.insert(init.end(), p, p + len); + return new SSLConnectionApple(&_serverCtx, socket, ::kSSLServerSide, "", init); +} + +SSLPeerInfo SSLManagerApple::parseAndValidatePeerCertificateDeprecated( + const SSLConnectionInterface* conn, const std::string& remoteHost) { + auto ssl = checked_cast<const SSLConnectionApple*>(conn)->get(); + + auto swPeerSubjectName = parseAndValidatePeerCertificate(ssl, remoteHost); + // We can't use uassertStatusOK here because we need to throw a NetworkException. + if (!swPeerSubjectName.isOK()) { + throwSocketError(SocketErrorKind::CONNECT_ERROR, swPeerSubjectName.getStatus().reason()); + } + return swPeerSubjectName.getValue().get_value_or(SSLPeerInfo()); +} + +StatusWith<boost::optional<SSLPeerInfo>> SSLManagerApple::parseAndValidatePeerCertificate( + ::SSLContextRef ssl, const std::string& remoteHost) { + + /* While we always have a system CA via the Keychain, + * we'll pretend not to in terms of validation if the server + * was started using a PEM file (legacy mode). + * + * When a certificate selector is used, we'll override hasCA to true + * so that the validation path runs anyway. + */ + if (!_sslConfiguration.hasCA && isSSLServer) { + return {boost::none}; + } + + const auto badCert = [](StringData msg, + bool warn = false) -> StatusWith<boost::optional<SSLPeerInfo>> { + constexpr StringData prefix = "SSL peer certificate validation failed: "_sd; + if (warn) { + warning() << prefix << msg; + return {boost::none}; + } else { + std::string m = str::stream() << prefix << msg << "; connection rejected"; + error() << m; + return Status(ErrorCodes::SSLHandshakeFailed, m); + } + }; + + ::SecTrustRef trust = nullptr; + const auto status = ::SSLCopyPeerTrust(ssl, &trust); + CFUniquePtr<::SecTrustRef> cftrust(trust); + if ((status != ::errSecSuccess) || (!cftrust)) { + return badCert(str::stream() << "Unable to retreive SSL trust from peer: " << status, + _weakValidation); + } + + if (_ca) { + auto status = ::SecTrustSetAnchorCertificates(cftrust.get(), _ca.get()); + if (status == ::errSecSuccess) { + status = ::SecTrustSetAnchorCertificatesOnly(cftrust.get(), true); + } + if (status != ::errSecSuccess) { + return badCert(str::stream() << "Unable to bind CA to trust chain: " << status, + _weakValidation); + } + } + + auto result = ::kSecTrustResultInvalid; + uassertOSStatusOK(::SecTrustEvaluate(cftrust.get(), &result), ErrorCodes::SSLHandshakeFailed); + if ((result != ::kSecTrustResultProceed) && (result != ::kSecTrustResultUnspecified)) { + if (result == ::kSecTrustResultDeny) { + return badCert("Certificate trust denied", _allowInvalidCertificates); + } else if (result == ::kSecTrustResultRecoverableTrustFailure) { + return badCert("Certificate trust failed (recoverably)", _allowInvalidCertificates); + } else if (result == ::kSecTrustResultFatalTrustFailure) { + return badCert("Certificate trust failure", _allowInvalidCertificates); + } else { + static_assert(std::is_signed<uint>::value == + std::is_signed<::SecTrustResultType>::value, + "Must cast status to same signedness"); + static_assert(sizeof(uint) >= sizeof(::SecTrustResultType), + "Must cast result to same or wider type"); + return badCert(str::stream() << "Unknown cause: " << static_cast<uint>(result), + _allowInvalidCertificates); + } + } + + auto cert = ::SecTrustGetCertificateAtIndex(cftrust.get(), 0); + if (!cert) { + return badCert("no SSL certificate provided by peer", _weakValidation); + } + + CFUniquePtr<::CFMutableArrayRef> oids( + ::CFArrayCreateMutable(nullptr, 2, &::kCFTypeArrayCallBacks)); + ::CFArrayAppendValue(oids.get(), ::kSecOIDX509V1SubjectName); + ::CFArrayAppendValue(oids.get(), ::kSecOIDSubjectAltName); + + ::CFErrorRef err = nullptr; + CFUniquePtr<::CFDictionaryRef> cfdict(::SecCertificateCopyValues(cert, oids.get(), &err)); + CFUniquePtr<::CFErrorRef> cferror(err); + if (cferror) { + return badCert(str::stream() << cferror.get(), _weakValidation); + } + + // Extract SubjectName into a human readable string. + auto swPeerSubjectName = extractSubjectName(cfdict.get()); + if (!swPeerSubjectName.isOK()) { + return swPeerSubjectName.getStatus(); + } + const auto peerSubjectName = std::move(swPeerSubjectName.getValue()); + LOG(2) << "Accepted TLS connection from peer: " << peerSubjectName; + + if (remoteHost.empty()) { + // If this is an SSL server context (on a mongod/mongos) + // parse any client roles out of the client certificate. + auto swPeerCertificateRoles = parsePeerRoles(cert); + if (!swPeerCertificateRoles.isOK()) { + return swPeerCertificateRoles.getStatus(); + } + return boost::make_optional( + SSLPeerInfo(peerSubjectName, std::move(swPeerCertificateRoles.getValue()))); + } + + // If this is an SSL client context (on a MongoDB server or client) + // perform hostname validation of the remote server + bool sanMatch = false; + bool cnMatch = false; + StringBuilder certErr; + certErr << "The server certificate does not match the host name. " + << "Hostname: " << remoteHost << " does not match "; + + // Attempt to retreive "Subject Alternative Name" + std::vector<std::string> sans; + auto swSANs = extractSubjectAlternateNames(cfdict.get()); + if (swSANs.isOK()) { + sans = std::move(swSANs.getValue()); + } + + if (!sans.empty()) { + certErr << "SAN(s): "; + for (auto& san : sans) { + if (hostNameMatchForX509Certificates(remoteHost, san)) { + sanMatch = true; + break; + } + certErr << san << " "; + } + + } else if (peerSubjectName.find("CN=") != std::string::npos) { + const auto cnBegin = peerSubjectName.find("CN=") + 3; + auto cnEnd = peerSubjectName.find(",", cnBegin); + if (cnEnd == std::string::npos) { + cnEnd = peerSubjectName.size(); + } + const auto commonName = peerSubjectName.substr(cnBegin, cnEnd - cnBegin); + cnMatch = hostNameMatchForX509Certificates(remoteHost, commonName); + certErr << "CN: " << commonName; + + } else { + certErr << "No Common Name (CN) or Subject Alternate Names (SAN) found"; + } + + if (!sanMatch && !cnMatch) { + const auto msg = certErr.str(); + if (_allowInvalidCertificates || _allowInvalidHostnames || isUnixDomainSocket(remoteHost)) { + warning() << msg; + } else { + error() << msg; + return Status(ErrorCodes::SSLHandshakeFailed, msg); + } + } + + return boost::make_optional(SSLPeerInfo(peerSubjectName, stdx::unordered_set<RoleName>())); +} + +int SSLManagerApple::SSL_read(SSLConnectionInterface* conn, void* buf, int num) { + auto ssl = checked_cast<SSLConnectionApple*>(conn)->get(); + size_t read = 0; + uassertOSStatusOK(::SSLRead(ssl, static_cast<uint8_t*>(buf), num, &read), + SocketErrorKind::RECV_ERROR); + return read; +} + +int SSLManagerApple::SSL_write(SSLConnectionInterface* conn, const void* buf, int num) { + auto ssl = checked_cast<SSLConnectionApple*>(conn)->get(); + size_t written = 0; + uassertOSStatusOK(::SSLWrite(ssl, static_cast<const uint8_t*>(buf), num, &written), + SocketErrorKind::SEND_ERROR); + return written; +} + +int SSLManagerApple::SSL_shutdown(SSLConnectionInterface* conn) { + auto ssl = checked_cast<SSLConnectionApple*>(conn)->get(); + const auto status = ::SSLClose(ssl); + if (status == ::errSSLWouldBlock) { + return 0; + } + uassertOSStatusOK(status, ErrorCodes::SocketException); + return 1; +} + +} // namespace + +///////////////////////////////////////////////////////////////////////////// + +// Global variable indicating if this is a server or a client instance +bool isSSLServer = false; + +namespace { +SimpleMutex sslManagerMtx; +SSLManagerInterface* theSSLManager = nullptr; +} // namespace + +std::unique_ptr<SSLManagerInterface> SSLManagerInterface::create(const SSLParams& params, + bool isServer) { + return stdx::make_unique<SSLManagerApple>(params, isServer); +} + +MONGO_INITIALIZER(SSLManager)(InitializerContext*) { + stdx::lock_guard<SimpleMutex> lck(sslManagerMtx); + if (!isSSLServer || (sslGlobalParams.sslMode.load() != SSLParams::SSLMode_disabled)) { + theSSLManager = new SSLManagerApple(sslGlobalParams, isSSLServer); + } + return Status::OK(); +} + +} // namespace mongo + +mongo::SSLManagerInterface* mongo::getSSLManager() { + stdx::lock_guard<SimpleMutex> lck(sslManagerMtx); + if (theSSLManager) { + return theSSLManager; + } + return nullptr; +} diff --git a/src/mongo/util/net/ssl_manager_openssl.cpp b/src/mongo/util/net/ssl_manager_openssl.cpp index 98db6a830fc..372c38b4975 100644 --- a/src/mongo/util/net/ssl_manager_openssl.cpp +++ b/src/mongo/util/net/ssl_manager_openssl.cpp @@ -1419,22 +1419,3 @@ void SSLManagerOpenSSL::_handleSSLError(int code, int ret) { throwSocketError(SocketErrorKind::CONNECT_ERROR, ""); } } // namespace mongo - -// TODO SERVER-11601 Use NFC Unicode canonicalization -bool mongo::hostNameMatchForX509Certificates(std::string nameToMatch, std::string certHostName) { - nameToMatch = removeFQDNRoot(std::move(nameToMatch)); - certHostName = removeFQDNRoot(std::move(certHostName)); - - if (certHostName.size() < 2) { - return false; - } - - // match wildcard DNS names - if (certHostName[0] == '*' && certHostName[1] == '.') { - // allow name.example.com if the cert is *.example.com, '*' does not match '.' - const char* subName = strchr(nameToMatch.c_str(), '.'); - return subName && !strcasecmp(certHostName.c_str() + 1, subName); - } else { - return !strcasecmp(nameToMatch.c_str(), certHostName.c_str()); - } -} diff --git a/src/mongo/util/version.cpp b/src/mongo/util/version.cpp index 9536caf1516..3df27505eec 100644 --- a/src/mongo/util/version.cpp +++ b/src/mongo/util/version.cpp @@ -150,7 +150,11 @@ void VersionInfoInterface::appendBuildInfo(BSONObjBuilder* result) const { #if MONGO_CONFIG_SSL_PROVIDER == SSL_PROVIDER_OPENSSL opensslInfo << "running" << openSSLVersion() << "compiled" << OPENSSL_VERSION_TEXT; #elif MONGO_CONFIG_SSL_PROVIDER == SSL_PROVIDER_WINDOWS - opensslInfo << "Windows SChannel"; + opensslInfo << "running" + << "Windows SChannel"; +#elif MONGO_CONFIG_SSL_PROVIDER == SSL_PROVIDER_APPLE + opensslInfo << "running" + << "Apple Secure Transport"; #else #error "Unknown SSL Provider" #endif // MONGO_CONFIG_SSL_PROVIDER |