F-9129: check the negotiated DH group size against the configured DH minimum in checkKeySize
parent
c5d622670c
commit
ea207d9f94
|
|
@ -4492,6 +4492,22 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_getKeySize
|
|||
#endif
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_getDhKeySize
|
||||
(JNIEnv* jenv, jobject jcl, jlong ssl)
|
||||
{
|
||||
(void)jenv;
|
||||
(void)jcl;
|
||||
|
||||
#ifndef NO_DH
|
||||
/* Returns negotiated DH group size in bits, 0 if no DH suite was
|
||||
* negotiated, or BAD_FUNC_ARG if ssl is NULL. */
|
||||
return wolfSSL_GetDhKey_Sz((WOLFSSL*)(uintptr_t)ssl);
|
||||
#else
|
||||
(void)ssl;
|
||||
return NOT_COMPILED_IN;
|
||||
#endif
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_getSide
|
||||
(JNIEnv* jenv, jobject jcl, jlong ssl)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -655,6 +655,14 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_WolfSSLSession_getServerWriteIV
|
|||
JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_getKeySize
|
||||
(JNIEnv *, jobject, jlong);
|
||||
|
||||
/*
|
||||
* Class: com_wolfssl_WolfSSLSession
|
||||
* Method: getDhKeySize
|
||||
* Signature: (J)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_getDhKeySize
|
||||
(JNIEnv *, jobject, jlong);
|
||||
|
||||
/*
|
||||
* Class: com_wolfssl_WolfSSLSession
|
||||
* Method: getSide
|
||||
|
|
|
|||
|
|
@ -659,6 +659,7 @@ public class WolfSSLSession {
|
|||
private native byte[] getServerWriteKey(long ssl);
|
||||
private native byte[] getServerWriteIV(long ssl);
|
||||
private native int getKeySize(long ssl);
|
||||
private native int getDhKeySize(long ssl);
|
||||
private native int getSide(long ssl);
|
||||
private native int isTLSv1_1(long ssl);
|
||||
private native int getBulkCipher(long ssl);
|
||||
|
|
@ -4381,6 +4382,27 @@ public class WolfSSLSession {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get DH group size (bits) negotiated during handshake.
|
||||
*
|
||||
* @return negotiated DH group size in bits, or 0 if the handshake
|
||||
* did not use a Diffie-Hellman cipher suite. Returns negative
|
||||
* error code on error (ex: NOT_COMPILED_IN, BAD_FUNC_ARG, etc).
|
||||
* @throws IllegalStateException WolfSSLSession object has been freed
|
||||
* @see #getKeySize()
|
||||
*/
|
||||
public int getDhKeySize() throws IllegalStateException {
|
||||
|
||||
confirmObjectIsActive();
|
||||
|
||||
synchronized (sslLock) {
|
||||
WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI,
|
||||
WolfSSLDebug.INFO, this.sslPtr, () -> "entered getDhKeySize()");
|
||||
|
||||
return getDhKeySize(this.sslPtr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows retrieval of the side of this wolfSSL connection.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -2164,60 +2164,42 @@ public class WolfSSLEngineHelper {
|
|||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the Diffie-Hellman group negotiated during the handshake is at
|
||||
* least the minimum size configured in jdk.tls.disabledAlgorithms.
|
||||
*/
|
||||
private void checkKeySize(WolfSSLSession ssl, boolean clientMode)
|
||||
throws SSLException, WolfSSLException {
|
||||
|
||||
int keySize = this.ssl.getKeySize();
|
||||
/* Skip unless a DHE group was negotiated. getDhKeySize() returns 0
|
||||
* for a non-DHE handshake and a negative code when DH is not compiled
|
||||
* into native wolfSSL, neither of which needs checking. */
|
||||
int dhKeySize = ssl.getDhKeySize();
|
||||
if (dhKeySize <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Before we update the cached values, and return from the handshake,
|
||||
* we check if we are running a legacy cipher suite, if so, we make sure
|
||||
* that the actual key size is at least 1024 bits.
|
||||
*/
|
||||
String[] cipherSuites = getCiphers();
|
||||
try {
|
||||
int minDHKeySize =
|
||||
WolfSSLUtil.getDisabledAlgorithmsKeySizeLimit("DH");
|
||||
|
||||
if (containsDHECiphers(cipherSuites)) {
|
||||
/* Get the minimum DH key size from security settings. */
|
||||
int minDHEKeySize;
|
||||
try {
|
||||
minDHEKeySize =
|
||||
WolfSSLUtil.getDisabledAlgorithmsKeySizeLimit("DH");
|
||||
|
||||
/*
|
||||
* If we're trying to use DHE with
|
||||
* insufficient key size, throw early. */
|
||||
if (isLegacyDHEnabled() && keySize < minDHEKeySize) {
|
||||
if (clientMode) {
|
||||
throw new SSLHandshakeException(
|
||||
"DH ServerKeyExchange does not comply to " +
|
||||
"algorithm constraints");
|
||||
} else {
|
||||
throw new SSLHandshakeException(
|
||||
"Received fatal alert: insufficient_security");
|
||||
}
|
||||
/* A minimum of 0 means no limit is configured */
|
||||
if (dhKeySize < minDHKeySize) {
|
||||
if (clientMode) {
|
||||
throw new SSLHandshakeException(
|
||||
"DH ServerKeyExchange does not comply to " +
|
||||
"algorithm constraints");
|
||||
} else {
|
||||
throw new SSLHandshakeException(
|
||||
"Received fatal alert: insufficient_security");
|
||||
}
|
||||
} catch (WolfSSLException e) {
|
||||
throw new WolfSSLException(
|
||||
"Failed to check DH key size constraints: ", e);
|
||||
}
|
||||
} catch (WolfSSLException e) {
|
||||
throw new WolfSSLException(
|
||||
"Failed to check DH key size constraints: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean containsDHECiphers(String[] cipherSuites) {
|
||||
for (String suite : cipherSuites) {
|
||||
if (suite.contains("_DHE_")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isLegacyDHEnabled() {
|
||||
/* Check if legacy DH is enabled through system properties. */
|
||||
String dhKeySize = System.getProperty("jdk.tls.ephemeralDHKeySize");
|
||||
return "legacy".equals(dhKeySize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates Server Name Indication (SNI) match between client request and
|
||||
* server matchers.
|
||||
|
|
|
|||
|
|
@ -421,6 +421,120 @@ public class WolfSSLSocketTest {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* A valid TLS 1.2 DHE_RSA handshake over a 2048 bit FFDHE group must
|
||||
* complete even with the legacy jdk.tls.ephemeralDHKeySize property set.
|
||||
* The group is above the DH minimum, so checkKeySize must not reject it.
|
||||
*/
|
||||
@Test
|
||||
public void testLegacyEphemeralDHKeySizeAllowsValidDheHandshake()
|
||||
throws Exception {
|
||||
|
||||
Assume.assumeTrue("TLS 1.2 and RSA required in native wolfSSL",
|
||||
WolfSSL.TLSv12Enabled() && WolfSSL.RsaEnabled());
|
||||
|
||||
String[] dheSuites = dheRsaIanaSuites();
|
||||
Assume.assumeTrue("DHE_RSA cipher suites not available",
|
||||
dheSuites.length > 0);
|
||||
|
||||
synchronized (WolfSSLPQCTestUtil.GROUP_PROP_LOCK) {
|
||||
String prevWolf = WolfSSLPQCTestUtil.setCurvesProperty(null);
|
||||
String prevJdk =
|
||||
WolfSSLPQCTestUtil.setJdkNamedGroupsProperty("ffdhe2048");
|
||||
String prevEphemeral =
|
||||
System.getProperty("jdk.tls.ephemeralDHKeySize");
|
||||
System.clearProperty("jdk.tls.ephemeralDHKeySize");
|
||||
|
||||
try {
|
||||
/* Probe without legacy property first, skip if build cannot
|
||||
* complete ffdhe2048 DHE handshake. */
|
||||
try {
|
||||
dheSocketHandshake(dheSuites);
|
||||
} catch (Exception e) {
|
||||
Assume.assumeNoException(
|
||||
"TLS 1.2 ffdhe2048 DHE handshake not supported by " +
|
||||
"this build", e);
|
||||
}
|
||||
|
||||
/* Legacy property must not cause checkKeySize to reject same
|
||||
* valid handshake. */
|
||||
System.setProperty("jdk.tls.ephemeralDHKeySize", "legacy");
|
||||
String suite = dheSocketHandshake(dheSuites);
|
||||
assertTrue("negotiated cipher suite must be DHE_RSA, got: " +
|
||||
suite, suite.startsWith("TLS_DHE_RSA_") ||
|
||||
suite.startsWith("DHE-RSA-"));
|
||||
}
|
||||
finally {
|
||||
if (prevEphemeral == null) {
|
||||
System.clearProperty("jdk.tls.ephemeralDHKeySize");
|
||||
} else {
|
||||
System.setProperty("jdk.tls.ephemeralDHKeySize",
|
||||
prevEphemeral);
|
||||
}
|
||||
WolfSSLPQCTestUtil.restoreCurvesProperty(prevWolf);
|
||||
WolfSSLPQCTestUtil.restoreJdkNamedGroupsProperty(prevJdk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 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");
|
||||
SSLServerSocket ss = null;
|
||||
SSLSocket cs = null;
|
||||
SSLSocket server = null;
|
||||
ExecutorService es = Executors.newSingleThreadExecutor();
|
||||
|
||||
try {
|
||||
ss = (SSLServerSocket)dheCtx.getServerSocketFactory()
|
||||
.createServerSocket(0);
|
||||
ss.setEnabledCipherSuites(dheSuites);
|
||||
ss.setEnabledProtocols(new String[] { "TLSv1.2" });
|
||||
|
||||
cs = (SSLSocket)dheCtx.getSocketFactory().createSocket();
|
||||
cs.setEnabledCipherSuites(dheSuites);
|
||||
cs.setEnabledProtocols(new String[] { "TLSv1.2" });
|
||||
cs.connect(new InetSocketAddress(ss.getLocalPort()));
|
||||
|
||||
final SSLSocket server0 = (SSLSocket)ss.accept();
|
||||
server = server0;
|
||||
|
||||
Future<Void> f = es.submit(() -> {
|
||||
server0.startHandshake();
|
||||
return null;
|
||||
});
|
||||
|
||||
cs.startHandshake();
|
||||
f.get(10, TimeUnit.SECONDS);
|
||||
|
||||
return cs.getSession().getCipherSuite();
|
||||
}
|
||||
finally {
|
||||
es.shutdownNow();
|
||||
if (cs != null) {
|
||||
cs.close();
|
||||
}
|
||||
if (server != null) {
|
||||
server.close();
|
||||
}
|
||||
if (ss != null) {
|
||||
ss.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* IANA-named DHE_RSA cipher suites compiled into native wolfSSL. */
|
||||
private static String[] dheRsaIanaSuites() {
|
||||
ArrayList<String> matched = new ArrayList<>();
|
||||
for (String suite : WolfSSL.getCiphersIana()) {
|
||||
if (suite.startsWith("TLS_DHE_RSA_")) {
|
||||
matched.add(suite);
|
||||
}
|
||||
}
|
||||
return matched.toArray(new String[matched.size()]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSupportedProtocols()
|
||||
throws NoSuchProviderException, NoSuchAlgorithmException {
|
||||
|
|
|
|||
|
|
@ -705,6 +705,177 @@ public class WolfSSLSessionTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_WolfSSLSession_getDhKeySizeBeforeHandshakeAndAfterFree()
|
||||
throws WolfSSLJNIException, WolfSSLException {
|
||||
|
||||
WolfSSLSession ssl = new WolfSSLSession(ctx);
|
||||
|
||||
/* Before any handshake, no DH group is negotiated */
|
||||
int dhSz = ssl.getDhKeySize();
|
||||
assertTrue("getDhKeySize() before handshake should be <= 0, got " +
|
||||
dhSz, dhSz <= 0);
|
||||
|
||||
ssl.freeSSL();
|
||||
|
||||
try {
|
||||
ssl.getDhKeySize();
|
||||
fail("getDhKeySize() after freeSSL() should throw " +
|
||||
"IllegalStateException");
|
||||
} catch (IllegalStateException e) {
|
||||
/* expected */
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_WolfSSLSession_getDhKeySizeAfterDheHandshake()
|
||||
throws Exception {
|
||||
|
||||
Assume.assumeTrue("TLS 1.2 not compiled into native wolfSSL",
|
||||
WolfSSL.TLSv12Enabled());
|
||||
Assume.assumeTrue("DHE-RSA-AES128-GCM-SHA256 not available",
|
||||
suiteAvailable("TLS_DHE_RSA_WITH_AES_128_GCM_SHA256"));
|
||||
Assume.assumeTrue("ffdhe2048 named group not supported",
|
||||
ffdhe2048Supported());
|
||||
|
||||
final String dheSuite = "DHE-RSA-AES128-GCM-SHA256";
|
||||
final int[] ffdhe = new int[] { WolfSSL.WOLFSSL_FFDHE_2048 };
|
||||
|
||||
final WolfSSLContext srvCtx = createAndSetupWolfSSLContext(
|
||||
srvCert, srvKey, WolfSSL.SSL_FILETYPE_PEM, cliCert,
|
||||
WolfSSL.TLSv1_2_ServerMethod());
|
||||
WolfSSLContext cliCtx = createAndSetupWolfSSLContext(
|
||||
cliCert, cliKey, WolfSSL.SSL_FILETYPE_PEM, caCert,
|
||||
WolfSSL.TLSv1_2_ClientMethod());
|
||||
|
||||
final ServerSocket srvSocket = new ServerSocket(0);
|
||||
final int port = srvSocket.getLocalPort();
|
||||
Socket cliSock = null;
|
||||
WolfSSLSession ssl = null;
|
||||
ExecutorService es = Executors.newSingleThreadExecutor();
|
||||
|
||||
try {
|
||||
final Future<Void> srvFuture = es.submit(new Callable<Void>() {
|
||||
@Override
|
||||
public Void call() throws Exception {
|
||||
Socket server = null;
|
||||
WolfSSLSession srvSes = null;
|
||||
try {
|
||||
server = srvSocket.accept();
|
||||
srvSes = new WolfSSLSession(srvCtx);
|
||||
assertEquals("server setCipherList",
|
||||
WolfSSL.SSL_SUCCESS,
|
||||
srvSes.setCipherList(dheSuite));
|
||||
assertEquals("server useSupportedCurves",
|
||||
WolfSSL.SSL_SUCCESS,
|
||||
srvSes.useSupportedCurves(ffdhe));
|
||||
assertEquals("server setFd", WolfSSL.SSL_SUCCESS,
|
||||
srvSes.setFd(server));
|
||||
|
||||
int ret, err;
|
||||
do {
|
||||
ret = srvSes.accept();
|
||||
err = srvSes.getError(ret);
|
||||
} while (ret != WolfSSL.SSL_SUCCESS &&
|
||||
(err == WolfSSL.SSL_ERROR_WANT_READ ||
|
||||
err == WolfSSL.SSL_ERROR_WANT_WRITE));
|
||||
} finally {
|
||||
if (srvSes != null) {
|
||||
srvSes.shutdownSSL();
|
||||
srvSes.freeSSL();
|
||||
}
|
||||
if (server != null) {
|
||||
server.close();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
cliSock = new Socket("localhost", port);
|
||||
ssl = new WolfSSLSession(cliCtx);
|
||||
assertEquals("client setCipherList", WolfSSL.SSL_SUCCESS,
|
||||
ssl.setCipherList(dheSuite));
|
||||
assertEquals("client useSupportedCurves", WolfSSL.SSL_SUCCESS,
|
||||
ssl.useSupportedCurves(ffdhe));
|
||||
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));
|
||||
|
||||
/* Allow for server-side exception to come through */
|
||||
srvFuture.get(10, TimeUnit.SECONDS);
|
||||
|
||||
/* Skip if build cannot complete the ffdhe2048 DHE handshake */
|
||||
Assume.assumeTrue("build cannot complete ffdhe2048 DHE handshake",
|
||||
ret == WolfSSL.SSL_SUCCESS);
|
||||
|
||||
int dhSz = ssl.getDhKeySize();
|
||||
assertTrue("getDhKeySize() after DHE handshake should be > 0, " +
|
||||
"got " + dhSz, dhSz > 0);
|
||||
assertEquals("ffdhe2048 handshake should report 2048 bits",
|
||||
2048, dhSz);
|
||||
|
||||
ssl.shutdownSSL();
|
||||
} finally {
|
||||
es.shutdownNow();
|
||||
if (ssl != null) {
|
||||
ssl.freeSSL();
|
||||
}
|
||||
if (cliSock != null) {
|
||||
cliSock.close();
|
||||
}
|
||||
srvSocket.close();
|
||||
cliCtx.free();
|
||||
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()) {
|
||||
if (suite.equals(ianaName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* True if native wolfSSL supports the ffdhe2048 named group. */
|
||||
private static boolean ffdhe2048Supported() {
|
||||
WolfSSLContext c = null;
|
||||
WolfSSLSession s = null;
|
||||
try {
|
||||
c = new WolfSSLContext(WolfSSL.SSLv23_ClientMethod());
|
||||
s = new WolfSSLSession(c);
|
||||
int[] ffdhe2048 = new int[] { WolfSSL.WOLFSSL_FFDHE_2048 };
|
||||
return s.useSupportedCurves(ffdhe2048) == WolfSSL.SSL_SUCCESS;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
} finally {
|
||||
if (s != null) {
|
||||
try {
|
||||
s.freeSSL();
|
||||
} catch (Exception e) {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
if (c != null) {
|
||||
try {
|
||||
c.free();
|
||||
} catch (Exception e) {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_WolfSSLSession_getPskIdentity()
|
||||
throws WolfSSLJNIException, WolfSSLException {
|
||||
|
|
|
|||
Loading…
Reference in New Issue