package com.nutomic.syncthingandroid.activities;
import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.net.http.SslCertificate;
import android.net.http.SslError;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import android.webkit.HttpAuthHandler;
import android.webkit.SslErrorHandler;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.nutomic.syncthingandroid.R;
import com.nutomic.syncthingandroid.service.SyncthingService;
import com.nutomic.syncthingandroid.util.ConfigXml;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
/**
* Holds a WebView that shows the web ui of the local syncthing instance.
*/
public class WebGuiActivity extends SyncthingActivity
implements SyncthingService.OnWebGuiAvailableListener {
private static final String TAG = "WebGuiActivity";
private WebView mWebView;
private View mLoadingView;
private X509Certificate mCaCert;
private ConfigXml mConfig;
/**
* Hides the loading screen and shows the WebView once it is fully loaded.
*/
private final WebViewClient mWebViewClient = new WebViewClient() {
/**
* Catch (self-signed) SSL errors and test if they correspond to Syncthing's certificate.
*/
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
try {
int sdk = android.os.Build.VERSION.SDK_INT;
if (sdk >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
// The mX509Certificate field is not available for ICS- devices
Log.w(TAG, "Skipping certificate check for devices <ICS");
handler.proceed();
return;
}
// Use reflection to access the private mX509Certificate field of SslCertificate
SslCertificate sslCert = error.getCertificate();
Field f = sslCert.getClass().getDeclaredField("mX509Certificate");
f.setAccessible(true);
X509Certificate cert = (X509Certificate)f.get(sslCert);
if (cert == null) {
Log.w(TAG, "X509Certificate reference invalid");
handler.cancel();
return;
}
cert.verify(mCaCert.getPublicKey());
handler.proceed();
} catch (NoSuchFieldException|IllegalAccessException|CertificateException|
NoSuchAlgorithmException|InvalidKeyException|NoSuchProviderException|
SignatureException e) {
Log.w(TAG, e);
handler.cancel();
}
}
public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
String password = PreferenceManager.getDefaultSharedPreferences(WebGuiActivity.this)
.getString("web_gui_password", "");
handler.proceed(mConfig.getUserName(), password);
}
@Override
public void onPageFinished(WebView view, String url) {
mWebView.setVisibility(View.VISIBLE);
mLoadingView.setVisibility(View.GONE);
}
};
/**
* Initialize WebView.
*
* Ignore lint javascript warning as js is loaded only from our known, local service.
*/
@Override
@SuppressLint("SetJavaScriptEnabled")
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_web_gui);
mLoadingView = findViewById(R.id.loading);
mConfig = new ConfigXml(this);
loadCaCert();
mWebView = (WebView) findViewById(R.id.webview);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.setWebViewClient(mWebViewClient);
mWebView.clearCache(true);
}
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
super.onServiceConnected(componentName, iBinder);
getService().registerOnWebGuiAvailableListener(WebGuiActivity.this);
}
@Override
public void onWebGuiAvailable() {
mWebView.loadUrl(getService().getWebGuiUrl().toString());
}
/**
* Reads the SyncthingService.HTTPS_CERT_FILE Ca Cert key and loads it in memory
*/
private void loadCaCert() {
InputStream inStream = null;
try {
String httpsCertPath = getFilesDir() + "/" + SyncthingService.HTTPS_CERT_FILE;
inStream = new FileInputStream(httpsCertPath);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
mCaCert = (X509Certificate)
cf.generateCertificate(inStream);
} catch (FileNotFoundException|CertificateException e) {
throw new IllegalArgumentException("Untrusted Certificate", e);
} finally {
try {
if (inStream != null)
inStream.close();
} catch (IOException e) {
Log.w(TAG, e);
}
}
}
}