Many improvements and bug fixes.

This commit is contained in:
anonTree1417 2020-03-31 22:09:42 +02:00
parent 5fdbc98d6e
commit e5a656ce57
6 changed files with 79 additions and 129 deletions

View File

@ -36,7 +36,7 @@ public class IssuesService {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY); logging.setLevel(HttpLoggingInterceptor.Level.BODY);
try { try { // try-catch can be problematic here
SSLContext sslContext = SSLContext.getInstance("TLS"); SSLContext sslContext = SSLContext.getInstance("TLS");
MemorizingTrustManager memorizingTrustManager = new MemorizingTrustManager(ctx); MemorizingTrustManager memorizingTrustManager = new MemorizingTrustManager(ctx);

View File

@ -36,7 +36,7 @@ public class PullRequestsService {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY); logging.setLevel(HttpLoggingInterceptor.Level.BODY);
try { try { // try-catch can be problematic here
SSLContext sslContext = SSLContext.getInstance("TLS"); SSLContext sslContext = SSLContext.getInstance("TLS");
MemorizingTrustManager memorizingTrustManager = new MemorizingTrustManager(ctx); MemorizingTrustManager memorizingTrustManager = new MemorizingTrustManager(ctx);

View File

@ -36,7 +36,7 @@ public class RetrofitClient {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY); logging.setLevel(HttpLoggingInterceptor.Level.BODY);
try { try { // try-catch can be problematic here
SSLContext sslContext = SSLContext.getInstance("TLS"); SSLContext sslContext = SSLContext.getInstance("TLS");
MemorizingTrustManager memorizingTrustManager = new MemorizingTrustManager(ctx); MemorizingTrustManager memorizingTrustManager = new MemorizingTrustManager(ctx);

View File

@ -18,9 +18,9 @@ public class MemorizingActivity extends Activity {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
Intent intent = getIntent(); Intent intent = getIntent();
int decisionId = intent.getIntExtra(MemorizingTrustManager.DECISION_INTENT_ID, MTMDecision.DECISION_INVALID); int decisionId = intent.getIntExtra("DECISION_INTENT_ID", MTMDecision.DECISION_INVALID);
int titleId = intent.getIntExtra(MemorizingTrustManager.DECISION_TITLE_ID, R.string.mtm_accept_cert); int titleId = intent.getIntExtra("DECISION_TITLE_ID", R.string.mtm_accept_cert);
String cert = intent.getStringExtra(MemorizingTrustManager.DECISION_INTENT_CERT); String cert = intent.getStringExtra("DECISION_INTENT_CERT");
AlertDialog.Builder builder = new AlertDialog.Builder(MemorizingActivity.this); AlertDialog.Builder builder = new AlertDialog.Builder(MemorizingActivity.this);
builder.setTitle(titleId); builder.setTitle(titleId);

View File

@ -46,20 +46,12 @@ import javax.net.ssl.X509TrustManager;
*/ */
public class MemorizingTrustManager implements X509TrustManager { public class MemorizingTrustManager implements X509TrustManager {
private final static String DECISION_INTENT = "de.duenndns.ssl.DECISION";
final static String DECISION_INTENT_ID = DECISION_INTENT + ".decisionId";
final static String DECISION_INTENT_CERT = DECISION_INTENT + ".cert";
private final static Logger LOGGER = Logger.getLogger(MemorizingTrustManager.class.getName());
final static String DECISION_TITLE_ID = DECISION_INTENT + ".titleId";
private final static int NOTIFICATION_ID = 100509; private final static int NOTIFICATION_ID = 100509;
private final static String KEYSTORE_NAME = "keystore"; private final static String KEYSTORE_NAME = "keystore";
private final static String KEYSTORE_KEY = "keystore"; private final static String KEYSTORE_KEY = "keystore";
private Context context; private Context context;
private Activity foregroundAct;
private NotificationManager notificationManager; private NotificationManager notificationManager;
private static int decisionId = 0; private static int decisionId = 0;
private static final SparseArray<MTMDecision> openDecisions = new SparseArray<>(); private static final SparseArray<MTMDecision> openDecisions = new SparseArray<>();
@ -143,38 +135,6 @@ public class MemorizingTrustManager implements X509TrustManager {
return new X509TrustManager[]{new MemorizingTrustManager(c)}; return new X509TrustManager[]{new MemorizingTrustManager(c)};
} }
/**
* Binds an Activity to the MTM for displaying the query dialog.
* <p>
* This is useful if your connection is run from a service that is
* triggered by user interaction -- in such cases the activity is
* visible and the user tends to ignore the service notification.
* <p>
* You should never have a hidden activity bound to MTM! Use this
* function in onResume() and @see unbindDisplayActivity in onPause().
*
* @param act Activity to be bound
*/
private void bindDisplayActivity(Activity act) {
foregroundAct = act;
}
/**
* Removes an Activity from the MTM display stack.
* <p>
* Always call this function when the Activity added with
* {@link #bindDisplayActivity(Activity)} is hidden.
*
* @param act Activity to be unbound
*/
public void unbindDisplayActivity(Activity act) {
// do not remove if it was overridden by a different activity
if(foregroundAct == act) {
foregroundAct = null;
}
}
/** /**
* Get a list of all certificate aliases stored in MTM. * Get a list of all certificate aliases stored in MTM.
* *
@ -365,6 +325,7 @@ public class MemorizingTrustManager implements X509TrustManager {
} }
e = e.getCause(); e = e.getCause();
} while(e != null); } while(e != null);
return false; return false;
} }
@ -374,17 +335,16 @@ public class MemorizingTrustManager implements X509TrustManager {
if(e instanceof CertPathValidatorException) { if(e instanceof CertPathValidatorException) {
return true; return true;
} }
e = e.getCause(); e = e.getCause();
} while(e != null); } while(e != null);
return false; return false;
} }
private void checkCertTrusted(X509Certificate[] chain, String authType, boolean isServer) throws CertificateException { private void checkCertTrusted(X509Certificate[] chain, String authType, boolean isServer) throws CertificateException {
LOGGER.log(Level.FINE, "checkCertTrusted(" + Arrays.toString(chain) + ", " + authType + ", " + isServer + ")");
try { try {
LOGGER.log(Level.FINE, "checkCertTrusted: trying appTrustManager");
if(isServer) { if(isServer) {
appTrustManager.checkServerTrusted(chain, authType); appTrustManager.checkServerTrusted(chain, authType);
} }
@ -393,22 +353,15 @@ public class MemorizingTrustManager implements X509TrustManager {
} }
} }
catch(CertificateException ae) { catch(CertificateException ae) {
LOGGER.log(Level.FINER, "checkCertTrusted: appTrustManager did not verify certificate. Will fall back to secondary verification mechanisms (if any).", ae);
// if the cert is stored in our appTrustManager, we ignore expiredness // if the cert is stored in our appTrustManager, we ignore expiredness
if(isExpiredException(ae)) { if(isExpiredException(ae) || isCertKnown(chain[0])) {
LOGGER.log(Level.INFO, "checkCertTrusted: accepting expired certificate from keystore");
return;
}
if(isCertKnown(chain[0])) {
LOGGER.log(Level.INFO, "checkCertTrusted: accepting cert already stored in keystore");
return; return;
} }
try { try {
if(defaultTrustManager == null) { if(defaultTrustManager == null) {
LOGGER.fine("No defaultTrustManager set. Verification failed, throwing " + ae);
throw ae; throw ae;
} }
LOGGER.log(Level.FINE, "checkCertTrusted: trying defaultTrustManager");
if(isServer) { if(isServer) {
defaultTrustManager.checkServerTrusted(chain, authType); defaultTrustManager.checkServerTrusted(chain, authType);
} }
@ -417,7 +370,6 @@ public class MemorizingTrustManager implements X509TrustManager {
} }
} }
catch(CertificateException e) { catch(CertificateException e) {
LOGGER.log(Level.FINER, "checkCertTrusted: defaultTrustManager failed", e);
interactCert(chain, authType, e); interactCert(chain, authType, e);
} }
} }
@ -452,12 +404,14 @@ public class MemorizingTrustManager implements X509TrustManager {
private static String hexString(byte[] data) { private static String hexString(byte[] data) {
StringBuilder si = new StringBuilder(); StringBuilder si = new StringBuilder();
for(int i = 0; i < data.length; i++) { for(int i = 0; i < data.length; i++) {
si.append(String.format("%02x", data[i])); si.append(String.format("%02x", data[i]));
if(i < data.length - 1) { if(i < data.length - 1) {
si.append(":"); si.append(":");
} }
} }
return si.toString(); return si.toString();
} }
@ -473,91 +427,97 @@ public class MemorizingTrustManager implements X509TrustManager {
} }
} }
private static void certDetails(StringBuilder si, X509Certificate c) { private static void certDetails(StringBuilder stringBuilder, X509Certificate c) {
SimpleDateFormat validityDateFormater = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); SimpleDateFormat validityDateFormater = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
si.append("\n");
si.append(c.getSubjectDN().toString()); stringBuilder.append("\n")
si.append("\n"); .append(c.getSubjectDN().toString())
si.append(validityDateFormater.format(c.getNotBefore())); .append("\n")
si.append(" - "); .append(validityDateFormater.format(c.getNotBefore()))
si.append(validityDateFormater.format(c.getNotAfter())); .append(" - ")
si.append("\nSHA-256: "); .append(validityDateFormater.format(c.getNotAfter()))
si.append(certHash(c, "SHA-256")); .append("\nSHA-256: ")
si.append("\nSHA-1: "); .append(certHash(c, "SHA-256"))
si.append(certHash(c, "SHA-1")); .append("\nSHA-1: ")
si.append("\nSigned by: "); .append(certHash(c, "SHA-1"))
si.append(c.getIssuerDN().toString()); .append("\nSigned by: ")
si.append("\n"); .append(c.getIssuerDN().toString())
.append("\n");
} }
private String certChainMessage(final X509Certificate[] chain, CertificateException cause) { private String certChainMessage(final X509Certificate[] chain, CertificateException cause) {
Throwable e = cause; Throwable e = cause;
StringBuilder si = new StringBuilder(); StringBuilder stringBuilder = new StringBuilder();
if(isPathException(e)) { if(isPathException(e)) {
si.append(context.getString(R.string.mtm_trust_anchor)); stringBuilder.append(context.getString(R.string.mtm_trust_anchor));
} }
else if(isExpiredException(e)) { else if(isExpiredException(e)) {
si.append(context.getString(R.string.mtm_cert_expired)); stringBuilder.append(context.getString(R.string.mtm_cert_expired));
} }
else { else {
// get to the cause // get to the cause
while(e.getCause() != null) while(e.getCause() != null) {
e = e.getCause(); e = e.getCause();
si.append(e.getLocalizedMessage()); }
stringBuilder.append(e.getLocalizedMessage());
} }
si.append("\n\n"); stringBuilder.append("\n\n");
si.append(context.getString(R.string.mtm_connect_anyway)); stringBuilder.append(context.getString(R.string.mtm_connect_anyway));
si.append("\n\n"); stringBuilder.append("\n\n");
si.append(context.getString(R.string.mtm_cert_details)); stringBuilder.append(context.getString(R.string.mtm_cert_details));
for(X509Certificate c : chain) { for(X509Certificate c : chain) {
certDetails(si, c); certDetails(stringBuilder, c);
} }
return si.toString();
return stringBuilder.toString();
} }
private String hostNameMessage(X509Certificate cert, String hostname) { private String hostNameMessage(X509Certificate cert, String hostname) {
StringBuilder si = new StringBuilder(); StringBuilder stringBuilder = new StringBuilder();
si.append(context.getString(R.string.mtm_hostname_mismatch, hostname)); stringBuilder.append(context.getString(R.string.mtm_hostname_mismatch, hostname));
si.append("\n\n"); stringBuilder.append("\n\n");
try { try {
Collection<List<?>> sans = cert.getSubjectAlternativeNames(); Collection<List<?>> sans = cert.getSubjectAlternativeNames();
if(sans == null) { if(sans == null) {
si.append(cert.getSubjectDN()); stringBuilder.append(cert.getSubjectDN());
si.append("\n"); stringBuilder.append("\n");
} }
else { else {
for(List<?> altName : sans) { for(List<?> altName : sans) {
Object name = altName.get(1); Object name = altName.get(1);
if(name instanceof String) { if(name instanceof String) {
si.append("["); stringBuilder.append("[");
si.append(altName.get(0)); stringBuilder.append(altName.get(0));
si.append("] "); stringBuilder.append("] ");
si.append(name); stringBuilder.append(name);
si.append("\n"); stringBuilder.append("\n");
} }
} }
} }
} }
catch(CertificateParsingException e) { catch(CertificateParsingException e) {
e.printStackTrace(); e.printStackTrace();
si.append("<Parsing error: "); stringBuilder.append("<Parsing error: ");
si.append(e.getLocalizedMessage()); stringBuilder.append(e.getLocalizedMessage());
si.append(">\n"); stringBuilder.append(">\n");
} }
si.append("\n"); stringBuilder.append("\n");
si.append(context.getString(R.string.mtm_connect_anyway)); stringBuilder.append(context.getString(R.string.mtm_connect_anyway));
si.append("\n\n"); stringBuilder.append("\n\n");
si.append(context.getString(R.string.mtm_cert_details)); stringBuilder.append(context.getString(R.string.mtm_cert_details));
certDetails(si, cert); certDetails(stringBuilder, cert);
return si.toString(); return stringBuilder.toString();
} }
/** /**
@ -584,29 +544,24 @@ public class MemorizingTrustManager implements X509TrustManager {
} }
} }
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void startActivityNotification(Intent intent, int decisionId, String certName) { private void startActivityNotification(Intent intent, int decisionId, String certName) {
final PendingIntent call = PendingIntent.getActivity(context, 0, intent, 0); final PendingIntent call = PendingIntent.getActivity(context, 0, intent, 0);
final String mtmNotification = context.getString(R.string.mtm_notification); final String mtmNotification = context.getString(R.string.mtm_notification);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "ssl").setSmallIcon(android.R.drawable.ic_lock_lock).setContentTitle(mtmNotification).setContentText(certName).setTicker(certName).setContentIntent(call).setAutoCancel(true).setPriority(NotificationCompat.PRIORITY_HIGH); NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "ssl")
.setSmallIcon(android.R.drawable.ic_lock_lock)
.setContentTitle(mtmNotification)
.setContentText(certName)
.setTicker(certName)
.setContentIntent(call)
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_HIGH);
notificationManager.notify(NOTIFICATION_ID + decisionId, builder.build()); notificationManager.notify(NOTIFICATION_ID + decisionId, builder.build());
} }
/**
* Returns the top-most entry of the activity stack.
*
* @return the Context of the currently bound UI or the master context if none is bound
*/
Context getUI() {
return (foregroundAct != null) ? foregroundAct : context;
}
private int interact(final String message, final int titleId) { private int interact(final String message, final int titleId) {
/* prepare the MTMDecision blocker object */
MTMDecision choice = new MTMDecision(); MTMDecision choice = new MTMDecision();
final int myId = createDecisionId(choice); final int myId = createDecisionId(choice);
@ -615,19 +570,14 @@ public class MemorizingTrustManager implements X509TrustManager {
public void run() { public void run() {
Intent intent = new Intent(context, MemorizingActivity.class); Intent intent = new Intent(context, MemorizingActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setData(Uri.parse(MemorizingTrustManager.class.getName() + "/" + myId)); intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
intent.putExtra(DECISION_INTENT_ID, myId); intent.putExtra("DECISION_INTENT_ID", myId);
intent.putExtra(DECISION_INTENT_CERT, message); intent.putExtra("DECISION_INTENT_CERT", message);
intent.putExtra(DECISION_TITLE_ID, titleId); intent.putExtra("DECISION_TITLE_ID", titleId);
// we try to directly start the activity and fall back to
// making a notification. If no foreground activity is set
// (foregroundAct==null) or if the app developer set an
// invalid / expired activity, the catch-all fallback is
// deployed.
try { try {
foregroundAct.startActivity(intent); context.startActivity(intent);
} }
catch(Exception e) { catch(Exception e) {
startActivityNotification(intent, myId, message); startActivityNotification(intent, myId, message);

View File

@ -92,7 +92,7 @@
<string name="urlInfoTooltip">1- Choose the correct protocol(https or http). \n2- Enter Gitea url e.g: try.gitea.io. \n3- If you have enabled 2FA for your account, enter the code in the OTP Code field. \n4- For HTTP basic auth use USERNAME@DOMAIN.COM in the URL field.</string> <string name="urlInfoTooltip">1- Choose the correct protocol(https or http). \n2- Enter Gitea url e.g: try.gitea.io. \n3- If you have enabled 2FA for your account, enter the code in the OTP Code field. \n4- For HTTP basic auth use USERNAME@DOMAIN.COM in the URL field.</string>
<string name="loginFailed">Wrong username/password</string> <string name="loginFailed">Wrong username/password</string>
<string name="protocolDelimiter" translatable="false">://</string> <string name="protocolDelimiter" translatable="false">://</string>
<string name="malformedUrl">Could'nt connect to host. Please check your URL or port for any errors.</string> <string name="malformedUrl">Could\'nt connect to host. Please check your URL or port for any errors.</string>
<string name="protocolError">It is not recommended to use HTTP protocol unless you are testing on local network.</string> <string name="protocolError">It is not recommended to use HTTP protocol unless you are testing on local network.</string>
<string name="malformedJson">Malformed JSON was received. Server response was not successful.</string> <string name="malformedJson">Malformed JSON was received. Server response was not successful.</string>
<string name="emptyFieldURL">Instance URL is required</string> <string name="emptyFieldURL">Instance URL is required</string>