} 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).
|