001/*
002 * jPOS Project [http://jpos.org]
003 * Copyright (C) 2000-2026 jPOS Software SRL
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jpos.iso;
020
021import org.jpos.core.Configurable;
022import org.jpos.core.Configuration;
023import org.jpos.core.ConfigurationException;
024import org.jpos.util.SimpleLogSource;
025
026import javax.net.ssl.*;
027import java.io.ByteArrayInputStream;
028import java.io.File;
029import java.io.FileInputStream;
030import java.io.IOException;
031import java.net.InetAddress;
032import java.net.ServerSocket;
033import java.net.Socket;
034import java.net.UnknownHostException;
035import java.security.cert.Certificate;
036import java.security.cert.CertificateException;
037import java.security.cert.CertificateFactory;
038import java.security.cert.X509Certificate;
039import java.security.GeneralSecurityException;
040import java.security.KeyStore;
041import java.security.SecureRandom;
042
043/**
044 * <code>SunJSSESocketFactory</code> is used by BaseChannel and ISOServer
045 * in order to provide hooks for SSL implementations.
046 *
047 * @version $Revision$ $Date$
048 * @author  Bharavi Gade
049 * @author Alwyn Schoeman
050 * @since   1.3.3
051 */
052public class GenericSSLSocketFactory 
053        extends SimpleLogSource 
054        implements ISOServerSocketFactory,ISOClientSocketFactory, Configurable
055{ 
056
057    private SSLContext sslc=null;
058    private SSLServerSocketFactory  serverFactory=null;
059    private SSLSocketFactory socketFactory=null;
060
061    private String keyStore=null;
062    private String password=null;
063    private String keyPassword=null;
064    private String serverName;
065    private boolean clientAuthNeeded=false;
066    private boolean serverAuthNeeded=false;
067    private String[] enabledCipherSuites;
068    private String[] enabledProtocols;
069
070    private Configuration cfg;
071
072    public void setKeyStore(String keyStore){
073        this.keyStore=keyStore;  
074    }
075
076    public void setPassword(String password){
077        this.password=password;  
078    }
079
080    public void setKeyPassword(String keyPassword){
081        this.keyPassword=keyPassword;  
082    }
083
084    public void setServerName(String serverName){
085        this.serverName=serverName;  
086    }
087
088    public void setClientAuthNeeded(boolean clientAuthNeeded){
089        this.clientAuthNeeded=clientAuthNeeded;  
090    }
091
092    public void setServerAuthNeeded(boolean serverAuthNeeded){
093        this.serverAuthNeeded=serverAuthNeeded;  
094    }
095
096    private TrustManager[] getTrustManagers(KeyStore ks)
097        throws GeneralSecurityException {
098        if (serverAuthNeeded) {
099            TrustManagerFactory tm = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
100            tm.init( ks ); 
101            return tm.getTrustManagers(); 
102        } else {
103            // Create a trust manager that does not validate certificate chains
104            return new TrustManager[]{
105                new X509TrustManager() {
106                    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
107                        return new java.security.cert.X509Certificate[] {};
108                    }
109                    public void checkClientTrusted(
110                        java.security.cert.X509Certificate[] certs, String authType) {
111                    }
112                    public void checkServerTrusted(
113                        java.security.cert.X509Certificate[] certs, String authType) {
114                    }
115                }
116            };
117        }
118    }
119
120    /**
121     * Create a SSLSocket Context
122     * @return the SSLContext
123     * @returns null if exception occurrs
124     */
125    private SSLContext getSSLContext() throws ISOException {
126        if(password==null)  password=getPassword();
127        if(keyPassword ==null)  keyPassword=getKeyPassword();
128        if(keyStore==null || keyStore.length()==0) {
129            keyStore=System.getProperty("user.home")+File.separator+".keystore";
130        }
131
132        try{
133            KeyStore ks = KeyStore.getInstance( "JKS" );
134            FileInputStream fis = new FileInputStream (new File (keyStore));
135            ks.load(fis,password.toCharArray());
136            fis.close();
137            KeyManagerFactory km = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
138            km.init( ks, keyPassword.toCharArray() );
139            KeyManager[] kma = km.getKeyManagers();
140            TrustManager[] tma = getTrustManagers( ks );
141            SSLContext sslc = SSLContext.getInstance( "SSL" ); 
142            sslc.init( kma, tma, new SecureRandom() );
143            return sslc;
144        } catch(Exception e) {
145            throw new ISOException (e);
146        } finally { 
147            password=null;
148            keyPassword=null;
149        }
150    }
151
152    /**
153     * Create a socket factory
154     * @return the socket factory
155     * @exception ISOException if an error occurs during server socket
156     * creation
157     */
158    protected SSLServerSocketFactory createServerSocketFactory() 
159        throws ISOException
160    {
161        if(sslc==null) sslc=getSSLContext();
162        return sslc.getServerSocketFactory();
163    }
164        
165    /**
166     * Create a socket factory
167     * @return the socket factory
168     * @exception ISOException if an error occurs during server socket
169     * creation
170     */
171    protected SSLSocketFactory createSocketFactory() 
172        throws ISOException
173    {
174        if(sslc==null) sslc=getSSLContext();
175        return sslc.getSocketFactory();
176    }
177    
178    /**
179     * Create a server socket on the specified port (port 0 indicates
180     * an anonymous port).
181     * @param  port the port number
182     * @return the server socket on the specified port
183     * @exception IOException should an I/O error occurs during 
184     * @exception ISOException should an error occurs during 
185     * creation
186     */
187    public ServerSocket createServerSocket(int port) 
188        throws IOException, ISOException
189    {
190        if(serverFactory==null) serverFactory=createServerSocketFactory();
191        ServerSocket socket = serverFactory.createServerSocket(port);
192        SSLServerSocket serverSocket = (SSLServerSocket) socket;
193        serverSocket.setNeedClientAuth(clientAuthNeeded);
194        if (enabledCipherSuites != null && enabledCipherSuites.length > 0) {
195            serverSocket.setEnabledCipherSuites(enabledCipherSuites);
196        }
197        if (enabledProtocols != null && enabledProtocols.length > 0) {
198            serverSocket.setEnabledProtocols(enabledProtocols);
199        }
200        return socket;
201    }
202    
203    /**
204     * Create a client socket connected to the specified host and port.
205     * @param  host   the host name
206     * @param  port   the port number
207     * @return a socket connected to the specified host and port.
208     * @exception IOException if an I/O error occurs during socket creation
209     * @exception ISOException should any other error occurs
210     */
211    public Socket createSocket(String host, int port) 
212        throws IOException, ISOException
213    {
214        if(socketFactory==null) socketFactory=createSocketFactory();
215        SSLSocket s = (SSLSocket) socketFactory.createSocket(host,port);
216        verifyHostname(s);
217        return s;
218    }
219
220    /**
221     * Verify that serverName and CN equals.
222     *
223     * <pre>
224     * Origin:      jakarta-commons/httpclient
225     * File:        StrictSSLProtocolSocketFactory.java
226     * Revision:    1.5
227     * License:     Apache-2.0
228     * </pre>
229     *
230     * @param socket a SSLSocket value
231     * @exception SSLPeerUnverifiedException  If there are problems obtaining
232     * the server certificates from the SSL session, or the server host name 
233     * does not match with the "Common Name" in the server certificates 
234     * SubjectDN.
235     * @exception UnknownHostException  If we are not able to resolve
236     * the SSL sessions returned server host name. 
237     */
238    private void verifyHostname(SSLSocket socket)
239        throws SSLPeerUnverifiedException, UnknownHostException
240    {
241        if (!serverAuthNeeded) {
242            return; 
243        }
244
245        SSLSession session = socket.getSession();
246
247        if (serverName==null || serverName.length()==0) {
248            serverName = session.getPeerHost();
249            try {
250                InetAddress addr = InetAddress.getByName(serverName);
251            } catch (UnknownHostException uhe) {
252                throw new UnknownHostException("Could not resolve SSL " +
253                                               "server name " + serverName);
254            }
255        }
256
257
258        Certificate[] certs = session.getPeerCertificates();
259        if (certs==null || certs.length==0)
260            throw new SSLPeerUnverifiedException("No server certificates found");
261
262        try {
263            CertificateFactory cf = CertificateFactory.getInstance("X.509");
264            ByteArrayInputStream bais = new ByteArrayInputStream(certs[0].getEncoded());
265            X509Certificate cert = (X509Certificate) cf.generateCertificate(bais);
266
267            //get the servers DN in its string representation
268            String dn = cert.getSubjectDN().getName();
269
270            //get the common name from the first cert
271            String cn = getCN(dn);
272            if (!serverName.equalsIgnoreCase(cn)) {
273                throw new SSLPeerUnverifiedException("Invalid SSL server name. "+
274                        "Expected '" + serverName +
275                        "', got '" + cn + "'");
276            }
277        } catch (CertificateException e) {
278            throw new SSLPeerUnverifiedException(e.getMessage());
279        }
280    }
281
282    /**
283     * Parses a X.500 distinguished name for the value of the 
284     * "Common Name" field.
285     * This is done a bit sloppy right now and should probably be done a bit
286     * more according to RFC 2253.
287     *
288     * <pre>
289     * Origin:      jakarta-commons/httpclient
290     * File:        StrictSSLProtocolSocketFactory.java
291     * Revision:    1.5
292     * License:     Apache-2.0
293     * </pre>
294     *
295     * @param dn  a X.500 distinguished name.
296     * @return the value of the "Common Name" field.
297     */
298    private String getCN(String dn) {
299        int i = dn.indexOf("CN=");
300        if (i == -1) {
301            return null;
302        }
303        //get the remaining DN without CN=
304        dn = dn.substring(i + 3);  
305        // System.out.println("dn=" + dn);
306        char[] dncs = dn.toCharArray();
307        for (i = 0; i < dncs.length; i++) {
308            if (dncs[i] == ','  && i > 0 && dncs[i - 1] != '\\') {
309                break;
310            }
311        }
312        return dn.substring(0, i);
313    }
314
315    public String getKeyStore() {
316        return keyStore;
317    }
318
319    // Have custom hooks get passwords
320    // You really need to modify these two implementations
321    protected String getPassword() {
322        return System.getProperty("jpos.ssl.storepass", "password");
323    }
324
325    protected String getKeyPassword() {
326        return System.getProperty("jpos.ssl.keypass", "password");
327    }
328
329    public String getServerName() {
330        return serverName;
331    }
332
333    public boolean getClientAuthNeeded() {
334        return clientAuthNeeded;
335    }
336
337    public boolean getServerAuthNeeded() {
338        return serverAuthNeeded;
339    }
340    
341    public void setEnabledCipherSuites(String[] enabledCipherSuites) {
342        this.enabledCipherSuites = enabledCipherSuites;
343    }
344
345    public String[] getEnabledCipherSuites() {
346        return enabledCipherSuites;
347    }
348
349
350    public void setConfiguration(Configuration cfg) throws ConfigurationException {
351        this.cfg = cfg;
352        keyStore = cfg.get("keystore");
353        clientAuthNeeded = cfg.getBoolean("clientauth");
354        serverAuthNeeded = cfg.getBoolean("serverauth");
355        serverName = cfg.get("servername");
356        password = cfg.get("storepassword", null);
357        keyPassword = cfg.get("keypassword", null);
358        enabledCipherSuites = cfg.getAll("addEnabledCipherSuite");
359        enabledProtocols = cfg.getAll("addEnabledProtocol");
360    }
361    public Configuration getConfiguration() {
362        return cfg;
363    }
364}