001/* 002 * ==================================================================== 003 * 004 * Copyright 1999-2006 The Apache Software Foundation 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); 007 * you may not use this file except in compliance with the License. 008 * You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 * ==================================================================== 018 * 019 * This software consists of voluntary contributions made by many 020 * individuals on behalf of the Apache Software Foundation. For more 021 * information on the Apache Software Foundation, please see 022 * <http://www.apache.org/>. 023 * 024 */ 025 026package org.apache.commons.httpclient.contrib.ssl; 027 028import org.apache.commons.ssl.HttpSecureProtocol; 029import org.apache.commons.ssl.KeyMaterial; 030import org.apache.commons.ssl.TrustMaterial; 031 032import java.io.IOException; 033import java.net.Socket; 034import java.security.GeneralSecurityException; 035import java.security.KeyManagementException; 036import java.security.KeyStoreException; 037import java.security.NoSuchAlgorithmException; 038import java.security.cert.CertificateException; 039 040/** 041 * <p/> 042 * TrustSSLProtocolSocketFactory allows you exercise full control over the 043 * HTTPS server certificates you are going to trust. Instead of relying 044 * on the Certificate Authorities already present in "jre/lib/security/cacerts", 045 * TrustSSLProtocolSocketFactory only trusts the public certificates you provide 046 * to its constructor. 047 * </p> 048 * <p/> 049 * TrustSSLProtocolSocketFactory can be used to create SSL {@link Socket}s 050 * that accepts self-signed certificates. Unlike EasySSLProtocolSocketFactory, 051 * TrustSSLProtocolSocketFactory can be used in production. This is because 052 * it forces you to pre-install the self-signed certificate you are going to 053 * trust locally. 054 * <p/> 055 * TrustSSLProtocolSocketFactory can parse both Java Keystore Files (*.jks) 056 * and base64 PEM encoded public certificates (*.pem). 057 * </p> 058 * <p/> 059 * Example of using TrustSSLProtocolSocketFactory 060 * <pre> 061 * 1. First we must find the certificate we want to trust. In this example 062 * we'll use gmail.google.com's certificate. 063 * <p/> 064 * openssl s_client -showcerts -connect gmail.google.com:443 065 * <p/> 066 * 2. Cut & paste into a "cert.pem" any certificates you are interested in 067 * trusting in accordance with your security policies. In this example I'll 068 * actually use the current "gmail.google.com" certificate (instead of the 069 * Thawte CA certificate that signed the gmail certificate - that would be 070 * too boring) - but it expires on June 7th, 2006, so this example won't be 071 * useful for very long! 072 * <p/> 073 * Here's what my "cert.pem" file looks like: 074 * <p/> 075 * -----BEGIN CERTIFICATE----- 076 * MIIDFjCCAn+gAwIBAgIDP3PeMA0GCSqGSIb3DQEBBAUAMEwxCzAJBgNVBAYTAlpB 077 * MSUwIwYDVQQKExxUaGF3dGUgQ29uc3VsdGluZyAoUHR5KSBMdGQuMRYwFAYDVQQD 078 * Ew1UaGF3dGUgU0dDIENBMB4XDTA1MDYwNzIyMTI1N1oXDTA2MDYwNzIyMTI1N1ow 079 * ajELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDU1v 080 * dW50YWluIFZpZXcxEzARBgNVBAoTCkdvb2dsZSBJbmMxGTAXBgNVBAMTEGdtYWls 081 * Lmdvb2dsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALoRiWYW0hZw 082 * 9TSn3s9912syZg1CP2TaC86PU1Ao2qf3pVu7Mx10Wl8W+aKZrQlvrYjTwku4sEh+ 083 * 9uI+gWnfmCd0OyVcXr1eFOGCYiiyaPv79Wtb0m0d8GuiRSJhYkZGzGlgFViws2vR 084 * BAMCD2fdp7WGJUVGYOO+s52dgAMUHQXxAgMBAAGjgecwgeQwKAYDVR0lBCEwHwYI 085 * KwYBBQUHAwEGCCsGAQUFBwMCBglghkgBhvhCBAEwNgYDVR0fBC8wLTAroCmgJ4Yl 086 * aHR0cDovL2NybC50aGF3dGUuY29tL1RoYXd0ZVNHQ0NBLmNybDByBggrBgEFBQcB 087 * AQRmMGQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnRoYXd0ZS5jb20wPgYIKwYB 088 * BQUHMAKGMmh0dHA6Ly93d3cudGhhd3RlLmNvbS9yZXBvc2l0b3J5L1RoYXd0ZV9T 089 * R0NfQ0EuY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQEEBQADgYEAktM1l1cV 090 * ebi+Uo6fCE/eLnvvY6QbNNCsU5Pi9B5E1BlEUG+AGpgzE2cSPw1N4ZZb+2AWWwjx 091 * H8/IrJ143KZZXM49ri3Z2e491Jj8qitrMauT7/hb16Jw6I02/74/do4TtHu/Eifr 092 * EZCaSOobSHGeufHjlqlC3ehC4Bx4mLexIMk= 093 * -----END CERTIFICATE----- 094 * <p/> 095 * 3. Run "openssl x509" to analyze the certificate more deeply. This helps 096 * us answer questions like "Do we really want to trust it? When does it 097 * expire? What's the value of the CN (Common Name) field?". 098 * <p/> 099 * "openssl x509" is also super cool, and will impress all your friends, 100 * coworkers, family, and that cute girl at the starbucks. :-) 101 * <p/> 102 * If you dig through "man x509" you'll find this example. Run it: 103 * <p/> 104 * openssl x509 -in cert.pem -noout -text 105 * <p/> 106 * 4. Rename "cert.pem" to "gmail.pem" so that step 5 works. 107 * <p/> 108 * 5. Setup the TrustSSLProtocolSocketFactory to trust "gmail.google.com" 109 * for URLS of the form "https-gmail://" - but don't trust anything else 110 * when using "https-gmail://": 111 * <p/> 112 * TrustSSLProtocolSocketFactory sf = new TrustSSLProtocolSocketFactory( "/path/to/gmail.pem" ); 113 * Protocol trustHttps = new Protocol("https-gmail", sf, 443); 114 * Protocol.registerProtocol("https-gmail", trustHttps); 115 * <p/> 116 * HttpClient client = new HttpClient(); 117 * GetMethod httpget = new GetMethod("https-gmail://gmail.google.com/"); 118 * client.executeMethod(httpget); 119 * <p/> 120 * 6. Notice that "https-gmail://" cannot connect to "www.wellsfargo.com" - 121 * the server's certificate isn't trusted! It would still work using 122 * regular "https://" because Java would use the "jre/lib/security/cacerts" 123 * file. 124 * <p/> 125 * httpget = new GetMethod("https-gmail://www.wellsfargo.com/"); 126 * client.executeMethod(httpget); 127 * <p/> 128 * javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: No trusted certificate found 129 * <p/> 130 * <p/> 131 * 7. Of course "https-gmail://" cannot connect to hosts where the CN field 132 * in the certificate doesn't match the hostname. The same is supposed to 133 * be true of regular "https://", but HTTPClient is a bit lenient. 134 * <p/> 135 * httpget = new GetMethod("https-gmail://gmail.com/"); 136 * client.executeMethod(httpget); 137 * <p/> 138 * javax.net.ssl.SSLException: hostname in certificate didn't match: <gmail.com> != <gmail.google.com> 139 * <p/> 140 * <p/> 141 * 8. You can use "*.jks" files instead of "*.pem" if you prefer. Use the 2nd constructor 142 * in that case to pass along the JKS password: 143 * <p/> 144 * new TrustSSLProtocolSocketFactory( "/path/to/gmail.jks", "my_password".toCharArray() ); 145 * <p/> 146 * </pre> 147 * 148 * @author Credit Union Central of British Columbia 149 * @author <a href="http://www.cucbc.com/">www.cucbc.com</a> 150 * @author <a href="mailto:juliusdavies@cucbc.com">juliusdavies@cucbc.com</a> 151 * <p/> 152 * <p/> 153 * DISCLAIMER: HttpClient developers DO NOT actively support this component. 154 * The component is provided as a reference material, which may be inappropriate 155 * for use without additional customization. 156 * </p> 157 * @since 17-Feb-2006 158 */ 159 160public class TrustSSLProtocolSocketFactory extends HttpSecureProtocol { 161 162 /** 163 * @param pathToTrustStore Path to either a ".jks" Java Key Store, or a 164 * ".pem" base64 encoded certificate. If it's a 165 * ".pem" base64 certificate, the file must start 166 * with "------BEGIN CERTIFICATE-----", and must end 167 * with "-------END CERTIFICATE--------". 168 */ 169 public TrustSSLProtocolSocketFactory(String pathToTrustStore) 170 throws GeneralSecurityException, IOException { 171 this(pathToTrustStore, null); 172 } 173 174 /** 175 * @param pathToTrustStore Path to either a ".jks" Java Key Store, or a 176 * ".pem" base64 encoded certificate. If it's a 177 * ".pem" base64 certificate, the file must start 178 * with "------BEGIN CERTIFICATE-----", and must end 179 * with "-------END CERTIFICATE--------". 180 * @param password Password to open the ".jks" file. If "truststore" 181 * is a ".pem" file, then password can be null; if 182 * password isn't null and we're using a ".pem" file, 183 * then technically, this becomes the password to 184 * open up the special in-memory keystore we create 185 * to hold the ".pem" file, but it's not important at 186 * all. 187 * @throws CertificateException 188 * @throws KeyStoreException 189 * @throws IOException 190 * @throws NoSuchAlgorithmException 191 * @throws KeyManagementException 192 */ 193 public TrustSSLProtocolSocketFactory(String pathToTrustStore, char[] password) 194 throws GeneralSecurityException, IOException { 195 super(); 196 TrustMaterial tm; 197 try { 198 tm = new KeyMaterial(pathToTrustStore, password); 199 } catch (KeyStoreException kse) { 200 // KeyMaterial constructor blows up in no keys found, 201 // so we fall back to TrustMaterial constructor instead. 202 tm = new TrustMaterial(pathToTrustStore, password); 203 } 204 super.setTrustMaterial(tm); 205 } 206 207}