diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 8b2a449f..6811fc72 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,13 +1,15 @@ OpenAS2 Server - Version 1.2.0 + Version 1.3.0 RELEASE NOTES -The OpenAS2 project is pleased to announce the release of OpenAS2 1.2.0 +The OpenAS2 project is pleased to announce the release of OpenAS2 1.3.0 -The release download file is: OpenAS2Server-1.2.0.zip +The release download file is: OpenAS2Server-1.3.0.zip The zip file contains a PDF document providing information on installing and using the application. -This release adds support for compression and decompression of AS2 messages per RFC5042. +This release adds support for: + - using HTTPS as the transport protocol. + - overriding the the password for the certificate store with a system parameter passed into the app at startup Java 1.5 or later is required. diff --git a/Server/bin/start-openas2.sh b/Server/bin/start-openas2.sh index 055f2874..0bd71557 100755 --- a/Server/bin/start-openas2.sh +++ b/Server/bin/start-openas2.sh @@ -1,5 +1,12 @@ #!/bin/sh # purpose: runs the OpenAS2 application +x=`basename $0` + +keyStorePwd=$1 +PWD_OVERRIDE="" +if [ ! -z $keyStorePwd ]; then + PWD_OVERRIDE="-Dorg.openas2.cert.Password=$keyStorePwd" +fi if [ -z $JAVA_HOME ]; then OS=$(uname -s) @@ -23,4 +30,4 @@ JAVA_EXE=$JAVA_HOME/bin/java # # remove -Dorg.apache.commons.logging.Log=org.openas2.logging.Log if using another logging package # -$JAVA_EXE -Xms32m -Xmx384m -Dorg.apache.commons.logging.Log=org.openas2.logging.Log -cp .:../lib/javax.mail.jar:../lib/bcpkix-jdk15on-152.jar:../lib/bcprov-jdk15on-152.jar:../lib/bcmail-jdk15on-152.jar:../lib/bcprov-jdk15on-152:../lib/commons-logging-1.2.jar:../lib/openas2-server.jar org.openas2.app.OpenAS2Server ../config/config.xml +$JAVA_EXE ${PWD_OVERRIDE} -Xms32m -Xmx384m -Dorg.apache.commons.logging.Log=org.openas2.logging.Log -cp .:../lib/javax.mail.jar:../lib/bcpkix-jdk15on-152.jar:../lib/bcprov-jdk15on-152.jar:../lib/bcmail-jdk15on-152.jar:../lib/bcprov-jdk15on-152:../lib/commons-logging-1.2.jar:../lib/openas2-server.jar org.openas2.app.OpenAS2Server ../config/config.xml diff --git a/Server/config/config.xml b/Server/config/config.xml index 23ab0f33..314eedb6 100644 --- a/Server/config/config.xml +++ b/Server/config/config.xml @@ -90,10 +90,29 @@ port="10080" errordir="%home%/../data/inbox/error" errorformat="sender.as2_id, receiver.as2_id, headers.message-id"/> - + + + options) throws OpenAS2Exception { super.init(session, options); - + + // Override the password if it was passed as a system property + String pwd = System.getProperty("org.openas2.cert.Password"); + if (pwd != null) + { + setPassword(pwd.toCharArray()); + } try { this.keyStore = AS2Util.getCryptoHelper().getKeyStore(); } catch (Exception e) { diff --git a/Server/src/org/openas2/processor/receiver/AS2ReceiverHandler.java b/Server/src/org/openas2/processor/receiver/AS2ReceiverHandler.java index 7d1ab9f3..26e8ce82 100644 --- a/Server/src/org/openas2/processor/receiver/AS2ReceiverHandler.java +++ b/Server/src/org/openas2/processor/receiver/AS2ReceiverHandler.java @@ -75,6 +75,7 @@ public void handle(NetModule owner, Socket s) { } catch (Exception e) { + logger.error("HTTP connection error on inbound message.", e); NetException ne = new NetException(s.getInetAddress(), s.getPort(), e); ne.terminate(); } diff --git a/Server/src/org/openas2/processor/receiver/NetModule.java b/Server/src/org/openas2/processor/receiver/NetModule.java index b1b5e28d..b9f65c46 100644 --- a/Server/src/org/openas2/processor/receiver/NetModule.java +++ b/Server/src/org/openas2/processor/receiver/NetModule.java @@ -1,13 +1,28 @@ package org.openas2.processor.receiver; import java.io.File; +import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; import java.util.Map; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLServerSocketFactory; +import javax.net.ssl.TrustManagerFactory; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.openas2.OpenAS2Exception; import org.openas2.Session; import org.openas2.WrappedException; @@ -15,25 +30,33 @@ import org.openas2.message.Message; import org.openas2.params.CompositeParameters; import org.openas2.params.DateParameters; +import org.openas2.params.InvalidParameterException; import org.openas2.params.MessageParameters; +import org.openas2.processor.sender.AS2SenderModule; import org.openas2.util.IOUtilOld; public abstract class NetModule extends BaseReceiverModule { public static final String PARAM_ADDRESS = "address"; public static final String PARAM_PORT = "port"; + public static final String PARAM_PROTOCOL = "protocol"; + public static final String PARAM_SSL_KEYSTORE = "ssl_keystore"; + public static final String PARAM_SSL_KEYSTORE_PASSWORD = "ssl_keystore_password"; + public static final String PARAM_SSL_PROTOCOL = "ssl_protocol"; public static final String PARAM_ERROR_DIRECTORY = "errordir"; public static final String PARAM_ERRORS = "errors"; public static final String DEFAULT_ERRORS = "$date.yyyyMMddhhmmss$"; - private MainThread mainThread; + private HTTPServerThread mainThread; + private Log logger = LogFactory.getLog(AS2SenderModule.class.getSimpleName()); public void doStart() throws OpenAS2Exception { try { - mainThread = new MainThread(this, getParameter(PARAM_ADDRESS, false), + mainThread = new HTTPServerThread(this, getParameter(PARAM_ADDRESS, false), getParameterInt(PARAM_PORT, true)); mainThread.start(); } catch (IOException ioe) { + logger.error("Error in HTTP connection.", ioe); throw new WrappedException(ioe); } } @@ -48,6 +71,13 @@ public void doStop() throws OpenAS2Exception { public void init(Session session, Map options) throws OpenAS2Exception { super.init(session, options); getParameter(PARAM_PORT, true); + // Override the password if it was passed as a system property + String pwd = System.getProperty("org.openas2.ssl.Password"); + if (pwd != null) + { + setParameter(PARAM_SSL_KEYSTORE_PASSWORD, pwd);; + } + } protected abstract NetModuleHandler getHandler(); @@ -122,22 +152,119 @@ public void run() { } } - protected class MainThread extends Thread { + protected class HTTPServerThread extends Thread { private NetModule owner; private ServerSocket socket; private boolean terminated; - public MainThread(NetModule owner, String address, int port) + public HTTPServerThread(NetModule owner, String address, int port) throws IOException { super(); this.owner = owner; - - socket = new ServerSocket(); - - if (address != null) { - socket.bind(new InetSocketAddress(address, port)); - } else { - socket.bind(new InetSocketAddress(port)); + String protocol = "http"; + String sslProtocol = "TLS"; + try + { + protocol = owner.getParameter(PARAM_PROTOCOL, "http"); + sslProtocol = owner.getParameter(PARAM_SSL_PROTOCOL, "TLS"); + } catch (InvalidParameterException e) + { + // Do nothing + } + if ("https".equalsIgnoreCase(protocol)) + { + String ksName; + char [] ksPass; + try + { + ksName = owner.getParameter(PARAM_SSL_KEYSTORE, true); + ksPass = owner.getParameter(PARAM_SSL_KEYSTORE_PASSWORD, true).toCharArray(); + } catch (InvalidParameterException e) + { + logger.error("Required SSL parameter missing.", e); + throw new IOException("Failed to retireve require SSL parameters. Check config XML"); + } + KeyStore ks; + try + { + ks = KeyStore.getInstance("JKS"); + } catch (KeyStoreException e) + { + logger.error("Failed to initialise SSL keystore.", e); + throw new IOException("Error initialising SSL keystore"); + } + try + { + ks.load(new FileInputStream(ksName), ksPass); + } catch (NoSuchAlgorithmException e) + { + logger.error("Failed to load keystore: " + ksName, e); + throw new IOException("Error loading SSL keystore"); + } catch (CertificateException e) + { + logger.error("Failed to load SSL certificate: " + ksName, e); + throw new IOException("Error loading SSL certificate"); + } + KeyManagerFactory kmf; + try + { + kmf = KeyManagerFactory.getInstance("SunX509"); + } catch (NoSuchAlgorithmException e) + { + logger.error("Failed to create key manager instance", e); + throw new IOException("Error creating SSL key manager instance"); + } + try + { + kmf.init(ks, ksPass); + } catch (Exception e) + { + logger.error("Failed to initialise key manager instance", e); + throw new IOException("Error initialising SSL key manager instance"); + } + // setup the trust manager factory + TrustManagerFactory tmf; + try + { + tmf = TrustManagerFactory.getInstance ( "SunX509" ); + tmf.init( ks ); + } catch (Exception e1) + { + logger.error("Failed to create trust manager instance", e1); + throw new IOException("Error creating SSL trust manager instance"); + } + SSLContext sc; + try + { + sc = SSLContext.getInstance(sslProtocol); + } catch (NoSuchAlgorithmException e) + { + logger.error("Failed to create SSL context instance", e); + throw new IOException("Error creating SSL context instance"); + } + try + { + sc.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + } catch (KeyManagementException e) + { + logger.error("Failed to initialise SSL context instance", e); + throw new IOException("Error initialising SSL context instance"); + } + SSLServerSocketFactory ssf = sc.getServerSocketFactory(); + if (address != null) { + socket = (SSLServerSocket) ssf.createServerSocket(port, 0, InetAddress.getByName(address)); + } + else + socket = (SSLServerSocket) ssf.createServerSocket(port); + } + else + { + socket = new ServerSocket(); + if (address != null) { + socket.bind(new InetSocketAddress(address, port)); + } else { + socket.bind(new InetSocketAddress(port)); + } } } diff --git a/Server/src/org/openas2/processor/sender/AS2SenderModule.java b/Server/src/org/openas2/processor/sender/AS2SenderModule.java index 9c2efb90..b9075292 100644 --- a/Server/src/org/openas2/processor/sender/AS2SenderModule.java +++ b/Server/src/org/openas2/processor/sender/AS2SenderModule.java @@ -79,6 +79,7 @@ public void handle(String action, Message msg, Map options) thro // Create the HTTP connection and set up headers String url = msg.getPartnership().getAttribute(AS2Partnership.PA_AS2_URL); HttpURLConnection conn = getConnection(url, true, true, false, "POST"); + try { updateHttpHeaders(conn, msg); msg.setAttribute(NetAttribute.MA_DESTINATION_IP, conn.getURL().getHost()); @@ -157,14 +158,14 @@ public void handle(String action, Message msg, Map options) thro resend(msg, hre, retries); } catch (IOException ioe) { // Resend if a network error occurs during // transmission - + logger.warn("Error making network connection: " , ioe); WrappedException wioe = new WrappedException(ioe); wioe.addSource(OpenAS2Exception.SOURCE_MESSAGE, msg); wioe.terminate(); resend(msg, wioe, retries); - } catch (Exception e) { // Propagate error if it can't be handled by a - // resend + } catch (Exception e) { // Propagate error if it can't be handled by a resend + logger.warn("Unkonwn Error making network connection: " , e); throw new WrappedException(e); } } diff --git a/Server/src/org/openas2/processor/sender/HttpSenderModule.java b/Server/src/org/openas2/processor/sender/HttpSenderModule.java index f2175a95..dda397cc 100644 --- a/Server/src/org/openas2/processor/sender/HttpSenderModule.java +++ b/Server/src/org/openas2/processor/sender/HttpSenderModule.java @@ -1,15 +1,25 @@ package org.openas2.processor.sender; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; -import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import javax.mail.internet.InternetHeaders; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +import java.security.KeyStore; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; import org.openas2.OpenAS2Exception; import org.openas2.WrappedException; @@ -26,9 +36,50 @@ public HttpURLConnection getConnection(String url, boolean output, boolean input try { System.setProperty("sun.net.client.defaultReadTimeout", getParameter(PARAM_READ_TIMEOUT, "60000")); System.setProperty("sun.net.client.defaultConnectTimeout", getParameter(PARAM_CONNECT_TIMEOUT, "60000")); - URL urlObj = new URL(url); HttpURLConnection conn; - conn = (HttpURLConnection) urlObj.openConnection(); + URL urlObj = new URL(url); + if (urlObj.getProtocol().equalsIgnoreCase("https")) + { + HttpsURLConnection connS = (HttpsURLConnection) urlObj.openConnection(); + String selfSignedCN = System.getProperty("org.openas2.cert.TrustSelfSignedCN"); + if (selfSignedCN != null) + { + File file = new File("jssecacerts"); + if (file.isFile() == false) + { + char SEP = File.separatorChar; + File dir = new File(System.getProperty("java.home") + SEP + "lib" + SEP + "security"); + file = new File(dir, "jssecacerts"); + if (file.isFile() == false) + { + file = new File(dir, "cacerts"); + } + } + InputStream in = new FileInputStream(file); + try + { + KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); + ks.load(in, "changeit".toCharArray()); + in.close(); + SSLContext context = SSLContext.getInstance("TLS"); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory + .getDefaultAlgorithm()); + tmf.init(ks); + X509TrustManager defaultTrustManager = (X509TrustManager) tmf.getTrustManagers()[0]; + SelfSignedTrustManager tm = new SelfSignedTrustManager(defaultTrustManager); + tm.setTrustCN(selfSignedCN); + context.init(null, new TrustManager[] { tm }, null); + connS.setSSLSocketFactory(context.getSocketFactory()); + } catch (Exception e) + { + throw new OpenAS2Exception("Error self signed certificate management", e); + } + } + conn = connS; + } else + { + conn = (HttpURLConnection) urlObj.openConnection(); + } conn.setDoOutput(output); conn.setDoInput(input); conn.setUseCaches(useCaches); @@ -48,14 +99,14 @@ protected void copyHttpHeaders(HttpURLConnection conn, InternetHeaders headers) String headerName; while (connHeadersIt.hasNext()) { - connHeader = (Entry>) connHeadersIt.next(); - headerName = (String) connHeader.getKey(); + connHeader = connHeadersIt.next(); + headerName = connHeader.getKey(); if (headerName != null) { - connValuesIt = ((Collection) connHeader.getValue()).iterator(); + connValuesIt = connHeader.getValue().iterator(); while (connValuesIt.hasNext()) { - String value = (String) connValuesIt.next(); + String value = connValuesIt.next(); if (headers.getHeader(headerName) == null) { headers.setHeader(headerName, value); @@ -66,4 +117,48 @@ protected void copyHttpHeaders(HttpURLConnection conn, InternetHeaders headers) } } } + + private static class SelfSignedTrustManager implements X509TrustManager + { + + private final X509TrustManager tm; + private String[] trustCN = null; + + + SelfSignedTrustManager(X509TrustManager tm) + { + this.tm = tm; + } + + public X509Certificate[] getAcceptedIssuers() + { + return tm.getAcceptedIssuers(); + } + + public void checkClientTrusted(X509Certificate[] chain, String authType) + { + throw new UnsupportedOperationException(); + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException + { + if (chain.length == 1) + { + // Only ignore the check for self signed certs where CN (Canonical Name) matches + String dn = chain[0].getIssuerDN().getName(); + for (int i = 0; i < trustCN.length; i++) + { + if (dn.contains("CN="+trustCN[i])) + return; + } + } + tm.checkServerTrusted(chain, authType); + } + + public void setTrustCN(String trustCN) + { + this.trustCN = trustCN.split(","); + } + + } } diff --git a/Server/src/org/openas2/util/HTTPUtil.java b/Server/src/org/openas2/util/HTTPUtil.java index 468842ac..c9fcca80 100644 --- a/Server/src/org/openas2/util/HTTPUtil.java +++ b/Server/src/org/openas2/util/HTTPUtil.java @@ -333,7 +333,7 @@ public static String[] readRequest(InputStream in) throws IOException { requestParts[2] = tokens.nextToken(); return requestParts; } else { - throw new IOException("Invalid HTTP Request"); + throw new IOException("Invalid HTTP Request: " + strBuf.toString()); } } diff --git a/changes.txt b/changes.txt index 70257859..9aeca04d 100644 --- a/changes.txt +++ b/changes.txt @@ -1,6 +1,10 @@ -Versio 1.2.0 — 2015-08-17 +Version 1.3.0 — 2015-08-22 + 1. Add support for using HTTPS as the transport protocol. + 2. Allow overriding the the password for the certificate store with a system parameter passed into the app at startup + +Version 1.2.0 — 2015-08-17 Add support for compression and decompression per RFC5402 -Versio 1.1.0 — 2015-08-03 +Version 1.1.0 — 2015-08-03 1. Enhanced logging to support configurable levels of output. Default level is INFO Added additional logging information to make it clearer what the real problem is when errors occur 2. Bouncy Castle Libraries upgraded to 1.5.2 and the code base changes implemented to support this. diff --git a/docs/OpenAS2HowTo.odt b/docs/OpenAS2HowTo.odt index 2e52c915..34ae2cdd 100644 Binary files a/docs/OpenAS2HowTo.odt and b/docs/OpenAS2HowTo.odt differ diff --git a/docs/OpenAS2HowTo.pdf b/docs/OpenAS2HowTo.pdf index d48b7443..07b5940a 100644 Binary files a/docs/OpenAS2HowTo.pdf and b/docs/OpenAS2HowTo.pdf differ