All Downloads are FREE. Search and download functionalities are using the official Maven repository.

es.ree.eemws.kit.cmd.trustserver.Main Maven / Gradle / Ivy

Go to download

Client implementation of IEC 62325-504 technical specification. eemws-kit includes command line utilities to invoke the eem web services, as well as several GUI applications (browser, editor, ...)

The newest version!
/*
 * Copyright 2024 Redeia.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published
 *  by the Free Software Foundation, version 3 of the license.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTIBIILTY or FITNESS FOR A PARTICULAR PURPOSE. See GNU Lesser General
 * Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program. If not, see
 * http://www.gnu.org/licenses/.
 *
 * Any redistribution and/or modification of this program has to make
 * reference to Redeia as the copyright owner of the program.
 */

package es.ree.eemws.kit.cmd.trustserver;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

import es.ree.eemws.client.get.GetMessage;
import es.ree.eemws.client.list.ListMessages;
import es.ree.eemws.core.utils.config.ConfigException;
import es.ree.eemws.core.utils.operations.get.GetOperationException;
import es.ree.eemws.core.utils.operations.list.ListOperationException;
import es.ree.eemws.core.utils.security.X509Util;
import es.ree.eemws.kit.cmd.MessageCatalog;
import es.ree.eemws.kit.cmd.ParentMain;
import jakarta.xml.ws.WebServiceException;

/**
 * Adds the given server to the list of trusted servers.
 *
 * @author Redeia.
 * @version 2.1 01/01/2024
 */
public final class Main extends ParentMain {

	/** Sets text for parameter url. */
	private static final String PARAMETER_URL = MessageCatalog.PARAMETER_URL.getMessage();

	/** Log messages. */
	private static final Logger LOGGER = Logger.getLogger("trustserver"); //$NON-NLS-1$

	/** Default https port. */
	private static final int DEFAULT_HTTPS_PORT = 443;

	/** Https protocol string. */
	private static final String HTTPS_PROTOCOL = "https"; //$NON-NLS-1$

	/** TSL protocol string. */
	private static final String TLS_PROTOCOL = "TLS"; //$NON-NLS-1$

	/** SO_TIMEOUT value. */
	private static final int SO_TIMEOUT = 10000;

	/** Java system property to get the trust store password. */
	private static final String LOCAL_TRUST_STORE_PASSWORD_KEY = "javax.net.ssl.trustStorePassword"; //$NON-NLS-1$

	/** Java system property to get the trust store file. */
	private static final String LOCAL_TRUST_STORE_FILE_KEY = "javax.net.ssl.trustStore"; //$NON-NLS-1$

	/** Local trust store. */
	private static KeyStore localTrustStore;

	/**
	 * A list of added certificates avoid trying to add twice the same certificate.
	 */
	private static List addedCertificates = new ArrayList<>();

	/**
	 * Main. Opens a connection to the remote server and adds its certificates to
	 * the local trust store.
	 *
	 * @param args command line arguments.
	 */
	public static void main(final String[] args) {

		var urlEndPoint = ""; //$NON-NLS-1$
		try {

			/* Reads command line parameters, store its values. */
			List arguments = new ArrayList<>(Arrays.asList(args));

			/* If the list has duplicates must stop the execution. */
			var dup = findDuplicates(arguments, PARAMETER_URL);
			if (dup != null) {
				throw new IllegalArgumentException(MessageCatalog.PARAMETER_REPEATED.getMessage(dup));
			}

			urlEndPoint = readParameter(arguments, PARAMETER_URL);

			if (!arguments.isEmpty()) {
				throw new IllegalArgumentException(MessageCatalog.UNKNOWN_PARAMETERS.getMessage(arguments.toString()));
			}

			urlEndPoint = setConfig(urlEndPoint);

			var url = new URL(urlEndPoint);

			if (!HTTPS_PROTOCOL.equals(url.getProtocol())) {
				throw new IllegalArgumentException(TrustServerMessageCatalog.TRUSTSERVER_ONLY_HTTPS.getMessage());
			}

			var localTrustFile = System.getProperty(LOCAL_TRUST_STORE_FILE_KEY);
			if (localTrustFile == null) {
				throw new IllegalArgumentException(TrustServerMessageCatalog.TRUSTSERVER_NO_TRUST_STORE.getMessage());
			}

			var passwd = System.getProperty(LOCAL_TRUST_STORE_PASSWORD_KEY);
			if (passwd == null) {
				passwd = ""; //$NON-NLS-1$
			}

			loadLocalTrustStore(localTrustFile, passwd.toCharArray());

			if (LOGGER.isLoggable(Level.INFO)) {
				LOGGER.info(TrustServerMessageCatalog.TRUSTSERVER_TRUST_SIZE.getMessage(localTrustStore.size()));
				LOGGER.info(TrustServerMessageCatalog.TRUSTSERVER_GETTING_SERVER_CERTICATES.getMessage());
			}

			var certAdded = addServerCerts(urlEndPoint);

			if (certAdded) {
				if (LOGGER.isLoggable(Level.INFO)) {
					LOGGER.info(TrustServerMessageCatalog.TRUSTSERVER_RERUN_COMMAND.getMessage());
				}
			} else {
				if (LOGGER.isLoggable(Level.INFO)) {
					LOGGER.info(TrustServerMessageCatalog.TRUSTSERVER_GETTING_SIGNATURE_CERTICATES.getMessage());
				}
				addSignatureCert(urlEndPoint);
			}

			storeLocalTrustStore(localTrustFile, passwd.toCharArray());

		} catch (MalformedURLException e) {
			if (LOGGER.isLoggable(Level.SEVERE)) {
				LOGGER.severe(MessageCatalog.INVALID_URL.getMessage(urlEndPoint));
			}
		} catch (IOException e) {
			if (LOGGER.isLoggable(Level.SEVERE)) {
				LOGGER.severe(e.getMessage());
			}
		} catch (ConfigException e) {

			if (LOGGER.isLoggable(Level.SEVERE)) {
				LOGGER.severe(MessageCatalog.INVALID_CONFIGURATION.getMessage(e.getMessage()));
			}

			/* Shows stack trace only for debug. Don't bother the user with this details. */
			LOGGER.log(Level.FINE, MessageCatalog.INVALID_CONFIGURATION.getMessage(e.getMessage()), e);
		} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
			if (LOGGER.isLoggable(Level.SEVERE)) {
				LOGGER.log(Level.SEVERE, TrustServerMessageCatalog.TRUSTSERVER_BAD_KEYSTORE.getMessage(e.getMessage()));
			}
		} catch (IllegalArgumentException e) {
			if (LOGGER.isLoggable(Level.INFO)) {
				LOGGER.info(e.getMessage());
				LOGGER.info(TrustServerMessageCatalog.TRUSTSERVER_USAGE.getMessage(PARAMETER_URL));
			}
		}

	}

	/**
	 * Loads the local trust store.
	 *
	 * @param filePath Full trust store file path.
	 * @param passwd   Trust store password.
	 * @throws IOException if it's no possible to open the configured trust store.
	 */
	private static void loadLocalTrustStore(final String filePath, final char[] passwd) throws IOException {
		var localCacert = new File(filePath);
		try (InputStream in = new FileInputStream(localCacert)) {
			localTrustStore = KeyStore.getInstance(KeyStore.getDefaultType());
			localTrustStore.load(in, passwd);
		} catch (IOException | KeyStoreException | NoSuchAlgorithmException | CertificateException e) {
			throw new IOException(TrustServerMessageCatalog.TRUSTSERVER_UNABLE_TO_LOAD.getMessage(), e);
		}
	}

	/**
	 * Saves (stores) the local trust store.
	 *
	 * @param filePath Full trust store file path.
	 * @param passwd   Trust store password.
	 * @throws IOException if it's no possible to open the configured trust store.
	 */
	private static void storeLocalTrustStore(final String filePath, final char[] passwd) throws IOException {
		var localCacert = new File(filePath);
		try (OutputStream out = new FileOutputStream(localCacert)) {
			localTrustStore.store(out, passwd);
		} catch (IOException | KeyStoreException | NoSuchAlgorithmException | CertificateException ex) {
			throw new IOException(TrustServerMessageCatalog.TRUSTSERVER_UNABLE_TO_SAVE.getMessage(), ex);
		}
	}

	/**
	 * Adds the certificate used for signature to the current trust store. This
	 * method perform a list + get process to get a signed message.
	 *
	 * @param urlEndPoint Web service url.
	 * @throws KeyStoreException     If the method is unable to add the retrieved
	 *                               certificate.
	 * @throws MalformedURLException If the current url is incorrecto (impossible at
	 *                               this point)
	 */
	private static void addSignatureCert(final String urlEndPoint) throws KeyStoreException, MalformedURLException {

		var url = new URL(urlEndPoint);
		GetMessage get = null;
		try {
			if (LOGGER.isLoggable(Level.INFO)) {
				LOGGER.info(TrustServerMessageCatalog.TRUSTSERVER_GETTING_MSG_LIST.getMessage());
			}

			var list = new ListMessages();
			list.setEndPoint(url);
			var listMsg = list.list(0L);

			if (listMsg.isEmpty()) {
				if (LOGGER.isLoggable(Level.WARNING)) {
					LOGGER.warning(TrustServerMessageCatalog.TRUSTSERVER_NO_MESSAGES_TO_LIST.getMessage());
				}
			} else {
				if (LOGGER.isLoggable(Level.INFO)) {
					LOGGER.info(TrustServerMessageCatalog.TRUSTSERVER_MSG_LIST.getMessage(listMsg.size()));
				}
				get = new GetMessage();
				get.setEndPoint(url);

				if (LOGGER.isLoggable(Level.INFO)) {
					LOGGER.info(TrustServerMessageCatalog.TRUSTSERVER_MSG_GET
					        .getMessage(String.valueOf(listMsg.get(0).getCode().longValue())));
				}
				get.get(listMsg.get(0).getCode().longValue());
			}
		} catch (ListOperationException ex) {
			if (LOGGER.isLoggable(Level.SEVERE)) {
				LOGGER.log(Level.SEVERE,
				        TrustServerMessageCatalog.TRUSTSERVER_UNABLE_TO_CONNECT_WITH_SERVER.getMessage(), ex);
			}
		} catch (GetOperationException ex) {
			if (LOGGER.isLoggable(Level.FINE)) {
				LOGGER.log(Level.FINE, TrustServerMessageCatalog.TRUSTSERVER_UNABLE_TO_CONNECT_WITH_SERVER.getMessage(),
				        ex);
			}
		} catch (WebServiceException ex) {
			LOGGER.warning(
			        TrustServerMessageCatalog.TRUSTSERVER_UNABLE_TO_CONNECT_WITH_SERVER.getMessage(ex.getMessage()));
			LOGGER.log(Level.FINE,
			        TrustServerMessageCatalog.TRUSTSERVER_UNABLE_TO_CONNECT_WITH_SERVER.getMessage(ex.getMessage()),
			        ex);
		} finally {
			if (get != null) {
				var md = get.getMessageMetaData();
				if (md != null) {
					var certificate = md.getSignatureCertificate();
					if (certificate != null) {
						addCertificate(url.getHost() + " (" + certificate.getIssuerX500Principal().getName() + ") (" //$NON-NLS-1$ //$NON-NLS-2$
						        + TrustServerMessageCatalog.TRUSTSERVER_SIGNATURE.getMessage() + ") ", certificate); //$NON-NLS-1$
					}
				}
			}
		}
	}

	/**
	 * Adds the all the certificates in the server's certificate chain to the local
	 * trust store.
	 *
	 * @param urlEndPoint Url to connect with in order to retrieve the certificate
	 *                    chain.
	 * @return true if a least one certificate is added.
	 * @throws KeyStoreException        If the method is unable to add the retrieved
	 *                                  certificate.
	 * @throws NoSuchAlgorithmException If the system is unable to deal with TSL
	 *                                  protocol (Is this possible by the way?)
	 * @throws KeyManagementException   If it is not possible to initialize the SSL
	 *                                  context with the local trust store.
	 * @throws IOException              If server didn't send certificate chain.
	 */
	private static boolean addServerCerts(final String urlEndPoint)
	        throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException, IOException {

		var certsAdded = false;

		var url = new URL(urlEndPoint);
		var host = url.getHost();
		var port = url.getPort();
		if (port == -1) {
			port = DEFAULT_HTTPS_PORT;
		}

		var tm = new ProxyTrustManager();
		var context = SSLContext.getInstance(TLS_PROTOCOL);
		context.init(null, new TrustManager[] { tm }, null);
		var factory = context.getSocketFactory();

		if (LOGGER.isLoggable(Level.INFO)) {
			LOGGER.info(TrustServerMessageCatalog.TRUSTSERVER_OPENING_CONNECTION.getMessage(urlEndPoint));
		}

		try (var socket = (SSLSocket) factory.createSocket(host, port)) {
			socket.setSoTimeout(SO_TIMEOUT);
			socket.startHandshake();
		} catch (UnknownHostException uhe) {
			if (LOGGER.isLoggable(Level.SEVERE)) {
				LOGGER.severe(TrustServerMessageCatalog.TRUSTSERVER_UNKNOW_HOST.getMessage(urlEndPoint));
			}
		} catch (ConnectException ce) {
			if (LOGGER.isLoggable(Level.SEVERE)) {
				LOGGER.severe(TrustServerMessageCatalog.TRUSTSERVER_CANNOT_CONNECT.getMessage(urlEndPoint));
			}
		} catch (IOException ioe) {
			if (LOGGER.isLoggable(Level.FINE)) {
				LOGGER.log(Level.FINE, TrustServerMessageCatalog.TRUSTSERVER_CANNOT_CONNECT.getMessage(urlEndPoint),
				        ioe);
			}
		} finally {

			var chain = tm.getServerCertificateChain();

			if ((chain == null) || (chain.length <= 0)) {
				throw new IOException(TrustServerMessageCatalog.TRUSTSERVER_NO_CERT_CHAIN.getMessage());
			}
			for (X509Certificate certificate : chain) {
				var subjectDN = certificate.getIssuerX500Principal().getName();

				if (addCertificate(host + " (" + subjectDN + ") ", certificate)) { //$NON-NLS-1$ //$NON-NLS-2$
					certsAdded = true;
				}
			}
		}

		return certsAdded;
	}

	/**
	 * Adds a certificate into the local trust store. Note that only valid
	 * certificates are added.
	 *
	 * @param alias       Alias Alias used to store the certificate.
	 * @param certificate Certificate to be added
	 * @return true if the certificate is added to the trust store.
	 *         false otherwise.
	 * @throws KeyStoreException If the method cannot modify the local trust store.
	 */
	private static boolean addCertificate(final String alias, final X509Certificate certificate)
	        throws KeyStoreException {
		var certAdded = false;
		var subjectDN = certificate.getIssuerX500Principal().getName();
		var issuer = certificate.getIssuerX500Principal().getName();
		try {
			var validity = X509Util.checkCertificate(certificate);

			if (!validity.isValid()) {
				throw validity.getCause();
			}

			if (LOGGER.isLoggable(Level.INFO)) {
				LOGGER.info(TrustServerMessageCatalog.TRUSTSERVER_SKIPPING_CERTIFICATE.getMessage(subjectDN, issuer));
			}

		} catch (CertificateExpiredException e) {
			if (LOGGER.isLoggable(Level.INFO)) {
				LOGGER.info(TrustServerMessageCatalog.TRUSTSERVER_SKIPPING_EXPIRED.getMessage(subjectDN, issuer));
			}
		} catch (CertificateNotYetValidException e) {
			if (LOGGER.isLoggable(Level.INFO)) {
				LOGGER.info(TrustServerMessageCatalog.TRUSTSERVER_SKIPPING_NOT_YET_VALID.getMessage(subjectDN, issuer));
			}
		} catch (CertificateException e1) {
			if (addedCertificates.contains(certificate)) {
				if (LOGGER.isLoggable(Level.INFO)) {
					LOGGER.info(TrustServerMessageCatalog.TRUSTSERVER_SKIPPING_ALREADY_ADDED.getMessage(subjectDN));
				}
			} else {
				if (LOGGER.isLoggable(Level.INFO)) {
					LOGGER.info(TrustServerMessageCatalog.TRUSTSERVER_ADDING_CERTIFICATE.getMessage(subjectDN, issuer));
				}
				addedCertificates.add(certificate);
				certAdded = true;
				localTrustStore.setCertificateEntry(alias, certificate);
			}
		}

		return certAdded;
	}

	/**
	 * Implements a proxy trust manager that will store the server's certificate
	 * chain.
	 */
	private static class ProxyTrustManager implements X509TrustManager {

		/** Default trust manager (real trust manager). */
		private final X509TrustManager defaultTrustManager;

		/** Server certificates chain. */
		private X509Certificate[] serverCertificateChain;

		/**
		 * Creates a new ProxyTrustManager using the given keystore as trusted server's
		 * CAs.
		 *
		 * @throws NoSuchAlgorithmException If it's not possible to get a
		 *                                  TrustManagerFactory instance for the default
		 *                                  algorithm.
		 * @throws KeyStoreException        if it's not possible to inicialize the trust
		 *                                  manager with the given key store.
		 */
		ProxyTrustManager() throws NoSuchAlgorithmException, KeyStoreException {

			var tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
			tmf.init(localTrustStore);
			defaultTrustManager = (X509TrustManager) tmf.getTrustManagers()[0];
		}

		/**
		 * Returns the retrieved server's certificate chain.
		 *
		 * @return An array with the retrieved certificate chain.
		 */
		public X509Certificate[] getServerCertificateChain() {
			return serverCertificateChain;
		}

		/**
		 * Returns the accepted issuers.
		 */
		@Override
		public X509Certificate[] getAcceptedIssuers() {
			return defaultTrustManager.getAcceptedIssuers();
		}

		/**
		 * Not supported. There is no need to implement this method, but it's necessary
		 * according to the X509TrustManager interface.
		 *
		 * @throws UnsupportedOperationException
		 */
		@Override
		public void checkClientTrusted(final X509Certificate[] chain, final String authType)
		        throws CertificateException {
			throw new UnsupportedOperationException("Not supported"); //$NON-NLS-1$
		}

		/**
		 * Stores the server's certificate chain. This method nevers returns
		 * CertificateException: All connections are trusted.
		 *
		 * @param chain    Server's certificate chain
		 * @param authType Authentification type.
		 * @throws CertificateException Never, all connections are trusted.
		 */
		@Override
		public void checkServerTrusted(final X509Certificate[] chain, final String authType)
		        throws CertificateException {
			serverCertificateChain = chain;
		}
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy