JNI: return original wolfSSL error from getError() instead of SSL_ERROR_SYSCALL

pull/442/head
Chris Conlon 2026-10-20 16:15:05 -06:00
parent 3f48caebcf
commit 62b90e0328
7 changed files with 326 additions and 20 deletions

View File

@ -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

View File

@ -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

View File

@ -230,6 +230,8 @@ public class WolfSSL {
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 */

View File

@ -2039,6 +2039,11 @@ public class WolfSSLSession {
* After <code>getError()</code> is called and returns the unique error
* code, <code>getErrorString()</code> may be called to get a human-
* readable error string.
* <p>
* 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.

View File

@ -239,6 +239,76 @@ public class WolfSSLEngineTest {
}
/**
* 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<String> 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,

View File

@ -41,6 +41,7 @@ import java.util.concurrent.TimeUnit;
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 class WolfSSLSocketTest {
/* 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 class WolfSSLSocketTest {
/* 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 class WolfSSLSocketTest {
}
}
/* 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 @@ public class WolfSSLSocketTest {
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 @@ public class WolfSSLSocketTest {
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<String> 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<Void> 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 {

View File

@ -767,14 +767,17 @@ public class WolfSSLSessionTest {
}
/* Accept one TLS connection on a background thread. A server-side
* handshake failure is expected once the client rejects the cert. */
private Future<Void> 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<int[]> runOneShotTlsServer(final ServerSocket srvSocket,
final WolfSSLContext srvCtx, ExecutorService es) {
return es.submit(new Callable<Void>() {
return es.submit(new Callable<int[]>() {
@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 class WolfSSLSessionTest {
} 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 class WolfSSLSessionTest {
} catch (Exception e) { }
}
}
return null;
return result;
}
});
}
@ -816,7 +820,7 @@ public class WolfSSLSessionTest {
WolfSSLContext srvCtx = null;
WolfSSLContext cliCtx = null;
ExecutorService es = null;
Future<Void> srvFuture = null;
Future<int[]> srvFuture = null;
Socket cliSock = null;
WolfSSLSession ssl = null;
@ -1101,6 +1105,97 @@ public class WolfSSLSessionTest {
}
}
@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<int[]> 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()) {