JDK-4815023 : GET fails after preceding GET succeeds
  • Type: Bug
  • Component: security-libs
  • Sub-Component: javax.net.ssl
  • Affected Version: 1.4.1
  • Priority: P4
  • Status: Closed
  • Resolution: Won't Fix
  • OS: windows_2000
  • CPU: x86
  • Submitted: 2003-02-07
  • Updated: 2011-02-03
  • Resolved: 2011-02-03
Description
 }
            else
            {
//                int nAvailable;
//                int sleepCount = 0;
//                while (((nAvailable = anInputStream.available()) < nToBeRead)
//                       && (sleepCount < 3))
//                {
//                   System.out.println(
//                      me + nAvailable + " bytes available, expecting "
//                      + nToBeRead + ".  Sleeping...");
//                   try
//                   {
//                      Thread.sleep( 300);
//                   }
//                   catch (InterruptedException exc)
//                   {
//                      exc.printStackTrace();
//                   }
//                   sleepCount++;
//                }
               System.out.println( me + "reading " + nToBeRead + " bytes...");
               nRead = anInputStream.read( contentBytes, 0, nToBeRead);
            }
            if (nRead > 0)
            {
               System.out.println( me + "got " + nRead + " bytes.");
               ostr.write( contentBytes, 0, nRead);
               totReadSoFar += nRead;
            }
            else
               System.out.println(
                  me + "anInputStream.read() returned " + nRead);
            nToBeRead = aContentLength - totReadSoFar;
         } while (nRead > 0 && nToBeRead > 0);
      }
      catch (IOException exc)
      {
         exc.printStackTrace();
      }
      try
      {
         ostr.close();
      }
      catch (IOException exc)
      {
         exc.printStackTrace();
      }
      return nRead;
   }

   /**
    * Saves the returned headers to a "temp" file, as in {@link
    * #saveInputStream( InputStream, int, String, String, File)}, above.
    *
    * @param aUrlConnection
    * @param aFilenamePrefix
    * @param aFilenameSuffix
    * @param aDirectory
    **/
   public static void saveHeaders( HttpURLConnection aUrlConnection,
                                   String aFilenamePrefix,
                                   String aFilenameSuffix,
                                   File aDirectory)
   {
      String me = HttpUtils.class.getName() + ".saveHeaders(): ";
      PrintWriter wrtr;
      
      try
      {
         File tempFile = File.createTempFile( aFilenamePrefix,
                                              aFilenameSuffix,
                                              aDirectory);
         System.out.println( me + "saving to " + tempFile.getPath());
         wrtr = new PrintWriter( new FileOutputStream( tempFile));
      }
      catch (IOException exc)
      {
         exc.printStackTrace();
         wrtr = new PrintWriter( System.out); // punt
      }
      wrtr.println( "Results of " + aUrlConnection.getURL()
                    + " at " + new Date());
      int i = 1;
      String headerFieldName = aUrlConnection.getHeaderFieldKey( i);
      while (headerFieldName != null)
      {
         String headerFieldValue = aUrlConnection.getHeaderField( i);
         wrtr.println( headerFieldName + ": " + headerFieldValue);
         i++;
         headerFieldName = aUrlConnection.getHeaderFieldKey( i);
      }
      wrtr.close();
   }

   /**
    *
    * @throws
    **/
   public static void main( String[] arg)
   {
      
   }

} // HttpUtils


package canopy.test;

import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import javax.net.ssl.HttpsURLConnection;

/**
 * Wraps a Sun X.509 TrustManager, providing a little more lenience. See
 * j2sdk1.4.1_01/docs/guide/security/jsse/JSSERefGuide.html#OwnX509TM.
 * @version $Revision$ $Date$
 * @author Lusk
 **/
public class LenientX509TrustManager implements X509TrustManager
{

   /**
    * Embed version stamp in class file.
    **/
   public static final String VERSION = "$Revision$ $Date$";

   /**
    **/
   public LenientX509TrustManager()
   {
//       String me = getClass().getName() + ".<init>(): ";
//       System.out.println( me);
      try
      {
         TrustManagerFactory tmf =
            TrustManagerFactory.getInstance("SunX509", "SunJSSE");
         tmf.init( (KeyStore) null);
//          System.out.println( me + "algorithm: " + tmf.getAlgorithm());
//          System.out.println( me + "default algorithm: " +
tmf.getDefaultAlgorithm());
//          System.out.println( me + "provider: " + tmf.getProvider());
         TrustManager[] tm = tmf.getTrustManagers();
//          for (int i = 0; i < tm.length; i++)
//          {
//             System.out.println(me + "tm[" + i + "]: " + tm[i]);
//          }
         mySunX509TrustManager = (X509TrustManager) tm[0];
      }
      catch (KeyStoreException exc)
      {
         exc.printStackTrace(); // We don't expect failure, so we take the
                                // minimal measures in dealing w/this
                                // exception.
      }
      catch (NoSuchAlgorithmException exc)
      {
         exc.printStackTrace(); // We don't expect failure, so we take the
                                // minimal measures in dealing w/this
                                // exception.
      }
      catch (NoSuchProviderException exc)
      {
         exc.printStackTrace(); // We don't expect failure, so we take the
                                // minimal measures in dealing w/this
                                // exception.
      }
   }
   
   /**
    * Installs this trust manager.  See
    * j2sdk1.4.1_01/docs/guide/security/jsse/JSSERefGuide.html#OwnX509TM.
    **/
   public static void install()
      throws KeyManagementException
   {
//       String me = LenientX509TrustManager.class.getName()
//          + ".install(): ";
//       System.out.println( me);
      
      String[] protocols = {
         "SSL"
         ,"TLS"
//          ,"TLSv1"
//          ,"SSLv3"
      };
      
      TrustManager[] tm = new TrustManager[] { new LenientX509TrustManager() };
      SSLContext ctxt = null;
      for (int i = 0; i < protocols.length; i++)
      {
//          System.out.println( me + "init context for protocol " + protocols[i]);
         try
         {
                                // SunJSSE seems to be the default provider
                                //   anyway.
            // ctxt = SSLContext.getInstance( "TLS", "SunJSSE");
            ctxt = SSLContext.getInstance( protocols[i]);
            ctxt.init( null, tm, null);
            HttpsURLConnection.setDefaultSSLSocketFactory(
               ctxt.getSocketFactory());
         }
         catch (NoSuchAlgorithmException exc)
         {
            exc.printStackTrace(); // We don't really expect this exception.
         }
      }
   }

   /**
    *
    *
    * @param void
    * @param chain
    * @param authType
    * @return
    * @throws CertificateException
    **/
   public void checkClientTrusted(X509Certificate[] chain, String authType)
      throws CertificateException
   {
      String me = getClass().getName() + ".checkClientTrusted(): ";
      System.out.println( me);
      mySunX509TrustManager.checkClientTrusted( chain, authType);
   }

   /**
    *
    *
    * @param void
    * @param chain
    * @param authType
    * @return
    * @throws CertificateException
    **/
   public void checkServerTrusted(X509Certificate[] chain, String authType)
      throws CertificateException
   {
      String me = getClass().getName() + ".checkServerTrusted(): ";
      try
      {
         mySunX509TrustManager.checkServerTrusted( chain, authType);
      }
      catch (CertificateExpiredException exc)
      {
         System.err.println(
            me + "Server cert expired, but I trust you anyway.");
      }
   }

   /**
    *
    *
    * @return
    **/
   public X509Certificate[] getAcceptedIssuers()
   {
      String me = getClass().getName() + ".getAcceptedIssuers(): ";
      System.out.println( me);
      return mySunX509TrustManager.getAcceptedIssuers();
   }

   /**
    *
    * @throws
    **/
   public static void main( String[] arg)
   {
      
   }
   /**
    *
    **/
   private X509TrustManager mySunX509TrustManager;
   // private TrustManager mySunX509TrustManager;
   
} // LenientX509TrustManager


package canopy.test;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;

/**
 *
 * @version $Revision$ $Date$
 * @author Lusk
 * @see <var>Other spiffy things that might be of interest/relevance.</var>
 **/
public class LenientHostnameVerifier implements HostnameVerifier
{
   /**
    * Embed version stamp in class file.
    **/
   public static final String VERSION = "$Revision$ $Date$";

   /**
    **/
   public LenientHostnameVerifier()
   {
   }
   /**
    *
    *
    * @param aHostname
    * @param anSSLSession
    * @return
    **/
   public boolean verify( String aHostname, SSLSession anSSLSession)
   {
      String me = getClass().getName() + ".verify(): ";
      System.out.println( me + "allowing conversation with host \""
                          + aHostname + "\"");
      return true;
   }


   /**
    *
    * @throws
    **/
   public static void main( String[] arg)
   {
      
   }
} // LenientHostnameVerifier

---------- END SOURCE ----------

CUSTOMER WORKAROUND :
In my particular case, for the moment, I can probably get
away with using a batch file or script to issue multiple
invocations of the JVM process, since I can use a cookie to
keep the same session context between requests.
(Review ID: 180008) 
======================================================================


Name: nt126004			Date: 02/07/2003


FULL PRODUCT VERSION :
W-JL06072001$ /java/j2sdk1.4.1_01/jre/bin/java -version
java version "1.4.1_01"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.1_01-b01)
Java HotSpot(TM) Client VM (build 1.4.1_01-b01, mixed mode)

FULL OPERATING SYSTEM VERSION :

  Microsoft Windows 2000 [Version 5.00.2195]

EXTRA RELEVANT SYSTEM CONFIGURATION :
I'm running J2SDK 1.4.1_01 on Win2K Pro.  My targets are: an IIS
4.0-on-WinNT server, and an IIS 5.0-on-Win2K server.  Both of them
fail on the *second* attempt to fetch a url from the server (the first
attempt works fine), but they *sometimes* succeed.

I do have JDK1.3.1_06 installed on this workstation, but I have
removed c:\winnt\system32\java*.exe (but not the DLLs) and I'm
invoking java.exe by absolute path in the j2sdk 1.4.1
installation.


A DESCRIPTION OF THE PROBLEM :
Ok, so I wrote some code the way y'all recommend, and it doesn't work.
Either (a) your stuff doesn't work or isn't robust or (b) your docs
are incorrect or (c) your diagnostics aren't helpful enough or (d) I
goofed up somewhere.

(I know it's slightly cheesy to file a bug report for this,but I sort
of think it might actually be a bug.  I've done what research I can,
on the forums and in the bug database, but I haven't found anything
useful.  The fix for bug #4639763 (reduced protocol set) doesn't help,
it just converts the errors into end-of-file errors.  You'll notice my
plea for help on the forums, unanswered.)

I followed the instructions in the JSSE Referenc Guide
(file:///C:/java/j2sdk1.4.1_01/docs/guide/security/jsse/JSSERefGuide.html#TrustManager,
file:///C:/java/j2sdk1.4.1_01/docs/guide/security/jsse/JSSERefGuide.html#OwnX509TM)
and also cribbed from a code sample from the 1.4 Almanac
(http://javaalmanac.com/egs/javax.net.ssl/TrustAll.html).

Also, if I try this against some generic server Out There on the
Internet, it works (including IIS 4.0 and 5.0 servers).

Sounds like something on our LAN is screwed up or maybe Microsoft
isn't playing by the standards (something I would be shocked, SHOCKED,
to discover), but our browsers aren't having any trouble, so whatever
it is, JSSE is being a little too delicate, methinks.  (If it's not
flat-out broken.)

I'll attach the code and some sample runs (sans -Djavax.net.debug=all,
because that's just too much noise, but I can always run it again for
y'all).

You'll notice that the attempt to hit https://localhost twice
succeeded, but it doesn't always.

Here's my command line (using cygwin):

rm -f /tmp/jssetest-multiple-targets.log
touch /tmp/jssetest-multiple-targets.log

for target in localhost cartman devtest1; do
  echo >> /tmp/jssetest-multiple-targets.log;
  echo "====================  $target  ======================" >>
     /tmp/jssetest-multiple-targets.log; env
  JAVA_HOME=/java/j2sdk1.4.1_01
  /java/j2sdk1.4.1_01/jre/bin/java -classpath     /Project/Canopy/r3.2.2 canopy.test.JsseTest     https://$target https://$target >> /tmp/jssetest-multiple-targets.log 2>&1; done


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Set up (or find) an IIS 4 or 5 server on the same LAN as
the workstation intend to reproduce this bug from.
2. Compile and run the code I'll include, using the command
line from the description I put in above.
3. You might have to do this several times in somewhat rapid
succession to get this bug to appear.

EXPECTED VERSUS ACTUAL BEHAVIOR :
I expected to be able to execute multiple GETs from w/in the
same jvm process.  Instead, I got exceptions *after* the
first GET was successful, but not always.

ERROR MESSAGES/STACK TRACES THAT OCCUR :
javax.net.ssl.SSLException: Received fatal alert: bad_record_mac
	at com.sun.net.ssl.internal.ssl.BaseSSLSocketImpl.a(DashoA6275)
	at com.sun.net.ssl.internal.ssl.BaseSSLSocketImpl.b(DashoA6275)
	at com.sun.net.ssl.internal.ssl.SSLSocketImpl.b(DashoA6275)
	at com.sun.net.ssl.internal.ssl.SSLSocketImpl.a(DashoA6275)
	at com.sun.net.ssl.internal.ssl.SSLSocketImpl.a(DashoA6275)
	at com.sun.net.ssl.internal.ssl.AppInputStream.read(DashoA6275)
	at java.io.BufferedInputStream.fill(BufferedInputStream.java:183)
	at java.io.BufferedInputStream.read1(BufferedInputStream.java:222)
	at java.io.BufferedInputStream.read(BufferedInputStream.java:277)
	at sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:741)
	at sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:702)
	at
sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:583)
	at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(DashoA6275)
	at canopy.test.JsseTest.test(JsseTest.java:97)
	at canopy.test.JsseTest.main(JsseTest.java:178)
------------------------------------------------------------------------
java.net.SocketException: Unexpected end of file from server
	at sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:802)
	at sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:702)
	at
sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:583)
	at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(DashoA6275)
	at canopy.test.JsseTest.test(JsseTest.java:97)
	at canopy.test.JsseTest.main(JsseTest.java:178)


REPRODUCIBILITY :
This bug can be reproduced often.

---------- BEGIN SOURCE ----------
package canopy.test;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URL;
import java.net.URLConnection;
import java.security.KeyManagementException;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSessionContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

/**
 * Basic test of my understanding of JSSE.  Possible URLs to test (in
 * addition to whatever you have locally):
 * <ul>
 *   <li>https://www.fortify.net/cgi-bin/ssl_2
 *   <li>https://www.stanford.edu/group/idg/leland/samples/secure/test.html
 * @version $Revision$ $Date$
 * @author Lusk
 **/
public class JsseTest
{

   /**
    * Embed version stamp in class file.
    **/
   public static final String VERSION = "$Revision$ $Date$";

   /**
    **/
   public JsseTest()
   {
   }

   /**
    * Test the connection, see if it's been set up properly.
    *
    * @param aUrl A url to issue a GET request to.
    **/
   public static void test( String aUrl)
      throws MalformedURLException, IOException
   {
      String me = JsseTest.class.getName() + ".test(): ";
      System.out.println( me + "BEGIN");
      URL url = new URL( aUrl);

      HttpURLConnection conn = (HttpURLConnection) url.openConnection();
      conn.setUseCaches( false);
      conn.setInstanceFollowRedirects( false);
      conn.setAllowUserInteraction( false);

      HttpsURLConnection sslConn;
      if (conn instanceof HttpsURLConnection)
      {
         sslConn = (HttpsURLConnection) conn;
         sslConn.setHostnameVerifier( new LenientHostnameVerifier());
      }

      Object urlContent = null;
      InputStream contentStream = null;
      try
      {
                                // Probably automatically issues
                                //   "GET / HTTP/1.x"
         contentStream = conn.getInputStream();
         if (contentStream == null)
         {
            urlContent = conn.getContent();
            System.out.println( me + "url content class is "
                                + urlContent.getClass());
         }
      }
      catch (Exception exc)
      {
         exc.printStackTrace();
      }
      
      int contentLength = conn.getContentLength();
      System.out.println( me + "got " + contentLength
                          + " bytes of content");
      int responseCode = conn.getResponseCode();
      String responseMsg = conn.getResponseMessage();
      System.out.println( me + aUrl + " gives status " + responseCode
                          + " " + responseMsg);
      HttpUtils.saveHeaders( conn, "JsseTestOutput/headers_", ".txt",
                             new File( "."));
      if (contentLength < 0)
      {
         System.out.println( me + "content length < 0, trying 4096");
         contentLength = 4096;
      }
      if (urlContent instanceof InputStream)
      {
         System.out.println( me + "urlContent class is "
                             + urlContent.getClass().getName());
         HttpUtils.saveInputStream( (InputStream) urlContent, contentLength,
                                    "JsseTestOutput/response_",
                                    ".html", new File( "."));
      }
      else if (contentStream != null)
      {
         System.out.println( me + "contentStream class is "
                             + contentStream.getClass().getName());
         HttpUtils.saveInputStream( contentStream, contentLength,
                                    "JsseTestOutput/response_",
                                    ".html", new File( "."));
      }
      
      
//       if (urlContent instanceof InputStream)
//       {
//          InputStream contentStream = (InputStream) urlContent;
//          int nRead = HttpUtils.dumpInputStream( contentStream, contentLength);
//          if (nRead == -1)
//             contentStream.close();
//       }

//       contentStream.close();
//       conn.disconnect();
      System.out.println( me + "END");
   }

   /**
    *
    * @throws
    **/
   public static void main( String[] arg)
      throws KeyManagementException, IOException
   {
      String me = JsseTest.class.getName() + ".main(): ";
      System.out.println( me);
      
                                // See Sun bug #4639763
      // System.setProperty( "https.protocols", "SSLv3,SSLv2Hello");
      
      if (arg.length < 1)
      {
         System.out.println(
            "Usage: java " + JsseTest.class.getName() + " <url>...");
      }
      else
      {
         LenientX509TrustManager.install();
         for (int i = 0; i < arg.length; i++)
         {
            test( arg[i]);
//             try
//             {
//                Thread.sleep( 300);
//             }
//             catch (InterruptedException exc)
//             {
//                exc.printStackTrace();
//             }
         }
      }
   }
} // JsseTest


package canopy.test;

import java.io.InputStream;
import java.io.IOException;
import java.io.File;
import java.io.OutputStream;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.util.Date;

/**
 * Static utility functions for dealing w/HTTP.
 * @version $Revision$ $Date$
 * @author Lusk
 **/
public class HttpUtils
{

   /**
    * Embed version stamp in class file.
    **/
   public static final String VERSION = "$Revision$ $Date$";

   /**
    **/
   public HttpUtils()
   {
   }

   /**
    * Reads an InputStream and dumps the results to stdout.  Reads at most
    * aContentLength bytes.  Returns the number of bytes read on the last read
    * attempt.  -1 indicates that EOF occurred.
    **/
   public static int dumpInputStream( InputStream anInputStream,
                                      int aContentLength)
   {
      String me = HttpUtils.class.getName() + ".dumpInputStream(): ";
      int nRead = 0;            // in last read

      try
      {
         int nAvail = anInputStream.available();
         int totReadSoFar = 0;
         if (nAvail < 4096)
            nAvail = 4096;
         byte[] contentBytes = new byte[ nAvail];
         int nToBeRead = aContentLength;
         do
         {
            if (nToBeRead >= 4096)
               nRead = anInputStream.read( contentBytes);
            else if (nToBeRead == 0)
            {
                                // Don't even try (we might just get a -1 and
                                //   set stream state to EOF, even though the
                                //   server is about to dump more data into
                                //   the socket).
            }
            else
            {
               System.out.println( me + "reading " + nToBeRead + " bytes...");
               nRead = anInputStream.read( contentBytes, 0, nToBeRead);
            }
            if (nRead > 0)
            {
               System.out.println( me + "got " + nRead + " bytes: "
                                   + new String( contentBytes, 0, nRead));
               totReadSoFar += nRead;
            }
            else
               System.out.println(
                  me + "anInputStream.read() returned " + nRead);
            nToBeRead = aContentLength - totReadSoFar;
         } while (nRead > 0 && nToBeRead > 0);
      }
      catch (IOException exc)
      {
         exc.printStackTrace();
      }
      return nRead;
   }

   /**
    * Saves the given InputStream to a "temp" file created with in the given
    * directory, having the given prefix and suffix.  Requirements for prefix,
    * suffix and dir args are the same as those for File.createTempFile().
    *
    * @param anInputStream
    * @param aContentLength Length of data in the InputStream
    * @param aFilenamePrefix
    * @param aFilenameSuffix
    * @param aDirectory
    * @return Result of last read() call.  -1 indicates EOF occurred.
    **/
   public static int saveInputStream( InputStream anInputStream,
                                      int aContentLength,
                                      String aFilenamePrefix,
                                      String aFilenameSuffix,
                                      File aDirectory)
   {
      String me = HttpUtils.class.getName() + ".saveInputStream(): ";
      OutputStream ostr;
      
      try
      {
         File tempFile = File.createTempFile( aFilenamePrefix,
                                              aFilenameSuffix,
                                              aDirectory);
         System.out.println( me + "saving to " + tempFile.getPath());
         ostr = new FileOutputStream( tempFile);
      }
      catch (IOException exc)
      {
         exc.printStackTrace();
         ostr = System.out;     // punt
      }
      
      int nRead = 0;            // in last read
      try
      {
         int nAvail = anInputStream.available();
         int totReadSoFar = 0;
         if (nAvail < 4096)
            nAvail = 4096;
         byte[] contentBytes = new byte[ nAvail];
         int nToBeRead = aContentLength;
         do
         {
            if (nToBeRead >= 4096)
               nRead = anInputStream.read( contentBytes);
            else if (nToBeRead <= 0)
            {
                                // Don't even try (we might just get a -1 and
                                //   set stream state to EOF, even though the
                                //   server is about to dump more data into
                                //   the socket).
           

Comments
EVALUATION We have had a lot of interop testing with ISS. This CR is too old to make further investigation. Please re-open it when run into similar issues.
03-02-2011

EVALUATION Without taking the time to set up a full Win2K server, I wasn't able to duplicate. Nathan, there are a number of questions we need to have answered: 0) Just double checking his invocations. He is doing the following? java JsseTest https://host1 https://host1 java JsseTest https://host2 https://host2 java JsseTest https://host3 https://host3 This creates a new JVM each time, and should completely restart the Java environment between hosts. Are then cartman and devtest1 the iis 4 and 5 machines? How does "localhost" fit into here, he mentioned this in his report for target in localhost cartman devtest1; do Looking at the exception, my immediate reaction was to think of: 4615819 "bad_record_mac" when connecting to servers that only support SSL 3.0, but I doubt this is it, if the first handshake succeeded. If this were the case, both hs should fail for the same reason. 1) I would like a javax.net.debug=all dump to see if there is a complete second handshake going on, or an abbreviated one. 2) are the servers he mentioned in his bug report sample code examples of servers where he has actually seen this issue come up? If so, I would expect to see the same problem. It makes me suspicious, because both of these servers are running Apache 1.3.27, not iis. * <li>https://www.fortify.net/cgi-bin/ssl_2 * <li>https://www.stanford.edu/group/idg/leland/samples/secure/test.html I had no problems with 6 GETS on these servers. Note, these were done from behind a firewall. 3) can you check to see if the customer might have the unbundled JSSE 1.0.3 installed in his environment? This is a stretch, but we've had reports of weird handshaking behaviour before, so I just want to eliminate this possibility. What is very strange is that the handshaking works fine for the first GET, but only fails for the second. This is a single threaded application that completely reads the input stream both times, and most likely does a keep-alive which means there shouldn't be a handshake. 4) can you see if he can put his Win2K server on the internet for a short period for me to test against? Given the current workload, this may be the best way for us to determine if this is really a problem. I do not have a iIS readily accessable which working remotely. I'm marking this bug as incomplete for now, please unmark it when we get a response from the user. If customer still sees this problem, I'll try to setup a Win2K server here and drill into it some more. Offhand, given the description and that this is the first report of such a problem, I think this is something that could wait for tiger. ###@###.### 2003-02-08 ================================================================== customer response follows along with some additional information. See attachments for the mentioned files. : Hi Nathanael, 0) Yes, the invocation is correct. I used localhost, which is running IIS 5.0 on Win2K (response headers also attached as headers_1090.txt). I had assumed we had a network problem, so I was kind of blind to the fact that I was running this on localhost, but I do actually have the problem when hitting localhost (my workstation). 1) javax.net.debug=all output attached as jssetest_2003-02-11_0947.log. 2) The external examples (fortify, stanford) mentioned in my javadoc are Apache servers, and they work fine. I'm not having trouble w/them. (That piece of javadoc was originally written for didactic purposes.) 3) I do have jsse1.0.3_01 installed on my system. I don't *think* I'm picking it up, but who knows? I'm a little reluctant to uninstall it (no telling what's going to stop working or how much of a hassle it's going to be to reinstall it, because some of our other developers have introduced poorly-documented post-install "fiddling" requirements in jre/lib/security (which I may have done in my jdk1.3.1 install, not my 1.4.1 install)) -- is there some way we can tell whether or not it's being picked up? I'm attaching my environment variables (env.txt), as seen by cygwin (admittedly, that leaves out registry settings). 4) As a matter of fact, we just happen to have a server available to the outside world, 64.132.109.12, on which I have just reproduced the problem. Log attached as "jssetest_2003-02-11_095233.log". Headers are attached as "headers_6830.txt". (We use a different IP address internally for that machine.) I have requested our IT people expose port 443 of the machine; that will probably be done by the time you get this email. One of my wild theories about why this might be broken involves Microsoft making some rude assumptions upon discovering that the network traffic is LAN rather than Internet, so I won't be *too* surprised if your engineer can't reproduce the problem. Finally, I'm attaching the code I ran to get all this, since I've changed it a bit. Notes on the code: ChoosyKeyManager isn't used at all in this case, but is necessary for the compile. JsseTest.getKeyManagers() immediately returns null, so I'm not actually bothering w/client-side certificates. JsseTest.getConnTuple() only tries the connect once in this case, not multiple times. Thanks (y'all) for working on this! John. -------- ANOTHER EMAIL ----------- Hi, Nathanael, I have some info to add. I have since put the HttpURLConnection.getContent() call into a loop that catches the exceptions I was complaining about and retries the connection, up to three times. (Basically, it calls disconnect() on the connection, drops its reference to the connection, and loops back to the code that instantiates a new HttpURLConnection.) This retrying process seems to work. I occasionally get a warning that the first GET attempt failed (after a previous successful GET), but the retry succeeds. Also, one of my coworkers has seen bad MAC errors in his code occasionally. He's using JCE (I don't know which version) in JDK 1.3.1 to create Blowfish-encyrpted messages and sending them, along with their MACs, by HTTP (not HTTPS). On receipt, he decrypts the message, re-computes the MAC and compares it to the received MAC. If they fail to match, he logs his own "bad MAC error" and requests that the sender re-send the message. A re-send is usually successful (at least, he never seems to have messages dropped for this reason). I believe his code processes over 10,000 messages/day and he sees on the order of 100 "bad MAC errors" per day. ###@###.### 2003-02-11 Ok, here's a couple more ideas to look into. I took a closer look at the jar file you provided in the bug report, and this email. I have tried to connect to all of the machines mentioned in his bug report, I haven't been able to get to any of them, either no DNS entry, or the host is down. If customer can check on this, that would help us. I have tried this both using Sun's proxies, and with a direct connect to the internet. https://64.132.109.12 - original machine in followup email traceroute shows: 17 core-02-so-3-0-0-0.chrl.twtelecom.net (168.215.53.145) 79.422 ms 79.597 ms 79.608 ms 18 dist-01-so-1-0-0-0.fytv.twtelecom.net (66.192.242.22) 82.932 ms 82.687 ms 82.691 ms 19 dist-01-so-1-0-0-0.rhdm.twtelecom.net (66.192.242.9) 84.853 ms 84.468 ms 87.786 ms 20 tagg-01-so-5-0-0.rhdm.twtelecom.net (66.192.242.142) 151.237 ms 154.456 ms 227.560 ms 21 64.132.140.22 (64.132.140.22) 89.734 ms 88.124 ms 88.086 ms https://secure.futurehealthcare.net - mentioned in the certificate in jssetest_2003-02-11_0947.log https://172.16.0.129 - mentioned in the Https url in jssetest_2003-02-11_095233.log (can't get to localhost :) ) >>>>If your engineer wants to suggest to some code that could be attached to >>>>the classloader so we know where it's pulling classes of interest, I'm game If JSSE 1.0.x isn't available to 1.4.1, I don't think it's a class loader issue. When I asked about 1.0.3, I wanted to know if he had it installed anywhere that the 1.4.1 code could pick it up, either from the jre/lib/ext extension directory, or maybe from a classpath. Anything in 1.3.1 should be irrelevent. So here's what is happening: Client connects to server full handshake btw C & S without client authentication occurs C sends "GET / HTTP1.1" S responds with a Hello Request, starts handshaking all over. C tries to resume existing session, but S doesn't accept. S sends a new session id, and this time requests C auth. C sends an empty certificate chain -X509KM.chooseClientAlias() returned null, in other words, the keymanager didn't have an appropriate key. C sends normal premaster secret/Change Cipher Spec, then sends the finished message. Finished Message should be encrypted/verified using the new keys. S sends it's Change Cipher Spec/Finished message. C receives the results of "GET / HTTP/1.1" above. At this point, the client's read side has successfully switched to the new parameters. At this point, the server may or may have not accepted the client's finished message, from this side it's impossible to tell. Then as part of Http 1.1, we always try to reuse keep-alive connections, so we then send the second GET message on the already established connection. When we try the read, we get the bad_record_mac message. What we don't know is whether this message was from the finished message from the second handshake or the second GET. The quickest thing to try is to have the ChoosyKeyManager supply a proper client authentication certificate. The list of acceptable certificate issuers can be found in the debug line labeled: *** CertificateRequest Cert Types: RSA, Cert Authorities: Currently, nothing is found. Possibly, the lack of a client auth cert is triggering an error condition which erroneously gets reported as a "bad mac." One workaround is to turn off client authentication, or is that an option? I've asked Andreas to have a look at this also, but please try supplying a valid cert. Thanks, ###@###.### 2003-02-15 Customer responds with: I tried turning off the request for a client cert (basically by modifying the > IIS directory security settings to "ignore client certs" rather than "accept > client certs"). > > Then I ran the test 10 times, and it worked every time. When I flipped the > setting back to "accept client certs", it failed, 10 times in a row. (Turning > off client certs in production is not an option.) > > I don't quite understand what your engineer said about CA certs. Below is the > section from my debug log on the most-recent attempt. It looks like 16 CAs > are presented. > > Also, when I flip the USE_KEY_MANAGER boolean in my test pgm and recompile, > the ChoosyKeyManager does choose a cert. I'll attach another log. I asked John to provide a dump of a valid certificate attempt, and it sure looks like we're using a valid certificate. I'm attaching jsseTest_2003-02-20_1806.log. > Nathanael, one final note: I see from the dialog your engineer sketched out > below that the server *requests* client authentication. The IIS server I'm > testing against is configured to *accept* a client cert, but not *require* > it. I assume the only reason it requests client auth is to prod the client to > give a cert if it's got one; I also assume that the server is perfectly > willing to continue if the client presents no cert. Hm...offhand, I'm running out of ideas. Perhaps one of the implementations isn't changing the mac algorithm appropriately? ###@###.### 2003-02-20 This does not appear to be a showstopper for Mantis, therefore I am marking this bug evaluated so that it falls of the Mantis list. We will continue to investigate the root cause. ###@###.### 2003-02-21 Machine is finally accessabible, have duplicated problem using their machine's server. ###@###.### 2003-02-21
21-02-2003