From aba417593ff4dac478eac08534ccf529dec2c56c Mon Sep 17 00:00:00 2001 From: Julian Winkler Date: Tue, 25 Mar 2025 22:39:31 +0100 Subject: [PATCH] WolfSSLSession.getPeerCertificates(): provide full cert chain This is needed for Certificate Pinning which is a common practice in Android applications. --- native/com_wolfssl_WolfSSLSession.c | 26 ++- native/com_wolfssl_WolfSSLSession.h | 12 +- src/java/com/wolfssl/WolfSSLSession.java | 29 +++- .../jsse/WolfSSLImplementSSLSession.java | 161 +++++++++++------- 4 files changed, 156 insertions(+), 72 deletions(-) diff --git a/native/com_wolfssl_WolfSSLSession.c b/native/com_wolfssl_WolfSSLSession.c index 897e530..7420b41 100644 --- a/native/com_wolfssl_WolfSSLSession.c +++ b/native/com_wolfssl_WolfSSLSession.c @@ -2858,9 +2858,29 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_sessionReused return wolfSSL_session_reused(ssl); } -JNIEXPORT jlong JNICALL Java_com_wolfssl_WolfSSLSession_getPeerCertificate +JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_getPeerCertificateCount (JNIEnv* jenv, jobject jcl, jlong sslPtr) { +#ifdef KEEP_PEER_CERT + WOLFSSL* ssl = (WOLFSSL*)(uintptr_t)sslPtr; + (void)jenv; + (void)jcl; + if (ssl == NULL) { + return (jlong)0; + } + WOLFSSL_X509_CHAIN* chain = wolfSSL_get_peer_chain(ssl); + return wolfSSL_get_chain_count(chain); +#else + (void)jenv; + (void)jcl; + (void)sslPtr; + return 0; +#endif +} + +JNIEXPORT jlong JNICALL Java_com_wolfssl_WolfSSLSession_getPeerCertificate + (JNIEnv* jenv, jobject jcl, jlong sslPtr, jint index) +{ #ifdef KEEP_PEER_CERT WOLFSSL_X509* x509 = NULL; WOLFSSL* ssl = (WOLFSSL*)(uintptr_t)sslPtr; @@ -2870,8 +2890,8 @@ JNIEXPORT jlong JNICALL Java_com_wolfssl_WolfSSLSession_getPeerCertificate if (ssl == NULL) { return (jlong)0; } - - x509 = wolfSSL_get_peer_certificate(ssl); + WOLFSSL_X509_CHAIN* chain = wolfSSL_get_peer_chain(ssl); + x509 = wolfSSL_get_chain_X509(chain, index); return (jlong)(uintptr_t)x509; #else diff --git a/native/com_wolfssl_WolfSSLSession.h b/native/com_wolfssl_WolfSSLSession.h index 017e457..fb1d41c 100644 --- a/native/com_wolfssl_WolfSSLSession.h +++ b/native/com_wolfssl_WolfSSLSession.h @@ -351,13 +351,21 @@ JNIEXPORT jobject JNICALL Java_com_wolfssl_WolfSSLSession_dtlsGetPeer JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_sessionReused (JNIEnv *, jobject, jlong); +/* + * Class: com_wolfssl_WolfSSLSession + * Method: getPeerCertificateCount + * Signature: (J)I + */ +JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_getPeerCertificateCount + (JNIEnv *, jobject, jlong); + /* * Class: com_wolfssl_WolfSSLSession * Method: getPeerCertificate - * Signature: (J)J + * Signature: (JI)J */ JNIEXPORT jlong JNICALL Java_com_wolfssl_WolfSSLSession_getPeerCertificate - (JNIEnv *, jobject, jlong); + (JNIEnv *, jobject, jlong, jint); /* * Class: com_wolfssl_WolfSSLSession diff --git a/src/java/com/wolfssl/WolfSSLSession.java b/src/java/com/wolfssl/WolfSSLSession.java index dd39fb6..fe3cac3 100644 --- a/src/java/com/wolfssl/WolfSSLSession.java +++ b/src/java/com/wolfssl/WolfSSLSession.java @@ -358,7 +358,8 @@ public class WolfSSLSession { private native long getDtlsReplayDropCount(long ssl); private native InetSocketAddress dtlsGetPeer(long ssl); private native int sessionReused(long ssl); - private native long getPeerCertificate(long ssl); + private native int getPeerCertificateCount(long ssl); + private native long getPeerCertificate(long ssl, int index); private native String getPeerX509Issuer(long ssl, long x509); private native String getPeerX509Subject(long ssl, long x509); private native String getPeerX509AltName(long ssl, long x509); @@ -2590,6 +2591,27 @@ public class WolfSSLSession { return ret; } + /** + * Returns the number of certificates in the peer's certificate chain. + * + * @return number of certificates in the peer's certificate chain. + * @throws IllegalStateException WolfSSLContext has been freed + * @throws WolfSSLJNIException Internal JNI error + * @see WolfSSLSession#getPeerCertificate(int) + */ + public int getPeerCertificateCount() + throws IllegalStateException, WolfSSLJNIException { + + confirmObjectIsActive(); + + synchronized (sslLock) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI, + WolfSSLDebug.INFO, this.sslPtr, "entered getPeerCertificateCount()"); + + return getPeerCertificateCount(this.sslPtr); + } + } + /** * Gets the native (long) WOLFSSL_X509 pointer to the peer's certificate. * This can be used to retrieve further information about the peer's @@ -2606,6 +2628,7 @@ public class WolfSSLSession { * Pointer should be freed by calling: * WolfSSLCertificate.freeX509(long x509); * + * @param index index of the certificate in the peer's certificate chain * @return (long) WOLFSSL_X509 pointer to the peer's certificate. * @throws IllegalStateException WolfSSLContext has been freed * @throws WolfSSLJNIException Internal JNI error @@ -2614,7 +2637,7 @@ public class WolfSSLSession { * @see WolfSSLSession#getVersion() * @see WolfSSLSession#getCurrentCipher() */ - public long getPeerCertificate() + public long getPeerCertificate(int index) throws IllegalStateException, WolfSSLJNIException { confirmObjectIsActive(); @@ -2623,7 +2646,7 @@ public class WolfSSLSession { WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI, WolfSSLDebug.INFO, this.sslPtr, "entered getPeerCertificate()"); - return getPeerCertificate(this.sslPtr); + return getPeerCertificate(this.sslPtr, index); } } diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLImplementSSLSession.java b/src/java/com/wolfssl/provider/jsse/WolfSSLImplementSSLSession.java index 3d97056..d156b1a 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLImplementSSLSession.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLImplementSSLSession.java @@ -516,17 +516,13 @@ public class WolfSSLImplementSSLSession extends ExtendedSSLSession "SSLSocket/Engine closed"); } + int numCerts; try { - x509 = this.ssl.getPeerCertificate(); + numCerts = this.ssl.getPeerCertificateCount(); } catch (IllegalStateException | WolfSSLJNIException ex) { Logger.getLogger( WolfSSLImplementSSLSession.class.getName()).log( Level.SEVERE, null, ex); - x509 = 0; - } - - /* if no peer cert, throw SSLPeerUnverifiedException */ - if (x509 == 0) { WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, "ssl.getPeerCertificates() returned null, trying cached cert"); @@ -542,56 +538,88 @@ public class WolfSSLImplementSSLSession extends ExtendedSSLSession throw new SSLPeerUnverifiedException("No peer certificate"); } } + X509Certificate[] certs = new X509Certificate[numCerts]; - try { - /* wolfSSL starting with 5.3.0 returns a new WOLFSSL_X509 - * structure from wolfSSL_get_peer_certificate(). In that case, - * we need to free the pointer when finished. Prior to 5.3.0, - * this memory was freed internally by wolfSSL since the API - * only returned a pointer to internal memory */ - if (WolfSSL.getLibVersionHex() >= 0x05003000) { - cert = new WolfSSLX509(x509, true); + for (int i = 0; i < numCerts; i++) { + try { + x509 = this.ssl.getPeerCertificate(i); + } catch (IllegalStateException | WolfSSLJNIException ex) { + Logger.getLogger( + WolfSSLImplementSSLSession.class.getName()).log( + Level.SEVERE, null, ex); + x509 = 0; } - else { - cert = new WolfSSLX509(x509, false); + + /* if no peer cert, throw SSLPeerUnverifiedException */ + if (x509 == 0) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + "ssl.getPeerCertificates() returned null, trying cached cert"); + + if (this.peerCerts != null) { + /* If peer cert is already cached, just return that */ + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + "peer cert already cached, returning it"); + return this.peerCerts.clone(); + } + else { + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + "No peer cert sent and none cached"); + throw new SSLPeerUnverifiedException("No peer certificate"); + } } - } catch (WolfSSLException ex) { - throw new SSLPeerUnverifiedException("Error creating certificate"); - } - /* convert WolfSSLX509 into X509Certificate so we can release - * our native memory */ - try { - cf = CertificateFactory.getInstance("X.509"); - } catch (CertificateException ex) { + try { + /* wolfSSL starting with 5.3.0 returns a new WOLFSSL_X509 + * structure from wolfSSL_get_peer_certificate(). In that case, + * we need to free the pointer when finished. Prior to 5.3.0, + * this memory was freed internally by wolfSSL since the API + * only returned a pointer to internal memory */ + if (WolfSSL.getLibVersionHex() >= 0x05003000) { + cert = new WolfSSLX509(x509, true); + } + else { + cert = new WolfSSLX509(x509, false); + } + } catch (WolfSSLException ex) { + throw new SSLPeerUnverifiedException("Error creating certificate"); + } + + /* convert WolfSSLX509 into X509Certificate so we can release + * our native memory */ + try { + cf = CertificateFactory.getInstance("X.509"); + } catch (CertificateException ex) { + cert.free(); + throw new SSLPeerUnverifiedException( + "Error getting CertificateFactory instance"); + } + + try { + der = new ByteArrayInputStream(cert.getEncoded()); + } catch (CertificateEncodingException ex) { + cert.free(); + throw new SSLPeerUnverifiedException( + "Error getting encoded DER from WolfSSLX509 object"); + } + + try { + exportCert = (X509Certificate)cf.generateCertificate(der); + } catch (CertificateException ex) { + cert.free(); + throw new SSLPeerUnverifiedException( + "Error generating X509Certificdate from DER encoding"); + } + + /* release native memory */ cert.free(); - throw new SSLPeerUnverifiedException( - "Error getting CertificateFactory instance"); - } - try { - der = new ByteArrayInputStream(cert.getEncoded()); - } catch (CertificateEncodingException ex) { - cert.free(); - throw new SSLPeerUnverifiedException( - "Error getting encoded DER from WolfSSLX509 object"); + certs[i] = exportCert; } - try { - exportCert = (X509Certificate)cf.generateCertificate(der); - } catch (CertificateException ex) { - cert.free(); - throw new SSLPeerUnverifiedException( - "Error generating X509Certificdate from DER encoding"); - } - - /* release native memory */ - cert.free(); - /* cache peer cert for use by app in resumed session */ - this.peerCerts = new X509Certificate[] { exportCert }; + this.peerCerts = certs; - return this.peerCerts.clone(); + return certs.clone(); } @Override @@ -612,25 +640,30 @@ public class WolfSSLImplementSSLSession extends ExtendedSSLSession } try { - peerX509 = this.ssl.getPeerCertificate(); - if (peerX509 == 0) { - return null; + int numCerts = this.ssl.getPeerCertificateCount(); + javax.security.cert.X509Certificate[] certs = new javax.security.cert.X509Certificate[numCerts]; + + for (int i = 0; i < numCerts; i++) { + peerX509 = this.ssl.getPeerCertificate(i); + if (peerX509 == 0) { + return null; + } + + /* wolfSSL starting with 5.3.0 returns a new WOLFSSL_X509 + * structure from wolfSSL_get_peer_certificate(). In that case, + * we need to free the pointer when finished. Prior to 5.3.0, + * this memory was freed internally by wolfSSL since the API + * only returned a pointer to internal memory */ + if (WolfSSL.getLibVersionHex() >= 0x05003000) { + x509 = new WolfSSLX509X(peerX509, true); + } + else { + x509 = new WolfSSLX509X(peerX509, false); + } + certs[i] = (javax.security.cert.X509Certificate)x509; } - /* wolfSSL starting with 5.3.0 returns a new WOLFSSL_X509 - * structure from wolfSSL_get_peer_certificate(). In that case, - * we need to free the pointer when finished. Prior to 5.3.0, - * this memory was freed internally by wolfSSL since the API - * only returned a pointer to internal memory */ - if (WolfSSL.getLibVersionHex() >= 0x05003000) { - x509 = new WolfSSLX509X(peerX509, true); - } - else { - x509 = new WolfSSLX509X(peerX509, false); - } - - return new javax.security.cert.X509Certificate[] { - (javax.security.cert.X509Certificate)x509 }; + return certs; } catch (IllegalStateException | WolfSSLJNIException | WolfSSLException ex) { @@ -654,7 +687,7 @@ public class WolfSSLImplementSSLSession extends ExtendedSSLSession } try { - peerX509 = this.ssl.getPeerCertificate(); + peerX509 = this.ssl.getPeerCertificate(0); if (peerX509 == 0) { return null; }