blob: 50382660853172b36605cc876bf6efc560764490 [file] [log] [blame]
// 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_Handle dart_this = ThrowIfError(Dart_GetNativeArgument(args, 0));
SSLFilter* filter = new SSLFilter;
SetFilter(args, filter);
filter->Init(dart_this);
}
void FUNCTION_NAME(SecureSocket_Connect)(Dart_NativeArguments args) {
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);
}
void FUNCTION_NAME(SecureSocket_Destroy)(Dart_NativeArguments args) {
SSLFilter* filter = GetFilter(args);
SetFilter(args, NULL);
filter->Destroy();
delete filter;
}
void FUNCTION_NAME(SecureSocket_Handshake)(Dart_NativeArguments args) {
GetFilter(args)->Handshake();
}
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_InitializeLibrary)
(Dart_NativeArguments args) {
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);
}
void FUNCTION_NAME(SecureSocket_PeerCertificate)
(Dart_NativeArguments args) {
Dart_SetReturnValue(args, GetFilter(args)->PeerCertificate());
}
void FUNCTION_NAME(SecureSocket_FilterPointer)(Dart_NativeArguments args) {
intptr_t filter_pointer = reinterpret_cast<intptr_t>(GetFilter(args));
Dart_SetReturnValue(args, Dart_NewInteger(filter_pointer));
}
/**
* 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_Handle x509_object = ssl_filter->PeerCertificate();
Dart_Handle result = Dart_InvokeClosure(callback, 1, &x509_object);
if (Dart_IsError(result)) {
ssl_filter->callback_error = result;
return SECFailure;
}
// Our wrapper is guaranteed to return a boolean.
bool c_result = DartUtils::GetBooleanValue(result);
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 {
if (callback_error != NULL) {
Dart_PropagateError(callback_error);
}
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_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);
}
}
} // namespace bin
} // namespace dart