diff --git a/native/com_wolfssl_WolfSSL.h b/native/com_wolfssl_WolfSSL.h
index 27a33d67..72a05e4e 100644
--- a/native/com_wolfssl_WolfSSL.h
+++ b/native/com_wolfssl_WolfSSL.h
@@ -101,6 +101,8 @@ extern "C" {
#define com_wolfssl_WolfSSL_UNKNOWN_ALPN_PROTOCOL_NAME_E -405L
#undef com_wolfssl_WolfSSL_APP_DATA_READY
#define com_wolfssl_WolfSSL_APP_DATA_READY -441L
+#undef com_wolfssl_WolfSSL_MATCH_SUITE_ERROR
+#define com_wolfssl_WolfSSL_MATCH_SUITE_ERROR -501L
#undef com_wolfssl_WolfSSL_WOLFSSL_CRL_CHECKALL
#define com_wolfssl_WolfSSL_WOLFSSL_CRL_CHECKALL 1L
#undef com_wolfssl_WolfSSL_WOLFSSL_OCSP_URL_OVERRIDE
diff --git a/native/com_wolfssl_WolfSSLSession.c b/native/com_wolfssl_WolfSSLSession.c
index edea56d2..1b7ca84e 100644
--- a/native/com_wolfssl_WolfSSLSession.c
+++ b/native/com_wolfssl_WolfSSLSession.c
@@ -2209,6 +2209,8 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_getError
(JNIEnv* jenv, jobject jcl, jlong sslPtr, jint ret)
{
WOLFSSL* ssl = (WOLFSSL*)(uintptr_t)sslPtr;
+ int err = 0;
+ int state = 0;
(void)jcl;
if (jenv == NULL) {
@@ -2216,7 +2218,19 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_getError
}
/* wolfSSL checks ssl for NULL */
- return wolfSSL_get_error(ssl, ret);
+ err = wolfSSL_get_error(ssl, ret);
+
+ /* With OPENSSL_EXTRA, wolfSSL_get_error() converts MATCH_SUITE_ERROR and
+ * SOCKET_PEER_CLOSED_E to SSL_ERROR_SYSCALL. Return the original error
+ * instead. */
+ if (err == SSL_ERROR_SYSCALL) {
+ state = wolfSSL_state(ssl);
+ if (state == MATCH_SUITE_ERROR || state == SOCKET_PEER_CLOSED_E) {
+ err = state;
+ }
+ }
+
+ return err;
}
JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_setSession
diff --git a/src/java/com/wolfssl/WolfSSL.java b/src/java/com/wolfssl/WolfSSL.java
index 513701fb..0ccc8156 100644
--- a/src/java/com/wolfssl/WolfSSL.java
+++ b/src/java/com/wolfssl/WolfSSL.java
@@ -230,6 +230,8 @@ public enum TLS_VERSION {
public static final int UNKNOWN_ALPN_PROTOCOL_NAME_E = -405;
/** DTLS application data ready for read */
public static final int APP_DATA_READY = -441;
+ /** Unable to match a cipher suite with peer */
+ public static final int MATCH_SUITE_ERROR = -501;
/* extra definitions from ssl.h */
/** CertManager: check all cert CRLs */
diff --git a/src/java/com/wolfssl/WolfSSLSession.java b/src/java/com/wolfssl/WolfSSLSession.java
index 1dba7692..e35a7d62 100644
--- a/src/java/com/wolfssl/WolfSSLSession.java
+++ b/src/java/com/wolfssl/WolfSSLSession.java
@@ -2039,6 +2039,11 @@ public int sendUserCanceled() throws IllegalStateException {
* After getError() is called and returns the unique error
* code, getErrorString() may be called to get a human-
* readable error string.
+ *
+ * When native wolfSSL is compiled with OPENSSL_EXTRA, wolfSSL_get_error()
+ * converts MATCH_SUITE_ERROR and SSL_ERROR_SOCKET_PEER_CLOSED to
+ * SSL_ERROR_SYSCALL. This method returns the original error code for those
+ * cases instead, to match builds without OPENSSL_EXTRA.
*
* @param ret return value of the previous function which resulted
* in an error return code.
diff --git a/src/test/com/wolfssl/provider/jsse/test/WolfSSLEngineTest.java b/src/test/com/wolfssl/provider/jsse/test/WolfSSLEngineTest.java
index e4c766b4..32683ad5 100644
--- a/src/test/com/wolfssl/provider/jsse/test/WolfSSLEngineTest.java
+++ b/src/test/com/wolfssl/provider/jsse/test/WolfSSLEngineTest.java
@@ -239,6 +239,76 @@ public void testSSLEngineSetCipher()
}
+ /**
+ * A handshake with no cipher suite in common must report the native
+ * MATCH_SUITE_ERROR from the server SSLEngine. Native wolfSSL compiled
+ * with OPENSSL_EXTRA converts that error to SSL_ERROR_SYSCALL in
+ * wolfSSL_get_error(), which would otherwise be reported instead.
+ */
+ @Test
+ public void testCipherSuiteMismatchReportsMatchSuiteError()
+ throws Exception {
+
+ final String srvSuite = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256";
+ final String cliSuite = "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384";
+
+ Assume.assumeTrue("TLS 1.2 and RSA required in native wolfSSL",
+ WolfSSL.TLSv12Enabled() && WolfSSL.RsaEnabled());
+ List available = Arrays.asList(WolfSSL.getCiphersIana());
+ Assume.assumeTrue("ECDHE_RSA AES-GCM cipher suites not available",
+ available.contains(srvSuite) && available.contains(cliSuite));
+
+ this.ctx = tf.createSSLContext("TLSv1.2", engineProvider);
+ SSLEngine server = this.ctx.createSSLEngine();
+ server.setUseClientMode(false);
+ server.setEnabledProtocols(new String[] { "TLSv1.2" });
+ server.setEnabledCipherSuites(new String[] { srvSuite });
+
+ SSLEngine client = this.ctx.createSSLEngine("wolfSSL test", 11111);
+ client.setUseClientMode(true);
+ client.setEnabledProtocols(new String[] { "TLSv1.2" });
+ client.setEnabledCipherSuites(new String[] { cliSuite });
+
+ ByteBuffer empty = ByteBuffer.allocate(0);
+ ByteBuffer cliToSrv = ByteBuffer.allocate(
+ client.getSession().getPacketBufferSize());
+ ByteBuffer srvToCli = ByteBuffer.allocate(
+ server.getSession().getPacketBufferSize());
+ ByteBuffer appData = ByteBuffer.allocate(
+ server.getSession().getApplicationBufferSize());
+ SSLHandshakeException srvException = null;
+
+ for (int i = 0; i < 20 && srvException == null; i++) {
+ client.wrap(empty, cliToSrv);
+ cliToSrv.flip();
+ try {
+ server.unwrap(cliToSrv, appData);
+ Runnable task;
+ while ((task = server.getDelegatedTask()) != null) {
+ task.run();
+ }
+ server.wrap(empty, srvToCli);
+ } catch (SSLHandshakeException e) {
+ srvException = e;
+ }
+ cliToSrv.compact();
+
+ srvToCli.flip();
+ try {
+ client.unwrap(srvToCli, appData);
+ } catch (SSLException e) {
+ /* expected once client processes handshake_failure alert */
+ }
+ srvToCli.compact();
+ }
+
+ assertNotNull("Server handshake should fail with no common suite",
+ srvException);
+ String msg = srvException.getMessage();
+ assertTrue("Server error should report MATCH_SUITE_ERROR, got: " +
+ msg, msg.contains(String.valueOf(WolfSSL.MATCH_SUITE_ERROR)));
+ }
+
@Test
public void testCipherConnectionTLS()
throws NoSuchProviderException, NoSuchAlgorithmException,
diff --git a/src/test/com/wolfssl/provider/jsse/test/WolfSSLSocketTest.java b/src/test/com/wolfssl/provider/jsse/test/WolfSSLSocketTest.java
index f586bfad..70ee11c7 100644
--- a/src/test/com/wolfssl/provider/jsse/test/WolfSSLSocketTest.java
+++ b/src/test/com/wolfssl/provider/jsse/test/WolfSSLSocketTest.java
@@ -41,6 +41,7 @@
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeoutException;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicIntegerArray;
@@ -461,7 +462,7 @@ public void testLegacyEphemeralDHKeySizeAllowsValidDheHandshake()
/* Probe without legacy property first, skip if build cannot
* complete ffdhe2048 DHE handshake. */
try {
- dheSocketHandshake(dheSuites);
+ tls12SocketHandshake(dheSuites, dheSuites);
} catch (Exception e) {
Assume.assumeNoException(
"TLS 1.2 ffdhe2048 DHE handshake not supported by " +
@@ -471,7 +472,7 @@ public void testLegacyEphemeralDHKeySizeAllowsValidDheHandshake()
/* Legacy property must not cause checkKeySize to reject same
* valid handshake. */
System.setProperty("jdk.tls.ephemeralDHKeySize", "legacy");
- String suite = dheSocketHandshake(dheSuites);
+ String suite = tls12SocketHandshake(dheSuites, dheSuites);
assertTrue("negotiated cipher suite must be DHE_RSA, got: " +
suite, suite.startsWith("TLS_DHE_RSA_") ||
suite.startsWith("DHE-RSA-"));
@@ -489,23 +490,27 @@ public void testLegacyEphemeralDHKeySizeAllowsValidDheHandshake()
}
}
- /* Complete a TLS 1.2 DHE_RSA SSLSocket handshake with the given suites
- * and return the negotiated cipher suite. Throws on handshake failure. */
- private String dheSocketHandshake(String[] dheSuites) throws Exception {
- SSLContext dheCtx = tf.createSSLContext("TLSv1.2", "wolfJSSE");
+ /* Complete a TLS 1.2 SSLSocket handshake with the given server and
+ * client cipher suites and return the negotiated cipher suite. On
+ * failure throws the server exception if the server handshake failed,
+ * otherwise the client exception. */
+ private String tls12SocketHandshake(String[] srvSuites,
+ String[] cliSuites) throws Exception {
+
+ SSLContext ctx = tf.createSSLContext("TLSv1.2", "wolfJSSE");
SSLServerSocket ss = null;
SSLSocket cs = null;
SSLSocket server = null;
ExecutorService es = Executors.newSingleThreadExecutor();
try {
- ss = (SSLServerSocket)dheCtx.getServerSocketFactory()
+ ss = (SSLServerSocket)ctx.getServerSocketFactory()
.createServerSocket(0);
- ss.setEnabledCipherSuites(dheSuites);
+ ss.setEnabledCipherSuites(srvSuites);
ss.setEnabledProtocols(new String[] { "TLSv1.2" });
- cs = (SSLSocket)dheCtx.getSocketFactory().createSocket();
- cs.setEnabledCipherSuites(dheSuites);
+ cs = (SSLSocket)ctx.getSocketFactory().createSocket();
+ cs.setEnabledCipherSuites(cliSuites);
cs.setEnabledProtocols(new String[] { "TLSv1.2" });
cs.connect(new InetSocketAddress(ss.getLocalPort()));
@@ -517,8 +522,26 @@ private String dheSocketHandshake(String[] dheSuites) throws Exception {
return null;
});
- cs.startHandshake();
- f.get(10, TimeUnit.SECONDS);
+ Exception cliEx = null;
+ try {
+ cs.startHandshake();
+ } catch (Exception e) {
+ cliEx = e;
+ /* Close client so a still waiting server fails fast */
+ cs.close();
+ }
+
+ try {
+ f.get(10, TimeUnit.SECONDS);
+ } catch (ExecutionException e) {
+ Throwable cause = e.getCause();
+ throw (cause instanceof Exception) ? (Exception)cause : e;
+ } catch (TimeoutException e) {
+ throw (cliEx != null) ? cliEx : e;
+ }
+ if (cliEx != null) {
+ throw cliEx;
+ }
return cs.getSession().getCipherSuite();
}
@@ -547,6 +570,101 @@ private static String[] dheRsaIanaSuites() {
return matched.toArray(new String[matched.size()]);
}
+ /**
+ * A handshake with no cipher suite in common must report the native
+ * MATCH_SUITE_ERROR from the server. Native wolfSSL compiled with
+ * OPENSSL_EXTRA converts that error to SSL_ERROR_SYSCALL in
+ * wolfSSL_get_error(), which reads as an I/O error instead.
+ */
+ @Test
+ public void testCipherSuiteMismatchReportsMatchSuiteError()
+ throws Exception {
+
+ final String srvSuite = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256";
+ final String cliSuite = "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384";
+
+ Assume.assumeTrue("TLS 1.2 and RSA required in native wolfSSL",
+ WolfSSL.TLSv12Enabled() && WolfSSL.RsaEnabled());
+ List available = Arrays.asList(WolfSSL.getCiphersIana());
+ Assume.assumeTrue("ECDHE_RSA AES-GCM cipher suites not available",
+ available.contains(srvSuite) && available.contains(cliSuite));
+
+ try {
+ tls12SocketHandshake(new String[] { srvSuite },
+ new String[] { cliSuite });
+ fail("Handshake should fail with no common cipher suite");
+ } catch (SSLHandshakeException e) {
+ String msg = e.getMessage();
+ assertTrue("Server error should report MATCH_SUITE_ERROR, " +
+ "got: " + msg, msg.contains("error code: " +
+ WolfSSL.MATCH_SUITE_ERROR + ","));
+ }
+ }
+
+ /**
+ * When the peer closes the TCP connection without sending close_notify,
+ * InputStream.read() must return the data already received and then
+ * end of stream. Native wolfSSL returns 0 with SOCKET_PEER_CLOSED_E,
+ * which wolfSSL_get_error() converts to SSL_ERROR_SYSCALL when compiled
+ * with OPENSSL_EXTRA, and read() used to return 0 in that case.
+ */
+ @Test
+ public void testReadReturnsEndOfStreamAfterPeerClose() throws Exception {
+
+ Assume.assumeTrue("TLS 1.2 required in native wolfSSL",
+ WolfSSL.TLSv12Enabled());
+
+ final SSLContext ctx = tf.createSSLContext("TLSv1.2", "wolfJSSE");
+ final byte[] msg = "hello".getBytes(StandardCharsets.UTF_8);
+ final ServerSocket ss = new ServerSocket(0);
+ SSLSocket cs = null;
+ ExecutorService es = Executors.newSingleThreadExecutor();
+
+ try {
+ /* Layer TLS over a plain socket so the server can close TCP
+ * without sending close_notify */
+ Future f = es.submit(() -> {
+ Socket s = ss.accept();
+ SSLSocket server = null;
+ try {
+ server = (SSLSocket)ctx.getSocketFactory()
+ .createSocket(s, "localhost", s.getPort(), false);
+ server.setUseClientMode(false);
+ server.startHandshake();
+ server.getOutputStream().write(msg);
+ }
+ finally {
+ /* Close TCP first so server.close() skips close_notify */
+ s.close();
+ if (server != null) {
+ server.close();
+ }
+ }
+ return null;
+ });
+
+ cs = (SSLSocket)ctx.getSocketFactory().createSocket(
+ "localhost", ss.getLocalPort());
+ cs.setSoTimeout(10000);
+ cs.startHandshake();
+ f.get(10, TimeUnit.SECONDS);
+
+ InputStream in = cs.getInputStream();
+ byte[] buf = new byte[msg.length];
+ assertEquals("read() should return data sent before close",
+ msg.length, in.read(buf, 0, buf.length));
+ assertEquals("read() should return end of stream after peer " +
+ "closed connection", -1, in.read(buf, 0, buf.length));
+ }
+ finally {
+ es.shutdownNow();
+ if (cs != null) {
+ cs.close();
+ }
+ ss.close();
+ }
+ }
+
@Test
public void testGetSupportedProtocols()
throws NoSuchProviderException, NoSuchAlgorithmException {
diff --git a/src/test/com/wolfssl/test/WolfSSLSessionTest.java b/src/test/com/wolfssl/test/WolfSSLSessionTest.java
index 288e4fb2..96c6e091 100644
--- a/src/test/com/wolfssl/test/WolfSSLSessionTest.java
+++ b/src/test/com/wolfssl/test/WolfSSLSessionTest.java
@@ -767,14 +767,17 @@ public Void call() throws Exception {
}
/* Accept one TLS connection on a background thread. A server-side
- * handshake failure is expected once the client rejects the cert. */
- private Future runOneShotTlsServer(final ServerSocket srvSocket,
+ * handshake failure is expected in the tests using this. Returns
+ * {ret, getError(ret)} from the last accept(), or null if an exception
+ * was thrown first. */
+ private Future runOneShotTlsServer(final ServerSocket srvSocket,
final WolfSSLContext srvCtx, ExecutorService es) {
- return es.submit(new Callable() {
+ return es.submit(new Callable() {
@Override
- public Void call() {
+ public int[] call() {
Socket server = null;
WolfSSLSession srvSes = null;
+ int[] result = null;
try {
server = srvSocket.accept();
server.setSoTimeout(10000);
@@ -787,8 +790,9 @@ public Void call() {
} while (ret != WolfSSL.SSL_SUCCESS &&
(err == WolfSSL.SSL_ERROR_WANT_READ ||
err == WolfSSL.SSL_ERROR_WANT_WRITE));
+ result = new int[] { ret, err };
} catch (Exception e) {
- /* expected once the client rejects the certificate */
+ /* expected once the client rejects the handshake */
} finally {
if (srvSes != null) {
try {
@@ -801,7 +805,7 @@ public Void call() {
} catch (Exception e) { }
}
}
- return null;
+ return result;
}
});
}
@@ -816,7 +820,7 @@ private void assertHandshakeFailsWithVerifyCallback(
WolfSSLContext srvCtx = null;
WolfSSLContext cliCtx = null;
ExecutorService es = null;
- Future srvFuture = null;
+ Future srvFuture = null;
Socket cliSock = null;
WolfSSLSession ssl = null;
@@ -1101,6 +1105,97 @@ public Void call() throws Exception {
}
}
+ @Test
+ public void test_WolfSSLSession_getErrorAfterCipherSuiteMismatch()
+ throws Exception {
+
+ Assume.assumeTrue("TLS 1.2 not compiled into native wolfSSL",
+ WolfSSL.TLSv12Enabled());
+ Assume.assumeTrue("ECDHE-RSA AES-GCM cipher suites not available",
+ suiteAvailable("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256") &&
+ suiteAvailable("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"));
+
+ ServerSocket srvSocket = null;
+ WolfSSLContext srvCtx = null;
+ WolfSSLContext cliCtx = null;
+ ExecutorService es = null;
+ Future srvFuture = null;
+ Socket cliSock = null;
+ WolfSSLSession ssl = null;
+
+ try {
+ srvCtx = createAndSetupWolfSSLContext(
+ srvCert, srvKey, WolfSSL.SSL_FILETYPE_PEM, cliCert,
+ WolfSSL.TLSv1_2_ServerMethod());
+ cliCtx = createAndSetupWolfSSLContext(
+ cliCert, cliKey, WolfSSL.SSL_FILETYPE_PEM, caCert,
+ WolfSSL.TLSv1_2_ClientMethod());
+ assertEquals("server setCipherList", WolfSSL.SSL_SUCCESS,
+ srvCtx.setCipherList("ECDHE-RSA-AES128-GCM-SHA256"));
+ assertEquals("client setCipherList", WolfSSL.SSL_SUCCESS,
+ cliCtx.setCipherList("ECDHE-RSA-AES256-GCM-SHA384"));
+
+ srvSocket = new ServerSocket(0);
+ srvSocket.setSoTimeout(10000);
+ es = Executors.newSingleThreadExecutor();
+ srvFuture = runOneShotTlsServer(srvSocket, srvCtx, es);
+
+ cliSock = new Socket("localhost", srvSocket.getLocalPort());
+ cliSock.setSoTimeout(10000);
+ ssl = new WolfSSLSession(cliCtx);
+ assertEquals("client setFd", WolfSSL.SSL_SUCCESS,
+ ssl.setFd(cliSock));
+
+ int ret, err;
+ do {
+ ret = ssl.connect();
+ err = ssl.getError(ret);
+ } while (ret != WolfSSL.SSL_SUCCESS &&
+ (err == WolfSSL.SSL_ERROR_WANT_READ ||
+ err == WolfSSL.SSL_ERROR_WANT_WRITE));
+ assertNotEquals("client handshake should fail with no " +
+ "common cipher suite", WolfSSL.SSL_SUCCESS, ret);
+
+ int[] srv = srvFuture.get(10, TimeUnit.SECONDS);
+ assertNotNull("server accept() should return a result", srv);
+ assertNotEquals("server handshake should fail with no " +
+ "common cipher suite", WolfSSL.SSL_SUCCESS, srv[0]);
+
+ /* Not converted to SSL_ERROR_SYSCALL even when native wolfSSL
+ * is compiled with OPENSSL_EXTRA */
+ assertEquals("server getError() should be MATCH_SUITE_ERROR",
+ WolfSSL.MATCH_SUITE_ERROR, srv[1]);
+
+ } finally {
+ if (ssl != null) {
+ ssl.freeSSL();
+ }
+ if (cliSock != null) {
+ cliSock.close();
+ }
+ /* Wait for server thread before freeing its context */
+ if (srvFuture != null) {
+ try {
+ srvFuture.get(10, TimeUnit.SECONDS);
+ } catch (Exception e) {
+ /* ignore on purpose */
+ }
+ }
+ if (es != null) {
+ es.shutdownNow();
+ }
+ if (srvSocket != null) {
+ srvSocket.close();
+ }
+ if (cliCtx != null) {
+ cliCtx.free();
+ }
+ if (srvCtx != null) {
+ srvCtx.free();
+ }
+ }
+ }
+
/* True if the given IANA cipher suite is compiled into native wolfSSL. */
private static boolean suiteAvailable(String ianaName) {
for (String suite : WolfSSL.getCiphersIana()) {