/*
* Copyright (c) 2014 The MITRE Corporation, All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this work except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mitre.svmp.net;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import com.google.PRNGFixes;
import de.duenndns.ssl.MemorizingTrustManager;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.mitre.svmp.auth.SVMPKeyManager;
import org.mitre.svmp.auth.module.CertificateModule;
import org.mitre.svmp.client.R;
import org.mitre.svmp.common.ConnectionInfo;
import org.mitre.svmp.common.Constants;
import org.mitre.svmp.common.Utility;
import javax.net.ssl.*;
import java.io.IOException;
import java.io.InputStream;
import java.security.*;
import java.security.cert.CertificateException;
/**
* @author Joe Portner, Dave Keppler
*/
public class SSLConfig implements Constants {
private static final String TAG = SSLConfig.class.getName();
private ConnectionInfo connectionInfo;
private Activity activity;
private Context context;
private SSLContext sslContext;
private MemorizingTrustManager mtm;
public SSLConfig(ConnectionInfo connectionInfo, Activity activity) {
this.connectionInfo = connectionInfo;
this.activity = activity;
this.context = activity.getApplicationContext();
}
// returns 0 if successful, otherwise returns resId for an error message
public int configure() {
// default return value is a generic SSL-related error
int value = R.string.appRTC_toast_socketConnector_failSSL;
// TODO: specific error messages?
try {
doConfigure();
value = 0;
} catch (KeyStoreException e) {
Log.e(TAG, "SSLConfig failed:", e);
} catch (NoSuchAlgorithmException e) {
Log.e(TAG, "SSLConfig failed:", e);
} catch (CertificateException e) {
Log.e(TAG, "SSLConfig failed:", e);
} catch (IOException e) {
Log.e(TAG, "SSLConfig failed:", e);
} catch (KeyManagementException e) {
Log.e(TAG, "SSLConfig failed:", e);
}
return value;
}
// only get SSLContext for Socket after it has been configured
public SSLContext getSSLContext() {
return sslContext;
}
// only get Apache SSLSocketFactory for HttpClient after it has been configured
public SSLSocketFactory getSocketFactory() {
SSLSocketFactory factory = null;
try {
factory = new SvmpSSLSocketFactory(sslContext, ENABLED_CIPHERS, ENABLED_PROTOCOLS);
} catch (NoSuchAlgorithmException e) {
Log.e(TAG, "getSocketFactory failed:", e);
} catch (KeyManagementException e) {
Log.e(TAG, "getSocketFactory failed:", e);
} catch (KeyStoreException e) {
Log.e(TAG, "getSocketFactory failed:", e);
} catch (UnrecoverableKeyException e) {
Log.e(TAG, "getSocketFactory failed:", e);
}
return factory;
}
@SuppressLint("TrulyRandom")
private void doConfigure() throws KeyStoreException, CertificateException,
NoSuchAlgorithmException, IOException, KeyManagementException {
// find out if we should use the MemorizingTrustManager instead of the system trust store (set in Preferences)
boolean useMTM = Utility.getPrefBool(context,
R.string.preferenceKey_connection_useMTM,
R.string.preferenceValue_connection_useMTM);
// determine whether we should use client certificate authentication
boolean useCertificateAuth = Constants.API_14 &&
(connectionInfo.getAuthType() & CertificateModule.AUTH_MODULE_ID) == CertificateModule.AUTH_MODULE_ID;
// set up key managers
KeyManager[] keyManagers = null;
// if certificate authentication is enabled, use a key manager with the provided alias
if (useCertificateAuth) {
keyManagers = new KeyManager[]{new SVMPKeyManager(context, connectionInfo.getCertificateAlias())};
}
// set up trust managers
TrustManager[] trustManagers = null;
KeyStore localTrustStore = KeyStore.getInstance("BKS");
InputStream in = context.getResources().openRawResource(R.raw.client_truststore);
localTrustStore.load(in, Constants.TRUSTSTORE_PASSWORD.toCharArray());
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(localTrustStore);
// 1) If "res/raw/client_truststore.bks" is not empty, use it as the pinned cert trust store (default is empty)
// 2) Otherwise, if the "Show certificate dialog" developer preference is enabled, use that (default is disabled)
// 3) Otherwise, use the default system trust store, consists of normal trusted Android CA certs
if (localTrustStore.size() > 0) {
// this means that "res/raw/client_truststore.bks" has been replaced with a trust store that is not empty
// we will use that "pinned" store to check server certificate trust
Log.d(TAG, "SSLConfig: Using static BKS trust store to check server cert trust");
trustManagers = trustManagerFactory.getTrustManagers();
// After switching to WebSockets, MTM causes the app to freeze; removed for now
} else if (useMTM) {
// by default useMTM is false ("Show certificate dialog" in developer preferences)
// this creates a certificate dialog to decide what to do with untrusted certificates, instead of flat-out rejecting them
Log.d(TAG, "SSLConfig: Static BKS trust store is empty but MTM is enabled, using MTM to check server cert trust");
mtm = new MemorizingTrustManager(context);
mtm.bindDisplayActivity(activity);
trustManagers = new X509TrustManager[] {mtm};
} else {
Log.d(TAG, "SSLConfig: Static BKS trust store is empty and MTM is disabled, using system trust store to check server cert trust");
// leaving trustManagers null accomplishes this
}
PRNGFixes.apply(); // fix Android SecureRandom issue on pre-KitKat platforms
sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers, trustManagers, new SecureRandom());
}
}