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}