001/* 002 * $HeadURL: http://juliusdavies.ca/svn/not-yet-commons-ssl/tags/commons-ssl-0.3.16/src/java/org/apache/commons/ssl/SSL.java $ 003 * $Revision: 180 $ 004 * $Date: 2014-09-23 11:33:47 -0700 (Tue, 23 Sep 2014) $ 005 * 006 * ==================================================================== 007 * Licensed to the Apache Software Foundation (ASF) under one 008 * or more contributor license agreements. See the NOTICE file 009 * distributed with this work for additional information 010 * regarding copyright ownership. The ASF licenses this file 011 * to you under the Apache License, Version 2.0 (the 012 * "License"); you may not use this file except in compliance 013 * with the License. You may obtain a copy of the License at 014 * 015 * http://www.apache.org/licenses/LICENSE-2.0 016 * 017 * Unless required by applicable law or agreed to in writing, 018 * software distributed under the License is distributed on an 019 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 020 * KIND, either express or implied. See the License for the 021 * specific language governing permissions and limitations 022 * under the License. 023 * ==================================================================== 024 * 025 * This software consists of voluntary contributions made by many 026 * individuals on behalf of the Apache Software Foundation. For more 027 * information on the Apache Software Foundation, please see 028 * <http://www.apache.org/>. 029 * 030 */ 031 032package org.apache.commons.ssl; 033 034import javax.net.SocketFactory; 035import javax.net.ssl.*; 036import java.io.File; 037import java.io.IOException; 038import java.net.InetAddress; 039import java.net.ServerSocket; 040import java.net.Socket; 041import java.net.UnknownHostException; 042import java.security.GeneralSecurityException; 043import java.security.KeyManagementException; 044import java.security.KeyStoreException; 045import java.security.NoSuchAlgorithmException; 046import java.security.cert.CertificateException; 047import java.security.cert.X509Certificate; 048import java.util.*; 049 050/** 051 * Not thread-safe. (But who would ever share this thing across multiple 052 * threads???) 053 * 054 * @author Credit Union Central of British Columbia 055 * @author <a href="http://www.cucbc.com/">www.cucbc.com</a> 056 * @author <a href="mailto:juliusdavies@cucbc.com">juliusdavies@cucbc.com</a> 057 * @since May 1, 2006 058 */ 059public class SSL { 060 private final static String[] KNOWN_PROTOCOLS = 061 {"TLSv1.2", "TLSv1.1", "TLSv1", "SSLv3", "SSLv2", "SSLv2Hello"}; 062 063 // SUPPORTED_CIPHERS_ARRAY is initialized in the static constructor. 064 private final static String[] SUPPORTED_CIPHERS; 065 066 public final static SortedSet KNOWN_PROTOCOLS_SET; 067 public final static SortedSet SUPPORTED_CIPHERS_SET; 068 069 static { 070 TreeSet<String> ts = new TreeSet<String>(Collections.reverseOrder()); 071 ts.addAll(Arrays.asList(KNOWN_PROTOCOLS)); 072 KNOWN_PROTOCOLS_SET = Collections.unmodifiableSortedSet(ts); 073 074 // SSLSocketFactory.getDefault() sometimes blocks on FileInputStream 075 // reads of "/dev/random" (Linux only?). You might find you system 076 // stuck here. Move the mouse around a little! 077 SSLSocketFactory s = (SSLSocketFactory) SSLSocketFactory.getDefault(); 078 ts = new TreeSet<String>(); 079 SUPPORTED_CIPHERS = s.getSupportedCipherSuites(); 080 Arrays.sort(SUPPORTED_CIPHERS); 081 ts.addAll(Arrays.asList(SUPPORTED_CIPHERS)); 082 SUPPORTED_CIPHERS_SET = Collections.unmodifiableSortedSet(ts); 083 } 084 085 private Object sslContext = null; 086 private int initCount = 0; 087 private SSLSocketFactory socketFactory = null; 088 private SSLServerSocketFactory serverSocketFactory = null; 089 private HostnameVerifier hostnameVerifier = HostnameVerifier.DEFAULT; 090 private boolean isSecure = true; // if false, the client-style operations only create plain sockets. 091 private boolean checkHostname = true; 092 private boolean checkCRL = true; 093 private boolean checkExpiry = true; 094 private boolean useClientMode = false; 095 private boolean useClientModeDefault = true; 096 private int soTimeout = 24 * 60 * 60 * 1000; // default: one day 097 private int connectTimeout = 60 * 60 * 1000; // default: one hour 098 private TrustChain trustChain = null; 099 private KeyMaterial keyMaterial = null; 100 private String[] enabledCiphers = null; 101 private String[] enabledProtocols = null; 102 private String defaultProtocol = "TLS"; 103 private X509Certificate[] currentServerChain; 104 private X509Certificate[] currentClientChain; 105 private boolean wantClientAuth = true; 106 private boolean needClientAuth = false; 107 private SSLWrapperFactory sslWrapperFactory = SSLWrapperFactory.NO_WRAP; 108 private Map dnsOverride; 109 110 protected final boolean usingSystemProperties; 111 112 public SSL() 113 throws GeneralSecurityException, IOException { 114 boolean usingSysProps = false; 115 Properties props = System.getProperties(); 116 boolean ksSet = props.containsKey("javax.net.ssl.keyStore"); 117 boolean tsSet = props.containsKey("javax.net.ssl.trustStore"); 118 if (ksSet) { 119 String path = System.getProperty("javax.net.ssl.keyStore"); 120 String pwd = System.getProperty("javax.net.ssl.keyStorePassword"); 121 pwd = pwd != null ? pwd : ""; // JSSE default is "". 122 File f = new File(path); 123 if (f.exists()) { 124 KeyMaterial km = new KeyMaterial(path, pwd.toCharArray()); 125 setKeyMaterial(km); 126 usingSysProps = true; 127 } 128 } 129 boolean trustMaterialSet = false; 130 if (tsSet) { 131 String path = System.getProperty("javax.net.ssl.trustStore"); 132 String pwd = System.getProperty("javax.net.ssl.trustStorePassword"); 133 boolean pwdWasNull = pwd == null; 134 pwd = pwdWasNull ? "" : pwd; // JSSE default is "". 135 File f = new File(path); 136 if (f.exists()) { 137 TrustMaterial tm; 138 try { 139 tm = new TrustMaterial(path, pwd.toCharArray()); 140 } 141 catch (GeneralSecurityException gse) { 142 // Probably a bad password. If we're using the default password, 143 // let's try and survive this setback. 144 if (pwdWasNull) { 145 tm = new TrustMaterial(path); 146 } else { 147 throw gse; 148 } 149 } 150 151 setTrustMaterial(tm); 152 usingSysProps = true; 153 trustMaterialSet = true; 154 } 155 } 156 157 /* 158 No default trust material was set. We'll use the JSSE standard way 159 where we test for "JSSE_CACERTS" first, and then fall back on 160 "CACERTS". We could just leave TrustMaterial null, but then our 161 setCheckCRL() and setCheckExpiry() features won't work. We need a 162 non-null TrustMaterial object in order to intercept and decorate 163 the JVM's default TrustManager. 164 */ 165 if (!trustMaterialSet) { 166 setTrustMaterial(TrustMaterial.DEFAULT); 167 } 168 this.usingSystemProperties = usingSysProps; 169 dirtyAndReloadIfYoung(); 170 } 171 172 private void dirty() { 173 this.sslContext = null; 174 this.socketFactory = null; 175 this.serverSocketFactory = null; 176 } 177 178 private void dirtyAndReloadIfYoung() 179 throws NoSuchAlgorithmException, KeyStoreException, 180 KeyManagementException, IOException, CertificateException { 181 dirty(); 182 if (initCount >= 0 && initCount <= 5) { 183 // The first five init's we do early (before any sockets are 184 // created) in the hope that will trigger any explosions nice 185 // and early, with the correct exception type. 186 187 // After the first five init's, we revert to a regular 188 // dirty / init pattern, and the "init" happens very late: 189 // just before the socket is created. If badness happens, a 190 // wrapping RuntimeException will be thrown. 191 init(); 192 } 193 } 194 195 String dnsOverride(String host) { 196 if (dnsOverride != null && dnsOverride.containsKey(host)) { 197 String override = (String) dnsOverride.get(host); 198 if (override != null && !"".equals(override.trim())) { 199 return override; 200 } 201 } 202 return host; 203 } 204 205 public void setDnsOverride(Map m) { 206 this.dnsOverride = m; 207 } 208 209 public void setIsSecure(boolean b) { 210 this.isSecure = b; 211 } 212 213 public boolean isSecure() { 214 return isSecure; 215 } 216 217 public SSLContext getSSLContext() 218 throws GeneralSecurityException, IOException 219 220 { 221 Object obj = getSSLContextAsObject(); 222 if (JavaImpl.isJava13()) { 223 try { 224 return (SSLContext) obj; 225 } 226 catch (ClassCastException cce) { 227 throw new ClassCastException("When using Java13 SSL, you must call SSL.getSSLContextAsObject() - " + cce); 228 } 229 } 230 return (SSLContext) obj; 231 } 232 233 /** 234 * @return com.sun.net.ssl.SSLContext or javax.net.ssl.SSLContext depending 235 * on the JSSE implementation we're using. 236 * @throws GeneralSecurityException problem creating SSLContext 237 * @throws IOException problem creating SSLContext 238 */ 239 public Object getSSLContextAsObject() 240 throws GeneralSecurityException, IOException 241 242 { 243 if (sslContext == null) { 244 init(); 245 } 246 return sslContext; 247 } 248 249 public void addTrustMaterial(TrustChain trustChain) 250 throws NoSuchAlgorithmException, KeyStoreException, 251 KeyManagementException, IOException, CertificateException { 252 if (this.trustChain == null || trustChain == TrustMaterial.TRUST_ALL) { 253 this.trustChain = trustChain; 254 } else { 255 this.trustChain.addTrustMaterial(trustChain); 256 } 257 dirtyAndReloadIfYoung(); 258 } 259 260 public void setTrustMaterial(TrustChain trustChain) 261 throws NoSuchAlgorithmException, KeyStoreException, 262 KeyManagementException, IOException, CertificateException { 263 this.trustChain = trustChain; 264 dirtyAndReloadIfYoung(); 265 } 266 267 public void setKeyMaterial(KeyMaterial keyMaterial) 268 throws NoSuchAlgorithmException, KeyStoreException, 269 KeyManagementException, IOException, CertificateException { 270 this.keyMaterial = keyMaterial; 271 dirtyAndReloadIfYoung(); 272 } 273 274 public X509Certificate[] getAssociatedCertificateChain() { 275 if (keyMaterial != null) { 276 List list = keyMaterial.getAssociatedCertificateChains(); 277 return (X509Certificate[]) list.get(0); 278 } else { 279 return null; 280 } 281 } 282 283 public String[] getEnabledCiphers() { 284 return enabledCiphers != null ? enabledCiphers : getDefaultCipherSuites(); 285 } 286 287 public void setEnabledCiphers(String[] ciphers) { 288 HashSet<String> desired = new HashSet<String>(Arrays.asList(ciphers)); 289 desired.removeAll(SUPPORTED_CIPHERS_SET); 290 if (!desired.isEmpty()) { 291 throw new IllegalArgumentException("following ciphers not supported: " + desired); 292 } 293 this.enabledCiphers = ciphers; 294 } 295 296 public String[] getEnabledProtocols() { 297 return enabledProtocols; 298 } 299 300 public void setEnabledProtocols(String[] protocols) { 301 this.enabledProtocols = protocols; 302 } 303 304 public String getDefaultProtocol() { 305 return defaultProtocol; 306 } 307 308 public void setDefaultProtocol(String protocol) { 309 this.defaultProtocol = protocol; 310 dirty(); 311 } 312 313 public boolean getCheckHostname() { 314 return checkHostname; 315 } 316 317 public void setCheckHostname(boolean checkHostname) { 318 this.checkHostname = checkHostname; 319 } 320 321 public void setHostnameVerifier(HostnameVerifier verifier) { 322 if (verifier == null) { 323 verifier = HostnameVerifier.DEFAULT; 324 } 325 this.hostnameVerifier = verifier; 326 } 327 328 public HostnameVerifier getHostnameVerifier() { 329 return hostnameVerifier; 330 } 331 332 public boolean getCheckCRL() { 333 return checkCRL; 334 } 335 336 public void setCheckCRL(boolean checkCRL) { 337 this.checkCRL = checkCRL; 338 } 339 340 public boolean getCheckExpiry() { 341 return checkExpiry; 342 } 343 344 public void setCheckExpiry(boolean checkExpiry) { 345 this.checkExpiry = checkExpiry; 346 } 347 348 public void setSoTimeout(int soTimeout) { 349 if (soTimeout < 0) { 350 throw new IllegalArgumentException("soTimeout must not be negative"); 351 } 352 this.soTimeout = soTimeout; 353 } 354 355 public int getSoTimeout() { 356 return soTimeout; 357 } 358 359 public void setConnectTimeout(int connectTimeout) { 360 if (connectTimeout < 0) { 361 throw new IllegalArgumentException("connectTimeout must not be negative"); 362 } 363 this.connectTimeout = connectTimeout; 364 } 365 366 public void setUseClientMode(boolean useClientMode) { 367 this.useClientModeDefault = false; 368 this.useClientMode = useClientMode; 369 } 370 371 public boolean getUseClientModeDefault() { 372 return useClientModeDefault; 373 } 374 375 public boolean getUseClientMode() { 376 return useClientMode; 377 } 378 379 public void setWantClientAuth(boolean wantClientAuth) { 380 this.wantClientAuth = wantClientAuth; 381 } 382 383 public void setNeedClientAuth(boolean needClientAuth) { 384 this.needClientAuth = needClientAuth; 385 } 386 387 public boolean getWantClientAuth() { 388 return wantClientAuth; 389 } 390 391 public boolean getNeedClientAuth() { 392 return needClientAuth; 393 } 394 395 public SSLWrapperFactory getSSLWrapperFactory() { 396 return this.sslWrapperFactory; 397 } 398 399 public void setSSLWrapperFactory(SSLWrapperFactory wf) { 400 this.sslWrapperFactory = wf; 401 } 402 403 private void initThrowRuntime() { 404 try { 405 init(); 406 } 407 catch (GeneralSecurityException gse) { 408 throw JavaImpl.newRuntimeException(gse); 409 } 410 catch (IOException ioe) { 411 throw JavaImpl.newRuntimeException(ioe); 412 } 413 } 414 415 private void init() 416 throws NoSuchAlgorithmException, KeyStoreException, 417 KeyManagementException, IOException, CertificateException { 418 socketFactory = null; 419 serverSocketFactory = null; 420 this.sslContext = JavaImpl.init(this, trustChain, keyMaterial); 421 initCount++; 422 } 423 424 public void doPreConnectSocketStuff(Socket s) throws IOException { 425 if (s instanceof SSLSocket && !useClientModeDefault) { 426 ((SSLSocket) s).setUseClientMode(useClientMode); 427 } 428 if (soTimeout > 0) { 429 s.setSoTimeout(soTimeout); 430 } 431 if (s instanceof SSLSocket) { 432 if (enabledProtocols != null) { 433 JavaImpl.setEnabledProtocols(s, enabledProtocols); 434 } 435 if (enabledCiphers != null) { 436 ((SSLSocket) s).setEnabledCipherSuites(enabledCiphers); 437 } 438 } 439 } 440 441 public void doPostConnectSocketStuff(Socket s, String host) 442 throws IOException { 443 if (checkHostname && s instanceof SSLSocket) { 444 hostnameVerifier.check(host, (SSLSocket) s); 445 } 446 } 447 448 public Socket createSocket() throws IOException { 449 if (isSecure) { 450 return sslWrapperFactory.wrap(JavaImpl.createSocket(this)); 451 } else { 452 Socket s = SocketFactory.getDefault().createSocket(); 453 doPreConnectSocketStuff(s); 454 return s; 455 } 456 } 457 458 /** 459 * Attempts to get a new socket connection to the given host within the 460 * given time limit. 461 * 462 * @param remoteHost the host name/IP 463 * @param remotePort the port on the host 464 * @param localHost the local host name/IP to bind the socket to 465 * @param localPort the port on the local machine 466 * @param timeout the connection timeout (0==infinite) 467 * @return Socket a new socket 468 * @throws IOException if an I/O error occurs while creating the socket 469 * @throws UnknownHostException if the IP address of the host cannot be 470 * determined 471 */ 472 public Socket createSocket( 473 String remoteHost, int remotePort, InetAddress localHost, int localPort, int timeout 474 ) throws IOException { 475 // Only use our factory-wide connectTimeout if this method was passed 476 // in a timeout of 0 (infinite). 477 int factoryTimeout = getConnectTimeout(); 478 int connectTimeout = timeout == 0 ? factoryTimeout : timeout; 479 Socket s; 480 if (isSecure) { 481 s = JavaImpl.createSocket( 482 this, remoteHost, remotePort, localHost, localPort, connectTimeout 483 ); 484 } else { 485 s = JavaImpl.createPlainSocket( 486 this, remoteHost, remotePort, localHost, localPort, connectTimeout 487 ); 488 } 489 return sslWrapperFactory.wrap(s); 490 } 491 492 public Socket createSocket( 493 Socket s, String remoteHost, int remotePort, boolean autoClose 494 ) throws IOException { 495 SSLSocketFactory sf = getSSLSocketFactory(); 496 s = sf.createSocket(s, remoteHost, remotePort, autoClose); 497 doPreConnectSocketStuff(s); 498 doPostConnectSocketStuff(s, remoteHost); 499 return sslWrapperFactory.wrap(s); 500 } 501 502 public ServerSocket createServerSocket() throws IOException { 503 SSLServerSocket ss = JavaImpl.createServerSocket(this); 504 return getSSLWrapperFactory().wrap(ss, this); 505 } 506 507 /** 508 * Attempts to get a new socket connection to the given host within the 509 * given time limit. 510 * 511 * @param localHost the local host name/IP to bind against (null == ANY) 512 * @param port the port to listen on 513 * @param backlog number of connections allowed to queue up for accept(). 514 * @return SSLServerSocket a new server socket 515 * @throws IOException if an I/O error occurs while creating thesocket 516 */ 517 public ServerSocket createServerSocket(int port, int backlog, 518 InetAddress localHost) 519 throws IOException { 520 SSLServerSocketFactory f = getSSLServerSocketFactory(); 521 ServerSocket ss = f.createServerSocket(port, backlog, localHost); 522 SSLServerSocket s = (SSLServerSocket) ss; 523 doPreConnectServerSocketStuff(s); 524 return getSSLWrapperFactory().wrap(s, this); 525 } 526 527 public void doPreConnectServerSocketStuff(SSLServerSocket s) 528 throws IOException { 529 if (soTimeout > 0) { 530 s.setSoTimeout(soTimeout); 531 } 532 if (enabledProtocols != null) { 533 JavaImpl.setEnabledProtocols(s, enabledProtocols); 534 } 535 if (enabledCiphers != null) { 536 s.setEnabledCipherSuites(enabledCiphers); 537 } 538 539 /* 540 setNeedClientAuth( false ) has an annoying side effect: it seems to 541 reset setWantClient( true ) back to to false. So I do things this 542 way to make sure setting things "true" happens after setting things 543 "false" - giving "true" priority. 544 */ 545 if (!wantClientAuth) { 546 JavaImpl.setWantClientAuth(s, false); 547 } 548 if (!needClientAuth) { 549 s.setNeedClientAuth(false); 550 } 551 if (wantClientAuth) { 552 JavaImpl.setWantClientAuth(s, true); 553 } 554 if (needClientAuth) { 555 s.setNeedClientAuth(true); 556 } 557 } 558 559 public SSLSocketFactory getSSLSocketFactory() { 560 if (sslContext == null) { 561 initThrowRuntime(); 562 } 563 if (socketFactory == null) { 564 socketFactory = JavaImpl.getSSLSocketFactory(sslContext); 565 } 566 return socketFactory; 567 } 568 569 public SSLServerSocketFactory getSSLServerSocketFactory() { 570 if (sslContext == null) { 571 initThrowRuntime(); 572 } 573 if (serverSocketFactory == null) { 574 serverSocketFactory = JavaImpl.getSSLServerSocketFactory(sslContext); 575 } 576 return serverSocketFactory; 577 } 578 579 public int getConnectTimeout() { 580 return connectTimeout; 581 } 582 583 public String[] getDefaultCipherSuites() { 584 return getSSLSocketFactory().getDefaultCipherSuites(); 585 } 586 587 public String[] getSupportedCipherSuites() { 588 String[] s = new String[SUPPORTED_CIPHERS.length]; 589 System.arraycopy(SUPPORTED_CIPHERS, 0, s, 0, s.length); 590 return s; 591 } 592 593 public TrustChain getTrustChain() { 594 return trustChain; 595 } 596 597 public void setCurrentServerChain(X509Certificate[] chain) { 598 this.currentServerChain = chain; 599 } 600 601 public void setCurrentClientChain(X509Certificate[] chain) { 602 this.currentClientChain = chain; 603 } 604 605 public X509Certificate[] getCurrentServerChain() { 606 return currentServerChain; 607 } 608 609 public X509Certificate[] getCurrentClientChain() { 610 return currentClientChain; 611 } 612}