Author: remm Date: Thu Jun 18 17:10:08 2015 New Revision: 1686276 URL: http://svn.apache.org/r1686276 Log: - Add JNI API updates from Netty and Twitter. jni.socket.* is not useful to Tomcat at the moment, but is a NIO2 style API on top of APR. - Update recommended native library version to 1.2. If not using the new OpenSSL features, this shouldn't break and I prefer not requiring a trunk build.
Added: tomcat/trunk/java/org/apache/tomcat/jni/CertificateVerifier.java tomcat/trunk/java/org/apache/tomcat/jni/SSLExt.java tomcat/trunk/java/org/apache/tomcat/jni/socket/ tomcat/trunk/java/org/apache/tomcat/jni/socket/AprSocket.java tomcat/trunk/java/org/apache/tomcat/jni/socket/AprSocketContext.java tomcat/trunk/java/org/apache/tomcat/jni/socket/HostInfo.java Modified: tomcat/trunk/NOTICE tomcat/trunk/java/org/apache/catalina/core/AprLifecycleListener.java tomcat/trunk/java/org/apache/tomcat/jni/SSL.java tomcat/trunk/java/org/apache/tomcat/jni/SSLContext.java Modified: tomcat/trunk/NOTICE URL: http://svn.apache.org/viewvc/tomcat/trunk/NOTICE?rev=1686276&r1=1686275&r2=1686276&view=diff ============================================================================== --- tomcat/trunk/NOTICE (original) +++ tomcat/trunk/NOTICE Thu Jun 18 17:10:08 2015 @@ -4,6 +4,12 @@ Copyright 1999-2015 The Apache Software This product includes software developed at The Apache Software Foundation (http://www.apache.org/). +This software contains code derived from netty-native +developed by the Netty project +(http://netty.io, https://github.com/netty/netty-tcnative/) +and from finagle-native developed at Twitter +(https://github.com/twitter/finagle). + The Windows Installer is built with the Nullsoft Scriptable Install System (NSIS), which is open source software. The original software and @@ -15,6 +21,13 @@ JDT Core Batch Compiler component, which The original software and related information is available at http://www.eclipse.org/jdt/core/. +For portions of the Tomcat JNI OpenSSL API and the OpenSSL JSSE integration +The org.apache.tomcat.jni and the org.apache.tomcat.net.openssl packages +are derivative work originating from the Netty project and the finagle-native +project developed at Twitter +* Copyright 2014 The Netty Project +* Copyright 2014 Twitter + The original XML Schemas for Java EE Deployment Descriptors: - javaee_5.xsd - javaee_web_services_1_2.xsd Modified: tomcat/trunk/java/org/apache/catalina/core/AprLifecycleListener.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/core/AprLifecycleListener.java?rev=1686276&r1=1686275&r2=1686276&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/core/AprLifecycleListener.java (original) +++ tomcat/trunk/java/org/apache/catalina/core/AprLifecycleListener.java Thu Jun 18 17:10:08 2015 @@ -68,8 +68,8 @@ public class AprLifecycleListener protected static final int TCN_REQUIRED_MAJOR = 1; protected static final int TCN_REQUIRED_MINOR = 1; protected static final int TCN_REQUIRED_PATCH = 32; - protected static final int TCN_RECOMMENDED_MINOR = 1; - protected static final int TCN_RECOMMENDED_PV = 33; + protected static final int TCN_RECOMMENDED_MINOR = 2; + protected static final int TCN_RECOMMENDED_PV = 0; // ---------------------------------------------- Properties Added: tomcat/trunk/java/org/apache/tomcat/jni/CertificateVerifier.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/jni/CertificateVerifier.java?rev=1686276&view=auto ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/jni/CertificateVerifier.java (added) +++ tomcat/trunk/java/org/apache/tomcat/jni/CertificateVerifier.java Thu Jun 18 17:10:08 2015 @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ +package org.apache.tomcat.jni; + +/** + * Is called during handshake and hooked into openssl via {@code SSL_CTX_set_cert_verify_callback}. + */ +public interface CertificateVerifier { + + /** + * Returns {@code true} if the passed in certificate chain could be verified and so the handshake + * should be successful, {@code false} otherwise. + * + * @param ssl the SSL instance + * @param x509 the {@code X509} certificate chain + * @param authAlgorithm the auth algorithm + * @return verified {@code true} if verified successful, {@code false} otherwise + */ + boolean verify(long ssl, byte[][] x509, String authAlgorithm); +} Modified: tomcat/trunk/java/org/apache/tomcat/jni/SSL.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/jni/SSL.java?rev=1686276&r1=1686275&r2=1686276&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/jni/SSL.java (original) +++ tomcat/trunk/java/org/apache/tomcat/jni/SSL.java Thu Jun 18 17:10:08 2015 @@ -226,6 +226,14 @@ public final class SSL { * Add certificate chain number to that flag (0 ... verify depth) */ public static final int SSL_INFO_CLIENT_CERT_CHAIN = 0x0400; + + /* Only support OFF and SERVER for now */ + public static final long SSL_SESS_CACHE_OFF = 0x0000; + public static final long SSL_SESS_CACHE_SERVER = 0x0002; + + public static final int SSL_SELECTOR_FAILURE_NO_ADVERTISE = 0; + public static final int SSL_SELECTOR_FAILURE_CHOOSE_MY_LAST_PROTOCOL = 1; + /* Return OpenSSL version number */ public static native int version(); @@ -347,4 +355,301 @@ public final class SSL { * @return true if all SSL_OP_* are supported by OpenSSL library. */ public static native boolean hasOp(int op); + + /* + * Begin Twitter API additions + */ + + public static final int SSL_SENT_SHUTDOWN = 1; + public static final int SSL_RECEIVED_SHUTDOWN = 2; + + public static final int SSL_ERROR_NONE = 0; + public static final int SSL_ERROR_SSL = 1; + public static final int SSL_ERROR_WANT_READ = 2; + public static final int SSL_ERROR_WANT_WRITE = 3; + public static final int SSL_ERROR_WANT_X509_LOOKUP = 4; + public static final int SSL_ERROR_SYSCALL = 5; /* look at error stack/return value/errno */ + public static final int SSL_ERROR_ZERO_RETURN = 6; + public static final int SSL_ERROR_WANT_CONNECT = 7; + public static final int SSL_ERROR_WANT_ACCEPT = 8; + + /** + * SSL_new + * @param ctx Server or Client context to use. + * @param server if true configure SSL instance to use accept handshake routines + * if false configure SSL instance to use connect handshake routines + * @return pointer to SSL instance (SSL *) + */ + public static native long newSSL(long ctx, boolean server); + + /** + * SSL_set_bio + * @param ssl SSL pointer (SSL *) + * @param rbio read BIO pointer (BIO *) + * @param wbio write BIO pointer (BIO *) + */ + public static native void setBIO(long ssl, long rbio, long wbio); + + /** + * SSL_get_error + * @param ssl SSL pointer (SSL *) + * @param ret TLS/SSL I/O return value + */ + public static native int getError(long ssl, int ret); + + /** + * BIO_ctrl_pending + * @param bio BIO pointer (BIO *) + * @return + */ + public static native int pendingWrittenBytesInBIO(long bio); + + /** + * SSL_pending + * @param ssl SSL pointer (SSL *) + * @return + */ + public static native int pendingReadableBytesInSSL(long ssl); + + /** + * BIO_write + * @param bio + * @param wbuf + * @param wlen + * @return + */ + public static native int writeToBIO(long bio, long wbuf, int wlen); + + /** + * BIO_read + * @param bio + * @param rbuf + * @param rlen + * @return + */ + public static native int readFromBIO(long bio, long rbuf, int rlen); + + /** + * SSL_write + * @param ssl the SSL instance (SSL *) + * @param wbuf + * @param wlen + * @return + */ + public static native int writeToSSL(long ssl, long wbuf, int wlen); + + /** + * SSL_read + * @param ssl the SSL instance (SSL *) + * @param rbuf + * @param rlen + * @return + */ + public static native int readFromSSL(long ssl, long rbuf, int rlen); + + /** + * SSL_get_shutdown + * @param ssl the SSL instance (SSL *) + * @return + */ + public static native int getShutdown(long ssl); + + /** + * SSL_set_shutdown + * @param ssl the SSL instance (SSL *) + * @param mode + */ + public static native void setShutdown(long ssl, int mode); + + /** + * SSL_free + * @param ssl the SSL instance (SSL *) + */ + public static native void freeSSL(long ssl); + + /** + * Wire up internal and network BIOs for the given SSL instance. + * + * <b>Warning: you must explicitly free this resource by calling freeBIO</b> + * + * While the SSL's internal/application data BIO will be freed when freeSSL is called on + * the provided SSL instance, you must call freeBIO on the returned network BIO. + * + * @param ssl the SSL instance (SSL *) + * @return pointer to the Network BIO (BIO *) + */ + public static native long makeNetworkBIO(long ssl); + + /** + * BIO_free + * @param bio + */ + public static native void freeBIO(long bio); + + /** + * BIO_flush + * @param bio + */ + public static native void flushBIO(long bio); + + /** + * SSL_shutdown + * @param ssl the SSL instance (SSL *) + * @return + */ + public static native int shutdownSSL(long ssl); + + /** + * Get the error number representing the last error OpenSSL encountered on this thread. + * @return + */ + public static native int getLastErrorNumber(); + + /** + * SSL_get_cipher + * @param ssl the SSL instance (SSL *) + * @return + */ + public static native String getCipherForSSL(long ssl); + + /** + * SSL_get_version + * @param ssl the SSL instance (SSL *) + * @return + */ + public static native String getVersion(long ssl); + + /** + * SSL_do_handshake + * @param ssl the SSL instance (SSL *) + */ + public static native int doHandshake(long ssl); + + /** + * SSL_in_init + * @param SSL + * @return + */ + public static native int isInInit(long SSL); + + /** + * SSL_get0_next_proto_negotiated + * @param ssl the SSL instance (SSL *) + * @return + */ + public static native String getNextProtoNegotiated(long ssl); + + /* + * End Twitter API Additions + */ + + /** + * SSL_get0_alpn_selected + * @param ssl the SSL instance (SSL *) + * @return + */ + public static native String getAlpnSelected(long ssl); + + /** + * Get the peer certificate chain or {@code null} if non was send. + */ + public static native byte[][] getPeerCertChain(long ssl); + + /** + * Get the peer certificate or {@code null} if non was send. + */ + public static native byte[] getPeerCertificate(long ssl); + /* + * Get the error number representing for the given {@code errorNumber}. + */ + public static native String getErrorString(long errorNumber); + + /** + * SSL_get_time + * @param ssl the SSL instance (SSL *) + * @return returns the time at which the session ssl was established. The time is given in seconds since the Epoch + */ + public static native long getTime(long ssl); + + /** + * Set Type of Client Certificate verification and Maximum depth of CA Certificates + * in Client Certificate verification. + * <br /> + * This directive sets the Certificate verification level for the Client + * Authentication. Notice that this directive can be used both in per-server + * and per-directory context. In per-server context it applies to the client + * authentication process used in the standard SSL handshake when a connection + * is established. In per-directory context it forces a SSL renegotiation with + * the reconfigured client verification level after the HTTP request was read + * but before the HTTP response is sent. + * <br /> + * The following levels are available for level: + * <pre> + * SSL_CVERIFY_NONE - No client Certificate is required at all + * SSL_CVERIFY_OPTIONAL - The client may present a valid Certificate + * SSL_CVERIFY_REQUIRE - The client has to present a valid Certificate + * SSL_CVERIFY_OPTIONAL_NO_CA - The client may present a valid Certificate + * but it need not to be (successfully) verifiable + * </pre> + * <br /> + * The depth actually is the maximum number of intermediate certificate issuers, + * i.e. the number of CA certificates which are max allowed to be followed while + * verifying the client certificate. A depth of 0 means that self-signed client + * certificates are accepted only, the default depth of 1 means the client + * certificate can be self-signed or has to be signed by a CA which is directly + * known to the server (i.e. the CA's certificate is under + * {@code setCACertificatePath}, etc. + * + * @param ssl the SSL instance (SSL *) + * @param level Type of Client Certificate verification. + * @param depth Maximum depth of CA Certificates in Client Certificate + * verification. + */ + public static native void setVerify(long ssl, int level, int depth); + + /** + * Set OpenSSL Option. + * @param ssl the SSL instance (SSL *) + * @param options See SSL.SSL_OP_* for option flags. + */ + public static native void setOptions(long ssl, int options); + + /** + * Get OpenSSL Option. + * @param ssl the SSL instance (SSL *) + * @return options See SSL.SSL_OP_* for option flags. + */ + public static native int getOptions(long ssl); + + /** + * Returns all Returns the cipher suites that are available for negotiation in an SSL handshake. + * @param ssl the SSL instance (SSL *) + * @return ciphers + */ + public static native String[] getCiphers(long ssl); + + /** + * Returns the cipher suites available for negotiation in SSL handshake. + * <br /> + * This complex directive uses a colon-separated cipher-spec string consisting + * of OpenSSL cipher specifications to configure the Cipher Suite the client + * is permitted to negotiate in the SSL handshake phase. Notice that this + * directive can be used both in per-server and per-directory context. + * In per-server context it applies to the standard SSL handshake when a + * connection is established. In per-directory context it forces a SSL + * renegotiation with the reconfigured Cipher Suite after the HTTP request + * was read but before the HTTP response is sent. + * @param ssl the SSL instance (SSL *) + * @param ciphers an SSL cipher specification + */ + public static native boolean setCipherSuites(long ssl, String ciphers) + throws Exception; + + /** + * Returns the ID of the session as byte array representation. + * + * @param ssl the SSL instance (SSL *) + * @return the session as byte array representation obtained via SSL_SESSION_get_id. + */ + public static native byte[] getSessionId(long ssl); } Modified: tomcat/trunk/java/org/apache/tomcat/jni/SSLContext.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/jni/SSLContext.java?rev=1686276&r1=1686275&r2=1686276&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/jni/SSLContext.java (original) +++ tomcat/trunk/java/org/apache/tomcat/jni/SSLContext.java Thu Jun 18 17:10:08 2015 @@ -14,6 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.apache.tomcat.jni; import java.util.Map; @@ -96,6 +97,13 @@ public final class SSLContext { public static native void setOptions(long ctx, int options); /** + * Get OpenSSL Option. + * @param ctx Server or Client context to use. + * @return options See SSL.SSL_OP_* for option flags. + */ + public static native int getOptions(long ctx); + + /** * Clears OpenSSL Options. * @param ctx Server or Client context to use. * @param options See SSL.SSL_OP_* for option flags. @@ -214,6 +222,62 @@ public final class SSLContext { throws Exception; /** + * Set the size of the internal session cache. + * http://www.openssl.org/docs/ssl/SSL_CTX_sess_set_cache_size.html + */ + public static native long setSessionCacheSize(long ctx, long size); + + /** + * Get the size of the internal session cache. + * http://www.openssl.org/docs/ssl/SSL_CTX_sess_get_cache_size.html + */ + public static native long getSessionCacheSize(long ctx); + + /** + * Set the timeout for the internal session cache in seconds. + * http://www.openssl.org/docs/ssl/SSL_CTX_set_timeout.html + */ + public static native long setSessionCacheTimeout(long ctx, long timeoutSeconds); + + /** + * Get the timeout for the internal session cache in seconds. + * http://www.openssl.org/docs/ssl/SSL_CTX_set_timeout.html + */ + public static native long getSessionCacheTimeout(long ctx); + + /** + * Set the mode of the internal session cache and return the previous used mode. + */ + public static native long setSessionCacheMode(long ctx, long mode); + + /** + * Get the mode of the current used internal session cache. + */ + public static native long getSessionCacheMode(long ctx); + + /** + * Session resumption statistics methods. + * http://www.openssl.org/docs/ssl/SSL_CTX_sess_number.html + */ + public static native long sessionAccept(long ctx); + public static native long sessionAcceptGood(long ctx); + public static native long sessionAcceptRenegotiate(long ctx); + public static native long sessionCacheFull(long ctx); + public static native long sessionCbHits(long ctx); + public static native long sessionConnect(long ctx); + public static native long sessionConnectGood(long ctx); + public static native long sessionConnectRenegotiate(long ctx); + public static native long sessionHits(long ctx); + public static native long sessionMisses(long ctx); + public static native long sessionNumber(long ctx); + public static native long sessionTimeouts(long ctx); + + /** + * Set TLS session keys. This allows us to share keys across TFEs. + */ + public static native void setSessionTicketKeys(long ctx, byte[] keys); + + /** * Set File and Directory of concatenated PEM-encoded CA Certificates * for Client Auth * <br> @@ -377,4 +441,72 @@ public final class SSLContext { */ public long getSslContext(String sniHostName); } + + /** + * Allow to hook {@link CertificateVerifier} into the handshake processing. + * This will call {@code SSL_CTX_set_cert_verify_callback} and so replace the default verification + * callback used by openssl + * @param ctx Server or Client context to use. + * @param verifier the verifier to call during handshake. + */ + public static native void setCertVerifyCallback(long ctx, CertificateVerifier verifier); + + /** + * Set next protocol for next protocol negotiation extension + * @param ctx Server context to use. + * @param nextProtos comma delimited list of protocols in priority order + * + * @deprecated use {@link #setNpnProtos(long, String[], int)} + */ + @Deprecated + public static void setNextProtos(long ctx, String nextProtos) { + setNpnProtos(ctx, nextProtos.split(","), SSL.SSL_SELECTOR_FAILURE_CHOOSE_MY_LAST_PROTOCOL); + } + + /** + * Set next protocol for next protocol negotiation extension + * @param ctx Server context to use. + * @param nextProtos protocols in priority order + * @param selectorFailureBehavior see {@link SSL#SSL_SELECTOR_FAILURE_NO_ADVERTISE} + * and {@link SSL#SSL_SELECTOR_FAILURE_CHOOSE_MY_LAST_PROTOCOL} + */ + public static native void setNpnProtos(long ctx, String[] nextProtos, int selectorFailureBehavior); + + /** + * Set application layer protocol for application layer protocol negotiation extension + * @param ctx Server context to use. + * @param alpnProtos protocols in priority order + * @param selectorFailureBehavior see {@link SSL#SSL_SELECTOR_FAILURE_NO_ADVERTISE} + * and {@link SSL#SSL_SELECTOR_FAILURE_CHOOSE_MY_LAST_PROTOCOL} + */ + public static native void setAlpnProtos(long ctx, String[] alpnProtos, int selectorFailureBehavior); + + /** + * Set DH parameters + * @param ctx Server context to use. + * @param cert DH param file (can be generated from e.g. {@code openssl dhparam -rand - 2048 > dhparam.pem} - + * see the <a href="https://www.openssl.org/docs/apps/dhparam.html">OpenSSL documentation</a>). + */ + public static native void setTmpDH(long ctx, String cert) + throws Exception; + + /** + * Set ECDH elliptic curve by name + * @param ctx Server context to use. + * @param curveName the name of the elliptic curve to use + * (available names can be obtained from {@code openssl ecparam -list_curves}). + */ + public static native void setTmpECDHByCurveName(long ctx, String curveName) + throws Exception; + + /** + * Set the context within which session be reused (server side only) + * http://www.openssl.org/docs/ssl/SSL_CTX_set_session_id_context.html + * + * @param ctx Server context to use. + * @param sidCtx can be any kind of binary data, it is therefore possible to use e.g. the name + * of the application and/or the hostname and/or service name + * @return {@code true} if success, {@code false} otherwise. + */ + public static native boolean setSessionIdContext(long ctx, byte[] sidCtx); } Added: tomcat/trunk/java/org/apache/tomcat/jni/SSLExt.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/jni/SSLExt.java?rev=1686276&view=auto ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/jni/SSLExt.java (added) +++ tomcat/trunk/java/org/apache/tomcat/jni/SSLExt.java Thu Jun 18 17:10:08 2015 @@ -0,0 +1,159 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ +package org.apache.tomcat.jni; + +/** + * Support TLS extensions and extra methods. + * + * The methods are separated to make it easier for java code to + * support existing native library - it can check if this class can + * be loaded in order to use the exensions. + * + * @author Costin Manolache + */ +public final class SSLExt { + + + /** + * Set advertised NPN protocol. + * This is only available for recent or patched openssl. + * + * Example: "\x06spdy/2" + * + * Works with TLS1, doesn't with SSL2/SSL3 + * + * Servers sends list in ServerHelo, client selects it and + * sends it back after ChangeChipher + * + * Not supported in 1.0.0, seems to be in 1.0.1 and after + */ + public static native int setNPN(long tcctx, byte[] proto, int len); + + /** + * Get other side's advertised protocols. + * Only works after handshake. + */ + public static native int getNPN(long tcsock, byte[] proto); + + /** + * Enabling dump/debugging on the socket. Both raw and decrypted + * packets will be logged. + */ + public static native int debug(long tcsock); + + /** + * Server: Extract the session data associated with the socket. + * Must be saved, keyed by session ID. + */ + public static native byte[] getSessionData(long tcsock); + + /** + * Server: Set the session data for a socket. + */ + public static native int setSessionData(long tcsock, byte[] data, int len); + + + /** + * Client: get the ticket received from server, if tickets are supported. + */ + public static native int getTicket(long tcsock, byte[] resBuf); + + /** + * Client: set the previously received ticket. + */ + public static native int setTicket(long tcsock, byte[] data, int len); + + /** + * Set the key used by server to generate tickets. + * Key must be 48 bytes. + */ + public static native int setTicketKeys(long ctx, byte[] data, int len); + + /** + * For client side calls. Data should be a \0 terminated string + */ + public static native int setSNI(long tcsock, byte[] data, int len); + + /** + * Return the last openssl error + */ + public static native String sslErrReasonErrorString(); + + public static native long sslCtxSetMode(long ctx, long mode); + + /* Allow SSL_write(..., n) to return r with 0 < r < n (i.e. report success + * when just a single record has been written): */ + public static final int SSL_MODE_ENABLE_PARTIAL_WRITE = 0x1; + + /* Make it possible to retry SSL_write() with changed buffer location + * (buffer contents must stay the same!); this is not the default to avoid + * the misconception that non-blocking SSL_write() behaves like + * non-blocking write(): */ + public static final int SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER = 0x2; + + /* Don't attempt to automatically build certificate chain */ + static final int SSL_MODE_NO_AUTO_CHAIN = 0x8; + + /* Save RAM by releasing read and write buffers when they're empty. (SSL3 and + * TLS only.) "Released" buffers are put onto a free-list in the context + * or just freed (depending on the context's setting for freelist_max_len). */ + public static final int SSL_MODE_RELEASE_BUFFERS = 0x10; + + // 1.1 + //static final int SSL_MODE_HANDSHAKE_CUTTHROUGH = ..; + + /** + * SSL_set_mode + */ + public static native long sslSetMode(long tcsock, long mode); + + public static int setNPN(long sslContext, byte[] spdyNPN) { + try { + return SSLExt.setNPN(sslContext, spdyNPN, spdyNPN.length); + } catch (Throwable t) { + t.printStackTrace(); + return -1; + } + } + + /** + * Higher level method, checking if the specified protocol has been + * negotiated. + */ + public static boolean checkNPN(long tcsocket, byte[] expected) { + byte[] npn = new byte[expected.length + 1]; + int npnLen = 0; + try { + npnLen = SSLExt.getNPN(tcsocket, npn); + if (npnLen != expected.length) { + return false; + } + } catch (Throwable t) { + // ignore + return false; + } + for (int i = 0; i < expected.length; i++) { + if (expected[i] != npn[i]) { + return false; + } + } + return true; + } + + + +} Added: tomcat/trunk/java/org/apache/tomcat/jni/socket/AprSocket.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/jni/socket/AprSocket.java?rev=1686276&view=auto ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/jni/socket/AprSocket.java (added) +++ tomcat/trunk/java/org/apache/tomcat/jni/socket/AprSocket.java Thu Jun 18 17:10:08 2015 @@ -0,0 +1,922 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ +package org.apache.tomcat.jni.socket; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.tomcat.jni.Address; +import org.apache.tomcat.jni.Error; +import org.apache.tomcat.jni.Poll; +import org.apache.tomcat.jni.SSL; +import org.apache.tomcat.jni.SSLExt; +import org.apache.tomcat.jni.SSLSocket; +import org.apache.tomcat.jni.Sockaddr; +import org.apache.tomcat.jni.Socket; +import org.apache.tomcat.jni.Status; +import org.apache.tomcat.jni.socket.AprSocketContext.AprPoller; +import org.apache.tomcat.jni.socket.AprSocketContext.BlockingPollHandler; + +/** + * Native socket, using JNI + APR + openssl. + * + * The socket is non-blocking - you can register either a blocking or non + * blocking callback. + * + * There is no explicit method to register/unregister poll interest - + * it is done automatically, when read/write methods return 0. + * + * To keep the socket polling you must read all the available data, until + * read() returns 0. If you want to pause - don't read all input. To resume - + * read again until it returns 0. + * + * Same for write - when write() returns 0 the socket is registered for + * write interest. + * + * You can also use the blocking read/write methods. + */ +public class AprSocket implements Runnable { + + private static final Logger log = + Logger.getLogger("org.apache.tomcat.jni.socket.AprSocket"); + + private static final byte[][] NO_CERTS = new byte[0][]; + + static final int CONNECTING = 0x1; + static final int CONNECTED = 0x2; + + // Current ( real ) poll status + static final int POLLIN_ACTIVE = 0x4; + static final int POLLOUT_ACTIVE = 0x8; + + static final int POLL = 0x10; + + static final int SSL_ATTACHED = 0x40; + + // Requested poll status. Set by read/write when needed. + // Cleared when polled + static final int POLLIN = 0x80; + static final int POLLOUT = 0x100; + + static final int ACCEPTED = 0x200; + static final int ERROR = 0x400; + static final int CLOSED = 0x800; + + static final int READING = 0x1000; + static final int WRITING = 0x2000; + + // Not null + private final AprSocketContext context; + + // only one - to save per/socket memory - context has similar callbacks. + BlockingPollHandler handler; + + // Set while it's associated with a poller - it'll stay associated after + // connect until close. Destroy will happen in the poller. + // POLL bit indicates if the socket is actually polling. + AprPoller poller; + + // Bit field indicating the status and socket should only be accessed with + // socketLock protection + private int status; + + long socket; + + //long to = 10000; + + // Persistent info about the peer ( SSL, etc ) + private HostInfo hostInfo; + + AprSocket(AprSocketContext context) { + this.context = context; + } + + public void recycle() { + status = 0; + hostInfo = null; + handler = null; + socket = 0; + poller = null; + } + + @Override + public String toString() { + return (context.isServer() ? "AprSrv-" : "AprCli-") + + Long.toHexString(socket) + " " + Integer.toHexString(status); + } + + public void setHandler(BlockingPollHandler l) { + handler = l; + } + + private void setNonBlocking() { + if (socket != 0 && context.running) { + Socket.optSet(socket, Socket.APR_SO_NONBLOCK, 1); + Socket.timeoutSet(socket, 0); + } + } + + /** + * Check if the socket is currently registered with a poller. + */ + public boolean isPolling() { + synchronized (this) { + return (status & POLL) != 0; + } + } + + public BlockingPollHandler getHandler() { + return handler; + } + + public AprSocketContext getContext() { + return context; + } + + AprSocket setHost(HostInfo hi) { + hostInfo = hi; + return this; + } + + /** + */ + public void connect() throws IOException { + if (isBlocking()) { + // will call handleConnected() at the end. + context.connectBlocking(this); + } else { + synchronized(this) { + if ((status & CONNECTING) != 0) { + return; + } + status |= CONNECTING; + } + context.connectExecutor.execute(this); + } + } + + + // after connection is done, called from a thread pool ( not IO thread ) + // may block for handshake. + void afterConnect() throws IOException { + if (hostInfo.secure) { + blockingStartTLS(); + } + + setNonBlocking(); // call again, to set the bits ( connect was blocking ) + + setStatus(CONNECTED); + clearStatus(CONNECTING); + + notifyConnected(false); + } + + public HostInfo getHost() { + return hostInfo; + } + + /** + * Write as much data as possible to the socket. + * + * @param data + * @param off + * @param len + * @return For both blocking and non-blocking, returns the number of bytes + * written. If no data can be written (e.g. if the buffers are + * full) 0 will be returned. + * @throws IOException + */ + public int write(byte[] data, int off, int len, long to) throws IOException { + long max = System.currentTimeMillis() + to; + + while (true) { + int rc = writeInternal(data, off, len); + if (rc < 0) { + throw new IOException("Write error " + rc); + } else if (rc == 0) { + // need poll out - do we need to update polling ? + context.findPollerAndAdd(this); + } else { + return rc; + } + + try { + long waitTime = max - System.currentTimeMillis(); + if (waitTime <= 0) { + return 0; + } + wait(waitTime); + } catch (InterruptedException e) { + return 0; + } + } + } + + public int write(byte[] data, int off, int len) throws IOException { + // In SSL mode, read/write can't be called at the same time. + int rc = writeInternal(data, off, len); + if (rc < 0) { + throw new IOException("Write error " + rc); + } else if (rc == 0) { + // need poll out - do we need to update polling ? + synchronized (this) { + context.findPollerAndAdd(this); + } + } + return rc; + } + + private int writeInternal(byte[] data, int off, int len) throws IOException { + int rt = 0; + int sent = 0; + synchronized(this) { + if ((status & CLOSED) != 0 + || socket == 0 + || !context.running) { + throw new IOException("Closed"); + } + if ((status & WRITING) != 0) { + throw new IOException("Write from 2 threads not allowed"); + } + status |= WRITING; + + while (len > 0) { + sent = Socket.send(socket, data, off, len); + if (sent <= 0) { + break; + } + off += sent; + len -= sent; + } + + status &= ~WRITING; + } + + if (context.rawDataHandler != null) { + context.rawData(this, false, data, off, sent, len, false); + } + + if (sent <= 0) { + if (sent == -Status.TIMEUP || sent == -Status.EAGAIN || sent == 0) { + setStatus(POLLOUT); + updatePolling(); + return rt; + } + log.warning("apr.send(): Failed to send, closing " + sent); + reset(); + throw new IOException("Error sending " + sent + " " + Error.strerror(-sent)); + } else { + off += sent; + len -= sent; + rt += sent; + return sent; + } + } + + public int read(byte[] data, int off, int len, long to) throws IOException { + int rd = readNB(data, off, len); + if (rd == 0) { + synchronized(this) { + try { + wait(to); + } catch (InterruptedException e) { + return 0; + } + } + rd = readNB(data, off, len); + } + return processReadResult(data, off, len, rd); + } + + public int read(byte[] data, int off, int len) throws IOException { + return readNB(data, off, len); + } + + private int processReadResult(byte[] data, int off, int len, int read) + throws IOException { + if (context.rawDataHandler != null) { + context.rawData(this, true, data, off, read, len, false); + } + + if (read > 0) { + return read; + } + + if (read == 0 || read == -Status.TIMEUP || read == -Status.ETIMEDOUT + || read == -Status.EAGAIN) { + read = 0; + setStatus(POLLIN); + updatePolling(); + return 0; + } + + if (read == -Status.APR_EOF || read == -1) { + close(); + return -1; + } + // abrupt close + reset(); + throw new IOException("apr.read(): " + read + " " + Error.strerror(-read)); + } + + public int readNB(byte[] data, int off, int len) throws IOException { + int read; + synchronized(this) { + if ((status & CLOSED) != 0 + || socket == 0 + || !context.running) { + return -1; + } + if ((status & READING) != 0) { + throw new IOException("Read from 2 threads not allowed"); + } + status |= READING; + + read = Socket.recv(socket, data, off, len); + status &= ~READING; + } + return processReadResult(data, off, len, read); + } + + /* + No support for shutdownOutput: SSL is quite tricky. + Use close() instead - no read/write will be allowed after. + + */ + + public void close() { + synchronized (this) { + if ((status & CLOSED) != 0 || socket == 0) { + return; + } + status |= CLOSED; + status &= ~POLLIN; + status &= ~POLLOUT; + } + if (context.rawDataHandler != null) { + context.rawDataHandler.rawData(this, false, null, 0, 0, 0, true); + } + Socket.close(socket); + if (poller == null) { + maybeDestroy(); + } else { + try { + poller.requestUpdate(this); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + void maybeDestroy() { + synchronized(this) { + if (socket == 0 || + (status & CONNECTING) != 0 || !context.running) { + // closed or operation in progress + // if context stopped, pool will be destroyed and close + // all sockets automatically. + return; + } + if ((status & CLOSED) == 0) { + return; // not closed + } + if ((status & (WRITING | READING)) != 0) { + return; // not closed + } + + if (context.rawDataHandler != null) { + context.rawDataHandler.rawData(this, false, null, -1, -1, -1, true); + } + if (log.isLoggable(Level.FINE)) { + log.info("closing: context.open=" + context.open.get() + " " + this); + } + + context.open.decrementAndGet(); + + if (socket != 0 && (status & CLOSED) == 0) { + Socket.close(socket); + status |= CLOSED; + } + + if (handler != null) { + if (isBlocking()) { + context.getExecutor().execute(this); + } else { + handler.closed(this); + } + } + + context.destroySocket(this); + } + } + + + + /** + * Close input and output, potentially sending RST, than close the socket. + * + * The proper way to close when gracefully done is by calling writeEnd() and + * reading all remaining input until -1 (EOF) is received. + * + * If EOF is received, the proper way to close is send whatever is remaining and + * call writeEnd(); + */ + public void reset() { + setStatus(ERROR); + close(); + } + + + /** + */ + public boolean isClosed() { + synchronized(this) { + if ((status & CLOSED) != 0 || socket == 0 || !context.running) { + return true; + } + return false; + } + } + + public long getIOTimeout() throws IOException { + if (socket != 0 && context.running) { + try { + return Socket.timeoutGet(socket) / 1000; + } catch (Exception e) { + throw new IOException(e); + } + } else { + throw new IOException("Socket is closed"); + } + } + + // Cert is in DER format + // Called after handshake + public byte[][] getPeerCert(boolean check) throws IOException { + getHost(); + if (hostInfo.certs != null && hostInfo.certs != NO_CERTS && !check) { + return hostInfo.certs; + } + if (!checkBitAndSocket(SSL_ATTACHED)) { + return NO_CERTS; + } + try { + int certLength = SSLSocket.getInfoI(socket, + SSL.SSL_INFO_CLIENT_CERT_CHAIN); + // TODO: if resumed, old certs are good. + // If not - warn if certs changed, remember first cert, etc. + if (certLength <= 0) { + // Can also happen on session resume - keep the old + if (hostInfo.certs == null) { + hostInfo.certs = NO_CERTS; + } + return hostInfo.certs; + } + hostInfo.certs = new byte[certLength + 1][]; + + hostInfo.certs[0] = SSLSocket.getInfoB(socket, + SSL.SSL_INFO_CLIENT_CERT); + for (int i = 0; i < certLength; i++) { + hostInfo.certs[i + 1] = SSLSocket.getInfoB(socket, + SSL.SSL_INFO_CLIENT_CERT_CHAIN + i); + } + return hostInfo.certs; + } catch (Exception e) { + throw new IOException(e); + } + } + + public X509Certificate[] getPeerX509Cert() throws IOException { + byte[][] certs = getPeerCert(false); + X509Certificate[] xcerts = new X509Certificate[certs.length]; + if (certs.length == 0) { + return xcerts; + } + try { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + for (int i = 0; i < certs.length; i++) { + if (certs[i] != null) { + ByteArrayInputStream bis = new ByteArrayInputStream( + certs[i]); + xcerts[i] = (X509Certificate) cf.generateCertificate(bis); + bis.close(); + } + } + } catch (CertificateException ex) { + throw new IOException(ex); + } + return xcerts; + } + + public String getCipherSuite() throws IOException { + if (checkBitAndSocket(SSL_ATTACHED)) { + return null; + } + try { + return SSLSocket.getInfoS(socket, SSL.SSL_INFO_CIPHER); + } catch (Exception e) { + throw new IOException(e); + } + } + + public int getKeySize() throws IOException { + if (checkBitAndSocket(SSL_ATTACHED)) { + return -1; + } + try { + return SSLSocket.getInfoI(socket, SSL.SSL_INFO_CIPHER_USEKEYSIZE); + } catch (Exception e) { + throw new IOException(e); + } + } + + public int getRemotePort() throws IOException { + if (socket != 0 && context.running) { + try { + long sa = Address.get(Socket.APR_REMOTE, socket); + Sockaddr addr = Address.getInfo(sa); + return addr.port; + } catch (Exception ex) { + throw new IOException(ex); + } + } + throw new IOException("Socket closed"); + } + + public String getRemoteAddress() throws IOException { + if (socket != 0 && context.running) { + try { + long sa = Address.get(Socket.APR_REMOTE, socket); + return Address.getip(sa); + } catch (Exception ex) { + throw new IOException(ex); + } + } + throw new IOException("Socket closed"); + } + + public String getRemoteHostname() throws IOException { + if (socket != 0 && context.running) { + try { + long sa = Address.get(Socket.APR_REMOTE, socket); + String remoteHost = Address.getnameinfo(sa, 0); + if (remoteHost == null) { + remoteHost = Address.getip(sa); + } + return remoteHost; + } catch (Exception ex) { + throw new IOException(ex); + } + } + throw new IOException("Socket closed"); + } + + public int getLocalPort() throws IOException { + if (socket != 0 && context.running) { + try { + long sa = Address.get(Socket.APR_LOCAL, socket); + Sockaddr addr = Address.getInfo(sa); + return addr.port; + } catch (Exception ex) { + throw new IOException(ex); + } + } + throw new IOException("Socket closed"); + } + + public String getLocalAddress() throws IOException { + if (socket != 0 && context.running) { + try { + long sa = Address.get(Socket.APR_LOCAL, socket); + return Address.getip(sa); + } catch (Exception ex) { + throw new IOException(ex); + } + } + throw new IOException("Socket closed"); + } + + public String getLocalHostname() throws IOException { + if (socket != 0 && context.running) { + try { + long sa = Address.get(Socket.APR_LOCAL, socket); + return Address.getnameinfo(sa, 0); + } catch (Exception ex) { + throw new IOException(ex); + } + } + throw new IOException("Socket closed"); + } + + public boolean isBlocking() { + return ! (handler instanceof AprSocketContext.NonBlockingPollHandler); + } + + public boolean isError() { + return checkPreConnect(ERROR); + } + + void notifyError(Throwable err, boolean needsThread) { + if (handler instanceof AprSocketContext.NonBlockingPollHandler) { + if (err != null) { + ((AprSocketContext.NonBlockingPollHandler) handler).error(this, err); + } + } else { + // poller destroyed, etc + if (needsThread) { + context.getExecutor().execute(this); + } else { + try { + notifyIO(); + } catch (IOException e) { + log.log(Level.SEVERE, this + " error ", e); + } + } + } + } + + // Called after connect and from poller. + void notifyIO() throws IOException { + long t0 = System.currentTimeMillis(); + try { + if (handler != null) { + handler.process(this, true, false, false); + } + } catch (Throwable t) { + throw new IOException(t); + } finally { + long t1 = System.currentTimeMillis(); + t1 -= t0; + if (t1 > context.maxHandlerTime.get()) { + context.maxHandlerTime.set(t1); + } + context.totalHandlerTime.addAndGet(t1); + context.handlerCount.incrementAndGet(); + } + } + + private void notifyConnected(boolean server) throws IOException { + // Will set the handler on the channel for accepted + context.onSocket(this); + + if (handler instanceof AprSocketContext.NonBlockingPollHandler) { + ((AprSocketContext.NonBlockingPollHandler) handler).connected(this); + + ((AprSocketContext.NonBlockingPollHandler) handler).process(this, true, true, false); + // Now register for polling - unless process() set suspendRead and + // doesn't need out notifications + updatePolling(); + } else { + if (server) { + // client will block in connect(). + // Server: call process(); + notifyIO(); + } + } + } + + private void updatePolling() throws IOException { + synchronized (this) { + if ((status & CLOSED) != 0) { + maybeDestroy(); + return; + } + } + context.findPollerAndAdd(this); + } + + @Override + public void run() { + if (!context.running) { + return; + } + if (checkPreConnect(CLOSED)) { + if (handler != null) { + handler.closed(this); + } + return; + } + if (!checkPreConnect(CONNECTED)) { + if (checkBitAndSocket(ACCEPTED)) { + try { + context.open.incrementAndGet(); + + if (log.isLoggable(Level.FINE)) { + log.info("Accept: " + context.open.get() + " " + this + " " + + getRemotePort()); + } + if (context.tcpNoDelay) { + Socket.optSet(socket, Socket.APR_TCP_NODELAY, 1); + } + + setStatus(CONNECTED); + if (context.sslMode) { + Socket.timeoutSet(socket, + context.connectTimeout * 1000L); + blockingStartTLS(); + } + setNonBlocking(); // call again, to set the bits ( connect was blocking ) + + notifyConnected(true); + return; + } catch (Throwable t) { + t.printStackTrace(); // no error handler yet + reset(); + notifyError(t, false); + return; + } + } + if (checkPreConnect(CONNECTING)) { + // Non-blocking connect - will call 'afterConnection' at the end. + try { + context.connectBlocking(this); + } catch (IOException t) { + reset(); // also sets status ERROR + if (handler instanceof AprSocketContext.NonBlockingPollHandler) { + ((AprSocketContext.NonBlockingPollHandler) handler).process(this, false, false, true); + } + notifyError(t, false); + } + } + } else { + if (handler != null) { + try { + notifyIO(); + } catch (Throwable e) { + log.log(Level.SEVERE, this + " error ", e); + reset(); + // no notifyIO - just did it. + } + } + } + } + + /** + * This is a blocking call ! ( can be made non-blocking, but too complex ) + * + * Will be called automatically after connect() or accept if 'secure' is + * true. + * + * Can be called manually to upgrade the channel + * @throws IOException + */ + public void blockingStartTLS() throws IOException { + synchronized(this) { + if (socket == 0 || !context.running) { + return; + } + if ((status & SSL_ATTACHED) != 0) { + return; + } + status |= SSL_ATTACHED; + } + + try { + if (log.isLoggable(Level.FINE)) { + log.info(this + " StartSSL"); + } + + AprSocketContext aprCon = context; + SSLSocket.attach(aprCon.getSslCtx(), socket); + + if (context.debugSSL) { + SSLExt.debug(socket); + } + if (!getContext().isServer()) { + if (context.USE_TICKETS && hostInfo.ticketLen > 0) { + SSLExt.setTicket(socket, hostInfo.ticket, + hostInfo.ticketLen); + } else if (hostInfo.sessDer != null) { + SSLExt.setSessionData(socket, hostInfo.sessDer, + hostInfo.sessDer.length); + } + } + SSLExt.sslSetMode(socket, SSLExt.SSL_MODE_ENABLE_PARTIAL_WRITE | + SSLExt.SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + + int rc = SSLSocket.handshake(socket); + + // At this point we have the session ID, remote certs, etc + // we can lookup host info + if (hostInfo == null) { + hostInfo = new HostInfo(); + } + + if (rc != Status.APR_SUCCESS) { + throw new IOException(this + " Handshake failed " + rc + " " + + Error.strerror(rc) + " SSLL " + + SSL.getLastError()); + } else { // SUCCESS + handshakeDone(); + } + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new IOException(e); + } + } + + private void handshakeDone() throws IOException { + getHost(); + if (socket == 0 || !context.running) { + throw new IOException("Socket closed"); + } + if (context.USE_TICKETS && ! context.isServer()) { + if (hostInfo.ticket == null) { + hostInfo.ticket = new byte[2048]; + } + int ticketLen = SSLExt.getTicket(socket, hostInfo.ticket); + if (ticketLen > 0) { + hostInfo.ticketLen = ticketLen; + if (log.isLoggable(Level.FINE)) { + log.info("Received ticket: " + ticketLen); + } + } + } + + // TODO: if the ticket, session id or session changed - callback to + // save the session again + try { + hostInfo.sessDer = SSLExt.getSessionData(socket); + getPeerCert(true); + hostInfo.sessionId = SSLSocket.getInfoS(socket, + SSL.SSL_INFO_SESSION_ID); + } catch (Exception e) { + throw new IOException(e); + } + + hostInfo.npn = new byte[32]; + hostInfo.npnLen = SSLExt.getNPN(socket, hostInfo.npn); + + // If custom verification is used - should check the certificates + if (context.tlsCertVerifier != null) { + context.tlsCertVerifier.handshakeDone(this); + } + } + + int requestedPolling() { + synchronized(this) { + if (socket == 0 || ((status & CLOSED) != 0)) { + return 0; + } + // Implicit: + //Poll.APR_POLLNVAL | Poll.APR_POLLHUP | Poll.APR_POLLERR | + int res = 0; + if ((status & POLLIN) != 0) { + res = Poll.APR_POLLIN; + } + if ((status & POLLOUT) != 0) { + res |= Poll.APR_POLLOUT; + } + return res; + } + } + + boolean checkBitAndSocket(int bit) { + synchronized (this) { + return ((status & bit) != 0 && socket != 0 && + (status & CLOSED) == 0 && context.running); + } + } + + boolean checkPreConnect(int bit) { + synchronized (this) { + return ((status & bit) != 0); + } + } + + void clearStatus(int bit) { + synchronized (this) { + status &= ~bit; + } + } + + boolean setStatus(int bit) { + synchronized (this) { + int old = status & bit; + status |= bit; + return old != 0; + } + } + + +} \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org