blob: 8ccd90bc5e2fe7ec21e44ec8c53ad50f83dc7324 [file] [log] [blame] [edit]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef CONSCRYPT_APP_DATA_H_
#define CONSCRYPT_APP_DATA_H_
#include <conscrypt/NetFd.h>
#include <conscrypt/compat.h>
#include <conscrypt/jniutil.h>
#include <conscrypt/netutil.h>
#include <conscrypt/trace.h>
#include <jni.h>
#include <atomic>
#include <memory>
#include <mutex> // NOLINT(build/c++11)
#ifdef _WIN32
// Needed for inet_ntop
#define _WIN32_WINNT _WIN32_WINNT_WIN8
#include <ws2ipdef.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
#include <winsock2.h>
#else // !_WIN32
#include <arpa/inet.h>
#include <poll.h>
#include <unistd.h>
#endif // !_WIN32
namespace conscrypt {
/**
* Our additional application data needed for getting synchronization right.
* This maybe warrants a bit of lengthy prose:
*
* (1) We use a flag to reflect whether we consider the SSL connection alive.
* Any read or write attempt loops will be cancelled once this flag becomes 0.
*
* (2) We use an int to count the number of threads that are blocked by the
* underlying socket. This may be at most two (one reader and one writer), since
* the Java layer ensures that no more threads will enter the native code at the
* same time.
*
* (3) The pipe is used primarily as a means of cancelling a blocking select()
* when we want to close the connection (aka "emergency button"). It is also
* necessary for dealing with a possible race condition situation: There might
* be cases where both threads see an SSL_ERROR_WANT_READ or
* SSL_ERROR_WANT_WRITE. Both will enter a select() with the proper argument.
* If one leaves the select() successfully before the other enters it, the
* "success" event is already consumed and the second thread will be blocked,
* possibly forever (depending on network conditions).
*
* The idea for solving the problem looks like this: Whenever a thread is
* successful in moving around data on the network, and it knows there is
* another thread stuck in a select(), it will write a byte to the pipe, waking
* up the other thread. A thread that returned from select(), on the other hand,
* knows whether it's been woken up by the pipe. If so, it will consume the
* byte, and the original state of affairs has been restored.
*
* The pipe may seem like a bit of overhead, but it fits in nicely with the
* other file descriptors of the select(), so there's only one condition to wait
* for.
*
* (4) Finally, a mutex is needed to make sure that at most one thread is in
* either SSL_read() or SSL_write() at any given time. This is an OpenSSL
* requirement. We use the same mutex to guard the field for counting the
* waiting threads.
*
* During handshaking, additional fields are used to up-call into
* Java to perform certificate verification and handshake
* completion. These are also used in any renegotiation.
*
* (5) the JNIEnv so we can invoke the Java callback
*
* (6) a NativeCrypto.SSLHandshakeCallbacks instance for callbacks from native to Java
*
* (7) a java.io.FileDescriptor wrapper to check for socket close
*
* We store the ALPN protocols list so we can either send it (from the server) or
* select a protocol (on the client). We eagerly acquire a pointer to the array
* data so the callback doesn't need to acquire resources that it cannot
* release.
*
* Because renegotiation can be requested by the peer at any time,
* care should be taken to maintain an appropriate JNIEnv on any
* downcall to openssl since it could result in an upcall to Java. The
* current code does try to cover these cases by conditionally setting
* the JNIEnv on calls that can read and write to the SSL such as
* SSL_do_handshake, SSL_read, SSL_write, and SSL_shutdown.
*/
class AppData {
public:
std::atomic<bool> aliveAndKicking;
int waitingThreads;
#ifdef _WIN32
HANDLE interruptEvent;
#else
int fdsEmergency[2];
#endif
std::mutex mutex;
JNIEnv* env;
jobject sslHandshakeCallbacks;
char* applicationProtocolsData;
size_t applicationProtocolsLength;
bool hasApplicationProtocolSelector;
/**
* Creates the application data context for the SSL*.
*/
static AppData* create() {
std::unique_ptr<AppData> appData(new AppData());
#ifdef _WIN32
HANDLE interruptEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
if (interruptEvent == nullptr) {
JNI_TRACE("AppData::create WSACreateEvent failed: %d", WSAGetLastError());
return nullptr;
}
appData.get()->interruptEvent = interruptEvent;
#else
if (pipe(appData.get()->fdsEmergency) == -1) {
CONSCRYPT_LOG_ERROR("AppData::create pipe(2) failed: %s", strerror(errno));
return nullptr;
}
if (!netutil::setBlocking(appData.get()->fdsEmergency[0], false)) {
CONSCRYPT_LOG_ERROR("AppData::create fcntl(2) failed: %s", strerror(errno));
return nullptr;
}
#endif
return appData.release();
}
~AppData() {
aliveAndKicking = false;
#ifdef _WIN32
if (interruptEvent != nullptr) {
CloseHandle(interruptEvent);
}
#else
if (fdsEmergency[0] != -1) {
close(fdsEmergency[0]);
}
if (fdsEmergency[1] != -1) {
close(fdsEmergency[1]);
}
#endif
clearApplicationProtocols();
clearCallbackState();
}
/**
* Only called in server mode. Sets the protocols for ALPN negotiation.
*
* @param env The JNIEnv
* @param alpnProtocols ALPN protocols so that they may be advertised (by the
* server) or selected (by the client). Passing
* non-null enables ALPN. This array is copied so that no
* global reference to the Java byte array is maintained.
*/
bool setApplicationProtocols(JNIEnv* e, jbyteArray applicationProtocolsJava) {
clearApplicationProtocols();
if (applicationProtocolsJava != nullptr) {
jbyte* applicationProtocols =
e->GetByteArrayElements(applicationProtocolsJava, nullptr);
if (applicationProtocols == nullptr) {
clearCallbackState();
JNI_TRACE("appData=%p setApplicationCallbackState => applicationProtocols == null",
this);
return false;
}
applicationProtocolsLength =
static_cast<size_t>(e->GetArrayLength(applicationProtocolsJava));
applicationProtocolsData = new char[applicationProtocolsLength];
memcpy(applicationProtocolsData, applicationProtocols, applicationProtocolsLength);
e->ReleaseByteArrayElements(applicationProtocolsJava, applicationProtocols, JNI_ABORT);
}
return true;
}
/**
* Used to set the SSL-to-Java callback state before each SSL_*
* call that may result in a callback. It should be cleared after
* the operation returns with clearCallbackState.
*
* @param env The JNIEnv
* @param shc The SSLHandshakeCallbacks
* @param fd The FileDescriptor
*/
bool setCallbackState(JNIEnv* e, jobject shc, jobject fd) {
std::unique_ptr<NetFd> netFd;
if (fd != nullptr) {
netFd.reset(new NetFd(e, fd));
if (netFd->isClosed()) {
JNI_TRACE("appData=%p setCallbackState => netFd->isClosed() == true", this);
return false;
}
}
env = e;
sslHandshakeCallbacks = shc;
return true;
}
void clearCallbackState() {
sslHandshakeCallbacks = nullptr;
env = nullptr;
}
private:
AppData()
: aliveAndKicking(true),
waitingThreads(0),
env(nullptr),
sslHandshakeCallbacks(nullptr),
applicationProtocolsData(nullptr),
applicationProtocolsLength(static_cast<size_t>(-1)),
hasApplicationProtocolSelector(false) {
#ifdef _WIN32
interruptEvent = nullptr;
#else
fdsEmergency[0] = -1;
fdsEmergency[1] = -1;
#endif
}
void clearApplicationProtocols() {
if (applicationProtocolsData != nullptr) {
delete applicationProtocolsData;
applicationProtocolsData = nullptr;
applicationProtocolsLength = static_cast<size_t>(-1);
}
}
};
} // namespace conscrypt
#endif // CONSCRYPT_APP_DATA_H_