/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.eas.client.threetier;
import com.eas.client.threetier.requests.ExceptionResponse;
import com.eas.client.threetier.platypus.PlatypusPlatypusConnection;
import com.eas.client.AppConnection;
import com.eas.client.ClientConstants;
import com.eas.client.login.Credentials;
import com.eas.client.threetier.requests.AccessControlExceptionResponse;
import com.eas.client.threetier.requests.JsonExceptionResponse;
import com.eas.client.threetier.requests.SqlExceptionResponse;
import com.eas.script.JsObjectException;
import com.eas.script.Scripts;
import com.eas.util.BinaryUtils;
import com.eas.util.StringUtils;
import java.awt.EventQueue;
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.OutputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.AccessControlException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ResourceBundle;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import javax.security.auth.AuthPermission;
import javax.swing.JOptionPane;
/**
*
* @author kl, mg refactoring
*/
public abstract class PlatypusConnection implements AppConnection {
public static final ResourceBundle clientLocalizations = ResourceBundle.getBundle("com/eas/client/threetier/clientlocalizations");
// local disk paths
public static final String SECURITY_SUBDIRECTORY = "security";
// SSL defaults
public static final String DEFAULT_KEYSTORE_PASSWORD = "keyword";
public static final String DEFAULT_TRUSTSTORE_PASSWORD = "trustword";
public static final String ACCEPTED_CERTSALIAS_PREFIX = "acceptedFromServers";
// Random number generator algorithm
public static final String DEFAULT_SECURE_RANDOM_ALGORITHM = "SHA1PRNG";
// Certificated storage type
public static final String DEFAULT_CETRS_STORE_TYPE = "JKS";
// Ssl protocol
public static final String DEFAULT_SSL_PROTOCOL = "TLS";
//
public static final String DEFAULT_TRUST_ALGORITHM = "PKIX";
// error messages
public static final String ACCESSCONTROL_EXCEPTION_LOG_MSG = "AccessControlException from server. {0}";
public static final String SQL_EXCEPTION_LOG_MSG = "SQLException from server. Message: {0}. SqlState: {1}. SqlError code: {2}.";
public static final String KEYSTORE_MISING_MSG = "Can't locate key store file. May be bad installation.";
public static final String TRUSTSTORE_MISSING_MSG = "Can't locate trust store file. May be bad installation.";
// misc
public static final int DEFAULT_MAX_THREADS = 25;
protected final URL url;
protected final String sourcePath;
protected Credentials credentials;
protected Callable<Credentials> onCredentials;
protected int maximumAuthenticateAttempts = 1;
protected static class Attempts {
public Attempts() {
super();
}
public int count;
}
public PlatypusConnection(URL aUrl, String aSourcePath, Callable<Credentials> aOnCredentials, int aMaximumAuthenticateAttempts) {
super();
url = aUrl;
sourcePath = aSourcePath;
onCredentials = aOnCredentials;
maximumAuthenticateAttempts = Math.max(1, aMaximumAuthenticateAttempts);
}
public URL getUrl() {
return url;
}
public Exception handleErrorResponse(ExceptionResponse aResponse, Scripts.Space aSpace) {
if (aResponse instanceof SqlExceptionResponse) {
SqlExceptionResponse errorResponse = (SqlExceptionResponse) aResponse;
Logger.getLogger(PlatypusPlatypusConnection.class.getName()).log(Level.WARNING, SQL_EXCEPTION_LOG_MSG, new Object[]{aResponse.getErrorMessage(), errorResponse.getSqlState(), errorResponse.getSqlErrorCode()});
return new SQLException(aResponse.getErrorMessage(), errorResponse.getSqlState(), errorResponse.getSqlErrorCode());
} else if (aResponse instanceof AccessControlExceptionResponse) {
AccessControlExceptionResponse errorResponse = (AccessControlExceptionResponse) aResponse;
Logger.getLogger(PlatypusPlatypusConnection.class.getName()).log(Level.WARNING, ACCESSCONTROL_EXCEPTION_LOG_MSG, new Object[]{errorResponse.getErrorMessage()});
return new AccessControlException(errorResponse.getErrorMessage(), errorResponse.isNotLoggedIn() ? new AuthPermission("*") : null);
} else if (aResponse instanceof JsonExceptionResponse) {
JsonExceptionResponse errorResponse = (JsonExceptionResponse) aResponse;
return new JsObjectException(aSpace.parseJsonWithDates(errorResponse.getJsonContent()));
} else {
String msg = "Exception from server. " + aResponse.getErrorMessage();
Logger.getLogger(PlatypusConnection.class.getName()).log(Level.WARNING, msg);
return new Exception(msg);
}
}
protected static KeyManager[] createKeyManagers() throws NoSuchAlgorithmException, NoSuchProviderException, KeyStoreException, FileNotFoundException, IOException, CertificateException, UnrecoverableKeyException, URISyntaxException {
KeyStore ks = KeyStore.getInstance(DEFAULT_CETRS_STORE_TYPE);
// get user password and file input stream
char[] password = DEFAULT_KEYSTORE_PASSWORD.toCharArray();
File keyStoreFile = new File(StringUtils.join(File.separator, System.getProperty(ClientConstants.USER_HOME_PROP_NAME), ClientConstants.USER_HOME_PLATYPUS_DIRECTORY_NAME, SECURITY_SUBDIRECTORY, "keystore"));
if (!keyStoreFile.exists()) {
File keyPath = new File(StringUtils.join(File.separator, System.getProperty(ClientConstants.USER_HOME_PROP_NAME), ClientConstants.USER_HOME_PLATYPUS_DIRECTORY_NAME, SECURITY_SUBDIRECTORY));
keyPath.mkdirs();
keyStoreFile.createNewFile();
try (OutputStream keyOut = new FileOutputStream(keyStoreFile); InputStream keyIn = PlatypusConnection.class.getResourceAsStream("defaultKeystore")) {
byte[] resData = BinaryUtils.readStream(keyIn, -1);
keyOut.write(resData);
}
}
if (keyStoreFile.exists()) {
try (InputStream is = new FileInputStream(keyStoreFile)) {
ks.load(is, password);
}
final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
keyManagerFactory.init(ks, password);
return keyManagerFactory.getKeyManagers();
} else {
throw new FileNotFoundException(KEYSTORE_MISING_MSG);
}
}
protected static TrustManager[] createTrustManagers() throws NoSuchAlgorithmException, NoSuchProviderException, KeyStoreException, FileNotFoundException, IOException, CertificateException, URISyntaxException {
KeyStore ks = KeyStore.getInstance(DEFAULT_CETRS_STORE_TYPE);
char[] password = getTrustStorePassword();
File trustStore = new File(StringUtils.join(File.separator, System.getProperty(ClientConstants.USER_HOME_PROP_NAME), ClientConstants.USER_HOME_PLATYPUS_DIRECTORY_NAME, SECURITY_SUBDIRECTORY, "truststore"));
if (!trustStore.exists()) {
File trustPath = new File(StringUtils.join(File.separator, System.getProperty(ClientConstants.USER_HOME_PROP_NAME), ClientConstants.USER_HOME_PLATYPUS_DIRECTORY_NAME, SECURITY_SUBDIRECTORY));
trustPath.mkdirs();
trustStore.createNewFile();
try (OutputStream trustOut = new FileOutputStream(trustStore); InputStream trustIn = PlatypusConnection.class.getResourceAsStream("emptyTruststore")) {
byte[] resData = BinaryUtils.readStream(trustIn, -1);
trustOut.write(resData);
}
}
if (trustStore.exists()) {
try (InputStream is = new FileInputStream(trustStore)) {
ks.load(is, password);
}
final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(DEFAULT_TRUST_ALGORITHM);
trustManagerFactory.init(ks);
return wrapTrustManagers(ks, trustManagerFactory.getTrustManagers());
} else {
throw new FileNotFoundException(TRUSTSTORE_MISSING_MSG);
}
}
public static SSLContext createSSLContext() throws NoSuchAlgorithmException, KeyManagementException, NoSuchProviderException, KeyStoreException, FileNotFoundException, IOException, CertificateException, UnrecoverableKeyException, URISyntaxException {
//return SSLContext.getDefault();
SSLContext context = SSLContext.getInstance(DEFAULT_SSL_PROTOCOL);
context.init(createKeyManagers(), createTrustManagers(), SecureRandom.getInstance(DEFAULT_SECURE_RANDOM_ALGORITHM));
return context;
}
private static TrustManager[] wrapTrustManagers(KeyStore aKeyStore, TrustManager[] aTrustManagers) {
return new TrustManager[]{new PlatypusTrustManager(aKeyStore, aTrustManagers)};
}
private static char[] getTrustStorePassword() {
char[] password = DEFAULT_TRUSTSTORE_PASSWORD.toCharArray();
return password;
}
private static class PlatypusTrustManager implements X509TrustManager {
private static class Choice {
public int choice;
}
protected KeyStore keyStore;
protected TrustManager[] defaultTrustManagers;
public PlatypusTrustManager(KeyStore aKeyStore, TrustManager[] aDefaultTrustManagers) {
super();
keyStore = aKeyStore;
defaultTrustManagers = aDefaultTrustManagers;
}
@Override
public void checkClientTrusted(X509Certificate[] aCertsChain, String aAlgotithm) throws CertificateException {
throw new UnsupportedOperationException("This client implementation can only check if server is trusted.");
}
@Override
public synchronized void checkServerTrusted(X509Certificate[] aCertsChain, String aAlgotithm) throws CertificateException {
try {
for (TrustManager tm : defaultTrustManagers) {
if (tm instanceof X509TrustManager) {
((X509TrustManager) tm).checkServerTrusted(aCertsChain, aAlgotithm);
}
}
} catch (Exception ex) {
Callable<Integer> uiChoicer = () -> {
return JOptionPane.showOptionDialog(
null,
clientLocalizations.getString("ssl.server.certificate.bad"),
clientLocalizations.getString("ssl.dialog.title"),
JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.WARNING_MESSAGE,
null,
new Object[]{
clientLocalizations.getString("ssl.server.certificate.accept"),
clientLocalizations.getString("ssl.server.certificate.acceptsave"),
clientLocalizations.getString("ssl.server.certificate.reject")
},
clientLocalizations.getString("ssl.server.certificate.accept"));
};
int userChoice = JOptionPane.CANCEL_OPTION;
try {
if (EventQueue.isDispatchThread()) {
userChoice = uiChoicer.call();
} else {
Choice ch = new Choice();
ch.choice = userChoice;
EventQueue.invokeAndWait(() -> {
try {
ch.choice = uiChoicer.call();
} catch (Exception _ex) {
// no op
}
});
userChoice = ch.choice;
}
} catch (Exception _ex) {
// no op
}
if (userChoice == JOptionPane.CANCEL_OPTION) {// Reject
throw ex;
} else {
try {
for (X509Certificate aCertsChain1 : aCertsChain) {
String alias = generateUniqueAlias(keyStore);
keyStore.setCertificateEntry(alias, aCertsChain1);
}
if (userChoice == JOptionPane.NO_OPTION) {
// Save accepted
char[] password = getTrustStorePassword();
File trustStore = new File(StringUtils.join(File.separator, System.getProperty(ClientConstants.USER_HOME_PROP_NAME), ClientConstants.USER_HOME_PLATYPUS_DIRECTORY_NAME, SECURITY_SUBDIRECTORY, "truststore"));
if (trustStore.exists()) {
try (FileOutputStream out = new FileOutputStream(trustStore)) {
keyStore.store(out, password);
}
}
}//else if(userChoice == JOptionPane.YES_OPTION)// Accept is no op. Nothing to save and nothing to throw
final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(DEFAULT_TRUST_ALGORITHM);
trustManagerFactory.init(keyStore);
defaultTrustManagers = trustManagerFactory.getTrustManagers();
} catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException ex1) {
throw new CertificateException(ex1);
}
}
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
List<X509Certificate> accepted = new ArrayList<>();
for (TrustManager tm : defaultTrustManagers) {
if (tm instanceof X509TrustManager) {
X509Certificate[] laccepted = ((X509TrustManager) tm).getAcceptedIssuers();
accepted.addAll(Arrays.asList(laccepted));
}
}
return accepted.toArray(new X509Certificate[0]);
}
private String generateUniqueAlias(KeyStore aKeyStore) throws KeyStoreException {
int nameCounter = 0;
String generated = ACCEPTED_CERTSALIAS_PREFIX;
while (aKeyStore.containsAlias(generated)) {
nameCounter++;
generated = ACCEPTED_CERTSALIAS_PREFIX + nameCounter;
}
return generated;
}
}
}