Use OS-provided trusted root certs on Linux

First, the 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 we fall back on the compiled-in trusted root
certificates. This behavior can be overridden
with the new flags --root-certs-file and
--root-certs-cache.

R=asiva@google.com

Review URL: https://codereview.chromium.org/2346683003 .
This commit is contained in:
Zachary Anderson 2016-09-16 09:08:51 -07:00
parent 44a04f18f9
commit 139db22be5
4 changed files with 170 additions and 23 deletions

View file

@ -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)).

View file

@ -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[=<port>[/<bind-address>]]\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=<path>\n"
" The path to a file containing the trusted root certificates to use for\n"
" secure socket connections.\n"
"--root-certs-cache=<path>\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");

View file

@ -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)

View file

@ -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<unsigned char*>(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();