dart-sdk/runtime/bin/secure_socket_boringssl.cc
Ryan Macnak 877284947b Rename TARGET_OS_* to HOST_OS_*.
Like HOST_ARCH_*, HOST_OS_* describes the OS the VM is running on, which may be different from the OS the VM is generating code for during AOT compilation.

Currently we conflate the two when emitting AOT as assembly, and we get away with it because Flutter only uses assembly for targeting iOS and one can only target iOS from a Mac, but we expect to use assembly for Android as well so native tools can unwind Dart frames.

R=zra@google.com

Review-Url: https://codereview.chromium.org/2750843003 .
2017-03-15 13:11:05 -07:00

1806 lines
59 KiB
C++

// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#if !defined(DART_IO_DISABLED) && !defined(DART_IO_SECURE_SOCKET_DISABLED)
#include "platform/globals.h"
#if defined(HOST_OS_ANDROID) || defined(HOST_OS_LINUX) || \
defined(HOST_OS_WINDOWS) || defined(HOST_OS_FUCHSIA)
#include "bin/secure_socket.h"
#include "bin/secure_socket_boringssl.h"
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/pkcs12.h>
#include <openssl/safestack.h>
#include <openssl/ssl.h>
#include <openssl/tls1.h>
#include <openssl/x509.h>
#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"
#include "bin/thread.h"
#include "bin/utils.h"
#include "platform/text_buffer.h"
#include "platform/utils.h"
#include "include/dart_api.h"
// Return the error from the containing function if handle is an error handle.
#define RETURN_IF_ERROR(handle) \
{ \
Dart_Handle __handle = handle; \
if (Dart_IsError((__handle))) { \
return __handle; \
} \
}
namespace dart {
namespace bin {
bool SSLFilter::library_initialized_ = false;
// To protect library initialization.
Mutex* SSLFilter::mutex_ = new Mutex();
int SSLFilter::filter_ssl_index;
const intptr_t SSLFilter::kInternalBIOSize = 10 * KB;
const intptr_t SSLFilter::kApproximateSize =
sizeof(SSLFilter) + (2 * SSLFilter::kInternalBIOSize);
// The security context won't necessarily use the compiled-in root certificates,
// but since there is no way to update the size of the allocation after creating
// the weak persistent handle, we assume that it will. Note that when the
// root certs aren't compiled in, |root_certificates_pem_length| is 0.
const intptr_t SSLContext::kApproximateSize =
sizeof(SSLContext) + root_certificates_pem_length;
static const int kSSLFilterNativeFieldIndex = 0;
static const int kSecurityContextNativeFieldIndex = 0;
static const int kX509NativeFieldIndex = 0;
static const bool SSL_LOG_STATUS = false;
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(const SSL* ssl, TextBuffer* text_buffer) {
uint32_t error = 0;
const char* path = NULL;
int line = -1;
const char* sep = File::PathSeparator();
do {
error = ERR_get_error_line(&path, &line);
const char* file = strrchr(path, sep[0]);
path = file ? file + 1 : path;
if ((ssl != NULL) && (ERR_GET_LIB(error) == ERR_LIB_SSL) &&
(ERR_GET_REASON(error) == SSL_R_CERTIFICATE_VERIFY_FAILED)) {
intptr_t result = SSL_get_verify_result(ssl);
text_buffer->Printf("\n\t%s: %s (%s:%d)", ERR_reason_error_string(error),
X509_verify_cert_error_string(result), path, line);
} else if (error != 0) {
text_buffer->Printf("\n\t%s (%s:%d)", ERR_reason_error_string(error),
path, line);
}
} while (error != 0);
}
// Handle an error reported from the BoringSSL library.
static void ThrowIOException(int status,
const char* exception_type,
const char* message,
const SSL* ssl) {
Dart_Handle exception;
{
TextBuffer error_string(SSL_ERROR_MESSAGE_BUFFER_SIZE);
FetchErrorString(ssl, &error_string);
OSError os_error_struct(status, error_string.buf(), OSError::kBoringSSL);
Dart_Handle os_error = DartUtils::NewDartOSError(&os_error_struct);
exception =
DartUtils::NewDartIOException(exception_type, message, os_error);
ASSERT(!Dart_IsError(exception));
}
Dart_ThrowException(exception);
UNREACHABLE();
}
static SSLFilter* GetFilter(Dart_NativeArguments args) {
SSLFilter* filter;
Dart_Handle dart_this = ThrowIfError(Dart_GetNativeArgument(args, 0));
ASSERT(Dart_IsInstance(dart_this));
ThrowIfError(
Dart_GetNativeInstanceField(dart_this, kSSLFilterNativeFieldIndex,
reinterpret_cast<intptr_t*>(&filter)));
return filter;
}
static void DeleteFilter(void* isolate_data,
Dart_WeakPersistentHandle handle,
void* context_pointer) {
SSLFilter* filter = reinterpret_cast<SSLFilter*>(context_pointer);
filter->Release();
}
static Dart_Handle SetFilter(Dart_NativeArguments args, SSLFilter* filter) {
ASSERT(filter != NULL);
Dart_Handle dart_this = Dart_GetNativeArgument(args, 0);
RETURN_IF_ERROR(dart_this);
ASSERT(Dart_IsInstance(dart_this));
Dart_Handle err =
Dart_SetNativeInstanceField(dart_this, kSSLFilterNativeFieldIndex,
reinterpret_cast<intptr_t>(filter));
RETURN_IF_ERROR(err);
Dart_NewWeakPersistentHandle(dart_this, reinterpret_cast<void*>(filter),
SSLFilter::kApproximateSize, DeleteFilter);
return Dart_Null();
}
static SSLContext* GetSecurityContext(Dart_NativeArguments args) {
SSLContext* context;
Dart_Handle dart_this = ThrowIfError(Dart_GetNativeArgument(args, 0));
ASSERT(Dart_IsInstance(dart_this));
ThrowIfError(
Dart_GetNativeInstanceField(dart_this, kSecurityContextNativeFieldIndex,
reinterpret_cast<intptr_t*>(&context)));
return context;
}
static void DeleteSecurityContext(void* isolate_data,
Dart_WeakPersistentHandle handle,
void* context_pointer) {
SSLContext* context = static_cast<SSLContext*>(context_pointer);
delete context;
}
static Dart_Handle SetSecurityContext(Dart_NativeArguments args,
SSLContext* context) {
Dart_Handle dart_this = Dart_GetNativeArgument(args, 0);
RETURN_IF_ERROR(dart_this);
ASSERT(Dart_IsInstance(dart_this));
Dart_Handle err =
Dart_SetNativeInstanceField(dart_this, kSecurityContextNativeFieldIndex,
reinterpret_cast<intptr_t>(context));
RETURN_IF_ERROR(err);
Dart_NewWeakPersistentHandle(dart_this, context, SSLContext::kApproximateSize,
DeleteSecurityContext);
return Dart_Null();
}
static X509* GetX509Certificate(Dart_NativeArguments args) {
X509* certificate;
Dart_Handle dart_this = ThrowIfError(Dart_GetNativeArgument(args, 0));
ASSERT(Dart_IsInstance(dart_this));
ThrowIfError(
Dart_GetNativeInstanceField(dart_this, kX509NativeFieldIndex,
reinterpret_cast<intptr_t*>(&certificate)));
return certificate;
}
// Forward declaration.
static void SetAlpnProtocolList(Dart_Handle protocols_handle,
SSL* ssl,
SSLContext* context,
bool is_server);
void FUNCTION_NAME(SecureSocket_Init)(Dart_NativeArguments args) {
Dart_Handle dart_this = ThrowIfError(Dart_GetNativeArgument(args, 0));
SSLFilter* filter = new SSLFilter();
Dart_Handle err = SetFilter(args, filter);
if (Dart_IsError(err)) {
filter->Release();
Dart_PropagateError(err);
}
err = filter->Init(dart_this);
if (Dart_IsError(err)) {
// The finalizer was set up by SetFilter. It will delete `filter` if there
// is an error.
filter->Destroy();
Dart_PropagateError(err);
}
}
void FUNCTION_NAME(SecureSocket_Connect)(Dart_NativeArguments args) {
Dart_Handle host_name_object = ThrowIfError(Dart_GetNativeArgument(args, 1));
Dart_Handle context_object = ThrowIfError(Dart_GetNativeArgument(args, 2));
bool is_server = DartUtils::GetBooleanValue(Dart_GetNativeArgument(args, 3));
bool request_client_certificate =
DartUtils::GetBooleanValue(Dart_GetNativeArgument(args, 4));
bool require_client_certificate =
DartUtils::GetBooleanValue(Dart_GetNativeArgument(args, 5));
Dart_Handle protocols_handle = ThrowIfError(Dart_GetNativeArgument(args, 6));
const char* host_name = NULL;
// TODO(whesse): Is truncating a Dart string containing \0 what we want?
ThrowIfError(Dart_StringToCString(host_name_object, &host_name));
SSLContext* context = NULL;
if (!Dart_IsNull(context_object)) {
ThrowIfError(Dart_GetNativeInstanceField(
context_object, kSecurityContextNativeFieldIndex,
reinterpret_cast<intptr_t*>(&context)));
}
// The protocols_handle is guaranteed to be a valid Uint8List.
// It will have the correct length encoding of the protocols array.
ASSERT(!Dart_IsNull(protocols_handle));
GetFilter(args)->Connect(host_name, context->context(), is_server,
request_client_certificate,
require_client_certificate, protocols_handle);
}
void FUNCTION_NAME(SecureSocket_Destroy)(Dart_NativeArguments args) {
SSLFilter* filter = GetFilter(args);
// There are two paths that can clean up an SSLFilter object. First,
// there is this explicit call to Destroy(), called from
// _SecureFilter.destroy() in Dart code. After a call to destroy(), the Dart
// code maintains the invariant that there will be no futher SSLFilter
// requests sent to the IO Service. Therefore, the internals of the SSLFilter
// are safe to deallocate, but not the SSLFilter itself, which is already
// set up to be cleaned up by the finalizer.
//
// The second path is through the finalizer, which we have to do in case
// some mishap prevents a call to _SecureFilter.destroy().
filter->Destroy();
}
void FUNCTION_NAME(SecureSocket_Handshake)(Dart_NativeArguments args) {
GetFilter(args)->Handshake();
}
void FUNCTION_NAME(SecureSocket_GetSelectedProtocol)(
Dart_NativeArguments args) {
GetFilter(args)->GetSelectedProtocol(args);
}
void FUNCTION_NAME(SecureSocket_Renegotiate)(Dart_NativeArguments args) {
bool use_session_cache =
DartUtils::GetBooleanValue(Dart_GetNativeArgument(args, 1));
bool request_client_certificate =
DartUtils::GetBooleanValue(Dart_GetNativeArgument(args, 2));
bool require_client_certificate =
DartUtils::GetBooleanValue(Dart_GetNativeArgument(args, 3));
GetFilter(args)->Renegotiate(use_session_cache, request_client_certificate,
require_client_certificate);
}
void FUNCTION_NAME(SecureSocket_RegisterHandshakeCompleteCallback)(
Dart_NativeArguments args) {
Dart_Handle handshake_complete =
ThrowIfError(Dart_GetNativeArgument(args, 1));
if (!Dart_IsClosure(handshake_complete)) {
Dart_ThrowException(DartUtils::NewDartArgumentError(
"Illegal argument to RegisterHandshakeCompleteCallback"));
}
GetFilter(args)->RegisterHandshakeCompleteCallback(handshake_complete);
}
void FUNCTION_NAME(SecureSocket_RegisterBadCertificateCallback)(
Dart_NativeArguments args) {
Dart_Handle callback = ThrowIfError(Dart_GetNativeArgument(args, 1));
if (!Dart_IsClosure(callback) && !Dart_IsNull(callback)) {
Dart_ThrowException(DartUtils::NewDartArgumentError(
"Illegal argument to RegisterBadCertificateCallback"));
}
GetFilter(args)->RegisterBadCertificateCallback(callback);
}
void FUNCTION_NAME(SecureSocket_PeerCertificate)(Dart_NativeArguments args) {
Dart_Handle cert = ThrowIfError(GetFilter(args)->PeerCertificate());
Dart_SetReturnValue(args, cert);
}
void FUNCTION_NAME(SecureSocket_FilterPointer)(Dart_NativeArguments args) {
SSLFilter* filter = GetFilter(args);
// This filter pointer is passed to the IO Service thread. The IO Service
// thread must Release() the pointer when it is done with it.
filter->Retain();
intptr_t filter_pointer = reinterpret_cast<intptr_t>(filter);
Dart_SetReturnValue(args, Dart_NewInteger(filter_pointer));
}
static void ReleaseCertificate(void* isolate_data,
Dart_WeakPersistentHandle handle,
void* context_pointer) {
X509* cert = reinterpret_cast<X509*>(context_pointer);
X509_free(cert);
}
static intptr_t EstimateX509Size(X509* certificate) {
intptr_t length = i2d_X509(certificate, NULL);
return length > 0 ? length : 0;
}
// Returns the handle for a Dart object wrapping the X509 certificate object.
// The caller should own a reference to the X509 object whose reference count
// won't drop to zero before the ReleaseCertificate finalizer runs.
static Dart_Handle WrappedX509Certificate(X509* certificate) {
if (certificate == NULL) {
return Dart_Null();
}
Dart_Handle x509_type =
DartUtils::GetDartType(DartUtils::kIOLibURL, "X509Certificate");
if (Dart_IsError(x509_type)) {
X509_free(certificate);
return x509_type;
}
Dart_Handle arguments[] = {NULL};
Dart_Handle result =
Dart_New(x509_type, DartUtils::NewString("_"), 0, arguments);
if (Dart_IsError(result)) {
X509_free(certificate);
return result;
}
ASSERT(Dart_IsInstance(result));
Dart_Handle status = Dart_SetNativeInstanceField(
result, kX509NativeFieldIndex, reinterpret_cast<intptr_t>(certificate));
if (Dart_IsError(status)) {
X509_free(certificate);
return status;
}
const intptr_t approximate_size_of_certificate =
sizeof(*certificate) + EstimateX509Size(certificate);
ASSERT(approximate_size_of_certificate > 0);
Dart_NewWeakPersistentHandle(result, reinterpret_cast<void*>(certificate),
approximate_size_of_certificate,
ReleaseCertificate);
return result;
}
int CertificateCallback(int preverify_ok, X509_STORE_CTX* store_ctx) {
if (preverify_ok == 1) {
return 1;
}
Dart_Isolate isolate = Dart_CurrentIsolate();
if (isolate == NULL) {
FATAL("CertificateCallback called with no current isolate\n");
}
X509* certificate = X509_STORE_CTX_get_current_cert(store_ctx);
int ssl_index = SSL_get_ex_data_X509_STORE_CTX_idx();
SSL* ssl =
static_cast<SSL*>(X509_STORE_CTX_get_ex_data(store_ctx, ssl_index));
SSLFilter* filter = static_cast<SSLFilter*>(
SSL_get_ex_data(ssl, SSLFilter::filter_ssl_index));
Dart_Handle callback = filter->bad_certificate_callback();
if (Dart_IsNull(callback)) {
return 0;
}
// Upref since the Dart X509 object may outlive the SecurityContext.
if (certificate != NULL) {
X509_up_ref(certificate);
}
Dart_Handle args[1];
args[0] = WrappedX509Certificate(certificate);
if (Dart_IsError(args[0])) {
filter->callback_error = args[0];
return 0;
}
Dart_Handle result = Dart_InvokeClosure(callback, 1, args);
if (!Dart_IsError(result) && !Dart_IsBoolean(result)) {
result = Dart_NewUnhandledExceptionError(DartUtils::NewDartIOException(
"HandshakeException",
"BadCertificateCallback returned a value that was not a boolean",
Dart_Null()));
}
if (Dart_IsError(result)) {
filter->callback_error = result;
return 0;
}
return DartUtils::GetBooleanValue(result);
}
void FUNCTION_NAME(SecurityContext_Allocate)(Dart_NativeArguments args) {
SSLFilter::InitializeLibrary();
SSL_CTX* ctx = SSL_CTX_new(TLS_method());
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, CertificateCallback);
SSL_CTX_set_min_proto_version(ctx, TLS1_VERSION);
SSL_CTX_set_cipher_list(ctx, "HIGH:MEDIUM");
SSLContext* context = new SSLContext(ctx);
Dart_Handle err = SetSecurityContext(args, context);
if (Dart_IsError(err)) {
delete context;
Dart_PropagateError(err);
}
}
int PasswordCallback(char* buf, int size, int rwflag, void* userdata) {
char* password = static_cast<char*>(userdata);
ASSERT(size == PEM_BUFSIZE);
strncpy(buf, password, size);
return strlen(password);
}
void CheckStatusSSL(int status,
const char* type,
const char* message,
const SSL* ssl) {
// TODO(24183): Take appropriate action on failed calls,
// throw exception that includes all messages from the error stack.
if (status == 1) {
return;
}
if (SSL_LOG_STATUS) {
int error = ERR_get_error();
Log::PrintErr("Failed: %s status %d", message, status);
char error_string[SSL_ERROR_MESSAGE_BUFFER_SIZE];
ERR_error_string_n(error, error_string, SSL_ERROR_MESSAGE_BUFFER_SIZE);
Log::PrintErr("ERROR: %d %s\n", error, error_string);
}
ThrowIOException(status, type, message, ssl);
}
void CheckStatus(int status, const char* type, const char* message) {
CheckStatusSSL(status, type, message, NULL);
}
// Where the argument to the constructor is the handle for an object
// implementing List<int>, this class creates a scope in which a memory-backed
// BIO is allocated. Leaving the scope cleans up the BIO and the buffer that
// was used to create it.
//
// Do not make Dart_ API calls while in a ScopedMemBIO.
// Do not call Dart_PropagateError while in a ScopedMemBIO.
class ScopedMemBIO {
public:
explicit ScopedMemBIO(Dart_Handle object) {
if (!Dart_IsTypedData(object) && !Dart_IsList(object)) {
Dart_ThrowException(
DartUtils::NewDartArgumentError("Argument is not a List<int>"));
}
uint8_t* bytes = NULL;
intptr_t bytes_len = 0;
bool is_typed_data = false;
if (Dart_IsTypedData(object)) {
is_typed_data = true;
Dart_TypedData_Type typ;
ThrowIfError(Dart_TypedDataAcquireData(
object, &typ, reinterpret_cast<void**>(&bytes), &bytes_len));
} else {
ASSERT(Dart_IsList(object));
ThrowIfError(Dart_ListLength(object, &bytes_len));
bytes = Dart_ScopeAllocate(bytes_len);
ASSERT(bytes != NULL);
ThrowIfError(Dart_ListGetAsBytes(object, 0, bytes, bytes_len));
}
object_ = object;
bytes_ = bytes;
bytes_len_ = bytes_len;
bio_ = BIO_new_mem_buf(bytes, bytes_len);
ASSERT(bio_ != NULL);
is_typed_data_ = is_typed_data;
}
~ScopedMemBIO() {
ASSERT(bio_ != NULL);
if (is_typed_data_) {
BIO_free(bio_);
ThrowIfError(Dart_TypedDataReleaseData(object_));
} else {
BIO_free(bio_);
}
}
BIO* bio() {
ASSERT(bio_ != NULL);
return bio_;
}
private:
Dart_Handle object_;
uint8_t* bytes_;
intptr_t bytes_len_;
BIO* bio_;
bool is_typed_data_;
DISALLOW_ALLOCATION();
DISALLOW_COPY_AND_ASSIGN(ScopedMemBIO);
};
template <typename T, void (*free_func)(T*)>
class ScopedSSLType {
public:
explicit ScopedSSLType(T* obj) : obj_(obj) {}
~ScopedSSLType() {
if (obj_ != NULL) {
free_func(obj_);
}
}
T* get() { return obj_; }
const T* get() const { return obj_; }
T* release() {
T* result = obj_;
obj_ = NULL;
return result;
}
private:
T* obj_;
DISALLOW_ALLOCATION();
DISALLOW_COPY_AND_ASSIGN(ScopedSSLType);
};
template <typename T, typename E, void (*func)(E*)>
class ScopedSSLStackType {
public:
explicit ScopedSSLStackType(T* obj) : obj_(obj) {}
~ScopedSSLStackType() {
if (obj_ != NULL) {
sk_pop_free(reinterpret_cast<_STACK*>(obj_),
reinterpret_cast<void (*)(void*)>(func));
}
}
T* get() { return obj_; }
const T* get() const { return obj_; }
T* release() {
T* result = obj_;
obj_ = NULL;
return result;
}
private:
T* obj_;
DISALLOW_ALLOCATION();
DISALLOW_COPY_AND_ASSIGN(ScopedSSLStackType);
};
typedef ScopedSSLType<PKCS12, PKCS12_free> ScopedPKCS12;
typedef ScopedSSLType<X509, X509_free> ScopedX509;
typedef ScopedSSLStackType<STACK_OF(X509), X509, X509_free> ScopedX509Stack;
static bool NoPEMStartLine() {
uint32_t last_error = ERR_peek_last_error();
return (ERR_GET_LIB(last_error) == ERR_LIB_PEM) &&
(ERR_GET_REASON(last_error) == PEM_R_NO_START_LINE);
}
static EVP_PKEY* GetPrivateKeyPKCS12(BIO* bio, const char* password) {
ScopedPKCS12 p12(d2i_PKCS12_bio(bio, NULL));
if (p12.get() == NULL) {
return NULL;
}
EVP_PKEY* key = NULL;
X509* cert = NULL;
STACK_OF(X509)* ca_certs = NULL;
int status = PKCS12_parse(p12.get(), password, &key, &cert, &ca_certs);
if (status == 0) {
return NULL;
}
// We only care about the private key.
ScopedX509 delete_cert(cert);
ScopedX509Stack delete_ca_certs(ca_certs);
return key;
}
static EVP_PKEY* GetPrivateKey(BIO* bio, const char* password) {
EVP_PKEY* key = PEM_read_bio_PrivateKey(bio, NULL, PasswordCallback,
const_cast<char*>(password));
if (key == NULL) {
// We try reading data as PKCS12 only if reading as PEM was unsuccessful and
// if there is no indication that the data is malformed PEM. We assume the
// data is malformed PEM if it contains the start line, i.e. a line
// with ----- BEGIN.
if (NoPEMStartLine()) {
// Reset the bio, and clear the error from trying to read as PEM.
ERR_clear_error();
BIO_reset(bio);
// Try to decode as PKCS12.
key = GetPrivateKeyPKCS12(bio, password);
}
}
return key;
}
static const char* GetPasswordArgument(Dart_NativeArguments args,
intptr_t index) {
Dart_Handle password_object =
ThrowIfError(Dart_GetNativeArgument(args, index));
const char* password = NULL;
if (Dart_IsString(password_object)) {
ThrowIfError(Dart_StringToCString(password_object, &password));
if (strlen(password) > PEM_BUFSIZE - 1) {
Dart_ThrowException(DartUtils::NewDartArgumentError(
"Password length is greater than 1023 (PEM_BUFSIZE)"));
}
} else if (Dart_IsNull(password_object)) {
password = "";
} else {
Dart_ThrowException(
DartUtils::NewDartArgumentError("Password is not a String or null"));
}
return password;
}
void FUNCTION_NAME(SecurityContext_UsePrivateKeyBytes)(
Dart_NativeArguments args) {
SSLContext* context = GetSecurityContext(args);
const char* password = GetPasswordArgument(args, 2);
int status;
{
ScopedMemBIO bio(ThrowIfError(Dart_GetNativeArgument(args, 1)));
EVP_PKEY* key = GetPrivateKey(bio.bio(), password);
status = SSL_CTX_use_PrivateKey(context->context(), key);
// SSL_CTX_use_PrivateKey increments the reference count of key on success,
// so we have to call EVP_PKEY_free on both success and failure.
EVP_PKEY_free(key);
}
// TODO(24184): Handle different expected errors here - file missing,
// incorrect password, file not a PEM, and throw exceptions.
// CheckStatus should also throw an exception in uncaught cases.
CheckStatus(status, "TlsException", "Failure in usePrivateKeyBytes");
}
static int SetTrustedCertificatesBytesPKCS12(SSL_CTX* context,
BIO* bio,
const char* password) {
ScopedPKCS12 p12(d2i_PKCS12_bio(bio, NULL));
if (p12.get() == NULL) {
return 0;
}
EVP_PKEY* key = NULL;
X509* cert = NULL;
STACK_OF(X509)* ca_certs = NULL;
int status = PKCS12_parse(p12.get(), password, &key, &cert, &ca_certs);
if (status == 0) {
return status;
}
ScopedX509Stack cert_stack(ca_certs);
X509_STORE* store = SSL_CTX_get_cert_store(context);
status = X509_STORE_add_cert(store, cert);
// X509_STORE_add_cert increments the reference count of cert on success.
X509_free(cert);
if (status == 0) {
return status;
}
X509* ca;
while ((ca = sk_X509_shift(cert_stack.get())) != NULL) {
status = X509_STORE_add_cert(store, ca);
// X509_STORE_add_cert increments the reference count of cert on success.
X509_free(ca);
if (status == 0) {
return status;
}
}
return status;
}
static int SetTrustedCertificatesBytesPEM(SSL_CTX* context, BIO* bio) {
X509_STORE* store = SSL_CTX_get_cert_store(context);
int status = 0;
X509* cert = NULL;
while ((cert = PEM_read_bio_X509(bio, NULL, NULL, NULL)) != NULL) {
status = X509_STORE_add_cert(store, cert);
// X509_STORE_add_cert increments the reference count of cert on success.
X509_free(cert);
if (status == 0) {
return status;
}
}
// If no PEM start line is found, it means that we read to the end of the
// file, or that the file isn't PEM. In the first case, status will be
// non-zero indicating success. In the second case, status will be 0,
// indicating that we should try to read as PKCS12. If there is some other
// error, we return it up to the caller.
return NoPEMStartLine() ? status : 0;
}
static int SetTrustedCertificatesBytes(SSL_CTX* context,
BIO* bio,
const char* password) {
int status = SetTrustedCertificatesBytesPEM(context, bio);
if (status == 0) {
if (NoPEMStartLine()) {
ERR_clear_error();
BIO_reset(bio);
status = SetTrustedCertificatesBytesPKCS12(context, bio, password);
}
} else {
// The PEM file was successfully parsed.
ERR_clear_error();
}
return status;
}
void FUNCTION_NAME(SecurityContext_SetTrustedCertificatesBytes)(
Dart_NativeArguments args) {
SSLContext* context = GetSecurityContext(args);
const char* password = GetPasswordArgument(args, 2);
int status;
{
ScopedMemBIO bio(ThrowIfError(Dart_GetNativeArgument(args, 1)));
status =
SetTrustedCertificatesBytes(context->context(), bio.bio(), password);
}
CheckStatus(status, "TlsException", "Failure in setTrustedCertificatesBytes");
}
void FUNCTION_NAME(SecurityContext_AlpnSupported)(Dart_NativeArguments args) {
Dart_SetReturnValue(args, Dart_NewBoolean(true));
}
static void AddCompiledInCerts(SSLContext* context) {
if (root_certificates_pem == NULL) {
if (SSL_LOG_STATUS) {
Log::Print("Missing compiled-in roots\n");
}
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),
root_certificates_pem_length);
X509* root_cert;
// PEM_read_bio_X509 reads PEM-encoded certificates from a bio (in our case,
// backed by a memory buffer), and returns X509 objects, one by one.
// When the end of the bio is reached, it returns null.
while ((root_cert = PEM_read_bio_X509(roots_bio, NULL, NULL, NULL)) != NULL) {
int status = X509_STORE_add_cert(store, root_cert);
// X509_STORE_add_cert increments the reference count of cert on success.
X509_free(root_cert);
if (status == 0) {
break;
}
}
BIO_free(roots_bio);
// If there is an error here, it must be the error indicating that we are done
// 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", NULL);
}
int status = SSL_CTX_load_verify_locations(context->context(), file, NULL);
CheckStatus(status, "TlsException", "Failure trusting builtin 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",
NULL);
}
int status = SSL_CTX_load_verify_locations(context->context(), NULL, cache);
CheckStatus(status, "TlsException", "Failure trusting builtin 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(HOST_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(HOST_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(HOST_OS_ANDROID)
// Fall back on the compiled-in certs if the standard locations don't exist,
// or we aren't on Linux.
if (SSL_LOG_STATUS) {
Log::Print("Trusting compiled-in roots\n");
}
AddCompiledInCerts(context);
}
static int UseChainBytesPKCS12(SSL_CTX* context,
BIO* bio,
const char* password) {
ScopedPKCS12 p12(d2i_PKCS12_bio(bio, NULL));
if (p12.get() == NULL) {
return 0;
}
EVP_PKEY* key = NULL;
X509* cert = NULL;
STACK_OF(X509)* ca_certs = NULL;
int status = PKCS12_parse(p12.get(), password, &key, &cert, &ca_certs);
if (status == 0) {
return status;
}
ScopedX509 x509(cert);
ScopedX509Stack certs(ca_certs);
status = SSL_CTX_use_certificate(context, x509.get());
if (ERR_peek_error() != 0) {
// Key/certificate mismatch doesn't imply status is 0.
status = 0;
}
if (status == 0) {
return status;
}
SSL_CTX_clear_chain_certs(context);
X509* ca;
while ((ca = sk_X509_shift(certs.get())) != NULL) {
status = SSL_CTX_add0_chain_cert(context, ca);
// SSL_CTX_add0_chain_cert does not inc ref count, so don't free unless the
// call fails.
if (status == 0) {
X509_free(ca);
return status;
}
}
return status;
}
static int UseChainBytesPEM(SSL_CTX* context, BIO* bio) {
int status = 0;
ScopedX509 x509(PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL));
if (x509.get() == NULL) {
return 0;
}
status = SSL_CTX_use_certificate(context, x509.get());
if (ERR_peek_error() != 0) {
// Key/certificate mismatch doesn't imply status is 0.
status = 0;
}
if (status == 0) {
return status;
}
SSL_CTX_clear_chain_certs(context);
X509* ca;
while ((ca = PEM_read_bio_X509(bio, NULL, NULL, NULL)) != NULL) {
status = SSL_CTX_add0_chain_cert(context, ca);
// SSL_CTX_add0_chain_cert does not inc ref count, so don't free unless the
// call fails.
if (status == 0) {
X509_free(ca);
return status;
}
// Note that we must not free `ca` if it was successfully added to the
// chain. We must free the main certificate x509, though since its reference
// count is increased by SSL_CTX_use_certificate.
}
return NoPEMStartLine() ? status : 0;
}
static int UseChainBytes(SSL_CTX* context, BIO* bio, const char* password) {
int status = UseChainBytesPEM(context, bio);
if (status == 0) {
if (NoPEMStartLine()) {
ERR_clear_error();
BIO_reset(bio);
status = UseChainBytesPKCS12(context, bio, password);
}
} else {
// The PEM file was successfully read.
ERR_clear_error();
}
return status;
}
void FUNCTION_NAME(SecurityContext_UseCertificateChainBytes)(
Dart_NativeArguments args) {
SSLContext* context = GetSecurityContext(args);
const char* password = GetPasswordArgument(args, 2);
int status;
{
ScopedMemBIO bio(ThrowIfError(Dart_GetNativeArgument(args, 1)));
status = UseChainBytes(context->context(), bio.bio(), password);
}
CheckStatus(status, "TlsException", "Failure in useCertificateChainBytes");
}
static int SetClientAuthoritiesPKCS12(SSL_CTX* context,
BIO* bio,
const char* password) {
ScopedPKCS12 p12(d2i_PKCS12_bio(bio, NULL));
if (p12.get() == NULL) {
return 0;
}
EVP_PKEY* key = NULL;
X509* cert = NULL;
STACK_OF(X509)* ca_certs = NULL;
int status = PKCS12_parse(p12.get(), password, &key, &cert, &ca_certs);
if (status == 0) {
return status;
}
ScopedX509Stack cert_stack(ca_certs);
status = SSL_CTX_add_client_CA(context, cert);
// SSL_CTX_add_client_CA increments the reference count of cert on success.
X509_free(cert);
if (status == 0) {
return status;
}
X509* ca;
while ((ca = sk_X509_shift(cert_stack.get())) != NULL) {
status = SSL_CTX_add_client_CA(context, ca);
// SSL_CTX_add_client_CA increments the reference count of ca on success.
X509_free(ca); // The name has been extracted.
if (status == 0) {
return status;
}
}
return status;
}
static int SetClientAuthoritiesPEM(SSL_CTX* context, BIO* bio) {
int status = 0;
X509* cert = NULL;
while ((cert = PEM_read_bio_X509(bio, NULL, NULL, NULL)) != NULL) {
status = SSL_CTX_add_client_CA(context, cert);
X509_free(cert); // The name has been extracted.
if (status == 0) {
return status;
}
}
return NoPEMStartLine() ? status : 0;
}
static int SetClientAuthorities(SSL_CTX* context,
BIO* bio,
const char* password) {
int status = SetClientAuthoritiesPEM(context, bio);
if (status == 0) {
if (NoPEMStartLine()) {
ERR_clear_error();
BIO_reset(bio);
status = SetClientAuthoritiesPKCS12(context, bio, password);
}
} else {
// The PEM file was successfully parsed.
ERR_clear_error();
}
return status;
}
void FUNCTION_NAME(SecurityContext_SetClientAuthoritiesBytes)(
Dart_NativeArguments args) {
SSLContext* context = GetSecurityContext(args);
const char* password = GetPasswordArgument(args, 2);
int status;
{
ScopedMemBIO bio(ThrowIfError(Dart_GetNativeArgument(args, 1)));
status = SetClientAuthorities(context->context(), bio.bio(), password);
}
CheckStatus(status, "TlsException", "Failure in setClientAuthoritiesBytes");
}
void FUNCTION_NAME(SecurityContext_SetAlpnProtocols)(
Dart_NativeArguments args) {
SSLContext* context = GetSecurityContext(args);
Dart_Handle protocols_handle = ThrowIfError(Dart_GetNativeArgument(args, 1));
Dart_Handle is_server_handle = ThrowIfError(Dart_GetNativeArgument(args, 2));
if (Dart_IsBoolean(is_server_handle)) {
bool is_server = DartUtils::GetBooleanValue(is_server_handle);
SetAlpnProtocolList(protocols_handle, NULL, context, is_server);
} else {
Dart_ThrowException(DartUtils::NewDartArgumentError(
"Non-boolean is_server argument passed to SetAlpnProtocols"));
}
}
void FUNCTION_NAME(X509_Subject)(Dart_NativeArguments args) {
X509* certificate = GetX509Certificate(args);
X509_NAME* subject = X509_get_subject_name(certificate);
char* subject_string = X509_NAME_oneline(subject, NULL, 0);
Dart_SetReturnValue(args, Dart_NewStringFromCString(subject_string));
OPENSSL_free(subject_string);
}
void FUNCTION_NAME(X509_Issuer)(Dart_NativeArguments args) {
X509* certificate = GetX509Certificate(args);
X509_NAME* issuer = X509_get_issuer_name(certificate);
char* issuer_string = X509_NAME_oneline(issuer, NULL, 0);
Dart_SetReturnValue(args, Dart_NewStringFromCString(issuer_string));
OPENSSL_free(issuer_string);
}
static Dart_Handle ASN1TimeToMilliseconds(ASN1_TIME* aTime) {
ASN1_UTCTIME* epoch_start = M_ASN1_UTCTIME_new();
ASN1_UTCTIME_set_string(epoch_start, "700101000000Z");
int days;
int seconds;
int result = ASN1_TIME_diff(&days, &seconds, epoch_start, aTime);
M_ASN1_UTCTIME_free(epoch_start);
if (result != 1) {
// TODO(whesse): Propagate an error to Dart.
Log::PrintErr("ASN1Time error %d\n", result);
}
return Dart_NewInteger((86400LL * days + seconds) * 1000LL);
}
void FUNCTION_NAME(X509_StartValidity)(Dart_NativeArguments args) {
X509* certificate = GetX509Certificate(args);
ASN1_TIME* not_before = X509_get_notBefore(certificate);
Dart_SetReturnValue(args, ASN1TimeToMilliseconds(not_before));
}
void FUNCTION_NAME(X509_EndValidity)(Dart_NativeArguments args) {
X509* certificate = GetX509Certificate(args);
ASN1_TIME* not_after = X509_get_notAfter(certificate);
Dart_SetReturnValue(args, ASN1TimeToMilliseconds(not_after));
}
/**
* Pushes data through the SSL filter, reading and writing from circular
* buffers shared with Dart.
*
* The Dart _SecureFilterImpl class contains 4 ExternalByteArrays used to
* pass encrypted and plaintext data to and from the C++ SSLFilter object.
*
* ProcessFilter is called with a CObject array containing the pointer to
* the SSLFilter, encoded as an int, and the start and end positions of the
* valid data in the four circular buffers. The function only reads from
* the valid data area of the input buffers, and only writes to the free
* area of the output buffers. The function returns the new start and end
* positions in the buffers, but it only updates start for input buffers, and
* end for output buffers. Therefore, the Dart thread can simultaneously
* write to the free space and end pointer of input buffers, and read from
* the data space of output buffers, and modify the start pointer.
*
* When ProcessFilter returns, the Dart thread is responsible for combining
* the updated pointers from Dart and C++, to make the new valid state of
* the circular buffer.
*/
CObject* SSLFilter::ProcessFilterRequest(const CObjectArray& request) {
CObjectIntptr filter_object(request[0]);
SSLFilter* filter = reinterpret_cast<SSLFilter*>(filter_object.Value());
RefCntReleaseScope<SSLFilter> rs(filter);
bool in_handshake = CObjectBool(request[1]).Value();
int starts[SSLFilter::kNumBuffers];
int ends[SSLFilter::kNumBuffers];
for (int i = 0; i < SSLFilter::kNumBuffers; ++i) {
starts[i] = CObjectInt32(request[2 * i + 2]).Value();
ends[i] = CObjectInt32(request[2 * i + 3]).Value();
}
if (filter->ProcessAllBuffers(starts, ends, in_handshake)) {
CObjectArray* result =
new CObjectArray(CObject::NewArray(SSLFilter::kNumBuffers * 2));
for (int i = 0; i < SSLFilter::kNumBuffers; ++i) {
result->SetAt(2 * i, new CObjectInt32(CObject::NewInt32(starts[i])));
result->SetAt(2 * i + 1, new CObjectInt32(CObject::NewInt32(ends[i])));
}
return result;
} else {
int32_t error_code = static_cast<int32_t>(ERR_peek_error());
TextBuffer error_string(SSL_ERROR_MESSAGE_BUFFER_SIZE);
FetchErrorString(filter->ssl_, &error_string);
CObjectArray* result = new CObjectArray(CObject::NewArray(2));
result->SetAt(0, new CObjectInt32(CObject::NewInt32(error_code)));
result->SetAt(1, new CObjectString(CObject::NewString(error_string.buf())));
return result;
}
}
bool SSLFilter::ProcessAllBuffers(int starts[kNumBuffers],
int ends[kNumBuffers],
bool in_handshake) {
for (int i = 0; i < kNumBuffers; ++i) {
if (in_handshake && (i == kReadPlaintext || i == kWritePlaintext)) continue;
int start = starts[i];
int end = ends[i];
int size = isBufferEncrypted(i) ? encrypted_buffer_size_ : buffer_size_;
if (start < 0 || end < 0 || start >= size || end >= size) {
FATAL("Out-of-bounds internal buffer access in dart:io SecureSocket");
}
switch (i) {
case kReadPlaintext:
case kWriteEncrypted:
// Write data to the circular buffer's free space. If the buffer
// is full, neither if statement is executed and nothing happens.
if (start <= end) {
// If the free space may be split into two segments,
// then the first is [end, size), unless start == 0.
// Then, since the last free byte is at position start - 2,
// the interval is [end, size - 1).
int buffer_end = (start == 0) ? size - 1 : size;
int bytes = (i == kReadPlaintext)
? ProcessReadPlaintextBuffer(end, buffer_end)
: ProcessWriteEncryptedBuffer(end, buffer_end);
if (bytes < 0) return false;
end += bytes;
ASSERT(end <= size);
if (end == size) end = 0;
}
if (start > end + 1) {
int bytes = (i == kReadPlaintext)
? ProcessReadPlaintextBuffer(end, start - 1)
: ProcessWriteEncryptedBuffer(end, start - 1);
if (bytes < 0) return false;
end += bytes;
ASSERT(end < start);
}
ends[i] = end;
break;
case kReadEncrypted:
case kWritePlaintext:
// Read/Write data from circular buffer. If the buffer is empty,
// neither if statement's condition is true.
if (end < start) {
// Data may be split into two segments. In this case,
// the first is [start, size).
int bytes = (i == kReadEncrypted)
? ProcessReadEncryptedBuffer(start, size)
: ProcessWritePlaintextBuffer(start, size);
if (bytes < 0) return false;
start += bytes;
ASSERT(start <= size);
if (start == size) start = 0;
}
if (start < end) {
int bytes = (i == kReadEncrypted)
? ProcessReadEncryptedBuffer(start, end)
: ProcessWritePlaintextBuffer(start, end);
if (bytes < 0) return false;
start += bytes;
ASSERT(start <= end);
}
starts[i] = start;
break;
default:
UNREACHABLE();
}
}
return true;
}
Dart_Handle SSLFilter::Init(Dart_Handle dart_this) {
if (!library_initialized_) {
InitializeLibrary();
}
ASSERT(string_start_ == NULL);
string_start_ = Dart_NewPersistentHandle(DartUtils::NewString("start"));
ASSERT(string_start_ != NULL);
ASSERT(string_length_ == NULL);
string_length_ = Dart_NewPersistentHandle(DartUtils::NewString("length"));
ASSERT(string_length_ != NULL);
ASSERT(bad_certificate_callback_ == NULL);
bad_certificate_callback_ = Dart_NewPersistentHandle(Dart_Null());
ASSERT(bad_certificate_callback_ != NULL);
// Caller handles cleanup on an error.
return InitializeBuffers(dart_this);
}
Dart_Handle SSLFilter::InitializeBuffers(Dart_Handle dart_this) {
// Create SSLFilter buffers as ExternalUint8Array objects.
Dart_Handle buffers_string = DartUtils::NewString("buffers");
RETURN_IF_ERROR(buffers_string);
Dart_Handle dart_buffers_object = Dart_GetField(dart_this, buffers_string);
RETURN_IF_ERROR(dart_buffers_object);
Dart_Handle secure_filter_impl_type = Dart_InstanceGetType(dart_this);
RETURN_IF_ERROR(secure_filter_impl_type);
Dart_Handle size_string = DartUtils::NewString("SIZE");
RETURN_IF_ERROR(size_string);
Dart_Handle dart_buffer_size =
Dart_GetField(secure_filter_impl_type, size_string);
RETURN_IF_ERROR(dart_buffer_size);
int64_t buffer_size = 0;
Dart_Handle err = Dart_IntegerToInt64(dart_buffer_size, &buffer_size);
RETURN_IF_ERROR(err);
Dart_Handle encrypted_size_string = DartUtils::NewString("ENCRYPTED_SIZE");
RETURN_IF_ERROR(encrypted_size_string);
Dart_Handle dart_encrypted_buffer_size =
Dart_GetField(secure_filter_impl_type, encrypted_size_string);
RETURN_IF_ERROR(dart_encrypted_buffer_size);
int64_t encrypted_buffer_size = 0;
err = Dart_IntegerToInt64(dart_encrypted_buffer_size, &encrypted_buffer_size);
RETURN_IF_ERROR(err);
if (buffer_size <= 0 || buffer_size > 1 * MB) {
FATAL("Invalid buffer size in _ExternalBuffer");
}
if (encrypted_buffer_size <= 0 || encrypted_buffer_size > 1 * MB) {
FATAL("Invalid encrypted buffer size in _ExternalBuffer");
}
buffer_size_ = static_cast<int>(buffer_size);
encrypted_buffer_size_ = static_cast<int>(encrypted_buffer_size);
Dart_Handle data_identifier = DartUtils::NewString("data");
RETURN_IF_ERROR(data_identifier);
for (int i = 0; i < kNumBuffers; i++) {
int size = isBufferEncrypted(i) ? encrypted_buffer_size_ : buffer_size_;
buffers_[i] = new uint8_t[size];
ASSERT(buffers_[i] != NULL);
dart_buffer_objects_[i] = NULL;
}
Dart_Handle result = Dart_Null();
for (int i = 0; i < kNumBuffers; ++i) {
int size = isBufferEncrypted(i) ? encrypted_buffer_size_ : buffer_size_;
result = Dart_ListGetAt(dart_buffers_object, i);
if (Dart_IsError(result)) {
break;
}
dart_buffer_objects_[i] = Dart_NewPersistentHandle(result);
ASSERT(dart_buffer_objects_[i] != NULL);
Dart_Handle data =
Dart_NewExternalTypedData(Dart_TypedData_kUint8, buffers_[i], size);
if (Dart_IsError(data)) {
result = data;
break;
}
result = Dart_HandleFromPersistent(dart_buffer_objects_[i]);
if (Dart_IsError(result)) {
break;
}
result = Dart_SetField(result, data_identifier, data);
if (Dart_IsError(result)) {
break;
}
}
// Caller handles cleanup on an error.
return result;
}
void SSLFilter::RegisterHandshakeCompleteCallback(Dart_Handle complete) {
ASSERT(NULL == handshake_complete_);
handshake_complete_ = Dart_NewPersistentHandle(complete);
ASSERT(handshake_complete_ != NULL);
}
void SSLFilter::RegisterBadCertificateCallback(Dart_Handle callback) {
ASSERT(bad_certificate_callback_ != NULL);
Dart_DeletePersistentHandle(bad_certificate_callback_);
bad_certificate_callback_ = Dart_NewPersistentHandle(callback);
ASSERT(bad_certificate_callback_ != NULL);
}
void SSLFilter::InitializeLibrary() {
MutexLocker locker(mutex_);
if (!library_initialized_) {
SSL_library_init();
filter_ssl_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
ASSERT(filter_ssl_index >= 0);
library_initialized_ = true;
}
}
Dart_Handle SSLFilter::PeerCertificate() {
// SSL_get_peer_certificate incs the refcount of certificate. X509_free is
// called by the finalizer set up by WrappedX509Certificate.
X509* certificate = SSL_get_peer_certificate(ssl_);
return WrappedX509Certificate(certificate);
}
int AlpnCallback(SSL* ssl,
const uint8_t** out,
uint8_t* outlen,
const uint8_t* in,
unsigned int inlen,
void* arg) {
// 'in' and 'arg' are sequences of (length, data) strings with 1-byte lengths.
// 'arg' is 0-terminated. Finds the first string in 'arg' that is in 'in'.
uint8_t* server_list = static_cast<uint8_t*>(arg);
while (*server_list != 0) {
uint8_t protocol_length = *server_list++;
const uint8_t* client_list = in;
while (client_list < in + inlen) {
uint8_t client_protocol_length = *client_list++;
if (client_protocol_length == protocol_length) {
if (0 == memcmp(server_list, client_list, protocol_length)) {
*out = client_list;
*outlen = client_protocol_length;
return SSL_TLSEXT_ERR_OK; // Success
}
}
client_list += client_protocol_length;
}
server_list += protocol_length;
}
// TODO(23580): Make failure send a fatal alert instead of ignoring ALPN.
return SSL_TLSEXT_ERR_NOACK;
}
// Sets the protocol list for ALPN on a SSL object or a context.
static void SetAlpnProtocolList(Dart_Handle protocols_handle,
SSL* ssl,
SSLContext* context,
bool is_server) {
// Enable ALPN (application layer protocol negotiation) if the caller provides
// a valid list of supported protocols.
Dart_TypedData_Type protocols_type;
uint8_t* protocol_string = NULL;
uint8_t* protocol_string_copy = NULL;
intptr_t protocol_string_len = 0;
int status;
Dart_Handle result = Dart_TypedDataAcquireData(
protocols_handle, &protocols_type,
reinterpret_cast<void**>(&protocol_string), &protocol_string_len);
if (Dart_IsError(result)) {
Dart_PropagateError(result);
}
if (protocols_type != Dart_TypedData_kUint8) {
Dart_TypedDataReleaseData(protocols_handle);
Dart_PropagateError(Dart_NewApiError(
"Unexpected type for protocols (expected valid Uint8List)."));
}
if (protocol_string_len > 0) {
if (is_server) {
// ALPN on server connections must be set on an SSL_CTX object,
// not on the SSL object of the individual connection.
ASSERT(context != NULL);
ASSERT(ssl == NULL);
// Because it must be passed as a single void*, terminate
// the list of (length, data) strings with a length 0 string.
protocol_string_copy =
static_cast<uint8_t*>(malloc(protocol_string_len + 1));
memmove(protocol_string_copy, protocol_string, protocol_string_len);
protocol_string_copy[protocol_string_len] = '\0';
SSL_CTX_set_alpn_select_cb(context->context(), AlpnCallback,
protocol_string_copy);
context->set_alpn_protocol_string(protocol_string_copy);
} else {
// The function makes a local copy of protocol_string, which it owns.
if (ssl != NULL) {
ASSERT(context == NULL);
status = SSL_set_alpn_protos(ssl, protocol_string, protocol_string_len);
} else {
ASSERT(context != NULL);
ASSERT(ssl == NULL);
status = SSL_CTX_set_alpn_protos(context->context(), protocol_string,
protocol_string_len);
}
ASSERT(status == 0); // The function returns a non-standard status.
}
}
Dart_TypedDataReleaseData(protocols_handle);
}
void SSLFilter::Connect(const char* hostname,
SSL_CTX* context,
bool is_server,
bool request_client_certificate,
bool require_client_certificate,
Dart_Handle protocols_handle) {
is_server_ = is_server;
if (in_handshake_) {
FATAL("Connect called twice on the same _SecureFilter.");
}
int status;
int error;
BIO* ssl_side;
status = BIO_new_bio_pair(&ssl_side, kInternalBIOSize, &socket_side_,
kInternalBIOSize);
CheckStatusSSL(status, "TlsException", "BIO_new_bio_pair", ssl_);
assert(context != NULL);
ssl_ = SSL_new(context);
SSL_set_bio(ssl_, ssl_side, ssl_side);
SSL_set_mode(ssl_, SSL_MODE_AUTO_RETRY); // TODO(whesse): Is this right?
SSL_set_ex_data(ssl_, filter_ssl_index, this);
if (is_server_) {
int certificate_mode =
request_client_certificate ? SSL_VERIFY_PEER : SSL_VERIFY_NONE;
if (require_client_certificate) {
certificate_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
}
SSL_set_verify(ssl_, certificate_mode, NULL);
} else {
SetAlpnProtocolList(protocols_handle, ssl_, NULL, false);
status = SSL_set_tlsext_host_name(ssl_, hostname);
CheckStatusSSL(status, "TlsException", "Set SNI host name", ssl_);
// Sets the hostname in the certificate-checking object, so it is checked
// against the certificate presented by the server.
X509_VERIFY_PARAM* certificate_checking_parameters = SSL_get0_param(ssl_);
hostname_ = strdup(hostname);
X509_VERIFY_PARAM_set_flags(
certificate_checking_parameters,
X509_V_FLAG_PARTIAL_CHAIN | X509_V_FLAG_TRUSTED_FIRST);
X509_VERIFY_PARAM_set_hostflags(certificate_checking_parameters, 0);
status = X509_VERIFY_PARAM_set1_host(certificate_checking_parameters,
hostname_, strlen(hostname_));
CheckStatusSSL(status, "TlsException",
"Set hostname for certificate checking", ssl_);
}
// Make the connection:
if (is_server_) {
status = SSL_accept(ssl_);
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);
}
}
} else {
status = SSL_connect(ssl_);
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);
}
}
}
Handshake();
}
int printErrorCallback(const char* str, size_t len, void* ctx) {
Log::PrintErr("%.*s\n", static_cast<int>(len), str);
return 1;
}
void SSLFilter::Handshake() {
// Try and push handshake along.
int status;
status = SSL_do_handshake(ssl_);
if (callback_error != NULL) {
// The SSL_do_handshake will try performing a handshake and might call
// a CertificateCallback. If the certificate validation
// failed the 'callback_error" will be set by the certificateCallback
// logic and we propagate the error"
Dart_PropagateError(callback_error);
}
if (SSL_want_write(ssl_) || SSL_want_read(ssl_)) {
in_handshake_ = true;
return;
}
CheckStatusSSL(
status, "HandshakeException",
is_server_ ? "Handshake error in server" : "Handshake error in client",
ssl_);
// Handshake succeeded.
if (in_handshake_) {
// TODO(24071): Check return value of SSL_get_verify_result, this
// should give us the hostname check.
int result = SSL_get_verify_result(ssl_);
if (SSL_LOG_STATUS) {
Log::Print("Handshake verification status: %d\n", result);
X509* peer_certificate = SSL_get_peer_certificate(ssl_);
if (peer_certificate == NULL) {
Log::Print("No peer certificate received\n");
} else {
X509_NAME* s_name = X509_get_subject_name(peer_certificate);
printf("Peer certificate SN: ");
X509_NAME_print_ex_fp(stdout, s_name, 4, 0);
printf("\n");
}
}
ThrowIfError(Dart_InvokeClosure(
Dart_HandleFromPersistent(handshake_complete_), 0, NULL));
in_handshake_ = false;
}
}
void SSLFilter::GetSelectedProtocol(Dart_NativeArguments args) {
const uint8_t* protocol;
unsigned length;
SSL_get0_alpn_selected(ssl_, &protocol, &length);
if (length == 0) {
Dart_SetReturnValue(args, Dart_Null());
} else {
Dart_SetReturnValue(args, Dart_NewStringFromUTF8(protocol, length));
}
}
void SSLFilter::Renegotiate(bool use_session_cache,
bool request_client_certificate,
bool require_client_certificate) {
// The SSL_REQUIRE_CERTIFICATE option only takes effect if the
// SSL_REQUEST_CERTIFICATE option is also set, so set it.
request_client_certificate =
request_client_certificate || require_client_certificate;
// TODO(24070, 24069): Implement setting the client certificate parameters,
// and triggering rehandshake.
}
void SSLFilter::FreeResources() {
if (ssl_ != NULL) {
SSL_free(ssl_);
ssl_ = NULL;
}
if (socket_side_ != NULL) {
BIO_free(socket_side_);
socket_side_ = NULL;
}
if (hostname_ != NULL) {
free(hostname_);
hostname_ = NULL;
}
for (int i = 0; i < kNumBuffers; ++i) {
if (buffers_[i] != NULL) {
delete[] buffers_[i];
buffers_[i] = NULL;
}
}
}
SSLFilter::~SSLFilter() {
FreeResources();
}
void SSLFilter::Destroy() {
for (int i = 0; i < kNumBuffers; ++i) {
if (dart_buffer_objects_[i] != NULL) {
Dart_DeletePersistentHandle(dart_buffer_objects_[i]);
dart_buffer_objects_[i] = NULL;
}
}
if (string_start_ != NULL) {
Dart_DeletePersistentHandle(string_start_);
string_start_ = NULL;
}
if (string_length_ != NULL) {
Dart_DeletePersistentHandle(string_length_);
string_length_ = NULL;
}
if (handshake_complete_ != NULL) {
Dart_DeletePersistentHandle(handshake_complete_);
handshake_complete_ = NULL;
}
if (bad_certificate_callback_ != NULL) {
Dart_DeletePersistentHandle(bad_certificate_callback_);
bad_certificate_callback_ = NULL;
}
FreeResources();
}
/* Read decrypted data from the filter to the circular buffer */
int SSLFilter::ProcessReadPlaintextBuffer(int start, int end) {
int length = end - start;
int bytes_processed = 0;
if (length > 0) {
bytes_processed = SSL_read(
ssl_, reinterpret_cast<char*>((buffers_[kReadPlaintext] + start)),
length);
if (bytes_processed < 0) {
int error = SSL_get_error(ssl_, bytes_processed);
USE(error);
bytes_processed = 0;
}
}
return bytes_processed;
}
int SSLFilter::ProcessWritePlaintextBuffer(int start, int end) {
int length = end - start;
int bytes_processed =
SSL_write(ssl_, buffers_[kWritePlaintext] + start, length);
if (bytes_processed < 0) {
if (SSL_LOG_DATA) {
Log::Print("SSL_write returned error %d\n", bytes_processed);
}
return 0;
}
return bytes_processed;
}
/* Read encrypted data from the circular buffer to the filter */
int SSLFilter::ProcessReadEncryptedBuffer(int start, int end) {
int length = end - start;
if (SSL_LOG_DATA)
Log::Print("Entering ProcessReadEncryptedBuffer with %d bytes\n", length);
int bytes_processed = 0;
if (length > 0) {
bytes_processed =
BIO_write(socket_side_, buffers_[kReadEncrypted] + start, length);
if (bytes_processed <= 0) {
bool retry = BIO_should_retry(socket_side_);
if (!retry) {
if (SSL_LOG_DATA)
Log::Print("BIO_write failed in ReadEncryptedBuffer\n");
}
bytes_processed = 0;
}
}
if (SSL_LOG_DATA)
Log::Print("Leaving ProcessReadEncryptedBuffer wrote %d bytes\n",
bytes_processed);
return bytes_processed;
}
int SSLFilter::ProcessWriteEncryptedBuffer(int start, int end) {
int length = end - start;
int bytes_processed = 0;
if (length > 0) {
bytes_processed =
BIO_read(socket_side_, buffers_[kWriteEncrypted] + start, length);
if (bytes_processed < 0) {
if (SSL_LOG_DATA)
Log::Print("WriteEncrypted BIO_read returned error %d\n",
bytes_processed);
return 0;
} else {
if (SSL_LOG_DATA)
Log::Print("WriteEncrypted BIO_read wrote %d bytes\n",
bytes_processed);
}
}
return bytes_processed;
}
} // namespace bin
} // namespace dart
#endif // defined(HOST_OS_LINUX)
#endif // !defined(DART_IO_DISABLED) &&
// !defined(DART_IO_SECURE_SOCKET_DISABLED)