add support for RFC7711 to MTM
This commit is contained in:
parent
1e7b4030bb
commit
cbc9c1fb20
|
@ -7,14 +7,14 @@ buildscript {
|
|||
}
|
||||
}
|
||||
|
||||
apply plugin: 'android-library'
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 19
|
||||
buildToolsVersion "19.1"
|
||||
compileSdkVersion 24
|
||||
buildToolsVersion "23.0.3"
|
||||
defaultConfig {
|
||||
minSdkVersion 7
|
||||
targetSdkVersion 19
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 24
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
|
|
|
@ -35,15 +35,32 @@ import android.app.PendingIntent;
|
|||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.os.Handler;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.*;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.text.SimpleDateFormat;
|
||||
|
@ -53,6 +70,7 @@ import java.util.List;
|
|||
import java.util.Locale;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
|
@ -68,7 +86,7 @@ import javax.net.ssl.X509TrustManager;
|
|||
* <b>WARNING:</b> This only works if a dedicated thread is used for
|
||||
* opening sockets!
|
||||
*/
|
||||
public class MemorizingTrustManager implements X509TrustManager {
|
||||
public class MemorizingTrustManager {
|
||||
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";
|
||||
|
@ -94,6 +112,7 @@ public class MemorizingTrustManager implements X509TrustManager {
|
|||
private KeyStore appKeyStore;
|
||||
private X509TrustManager defaultTrustManager;
|
||||
private X509TrustManager appTrustManager;
|
||||
private String poshCacheDir;
|
||||
|
||||
/** Creates an instance of the MemorizingTrustManager class that falls back to a custom TrustManager.
|
||||
*
|
||||
|
@ -149,28 +168,11 @@ public class MemorizingTrustManager implements X509TrustManager {
|
|||
File dir = app.getDir(KEYSTORE_DIR, Context.MODE_PRIVATE);
|
||||
keyStoreFile = new File(dir + File.separator + KEYSTORE_FILE);
|
||||
|
||||
poshCacheDir = app.getFilesDir().getAbsolutePath()+"/posh_cache/";
|
||||
|
||||
appKeyStore = loadAppKeyStore();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a X509TrustManager list containing a new instance of
|
||||
* TrustManagerFactory.
|
||||
*
|
||||
* This function is meant for convenience only. You can use it
|
||||
* as follows to integrate TrustManagerFactory for HTTPS sockets:
|
||||
*
|
||||
* <pre>
|
||||
* SSLContext sc = SSLContext.getInstance("TLS");
|
||||
* sc.init(null, MemorizingTrustManager.getInstanceList(this),
|
||||
* new java.security.SecureRandom());
|
||||
* HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
|
||||
* </pre>
|
||||
* @param c Activity or Service to show the Dialog / Notification
|
||||
*/
|
||||
public static X509TrustManager[] getInstanceList(Context c) {
|
||||
return new X509TrustManager[] { new MemorizingTrustManager(c) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds an Activity to the MTM for displaying the query dialog.
|
||||
|
@ -389,7 +391,7 @@ public class MemorizingTrustManager implements X509TrustManager {
|
|||
return false;
|
||||
}
|
||||
|
||||
public void checkCertTrusted(X509Certificate[] chain, String authType, boolean isServer, boolean interactive)
|
||||
public void checkCertTrusted(X509Certificate[] chain, String authType, String domain, boolean isServer, boolean interactive)
|
||||
throws CertificateException
|
||||
{
|
||||
LOGGER.log(Level.FINE, "checkCertTrusted(" + chain + ", " + authType + ", " + isServer + ")");
|
||||
|
@ -419,6 +421,14 @@ public class MemorizingTrustManager implements X509TrustManager {
|
|||
else
|
||||
defaultTrustManager.checkClientTrusted(chain, authType);
|
||||
} catch (CertificateException e) {
|
||||
if (domain != null && isServer) {
|
||||
String hash = getBase64Hash(chain[0],"SHA-256");
|
||||
List<String> fingerprints = getPoshFingerprints(domain);
|
||||
if (hash != null && fingerprints.contains(hash)) {
|
||||
Log.d("mtm","trusted cert fingerprint of "+domain+" via posh");
|
||||
return;
|
||||
}
|
||||
}
|
||||
e.printStackTrace();
|
||||
if (interactive) {
|
||||
interactCert(chain, authType, e);
|
||||
|
@ -429,20 +439,121 @@ public class MemorizingTrustManager implements X509TrustManager {
|
|||
}
|
||||
}
|
||||
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType)
|
||||
throws CertificateException
|
||||
{
|
||||
checkCertTrusted(chain, authType, false,true);
|
||||
private List<String> getPoshFingerprints(String domain) {
|
||||
List<String> cached = getPoshFingerprintsFromCache(domain);
|
||||
if (cached == null) {
|
||||
return getPoshFingerprintsFromServer(domain);
|
||||
} else {
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType)
|
||||
throws CertificateException
|
||||
{
|
||||
checkCertTrusted(chain, authType, true,true);
|
||||
private List<String> getPoshFingerprintsFromServer(String domain) {
|
||||
try {
|
||||
List<String> results = new ArrayList<>();
|
||||
URL url = new URL("https://"+domain+"/.well-known/posh/xmpp-client.json");
|
||||
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
||||
String inputLine;
|
||||
StringBuilder builder = new StringBuilder();
|
||||
while ((inputLine = in.readLine()) != null) {
|
||||
builder.append(inputLine);
|
||||
}
|
||||
JSONObject jsonObject = new JSONObject(builder.toString());
|
||||
in.close();
|
||||
JSONArray fingerprints = jsonObject.getJSONArray("fingerprints");
|
||||
for(int i = 0; i < fingerprints.length(); i++) {
|
||||
JSONObject fingerprint = fingerprints.getJSONObject(i);
|
||||
String sha256 = fingerprint.getString("sha-256");
|
||||
if (sha256 != null) {
|
||||
results.add(sha256);
|
||||
}
|
||||
}
|
||||
int expires = jsonObject.getInt("expires");
|
||||
if (expires <= 0) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
in.close();
|
||||
writeFingerprintsToCache(domain, results,1000L * expires+System.currentTimeMillis());
|
||||
return results;
|
||||
} catch (Exception e) {
|
||||
Log.d("mtm","error fetching posh "+e.getMessage());
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
public X509Certificate[] getAcceptedIssuers()
|
||||
{
|
||||
private File getPoshCacheFile(String domain) {
|
||||
return new File(poshCacheDir+domain+".json");
|
||||
}
|
||||
|
||||
private void writeFingerprintsToCache(String domain, List<String> results, long expires) {
|
||||
File file = getPoshCacheFile(domain);
|
||||
file.getParentFile().mkdirs();
|
||||
try {
|
||||
file.createNewFile();
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("expires",expires);
|
||||
jsonObject.put("fingerprints",new JSONArray(results));
|
||||
FileOutputStream outputStream = new FileOutputStream(file);
|
||||
outputStream.write(jsonObject.toString().getBytes());
|
||||
outputStream.flush();
|
||||
outputStream.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> getPoshFingerprintsFromCache(String domain) {
|
||||
File file = getPoshCacheFile(domain);
|
||||
try {
|
||||
InputStream is = new FileInputStream(file);
|
||||
BufferedReader buf = new BufferedReader(new InputStreamReader(is));
|
||||
|
||||
String line = buf.readLine();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
while(line != null){
|
||||
sb.append(line).append("\n");
|
||||
line = buf.readLine();
|
||||
}
|
||||
JSONObject jsonObject = new JSONObject(sb.toString());
|
||||
is.close();
|
||||
long expires = jsonObject.getLong("expires");
|
||||
long expiresIn = expires - System.currentTimeMillis();
|
||||
if (expiresIn < 0) {
|
||||
file.delete();
|
||||
return null;
|
||||
} else {
|
||||
Log.d("mtm","posh fingerprints expire in "+(expiresIn/1000)+"s");
|
||||
}
|
||||
List<String> result = new ArrayList<>();
|
||||
JSONArray jsonArray = jsonObject.getJSONArray("fingerprints");
|
||||
for(int i = 0; i < jsonArray.length(); ++i) {
|
||||
result.add(jsonArray.getString(i));
|
||||
}
|
||||
return result;
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
} catch (JSONException e) {
|
||||
file.delete();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getBase64Hash(X509Certificate certificate, String digest) throws CertificateEncodingException {
|
||||
MessageDigest md;
|
||||
try {
|
||||
md = MessageDigest.getInstance(digest);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
return null;
|
||||
}
|
||||
md.update(certificate.getEncoded());
|
||||
return Base64.encodeToString(md.digest(),Base64.NO_WRAP);
|
||||
}
|
||||
|
||||
private X509Certificate[] getAcceptedIssuers() {
|
||||
LOGGER.log(Level.FINE, "getAcceptedIssuers()");
|
||||
return defaultTrustManager.getAcceptedIssuers();
|
||||
}
|
||||
|
@ -553,22 +664,6 @@ public class MemorizingTrustManager implements X509TrustManager {
|
|||
certDetails(si, cert);
|
||||
return si.toString();
|
||||
}
|
||||
|
||||
// We can use Notification.Builder once MTM's minSDK is >= 11
|
||||
@SuppressWarnings("deprecation")
|
||||
void startActivityNotification(Intent intent, int decisionId, String certName) {
|
||||
Notification n = new Notification(android.R.drawable.ic_lock_lock,
|
||||
master.getString(R.string.mtm_notification),
|
||||
System.currentTimeMillis());
|
||||
PendingIntent call = PendingIntent.getActivity(master, 0, intent, 0);
|
||||
n.setLatestEventInfo(master.getApplicationContext(),
|
||||
master.getString(R.string.mtm_notification),
|
||||
certName, call);
|
||||
n.flags |= Notification.FLAG_AUTO_CANCEL;
|
||||
|
||||
notificationManager.notify(NOTIFICATION_ID + decisionId, n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the top-most entry of the activity stack.
|
||||
*
|
||||
|
@ -598,7 +693,6 @@ public class MemorizingTrustManager implements X509TrustManager {
|
|||
getUI().startActivity(ni);
|
||||
} catch (Exception e) {
|
||||
LOGGER.log(Level.FINE, "startActivity(MemorizingActivity)", e);
|
||||
startActivityNotification(ni, myId, message);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -708,22 +802,39 @@ public class MemorizingTrustManager implements X509TrustManager {
|
|||
|
||||
}
|
||||
|
||||
public X509TrustManager getNonInteractive(String domain) {
|
||||
return new NonInteractiveMemorizingTrustManager(domain);
|
||||
}
|
||||
|
||||
public X509TrustManager getInteractive(String domain) {
|
||||
return new InteractiveMemorizingTrustManager(domain);
|
||||
}
|
||||
|
||||
public X509TrustManager getNonInteractive() {
|
||||
return new NonInteractiveMemorizingTrustManager();
|
||||
return new NonInteractiveMemorizingTrustManager(null);
|
||||
}
|
||||
|
||||
public X509TrustManager getInteractive() {
|
||||
return new InteractiveMemorizingTrustManager(null);
|
||||
}
|
||||
|
||||
private class NonInteractiveMemorizingTrustManager implements X509TrustManager {
|
||||
|
||||
private final String domain;
|
||||
|
||||
public NonInteractiveMemorizingTrustManager(String domain) {
|
||||
this.domain = domain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType)
|
||||
throws CertificateException {
|
||||
MemorizingTrustManager.this.checkCertTrusted(chain, authType, false, false);
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
||||
MemorizingTrustManager.this.checkCertTrusted(chain, authType, domain, false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType)
|
||||
throws CertificateException {
|
||||
MemorizingTrustManager.this.checkCertTrusted(chain, authType, true, false);
|
||||
MemorizingTrustManager.this.checkCertTrusted(chain, authType, domain, true, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -732,4 +843,28 @@ public class MemorizingTrustManager implements X509TrustManager {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
private class InteractiveMemorizingTrustManager implements X509TrustManager {
|
||||
private final String domain;
|
||||
|
||||
public InteractiveMemorizingTrustManager(String domain) {
|
||||
this.domain = domain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
||||
MemorizingTrustManager.this.checkCertTrusted(chain, authType, domain, false, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType)
|
||||
throws CertificateException {
|
||||
MemorizingTrustManager.this.checkCertTrusted(chain, authType, domain, true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return MemorizingTrustManager.this.getAcceptedIssuers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ public class HttpConnectionManager extends AbstractConnectionManager {
|
|||
final X509TrustManager trustManager;
|
||||
final HostnameVerifier hostnameVerifier;
|
||||
if (interactive) {
|
||||
trustManager = mXmppConnectionService.getMemorizingTrustManager();
|
||||
trustManager = mXmppConnectionService.getMemorizingTrustManager().getInteractive();
|
||||
hostnameVerifier = mXmppConnectionService
|
||||
.getMemorizingTrustManager().wrapHostnameVerifier(
|
||||
new StrictHostnameVerifier());
|
||||
|
|
|
@ -1666,7 +1666,7 @@ public class XmppConnectionService extends Service {
|
|||
callback.onAccountCreated(account);
|
||||
if (Config.X509_VERIFICATION) {
|
||||
try {
|
||||
getMemorizingTrustManager().getNonInteractive().checkClientTrusted(chain, "RSA");
|
||||
getMemorizingTrustManager().getNonInteractive(account.getJid().getDomainpart()).checkClientTrusted(chain, "RSA");
|
||||
} catch (CertificateException e) {
|
||||
callback.informUser(R.string.certificate_chain_is_not_trusted);
|
||||
}
|
||||
|
@ -1694,7 +1694,7 @@ public class XmppConnectionService extends Service {
|
|||
databaseBackend.updateAccount(account);
|
||||
if (Config.X509_VERIFICATION) {
|
||||
try {
|
||||
getMemorizingTrustManager().getNonInteractive().checkClientTrusted(chain, "RSA");
|
||||
getMemorizingTrustManager().getNonInteractive(account.getJid().getDomainpart()).checkClientTrusted(chain, "RSA");
|
||||
} catch (CertificateException e) {
|
||||
showErrorToastInUi(R.string.certificate_chain_is_not_trusted);
|
||||
}
|
||||
|
|
|
@ -495,7 +495,8 @@ public class XmppConnection implements Runnable {
|
|||
} else {
|
||||
keyManager = null;
|
||||
}
|
||||
sc.init(keyManager, new X509TrustManager[]{mInteractive ? trustManager : trustManager.getNonInteractive()}, mXmppConnectionService.getRNG());
|
||||
String domain = account.getJid().getDomainpart();
|
||||
sc.init(keyManager, new X509TrustManager[]{mInteractive ? trustManager.getInteractive(domain) : trustManager.getNonInteractive(domain)}, mXmppConnectionService.getRNG());
|
||||
final SSLSocketFactory factory = sc.getSocketFactory();
|
||||
final HostnameVerifier verifier;
|
||||
if (mInteractive) {
|
||||
|
|
Loading…
Reference in a new issue