Added SSL Validation Override and CA Selection

- Added fields to login page to a) disable ssl validation or b) select
  a custom Certificate Authority certificate to use with the server.

- Changed visibility of widgets on login page from INVISIBLE to GONE so
  they don't take up space while hidden (since this was causing weird
  spacing issues with the new fields).

- Added state to settings to store ssl validation choice or certificate
  data.

- Added fields to various HTTP methods to disable ssl validation or set
  valid certificate authority if either setting is enabled.
This commit is contained in:
Galen Abell
2018-11-07 17:28:25 -05:00
parent 97ab5a6871
commit 2d14ef1b6f
10 changed files with 362 additions and 45 deletions

View File

@@ -1,5 +1,6 @@
package com.github.gotify;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -7,14 +8,30 @@ import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.text.format.DateUtils;
import android.view.View;
import androidx.annotation.NonNull;
import com.github.gotify.client.ApiClient;
import com.github.gotify.client.JSON;
import com.github.gotify.log.Log;
import com.google.android.material.snackbar.Snackbar;
import com.squareup.picasso.Picasso;
import com.squareup.picasso.Target;
import okhttp3.OkHttpClient;
import okio.Buffer;
import org.threeten.bp.OffsetDateTime;
import javax.net.ssl.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.io.InputStream;
import java.util.Collection;
public class Utils {
public static void showSnackBar(Activity activity, String message) {
View rootView = activity.getWindow().getDecorView().findViewById(android.R.id.content);
@@ -52,4 +69,110 @@ public class Utils {
public interface DrawableReceiver {
void loaded(Drawable drawable);
}
public static InputStream stringToInputStream(String str) {
if (str == null) return null;
return new Buffer()
.writeUtf8(str)
.inputStream();
}
public static String readFileFromStream(@NonNull InputStream inputStream) {
StringBuilder sb = new StringBuilder();
String currentLine;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
while ((currentLine = reader.readLine()) != null) {
sb.append(currentLine).append("\n");
}
} catch (IOException e) {
throw new IllegalArgumentException("failed to read input");
}
return sb.toString();
}
/////////////////////////
///// SSL Utilities /////
/////////////////////////
public static Certificate parseCertificate(String cert) {
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
return certificateFactory.generateCertificate(Utils.stringToInputStream(cert));
} catch (Exception e) {
throw new IllegalArgumentException("certificate is invalid");
}
}
public static class SSLSettings {
boolean validateSSL;
String cert;
public SSLSettings(boolean validateSSL, String cert) {
this.validateSSL = validateSSL;
this.cert = cert;
}
}
// TrustManager that trusts all SSL Certs
private static final TrustManager trustAll = new X509TrustManager() {
@SuppressLint("TrustAllX509TrustManager")
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {}
@SuppressLint("TrustAllX509TrustManager")
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {}
@Override
public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[]{}; }
};
public static void applySslSettings(OkHttpClient.Builder builder, SSLSettings settings) {
// Modified from ApiClient.applySslSettings in the client package.
try {
TrustManager[] trustManagers = null;
HostnameVerifier hostnameVerifier = null;
if (!settings.validateSSL) {
trustManagers = new TrustManager[]{ trustAll };
hostnameVerifier = (hostname, session) -> true;
} else if (settings.cert != null) {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
Collection<? extends Certificate> certificates = certificateFactory.generateCertificates(stringToInputStream(settings.cert));
if (certificates.isEmpty()) {
throw new IllegalArgumentException("expected non-empty set of trusted certificates");
}
KeyStore caKeyStore = newEmptyKeyStore();
int index = 0;
for (Certificate certificate : certificates) {
String certificateAlias = "ca" + Integer.toString(index++);
caKeyStore.setCertificateEntry(certificateAlias, certificate);
}
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(caKeyStore);
trustManagers = trustManagerFactory.getTrustManagers();
}
if (trustManagers != null && trustManagers.length > 0) {
SSLContext context = SSLContext.getInstance("TLS");
context.init(new KeyManager[]{}, trustManagers, new SecureRandom());
builder.sslSocketFactory(context.getSocketFactory(), (X509TrustManager) trustManagers[0]);
}
if (hostnameVerifier != null) builder.hostnameVerifier(hostnameVerifier);
} catch (Exception e) {
// We shouldn't have issues since the cert is verified on login.
Log.e("Failed to apply SSL settings", e);
}
}
private static KeyStore newEmptyKeyStore() throws GeneralSecurityException {
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
return keyStore;
} catch (IOException e) {
throw new AssertionError(e);
}
}
}