c256ddfe10
BUG=dartbug.com/11384 R=ajohnsen@google.com Review URL: https://codereview.chromium.org//18984008 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@24916 260f80e4-7a28-3924-810f-c04153c831b5
1004 lines
36 KiB
C++
1004 lines
36 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.
|
|
|
|
#include "bin/secure_socket.h"
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <key.h>
|
|
#include <keyt.h>
|
|
#include <nss.h>
|
|
#include <pk11pub.h>
|
|
#include <prerror.h>
|
|
#include <prinit.h>
|
|
#include <prnetdb.h>
|
|
#include <secmod.h>
|
|
#include <ssl.h>
|
|
#include <sslproto.h>
|
|
|
|
#include "bin/builtin.h"
|
|
#include "bin/dartutils.h"
|
|
#include "bin/net/nss_memio.h"
|
|
#include "bin/socket.h"
|
|
#include "bin/thread.h"
|
|
#include "bin/utils.h"
|
|
#include "platform/utils.h"
|
|
|
|
#include "include/dart_api.h"
|
|
|
|
|
|
namespace dart {
|
|
namespace bin {
|
|
|
|
bool SSLFilter::library_initialized_ = false;
|
|
// To protect library initialization.
|
|
dart::Mutex* SSLFilter::mutex_ = new dart::Mutex();
|
|
// The password is needed when creating secure server sockets. It can
|
|
// be null if only secure client sockets are used.
|
|
const char* SSLFilter::password_ = NULL;
|
|
|
|
// Forward declaration.
|
|
static void ProcessFilter(Dart_Port dest_port_id,
|
|
Dart_Port reply_port_id,
|
|
Dart_CObject* message);
|
|
|
|
NativeService SSLFilter::filter_service_("FilterService", ProcessFilter, 16);
|
|
|
|
static const int kSSLFilterNativeFieldIndex = 0;
|
|
|
|
|
|
/* Handle an error reported from the NSS library. */
|
|
static void ThrowPRException(const char* exception_type,
|
|
const char* message,
|
|
bool free_message = false) {
|
|
PRErrorCode error_code = PR_GetError();
|
|
const char* error_message = PR_ErrorToString(error_code, PR_LANGUAGE_EN);
|
|
OSError os_error_struct(error_code, error_message, OSError::kNSS);
|
|
Dart_Handle os_error = DartUtils::NewDartOSError(&os_error_struct);
|
|
Dart_Handle exception =
|
|
DartUtils::NewDartIOException(exception_type, message, os_error);
|
|
if (free_message) {
|
|
free(const_cast<char*>(message));
|
|
}
|
|
Dart_ThrowException(exception);
|
|
}
|
|
|
|
|
|
static void ThrowCertificateException(const char* format,
|
|
const char* certificate_name) {
|
|
int length = strlen(certificate_name);
|
|
length += strlen(format);
|
|
char* message = reinterpret_cast<char*>(malloc(length + 1));
|
|
if (message == NULL) {
|
|
FATAL("Out of memory formatting CertificateException for throwing");
|
|
}
|
|
snprintf(message, length + 1, format, certificate_name);
|
|
message[length] = '\0';
|
|
ThrowPRException("CertificateException", message, true);
|
|
}
|
|
|
|
|
|
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 SetFilter(Dart_NativeArguments args, SSLFilter* filter) {
|
|
Dart_Handle dart_this = ThrowIfError(Dart_GetNativeArgument(args, 0));
|
|
ASSERT(Dart_IsInstance(dart_this));
|
|
ThrowIfError(Dart_SetNativeInstanceField(
|
|
dart_this,
|
|
kSSLFilterNativeFieldIndex,
|
|
reinterpret_cast<intptr_t>(filter)));
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(SecureSocket_Init)(Dart_NativeArguments args) {
|
|
Dart_EnterScope();
|
|
Dart_Handle dart_this = ThrowIfError(Dart_GetNativeArgument(args, 0));
|
|
SSLFilter* filter = new SSLFilter;
|
|
SetFilter(args, filter);
|
|
filter->Init(dart_this);
|
|
Dart_ExitScope();
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(SecureSocket_Connect)(Dart_NativeArguments args) {
|
|
Dart_EnterScope();
|
|
Dart_Handle host_name_object = ThrowIfError(Dart_GetNativeArgument(args, 1));
|
|
Dart_Handle host_sockaddr_storage_object =
|
|
ThrowIfError(Dart_GetNativeArgument(args, 2));
|
|
Dart_Handle port_object = ThrowIfError(Dart_GetNativeArgument(args, 3));
|
|
bool is_server = DartUtils::GetBooleanValue(Dart_GetNativeArgument(args, 4));
|
|
Dart_Handle certificate_name_object =
|
|
ThrowIfError(Dart_GetNativeArgument(args, 5));
|
|
bool request_client_certificate =
|
|
DartUtils::GetBooleanValue(Dart_GetNativeArgument(args, 6));
|
|
bool require_client_certificate =
|
|
DartUtils::GetBooleanValue(Dart_GetNativeArgument(args, 7));
|
|
bool send_client_certificate =
|
|
DartUtils::GetBooleanValue(Dart_GetNativeArgument(args, 8));
|
|
|
|
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));
|
|
|
|
RawAddr raw_addr;
|
|
Dart_TypedData_Type type;
|
|
uint8_t* buffer = NULL;
|
|
intptr_t len;
|
|
ThrowIfError(Dart_TypedDataAcquireData(host_sockaddr_storage_object,
|
|
&type,
|
|
reinterpret_cast<void**>(&buffer),
|
|
&len));
|
|
ASSERT(static_cast<size_t>(len) <= sizeof(raw_addr));
|
|
memmove(&raw_addr, buffer, len);
|
|
Dart_TypedDataReleaseData(host_sockaddr_storage_object);
|
|
|
|
int64_t port;
|
|
if (!DartUtils::GetInt64Value(port_object, &port)) {
|
|
FATAL("The range of port_object was checked in Dart - it cannot fail here");
|
|
}
|
|
|
|
const char* certificate_name = NULL;
|
|
if (Dart_IsString(certificate_name_object)) {
|
|
ThrowIfError(Dart_StringToCString(certificate_name_object,
|
|
&certificate_name));
|
|
}
|
|
// If this is a server connection, it must have a certificate to connect with.
|
|
ASSERT(!is_server || certificate_name != NULL);
|
|
|
|
GetFilter(args)->Connect(host_name,
|
|
&raw_addr,
|
|
static_cast<int>(port),
|
|
is_server,
|
|
certificate_name,
|
|
request_client_certificate,
|
|
require_client_certificate,
|
|
send_client_certificate);
|
|
Dart_ExitScope();
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(SecureSocket_Destroy)(Dart_NativeArguments args) {
|
|
Dart_EnterScope();
|
|
SSLFilter* filter = GetFilter(args);
|
|
SetFilter(args, NULL);
|
|
filter->Destroy();
|
|
delete filter;
|
|
Dart_ExitScope();
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(SecureSocket_Handshake)(Dart_NativeArguments args) {
|
|
Dart_EnterScope();
|
|
GetFilter(args)->Handshake();
|
|
Dart_ExitScope();
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(SecureSocket_Renegotiate)(Dart_NativeArguments args) {
|
|
Dart_EnterScope();
|
|
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);
|
|
Dart_ExitScope();
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(SecureSocket_RegisterHandshakeCompleteCallback)(
|
|
Dart_NativeArguments args) {
|
|
Dart_EnterScope();
|
|
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);
|
|
Dart_ExitScope();
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(SecureSocket_RegisterBadCertificateCallback)(
|
|
Dart_NativeArguments args) {
|
|
Dart_EnterScope();
|
|
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);
|
|
Dart_ExitScope();
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(SecureSocket_InitializeLibrary)
|
|
(Dart_NativeArguments args) {
|
|
Dart_EnterScope();
|
|
Dart_Handle certificate_database_object =
|
|
ThrowIfError(Dart_GetNativeArgument(args, 0));
|
|
// Check that the type is string, and get the UTF-8 C string value from it.
|
|
const char* certificate_database = NULL;
|
|
if (Dart_IsString(certificate_database_object)) {
|
|
ThrowIfError(Dart_StringToCString(certificate_database_object,
|
|
&certificate_database));
|
|
} else if (!Dart_IsNull(certificate_database_object)) {
|
|
Dart_ThrowException(DartUtils::NewDartArgumentError(
|
|
"Non-String certificate directory argument to SetCertificateDatabase"));
|
|
}
|
|
// Leave certificate_database as NULL if no value was provided.
|
|
|
|
Dart_Handle password_object = ThrowIfError(Dart_GetNativeArgument(args, 1));
|
|
// Check that the type is string or null,
|
|
// and get the UTF-8 C string value from it.
|
|
const char* password = NULL;
|
|
if (Dart_IsString(password_object)) {
|
|
ThrowIfError(Dart_StringToCString(password_object, &password));
|
|
} else if (Dart_IsNull(password_object)) {
|
|
// Pass the empty string as the password.
|
|
password = "";
|
|
} else {
|
|
Dart_ThrowException(DartUtils::NewDartArgumentError(
|
|
"Password argument to SetCertificateDatabase is not a String or null"));
|
|
}
|
|
|
|
Dart_Handle builtin_roots_object =
|
|
ThrowIfError(Dart_GetNativeArgument(args, 2));
|
|
// Check that the type is boolean, and get the boolean value from it.
|
|
bool builtin_roots = true;
|
|
if (Dart_IsBoolean(builtin_roots_object)) {
|
|
ThrowIfError(Dart_BooleanValue(builtin_roots_object, &builtin_roots));
|
|
} else {
|
|
Dart_ThrowException(DartUtils::NewDartArgumentError(
|
|
"UseBuiltinRoots argument to SetCertificateDatabase is not a bool"));
|
|
}
|
|
|
|
SSLFilter::InitializeLibrary(certificate_database, password, builtin_roots);
|
|
Dart_ExitScope();
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(SecureSocket_PeerCertificate)
|
|
(Dart_NativeArguments args) {
|
|
Dart_EnterScope();
|
|
Dart_SetReturnValue(args, GetFilter(args)->PeerCertificate());
|
|
Dart_ExitScope();
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(SecureSocket_FilterPointer)(Dart_NativeArguments args) {
|
|
Dart_EnterScope();
|
|
intptr_t filter_pointer = reinterpret_cast<intptr_t>(GetFilter(args));
|
|
Dart_SetReturnValue(args, Dart_NewInteger(filter_pointer));
|
|
Dart_ExitScope();
|
|
}
|
|
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
static void ProcessFilter(Dart_Port dest_port_id,
|
|
Dart_Port reply_port_id,
|
|
Dart_CObject* message) {
|
|
CObjectArray args(message);
|
|
CObjectIntptr filter_object(args[0]);
|
|
SSLFilter* filter = reinterpret_cast<SSLFilter*>(filter_object.Value());
|
|
bool in_handshake = CObjectBool(args[1]).Value();
|
|
int starts[SSLFilter::kNumBuffers];
|
|
int ends[SSLFilter::kNumBuffers];
|
|
for (int i = 0; i < SSLFilter::kNumBuffers; ++i) {
|
|
starts[i] = CObjectInt32(args[2 * i + 2]).Value();
|
|
ends[i] = CObjectInt32(args[2 * i + 3]).Value();
|
|
}
|
|
|
|
if (filter->ProcessAllBuffers(starts, ends, in_handshake)) {
|
|
for (int i = 0; i < SSLFilter::kNumBuffers; ++i) {
|
|
args[2 * i + 2]->AsApiCObject()->value.as_int32 = starts[i];
|
|
args[2 * i + 3]->AsApiCObject()->value.as_int32 = ends[i];
|
|
}
|
|
Dart_PostCObject(reply_port_id, args.AsApiCObject());
|
|
} else {
|
|
PRErrorCode error_code = PR_GetError();
|
|
const char* error_message = PR_ErrorToString(error_code, PR_LANGUAGE_EN);
|
|
CObjectArray* result = new CObjectArray(CObject::NewArray(2));
|
|
result->SetAt(0, new CObjectInt32(CObject::NewInt32(error_code)));
|
|
result->SetAt(1, new CObjectString(CObject::NewString(error_message)));
|
|
Dart_PostCObject(reply_port_id, result->AsApiCObject());
|
|
}
|
|
}
|
|
|
|
|
|
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:
|
|
// Read data from circular buffer.
|
|
if (end < start) {
|
|
// Data may be split into two segments. In this case,
|
|
// the first is [start, size).
|
|
int bytes = ProcessReadEncryptedBuffer(start, size);
|
|
if (bytes < 0) return false;
|
|
start += bytes;
|
|
ASSERT(start <= size);
|
|
if (start == size) start = 0;
|
|
}
|
|
if (start < end) {
|
|
int bytes = ProcessReadEncryptedBuffer(start, end);
|
|
if (bytes < 0) return false;
|
|
start += bytes;
|
|
ASSERT(start <= end);
|
|
}
|
|
starts[i] = start;
|
|
break;
|
|
case kWritePlaintext:
|
|
if (end < start) {
|
|
// Data is split into two segments, [start, size) and [0, end).
|
|
int bytes = ProcessWritePlaintextBuffer(start, size, 0, end);
|
|
if (bytes < 0) return false;
|
|
start += bytes;
|
|
if (start >= size) start -= size;
|
|
} else {
|
|
int bytes = ProcessWritePlaintextBuffer(start, end, 0, 0);
|
|
if (bytes < 0) return false;
|
|
start += bytes;
|
|
ASSERT(start <= end);
|
|
}
|
|
starts[i] = start;
|
|
break;
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
static Dart_Handle X509FromCertificate(CERTCertificate* certificate) {
|
|
PRTime start_validity;
|
|
PRTime end_validity;
|
|
SECStatus status =
|
|
CERT_GetCertTimes(certificate, &start_validity, &end_validity);
|
|
if (status != SECSuccess) {
|
|
ThrowPRException("CertificateException",
|
|
"Cannot get validity times from certificate");
|
|
}
|
|
int64_t start_epoch_ms = start_validity / PR_USEC_PER_MSEC;
|
|
int64_t end_epoch_ms = end_validity / PR_USEC_PER_MSEC;
|
|
Dart_Handle subject_name_object =
|
|
DartUtils::NewString(certificate->subjectName);
|
|
Dart_Handle issuer_name_object =
|
|
DartUtils::NewString(certificate->issuerName);
|
|
Dart_Handle start_epoch_ms_int = Dart_NewInteger(start_epoch_ms);
|
|
Dart_Handle end_epoch_ms_int = Dart_NewInteger(end_epoch_ms);
|
|
|
|
Dart_Handle date_type =
|
|
DartUtils::GetDartType(DartUtils::kCoreLibURL, "DateTime");
|
|
Dart_Handle from_milliseconds =
|
|
DartUtils::NewString("fromMillisecondsSinceEpoch");
|
|
|
|
Dart_Handle start_validity_date =
|
|
Dart_New(date_type, from_milliseconds, 1, &start_epoch_ms_int);
|
|
Dart_Handle end_validity_date =
|
|
Dart_New(date_type, from_milliseconds, 1, &end_epoch_ms_int);
|
|
|
|
Dart_Handle x509_type =
|
|
DartUtils::GetDartType(DartUtils::kIOLibURL, "X509Certificate");
|
|
Dart_Handle arguments[] = { subject_name_object,
|
|
issuer_name_object,
|
|
start_validity_date,
|
|
end_validity_date };
|
|
return Dart_New(x509_type, Dart_Null(), 4, arguments);
|
|
}
|
|
|
|
|
|
void SSLFilter::Init(Dart_Handle dart_this) {
|
|
if (!library_initialized_) {
|
|
InitializeLibrary(NULL, "", true, false);
|
|
}
|
|
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);
|
|
|
|
InitializeBuffers(dart_this);
|
|
filter_ = memio_CreateIOLayer(kMemioBufferSize);
|
|
}
|
|
|
|
|
|
void SSLFilter::InitializeBuffers(Dart_Handle dart_this) {
|
|
// Create SSLFilter buffers as ExternalUint8Array objects.
|
|
Dart_Handle dart_buffers_object = ThrowIfError(
|
|
Dart_GetField(dart_this, DartUtils::NewString("buffers")));
|
|
Dart_Handle secure_filter_impl_type =
|
|
Dart_InstanceGetType(dart_this);
|
|
Dart_Handle dart_buffer_size = ThrowIfError(
|
|
Dart_GetField(secure_filter_impl_type, DartUtils::NewString("SIZE")));
|
|
int64_t buffer_size = DartUtils::GetIntegerValue(dart_buffer_size);
|
|
Dart_Handle dart_encrypted_buffer_size = ThrowIfError(
|
|
Dart_GetField(secure_filter_impl_type,
|
|
DartUtils::NewString("ENCRYPTED_SIZE")));
|
|
int64_t encrypted_buffer_size =
|
|
DartUtils::GetIntegerValue(dart_encrypted_buffer_size);
|
|
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");
|
|
for (int i = 0; i < kNumBuffers; ++i) {
|
|
int size = isBufferEncrypted(i) ? encrypted_buffer_size_ : buffer_size_;
|
|
dart_buffer_objects_[i] =
|
|
Dart_NewPersistentHandle(Dart_ListGetAt(dart_buffers_object, i));
|
|
ASSERT(dart_buffer_objects_[i] != NULL);
|
|
buffers_[i] = new uint8_t[size];
|
|
Dart_Handle data = ThrowIfError(
|
|
Dart_NewExternalTypedData(Dart_TypedData_kUint8, buffers_[i], size));
|
|
ThrowIfError(
|
|
Dart_SetField(Dart_HandleFromPersistent(dart_buffer_objects_[i]),
|
|
data_identifier,
|
|
data));
|
|
}
|
|
}
|
|
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
char* PasswordCallback(PK11SlotInfo* slot, PRBool retry, void* arg) {
|
|
if (!retry) {
|
|
return PL_strdup(static_cast<char*>(arg)); // Freed by NSS internals.
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static const char* builtin_roots_module =
|
|
#if defined(TARGET_OS_LINUX) || defined(TARGET_OS_ANDROID)
|
|
"name=\"Root Certs\" library=\"libnssckbi.so\"";
|
|
#elif defined(TARGET_OS_MACOS)
|
|
"name=\"Root Certs\" library=\"libnssckbi.dylib\"";
|
|
#elif defined(TARGET_OS_WINDOWS)
|
|
"name=\"Root Certs\" library=\"nssckbi.dll\"";
|
|
#else
|
|
#error Automatic target os detection failed.
|
|
#endif
|
|
|
|
|
|
|
|
void SSLFilter::InitializeLibrary(const char* certificate_database,
|
|
const char* password,
|
|
bool use_builtin_root_certificates,
|
|
bool report_duplicate_initialization) {
|
|
MutexLocker locker(mutex_);
|
|
SECStatus status;
|
|
if (!library_initialized_) {
|
|
PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
|
|
// TODO(whesse): Verify there are no UTF-8 issues here.
|
|
if (certificate_database == NULL || certificate_database[0] == '\0') {
|
|
status = NSS_NoDB_Init(NULL);
|
|
if (status != SECSuccess) {
|
|
mutex_->Unlock(); // MutexLocker destructor not called when throwing.
|
|
ThrowPRException("TlsException",
|
|
"Failed NSS_NoDB_Init call.");
|
|
}
|
|
if (use_builtin_root_certificates) {
|
|
SECMODModule* module = SECMOD_LoadUserModule(
|
|
const_cast<char*>(builtin_roots_module), NULL, PR_FALSE);
|
|
if (!module) {
|
|
mutex_->Unlock(); // MutexLocker destructor not called when throwing.
|
|
ThrowPRException("TlsException",
|
|
"Failed to load builtin root certificates.");
|
|
}
|
|
}
|
|
} else {
|
|
PRUint32 init_flags = NSS_INIT_READONLY;
|
|
if (!use_builtin_root_certificates) {
|
|
init_flags |= NSS_INIT_NOMODDB;
|
|
}
|
|
status = NSS_Initialize(certificate_database,
|
|
"",
|
|
"",
|
|
SECMOD_DB,
|
|
init_flags);
|
|
if (status != SECSuccess) {
|
|
mutex_->Unlock(); // MutexLocker destructor not called when throwing.
|
|
ThrowPRException("TlsException",
|
|
"Failed NSS_Init call.");
|
|
}
|
|
password_ = strdup(password); // This one copy persists until Dart exits.
|
|
PK11_SetPasswordFunc(PasswordCallback);
|
|
}
|
|
library_initialized_ = true;
|
|
|
|
status = NSS_SetDomesticPolicy();
|
|
if (status != SECSuccess) {
|
|
mutex_->Unlock(); // MutexLocker destructor not called when throwing.
|
|
ThrowPRException("TlsException",
|
|
"Failed NSS_SetDomesticPolicy call.");
|
|
}
|
|
// Enable TLS, as well as SSL3 and SSL2.
|
|
status = SSL_OptionSetDefault(SSL_ENABLE_TLS, PR_TRUE);
|
|
if (status != SECSuccess) {
|
|
mutex_->Unlock(); // MutexLocker destructor not called when throwing.
|
|
ThrowPRException("TlsException",
|
|
"Failed SSL_OptionSetDefault enable TLS call.");
|
|
}
|
|
status = SSL_ConfigServerSessionIDCache(0, 0, 0, NULL);
|
|
if (status != SECSuccess) {
|
|
mutex_->Unlock(); // MutexLocker destructor not called when throwing.
|
|
ThrowPRException("TlsException",
|
|
"Failed SSL_ConfigServerSessionIDCache call.");
|
|
}
|
|
|
|
} else if (report_duplicate_initialization) {
|
|
mutex_->Unlock(); // MutexLocker destructor not called when throwing.
|
|
// Like ThrowPRException, without adding an OSError.
|
|
Dart_ThrowException(DartUtils::NewDartIOException("TlsException",
|
|
"Called SecureSocket.initialize more than once",
|
|
Dart_Null()));
|
|
}
|
|
}
|
|
|
|
|
|
SECStatus BadCertificateCallback(void* filter, PRFileDesc* fd) {
|
|
SSLFilter* ssl_filter = static_cast<SSLFilter*>(filter);
|
|
Dart_Handle callback = ssl_filter->bad_certificate_callback();
|
|
if (Dart_IsNull(callback)) return SECFailure;
|
|
|
|
Dart_EnterScope();
|
|
Dart_Handle x509_object = ssl_filter->PeerCertificate();
|
|
Dart_Handle result =
|
|
ThrowIfError(Dart_InvokeClosure(callback, 1, &x509_object));
|
|
bool c_result = Dart_IsBoolean(result) && DartUtils::GetBooleanValue(result);
|
|
Dart_ExitScope();
|
|
return c_result ? SECSuccess : SECFailure;
|
|
}
|
|
|
|
|
|
Dart_Handle SSLFilter::PeerCertificate() {
|
|
CERTCertificate* certificate = SSL_PeerCertificate(filter_);
|
|
if (certificate == NULL) return Dart_Null();
|
|
Dart_Handle x509_object = X509FromCertificate(certificate);
|
|
CERT_DestroyCertificate(certificate);
|
|
return x509_object;
|
|
}
|
|
|
|
|
|
void SSLFilter::Connect(const char* host_name,
|
|
RawAddr* raw_addr,
|
|
int port,
|
|
bool is_server,
|
|
const char* certificate_name,
|
|
bool request_client_certificate,
|
|
bool require_client_certificate,
|
|
bool send_client_certificate) {
|
|
is_server_ = is_server;
|
|
if (in_handshake_) {
|
|
FATAL("Connect called twice on the same _SecureFilter.");
|
|
}
|
|
|
|
if (!is_server && certificate_name != NULL) {
|
|
client_certificate_name_ = strdup(certificate_name);
|
|
}
|
|
|
|
filter_ = SSL_ImportFD(NULL, filter_);
|
|
if (filter_ == NULL) {
|
|
ThrowPRException("TlsException", "Failed SSL_ImportFD call");
|
|
}
|
|
|
|
SSLVersionRange vrange;
|
|
vrange.min = SSL_LIBRARY_VERSION_3_0;
|
|
vrange.max = SSL_LIBRARY_VERSION_TLS_1_1;
|
|
SSL_VersionRangeSet(filter_, &vrange);
|
|
|
|
SECStatus status;
|
|
if (is_server) {
|
|
CERTCertificate* certificate = NULL;
|
|
if (strstr(certificate_name, "CN=") != NULL) {
|
|
// Look up certificate using the distinguished name (DN) certificate_name.
|
|
CERTCertDBHandle* certificate_database = CERT_GetDefaultCertDB();
|
|
if (certificate_database == NULL) {
|
|
ThrowPRException("CertificateException",
|
|
"Certificate database cannot be loaded");
|
|
}
|
|
certificate = CERT_FindCertByNameString(certificate_database,
|
|
const_cast<char*>(certificate_name));
|
|
if (certificate == NULL) {
|
|
ThrowCertificateException(
|
|
"Cannot find server certificate by distinguished name: %s",
|
|
certificate_name);
|
|
}
|
|
} else {
|
|
// Look up certificate using the nickname certificate_name.
|
|
certificate = PK11_FindCertFromNickname(
|
|
const_cast<char*>(certificate_name),
|
|
static_cast<void*>(const_cast<char*>(password_)));
|
|
if (certificate == NULL) {
|
|
ThrowCertificateException(
|
|
"Cannot find server certificate by nickname: %s",
|
|
certificate_name);
|
|
}
|
|
}
|
|
SECKEYPrivateKey* key = PK11_FindKeyByAnyCert(
|
|
certificate,
|
|
static_cast<void*>(const_cast<char*>(password_)));
|
|
if (key == NULL) {
|
|
CERT_DestroyCertificate(certificate);
|
|
if (PR_GetError() == -8177) {
|
|
ThrowPRException("CertificateException",
|
|
"Certificate database password incorrect");
|
|
} else {
|
|
ThrowCertificateException(
|
|
"Cannot find private key for certificate %s",
|
|
certificate_name);
|
|
}
|
|
}
|
|
// kt_rsa (key type RSA) is an enum constant from the NSS libraries.
|
|
// TODO(whesse): Allow different key types.
|
|
status = SSL_ConfigSecureServer(filter_, certificate, key, kt_rsa);
|
|
CERT_DestroyCertificate(certificate);
|
|
SECKEY_DestroyPrivateKey(key);
|
|
if (status != SECSuccess) {
|
|
ThrowCertificateException(
|
|
"Failed SSL_ConfigSecureServer call with certificate %s",
|
|
certificate_name);
|
|
}
|
|
|
|
if (request_client_certificate) {
|
|
status = SSL_OptionSet(filter_, SSL_REQUEST_CERTIFICATE, PR_TRUE);
|
|
if (status != SECSuccess) {
|
|
ThrowPRException("TlsException",
|
|
"Failed SSL_OptionSet(REQUEST_CERTIFICATE) call");
|
|
}
|
|
status = SSL_OptionSet(filter_,
|
|
SSL_REQUIRE_CERTIFICATE,
|
|
require_client_certificate);
|
|
if (status != SECSuccess) {
|
|
ThrowPRException("TlsException",
|
|
"Failed SSL_OptionSet(REQUIRE_CERTIFICATE) call");
|
|
}
|
|
}
|
|
} else { // Client.
|
|
if (SSL_SetURL(filter_, host_name) == -1) {
|
|
ThrowPRException("TlsException",
|
|
"Failed SetURL call");
|
|
}
|
|
|
|
// This disables the SSL session cache for client connections.
|
|
// This resolves issue 7208, but degrades performance.
|
|
// TODO(7230): Reenable session cache, without breaking client connections.
|
|
status = SSL_OptionSet(filter_, SSL_NO_CACHE, PR_TRUE);
|
|
if (status != SECSuccess) {
|
|
ThrowPRException("TlsException",
|
|
"Failed SSL_OptionSet(NO_CACHE) call");
|
|
}
|
|
|
|
if (send_client_certificate) {
|
|
SSL_SetPKCS11PinArg(filter_, const_cast<char*>(password_));
|
|
status = SSL_GetClientAuthDataHook(
|
|
filter_,
|
|
NSS_GetClientAuthData,
|
|
static_cast<void*>(client_certificate_name_));
|
|
if (status != SECSuccess) {
|
|
ThrowPRException("TlsException",
|
|
"Failed SSL_GetClientAuthDataHook call");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Install bad certificate callback, and pass 'this' to it if it is called.
|
|
status = SSL_BadCertHook(filter_,
|
|
BadCertificateCallback,
|
|
static_cast<void*>(this));
|
|
|
|
status = SSL_ResetHandshake(filter_, is_server);
|
|
if (status != SECSuccess) {
|
|
ThrowPRException("TlsException",
|
|
"Failed SSL_ResetHandshake call");
|
|
}
|
|
|
|
// Set the peer address from the address passed. The DNS has already
|
|
// been done in Dart code, so just use that address. This relies on
|
|
// following about PRNetAddr: "The raw member of the union is
|
|
// equivalent to struct sockaddr", which is stated in the NSS
|
|
// documentation.
|
|
PRNetAddr peername;
|
|
memset(&peername, 0, sizeof(peername));
|
|
intptr_t len = SocketAddress::GetAddrLength(raw_addr);
|
|
ASSERT(static_cast<size_t>(len) <= sizeof(peername));
|
|
memmove(&peername, &raw_addr->addr, len);
|
|
|
|
// Adjust the address family field for BSD, whose sockaddr
|
|
// structure has a one-byte length and one-byte address family
|
|
// field at the beginning. PRNetAddr has a two-byte address
|
|
// family field at the beginning.
|
|
peername.raw.family = raw_addr->addr.sa_family;
|
|
|
|
memio_SetPeerName(filter_, &peername);
|
|
}
|
|
|
|
|
|
void SSLFilter::Handshake() {
|
|
SECStatus status = SSL_ForceHandshake(filter_);
|
|
if (status == SECSuccess) {
|
|
if (in_handshake_) {
|
|
ThrowIfError(Dart_InvokeClosure(
|
|
Dart_HandleFromPersistent(handshake_complete_), 0, NULL));
|
|
in_handshake_ = false;
|
|
}
|
|
} else {
|
|
PRErrorCode error = PR_GetError();
|
|
if (error == PR_WOULD_BLOCK_ERROR) {
|
|
if (!in_handshake_) {
|
|
in_handshake_ = true;
|
|
}
|
|
} else {
|
|
if (is_server_) {
|
|
ThrowPRException("HandshakeException",
|
|
"Handshake error in server");
|
|
} else {
|
|
ThrowPRException("HandshakeException",
|
|
"Handshake error in client");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void SSLFilter::Renegotiate(bool use_session_cache,
|
|
bool request_client_certificate,
|
|
bool require_client_certificate) {
|
|
SECStatus status;
|
|
// 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;
|
|
|
|
status = SSL_OptionSet(filter_,
|
|
SSL_REQUEST_CERTIFICATE,
|
|
request_client_certificate);
|
|
if (status != SECSuccess) {
|
|
ThrowPRException("TlsException",
|
|
"Failure in (Raw)SecureSocket.renegotiate request_client_certificate");
|
|
}
|
|
status = SSL_OptionSet(filter_,
|
|
SSL_REQUIRE_CERTIFICATE,
|
|
require_client_certificate);
|
|
if (status != SECSuccess) {
|
|
ThrowPRException("TlsException",
|
|
"Failure in (Raw)SecureSocket.renegotiate require_client_certificate");
|
|
}
|
|
bool flush_cache = !use_session_cache;
|
|
status = SSL_ReHandshake(filter_, flush_cache);
|
|
if (status != SECSuccess) {
|
|
if (is_server_) {
|
|
ThrowPRException("HandshakeException",
|
|
"Failure in (Raw)SecureSocket.renegotiate in server");
|
|
} else {
|
|
ThrowPRException("HandshakeException",
|
|
"Failure in (Raw)SecureSocket.renegotiate in client");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void SSLFilter::Destroy() {
|
|
for (int i = 0; i < kNumBuffers; ++i) {
|
|
Dart_DeletePersistentHandle(dart_buffer_objects_[i]);
|
|
delete[] buffers_[i];
|
|
}
|
|
Dart_DeletePersistentHandle(string_start_);
|
|
Dart_DeletePersistentHandle(string_length_);
|
|
Dart_DeletePersistentHandle(handshake_complete_);
|
|
Dart_DeletePersistentHandle(bad_certificate_callback_);
|
|
free(client_certificate_name_);
|
|
|
|
PR_Close(filter_);
|
|
}
|
|
|
|
|
|
intptr_t SSLFilter::ProcessReadPlaintextBuffer(int start, int end) {
|
|
int length = end - start;
|
|
int bytes_processed = 0;
|
|
if (length > 0) {
|
|
bytes_processed = PR_Read(filter_,
|
|
buffers_[kReadPlaintext] + start,
|
|
length);
|
|
if (bytes_processed < 0) {
|
|
ASSERT(bytes_processed == -1);
|
|
PRErrorCode pr_error = PR_GetError();
|
|
if (PR_WOULD_BLOCK_ERROR != pr_error) {
|
|
return -1;
|
|
}
|
|
bytes_processed = 0;
|
|
}
|
|
}
|
|
return bytes_processed;
|
|
}
|
|
|
|
|
|
intptr_t SSLFilter::ProcessWritePlaintextBuffer(int start1, int end1,
|
|
int start2, int end2) {
|
|
PRIOVec ranges[2];
|
|
uint8_t* buffer = buffers_[kWritePlaintext];
|
|
ranges[0].iov_base = reinterpret_cast<char*>(buffer + start1);
|
|
ranges[0].iov_len = end1 - start1;
|
|
ranges[1].iov_base = reinterpret_cast<char*>(buffer + start2);
|
|
ranges[1].iov_len = end2 - start2;
|
|
int bytes_processed = PR_Writev(filter_, ranges, 2, PR_INTERVAL_NO_TIMEOUT);
|
|
if (bytes_processed < 0) {
|
|
ASSERT(bytes_processed == -1);
|
|
PRErrorCode pr_error = PR_GetError();
|
|
if (PR_WOULD_BLOCK_ERROR != pr_error) {
|
|
return -1;
|
|
}
|
|
bytes_processed = 0;
|
|
}
|
|
return bytes_processed;
|
|
}
|
|
|
|
|
|
intptr_t SSLFilter::ProcessReadEncryptedBuffer(int start, int end) {
|
|
int length = end - start;
|
|
int bytes_processed = 0;
|
|
if (length > 0) {
|
|
memio_Private* secret = memio_GetSecret(filter_);
|
|
uint8_t* filter_buf;
|
|
int free_bytes = memio_GetReadParams(secret, &filter_buf);
|
|
bytes_processed = dart::Utils::Minimum(length, free_bytes);
|
|
memmove(filter_buf, buffers_[kReadEncrypted] + start, bytes_processed);
|
|
memio_PutReadResult(secret, bytes_processed);
|
|
}
|
|
return bytes_processed;
|
|
}
|
|
|
|
|
|
intptr_t SSLFilter::ProcessWriteEncryptedBuffer(int start, int end) {
|
|
int length = end - start;
|
|
int bytes_processed = 0;
|
|
if (length > 0) {
|
|
uint8_t* buffer = buffers_[kWriteEncrypted];
|
|
const uint8_t* buf1;
|
|
const uint8_t* buf2;
|
|
unsigned int len1;
|
|
unsigned int len2;
|
|
memio_Private* secret = memio_GetSecret(filter_);
|
|
memio_GetWriteParams(secret, &buf1, &len1, &buf2, &len2);
|
|
int bytes_to_send =
|
|
dart::Utils::Minimum(len1, static_cast<unsigned>(length));
|
|
if (bytes_to_send > 0) {
|
|
memmove(buffer + start, buf1, bytes_to_send);
|
|
bytes_processed = bytes_to_send;
|
|
}
|
|
bytes_to_send = dart::Utils::Minimum(len2,
|
|
static_cast<unsigned>(length - bytes_processed));
|
|
if (bytes_to_send > 0) {
|
|
memmove(buffer + start + bytes_processed, buf2, bytes_to_send);
|
|
bytes_processed += bytes_to_send;
|
|
}
|
|
if (bytes_processed > 0) {
|
|
memio_PutWriteResult(secret, bytes_processed);
|
|
}
|
|
}
|
|
return bytes_processed;
|
|
}
|
|
|
|
|
|
Dart_Port SSLFilter::GetServicePort() {
|
|
return filter_service_.GetServicePort();
|
|
}
|
|
|
|
|
|
void FUNCTION_NAME(SecureSocket_NewServicePort)(Dart_NativeArguments args) {
|
|
Dart_EnterScope();
|
|
Dart_SetReturnValue(args, Dart_Null());
|
|
Dart_Port service_port = SSLFilter::GetServicePort();
|
|
if (service_port != ILLEGAL_PORT) {
|
|
// Return a send port for the service port.
|
|
Dart_Handle send_port = Dart_NewSendPort(service_port);
|
|
Dart_SetReturnValue(args, send_port);
|
|
}
|
|
Dart_ExitScope();
|
|
}
|
|
|
|
|
|
} // namespace bin
|
|
} // namespace dart
|