diff --git a/native/com_wolfssl_WolfSSLCertificate.c b/native/com_wolfssl_WolfSSLCertificate.c index 2f30dcce..a15d951a 100644 --- a/native/com_wolfssl_WolfSSLCertificate.c +++ b/native/com_wolfssl_WolfSSLCertificate.c @@ -1565,6 +1565,107 @@ JNIEXPORT jbooleanArray JNICALL Java_com_wolfssl_WolfSSLCertificate_X509_1get_1k return ret; } +/* Helper function to add an Extended Key Usage (EKU) OID string to the + * jobjectArray array if the flag is set. Returns updated index in array. */ +static int addEkuOid(JNIEnv* jenv, jobjectArray ret, int idx, + unsigned int ekuBits, unsigned int flag, int nid) +{ + WOLFSSL_ASN1_OBJECT* obj = NULL; + jstring ekuStr = NULL; + char oidBuf[128]; + int oidLen = 0; + + if (ekuBits & flag) { + + /* Convert NID to WOLFSSL_ASN1_OBJECT */ + obj = wolfSSL_OBJ_nid2obj(nid); + if (obj != NULL) { + + /* Convert WOLFSSL_ASN1_OBJECT to OID string */ + oidLen = wolfSSL_OBJ_obj2txt(oidBuf, sizeof(oidBuf), obj, 1); + if (oidLen > 0) { + + /* Create Java String and add to array */ + ekuStr = (*jenv)->NewStringUTF(jenv, oidBuf); + if (ekuStr != NULL) { + (*jenv)->SetObjectArrayElement(jenv, ret, idx, ekuStr); + (*jenv)->DeleteLocalRef(jenv, ekuStr); + idx++; + } + } + + /* Free WOLFSSL_ASN1_OBJECT */ + wolfSSL_ASN1_OBJECT_free(obj); + } + } + + return idx; +} + +JNIEXPORT jobjectArray JNICALL Java_com_wolfssl_WolfSSLCertificate_X509_1get_1extended_1key_1usage + (JNIEnv* jenv, jclass jcl, jlong x509Ptr) +{ + jobjectArray ret = NULL; + jclass stringClass = NULL; + unsigned int ekuBits = 0; + int ekuCount = 0; + int idx = 0; + WOLFSSL_X509* x509 = (WOLFSSL_X509*)(uintptr_t)x509Ptr; + (void)jcl; + + if (jenv == NULL || x509 == NULL) { + return NULL; + } + + /* Get extended key usage bitmask from wolfSSL */ + ekuBits = wolfSSL_X509_get_extended_key_usage(x509); + if (ekuBits == 0) { + return NULL; + } + + /* Count how many EKU bits are set */ + if (ekuBits & XKU_SSL_SERVER) ekuCount++; + if (ekuBits & XKU_SSL_CLIENT) ekuCount++; + if (ekuBits & XKU_CODE_SIGN) ekuCount++; + if (ekuBits & XKU_SMIME) ekuCount++; + if (ekuBits & XKU_TIMESTAMP) ekuCount++; + if (ekuBits & XKU_OCSP_SIGN) ekuCount++; + + if (ekuCount == 0) { + return NULL; + } + + /* Create String[] array to return */ + stringClass = (*jenv)->FindClass(jenv, "java/lang/String"); + if (stringClass == NULL) { + return NULL; + } + + ret = (*jenv)->NewObjectArray(jenv, ekuCount, stringClass, NULL); + if (ret == NULL) { + (*jenv)->DeleteLocalRef(jenv, stringClass); + return NULL; + } + + /* Add each EKU OID string to array */ + idx = addEkuOid(jenv, ret, idx, ekuBits, XKU_SSL_SERVER, + EKU_SERVER_AUTH_OID); + idx = addEkuOid(jenv, ret, idx, ekuBits, XKU_SSL_CLIENT, + EKU_CLIENT_AUTH_OID); + idx = addEkuOid(jenv, ret, idx, ekuBits, XKU_CODE_SIGN, + EKU_CODESIGNING_OID); + idx = addEkuOid(jenv, ret, idx, ekuBits, XKU_SMIME, + EKU_EMAILPROTECT_OID); + idx = addEkuOid(jenv, ret, idx, ekuBits, XKU_TIMESTAMP, + EKU_TIMESTAMP_OID); + idx = addEkuOid(jenv, ret, idx, ekuBits, XKU_OCSP_SIGN, + EKU_OCSP_SIGN_OID); + + (*jenv)->DeleteLocalRef(jenv, stringClass); + + return ret; +} + JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_WolfSSLCertificate_X509_1get_1extension (JNIEnv* jenv, jclass jcl, jlong x509Ptr, jstring oidIn) { diff --git a/native/com_wolfssl_WolfSSLCertificate.h b/native/com_wolfssl_WolfSSLCertificate.h index feb428d3..128a426b 100644 --- a/native/com_wolfssl_WolfSSLCertificate.h +++ b/native/com_wolfssl_WolfSSLCertificate.h @@ -181,6 +181,14 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLCertificate_X509_1verify JNIEXPORT jbooleanArray JNICALL Java_com_wolfssl_WolfSSLCertificate_X509_1get_1key_1usage (JNIEnv *, jclass, jlong); +/* + * Class: com_wolfssl_WolfSSLCertificate + * Method: X509_get_extended_key_usage + * Signature: (J)[Ljava/lang/String; + */ +JNIEXPORT jobjectArray JNICALL Java_com_wolfssl_WolfSSLCertificate_X509_1get_1extended_1key_1usage + (JNIEnv *, jclass, jlong); + /* * Class: com_wolfssl_WolfSSLCertificate * Method: X509_get_extension diff --git a/native/com_wolfssl_WolfSSLX509StoreCtx.c b/native/com_wolfssl_WolfSSLX509StoreCtx.c index ec437071..558f6448 100644 --- a/native/com_wolfssl_WolfSSLX509StoreCtx.c +++ b/native/com_wolfssl_WolfSSLX509StoreCtx.c @@ -93,11 +93,17 @@ JNIEXPORT jobjectArray JNICALL Java_com_wolfssl_WolfSSLX509StoreCtx_X509_1STORE_ } XMEMCPY(buf, der, derSz); (*jenv)->ReleaseByteArrayElements(jenv, derArr, buf, 0); + + #if LIBWOLFSSL_VERSION_HEX >= 0x05008000 /* Reverse order, so peer cert is first in returned array, * followed by intermediates, lastly by root. Native * wolfSSL_X509_STORE_GetCerts() returns certs in order of * root to peer, but Java/JSSE expects peer to root */ (*jenv)->SetObjectArrayElement(jenv, certArr, skNum-1-i, derArr); + #else + /* wolfSSL < 5.8.0 returns certs peer->root, matches Java needs */ + (*jenv)->SetObjectArrayElement(jenv, certArr, i, derArr); + #endif (*jenv)->DeleteLocalRef(jenv, derArr); } } diff --git a/src/java/com/wolfssl/WolfSSLCertificate.java b/src/java/com/wolfssl/WolfSSLCertificate.java index bc9fadf7..1d351c98 100644 --- a/src/java/com/wolfssl/WolfSSLCertificate.java +++ b/src/java/com/wolfssl/WolfSSLCertificate.java @@ -98,6 +98,7 @@ public class WolfSSLCertificate implements Serializable { static native int X509_get_pathLength(long x509); static native int X509_verify(long x509, byte[] pubKey, int pubKeySz); static native boolean[] X509_get_key_usage(long x509); + static native String[] X509_get_extended_key_usage(long x509); static native byte[] X509_get_extension(long x509, String oid); static native int X509_is_extension_set(long x509, String oid); static native String X509_get_next_altname(long x509); @@ -1550,6 +1551,34 @@ public boolean[] getKeyUsage() throws IllegalStateException { } } + /** + * Get extended key usage OIDs from X.509 certificate. + * + * Returns an array of OID strings representing the extended key usage + * values present in the certificate. Common OIDs include: + * - 1.3.6.1.5.5.7.3.1 (TLS Web Server Authentication / serverAuth) + * - 1.3.6.1.5.5.7.3.2 (TLS Web Client Authentication / clientAuth) + * - 1.3.6.1.5.5.7.3.3 (Code Signing / codeSigning) + * - 1.3.6.1.5.5.7.3.4 (Email Protection / emailProtection) + * + * @return Array of OID strings, or null if Extended Key Usage extension + * is not present in certificate + * + * @throws IllegalStateException if WolfSSLCertificate has been freed + */ + public String[] getExtendedKeyUsage() throws IllegalStateException { + + confirmObjectIsActive(); + + synchronized (x509Lock) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI, + WolfSSLDebug.INFO, this.x509Ptr, + () -> "entering getExtendedKeyUsage()"); + + return X509_get_extended_key_usage(this.x509Ptr); + } + } + /** * Get DER encoded extension value from a specified OID * diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLEngine.java b/src/java/com/wolfssl/provider/jsse/WolfSSLEngine.java index c1b46784..ec9edf2b 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLEngine.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLEngine.java @@ -354,6 +354,7 @@ private void unsetSSLCallbacks() throws WolfSSLJNIException { ssl.setIOSendByteBuffer(null); ssl.setIOReadCtx(null); ssl.setIOWriteCtx(null); + ssl.setSessionTicketCb(null, null); } } @@ -773,148 +774,155 @@ public synchronized SSLEngineResult wrap(ByteBuffer[] in, int ofst, int len, throw new SSLException(e); } - if (needInit) { - checkAndInitSSLEngine(); - } - - synchronized (netDataLock) { - this.netData = null; - } - - /* Force out buffer to be large enough to hold max packet size */ - if (out.remaining() < - this.engineHelper.getSession().getPacketBufferSize()) { - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "out.remaining() too small (" + - out.remaining() + "), need at least: " + - this.engineHelper.getSession().getPacketBufferSize()); - return new SSLEngineResult(Status.BUFFER_OVERFLOW, hs, 0, 0); - } - - /* Copy buffered data to be sent into output buffer */ - produced = CopyOutPacket(out); - - /* Closing down connection if buffered data has been sent and: - * 1. Outbound has been closed (!outBoundOpen) - * 2. Inbound is closed and close_notify has been sent - */ - if (produced >= 0 && - (!outBoundOpen || (!inBoundOpen && this.closeNotifySent))) { - /* Mark SSLEngine status as CLOSED */ - status = SSLEngineResult.Status.CLOSED; - /* Handshake has finished and SSLEngine is closed, release - * global JNI verify callback pointer */ - this.engineHelper.unsetVerifyCallback(); + /* Wrap in try/finally to ensure we unset callbacks on any exit path */ + try { + if (needInit) { + checkAndInitSSLEngine(); + } - try { - ClosingConnection(); - } catch (SocketException | SocketTimeoutException e) { - throw new SSLException(e); + synchronized (netDataLock) { + this.netData = null; } - produced += CopyOutPacket(out); - } - else if ((produced > 0) && !inBoundOpen && - (!this.closeNotifySent && !this.closeNotifyReceived)) { - /* We had buffered data to send, but inbound was already closed. - * Most likely this is because we needed to send an alert to - * the peer. We should now mark outbound as closed since we - * won't be sending anything after the alert went out. */ - this.outBoundOpen = false; - } - else if (produced == 0) { - /* continue handshake or application data */ - if (!this.handshakeFinished) { - ret = DoHandshake(true); + + /* Force out buffer to be large enough to hold max packet size */ + if (out.remaining() < + this.engineHelper.getSession().getPacketBufferSize()) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "out.remaining() too small (" + + out.remaining() + "), need at least: " + + this.engineHelper.getSession().getPacketBufferSize()); + return new SSLEngineResult(Status.BUFFER_OVERFLOW, hs, 0, 0); } - else { + + /* Copy buffered data to be sent into output buffer */ + produced = CopyOutPacket(out); + + /* Closing down connection if buffered data has been sent and: + * 1. Outbound has been closed (!outBoundOpen) + * 2. Inbound is closed and close_notify has been sent + */ + if (produced >= 0 && + (!outBoundOpen || (!inBoundOpen && this.closeNotifySent))) { + /* Mark SSLEngine status as CLOSED */ + status = SSLEngineResult.Status.CLOSED; + /* Handshake has finished and SSLEngine is closed, release + * global JNI verify callback pointer */ + this.engineHelper.unsetVerifyCallback(); + try { - ret = SendAppData(in, ofst, len); - if (ret > 0) { - consumed += ret; - } + ClosingConnection(); } catch (SocketException | SocketTimeoutException e) { throw new SSLException(e); } + produced += CopyOutPacket(out); } + else if ((produced > 0) && !inBoundOpen && + (!this.closeNotifySent && !this.closeNotifyReceived)) { + /* We had buffered data to send, but inbound was already + * closed. Most likely this is because we needed to send an + * alert to the peer. We should now mark outbound as closed + * since we won't be sending anything after the alert went + * out. */ + this.outBoundOpen = false; + } + else if (produced == 0) { + /* continue handshake or application data */ + if (!this.handshakeFinished) { + ret = DoHandshake(true); + } + else { + try { + ret = SendAppData(in, ofst, len); + if (ret > 0) { + consumed += ret; + } + } catch (SocketException | SocketTimeoutException e) { + throw new SSLException(e); + } + } - /* copy any produced data into output buffer */ - produced += CopyOutPacket(out); - } + /* copy any produced data into output buffer */ + produced += CopyOutPacket(out); + } - SetHandshakeStatus(ret); + SetHandshakeStatus(ret); - if (extraDebugEnabled) { - final HandshakeStatus tmpHs = hs; - final Status tmpStatus = status; - final int tmpConsumed = consumed; - final int tmpProduced = produced; - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "==== [ exiting wrap() ] ============================="); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "setUseClientMode: " + - this.engineHelper.getUseClientMode()); - for (i = 0; i < len; i++) { - final int idx = i; + if (extraDebugEnabled) { + final HandshakeStatus tmpHs = hs; + final Status tmpStatus = status; + final int tmpConsumed = consumed; + final int tmpProduced = produced; WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "ByteBuffer in["+idx+"].remaining(): " + - in[idx].remaining()); + () -> "==== [ exiting wrap() ] =========================="); WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "ByteBuffer in["+idx+"].position(): " + - in[idx].position()); + () -> "setUseClientMode: " + + this.engineHelper.getUseClientMode()); + for (i = 0; i < len; i++) { + final int idx = i; + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "ByteBuffer in["+idx+"].remaining(): " + + in[idx].remaining()); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "ByteBuffer in["+idx+"].position(): " + + in[idx].position()); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "ByteBuffer in["+idx+"].limit(): " + + in[idx].limit()); + } WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "ByteBuffer in["+idx+"].limit(): " + - in[idx].limit()); + () -> "ofst: " + ofst + ", len: " + len); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "out.remaining(): " + out.remaining()); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "out.position(): " + out.position()); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "out.limit(): " + out.limit()); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "internalIOSendBufOffset: " + + this.internalIOSendBufOffset); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "closeNotifySent: " + this.closeNotifySent); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "closeNotifyReceived: " + this.closeNotifyReceived); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "handshakeFinished: " + this.handshakeFinished); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "inBoundOpen: " + this.inBoundOpen); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "outBoundOpen: " + this.outBoundOpen); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "handshakeStatus: " + tmpHs); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "status: " + tmpStatus); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "consumed: " + tmpConsumed); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "produced: " + tmpProduced); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "nativeHandshakeState: " + + this.ssl.getStateStringLong()); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "nativeWantsToRead: " + this.nativeWantsToRead); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "nativeWantsToWrite: " + this.nativeWantsToWrite); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "=================================================="); } - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "ofst: " + ofst + ", len: " + len); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "out.remaining(): " + out.remaining()); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "out.position(): " + out.position()); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "out.limit(): " + out.limit()); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "internalIOSendBufOffset: " + - this.internalIOSendBufOffset); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "closeNotifySent: " + this.closeNotifySent); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "closeNotifyReceived: " + this.closeNotifyReceived); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "handshakeFinished: " + this.handshakeFinished); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "inBoundOpen: " + this.inBoundOpen); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "outBoundOpen: " + this.outBoundOpen); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "handshakeStatus: " + tmpHs); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "status: " + tmpStatus); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "consumed: " + tmpConsumed); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "produced: " + tmpProduced); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "nativeHandshakeState: " + - this.ssl.getStateStringLong()); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "nativeWantsToRead: " + this.nativeWantsToRead); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "nativeWantsToWrite: " + this.nativeWantsToWrite); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "====================================================="); - } - try { - /* Don't hold references to this SSLEngine object in WolfSSLSession, - * can prevent proper garbage collection */ - unsetSSLCallbacks(); + return new SSLEngineResult(status, hs, consumed, produced); - } catch (WolfSSLJNIException e) { - throw new SSLException(e); + } finally { + try { + /* Don't hold references to this SSLEngine object in + * WolfSSLSession, can prevent proper garbage collection */ + unsetSSLCallbacks(); + } catch (WolfSSLJNIException e) { + /* Don't throw from finally block, just log for info */ + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "unsetSSLCallbacks() exception during wrap(), " + + "just logging: " + e.getMessage()); + } } - - return new SSLEngineResult(status, hs, consumed, produced); } /** @@ -1210,302 +1218,321 @@ public synchronized SSLEngineResult unwrap(ByteBuffer in, ByteBuffer[] out, throw new SSLException(e); } - if (getUseClientMode() && needInit) { - /* If unwrap() is called before handshake has started, return - * WANT_WRAP since we'll need to send a ClientHello first */ - hs = SSLEngineResult.HandshakeStatus.NEED_WRAP; - } - else if (hs == SSLEngineResult.HandshakeStatus.NEED_WRAP && - this.internalIOSendBufOffset > 0) { - /* Already have data buffered to send and in NEED_WRAP state, - * just return so wrap() can be called */ - hs = SSLEngineResult.HandshakeStatus.NEED_WRAP; - } - else { - - if (needInit) { - checkAndInitSSLEngine(); + /* Wrap in try/finally to ensure we unset callbacks on any exit path */ + try { + if (getUseClientMode() && needInit) { + /* If unwrap() is called before handshake has started, return + * WANT_WRAP since we'll need to send a ClientHello first */ + hs = SSLEngineResult.HandshakeStatus.NEED_WRAP; } - - if (outBoundOpen == false) { - try { - if (ClosingConnection() == WolfSSL.SSL_SUCCESS) { - /* Mark SSLEngine status as CLOSED */ - status = SSLEngineResult.Status.CLOSED; - /* Handshake has finished and SSLEngine is closed, - * release, global JNI verify callback pointer */ - this.engineHelper.unsetVerifyCallback(); - } - } catch (SocketException | SocketTimeoutException e) { - throw new SSLException(e); - } + else if (hs == SSLEngineResult.HandshakeStatus.NEED_WRAP && + this.internalIOSendBufOffset > 0) { + /* Already have data buffered to send and in NEED_WRAP state, + * just return so wrap() can be called */ + hs = SSLEngineResult.HandshakeStatus.NEED_WRAP; } else { - /* Get previous DTLS drop count and session ticket count, - * before we process any incomming data. Allows us to set - * BUFFER_UNDERFLOW status (or not if packet decrypt failed - * and was dropped) */ - synchronized (ioLock) { - dtlsPrevDropCount = ssl.getDtlsMacDropCount(); - prevSessionTicketCount = this.sessionTicketCount; + + if (needInit) { + checkAndInitSSLEngine(); } - if (this.handshakeFinished == false) { - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "starting or continuing handshake"); - ret = DoHandshake(false); + if (outBoundOpen == false) { + try { + if (ClosingConnection() == WolfSSL.SSL_SUCCESS) { + /* Mark SSLEngine status as CLOSED */ + status = SSLEngineResult.Status.CLOSED; + /* Handshake has finished and SSLEngine is closed, + * release, global JNI verify callback pointer */ + this.engineHelper.unsetVerifyCallback(); + } + } catch (SocketException | SocketTimeoutException e) { + throw new SSLException(e); + } } else { - /* If we have input data, make sure output buffer length is - * greater than zero, otherwise ask app to expand out buffer. - * There may be edge cases where this could be tightened up, - * but this will err on the side of giving us more output - * space than we need. */ - if (inRemaining > 0 && - getTotalOutputSize(out, ofst, length) == 0) { - status = SSLEngineResult.Status.BUFFER_OVERFLOW; + /* Get previous DTLS drop count and session ticket count, + * before we process any incomming data. Allows us to set + * BUFFER_UNDERFLOW status (or not if packet decrypt failed + * and was dropped) */ + synchronized (ioLock) { + dtlsPrevDropCount = ssl.getDtlsMacDropCount(); + prevSessionTicketCount = this.sessionTicketCount; } - else { + if (this.handshakeFinished == false) { WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "receiving application data"); - ret = RecvAppData(out, ofst, length); - tmpRet = ret; - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "received application data: " + tmpRet + - " bytes (from RecvAppData)"); - if (ret > 0) { - produced += ret; + () -> "starting or continuing handshake"); + ret = DoHandshake(false); + } + else { + /* If we have input data, make sure output buffer + * length is greater than zero, otherwise ask app to + * expand out buffer. There may be edge cases where + * this could be tightened up, but this will err on + * the side of giving us more output space than we + * need. */ + if (inRemaining > 0 && + getTotalOutputSize(out, ofst, length) == 0) { + status = SSLEngineResult.Status.BUFFER_OVERFLOW; } + else { - /* Check for BUFFER_OVERFLOW status. This can happen - * if either we have data cached internally - * (in.remaining()) and we have no more output space, - * or if we have more decrypted plaintext in the native - * wolfSSL output buffer and ssl.pending() is > 0. */ - synchronized (ioLock) { - if ((ret > 0) && (ssl.pending() > 0)) { - status = - SSLEngineResult.Status.BUFFER_OVERFLOW; + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "receiving application data"); + ret = RecvAppData(out, ofst, length); + tmpRet = ret; + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "received application data: " + tmpRet + + " bytes (from RecvAppData)"); + if (ret > 0) { + produced += ret; } - else if ((ret == 0) && (ssl.pending() > 0) && - getTotalOutputSize(out, ofst, length) == 0) { - status = - SSLEngineResult.Status.BUFFER_OVERFLOW; + + /* Check for BUFFER_OVERFLOW status. This can + * happen if either we have data cached internally + * (in.remaining()) and we have no more output + * space, or if we have more decrypted plaintext + * in the native wolfSSL output buffer and + * ssl.pending() is > 0. */ + synchronized (ioLock) { + if ((ret > 0) && (ssl.pending() > 0)) { + status = + SSLEngineResult.Status.BUFFER_OVERFLOW; + } + else if ((ret == 0) && (ssl.pending() > 0) && + getTotalOutputSize(out, ofst, length) == 0) { + status = + SSLEngineResult.Status.BUFFER_OVERFLOW; + } } - } - synchronized (netDataLock) { - if (ret == 0 && in.remaining() > 0 && - getTotalOutputSize(out, ofst, - length) == 0) { - /* We have more data to read, but no more - * out space left in ByteBuffer[], ask for - * more */ - status = - SSLEngineResult.Status.BUFFER_OVERFLOW; + synchronized (netDataLock) { + if (ret == 0 && in.remaining() > 0 && + getTotalOutputSize(out, ofst, + length) == 0) { + /* We have more data to read, but no more + * out space left in ByteBuffer[], ask for + * more */ + status = + SSLEngineResult.Status.BUFFER_OVERFLOW; + } } } - } - /* If we haven't stored session yet, try again. For TLS 1.3 - * we may need to wait for session ticket. We do try - * right after wolfSSL_connect/accept() finishes, but - * we might not have had session ticket at that time. */ - synchronized (ioLock) { - if (this.handshakeFinished && (ssl.getError(0) == 0) && - !this.sessionStored) { - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "calling engineHelper.saveSession()"); - int ret2 = this.engineHelper.saveSession(); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "return from saveSession(), ret = " + - ret2); - if (ret2 == WolfSSL.SSL_SUCCESS) { - this.sessionStored = true; + /* If we haven't stored session yet, try again. For + * TLS 1.3 we may need to wait for session ticket. We + * do try right after wolfSSL_connect/accept() finishes, + * but we might not have had session ticket at that + * time. */ + synchronized (ioLock) { + if (this.handshakeFinished && + (ssl.getError(0) == 0) && + !this.sessionStored) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "calling engineHelper.saveSession()"); + int ret2 = this.engineHelper.saveSession(); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "return from saveSession(), ret = " + + ret2); + if (ret2 == WolfSSL.SSL_SUCCESS) { + this.sessionStored = true; + } } } + } /* end DoHandshake() / RecvAppData() */ + + if (outBoundOpen == false || this.closeNotifySent) { + /* Mark SSLEngine status as CLOSED */ + status = SSLEngineResult.Status.CLOSED; + /* Handshake has finished and SSLEngine is closed, + * release, global JNI verify callback pointer */ + this.engineHelper.unsetVerifyCallback(); } - } /* end DoHandshake() / RecvAppData() */ - - if (outBoundOpen == false || this.closeNotifySent) { - /* Mark SSLEngine status as CLOSED */ - status = SSLEngineResult.Status.CLOSED; - /* Handshake has finished and SSLEngine is closed, - * release, global JNI verify callback pointer */ - this.engineHelper.unsetVerifyCallback(); - } - synchronized (ioLock) { - err = ssl.getError(ret); - } - if (ret < 0 && (this.ssl.dtls() == 1) && - (err == WolfSSL.OUT_OF_ORDER_E)) { - /* Received out of order DTLS message. Ignore and set our - * status to NEED_UNWRAP again to wait for correct data */ - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "out of order message, dropping and " + - "putting state back to NEED_UNWRAP"); - } - else if (ret < 0 && - (err != WolfSSL.SSL_ERROR_WANT_READ) && - (err != WolfSSL.SSL_ERROR_WANT_WRITE)) { - - if (err == WolfSSL.UNKNOWN_ALPN_PROTOCOL_NAME_E) { - /* Native wolfSSL could not negotiate a common ALPN - * protocol */ - this.inBoundOpen = false; - throw new SSLHandshakeException( - "Unrecognized protocol name error, ret:err = " + - ret + " : " + err); + synchronized (ioLock) { + err = ssl.getError(ret); } - else { - /* Native wolfSSL threw an exception when unwrapping - * data, close inbound since we can't receive more + if (ret < 0 && (this.ssl.dtls() == 1) && + (err == WolfSSL.OUT_OF_ORDER_E)) { + /* Received out of order DTLS message. Ignore and set + * our status to NEED_UNWRAP again to wait for correct * data */ - this.inBoundOpen = false; - if (err == WolfSSL.FATAL_ERROR) { - /* If client side and we received fatal alert, - * close outbound since we won't be receiving - * any more data */ - this.outBoundOpen = false; + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "out of order message, dropping and " + + "putting state back to NEED_UNWRAP"); + } + else if (ret < 0 && + (err != WolfSSL.SSL_ERROR_WANT_READ) && + (err != WolfSSL.SSL_ERROR_WANT_WRITE)) { + + if (err == WolfSSL.UNKNOWN_ALPN_PROTOCOL_NAME_E) { + /* Native wolfSSL could not negotiate a common ALPN + * protocol */ + this.inBoundOpen = false; + throw new SSLHandshakeException( + "Unrecognized protocol name error, ret:err = " + + ret + " : " + err); + } + else { + /* Native wolfSSL threw an exception when unwrapping + * data, close inbound since we can't receive more + * data */ + this.inBoundOpen = false; + if (err == WolfSSL.FATAL_ERROR) { + /* If client side and we received fatal alert, + * close outbound since we won't be receiving + * any more data */ + this.outBoundOpen = false; + } + throw new SSLException( + "wolfSSL error, ret:err = " + ret + " : " + + err); } - throw new SSLException( - "wolfSSL error, ret:err = " + ret + " : " + err); } - } - /* Get DTLS drop count after data has been processed. To be - * used below when setting BUFFER_UNDERFLOW status. */ - synchronized (ioLock) { - dtlsCurrDropCount = ssl.getDtlsMacDropCount(); - } + /* Get DTLS drop count after data has been processed. To be + * used below when setting BUFFER_UNDERFLOW status. */ + synchronized (ioLock) { + dtlsCurrDropCount = ssl.getDtlsMacDropCount(); + } - /* Detect if we need to set BUFFER_UNDERFLOW. - * If we consume data in unwrap() but it's just a session - * ticket, we don't set BUFFER_UNDERFLOW and just continue - * on to set status as OK. */ - synchronized (toSendLock) { - synchronized (netDataLock) { - if (ret <= 0 && err == WolfSSL.SSL_ERROR_WANT_READ && - in.remaining() == 0 && - (this.internalIOSendBufOffset == 0) && - (prevSessionTicketCount == - this.sessionTicketCount)) { - - if ((this.ssl.dtls() == 0) || - (this.handshakeFinished && - (dtlsPrevDropCount == dtlsCurrDropCount))) { - /* Need more data. For DTLS only set - * after handshake has completed, since - * apps expect to switch on NEED_UNWRAP - * HandshakeStatus at that point */ - status = SSLEngineResult.Status.BUFFER_UNDERFLOW; + /* Detect if we need to set BUFFER_UNDERFLOW. + * If we consume data in unwrap() but it's just a session + * ticket, we don't set BUFFER_UNDERFLOW and just continue + * on to set status as OK. */ + synchronized (toSendLock) { + synchronized (netDataLock) { + if (ret <= 0 && + err == WolfSSL.SSL_ERROR_WANT_READ && + in.remaining() == 0 && + (this.internalIOSendBufOffset == 0) && + (prevSessionTicketCount == + this.sessionTicketCount)) { + + if ((this.ssl.dtls() == 0) || + (this.handshakeFinished && + (dtlsPrevDropCount == + dtlsCurrDropCount))) { + /* Need more data. For DTLS only set + * after handshake has completed, since + * apps expect to switch on NEED_UNWRAP + * HandshakeStatus at that point */ + status = + SSLEngineResult.Status.BUFFER_UNDERFLOW; + } } } } } - } - synchronized (netDataLock) { - consumed += in.position() - inPosition; + synchronized (netDataLock) { + consumed += in.position() - inPosition; + } + SetHandshakeStatus(ret); } - SetHandshakeStatus(ret); - } - /* If client side, handshake is done, and we have just received a - * TLS 1.3 session ticket, we should return FINISHED HandshakeStatus - * from unwrap() directly but not from getHandshakeStatus(). Keep track - * of if we have received ticket, so we only set/return this once */ - synchronized (ioLock) { - if (this.getUseClientMode() && this.handshakeFinished && - this.ssl.hasSessionTicket() && - this.sessionTicketReceived == false) { - if (this.ssl.dtls() == 1 && this.internalIOSendBufOffset > 0) { - /* DTLS 1.3 ACK has been produced in response to - * session ticket message, let's set HS status to - * NEED_WRAP so application knows it needs to be sent. */ - hs = SSLEngineResult.HandshakeStatus.NEED_WRAP; - } - else { - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "received session ticket, returning " + - "HandshakeStatus FINISHED"); - hs = SSLEngineResult.HandshakeStatus.FINISHED; + /* If client side, handshake is done, and we have just received a + * TLS 1.3 session ticket, we should return FINISHED HandshakeStatus + * from unwrap() directly but not from getHandshakeStatus(). Keep + * track of if we have received ticket, so we only set/return this + * once */ + synchronized (ioLock) { + if (this.getUseClientMode() && this.handshakeFinished && + this.ssl.hasSessionTicket() && + this.sessionTicketReceived == false) { + if (this.ssl.dtls() == 1 && + this.internalIOSendBufOffset > 0) { + /* DTLS 1.3 ACK has been produced in response to + * session ticket message, let's set HS status to + * NEED_WRAP so application knows it needs to be sent */ + hs = SSLEngineResult.HandshakeStatus.NEED_WRAP; + } + else { + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "received session ticket, returning " + + "HandshakeStatus FINISHED"); + hs = SSLEngineResult.HandshakeStatus.FINISHED; + } + this.sessionTicketReceived = true; } - this.sessionTicketReceived = true; } - } - if (extraDebugEnabled == true) { - final Status tmpStatus = status; - final int tmpConsumed = consumed; - final int tmpProduced = produced; - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "==== [ exiting unwrap() ] ============================"); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "setUseClientMode: " + - this.engineHelper.getUseClientMode()); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "in.remaining(): " + in.remaining()); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "in.position(): " + in.position()); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "in.limit(): " + in.limit()); - for (i = 0; i < length; i++) { - final int idx = i; + if (extraDebugEnabled == true) { + final Status tmpStatus = status; + final int tmpConsumed = consumed; + final int tmpProduced = produced; WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "out["+idx+"].remaining(): " + - out[idx].remaining()); + () -> "==== [ exiting unwrap() ] " + + "============================"); WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "out["+idx+"].position(): " + - out[idx].position()); + () -> "setUseClientMode: " + + this.engineHelper.getUseClientMode()); WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "out["+idx+"].limit(): " + - out[idx].limit()); + () -> "in.remaining(): " + in.remaining()); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "in.position(): " + in.position()); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "in.limit(): " + in.limit()); + for (i = 0; i < length; i++) { + final int idx = i; + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "out["+idx+"].remaining(): " + + out[idx].remaining()); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "out["+idx+"].position(): " + + out[idx].position()); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "out["+idx+"].limit(): " + + out[idx].limit()); + } + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "ofst: " + ofst + ", length: " + length); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "internalIOSendBufOffset: " + + this.internalIOSendBufOffset); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "handshakeFinished: " + this.handshakeFinished); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "closeNotifySent: " + this.closeNotifySent); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "closeNotifyReceived: " + this.closeNotifyReceived); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "inBoundOpen: " + this.inBoundOpen); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "outBoundOpen: " + this.outBoundOpen); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "handshakeStatus: " + hs); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "status: " + tmpStatus); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "consumed: " + tmpConsumed); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "produced: " + tmpProduced); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "nativeHandshakeState: " + + this.ssl.getStateStringLong()); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "nativeWantsToRead: " + this.nativeWantsToRead); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "nativeWantsToWrite: " + this.nativeWantsToWrite); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "=================================================="); } - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "ofst: " + ofst + ", length: " + length); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "internalIOSendBufOffset: " + - this.internalIOSendBufOffset); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "handshakeFinished: " + this.handshakeFinished); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "closeNotifySent: " + this.closeNotifySent); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "closeNotifyReceived: " + this.closeNotifyReceived); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "inBoundOpen: " + this.inBoundOpen); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "outBoundOpen: " + this.outBoundOpen); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "handshakeStatus: " + hs); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "status: " + tmpStatus); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "consumed: " + tmpConsumed); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "produced: " + tmpProduced); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "nativeHandshakeState: " + this.ssl.getStateStringLong()); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "nativeWantsToRead: " + this.nativeWantsToRead); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "nativeWantsToWrite: " + this.nativeWantsToWrite); - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - () -> "====================================================="); - } - try { - /* Don't hold references to this SSLEngine object in WolfSSLSession, - * can prevent proper garbage collection */ - unsetSSLCallbacks(); + return new SSLEngineResult(status, hs, consumed, produced); - } catch (WolfSSLJNIException e) { - throw new SSLException(e); + } finally { + try { + /* Don't hold references to this SSLEngine object in + * WolfSSLSession, can prevent proper garbage collection */ + unsetSSLCallbacks(); + } catch (WolfSSLJNIException e) { + /* Don't throw from finally block, just log for info */ + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "unsetSSLCallbacks() exception during unwrap(), " + + "just logging: " + e.getMessage()); + } } - - return new SSLEngineResult(status, hs, consumed, produced); } /** @@ -1844,11 +1871,11 @@ else if (!this.needInit && !this.handshakeFinished) { throw new SSLException(e); } - /* will throw SSLHandshakeException if session creation is - not allowed */ - checkAndInitSSLEngine(); - try { + /* will throw SSLHandshakeException if session creation is + not allowed */ + checkAndInitSSLEngine(); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, () -> "calling engineHelper.doHandshake()"); @@ -1870,14 +1897,15 @@ else if (!this.needInit && !this.handshakeFinished) { throw new SSLException(e); } finally { - try { /* Don't hold references to this SSLEngine object in * WolfSSLSession, can prevent proper garbage collection */ unsetSSLCallbacks(); - - } catch (WolfSSLJNIException e) { - throw new SSLException(e); + } catch (Exception e) { + /* Don't throw from finally block, just log for info */ + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "unsetSSLCallbacks() exception during " + + "beginHandshake(), just logging: " + e.getMessage()); } } } diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLInternalVerifyCb.java b/src/java/com/wolfssl/provider/jsse/WolfSSLInternalVerifyCb.java index 43b7a34b..be5b1768 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLInternalVerifyCb.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLInternalVerifyCb.java @@ -34,6 +34,7 @@ import javax.net.ssl.X509TrustManager; import javax.net.ssl.X509ExtendedTrustManager; import java.io.IOException; +import java.lang.ref.WeakReference; /** * Internal verify callback. @@ -47,10 +48,14 @@ public class WolfSSLInternalVerifyCb implements WolfSSLVerifyCallback { private X509TrustManager tm = null; private boolean clientMode; - private SSLSocket callingSocket = null; - private SSLEngine callingEngine = null; private WolfSSLParameters params = null; + /* Use WeakReference for SSLSocket and SSLEngine to avoid + * holding back garbage collection of WolfSSLSocket/WolfSSLEngine + * objects */ + private WeakReference callingSocket = null; + private WeakReference callingEngine = null; + /** * Create new WolfSSLInternalVerifyCb * @@ -64,9 +69,19 @@ public WolfSSLInternalVerifyCb(X509TrustManager xtm, boolean client, SSLSocket socket, SSLEngine engine, WolfSSLParameters params) { this.tm = xtm; this.clientMode = client; - this.callingSocket = socket; - this.callingEngine = engine; this.params = params; + + if (socket != null) { + this.callingSocket = new WeakReference<>(socket); + } else { + this.callingSocket = null; + } + + if (engine != null) { + this.callingEngine = new WeakReference<>(engine); + } else { + this.callingEngine = null; + } } /** @@ -99,19 +114,25 @@ private int verifyHostnameOnly(X509Certificate peer) { WolfSSLTrustX509 wolfTM = (WolfSSLTrustX509)tm; try { - if (this.callingSocket != null) { + if (this.callingSocket != null && + this.callingSocket.get() != null) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, () -> "checking hostname verification using SSLSocket"); + /* Throws CertificateException when verify fails */ - wolfTM.verifyHostname(peer, this.callingSocket, + wolfTM.verifyHostname(peer, this.callingSocket.get(), null, clientMode); } - else if (this.callingEngine != null) { + else if (this.callingEngine != null && + this.callingEngine.get() != null) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, () -> "checking hostname verification using SSLEngine"); + /* Throws CertificateException when verify fails */ wolfTM.verifyHostname(peer, null, - this.callingEngine, clientMode); + this.callingEngine.get(), clientMode); } else { throw new CertificateException( @@ -159,19 +180,26 @@ private boolean VerifyCertChainWithTrustManager(X509Certificate[] certs, if (this.tm instanceof X509ExtendedTrustManager) { X509ExtendedTrustManager xtm = (X509ExtendedTrustManager)this.tm; - if (this.callingSocket != null) { + + if (this.callingSocket != null && + this.callingSocket.get() != null) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, () -> "Calling TrustManager.checkServerTrusted(" + "SSLSocket)"); + xtm.checkServerTrusted(certs, authType, - this.callingSocket); + this.callingSocket.get()); } - else if (this.callingEngine != null) { + else if (this.callingEngine != null && + this.callingEngine.get() != null) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, () -> "Calling TrustManager.checkServerTrusted(" + "SSLEngine)"); + xtm.checkServerTrusted(certs, authType, - this.callingEngine); + this.callingEngine.get()); } else { /* If we do have access to X509ExtendedTrustManager, @@ -194,19 +222,26 @@ else if (this.callingEngine != null) { if (this.tm instanceof X509ExtendedTrustManager) { X509ExtendedTrustManager xtm = (X509ExtendedTrustManager)this.tm; - if (this.callingSocket != null) { + + if (this.callingSocket != null && + this.callingSocket.get() != null) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, () -> "Calling TrustManager.checkClientTrusted(" + "SSLSocket)"); + xtm.checkClientTrusted(certs, authType, - this.callingSocket); + this.callingSocket.get()); } - else if (this.callingEngine != null) { + else if (this.callingEngine != null && + this.callingEngine.get() != null) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, () -> "Calling TrustManager.checkClientTrusted(" + "SSLEngine)"); + xtm.checkClientTrusted(certs, authType, - this.callingEngine); + this.callingEngine.get()); } else { /* If we do have access to X509ExtendedTrustManager, diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLTrustX509.java b/src/java/com/wolfssl/provider/jsse/WolfSSLTrustX509.java index fb455833..ecc911af 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLTrustX509.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLTrustX509.java @@ -94,6 +94,7 @@ private X509Certificate[] sortCertChainBySubjectIssuer( X509Certificate[] certs) throws CertificateException { int i, curr, next; + int leafIdx = -1; boolean nextFound = false; final X509Certificate[] chain; X509Certificate[] retChain = null; @@ -120,7 +121,38 @@ private X509Certificate[] sortCertChainBySubjectIssuer( chain[tmpI].getSubjectX500Principal().getName()); } - /* Assume peer/leaf cert is first in array */ + /* Find the leaf certificate using BasicConstraints extension. + * Per RFC 5280, leaf/end-entity certs have getBasicConstraints() + * return -1, while CA certs return >= 0. */ + for (i = 0; i < chain.length; i++) { + if (chain[i].getBasicConstraints() == -1) { + leafIdx = i; + final int tmpLeaf = i; + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "Identified leaf cert at index " + tmpLeaf + + " (BasicConstraints CA=false)"); + break; + } + } + + /* If we couldn't identify leaf cert by BasicConstraints, default + * to treat the first cert as peer */ + if (leafIdx == -1) { + final int tmpLeaf = 0; + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "Could not identify leaf cert by BasicConstraints, " + + "assuming index " + tmpLeaf); + leafIdx = 0; + } + + /* Move leaf cert to position 0 if not already there */ + if (leafIdx != 0) { + X509Certificate tmp = chain[0]; + chain[0] = chain[leafIdx]; + chain[leafIdx] = tmp; + } + + /* Now build chain from leaf to root */ for (curr = 0; curr < chain.length; curr++) { nextFound = false; for (next = curr + 1; next < chain.length; next++) { diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLX509.java b/src/java/com/wolfssl/provider/jsse/WolfSSLX509.java index ec329cb4..5c79b0f9 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLX509.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLX509.java @@ -40,6 +40,7 @@ import java.security.cert.CertificateParsingException; import java.security.cert.X509Certificate; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.Set; import java.util.TreeSet; @@ -319,6 +320,34 @@ public boolean[] getKeyUsage() { return this.cert.getKeyUsage(); } + @Override + public List getExtendedKeyUsage() + throws CertificateParsingException { + + String[] ekuArray; + + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + () -> "entered getExtendedKeyUsage()"); + + if (this.cert == null) { + return null; + } + + ekuArray = this.cert.getExtendedKeyUsage(); + if (ekuArray == null) { + return null; + } + + /* Convert String[] to List as required by + * X509Certificate.getExtendedKeyUsage() API */ + List ekuList = new ArrayList(); + for (String oid : ekuArray) { + ekuList.add(oid); + } + + return Collections.unmodifiableList(ekuList); + } + @Override public int getBasicConstraints() { diff --git a/src/test/com/wolfssl/provider/jsse/test/WolfSSLEngineMemoryLeakTest.java b/src/test/com/wolfssl/provider/jsse/test/WolfSSLEngineMemoryLeakTest.java new file mode 100644 index 00000000..9c92cb64 --- /dev/null +++ b/src/test/com/wolfssl/provider/jsse/test/WolfSSLEngineMemoryLeakTest.java @@ -0,0 +1,265 @@ +/* WolfSSLEngineMemoryLeakTest.java + * + * Copyright (C) 2006-2024 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +package com.wolfssl.provider.jsse.test; + +import org.junit.Test; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.rules.Timeout; +import static org.junit.Assert.*; + +import java.util.concurrent.TimeUnit; +import java.nio.ByteBuffer; +import java.security.Security; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLException; + +import com.wolfssl.provider.jsse.WolfSSLProvider; + +/** + * Memory leak regression test for WolfSSLEngine. + * + * Tests that JNI global references are properly cleaned up when SSLEngine + * instances are abandoned (e.g., due to exceptions during handshake). + * + * This test verifies fixes for memory leak caused by: + * - I/O callback references (setIOWriteCtx, setIOReadCtx) + * - Session ticket callback references (setSessionTicketCb) + * - Verify callback references (WolfSSLInternalVerifyCb.callingEngine) + */ +public class WolfSSLEngineMemoryLeakTest { + + /** + * Global timeout for all tests in this class. + */ + @Rule + public Timeout globalTimeout = new Timeout(60, TimeUnit.SECONDS); + + @BeforeClass + public static void setupProvider() { + + System.out.println("WolfSSLEngineMemoryLeakTest"); + + Security.addProvider(new WolfSSLProvider()); + } + + private void pass(String msg) { + WolfSSLTestFactory.pass(msg); + } + + private void error(String msg) { + WolfSSLTestFactory.fail(msg); + } + + /** + * Test that abandoned SSLEngine instances can be garbage collected. + * + * This test creates many SSLEngine instances that fail during handshake + * (simulating real-world scenarios where connections are dropped), then + * verifies that memory is properly released after garbage collection. + * + * The test will fail if JNI global references prevent garbage collection. + */ + @Test + public void testEngineMemoryLeakWithAbandonedEngines() throws Exception { + + /* Number of engines to create. Use a smaller number for unit tests + * to keep test time reasonable (few seconds). */ + final int numEngines = 500; + + /* Threshold for acceptable memory growth (in MB). + * I/O and session ticket callbacks are immediately released. Verify + * callbacks may be retained until finalization. + * JDK 21+ has larger object overhead than earlier JDKs. + * Acceptable: ~20-65 MB for 500 engines depending on JDK version. + * Before fixes, growth would be ~230+ MB for 500 engines. + * We use a conservative threshold that detects major leaks while + * accounting for JVM differences. */ + final double maxAcceptableGrowthMB = 80.0; + + String javaVersion = System.getProperty("java.version"); + System.out.print("\tmem leak test with " + numEngines + + " engines (JDK " + javaVersion + ")"); + + /* Measure baseline memory - use aggressive GC */ + for (int i = 0; i < 3; i++) { + System.gc(); + System.runFinalization(); + } + Thread.sleep(200); + long baselineMemory = getUsedMemoryBytes(); + + /* Create and abandon many SSLEngine instances */ + for (int i = 0; i < numEngines; i++) { + createAndAbandonSSLEngine(); + } + + /* Force aggressive garbage collection and finalization. + * Multiple rounds help ensure finalizers run for abandoned engines. + * This may be important for JDK 21+ which seems to have different + * GC timing characteristics. */ + for (int i = 0; i < 5; i++) { + System.gc(); + System.runFinalization(); + } + Thread.sleep(300); + + /* Measure final memory */ + long finalMemory = getUsedMemoryBytes(); + long memoryGrowthBytes = finalMemory - baselineMemory; + double memoryGrowthMB = memoryGrowthBytes / (1024.0 * 1024.0); + + /* Verify memory growth is within acceptable limits */ + String message = String.format( + "Memory leak detected: created %d engines, " + + "memory grew by %.2f MB (max acceptable: %.2f MB). " + + "JNI global references may not be properly cleaned up.", + numEngines, memoryGrowthMB, maxAcceptableGrowthMB); + + if (memoryGrowthMB > maxAcceptableGrowthMB) { + error("\t... failed"); + fail(message); + } + + pass("\t... passed"); + } + + /** + * Test that SSLEngine instances that complete handshake successfully + * and are properly closed do not leak memory. + */ + @Test + public void testEngineMemoryLeakWithProperClose() throws Exception { + + final int numEngines = 100; + final double maxAcceptableGrowthMB = 10.0; + + System.gc(); + System.gc(); + Thread.sleep(100); + long baselineMemory = getUsedMemoryBytes(); + + System.out.print("\tmem leak test closed engines"); + + /* Create engines and explicitly close them */ + for (int i = 0; i < numEngines; i++) { + SSLContext sslContext = + SSLContext.getInstance("TLS", "wolfJSSE"); + sslContext.init(null, null, null); + + SSLEngine engine = + sslContext.createSSLEngine("example.com", 443); + engine.setUseClientMode(true); + + /* Explicitly close the engine */ + engine.closeOutbound(); + + /* For inbound, we need to handle the exception if not connected */ + try { + engine.closeInbound(); + } catch (SSLException e) { + /* Expected - not a real connection */ + } + } + + System.gc(); + System.gc(); + Thread.sleep(100); + + long finalMemory = getUsedMemoryBytes(); + long memoryGrowthBytes = finalMemory - baselineMemory; + double memoryGrowthMB = memoryGrowthBytes / (1024.0 * 1024.0); + + String message = String.format( + "Memory leak detected with proper close: created %d engines, " + + "memory grew by %.2f MB (max acceptable: %.2f MB).", + numEngines, memoryGrowthMB, maxAcceptableGrowthMB); + + if (memoryGrowthMB > maxAcceptableGrowthMB) { + error("\t... failed"); + fail(message); + } + + pass("\t... passed"); + } + + /** + * Creates an SSLEngine, initializes it (which creates JNI global + * references), attempts a wrap operation that will fail, then abandons + * the engine without proper cleanup. + * + * This simulates real-world scenarios where: + * - Connections are dropped mid-handshake + * - Exceptions occur during handshake + * - Applications don't properly close engines + */ + private void createAndAbandonSSLEngine() throws Exception { + + /* Create SSLContext using WolfSSL provider */ + SSLContext sslContext = SSLContext.getInstance("TLS", "wolfJSSE"); + + /* Initialize with null (will use default trust/key managers) */ + sslContext.init(null, null, null); + + /* Create SSLEngine in client mode */ + SSLEngine engine = sslContext.createSSLEngine("wolfssl.com", 443); + engine.setUseClientMode(true); + + /* Begin handshake - this triggers initialization of callbacks + * and creates JNI global references */ + engine.beginHandshake(); + + /* Allocate buffers for handshake */ + ByteBuffer netBuffer = ByteBuffer.allocate( + engine.getSession().getPacketBufferSize()); + ByteBuffer appBuffer = ByteBuffer.allocate( + engine.getSession().getApplicationBufferSize()); + + try { + /* Attempt a wrap operation - this will create initial ClientHello + * and set up all the JNI global references. The operation will + * fail because there's no peer to connect to. */ + engine.wrap(appBuffer, netBuffer); + + } catch (SSLException e) { + /* Expected - we don't have a real peer to connect to */ + } + + /* IMPORTANT: We intentionally do NOT call: + * - engine.closeOutbound() + * - engine.closeInbound() + * - Any final wrap()/unwrap() operations + * + * This simulates scenarios where connections are abruptly dropped + * or applications don't properly close engines. Without the fix, + * JNI global references will keep the engine in memory forever. */ + } + + /** + * Gets the current used memory in bytes. + */ + private long getUsedMemoryBytes() { + Runtime runtime = Runtime.getRuntime(); + return runtime.totalMemory() - runtime.freeMemory(); + } +} diff --git a/src/test/com/wolfssl/provider/jsse/test/WolfSSLJSSETestSuite.java b/src/test/com/wolfssl/provider/jsse/test/WolfSSLJSSETestSuite.java index 471509f2..4a341157 100644 --- a/src/test/com/wolfssl/provider/jsse/test/WolfSSLJSSETestSuite.java +++ b/src/test/com/wolfssl/provider/jsse/test/WolfSSLJSSETestSuite.java @@ -29,6 +29,7 @@ WolfSSLTrustX509Test.class, WolfSSLContextTest.class, WolfSSLEngineTest.class, + WolfSSLEngineMemoryLeakTest.class, WolfSSLSocketFactoryTest.class, WolfSSLSocketTest.class, WolfSSLServerSocketFactoryTest.class, diff --git a/src/test/com/wolfssl/provider/jsse/test/WolfSSLSocketTest.java b/src/test/com/wolfssl/provider/jsse/test/WolfSSLSocketTest.java index c570d29b..623f2f01 100644 --- a/src/test/com/wolfssl/provider/jsse/test/WolfSSLSocketTest.java +++ b/src/test/com/wolfssl/provider/jsse/test/WolfSSLSocketTest.java @@ -58,6 +58,10 @@ import javax.net.ssl.SSLParameters; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509ExtendedTrustManager; +import javax.net.ssl.X509TrustManager; +import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.HandshakeCompletedListener; @@ -81,7 +85,13 @@ import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.PrivateKey; +import java.security.KeyFactory; +import java.security.spec.PKCS8EncodedKeySpec; import java.lang.reflect.Field; +import java.io.BufferedInputStream; +import java.util.Base64; import com.wolfssl.provider.jsse.WolfSSLProvider; import com.wolfssl.provider.jsse.WolfSSLSocketFactory; @@ -179,7 +189,6 @@ public static void testSetupSocketFactory() throws NoSuchProviderException, try { tf = new WolfSSLTestFactory(); } catch (WolfSSLException e) { - /* TODO Auto-generated catch block */ e.printStackTrace(); } @@ -1127,8 +1136,9 @@ public void testExtendedThreadingUse() System.out.print("\tTesting ExtendedThreadingUse"); - /* This test hangs on Android, marking TODO for later investigation. Seems to be - * something specific to the test code, not library proper. */ + /* This test hangs on Android, marking TODO for later investigation. + * Seems to be something specific to the test code, not library + * proper. */ if (WolfSSLTestFactory.isAndroid()) { System.out.println("\t... skipped"); return; @@ -1137,7 +1147,8 @@ public void testExtendedThreadingUse() /* Start up simple TLS test server */ CountDownLatch serverOpenLatch = new CountDownLatch(1); InternalMultiThreadedSSLSocketServer server = - new InternalMultiThreadedSSLSocketServer(svrPort, serverOpenLatch, numThreads); + new InternalMultiThreadedSSLSocketServer(svrPort, serverOpenLatch, + numThreads); server.start(); /* Wait for server thread to start up before connecting clients */ @@ -3496,15 +3507,29 @@ public void testSNIMatchers() throws Exception { System.out.print("\tTesting SNI Matchers"); + /* SNI matcher functionality requires wolfSSL 5.7.2 or later. + * Older versions have a limitation where wolfSSL_SNI_GetRequest() + * only returns SNI data if native wolfSSL already matched it, but + * wolfJSSE relies on retrieving the SNI to do matching at the Java + * level. This was fixed in wolfSSL 5.7.2 by adding an ignoreStatus + * parameter to TLSX_SNI_GetRequest(). */ + long libVerHex = WolfSSL.getLibVersionHex(); + if (libVerHex < 0x05007002L) { + System.out.println("\t\t... skipped"); + return; + } + /* create new CTX */ this.ctx = tf.createSSLContext("TLS", ctxProvider); /* create SSLServerSocket first to get ephemeral port */ - final SSLServerSocket ss = (SSLServerSocket)ctx.getServerSocketFactory() - .createServerSocket(0); + final SSLServerSocket ss = + (SSLServerSocket)ctx.getServerSocketFactory() + .createServerSocket(0); /* Configure SNI matcher for server*/ - SNIMatcher matcher = SNIHostName.createSNIMatcher("www\\.example\\.com"); + SNIMatcher matcher = + SNIHostName.createSNIMatcher("www\\.example\\.com"); Collection matchers = new ArrayList<>(); matchers.add(matcher); SSLParameters sp = ss.getSSLParameters(); @@ -3592,7 +3617,8 @@ public Void call() throws Exception { System.out.println("\t\t... passed"); } catch (Exception e) { System.out.println("\t\t... failed"); - fail(); + e.printStackTrace(); + fail("SNI Matcher test failed: " + e.getMessage()); } finally { ss.close(); } @@ -4013,5 +4039,246 @@ public void testCloseWithNullEngineHelper() System.out.println("\t... passed"); } + + /** + * This test verifies that WolfSSLX509StoreCtx.getCerts() returns + * certificates in the correct peer to root order during a TLS + * handshake. Register a custom X509TrustManager (not WolfSSLTrustX509) + * so that the internal sorting logic in WolfSSLTrustX509 doesn't run. + * This exposes the raw certificate order coming from the JNI layer. + */ + @Test + public void testCertificateChainOrderingFromStoreCtx() + throws Exception { + + System.out.print("\tProper chain ordering"); + + /* Custom TrustManager that checks certificate order */ + final boolean[] wasCalled = {false}; + final boolean[] orderCorrect = {false}; + final String[] errorMsg = {null}; + final int[] chainLen = {0}; + + String serverIntCert = + "examples/certs/intermediate/server-int-ecc-cert.pem"; + String serverIntKey = "examples/certs/ecc-key.pem"; + String intCaCert = "examples/certs/intermediate/ca-int-ecc-cert.pem"; + String int2CaCert = + "examples/certs/intermediate/ca-int2-ecc-cert.pem"; + + X509TrustManager customTM = new X509TrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] chain, + String authType) throws CertificateException { + /* Not used in this test */ + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, + String authType) throws CertificateException { + wasCalled[0] = true; + + if (chain == null || chain.length == 0) { + errorMsg[0] = "Certificate chain is null or empty"; + throw new CertificateException(errorMsg[0]); + } + + chainLen[0] = chain.length; + + /* Per RFC 5280, leaf/end-entity certs have + * getBasicConstraints() return -1, while CA certs return + * >= 0. The first certificate in the chain MUST be the + * peer/leaf certificate. */ + int firstCertBC = chain[0].getBasicConstraints(); + + if (firstCertBC == -1) { + /* First cert is leaf/peer cert, order is correct */ + orderCorrect[0] = true; + + } else { + /* First cert is a CA cert, order is WRONG */ + orderCorrect[0] = false; + errorMsg[0] = "Certificate chain order is incorrect: " + + "first cert is CA (BasicConstraints=" + firstCertBC + + "), expected leaf/peer cert (BasicConstraints=-1). " + + "Chain length: " + chain.length + ". " + + "First cert subject: " + + chain[0].getSubjectX500Principal().getName(); + /* NOTE: We don't throw here so test can verify the flag, + * but in production a wrapper TrustManager would fail */ + } + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + }; + + /* Build server KeyStore with intermediate chain from pem files */ + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + + FileInputStream fis = new FileInputStream(serverIntCert); + BufferedInputStream bis = new BufferedInputStream(fis); + X509Certificate serverCert = + (X509Certificate)cf.generateCertificate(bis); + bis.close(); + fis.close(); + + fis = new FileInputStream(intCaCert); + bis = new BufferedInputStream(fis); + X509Certificate intCert = (X509Certificate)cf.generateCertificate(bis); + bis.close(); + fis.close(); + + fis = new FileInputStream(int2CaCert); + bis = new BufferedInputStream(fis); + X509Certificate int2Cert = (X509Certificate)cf.generateCertificate(bis); + bis.close(); + fis.close(); + + /* Create KeyStore and add server cert with chain */ + KeyStore serverKeyStore = KeyStore.getInstance("JKS"); + serverKeyStore.load(null, null); + + /* Build certificate chain: server, int2 (immediate issuer), int */ + Certificate[] certChain = new Certificate[3]; + certChain[0] = serverCert; + certChain[1] = int2Cert; + certChain[2] = intCert; + + /* Load existing ECC private key from server-ecc.jks, since Java + * doesn't natively support SEC1 ECC format without Bouncy Castle */ + KeyStore tmpKS = KeyStore.getInstance("JKS"); + fis = new FileInputStream("examples/provider/server-ecc.jks"); + tmpKS.load(fis, "wolfSSL test".toCharArray()); + fis.close(); + + java.security.PrivateKey privateKey = + (java.security.PrivateKey)tmpKS.getKey("server-ecc", + "wolfSSL test".toCharArray()); + + /* Add private key with intermediate certificate chain to keystore */ + serverKeyStore.setKeyEntry("server-int-ecc", privateKey, + "wolfSSL test".toCharArray(), certChain); + + /* Set up server with intermediate certificate chain */ + ExecutorService executor = Executors.newSingleThreadExecutor(); + SSLServerSocket serverSocket = null; + + try { + KeyManagerFactory serverKM = + KeyManagerFactory.getInstance("SunX509"); + serverKM.init(serverKeyStore, "wolfSSL test".toCharArray()); + + /* Server uses default wolfSSL TrustManager */ + TrustManagerFactory serverTM = + TrustManagerFactory.getInstance("SunX509"); + serverTM.init(serverKeyStore); + + SSLContext serverCtx = + SSLContext.getInstance("TLSv1.2", "wolfJSSE"); + serverCtx.init(serverKM.getKeyManagers(), + serverTM.getTrustManagers(), null); + + serverSocket = + (SSLServerSocket)serverCtx.getServerSocketFactory() + .createServerSocket(0); + final int serverPort = serverSocket.getLocalPort(); + final SSLServerSocket finalServerSocket = serverSocket; + + /* Start server thread */ + Future serverFuture = + executor.submit(new Callable() { + @Override + public Void call() throws Exception { + SSLSocket serverSock = null; + try { + serverSock = + (SSLSocket)finalServerSocket.accept(); + InputStream in = serverSock.getInputStream(); + OutputStream out = serverSock.getOutputStream(); + + /* Simple echo */ + byte[] buf = new byte[1024]; + int read = in.read(buf); + if (read > 0) { + out.write(buf, 0, read); + } + } finally { + if (serverSock != null) { + serverSock.close(); + } + } + return null; + } + }); + + /* Set up client with CUSTOM TrustManager (not WolfSSLTrustX509) */ + KeyStore clientKeyStore = KeyStore.getInstance(tf.keyStoreType); + InputStream stream = new FileInputStream(tf.clientJKS); + clientKeyStore.load(stream, jksPass); + stream.close(); + + KeyManagerFactory clientKM = + KeyManagerFactory.getInstance("SunX509"); + clientKM.init(clientKeyStore, jksPass); + + /* Client uses our CUSTOM TrustManager */ + SSLContext clientCtx = + SSLContext.getInstance("TLSv1.2", "wolfJSSE"); + clientCtx.init(clientKM.getKeyManagers(), + new TrustManager[] { customTM }, null); + + /* Connect client */ + SSLSocket clientSocket = null; + try { + clientSocket = (SSLSocket)clientCtx.getSocketFactory() + .createSocket("localhost", serverPort); + + /* Force handshake - this will call our custom TrustManager */ + clientSocket.startHandshake(); + + /* Send test data */ + OutputStream out = clientSocket.getOutputStream(); + out.write("test".getBytes()); + out.flush(); + + /* Read response */ + InputStream in = clientSocket.getInputStream(); + byte[] buf = new byte[1024]; + in.read(buf); + + } finally { + if (clientSocket != null) { + clientSocket.close(); + } + } + + /* Wait for server to finish */ + serverFuture.get(10, TimeUnit.SECONDS); + + } finally { + if (serverSocket != null) { + serverSocket.close(); + } + executor.shutdown(); + } + + /* Verify TrustManager was called */ + assertTrue("Custom TrustManager.checkServerTrusted() was not called", + wasCalled[0]); + + /* Verify we got a chain with multiple certs */ + assertTrue("Expected chain length > 1, got: " + chainLen[0], + chainLen[0] > 1); + + assertTrue("Certificate chain order is incorrect: " + + (errorMsg[0] != null ? errorMsg[0] : + "first cert was not peer/leaf cert"), + orderCorrect[0]); + + System.out.println("\t... passed"); + } } diff --git a/src/test/com/wolfssl/provider/jsse/test/WolfSSLTrustX509Test.java b/src/test/com/wolfssl/provider/jsse/test/WolfSSLTrustX509Test.java index e88923c8..17506731 100644 --- a/src/test/com/wolfssl/provider/jsse/test/WolfSSLTrustX509Test.java +++ b/src/test/com/wolfssl/provider/jsse/test/WolfSSLTrustX509Test.java @@ -45,6 +45,7 @@ import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.CertificateException; +import java.security.cert.CertificateParsingException; import java.security.cert.X509Certificate; import java.net.Socket; import java.net.InetSocketAddress; @@ -1145,6 +1146,7 @@ public void testCheckServerTrustedWithChainWrongOrder() pass("\t... passed"); } + @Test public void testCheckServerTrustedWithChainReturnsChain() throws NoSuchProviderException, NoSuchAlgorithmException, @@ -2487,6 +2489,117 @@ public void testX509ExtendedTrustManagerExternal() pass("\t... passed"); } + @Test + public void testExtendedKeyUsageWithLeafNotFirst() throws Exception { + + System.out.print("\tEKU validation leaf not first"); + + /* This test reproduces the case where a certificate chain arrives + * with CA before leaf cert and helps verify the peer chain sorting + * logic. It exercises a common code path to validate the Extended + * Key Usage as a sanity check. */ + + String eccServerCert = + "examples/certs/intermediate/server-int-ecc-cert.pem"; + String eccInt2CaCert = + "examples/certs/intermediate/ca-int2-ecc-cert.pem"; + String eccIntCaCert = + "examples/certs/intermediate/ca-int-ecc-cert.pem"; + + FileInputStream fis; + BufferedInputStream bis; + X509Certificate[] certArray = new X509Certificate[3]; + + if (WolfSSLTestFactory.isAndroid()) { + eccServerCert = "/sdcard/" + eccServerCert; + eccInt2CaCert = "/sdcard/" + eccInt2CaCert; + eccIntCaCert = "/sdcard/" + eccIntCaCert; + } + + /* Load certificates in REVERSE order (CA first, server last) + * to simulate the bug scenario */ + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + + /* certArray[0]: Intermediate2 CA - has CA:TRUE, no serverAuth EKU */ + fis = new FileInputStream(eccInt2CaCert); + bis = new java.io.BufferedInputStream(fis); + certArray[0] = (X509Certificate)cf.generateCertificate(bis); + bis.close(); + fis.close(); + + /* certArray[1]: Intermediate CA - has CA:TRUE, no serverAuth EKU */ + fis = new FileInputStream(eccIntCaCert); + bis = new java.io.BufferedInputStream(fis); + certArray[1] = (X509Certificate)cf.generateCertificate(bis); + bis.close(); + fis.close(); + + /* certArray[2]: Server cert - has CA:FALSE, HAS serverAuth EKU */ + fis = new FileInputStream(eccServerCert); + bis = new java.io.BufferedInputStream(fis); + certArray[2] = (X509Certificate)cf.generateCertificate(bis); + bis.close(); + fis.close(); + + /* Create wolfSSL TrustManager for verification. */ + TrustManager[] baseTm = tf.createTrustManager("SunX509", + tf.caJKS, provider); + WolfSSLTrustX509 wolfTm = (WolfSSLTrustX509) baseTm[0]; + + /* Call checkServerTrusted() with chain-returning overload. + * The sorting logic code should: + * 1. Identify the leaf using BasicConstraints (index 2) + * 2. Move server cert to index 0 + * 3. Return chain with server cert first */ + List sortedChain = null; + try { + sortedChain = wolfTm.checkServerTrusted( + certArray, "EC", (String)null); + + } catch (CertificateException e) { + e.printStackTrace(); + fail("checkServerTrusted() threw exception: " + e.getMessage()); + } + + /* Validate Extended Key Usage on the peer cert. + * The peer cert should be first in the sorted chain. */ + if (sortedChain == null || sortedChain.size() == 0) { + fail("Sorted chain is null or empty"); + } + + X509Certificate peerCert = sortedChain.get(0); + List ekuOids = null; + + try { + ekuOids = peerCert.getExtendedKeyUsage(); + + } catch (java.security.cert.CertificateParsingException e) { + e.printStackTrace(); + fail("Failed to parse Extended Key Usage: " + e.getMessage()); + } + + /* Check for serverAuth EKU (1.3.6.1.5.5.7.3.1) */ + boolean hasServerAuth = false; + if (ekuOids != null) { + for (String oid : ekuOids) { + if (oid.equals("1.3.6.1.5.5.7.3.1")) { + hasServerAuth = true; + break; + } + } + } + + if (!hasServerAuth) { + fail("EKU validation failed. The certificate chain was not " + + "properly sorted, and a CA cert (without serverAuth EKU) " + + "was treated as the peer cert instead of the server cert. " + + "Peer cert: " + peerCert.getSubjectX500Principal() + + ", EKU list: " + ekuOids); + } + + System.out.println("\t... passed"); + } + /* TrustManager that trusts all certificates */ TrustManager[] trustAllCerts = { new X509ExtendedTrustManager() { diff --git a/src/test/com/wolfssl/provider/jsse/test/WolfSSLX509Test.java b/src/test/com/wolfssl/provider/jsse/test/WolfSSLX509Test.java index 74fa97e3..ed42149e 100644 --- a/src/test/com/wolfssl/provider/jsse/test/WolfSSLX509Test.java +++ b/src/test/com/wolfssl/provider/jsse/test/WolfSSLX509Test.java @@ -94,7 +94,6 @@ public static void testProviderInstallationAtRuntime() try { tf = new WolfSSLTestFactory(); } catch (WolfSSLException e) { - // TODO Auto-generated catch block e.printStackTrace(); } } @@ -804,6 +803,70 @@ public void testWolfSSLPrincipalInterfaceImplementation() { pass("\t... passed"); } + @Test + public void testGetExtendedKeyUsage() { + X509Certificate x509; + List eku; + + System.out.print("\tTesting getExtendedKeyUsage"); + + /* skip if wolfSSL compiled with NO_FILESYSTEM */ + if (WolfSSL.FileSystemEnabled() == false) { + pass("\t... skipped"); + return; + } + + try { + /* Test with server certificate which has Extended Key Usage + * extension with serverAuth and clientAuth */ + byte[] der = tf.getCert("server"); + x509 = new WolfSSLX509(der); + eku = x509.getExtendedKeyUsage(); + + /* Server cert should have EKU extension */ + if (eku == null) { + error("\t... failed"); + fail("getExtendedKeyUsage() returned null for server cert"); + } + + /* Server cert should have serverAuth (1.3.6.1.5.5.7.3.1) and + * clientAuth (1.3.6.1.5.5.7.3.2) */ + if (eku.size() != 2) { + error("\t... failed"); + fail("Expected 2 EKU OIDs, got: " + eku.size()); + } + + if (!eku.contains("1.3.6.1.5.5.7.3.1")) { + error("\t... failed"); + fail("Missing serverAuth OID 1.3.6.1.5.5.7.3.1"); + } + + if (!eku.contains("1.3.6.1.5.5.7.3.2")) { + error("\t... failed"); + fail("Missing clientAuth OID 1.3.6.1.5.5.7.3.2"); + } + + /* Verify all OIDs are properly formatted */ + for (String oid : eku) { + if (oid == null || oid.isEmpty()) { + error("\t... failed"); + fail("EKU OID is null or empty"); + } + if (!oid.matches("^[0-9]+(\\.[0-9]+)*$")) { + error("\t... failed"); + fail("Invalid OID format: " + oid); + } + } + + } catch (Exception ex) { + error("\t... failed"); + ex.printStackTrace(); + fail("unexpected exception: " + ex.getMessage()); + } + + pass("\t... passed"); + } + @Test public void testWolfSSLPrincipalImplies() { Subject emptySubject, subjectWithPrincipals; diff --git a/src/test/com/wolfssl/test/WolfSSLCertificateTest.java b/src/test/com/wolfssl/test/WolfSSLCertificateTest.java index b4403a98..bb631595 100644 --- a/src/test/com/wolfssl/test/WolfSSLCertificateTest.java +++ b/src/test/com/wolfssl/test/WolfSSLCertificateTest.java @@ -140,6 +140,7 @@ public void test_runCertTestsAfterConstructor() { /* Key Usage and Extended Key Usage only work with wolfSSL * later than 5.6.3 */ test_getKeyUsage(); + test_getExtendedKeyUsage(); } test_getExtensionSet(); test_toString(); @@ -600,6 +601,60 @@ public void test_getKeyUsage() { System.out.println("\t\t... passed"); } + public void test_getExtendedKeyUsage() { + int i; + String[] eku; + String[] expected = { + "1.3.6.1.5.5.7.3.1", /* TLS Web Server Authentication */ + "1.3.6.1.5.5.7.3.2" /* TLS Web Client Authentication */ + }; + + System.out.print("\t\tgetExtendedKeyUsage"); + + /* Client cert has Extended Key Usage extension with serverAuth + * and clientAuth */ + eku = this.cert.getExtendedKeyUsage(); + if (eku == null) { + System.out.println("\t... failed"); + fail("getExtendedKeyUsage() returned null for client cert"); + } + + if (eku.length != expected.length) { + System.out.println("\t... failed"); + fail("Expected " + expected.length + " EKU OIDs, got: " + + eku.length); + } + + /* Verify expected OIDs are present */ + for (i = 0; i < expected.length; i++) { + boolean found = false; + for (String oid : eku) { + if (oid.equals(expected[i])) { + found = true; + break; + } + } + if (!found) { + System.out.println("\t... failed"); + fail("Missing expected OID: " + expected[i]); + } + } + + /* Verify all OIDs are properly formatted */ + for (i = 0; i < eku.length; i++) { + if (eku[i] == null || eku[i].isEmpty()) { + System.out.println("\t... failed"); + fail("Extended key usage OID is null or empty"); + } + if (!eku[i].matches("^[0-9]+(\\.[0-9]+)*$")) { + System.out.println("\t... failed"); + fail("Invalid OID format: " + eku[i]); + } + } + + System.out.println("\t... passed"); + } + public void test_getExtensionSet() { System.out.print("\t\tgetExtensionSet"); diff --git a/src/test/com/wolfssl/test/WolfSSLSessionTest.java b/src/test/com/wolfssl/test/WolfSSLSessionTest.java index a06cab67..40dd158a 100644 --- a/src/test/com/wolfssl/test/WolfSSLSessionTest.java +++ b/src/test/com/wolfssl/test/WolfSSLSessionTest.java @@ -2424,7 +2424,7 @@ public void test_WolfSSLSession_readByteBuffer() throws Exception { final WolfSSLContext srvCtx; WolfSSLContext cliCtx; - System.out.print("\tTesting ByteBuffer read() method"); + System.out.print("\tTesting ByteBuffer read()"); /* Create ServerSocket first to get ephemeral port */ final ServerSocket srvSocket = new ServerSocket(0);