Skip to content

Commit

Permalink
Add "onReceivedSslError" callback to WPEViewClient
Browse files Browse the repository at this point in the history
  • Loading branch information
neodesys committed Jan 13, 2025
1 parent 1ae79a7 commit 37fa2d9
Show file tree
Hide file tree
Showing 3 changed files with 306 additions and 4 deletions.
186 changes: 182 additions & 4 deletions wpeview/src/main/cpp/Runtime/WKWebView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,93 @@ void handleCommitBuffer(void* context, WPEAndroidBuffer* buffer, int fenceID)

const int httpErrorsStart = 400;

class SslErrorHandler final {
public:
static SslErrorHandler* createHandler(
WebKitWebView* webView, GTlsCertificate* certificate, const char* failingURI, gchar** certificatePEM) noexcept
{
if (!webView || !certificate || !failingURI || !*failingURI || !certificatePEM) {
return nullptr;
}

gchar* host = nullptr;
// NOLINTBEGIN(clang-analyzer-optin.core.EnumCastOutOfRange)
GUri* uri
= g_uri_parse(failingURI, static_cast<GUriFlags>(G_URI_FLAGS_PARSE_RELAXED | G_URI_FLAGS_ENCODED), nullptr);
// NOLINTEND(clang-analyzer-optin.core.EnumCastOutOfRange)
if (uri) {
const char* str = g_uri_get_host(uri);
if (str) {
host = g_strdup(str);
}
g_uri_unref(uri);
}
if (!host) {
return nullptr;
}

*certificatePEM = nullptr;
g_object_get(certificate, "certificate-pem", certificatePEM, nullptr);
if (!*certificatePEM) {
g_free(host);
return nullptr;
}

return new SslErrorHandler(webView, certificate, g_strdup(failingURI), host);
}

~SslErrorHandler()
{
handlingFinished();
g_weak_ref_clear(&m_webViewWeakRef);
}

SslErrorHandler(SslErrorHandler&&) = delete;
SslErrorHandler& operator=(SslErrorHandler&&) = delete;
SslErrorHandler(const SslErrorHandler&) = delete;
SslErrorHandler& operator=(const SslErrorHandler&) = delete;

void acceptCertificate() noexcept
{
auto* webView = static_cast<WebKitWebView*>(g_weak_ref_get(&m_webViewWeakRef));
if (webView) {
auto* networkSession = webkit_web_view_get_network_session(webView);
if (networkSession) {
webkit_network_session_allow_tls_certificate_for_host(networkSession, m_certificate, m_host);
g_object_unref(networkSession);

webkit_web_view_load_uri(webView, m_failingURI);
}
g_object_unref(webView);
}
handlingFinished();
}

void rejectCertificate() noexcept { handlingFinished(); }

private:
GWeakRef m_webViewWeakRef = {};
GTlsCertificate* m_certificate = nullptr;
gchar* m_failingURI = nullptr;
gchar* m_host = nullptr;

SslErrorHandler(WebKitWebView* webView, GTlsCertificate* certificate, gchar* failingURI, gchar* host) noexcept
: m_failingURI(failingURI)
, m_host(host)
{
g_weak_ref_init(&m_webViewWeakRef, webView);
m_certificate = g_object_ref(certificate);
}

void handlingFinished() noexcept
{
g_weak_ref_set(&m_webViewWeakRef, nullptr);
g_clear_object(&m_certificate);
g_clear_pointer(&m_failingURI, g_free);
g_clear_pointer(&m_host, g_free);
}
};

} // namespace

/***********************************************************************************************************************
Expand Down Expand Up @@ -92,7 +179,7 @@ class JNIWKWebViewCache final : public JNI::TypedClass<JNIWKWebView> {
static_cast<jboolean>(webkit_web_view_can_go_forward(webView)));
}

static gboolean onScriptDialog(WKWebView* wkWebView, WebKitScriptDialog* dialog, WebKitWebView* webView)
static gboolean onScriptDialog(WKWebView* wkWebView, WebKitScriptDialog* dialog, WebKitWebView* webView) noexcept
{
auto dialogPtr = reinterpret_cast<jlong>(webkit_script_dialog_ref(dialog));
auto jActiveURL = JNI::String(webkit_web_view_get_uri(webView));
Expand All @@ -108,7 +195,7 @@ class JNIWKWebViewCache final : public JNI::TypedClass<JNIWKWebView> {
}

static gboolean onDecidePolicy(WKWebView* wkWebView, WebKitPolicyDecision* decision,
WebKitPolicyDecisionType decisionType, WebKitWebView* /*webView*/)
WebKitPolicyDecisionType decisionType, WebKitWebView* /*webView*/) noexcept
{
if (decisionType != WEBKIT_POLICY_DECISION_TYPE_RESPONSE)
return FALSE;
Expand Down Expand Up @@ -164,6 +251,72 @@ class JNIWKWebViewCache final : public JNI::TypedClass<JNIWKWebView> {
return FALSE;
}

static gboolean onReceivedSslError(WKWebView* wkWebView, char* failingURI, GTlsCertificate* certificate,
GTlsCertificateFlags errorFlags, WebKitWebView* webView) noexcept
{
if (wkWebView->m_webView != webView) {
return FALSE;
}

gchar* certificatePEM = nullptr;
SslErrorHandler* handler = SslErrorHandler::createHandler(webView, certificate, failingURI, &certificatePEM);
if (!handler) {
return FALSE;
}

auto jFailingURI = JNI::String(failingURI);
auto jCertificatePEM = JNI::String(certificatePEM);
g_free(certificatePEM);

// Android SslError values are:
// (https://developer.android.com/reference/android/net/http/SslError#constants_1)
//
// SSL_NOTYETVALID = 0x00
// SSL_EXPIRED = 0x01
// SSL_IDMISMATCH = 0x02
// SSL_UNTRUSTED = 0x03
// SSL_DATE_INVALID = 0x04
// SSL_INVALID = 0x05
int nbErrors = 0;
int errors[5] = {0};
if (errorFlags & (G_TLS_CERTIFICATE_UNKNOWN_CA | G_TLS_CERTIFICATE_REVOKED)) {
errors[nbErrors++] = 0x03; // SSL_UNTRUSTED
}
if (errorFlags & G_TLS_CERTIFICATE_EXPIRED) {
errors[nbErrors++] = 0x01; // SSL_EXPIRED
}
if (errorFlags & G_TLS_CERTIFICATE_NOT_ACTIVATED) {
errors[nbErrors++] = 0x00; // SSL_NOTYETVALID
}
if (errorFlags & G_TLS_CERTIFICATE_BAD_IDENTITY) {
errors[nbErrors++] = 0x02; // SSL_IDMISMATCH
}
if ((errorFlags & (G_TLS_CERTIFICATE_INSECURE | G_TLS_CERTIFICATE_GENERIC_ERROR)) || (nbErrors == 0)) {
errors[nbErrors++] = 0x05; // SSL_INVALID
}

auto jErrorsArray = JNI::ScalarArray<jint>(nbErrors);
nbErrors = 0;
for (auto& arrayValue : jErrorsArray.getContent()) {
arrayValue = errors[nbErrors++];
}

try {
if (!getJNIPageCache().m_onReceivedSslError.invoke(wkWebView->m_webViewJavaInstance.get(),
static_cast<jstring>(jFailingURI), static_cast<jstring>(jCertificatePEM),
static_cast<jintArray>(jErrorsArray), reinterpret_cast<jlong>(handler))) {
delete handler;
return FALSE;
}

return TRUE;
} catch (const std::exception& ex) {
Logging::logError("Cannot send the [received SSL error] event to Java runtime (%s)", ex.what());
delete handler;
return FALSE;
}
}

static bool onFullscreenRequest(WKWebView* wkWebView, bool fullscreen) noexcept
{
if (wkWebView->m_viewBackend != nullptr) {
Expand All @@ -184,7 +337,7 @@ class JNIWKWebViewCache final : public JNI::TypedClass<JNIWKWebView> {

void onInputMethodContextOut(jobject obj) const noexcept { callJavaMethod(m_onInputMethodContextOut, obj); }

static void onEvaluateJavascriptReady(WebKitWebView* webView, GAsyncResult* result, JNIWKCallback callback)
static void onEvaluateJavascriptReady(WebKitWebView* webView, GAsyncResult* result, JNIWKCallback callback) noexcept
{
GError* error = nullptr;
JSCValue* value = webkit_web_view_evaluate_javascript_finish(webView, result, &error);
Expand Down Expand Up @@ -232,6 +385,7 @@ class JNIWKWebViewCache final : public JNI::TypedClass<JNIWKWebView> {
const JNI::Method<jboolean(jlong, jint, jstring, jstring, jstring)> m_onScriptDialog;
const JNI::Method<void()> m_onInputMethodContextIn;
const JNI::Method<void()> m_onInputMethodContextOut;
const JNI::Method<jboolean(jstring, jstring, jintArray, jlong)> m_onReceivedSslError;
const JNI::Method<void()> m_onEnterFullscreenMode;
const JNI::Method<void()> m_onExitFullscreenMode;
const JNI::Method<void(jstring, jstring, jstringArray, jstring, jint, jstringArray)> m_onReceivedHttpError;
Expand Down Expand Up @@ -265,6 +419,9 @@ class JNIWKWebViewCache final : public JNI::TypedClass<JNIWKWebView> {
static void nativeScriptDialogConfirm(
JNIEnv* env, jobject obj, jlong dialogPtr, jboolean confirm, jstring text) noexcept;
static void nativeSetTLSErrorsPolicy(JNIEnv* env, jobject obj, jlong wkWebViewPtr, jint policy) noexcept;

static void nativeTriggerSslErrorHandler(
JNIEnv* env, jclass klass, jlong handlerPtr, jboolean acceptCertificate) noexcept;
};

const JNIWKWebViewCache& getJNIPageCache()
Expand All @@ -283,6 +440,7 @@ JNIWKWebViewCache::JNIWKWebViewCache()
, m_onScriptDialog(getMethod<jboolean(jlong, jint, jstring, jstring, jstring)>("onScriptDialog"))
, m_onInputMethodContextIn(getMethod<void()>("onInputMethodContextIn"))
, m_onInputMethodContextOut(getMethod<void()>("onInputMethodContextOut"))
, m_onReceivedSslError(getMethod<jboolean(jstring, jstring, jintArray, jlong)>("onReceivedSslError"))
, m_onEnterFullscreenMode(getMethod<void()>("onEnterFullscreenMode"))
, m_onExitFullscreenMode(getMethod<void()>("onExitFullscreenMode"))
, m_onReceivedHttpError(
Expand Down Expand Up @@ -319,7 +477,9 @@ JNIWKWebViewCache::JNIWKWebViewCache()
JNI::NativeMethod<void(jlong)>("nativeScriptDialogClose", JNIWKWebViewCache::nativeScriptDialogClose),
JNI::NativeMethod<void(jlong, jboolean, jstring)>(
"nativeScriptDialogConfirm", JNIWKWebViewCache::nativeScriptDialogConfirm),
JNI::NativeMethod<void(jlong, jint)>("nativeSetTLSErrorsPolicy", JNIWKWebViewCache::nativeSetTLSErrorsPolicy));
JNI::NativeMethod<void(jlong, jint)>("nativeSetTLSErrorsPolicy", JNIWKWebViewCache::nativeSetTLSErrorsPolicy),
JNI::StaticNativeMethod<void(jlong, jboolean)>(
"nativeTriggerSslErrorHandler", JNIWKWebViewCache::nativeTriggerSslErrorHandler));
}

jlong JNIWKWebViewCache::nativeInit(
Expand Down Expand Up @@ -595,6 +755,22 @@ void JNIWKWebViewCache::nativeSetTLSErrorsPolicy(
}
}

void JNIWKWebViewCache::nativeTriggerSslErrorHandler(
JNIEnv* /*env*/, jclass /*klass*/, jlong handlerPtr, jboolean acceptCertificate) noexcept
{
Logging::logDebug(
"WKWebView::nativeTriggerSslErrorHandler(%s) [tid %d]", acceptCertificate ? "true" : "false", gettid());
auto* handler = reinterpret_cast<SslErrorHandler*>(handlerPtr); // NOLINT(performance-no-int-to-ptr)
if (handler != nullptr) {
if (acceptCertificate) {
handler->acceptCertificate();
} else {
handler->rejectCertificate();
}
delete handler;
}
}

/***********************************************************************************************************************
* Native WKWebView class implementation
**********************************************************************************************************************/
Expand Down Expand Up @@ -640,6 +816,8 @@ WKWebView::WKWebView(JNIEnv* env, JNIWKWebView jniWKWebView, WKWebContext* wkWeb
g_signal_connect_swapped(m_webView, "script-dialog", G_CALLBACK(JNIWKWebViewCache::onScriptDialog), this));
m_signalHandlers.push_back(
g_signal_connect_swapped(m_webView, "decide-policy", G_CALLBACK(JNIWKWebViewCache::onDecidePolicy), this));
m_signalHandlers.push_back(g_signal_connect_swapped(
m_webView, "load-failed-with-tls-errors", G_CALLBACK(JNIWKWebViewCache::onReceivedSslError), this));

wpe_view_backend_set_fullscreen_handler(wpeBackend,
reinterpret_cast<wpe_view_backend_fullscreen_handler>(JNIWKWebViewCache::onFullscreenRequest), this);
Expand Down
88 changes: 88 additions & 0 deletions wpeview/src/main/java/org/wpewebkit/wpe/WKWebView.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import android.content.Context;
import android.content.DialogInterface;
import android.net.Uri;
import android.net.http.SslError;
import android.os.Handler;
import android.os.Looper;
import android.util.DisplayMetrics;
Expand Down Expand Up @@ -58,9 +59,12 @@
import org.wpewebkit.wpeview.WPEView;
import org.wpewebkit.wpeview.WPEViewClient;

import java.io.ByteArrayInputStream;
import java.lang.ref.WeakReference;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;

Expand Down Expand Up @@ -584,6 +588,88 @@ private void onInputMethodContextOut() {
});
}

private static final class SslErrorHandlerImpl implements WPEViewClient.SslErrorHandler {
private final Handler m_handler;
private long m_nativeHandlerPtr = 0;

protected SslErrorHandlerImpl(long nativeHandlerPtr) {
m_nativeHandlerPtr = nativeHandlerPtr;

Looper looper = Looper.myLooper();
if (looper == null) {
looper = Looper.getMainLooper();
}
m_handler = new Handler(looper);
}

private void triggerSslErrorHandler(boolean acceptCertificate) {
if (m_nativeHandlerPtr != 0) {
nativeTriggerSslErrorHandler(m_nativeHandlerPtr, acceptCertificate);
m_nativeHandlerPtr = 0;
}
}

private void executeOrPostTask(Runnable task) {
if (Looper.myLooper() == m_handler.getLooper()) {
task.run();
} else {
m_handler.post(task);
}
}

@Override
public void proceed() {
executeOrPostTask(() -> triggerSslErrorHandler(true));
}

@Override
public void cancel() {
executeOrPostTask(() -> triggerSslErrorHandler(false));
}

@Override
protected void finalize() throws Throwable {
cancel();
}
}

@Keep
private boolean onReceivedSslError(@NonNull String failingURI, @NonNull String certificatePEM,
@NonNull int[] sslErrors, long nativeHandlerPtr) {
Log.d(LOGTAG, "onReceivedSslError()");
if (wpeViewClient == null) {
return false;
}

SslError sslError;
try {
CertificateFactory factory = CertificateFactory.getInstance("X.509");
X509Certificate certificate =
(X509Certificate)factory.generateCertificate(new ByteArrayInputStream(certificatePEM.getBytes()));

sslError = new SslError(sslErrors[0], certificate, failingURI);
for (int i = 1; i < sslErrors.length; ++i) {
sslError.addError(sslErrors[i]);
}
} catch (Exception e) {
Log.e(LOGTAG, "Error while wrapping the SSL certificate in onReceivedSslError()", e);
return false;
}

SslErrorHandlerImpl handler = new SslErrorHandlerImpl(nativeHandlerPtr);
try {
wpeViewClient.onReceivedSslError(wpeView, handler, sslError);
} catch (Exception e) {
Log.e(LOGTAG,
"Exception thrown while calling WPEViewClient.onReceivedSslError(), certificate is "
+ "automatically rejected",
e);
handler.cancel();
}

return true;
}

@Keep
private void onEnterFullscreenMode() {
Log.d(LOGTAG, "onEnterFullscreenMode()");
Expand Down Expand Up @@ -658,4 +744,6 @@ private native void nativeOnTouchEvent(long nativePtr, long time, int type, int
private native void nativeScriptDialogClose(long nativeDialogPtr);
private native void nativeScriptDialogConfirm(long nativeDialogPtr, boolean confirm, @Nullable String text);
private native void nativeSetTLSErrorsPolicy(long nativePtr, int policy);

protected static native void nativeTriggerSslErrorHandler(long nativeHandlerPtr, boolean acceptCertificate);
}
Loading

0 comments on commit 37fa2d9

Please sign in to comment.