We are using a slightly modified AuthSSLProtocolSocketFactory 
provided as a sample on the apache commons file server (attached).

I tried this on both 1.4.2 and 1.5.0_03.


-----Original Message-----
From: Oleg Kalnichevski [mailto:[EMAIL PROTECTED] 
Sent: Tuesday, August 09, 2005 3:19 PM
To: HttpClient User Discussion
Subject: Re: manager, timeout, and SSL - PLEASE HELP!!!

Ivan,
(1) Please post the source code of the AuthSSLProtocolSocketFactory class
(2) Let me know which JRE you you using. Connection timeouts are handled
completely differently for JRE < 1.4 and those >= 1.4

I'll look at it tomorrow. It is kind of late here

Oleg


On Tue, 2005-08-09 at 15:08 -0700, Ivan B wrote:
> I would appreciate anyone's input on this it is driving me crazy.
> 
> Basically, we are accessing a host that has personal cert installed 
> over https. In the sample code bellow everything works just fine until 
> I uncomment the commented line. Once I do that I get "Peer not 
> verified" exception.
> 
> 
> 
> public SmapleTestCraft() throws Exception {
>         CertificateManager certManager = CertificateManager.getInstance();
>         ProtocolSocketFactory sf = new 
> AuthSSLProtocolSocketFactory(certManager);;
>         
>         Protocol.registerProtocol("https", new Protocol("https", sf, 
> 443));
>         
>         MultiThreadedHttpConnectionManager connectionManager = new 
> MultiThreadedHttpConnectionManager();
>         // THIS LINE BREAKS EVERYTHING, BUT WE NEED TIMEOUT VALUE
>         //connectionManager.getParams().setConnectionTimeout(5000);
>         
>         HttpClient httpclient = new HttpClient(connectionManager);
>  
> httpclient.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBIL
> ITY);
>         
>         GetMethod httpget = new 
> GetMethod("https://127.0.0.1/banner.txt";);
>         
>         try {
>             httpclient.executeMethod(httpget);
>             System.out.println(httpget.getStatusLine());
>         } finally {
>             httpget.releaseConnection();
>         }
>     }
> 
> 
> 
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [EMAIL PROTECTED]
> For additional commands, e-mail: 
> [EMAIL PROTECTED]
> 
> 


---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]
/*
 * $Revision: 1.5 $
 * $Date: 2005/08/09 00:19:38 $
 *
 * ====================================================================
 *
 *  Copyright 2002-2004 The Apache Software Foundation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 */

package com.infoblox.client;

import java.io.IOException;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URL;
import java.net.UnknownHostException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Enumeration;

import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.KeyManager; 
import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import 
javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import javax.swing.JOptionPane;

import org.apache.commons.httpclient.ConnectTimeoutException;
import org.apache.commons.httpclient.params.HttpConnectionParams;
import org.apache.commons.httpclient.protocol.ControllerThreadSocketFactory;
import org.apache.commons.httpclient.protocol.ReflectionSocketFactory;
import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;

import com.infoblox.util.IDebug;

/**
 * <p>
 * AuthSSLProtocolSocketFactory can be used to validate the identity of the 
HTTPS
 * server against a list of trusted certificates and to authenticate to the 
HTTPS
 * server using a private key.
 * </p>
 *
 * <p>
 * AuthSSLProtocolSocketFactory will enable server authentication when supplied 
with
 * a [EMAIL PROTECTED] KeyStore truststore} file containg one or several 
trusted certificates.
 * The client secure socket will reject the connection during the SSL session 
handshake
 * if the target HTTPS server attempts to authenticate itself with a non-trusted
 * certificate.
 * </p>
 *
 * <p>
 * Use JDK keytool utility to import a trusted certificate and generate 
a truststore file:    
 *    <pre>
 *     keytool -import -alias "my server cert" -file server.crt 
-keystore my.truststore
 *    </pre>
 * </p>
 *
 * <p>
 * AuthSSLProtocolSocketFactory will enable client authentication when supplied 
with
 * a [EMAIL PROTECTED] KeyStore keystore} file containg a private key/public 
certificate pair.
 * The client secure socket will use the private key to authenticate itself to 
the target
 * HTTPS server during the SSL session handshake if requested to do so by the 
server.
 * The target HTTPS server will in its turn verify the certificate presented by 
the client
 * in order to establish client's authenticity
 * </p>
 *
 * <p>
 * Use the following sequence of actions to generate a keystore file
 * </p>
 *   <ul>
 *     <li>
 *      <p>
 *      Use JDK keytool utility to generate a new key
 *      <pre>keytool -genkey -v -alias "my client key" -validity 365 
-keystore my.keystore</pre>
 *      For simplicity use the same password for the key as that of the 
keystore
 *      </p>
 *     </li>
 *     <li>
 *      <p>
 *      Issue a certificate signing request (CSR)
 *      <pre>keytool -certreq -alias "my client key" -file mycertreq.csr 
-keystore my.keystore</pre>
 *     </p>
 *     </li>
 *     <li>
 *      <p>
 *      Send the certificate request to the trusted Certificate 
Authority for signature.
 *      One may choose to act as her own CA and sign the certificate 
request using a PKI
 *      tool, such as OpenSSL.
 *      </p>
 *     </li>
 *     <li>
 *      <p>
 *       Import the trusted CA root certificate
 *       <pre>keytool -import -alias "my trusted ca" -file caroot.crt 
-keystore my.keystore</pre>
 *      </p>
 *     </li>
 *     <li>
 *      <p>
 *       Import the PKCS#7 file containg the complete certificate chain
 *       <pre>keytool -import -alias "my client key" -file mycert.p7 
-keystore my.keystore</pre>
 *      </p>
 *     </li>
 *     <li>
 *      <p>
 *       Verify the content the resultant keystore file
 *       <pre>keytool -list -v -keystore my.keystore</pre>
 *      </p>
 *     </li>
 *   </ul>
 * <p>
 * Example of using custom protocol socket factory for a specific host:
 *     <pre>
 *     Protocol authhttps = new Protocol("https",  
 *          new AuthSSLProtocolSocketFactory(
 *              new URL("file:my.keystore"), "mypassword",
 *              new URL("file:my.truststore"), "mypassword"), 443);
 *
 *     HttpClient client = new HttpClient();
 *     client.getHostConfiguration().setHost("localhost", 443, authhttps);
 *     // use relative url only
 *     GetMethod httpget = new GetMethod("/");
 *     client.executeMethod(httpget);
 *     </pre>
 * </p>
 * <p>
 * Example of using custom protocol socket factory per default instead of the 
standard one:
 *     <pre>
 *     Protocol authhttps = new Protocol("https",  
 *          new AuthSSLProtocolSocketFactory(
 *              new URL("file:my.keystore"), "mypassword",
 *              new URL("file:my.truststore"), "mypassword"), 443);
 *     Protocol.registerProtocol("https", authhttps);
 *
 *     HttpClient client = new HttpClient();
 *     GetMethod httpget = new GetMethod("https://localhost/";);
 *     client.executeMethod(httpget);
 *     </pre>
 * </p>
 * @author <a href="mailto:oleg -at- ural.ru">Oleg Kalnichevski</a>
 *
 * <p>
 * DISCLAIMER: HttpClient developers DO NOT actively support this component.
 * The component is provided as a reference material, which may be inappropriate
 * to be used without additional customization.
 * </p>
 */

public class AuthSSLProtocolSocketFactory implements 
SecureProtocolSocketFactory {

    private static final String USER_DECLINED = "User Declined to continue";

    private static final String LINE_SEP = System.getProperty("line.separator");

    /** Host name verify flag. */
    private boolean verifyHostname = true;
    
    private boolean verified = false;
    
    private URL keystoreUrl = null;
    private String keystorePassword = null;
    private URL truststoreUrl = null;
    private String truststorePassword = null;
    private SSLContext sslcontext = null;

    /**
     * Constructor for AuthSSLProtocolSocketFactory. Either a keystore or 
truststore file
     * must be given. Otherwise SSL context initialization error will result.
     *
     * @param keystoreUrl URL of the keystore file. May be <tt>null</tt> if 
HTTPS client
     *        authentication is not to be used.
     * @param keystorePassword Password to unlock the keystore. 
IMPORTANT: this implementation
     *        assumes that the same password is used to protect the key 
and the keystore itself.
     * @param truststoreUrl URL of the truststore file. May be <tt>null</tt> if 
HTTPS server
     *        authentication is not to be used.
     * @param truststorePassword Password to unlock the truststore.
     * @deprecated by [EMAIL PROTECTED]
#AuthSSLProtocolSocketFactory(CertificateManager)}
     */
    public AuthSSLProtocolSocketFactory(
        final URL keystoreUrl, final String keystorePassword,
        final URL truststoreUrl, final String truststorePassword)
    {
        super();
        this.keystoreUrl = keystoreUrl;
        this.keystorePassword = keystorePassword;
        this.truststoreUrl = truststoreUrl;
        this.truststorePassword = truststorePassword;
    }
    
    /**
     * Constructor for AuthSSLProtocolSocketFactory. Either a keystore or 
truststore file
     * must be initialized in passed structure. Otherwise SSL context 
initialization error
     * will result.
     *
     * @param certManager
     * @throws MalformedURLException
     */
    public AuthSSLProtocolSocketFactory(final CertificateManager
certManager)
                      throws MalformedURLException
    {
            super();
            this.keystoreUrl = certManager.getKeyStoreURIPath();
            this.keystorePassword = CertificateManager.KEYSTORE_PASSWORD;
            this.truststoreUrl = certManager.getTrustStoreURIPath();
            this.truststorePassword = CertificateManager.TRUSTORE_PASSWORD;
    }

    private KeyStore createKeyStore(final URL url, final String password)
        throws KeyStoreException, NoSuchAlgorithmException, 
CertificateException, IOException
    {
        if (url == null) {
            throw new IllegalArgumentException("Keystore url may not be null");
        }
        IDebug.print("Initializing key store");
        KeyStore keystore  = KeyStore.getInstance("jks");
        keystore.load(url.openStream(), password != null ? 
password.toCharArray(): null);
        return keystore;
    }
    
    private static KeyManager[] createKeyManagers(final KeyStore keystore, 
final String password)
        throws KeyStoreException, NoSuchAlgorithmException, 
UnrecoverableKeyException
    {
        if (keystore == null) {
            throw new IllegalArgumentException("Keystore may not be null");
        }
        IDebug.print("Initializing key manager");
        KeyManagerFactory kmfactory = KeyManagerFactory.getInstance(
            KeyManagerFactory.getDefaultAlgorithm());
        kmfactory.init(keystore, password != null ? 
password.toCharArray(): null);
        return kmfactory.getKeyManagers();
    }

    private TrustManager[] createTrustManagers(KeyStore truststore, KeyStore 
keystore)
        throws KeyStoreException, NoSuchAlgorithmException
    {
        if (truststore == null) {
            throw new IllegalArgumentException("Trust Store may not be null");
        }
        IDebug.print("Initializing trust manager");
        TrustManagerFactory tmfactory = TrustManagerFactory.getInstance(
            TrustManagerFactory.getDefaultAlgorithm());
        tmfactory.init(truststore);
        TrustManager[] trustmanagers = tmfactory.getTrustManagers();
        for (int i = 0; i < trustmanagers.length; i++) {
            if (trustmanagers[i] instanceof X509TrustManager) {
                trustmanagers[i] = new AuthSSLX509TrustManager(
                        truststore, keystore, keystoreUrl.getPath(), 
keystorePassword.toCharArray());
            }
        }
        return trustmanagers;
    }

    private SSLContext createSSLContext() {
        try {
            KeyManager[] keymanagers = null;
            TrustManager[] trustmanagers = null;
            KeyStore keystore = null;
            if (this.keystoreUrl != null) {
                keystore = createKeyStore(this.keystoreUrl, 
this.keystorePassword);
                if (IDebug.isDebugEnabled()) {
                    Enumeration aliases = keystore.aliases();
                    while (aliases.hasMoreElements()) {
                        String alias = 
(String)aliases.nextElement();                        
                        Certificate[] certs = 
keystore.getCertificateChain(alias);
                        if (certs != null) {
                            IDebug.print("Certificate chain '" + alias + "':");
                            for (int c = 0; c < certs.length; c++) {
                                if (certs[c] instanceof X509Certificate) {
                                    X509Certificate cert = 
(X509Certificate)certs[c];
                                    IDebug.print(" Certificate " + (c +
1) + ":");
                                    IDebug.print("  Subject DN: " + 
cert.getSubjectDN());
                                    IDebug.print("  Signature Algorithm: 
" + cert.getSigAlgName());
                                    IDebug.print("  Valid from: " +
cert.getNotBefore() );
                                    IDebug.print("  Valid until: " + 
cert.getNotAfter());
                                    IDebug.print("  Issuer: " + 
cert.getIssuerDN());
                                }
                            }
                        }
                    }
                }
                keymanagers = createKeyManagers(keystore, 
this.keystorePassword);
            }
            
            if (this.truststoreUrl != null) {
                KeyStore truststore = createKeyStore(this.truststoreUrl,
this.truststorePassword);
                if (IDebug.isDebugEnabled()) {
                    Enumeration aliases = truststore.aliases();
                    while (aliases.hasMoreElements()) {
                        String alias = (String)aliases.nextElement();
                        IDebug.print("Trusted certificate '" + alias + "':");
                        Certificate trustedcert = 
truststore.getCertificate(alias);
                        if (trustedcert != null && trustedcert instanceof 
X509Certificate) {
                            X509Certificate cert = (X509Certificate)trustedcert;
                            IDebug.print("  Subject DN: " + 
cert.getSubjectDN());
                            IDebug.print("  Signature Algorithm: " + 
cert.getSigAlgName());
                            IDebug.print("  Valid from: " +
cert.getNotBefore() );
                            IDebug.print("  Valid until: " + 
cert.getNotAfter());
                            IDebug.print("  Issuer: " + cert.getIssuerDN());
                        }
                    }
                }
                trustmanagers = createTrustManagers(truststore, keystore);
            }
            SSLContext sslcontext = SSLContext.getInstance("SSL");
            //sslcontext.init(keymanagers, trustmanagers, new SecureRandom());
            sslcontext.init(keymanagers, trustmanagers, null);
            return sslcontext;
        } catch (NoSuchAlgorithmException e) {
            IDebug.print(e);
            throw new AuthSSLInitializationError("Unsupported algorithm
exception: " + e.getMessage());
        } catch (KeyStoreException e) {
            IDebug.print(e);
            throw new AuthSSLInitializationError("Keystore exception: " 
+ e.getMessage());
        } catch (GeneralSecurityException e) {
            IDebug.print(e);
            throw new AuthSSLInitializationError("Key management
exception: " + e.getMessage());
        } catch (IOException e) {
            IDebug.print(e);
            throw new AuthSSLInitializationError("I/O error reading 
keystore/truststore file: " + e.getMessage());
        }
    }

    private SSLContext getSSLContext() {
        if (this.sslcontext == null) {
            this.sslcontext = createSSLContext();
        }
        return this.sslcontext;
    }

    /**
     * @see
SecureProtocolSocketFactory#createSocket(java.lang.String,int,java.net.InetAddress,int)
     */
    public Socket createSocket(String host,
                               int port,
                               InetAddress clientHost,
                               int clientPort)
       throws IOException, UnknownHostException {
        
        SSLContext sslc = createSSLContext();
        SSLSocketFactory sslsf = sslc.getSocketFactory();
        HttpsURLConnection.setDefaultSSLSocketFactory(  sslsf  );
        SSLSocket sslSocket = (SSLSocket) sslsf.createSocket(host, port,
                    clientHost,
                    clientPort);
       
       verifyHostname(host, sslSocket);
       
       return sslSocket;
    }

    /**
     * @see SecureProtocolSocketFactory#createSocket(java.lang.String,int)
     */
    public Socket createSocket(String host, int port)
        throws IOException, UnknownHostException  {
        
        Socket socket =
            getSSLContext().getSocketFactory().createSocket(host, port);
        
        verifyHostname(host, (SSLSocket)socket);
        
        return socket;        
    }

    /**
     * @see
SecureProtocolSocketFactory#createSocket(java.net.Socket,java.lang.String,int,boolean)
     */
    public Socket createSocket(Socket socket,
                               String host,
                               int port,
                               boolean autoClose)
        throws IOException, UnknownHostException {
        
        Socket newSocket =
            getSSLContext().getSocketFactory().createSocket(
                socket,
                host,
                port,
                autoClose);
        
        verifyHostname(host, (SSLSocket)newSocket);
        
        return newSocket;        
    }
    
    /**
     * Attempts to get a new socket connection to the given host within the 
given time limit.
     * <p>
     * This method employs several techniques to circumvent the limitations of 
older JREs that
     * do not support connect timeout. When running in JRE 1.4 or above 
reflection is used to
     * call Socket#connect(SocketAddress endpoint, int timeout) method. 
When executing in older
     * JREs a controller thread is executed. The controller thread attempts to 
create a new socket
     * within the given limit of time. If socket constructor does not return 
until the timeout
     * expires, the controller terminates and throws an [EMAIL PROTECTED] 
ConnectTimeoutException}
     * </p>
     *  
     * @param host the host name/IP
     * @param port the port on the host
     * @param clientHost the local host name/IP to bind the socket to
     * @param clientPort the port on the local machine
     * @param params [EMAIL PROTECTED] HttpConnectionParams Http connection 
parameters}
     *
     * @return Socket a new socket
     *
     * @throws IOException if an I/O error occurs while creating the socket
     * @throws UnknownHostException if the IP address of the host cannot be
     * determined
     */
    public Socket createSocket(String host,
                               int port,
                               InetAddress localAddress,
                               int localPort,
                               HttpConnectionParams params)
        throws IOException,
               UnknownHostException,
               ConnectTimeoutException {
        
        if (params == null) {
            throw new IllegalArgumentException("Parameters may not be null");
        }
        
        int timeout = params.getConnectionTimeout();
        
        if (timeout == 0) {
            return createSocket(host, port, localAddress, localPort);
        }
        else {
            // To be eventually deprecated when migrated to Java 1.4 or above
            SSLSocket sslSocket = (SSLSocket) 
ReflectionSocketFactory.createSocket(
                "javax.net.ssl.SSLSocketFactory", host, port, localAddress, 
localPort, timeout);
            if (sslSocket == null) {
                sslSocket = (SSLSocket) 
ControllerThreadSocketFactory.createSocket(
                    this, host, port, localAddress, localPort, timeout);
            }
            verifyHostname(host, sslSocket);
            return sslSocket;
        }
    }

    /**
     * Describe <code>verifyHostname</code> method here.
     *
     * @param socket a <code>SSLSocket</code> value
     * @exception SSLPeerUnverifiedException  If there are problems obtaining
     * the server certificates from the SSL session, or the server host name
     * does not match with the "Common Name" in the server certificates
     * SubjectDN.
     * @exception UnknownHostException  If we are not able to resolve
     * the SSL sessions returned server host name.
     */
    private void verifyHostname(String host, SSLSocket socket)
        throws SSLPeerUnverifiedException, UnknownHostException {
        
        if ((!verifyHostname) || (verified)) {
            return;
        }
        
        SSLSession session = socket.getSession();
        String hostname = session.getPeerHost();
        
        try {
            InetAddress addr = InetAddress.getByName(hostname);
        }
        catch (UnknownHostException uhe) {
            throw new UnknownHostException("Could not resolve SSL sessions "
                                           + "server hostname: " + hostname);
        }
        
        javax.security.cert.X509Certificate[] certs = 
session.getPeerCertificateChain();
        if (certs == null || certs.length == 0)
            throw new SSLPeerUnverifiedException("No server certificates 
found!");
        
        //get the servers DN in its string representation
        String dn = certs[0].getSubjectDN().getName();

        //might be useful to print out all certificates we receive from the
        //server, in case one has to debug a problem with the installed certs.
        //IDebug.print("Server certificate chain:");
        //for (int i = 0; i < certs.length; i++) {
           //debug.print("X509Certificate[" + i + "]=" + certs[i]);
        //}
        
        //get the common name from the first cert
        String cn = getCN(dn);
        
        //mark verified if hostnames match
        if (hostname.equalsIgnoreCase(cn)) {
            verified = true;
            //IDebug.print("Target hostname valid: " + cn);
        }
        else {
            StringBuffer sb = new StringBuffer();
            sb.append("The hostname on the server security certificate");
            sb.append(LINE_SEP);
            sb.append("does not match the name of the server");
            sb.append(LINE_SEP);
            sb.append(LINE_SEP);
            sb.append("Hostname of the URL: ");
            sb.append(hostname);
            sb.append(LINE_SEP);
            sb.append("Hostname from the certificate : ");
            sb.append(cn);
            sb.append(LINE_SEP);
            sb.append(LINE_SEP);
            sb.append("Do you want to proceed?");
            sb.append(LINE_SEP);
            int returnValue = JOptionPane.showConfirmDialog(
                    null,
                    sb.toString(),
                    "Hostname Mismatch",
                    JOptionPane.YES_NO_OPTION,
                    JOptionPane.WARNING_MESSAGE);
            
            if (returnValue > 0) {
                throw new SSLPeerUnverifiedException(USER_DECLINED);
            }
            verified = true;
        }
    }


    /**
     * Parses a X.500 distinguished name for the value of the
     * "Common Name" field.
     * This is done a bit sloppy right now and should probably be done a bit
     * more according to <code>RFC 2253</code>.
     *
     * @param dn  a X.500 distinguished name.
     * @return the value of the "Common Name" field.
     */
    private String getCN(String dn) {
        int i = 0;
        i = dn.indexOf("CN=");
        if (i == -1) {
            return null;
        }
        //get the remaining DN without CN=
        dn = dn.substring(i + 3);  
        // System.out.println("dn=" + dn);
        char[] dncs = dn.toCharArray();
        for (i = 0; i < dncs.length; i++) {
            if (dncs[i] == ','  && i > 0 && dncs[i - 1] != '\\') {
                break;
            }
        }
        return dn.substring(0, i);
    }
    

}


---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to