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