diff --git a/CHANGELOG.md b/CHANGELOG.md index e1ed14455af..7c874dca2a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ [article on native extensions](https://www.dartlang.org/articles/dart-vm/native-extensions) to reflect the VM's improved behavior. +* We have improved the way the VM searches for trusted root certificates for + secure socket connections on Linux. First, the VM will look for trusted root + certificates in standard locations on the file system + (/etc/pki/tls/certs/ca-bundle.crt followed by /etc/ssl/certs), and only if + these do not exist will it fall back on the builtin trusted root certificates. + This behavior can be overridden on Linux with the new flags + --root-certs-file and --root-certs-cache. The former is the path to a file + containing the trusted root certificates, and the latter is the path to a + directory containing root certificate files hashed using `c_rehash`. + ### Core library changes * `dart:core`: Remove deprecated `Resource` class. Use the class in `package:resource` instead. @@ -21,7 +31,7 @@ * Added `WebSocket.addUtf8Text` to allow sending a pre-encoded text message without a round-trip UTF-8 conversion. -## Strong Mode +### Strong Mode * Breaking change - it is an error if a generic type parameter cannot be inferred (SDK issue [26992](https://github.com/dart-lang/sdk/issues/26992)). diff --git a/runtime/bin/main.cc b/runtime/bin/main.cc index 9f7123843e8..277220ef1fc 100644 --- a/runtime/bin/main.cc +++ b/runtime/bin/main.cc @@ -494,6 +494,43 @@ static bool ProcessShortSocketWriteOption(const char* arg, } +#if !defined(TARGET_OS_MACOS) +extern const char* commandline_root_certs_file; +extern const char* commandline_root_certs_cache; + +static bool ProcessRootCertsFileOption(const char* arg, + CommandLineOptions* vm_options) { + ASSERT(arg != NULL); + if (*arg == '-') { + return false; + } + if (commandline_root_certs_cache != NULL) { + Log::PrintErr("Only one of --root-certs-file and --root-certs-cache " + "may be specified"); + return false; + } + commandline_root_certs_file = arg; + return true; +} + + +static bool ProcessRootCertsCacheOption(const char* arg, + CommandLineOptions* vm_options) { + ASSERT(arg != NULL); + if (*arg == '-') { + return false; + } + if (commandline_root_certs_file != NULL) { + Log::PrintErr("Only one of --root-certs-file and --root-certs-cache " + "may be specified"); + return false; + } + commandline_root_certs_cache = arg; + return true; +} +#endif // !defined(TARGET_OS_MACOS) + + static struct { const char* option_name; bool (*process)(const char* option, CommandLineOptions* vm_options); @@ -522,6 +559,10 @@ static struct { { "--hot-reload-rollback-test-mode", ProcessHotReloadRollbackTestModeOption }, { "--short_socket_read", ProcessShortSocketReadOption }, { "--short_socket_write", ProcessShortSocketWriteOption }, +#if !defined(TARGET_OS_MACOS) + { "--root-certs-file=", ProcessRootCertsFileOption }, + { "--root-certs-cache=", ProcessRootCertsCacheOption }, +#endif // !defined(TARGET_OS_MACOS) { NULL, NULL } }; @@ -960,6 +1001,15 @@ static void PrintUsage() { "--enable-vm-service[=[/]]\n" " enables the VM service and listens on specified port for connections\n" " (default port number is 8181, default bind address is 127.0.0.1).\n" +#if !defined(TARGET_OS_MACOS) +"\n" +"--root-certs-file=\n" +" The path to a file containing the trusted root certificates to use for\n" +" secure socket connections.\n" +"--root-certs-cache=\n" +" The path to a cache directory containing the trusted root certificates to\n" +" use for secure socket connections.\n" +#endif // !defined(TARGET_OS_MACOS) "\n" "The following options are only used for VM development and may\n" "be changed in any future version:\n"); diff --git a/runtime/bin/root_certificates_unsupported.cc b/runtime/bin/root_certificates_unsupported.cc index a899e3c2a5b..304a59e4bb0 100644 --- a/runtime/bin/root_certificates_unsupported.cc +++ b/runtime/bin/root_certificates_unsupported.cc @@ -5,7 +5,9 @@ #if !defined(DART_IO_DISABLED) && !defined(DART_IO_SECURE_SOCKET_DISABLED) #include "platform/globals.h" -#if defined(TARGET_OS_MACOS) || defined(TARGET_OS_ANDROID) +#if defined(TARGET_OS_MACOS) || \ + defined(TARGET_OS_ANDROID) || \ + defined(DART_IO_ROOT_CERTS_DISABLED) namespace dart { namespace bin { @@ -16,7 +18,7 @@ unsigned int root_certificates_pem_length = 0; } // namespace bin } // namespace dart -#endif // defined(TARGET_OS_MACOS) || defined(TARGET_OS_ANDROID) +#endif // defined(TARGET_OS_MACOS) || defined(TARGET_OS_ANDROID) || ... #endif // !defined(DART_IO_DISABLED) && // !defined(DART_IO_SECURE_SOCKET_DISABLED) diff --git a/runtime/bin/secure_socket_boringssl.cc b/runtime/bin/secure_socket_boringssl.cc index e881815796a..132588d31c4 100644 --- a/runtime/bin/secure_socket_boringssl.cc +++ b/runtime/bin/secure_socket_boringssl.cc @@ -28,6 +28,8 @@ #include "bin/builtin.h" #include "bin/dartutils.h" +#include "bin/directory.h" +#include "bin/file.h" #include "bin/lockers.h" #include "bin/log.h" #include "bin/socket.h" @@ -64,6 +66,10 @@ static const bool SSL_LOG_DATA = false; static const int SSL_ERROR_MESSAGE_BUFFER_SIZE = 1000; +const char* commandline_root_certs_file = NULL; +const char* commandline_root_certs_cache = NULL; + + /* Get the error messages from BoringSSL, and put them in buffer as a * null-terminated string. */ static void FetchErrorString(char* buffer, int length) { @@ -788,22 +794,10 @@ void FUNCTION_NAME(SecurityContext_AlpnSupported)(Dart_NativeArguments args) { } -void FUNCTION_NAME(SecurityContext_TrustBuiltinRoots)( - Dart_NativeArguments args) { - SSLContext* context = GetSecurityContext(args); -#if defined(TARGET_OS_ANDROID) - // On Android, we don't compile in the trusted root certificates. Insead, - // we use the directory of trusted certificates already present on the device. - // This saves ~240KB from the size of the binary. This has the drawback that - // SSL_do_handshake will synchronously hit the filesystem looking for root - // certs during its trust evaluation. We call SSL_do_handshake directly from - // the Dart thread so that Dart code can be invoked from the "bad certificate" - // callback called by SSL_do_handshake. - const char* android_cacerts = "/system/etc/security/cacerts"; - int status = SSL_CTX_load_verify_locations( - context->context(), NULL, android_cacerts); - CheckStatus(status, "TlsException", "Failure trusting builtint roots"); -#else +static void AddCompiledInCerts(SSLContext* context) { + if (root_certificates_pem == NULL) { + return; + } X509_STORE* store = SSL_CTX_get_cert_store(context->context()); BIO* roots_bio = BIO_new_mem_buf(const_cast(root_certificates_pem), @@ -825,7 +819,90 @@ void FUNCTION_NAME(SecurityContext_TrustBuiltinRoots)( // reading PEM certificates. ASSERT((ERR_peek_error() == 0) || NoPEMStartLine()); ERR_clear_error(); +} + + +static void LoadRootCertFile(SSLContext* context, const char* file) { + if (SSL_LOG_STATUS) { + Log::Print("Looking for trusted roots in %s\n", file); + } + if (!File::Exists(file)) { + ThrowIOException(-1, "TlsException", "Failed to find root cert file"); + } + int status = SSL_CTX_load_verify_locations(context->context(), file, NULL); + CheckStatus(status, "TlsException", "Failure trusting builtint roots"); + if (SSL_LOG_STATUS) { + Log::Print("Trusting roots from: %s\n", file); + } +} + + +static void LoadRootCertCache(SSLContext* context, const char* cache) { + if (SSL_LOG_STATUS) { + Log::Print("Looking for trusted roots in %s\n", cache); + } + if (Directory::Exists(cache) != Directory::EXISTS) { + ThrowIOException(-1, "TlsException", "Failed to find root cert cache"); + } + int status = SSL_CTX_load_verify_locations(context->context(), NULL, cache); + CheckStatus(status, "TlsException", "Failure trusting builtint roots"); + if (SSL_LOG_STATUS) { + Log::Print("Trusting roots from: %s\n", cache); + } +} + + +void FUNCTION_NAME(SecurityContext_TrustBuiltinRoots)( + Dart_NativeArguments args) { + SSLContext* context = GetSecurityContext(args); + + // First, try to use locations specified on the command line. + if (commandline_root_certs_file != NULL) { + LoadRootCertFile(context, commandline_root_certs_file); + return; + } + + if (commandline_root_certs_cache != NULL) { + LoadRootCertCache(context, commandline_root_certs_cache); + return; + } + +#if defined(TARGET_OS_ANDROID) + // On Android, we don't compile in the trusted root certificates. Insead, + // we use the directory of trusted certificates already present on the device. + // This saves ~240KB from the size of the binary. This has the drawback that + // SSL_do_handshake will synchronously hit the filesystem looking for root + // certs during its trust evaluation. We call SSL_do_handshake directly from + // the Dart thread so that Dart code can be invoked from the "bad certificate" + // callback called by SSL_do_handshake. + const char* android_cacerts = "/system/etc/security/cacerts"; + LoadRootCertCache(context, android_cacerts); + return; +#elif defined(TARGET_OS_LINUX) + // On Linux, we use the compiled-in trusted certs as a last resort. First, + // we try to find the trusted certs in various standard locations. A good + // discussion of the complexities of this endeavor can be found here: + // + // https://www.happyassassin.net/2015/01/12/a-note-about-ssltls-trusted-certificate-stores-and-platforms/ + const char* bundle = "/etc/pki/tls/certs/ca-bundle.crt"; + const char* cachedir = "/etc/ssl/certs"; + if (File::Exists(bundle)) { + LoadRootCertFile(context, bundle); + return; + } + + if (Directory::Exists(cachedir) == Directory::EXISTS) { + LoadRootCertCache(context, cachedir); + return; + } #endif // defined(TARGET_OS_ANDROID) + + // Fall back on the compiled-in certs if the standard locations don't exist, + // or we aren't on Linux. + AddCompiledInCerts(context); + if (SSL_LOG_STATUS) { + Log::Print("Trusting compiled-in roots\n"); + } } @@ -1494,19 +1571,27 @@ void SSLFilter::Connect(const char* hostname, // Make the connection: if (is_server_) { status = SSL_accept(ssl_); - if (SSL_LOG_STATUS) Log::Print("SSL_accept status: %d\n", status); + if (SSL_LOG_STATUS) { + Log::Print("SSL_accept status: %d\n", status); + } if (status != 1) { // TODO(whesse): expect a needs-data error here. Handle other errors. error = SSL_get_error(ssl_, status); - if (SSL_LOG_STATUS) Log::Print("SSL_accept error: %d\n", error); + if (SSL_LOG_STATUS) { + Log::Print("SSL_accept error: %d\n", error); + } } } else { status = SSL_connect(ssl_); - if (SSL_LOG_STATUS) Log::Print("SSL_connect status: %d\n", status); + if (SSL_LOG_STATUS) { + Log::Print("SSL_connect status: %d\n", status); + } if (status != 1) { // TODO(whesse): expect a needs-data error here. Handle other errors. error = SSL_get_error(ssl_, status); - if (SSL_LOG_STATUS) Log::Print("SSL_connect error: %d\n", error); + if (SSL_LOG_STATUS) { + Log::Print("SSL_connect error: %d\n", error); + } } } Handshake();