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.naming.InvalidNameException; 027import javax.naming.ldap.LdapName; 028import javax.naming.ldap.Rdn; 029import javax.net.ssl.*; 030import javax.security.auth.x500.X500Principal; 031import java.io.File; 032import java.io.FileInputStream; 033import java.io.IOException; 034import java.net.InetAddress; 035import java.net.ServerSocket; 036import java.net.Socket; 037import java.net.UnknownHostException; 038import java.security.cert.Certificate; 039import java.security.cert.CertificateException; 040import java.security.cert.X509Certificate; 041import java.security.GeneralSecurityException; 042import java.security.KeyStore; 043import java.security.SecureRandom; 044 045/** 046 * <code>SunJSSESocketFactory</code> is used by BaseChannel and ISOServer 047 * in order to provide hooks for SSL implementations. 048 * 049 * @version $Revision$ $Date$ 050 * @author Bharavi Gade 051 * @author Alwyn Schoeman 052 * @since 1.3.3 053 */ 054public class GenericSSLSocketFactory 055 extends SimpleLogSource 056 implements ISOServerSocketFactory,ISOClientSocketFactory, Configurable 057{ 058 /** Default constructor; no instance state to initialise. */ 059 public GenericSSLSocketFactory() {} 060 061 private SSLContext sslc=null; 062 private SSLServerSocketFactory serverFactory=null; 063 private SSLSocketFactory socketFactory=null; 064 065 private String keyStore=null; 066 private String password=null; 067 private String keyPassword=null; 068 private String serverName; 069 private boolean clientAuthNeeded=false; 070 private boolean serverAuthNeeded=false; 071 private String[] enabledCipherSuites; 072 private String[] enabledProtocols; 073 074 private Configuration cfg; 075 076 /** 077 * Sets the path of the JKS key store used for the TLS handshake. 078 * 079 * @param keyStore filesystem path of the JKS key store 080 */ 081 public void setKeyStore(String keyStore){ 082 this.keyStore=keyStore; 083 } 084 085 /** 086 * Sets the key store password. 087 * 088 * @param password key store password 089 */ 090 public void setPassword(String password){ 091 this.password=password; 092 } 093 094 /** 095 * Sets the password protecting the private key entry. 096 * 097 * @param keyPassword password protecting the private key entry 098 */ 099 public void setKeyPassword(String keyPassword){ 100 this.keyPassword=keyPassword; 101 } 102 103 /** 104 * Sets the Common Name (CN) used to verify the peer certificate. 105 * 106 * @param serverName expected Common Name (CN) of the peer certificate 107 */ 108 public void setServerName(String serverName){ 109 this.serverName=serverName; 110 } 111 112 /** 113 * Toggles whether accepted sockets require TLS client authentication. 114 * 115 * @param clientAuthNeeded require TLS client authentication on accepted sockets 116 */ 117 public void setClientAuthNeeded(boolean clientAuthNeeded){ 118 this.clientAuthNeeded=clientAuthNeeded; 119 } 120 121 /** 122 * Toggles whether outbound sockets validate the server certificate chain. 123 * 124 * @param serverAuthNeeded validate the server certificate chain on outbound sockets 125 */ 126 public void setServerAuthNeeded(boolean serverAuthNeeded){ 127 this.serverAuthNeeded=serverAuthNeeded; 128 } 129 130 private TrustManager[] getTrustManagers(KeyStore ks) 131 throws GeneralSecurityException { 132 if (serverAuthNeeded) { 133 TrustManagerFactory tm = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 134 tm.init( ks ); 135 return tm.getTrustManagers(); 136 } else { 137 // Create a trust manager that does not validate certificate chains 138 return new TrustManager[]{ 139 new X509TrustManager() { 140 public java.security.cert.X509Certificate[] getAcceptedIssuers() { 141 return new java.security.cert.X509Certificate[] {}; 142 } 143 public void checkClientTrusted( 144 java.security.cert.X509Certificate[] certs, String authType) { 145 } 146 public void checkServerTrusted( 147 java.security.cert.X509Certificate[] certs, String authType) { 148 } 149 } 150 }; 151 } 152 } 153 154 /** 155 * Create a SSLSocket Context 156 * @return the SSLContext 157 * @returns null if exception occurrs 158 */ 159 private SSLContext getSSLContext() throws ISOException { 160 if(password==null) password=getPassword(); 161 if(keyPassword ==null) keyPassword=getKeyPassword(); 162 if(keyStore==null || keyStore.length()==0) { 163 keyStore=System.getProperty("user.home")+File.separator+".keystore"; 164 } 165 166 try{ 167 KeyStore ks = KeyStore.getInstance( "JKS" ); 168 FileInputStream fis = new FileInputStream (new File (keyStore)); 169 ks.load(fis,password.toCharArray()); 170 fis.close(); 171 KeyManagerFactory km = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); 172 km.init( ks, keyPassword.toCharArray() ); 173 KeyManager[] kma = km.getKeyManagers(); 174 TrustManager[] tma = getTrustManagers( ks ); 175 SSLContext sslc = SSLContext.getInstance( "TLS" ); 176 sslc.init( kma, tma, new SecureRandom() ); 177 return sslc; 178 } catch(Exception e) { 179 throw new ISOException (e); 180 } finally { 181 password=null; 182 keyPassword=null; 183 } 184 } 185 186 /** 187 * Create a socket factory 188 * @return the socket factory 189 * @exception ISOException if an error occurs during server socket 190 * creation 191 */ 192 protected SSLServerSocketFactory createServerSocketFactory() 193 throws ISOException 194 { 195 if(sslc==null) sslc=getSSLContext(); 196 return sslc.getServerSocketFactory(); 197 } 198 199 /** 200 * Create a socket factory 201 * @return the socket factory 202 * @exception ISOException if an error occurs during server socket 203 * creation 204 */ 205 protected SSLSocketFactory createSocketFactory() 206 throws ISOException 207 { 208 if(sslc==null) sslc=getSSLContext(); 209 return sslc.getSocketFactory(); 210 } 211 212 /** 213 * Create a server socket on the specified port (port 0 indicates 214 * an anonymous port). 215 * @param port the port number 216 * @return the server socket on the specified port 217 * @exception IOException should an I/O error occurs during 218 * @exception ISOException should an error occurs during 219 * creation 220 */ 221 public ServerSocket createServerSocket(int port) 222 throws IOException, ISOException 223 { 224 if(serverFactory==null) serverFactory=createServerSocketFactory(); 225 ServerSocket socket = serverFactory.createServerSocket(port); 226 SSLServerSocket serverSocket = (SSLServerSocket) socket; 227 serverSocket.setNeedClientAuth(clientAuthNeeded); 228 if (enabledCipherSuites != null && enabledCipherSuites.length > 0) { 229 serverSocket.setEnabledCipherSuites(enabledCipherSuites); 230 } 231 if (enabledProtocols != null && enabledProtocols.length > 0) { 232 serverSocket.setEnabledProtocols(enabledProtocols); 233 } 234 return socket; 235 } 236 237 /** 238 * Create a client socket connected to the specified host and port. 239 * @param host the host name 240 * @param port the port number 241 * @return a socket connected to the specified host and port. 242 * @exception IOException if an I/O error occurs during socket creation 243 * @exception ISOException should any other error occurs 244 */ 245 public Socket createSocket(String host, int port) 246 throws IOException, ISOException 247 { 248 if(socketFactory==null) socketFactory=createSocketFactory(); 249 SSLSocket s = (SSLSocket) socketFactory.createSocket(host,port); 250 verifyHostname(s); 251 return s; 252 } 253 254 /** 255 * Verify that serverName and CN equals. 256 * 257 * <pre> 258 * Origin: jakarta-commons/httpclient 259 * File: StrictSSLProtocolSocketFactory.java 260 * Revision: 1.5 261 * License: Apache-2.0 262 * </pre> 263 * 264 * @param socket a SSLSocket value 265 * @exception SSLPeerUnverifiedException If there are problems obtaining 266 * the server certificates from the SSL session, or the server host name 267 * does not match with the "Common Name" in the server certificates 268 * SubjectDN. 269 * @exception UnknownHostException If we are not able to resolve 270 * the SSL sessions returned server host name. 271 */ 272 private void verifyHostname(SSLSocket socket) 273 throws SSLPeerUnverifiedException, UnknownHostException 274 { 275 if (!serverAuthNeeded) { 276 return; 277 } 278 279 SSLSession session = socket.getSession(); 280 281 if (serverName==null || serverName.length()==0) { 282 serverName = session.getPeerHost(); 283 try { 284 InetAddress addr = InetAddress.getByName(serverName); 285 } catch (UnknownHostException uhe) { 286 throw new UnknownHostException("Could not resolve SSL " + 287 "server name " + serverName); 288 } 289 } 290 291 292 Certificate[] certs = session.getPeerCertificates(); 293 if (certs==null || certs.length==0) 294 throw new SSLPeerUnverifiedException("No server certificates found"); 295 296 if (!(certs[0] instanceof X509Certificate cert)) 297 throw new SSLPeerUnverifiedException("Server certificate is not X.509"); 298 299 String cn = getCN(cert); 300 if (!serverName.equalsIgnoreCase(cn)) { 301 throw new SSLPeerUnverifiedException("Invalid SSL server name. "+ 302 "Expected '" + serverName + 303 "', got '" + cn + "'"); 304 } 305 } 306 307 /** 308 * Extracts the Common Name (CN) from an X.509 certificate's subject DN 309 * using the standard {@link X500Principal} and {@link LdapName} APIs, 310 * which correctly handle quoting and escaping per RFC 2253. 311 * 312 * @param cert an X.509 certificate 313 * @return the value of the "Common Name" field, or null if not present. 314 */ 315 private String getCN(X509Certificate cert) throws SSLPeerUnverifiedException { 316 try { 317 X500Principal principal = cert.getSubjectX500Principal(); 318 LdapName ln = new LdapName(principal.getName(X500Principal.RFC2253)); 319 for (Rdn rdn : ln.getRdns()) { 320 if ("CN".equalsIgnoreCase(rdn.getType())) { 321 return rdn.getValue().toString(); 322 } 323 } 324 return null; 325 } catch (InvalidNameException e) { 326 throw new SSLPeerUnverifiedException("Invalid subject DN: " + e.getMessage()); 327 } 328 } 329 330 /** 331 * Returns the path of the configured JKS key store. 332 * 333 * @return filesystem path of the JKS key store 334 */ 335 public String getKeyStore() { 336 return keyStore; 337 } 338 339 /** 340 * Hook returning the key store password. 341 * Subclasses are expected to override this to source the password 342 * from a secret manager rather than a system property. 343 * 344 * @return key store password 345 */ 346 protected String getPassword() { 347 return System.getProperty("jpos.ssl.storepass", "password"); 348 } 349 350 /** 351 * Hook returning the private-key entry password. 352 * Subclasses are expected to override this to source the password 353 * from a secret manager rather than a system property. 354 * 355 * @return private-key entry password 356 */ 357 protected String getKeyPassword() { 358 return System.getProperty("jpos.ssl.keypass", "password"); 359 } 360 361 /** 362 * Returns the configured peer certificate Common Name. 363 * 364 * @return expected Common Name (CN) of the peer certificate 365 */ 366 public String getServerName() { 367 return serverName; 368 } 369 370 /** 371 * Returns whether accepted sockets require TLS client authentication. 372 * 373 * @return {@code true} when accepted sockets require TLS client authentication 374 */ 375 public boolean getClientAuthNeeded() { 376 return clientAuthNeeded; 377 } 378 379 /** 380 * Returns whether outbound sockets validate the server certificate chain. 381 * 382 * @return {@code true} when outbound sockets validate the server certificate chain 383 */ 384 public boolean getServerAuthNeeded() { 385 return serverAuthNeeded; 386 } 387 388 /** 389 * Sets the explicit list of TLS cipher suites enabled on created sockets. 390 * 391 * @param enabledCipherSuites cipher suites to enable on created sockets; 392 * {@code null} or empty leaves provider defaults in place 393 */ 394 public void setEnabledCipherSuites(String[] enabledCipherSuites) { 395 this.enabledCipherSuites = enabledCipherSuites; 396 } 397 398 /** 399 * Returns the explicit list of TLS cipher suites enabled on created sockets. 400 * 401 * @return cipher suites enabled on created sockets, or {@code null} when provider defaults apply 402 */ 403 public String[] getEnabledCipherSuites() { 404 return enabledCipherSuites; 405 } 406 407 408 public void setConfiguration(Configuration cfg) throws ConfigurationException { 409 this.cfg = cfg; 410 keyStore = cfg.get("keystore"); 411 clientAuthNeeded = cfg.getBoolean("clientauth"); 412 serverAuthNeeded = cfg.getBoolean("serverauth"); 413 serverName = cfg.get("servername"); 414 password = cfg.get("storepassword", null); 415 keyPassword = cfg.get("keypassword", null); 416 enabledCipherSuites = cfg.getAll("addEnabledCipherSuite"); 417 enabledProtocols = cfg.getAll("addEnabledProtocol"); 418 } 419 /** 420 * Returns the configuration applied via {@link #setConfiguration(Configuration)}. 421 * 422 * @return active configuration, or {@code null} if not yet configured 423 */ 424 public Configuration getConfiguration() { 425 return cfg; 426 } 427}