diff --git a/cmake/JSSConfig.cmake b/cmake/JSSConfig.cmake index 9ccb148bb..373b4d155 100644 --- a/cmake/JSSConfig.cmake +++ b/cmake/JSSConfig.cmake @@ -349,6 +349,10 @@ macro(jss_config_template) "${PROJECT_SOURCE_DIR}/tools/run_test.sh.in" "${CMAKE_BINARY_DIR}/run_test.sh" ) + configure_file( + "${PROJECT_SOURCE_DIR}/tools/run_java.sh.in" + "${CMAKE_BINARY_DIR}/run_java.sh" + ) set(JSS_CFG_PATH "${CONFIG_OUTPUT_DIR}/jss.cfg") configure_file( "${PROJECT_SOURCE_DIR}/tools/java.security.in" diff --git a/cmake/JSSTests.cmake b/cmake/JSSTests.cmake index 6f31ad2a2..68a7df93e 100644 --- a/cmake/JSSTests.cmake +++ b/cmake/JSSTests.cmake @@ -70,6 +70,10 @@ macro(jss_tests) NAME "TestBufferPRFD" COMMAND "${BIN_OUTPUT_DIR}/TestBufferPRFD" ) + jss_test_java( + NAME "TestJByteBuffer" + COMMAND "org.mozilla.jss.tests.TestJByteBuffer" + ) jss_test_java( NAME "Test_UTF-8_Converter" COMMAND "org.mozilla.jss.tests.UTF8ConverterTest" @@ -175,6 +179,11 @@ macro(jss_tests) COMMAND "pk12util" "-o" "${RESULTS_NSSDB_OUTPUT_DIR}/rsa.pfx" "-n" "CA_RSA" "-d" "${RESULTS_NSSDB_OUTPUT_DIR}" "-K" "${DB_PWD}" "-W" "${DB_PWD}" DEPENDS "Generate_known_RSA_cert_pair" ) + jss_test_exec( + NAME "Create_PKCS11_cert_to_PKCS12_server_rsa.pfx" + COMMAND "pk12util" "-o" "${RESULTS_NSSDB_OUTPUT_DIR}/rsa-server.pfx" "-n" "Server_RSA" "-d" "${RESULTS_NSSDB_OUTPUT_DIR}" "-K" "${DB_PWD}" "-W" "${DB_PWD}" + DEPENDS "Generate_known_RSA_cert_pair" + ) jss_test_exec( NAME "Create_PKCS11_cert_to_PKCS12_ecdsa.pfx" COMMAND "pk12util" "-o" "${RESULTS_NSSDB_OUTPUT_DIR}/ecdsa.pfx" "-n" "CA_ECDSA" "-d" "${RESULTS_NSSDB_OUTPUT_DIR}" "-K" "${DB_PWD}" "-W" "${DB_PWD}" diff --git a/docs/usage/benchmarksslsocket.md b/docs/usage/benchmarksslsocket.md index 63504689e..6fbde52be 100644 --- a/docs/usage/benchmarksslsocket.md +++ b/docs/usage/benchmarksslsocket.md @@ -37,7 +37,10 @@ java.security. When utilizing SunJSSE, for best results, do not load JSS via java.security. It is suggested to use `run_test.sh` from the `build/` directory for -executing this utility. +executing this utility. `run_java.sh` can be used when benchmarking +`SunJSSE.SSLSocket` as it doesn't load JSS via the `java.security` +file by default. Additionally, a RSA server certificate is placed +under `build/results/nssdb/rsa-ssl.pfx` with the default password. # Past Performance diff --git a/lib/jss.map b/lib/jss.map index 8a6fdcb2f..eeb18c661 100644 --- a/lib/jss.map +++ b/lib/jss.map @@ -482,6 +482,14 @@ Java_org_mozilla_jss_nss_SECErrors_getRevokedCertificateOCSP; Java_org_mozilla_jss_nss_SECErrors_getRevokedCertificate; Java_org_mozilla_jss_nss_SECErrors_getUntrustedIssuer; Java_org_mozilla_jss_nss_SECErrors_getUntrustedCert; +Java_org_mozilla_jss_nss_Buffer_ReadOffset; +Java_org_mozilla_jss_nss_Buffer_WriteOffset; +Java_org_mozilla_jss_nss_JByteBuffer_Create; +Java_org_mozilla_jss_nss_JByteBuffer_ClearBufferNative; +Java_org_mozilla_jss_nss_JByteBuffer_SetBufferNative; +Java_org_mozilla_jss_nss_JByteBuffer_Capacity; +Java_org_mozilla_jss_nss_JByteBuffer_FreeNative; +Java_org_mozilla_jss_nss_PR_NewByteBufferPRFD; local: *; }; diff --git a/src/main/java/org/mozilla/jss/nss/ByteBufferProxy.c b/src/main/java/org/mozilla/jss/nss/ByteBufferProxy.c new file mode 100644 index 000000000..822be5270 --- /dev/null +++ b/src/main/java/org/mozilla/jss/nss/ByteBufferProxy.c @@ -0,0 +1,51 @@ +#include +#include + +#include "java_ids.h" +#include "jssutil.h" +#include "ByteBufferProxy.h" + +jobject +JSS_PR_wrapJByteBuffer(JNIEnv *env, j_bytebuffer **buffer) +{ + jbyteArray pointer = NULL; + jclass proxyClass; + jmethodID constructor; + jobject bufferObj = NULL; + + PR_ASSERT(env != NULL && buffer != NULL && *buffer != NULL); + + /* convert pointer to byte array */ + pointer = JSS_ptrToByteArray(env, *buffer); + + /* + * Lookup the class and constructor + */ + proxyClass = (*env)->FindClass(env, BYTE_BUFFER_PROXY_CLASS_NAME); + if (proxyClass == NULL) { + ASSERT_OUTOFMEM(env); + goto finish; + } + constructor = (*env)->GetMethodID(env, proxyClass, + PLAIN_CONSTRUCTOR, + BYTE_BUFFER_PROXY_CONSTRUCTOR_SIG); + if (constructor == NULL) { + ASSERT_OUTOFMEM(env); + goto finish; + } + + /* call the constructor */ + bufferObj = (*env)->NewObject(env, proxyClass, constructor, pointer); + +finish: + *buffer = NULL; + + PR_ASSERT(bufferObj || (*env)->ExceptionOccurred(env)); + return bufferObj; +} + +PRStatus +JSS_PR_unwrapJByteBuffer(JNIEnv *env, jobject buffer_proxy, j_bytebuffer **buffer) +{ + return JSS_getPtrFromProxy(env, buffer_proxy, (void**)buffer); +} diff --git a/src/main/java/org/mozilla/jss/nss/ByteBufferProxy.h b/src/main/java/org/mozilla/jss/nss/ByteBufferProxy.h new file mode 100644 index 000000000..e7d35f776 --- /dev/null +++ b/src/main/java/org/mozilla/jss/nss/ByteBufferProxy.h @@ -0,0 +1,10 @@ +#include +#include "j_bytebuffer.h" + +#pragma once + +/* Wrap a j_buffer object into a BufferProxy, freeing the buffer on error. */ +jobject JSS_PR_wrapJByteBuffer(JNIEnv *env, j_bytebuffer **buffer); + +/* Extract a j_buffer pointer from an instance of a BufferProxy. */ +PRStatus JSS_PR_unwrapJByteBuffer(JNIEnv *env, jobject buffer_proxy, j_bytebuffer **buffer); diff --git a/src/main/java/org/mozilla/jss/nss/ByteBufferProxy.java b/src/main/java/org/mozilla/jss/nss/ByteBufferProxy.java new file mode 100644 index 000000000..04a571a76 --- /dev/null +++ b/src/main/java/org/mozilla/jss/nss/ByteBufferProxy.java @@ -0,0 +1,25 @@ +package org.mozilla.jss.nss; + +import java.nio.ByteBuffer; + +public class ByteBufferProxy extends org.mozilla.jss.util.NativeProxy { + protected ByteBuffer last; + + public ByteBufferProxy(byte[] pointer) { + super(pointer); + } + + /** + * It is usually better to call org.mozilla.jss.nss.JByteBuffer.Free(...) + * instead. + * + * But this does it for you. + */ + protected void releaseNativeResources() { + JByteBuffer.Free(this); + } + + protected void finalize() throws Throwable { + super.finalize(); + } +} diff --git a/src/main/java/org/mozilla/jss/nss/JByteBuffer.c b/src/main/java/org/mozilla/jss/nss/JByteBuffer.c new file mode 100644 index 000000000..5c02ce0a3 --- /dev/null +++ b/src/main/java/org/mozilla/jss/nss/JByteBuffer.c @@ -0,0 +1,89 @@ +#include +#include +#include +#include + +#include "jssutil.h" +#include "ByteBufferProxy.h" +#include "j_bytebuffer.h" + +#include "_jni/org_mozilla_jss_nss_JByteBuffer.h" + +JNIEXPORT jobject JNICALL +Java_org_mozilla_jss_nss_JByteBuffer_Create(JNIEnv *env, jclass clazz, + jboolean writable) +{ + j_bytebuffer *buf = NULL; + + PR_ASSERT(env != NULL); + + buf = jbb_alloc(writable == JNI_TRUE); + + PR_ASSERT(buf != NULL); + + return JSS_PR_wrapJByteBuffer(env, &buf); +} + +JNIEXPORT jint JNICALL +Java_org_mozilla_jss_nss_JByteBuffer_ClearBufferNative(JNIEnv *env, + jclass clazz, jobject proxy, jbyteArray last) +{ + j_bytebuffer *buf = NULL; + + PR_ASSERT(env != NULL && proxy != NULL); + + if (JSS_PR_unwrapJByteBuffer(env, proxy, &buf) != PR_SUCCESS) { + return 0; + } + + return jbb_clear_buffer(buf, env, last); +} + +JNIEXPORT jboolean JNICALL +Java_org_mozilla_jss_nss_JByteBuffer_SetBufferNative(JNIEnv *env, + jclass clazz, jobject proxy, jbyteArray array, jlong offset, + jlong limit) +{ + j_bytebuffer *buf = NULL; + + PR_ASSERT(env != NULL && proxy != NULL); + + if (JSS_PR_unwrapJByteBuffer(env, proxy, &buf) != PR_SUCCESS || + buf == NULL) { + return JNI_FALSE; + } + + return jbb_set_buffer(buf, env, array, offset, limit); +} + +JNIEXPORT jint JNICALL +Java_org_mozilla_jss_nss_JByteBuffer_Capacity(JNIEnv *env, + jclass clazz, jobject proxy) +{ + j_bytebuffer *buf = NULL; + + PR_ASSERT(env != NULL && proxy != NULL); + + if (JSS_PR_unwrapJByteBuffer(env, proxy, &buf) != PR_SUCCESS) { + return 0; + } + + return jbb_capacity(buf); +} + +JNIEXPORT void JNICALL +Java_org_mozilla_jss_nss_JByteBuffer_FreeNative(JNIEnv *env, jclass clazz, + jobject proxy) +{ + j_bytebuffer *buf = NULL; + + PR_ASSERT(env != NULL && proxy != NULL); + + if (JSS_PR_unwrapJByteBuffer(env, proxy, &buf) != PR_SUCCESS || + buf == NULL) { + return; + } + + jbb_free(buf, env); + JSS_clearPtrFromProxy(env, proxy); +} diff --git a/src/main/java/org/mozilla/jss/nss/JByteBuffer.java b/src/main/java/org/mozilla/jss/nss/JByteBuffer.java new file mode 100644 index 000000000..de6f8eb39 --- /dev/null +++ b/src/main/java/org/mozilla/jss/nss/JByteBuffer.java @@ -0,0 +1,97 @@ +package org.mozilla.jss.nss; + +import java.nio.ByteBuffer; + +public class JByteBuffer { + /** + * Create a new j_buffer object with the specified number of bytes. + * + * See also: jbb_alloc in org/mozilla/jss/ssl/javax/j_bytebuffer.h + */ + public static native ByteBufferProxy Create(boolean writable); + + /** + * Removes the existing ByteBuffer from this proxy instance. + * + * See also: jbb_clear_buffer in org/mozilla/jss/ssl/javax/j_bytebuffer.h + */ + public static int ClearBuffer(ByteBufferProxy proxy) { + if (proxy == null || proxy.last == null) { + return 0; + } + + int offset = ClearBufferNative(proxy, proxy.last.array()); + proxy.last.position(proxy.last.position() + offset); + + proxy.last = null; + + return offset; + } + + /** + * Set the underlying buffer for this ByteBufferProxy instance. + * + * See also: jbb_set_buffer in org/mozilla/jss/ssl/javax/j_bytebuffer.h + */ + public static void SetBuffer(ByteBufferProxy proxy, ByteBuffer buffer) { + if (proxy == null) { + return; + } + + if (buffer != null && !buffer.hasArray()) { + String msg = "Unable to support ByteBuffers which are not backed "; + msg += "by real arrays."; + throw new RuntimeException(msg); + } + + if (proxy.last != null) { + // This is an unusual case. We should always clear the last buffer + // prior to attempting to set a new one. Luckily, we store the + // last buffer, so we can clean up safely. + ClearBuffer(proxy); + } + + if (!SetBufferNative(proxy, buffer.array(), buffer.position(), buffer.limit())) { + throw new RuntimeException("Unable to set bufer for an unknown reason."); + } + + proxy.last = buffer; + } + + /** + * Get the remaining capacity of this buffer. + * + * See also: jbb_capacity in org/mozilla/jss/ssl/javax/j_bytebuffer.h + */ + public static native int Capacity(ByteBufferProxy proxy); + + /** + * Clear the underlying buffer for this ByteBufferProxy instance. + */ + private static native int ClearBufferNative(ByteBufferProxy proxy, byte[] last); + + /** + * Internal helper to implement the native portion of SetBuffer. + * + * See also: jbb_set_buffer in org/mozilla/jss/ssl/javax/j_bytebuffer.h + */ + private static native boolean SetBufferNative(ByteBufferProxy proxy, byte[] array, long offset, long limit); + + /** + * Destroy a buffer object, freeing its resources. + * + * See also: jbb_free in org/mozilla/jss/ssl/javax/j_buffer.h + */ + public static void Free(ByteBufferProxy proxy) { + // Make sure we call ClearBuffer ourselves; otherwise, any changes to + // the underlying ByteBuffer won't be reflected as FreeNative will + // discard them. + ClearBuffer(proxy); + FreeNative(proxy); + } + + /** + * Internal helper to implement the free call. + */ + private static native void FreeNative(ByteBufferProxy proxy); +} diff --git a/src/main/java/org/mozilla/jss/nss/PR.c b/src/main/java/org/mozilla/jss/nss/PR.c index de7825f83..f3bc01091 100644 --- a/src/main/java/org/mozilla/jss/nss/PR.c +++ b/src/main/java/org/mozilla/jss/nss/PR.c @@ -7,7 +7,9 @@ #include "jss_exceptions.h" #include "PRFDProxy.h" #include "BufferProxy.h" +#include "ByteBufferProxy.h" #include "BufferPRFD.h" +#include "ByteBufferPRFD.h" #include "_jni/org_mozilla_jss_nss_PR.h" @@ -102,7 +104,7 @@ Java_org_mozilla_jss_nss_PR_Close(JNIEnv *env, jclass clazz, jobject fd, jboolea return PR_SUCCESS; } - if (JSS_PR_getPRFileDesc(env, fd, &real_fd) != PR_SUCCESS) { + if (JSS_PR_getPRFileDesc(env, fd, &real_fd) != PR_SUCCESS || real_fd == NULL) { return PR_FAILURE; } @@ -114,6 +116,44 @@ Java_org_mozilla_jss_nss_PR_Close(JNIEnv *env, jclass clazz, jobject fd, jboolea return ret; } +JNIEXPORT jobject JNICALL +Java_org_mozilla_jss_nss_PR_NewByteBufferPRFD(JNIEnv *env, jclass clazz, + jobject read_buf, jobject write_buf, jbyteArray peer_info) +{ + j_bytebuffer *real_read_buf = NULL; + j_bytebuffer *real_write_buf = NULL; + uint8_t *real_peer_info = NULL; + size_t peer_info_len = 0; + + PRFileDesc *buf_prfd; + jobject result = NULL; + + PR_ASSERT(env != NULL && read_buf != NULL && write_buf != NULL); + PR_SetError(0, 0); + + if (JSS_PR_unwrapJByteBuffer(env, read_buf, &real_read_buf) != PR_SUCCESS) { + return result; + } + + if (JSS_PR_unwrapJByteBuffer(env, write_buf, &real_write_buf) != PR_SUCCESS) { + return result; + } + + if (peer_info != NULL && !JSS_FromByteArray(env, peer_info, &real_peer_info, &peer_info_len)) { + return result; + } + + buf_prfd = newByteBufferPRFileDesc(real_read_buf, real_write_buf, + real_peer_info, peer_info_len); + if (buf_prfd == NULL) { + return result; + } + + result = JSS_PR_wrapPRFDProxy(env, &buf_prfd); + free(real_peer_info); + return result; +} + JNIEXPORT int JNICALL Java_org_mozilla_jss_nss_PR_Shutdown(JNIEnv *env, jclass clazz, jobject fd, jint how) @@ -127,7 +167,7 @@ Java_org_mozilla_jss_nss_PR_Shutdown(JNIEnv *env, jclass clazz, jobject fd, return PR_SUCCESS; } - if (JSS_PR_getPRFileDesc(env, fd, &real_fd) != PR_SUCCESS) { + if (JSS_PR_getPRFileDesc(env, fd, &real_fd) != PR_SUCCESS || real_fd == NULL) { return PR_FAILURE; } @@ -149,7 +189,7 @@ Java_org_mozilla_jss_nss_PR_Read(JNIEnv *env, jclass clazz, jobject fd, PR_ASSERT(env != NULL && fd != NULL && amount >= 0); PR_SetError(0, 0); - if (JSS_PR_getPRFileDesc(env, fd, &real_fd) != PR_SUCCESS) { + if (JSS_PR_getPRFileDesc(env, fd, &real_fd) != PR_SUCCESS || real_fd == NULL) { return NULL; } @@ -221,7 +261,7 @@ Java_org_mozilla_jss_nss_PR_Write(JNIEnv *env, jclass clazz, jobject fd, PR_ASSERT(env != NULL && fd != NULL); PR_SetError(0, 0); - if (JSS_PR_getPRFileDesc(env, fd, &real_fd) != PR_SUCCESS) { + if (JSS_PR_getPRFileDesc(env, fd, &real_fd) != PR_SUCCESS || real_fd == NULL) { return 0; } @@ -263,7 +303,7 @@ Java_org_mozilla_jss_nss_PR_Recv(JNIEnv *env, jclass clazz, jobject fd, timeout >= 0 && timeout <= UINT32_MAX); PR_SetError(0, 0); - if (JSS_PR_getPRFileDesc(env, fd, &real_fd) != PR_SUCCESS) { + if (JSS_PR_getPRFileDesc(env, fd, &real_fd) != PR_SUCCESS || real_fd == NULL) { return NULL; } @@ -298,7 +338,7 @@ Java_org_mozilla_jss_nss_PR_Send(JNIEnv *env, jclass clazz, jobject fd, timeout >= 0 && timeout <= UINT32_MAX); PR_SetError(0, 0); - if (JSS_PR_getPRFileDesc(env, fd, &real_fd) != PR_SUCCESS) { + if (JSS_PR_getPRFileDesc(env, fd, &real_fd) != PR_SUCCESS || real_fd == NULL) { return 0; } diff --git a/src/main/java/org/mozilla/jss/nss/PR.java b/src/main/java/org/mozilla/jss/nss/PR.java index 90e770e83..6e80f4b72 100644 --- a/src/main/java/org/mozilla/jss/nss/PR.java +++ b/src/main/java/org/mozilla/jss/nss/PR.java @@ -65,6 +65,17 @@ public class PR { public static native PRFDProxy NewBufferPRFD(BufferProxy read_buf, BufferProxy write_buf, byte[] peer_info); + + /** + * Create a new j_bytebuffer backed PRFileDesc, mimicing a TCP socket with + * the specified peer_info. + * + * See also: newByteBufferPRFileDesc in org/mozilla/jss/ssl/javax/ByteBufferPRFD.h + */ + public static native PRFDProxy NewByteBufferPRFD(ByteBufferProxy read_buf, + ByteBufferProxy write_buf, + byte[] peer_info); + /** * Close an existing PRFDProxy, clearing the pointer if successful. * diff --git a/src/main/java/org/mozilla/jss/provider/javax/net/JSSContextSpi.java b/src/main/java/org/mozilla/jss/provider/javax/net/JSSContextSpi.java index e834c554e..670ee0ea6 100644 --- a/src/main/java/org/mozilla/jss/provider/javax/net/JSSContextSpi.java +++ b/src/main/java/org/mozilla/jss/provider/javax/net/JSSContextSpi.java @@ -10,7 +10,7 @@ import org.mozilla.jss.provider.javax.crypto.JSSKeyManager; import org.mozilla.jss.ssl.javax.JSSEngine; -import org.mozilla.jss.ssl.javax.JSSEngineReferenceImpl; +import org.mozilla.jss.ssl.javax.JSSEngineOptimizedImpl; import org.mozilla.jss.ssl.javax.JSSParameters; import org.mozilla.jss.ssl.javax.JSSServerSocketFactory; import org.mozilla.jss.ssl.javax.JSSSocketFactory; @@ -53,7 +53,7 @@ public void engineInit(KeyManager[] kms, TrustManager[] tms, SecureRandom sr) th public SSLEngine engineCreateSSLEngine() { logger.debug("JSSContextSpi.engineCreateSSLEngine()"); - JSSEngine ret = new JSSEngineReferenceImpl(); + JSSEngine ret = new JSSEngineOptimizedImpl(); initializeEngine(ret); return ret; @@ -63,7 +63,7 @@ public SSLEngine engineCreateSSLEngine() { public SSLEngine engineCreateSSLEngine(String host, int port) { logger.debug("JSSContextSpi.engineCreateSSLEngine(" + host + ", " + port + ")"); - JSSEngine ret = new JSSEngineReferenceImpl(host, port); + JSSEngine ret = new JSSEngineOptimizedImpl(host, port); initializeEngine(ret); return ret; diff --git a/src/main/java/org/mozilla/jss/ssl/javax/BufferPRFD.h b/src/main/java/org/mozilla/jss/ssl/javax/BufferPRFD.h index 74c8ba208..8dce4d6ef 100644 --- a/src/main/java/org/mozilla/jss/ssl/javax/BufferPRFD.h +++ b/src/main/java/org/mozilla/jss/ssl/javax/BufferPRFD.h @@ -12,14 +12,6 @@ /* Use a modern pragma guard... */ #pragma once -/* Free a Buffer-backed PRFileDesc. Note that it is usually sufficient to call - * PR_Close(...) on the buffer instead. This is provided for completeness and - * should not be called in a SSL context as the buffer PRFileDesc is wrapped - * by the SSL PRFileDesc. Note that this only removes references to the - * underlying j_buffers and does not free them; it is up to the caller to - * do so. */ -void freeBufferPRFileDesc(PRFileDesc *fd); - /* Construct a new PRFileDesc backed by a pair of buffers. Note that these * should be separate buffers, but need not be unique to this PRFileDesc; * that is, a client and server could share (but be swapped) j_buffers. diff --git a/src/main/java/org/mozilla/jss/ssl/javax/ByteBufferPRFD.c b/src/main/java/org/mozilla/jss/ssl/javax/ByteBufferPRFD.c new file mode 100644 index 000000000..1649aead2 --- /dev/null +++ b/src/main/java/org/mozilla/jss/ssl/javax/ByteBufferPRFD.c @@ -0,0 +1,350 @@ +#include + +/* Necessary for correctly propagating E_WOULDBLOCK. */ +#include + +#include +#include + +/* Ring buffer implementation to handle read/write calls. */ +#include "j_bytebuffer.h" +#include "ByteBufferPRFD.h" + +/* This struct stores all the private data we need access to from inside our + * PRFileDesc calls. We store the following information: + * + * read_buffer -- raw buffer to read from for recv(...) calls + * + * write_buffer -- raw buffer to write to for send(...) calls + * + * peer_addr -- peer address info, truncated to 16 bytes + * + * As the read_* and write_* members should be provided by the creator, and + * actors outside our PRFileDesc need access to this information (to add more + * bytes to the read buffer when more data arrives for instance), we store + * pointers and not the values themselves. + * + * The creator is responsible for ensuring that all data gets correctly freed + * when the program exits; we will not free any of our pointers that were not + * created by us. + */ +struct PRFilePrivate { + j_bytebuffer *read_buffer; + j_bytebuffer *write_buffer; + + uint8_t *peer_addr; +}; + +static PRDescIdentity byte_buffer_layer_id = 0; + +// This function is provided as a stub for all unimplemented calls. +static PRIntn invalidInternalCall(/* anything */) +{ + // For debugging; any invalid calls are asserted, so we can get a full + // backtrace from the debugger and _hopefully_ we can find out which call + // was attempted. To enable asserts, define DEBUG or FORCE_PR_ASSERT + // before loading the NSPR headers, e.g., by using the DEBUG release type + // during CMake configuration time. + PR_ASSERT(!"invalidInternalCall performed!"); + return 0; +} + +// This function mimics shutting down a buffer. +static PRStatus PRByteBufferShutdown(PRFileDesc *fd, PRIntn how) +{ + // This method has no functionality; we're a lower level under both + // the SSLEngine and NSS's SSL context. When the application issues + // a shutdown request, SSLEngine refuses to allow new writes (and only + // reads until the remote party acknowledges the shutdown). All data + // should be written into the NSS connection buffer and NSS's shutdown + // should be called. At this point, there's nothing left for us to do: + // there's no TCP socket we need to terminate, and we need to allow + // any remaining buffered bytes to be written. So, the only thing we + // can do is return success here. + return PR_SUCCESS; +} + +// This function mimics closing a buffer and frees our associated data. +static PRStatus PRByteBufferClose(PRFileDesc *fd) +{ + PRStatus rv = PR_SUCCESS; + + if (fd->secret != NULL) { + // We intentionally don't free read_buffer or write_buffer; we assume + // the caller has a copy of these data structures as well, and could + // still be reading after the PRFileDesc is closed. + fd->secret->read_buffer = NULL; + fd->secret->write_buffer = NULL; + + // Free the peer address we allocated during initialiation. + free(fd->secret->peer_addr); + fd->secret->peer_addr = NULL; + + // Free our internal data structure. + free(fd->secret); + fd->secret = NULL; + } + + PR_ASSERT(fd->identity == byte_buffer_layer_id); + PR_ASSERT(fd->higher == NULL); + PR_ASSERT(fd->lower == NULL); + + return rv; +} + +// Fake getting the name of the remote peer +static PRStatus PRByteBufferGetPeerName(PRFileDesc *fd, PRNetAddr *addr) +{ + /* getPeerName takes a PRFileDesc and modifies the PRNetAddr with the + * name of the peer. Because of the specifics of the NSS Implementation, + * we return whatever name was passed to us on creation; we lack a real + * TCP socket and thus a real TCP name. + * + * However, we have to provide the peer name as type IPv6, else it either + * gets mangled by the IPv4 -> IPv6 translation or a + * PR_ADDRESS_NOT_SUPPORTED_ERROR is thrown by ssl_GetPeerInfo(...). + */ + + /* There are three main places this is called in a normal TLS connection: + * + * ssl_ImportFD(...) -- where the result of this function is compared + * to PR_SUCCESS to see if it is connected + * ssl_BeginClientHandshake(...) -- where this function sets local + * values for session resumption on + * the client + * ssl3_HandleClientHello(...) -- where this function sets local + * values for evaluating session + * resumption from the client. + * + * Because these results have to be consistent for session resumption to + * work, we must query the internal structure and return that value. + * Note that it isn't a security risk if an incorrect peer_addr is + * provided to us: the other party must _also_ know the session keys for + * this to matter. + */ + + // https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PRNetAddr + if (fd->secret == NULL || addr == NULL) { + return PR_FAILURE; + } + + PRFilePrivate *internal = fd->secret; + addr->ipv6.family = PR_AF_INET6; + addr->ipv6.port = 0xFFFF; + addr->ipv6.flowinfo = 0x00000000; + + // We validate that strlen(peer_addr) <= 16 on creation by truncating + // it to 16 bytes. Thus the memcpy with strlen(...) won't overflow the + // size of ipv6.ip. + memset(&addr->ipv6.ip, 0, 16); + memcpy(&addr->ipv6.ip, internal->peer_addr, 16); + return PR_SUCCESS; +} + +// Respond to send requests +static PRInt32 PRByteBufferSend(PRFileDesc *fd, const void *buf, PRInt32 amount, + PRIntn flags, PRIntervalTime timeout) +{ + /* Send takes a PRFileDesc and attempts to send some amount of bytes from + * the start of buf to the other party before timeout is reached. Because + * we're implementing this as a buffer, copy into the buffer if there is + * free space, else return EWOULDBLOCK. */ + + PRFilePrivate *internal = fd->secret; + + /* Prefer the initial write call. */ + PRInt32 this_write = jbb_write(internal->write_buffer, (const uint8_t *) buf, amount); + + if (this_write == 0) { + /* Under correct Unix non-blocking socket semantics, if we lack data + * to write, return a negative length and set EWOULDBLOCK. This is + * documented in `man 2 recv`. */ + PR_SetError(PR_WOULD_BLOCK_ERROR, EWOULDBLOCK); + return -1; + } + + return this_write; +} + +// Respond to write requests +static PRInt32 PRByteBufferWrite(PRFileDesc *fd, const void *buf, PRInt32 amount) +{ + /* Write is the same as Send except that it doesn't have a timeout or + * understand flags. Since our implementation of Send is fake, pass + * arbitrary data and use it to implement Write as well. */ + return PRByteBufferSend(fd, buf, amount, 0, -1); +} + + +// Respond to recv requests +static PRInt32 PRByteBufferRecv(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags, PRIntervalTime timeout) +{ + /* Recv takes a PRFileDesc and attempts to read some amount of bytes from + * the start of buf to return to the caller before timeout is reached. + * Because we're implementing this as a buffer, copy from the buffer when + * there is something in it, else return EWOULDBLOCK. */ + PRFilePrivate *internal = fd->secret; + + PRInt32 this_read = jbb_read(internal->read_buffer, (uint8_t *) buf, amount); + if (this_read == 0) { + /* See comment in PRByteBufferSend about EWOULDBLOCK. */ + PR_SetError(PR_WOULD_BLOCK_ERROR, EWOULDBLOCK); + return -1; + } + + return this_read; +} + +// Respond to read requests +static PRInt32 PRByteBufferRead(PRFileDesc *fd, void *buf, PRInt32 amount) +{ + /* Read is the same as Recv except that it doesn't have a timeout or + * understand flags. Since our implementation of Recv is fake, pass + * arbitrary data and use it to implement Read as well. */ + return PRByteBufferRecv(fd, buf, amount, 0, -1); +} + +// Fake responses to getSocketOption requests +static PRStatus PRByteBufferGetSocketOption(PRFileDesc *fd, PRSocketOptionData *data) +{ + /* getSocketOption takes a PRFileDesc and modifies the value field of data + * with socket option specified in the option field. We fake responses with + * a couple of sane defaults here: + * + * non_blocking = true + * reuse_addr = true + * keep_alive = false + * no_delay = true + * + * We return valid responses to three other options: + * + * max_segment = capacity of read_buffer + * recv_buffer_size = capacity of read buffer + * send_buffer_size = capacity of write buffer + * + * Note that all responses are "fake" in that calls to SetSocketOption will + * not be reflected here. + */ + + if (!data || !fd) { + return PR_FAILURE; + } + + PRFilePrivate *internal = fd->secret; + size_t read_capacity = jbb_capacity(internal->read_buffer); + size_t write_capacity = jbb_capacity(internal->write_buffer); + size_t max_segment = read_capacity; + if (write_capacity > max_segment) { + max_segment = write_capacity; + } + + switch (data->option) { + case PR_SockOpt_Nonblocking: + data->value.non_blocking = PR_TRUE; + return PR_SUCCESS; + case PR_SockOpt_Reuseaddr: + data->value.reuse_addr = PR_TRUE; + return PR_SUCCESS; + case PR_SockOpt_Keepalive: + data->value.keep_alive = PR_FALSE; + return PR_SUCCESS; + case PR_SockOpt_NoDelay: + data->value.no_delay = PR_TRUE; + return PR_SUCCESS; + case PR_SockOpt_MaxSegment: + data->value.max_segment = max_segment; + return PR_SUCCESS; + case PR_SockOpt_RecvBufferSize: + data->value.recv_buffer_size = read_capacity; + return PR_SUCCESS; + case PR_SockOpt_SendBufferSize: + data->value.send_buffer_size = write_capacity; + return PR_SUCCESS; + default: + return PR_FAILURE; + } +} + +// Fake responses to setSocketOption +static PRStatus PRByteBufferSetSocketOption(PRFileDesc *fd, const PRSocketOptionData *data) +{ + /* This gives the caller control over setting socket options. It is the + * equivalent of fcntl() with F_SETFL. In our case, O_NONBLOCK is the + * only thing passed in, which we always return as true anyways, so + * ignore the result. */ + return PR_SUCCESS; +} + +// Create a method table with all our implemented functions +static const PRIOMethods PRIOByteBufferMethods = { + PR_DESC_SOCKET_TCP, + PRByteBufferClose, + PRByteBufferRead, + PRByteBufferWrite, + (PRAvailableFN)invalidInternalCall, + (PRAvailable64FN)invalidInternalCall, + (PRFsyncFN)invalidInternalCall, + (PRSeekFN)invalidInternalCall, + (PRSeek64FN)invalidInternalCall, + (PRFileInfoFN)invalidInternalCall, + (PRFileInfo64FN)invalidInternalCall, + (PRWritevFN)invalidInternalCall, + (PRConnectFN)invalidInternalCall, + (PRAcceptFN)invalidInternalCall, + (PRBindFN)invalidInternalCall, + (PRListenFN)invalidInternalCall, + PRByteBufferShutdown, + PRByteBufferRecv, + PRByteBufferSend, + (PRRecvfromFN)invalidInternalCall, + (PRSendtoFN)invalidInternalCall, + (PRPollFN)invalidInternalCall, + (PRAcceptreadFN)invalidInternalCall, + (PRTransmitfileFN)invalidInternalCall, + (PRGetsocknameFN)invalidInternalCall, + PRByteBufferGetPeerName, + (PRReservedFN)invalidInternalCall, + (PRReservedFN)invalidInternalCall, + PRByteBufferGetSocketOption, + PRByteBufferSetSocketOption, + (PRSendfileFN)invalidInternalCall, + (PRConnectcontinueFN)invalidInternalCall, + (PRReservedFN)invalidInternalCall, + (PRReservedFN)invalidInternalCall, + (PRReservedFN)invalidInternalCall, + (PRReservedFN)invalidInternalCall +}; + +/* Construct a new PRFileDesc backed by a pair of buffers. Note that these + * should be separate buffers, but need not be unique to this PRFileDesc; + * that is, a client and server could share (but be swapped) j_bytebuffers. + * The caller is expected to provide a peer_info to be used for optional + * session resumption; this will be truncated at 16 bytes of data, and + * extended with nulls if it is shorter. It is suggested that this be an IPv4 + * or IPv6 address. Note that this value is not used to validate the hostname + * in any way (see SSL_SetURL to validate the peer). */ +PRFileDesc *newByteBufferPRFileDesc(j_bytebuffer *read_buf, j_bytebuffer *write_buf, + uint8_t *peer_info, size_t peer_info_len) +{ + PRFileDesc *fd; + + if (byte_buffer_layer_id == 0) { + byte_buffer_layer_id = PR_GetUniqueIdentity("ByteBuffer"); + } + + fd = PR_CreateIOLayerStub(byte_buffer_layer_id, &PRIOByteBufferMethods); + if (fd) { + fd->secret = PR_NEW(PRFilePrivate); + + fd->secret->read_buffer = read_buf; + fd->secret->write_buffer = write_buf; + + size_t len = peer_info_len; + if (len > 16) { len = 16; } + + fd->secret->peer_addr = calloc(16, sizeof(uint8_t)); + memcpy(fd->secret->peer_addr, peer_info, len); + } + + return fd; +} diff --git a/src/main/java/org/mozilla/jss/ssl/javax/ByteBufferPRFD.h b/src/main/java/org/mozilla/jss/ssl/javax/ByteBufferPRFD.h new file mode 100644 index 000000000..f7cb8c6f4 --- /dev/null +++ b/src/main/java/org/mozilla/jss/ssl/javax/ByteBufferPRFD.h @@ -0,0 +1,24 @@ +#include + +/* Necessary for correctly propagating E_WOULDBLOCK. */ +#include + +#include +#include + +/* Ring buffer implementation to handle read/write calls. */ +#include "j_bytebuffer.h" + +/* Use a modern pragma guard... */ +#pragma once + +/* Construct a new PRFileDesc backed by a pair of ByteBuffers. Note that these + * should be separate buffers, but need not be unique to this PRFileDesc; + * that is, a client and server could share (but be swapped) j_bytebuffers. + * The caller is expected to provide a peer_info to be used for optional + * session resumption; this will be truncated at 16 bytes of data, and + * extended with nulls if it is shorter. It is suggested that this be an IPv4 + * or IPv6 address. Note that this value is not used to validate the hostname + * in any way (see SSL_SetURL to validate the peer). */ +PRFileDesc *newByteBufferPRFileDesc(j_bytebuffer *read_buf, j_bytebuffer *write_buf, + uint8_t *peer_info, size_t peer_info_len); diff --git a/src/main/java/org/mozilla/jss/ssl/javax/JSSEngineOptimizedImpl.java b/src/main/java/org/mozilla/jss/ssl/javax/JSSEngineOptimizedImpl.java new file mode 100644 index 000000000..091f10731 --- /dev/null +++ b/src/main/java/org/mozilla/jss/ssl/javax/JSSEngineOptimizedImpl.java @@ -0,0 +1,1409 @@ +package org.mozilla.jss.ssl.javax; + +import java.lang.*; +import java.util.*; + +import java.io.InputStream; +import java.io.OutputStream; +import java.net.ServerSocket; +import java.net.Socket; +import java.nio.channels.WritableByteChannel; +import java.nio.channels.Channels; +import java.security.PublicKey; +import java.security.cert.CertificateException; + +import java.nio.ByteBuffer; + +import javax.net.ssl.*; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.mozilla.jss.nss.*; +import org.mozilla.jss.pkcs11.*; +import org.mozilla.jss.provider.javax.crypto.*; +import org.mozilla.jss.ssl.*; + +import org.mozilla.jss.crypto.Policy; +import org.mozilla.jss.crypto.PrivateKey; +import org.mozilla.jss.crypto.X509Certificate; + +/** + * An optimized JSSEngine implementation. + * + * This JSSEngine implementation is optimized for lower JNI overhead when + * compared to the reference implementation. + */ +public class JSSEngineOptimizedImpl extends JSSEngine { + private String peer_info; + + private boolean closed_fd = true; + private ByteBufferProxy read_buf; + private ByteBufferProxy write_buf; + + private int unknown_state_count; + private boolean step_handshake; + + private SSLException ssl_exception; + private boolean seen_exception; + + private CertValidationTask task; + + public JSSEngineOptimizedImpl() { + super(); + + peer_info = ""; + + logger.debug("JSSEngine: constructor()"); + } + + public JSSEngineOptimizedImpl(String peerHost, int peerPort) { + super(peerHost, peerPort); + + peer_info = peerHost + ":" + peerPort; + + logger.debug("JSSEngine: constructor(" + peerHost + ", " + peerPort + ")"); + } + + public JSSEngineOptimizedImpl(String peerHost, int peerPort, + org.mozilla.jss.crypto.X509Certificate localCert, + org.mozilla.jss.crypto.PrivateKey localKey) { + super(peerHost, peerPort, localCert, localKey); + + peer_info = peerHost + ":" + peerPort; + + logger.debug("JSSEngine: constructor(" + peerHost + ", " + peerPort + ", " + localCert + ", " + localKey + ")"); + } + + private void init() throws SSLException { + logger.debug("JSSEngine: init()"); + + // Initialize our JSSEngine when we begin to handshake; otherwise, + // calls to Set