diff options
Diffstat (limited to 'src/com/isode')
-rw-r--r-- | src/com/isode/stroke/tls/PlatformTLSFactories.java | 7 | ||||
-rw-r--r-- | src/com/isode/stroke/tls/java/JSSEContext.java | 665 |
2 files changed, 471 insertions, 201 deletions
diff --git a/src/com/isode/stroke/tls/PlatformTLSFactories.java b/src/com/isode/stroke/tls/PlatformTLSFactories.java index f5b40d1..6b98a95 100644 --- a/src/com/isode/stroke/tls/PlatformTLSFactories.java +++ b/src/com/isode/stroke/tls/PlatformTLSFactories.java @@ -12,12 +12,7 @@ import com.isode.stroke.tls.java.JSSEContextFactory; public class PlatformTLSFactories { public TLSContextFactory getTLSContextFactory() { - // TODO: JSSEContextFactory is implemented, and so uncommenting - // this line will result in the client attempting TLS handshakes, but - // other support is required inside CoreClient etc. and so for the - // moment we just return null - //return new JSSEContextFactory(); - return null; + return new JSSEContextFactory(); } public CertificateFactory getCertificateFactory() { diff --git a/src/com/isode/stroke/tls/java/JSSEContext.java b/src/com/isode/stroke/tls/java/JSSEContext.java index e052b19..887a2b7 100644 --- a/src/com/isode/stroke/tls/java/JSSEContext.java +++ b/src/com/isode/stroke/tls/java/JSSEContext.java @@ -12,6 +12,7 @@ package com.isode.stroke.tls.java; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; +import java.security.GeneralSecurityException; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; @@ -19,6 +20,8 @@ import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; import java.util.Vector; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; @@ -88,6 +91,10 @@ public class JSSEContext extends TLSContext { */ private void emitError(Exception e, String m) { JSSEContextError jsseContextError = new JSSEContextError(e, m); + /* onError.emit() won't provide any info about what the error was, + * so log a warning here as well + */ + logger_.log(Level.WARNING, jsseContextError.toString(), e); errorsEmitted.add(jsseContextError); onError.emit(); } @@ -115,38 +122,67 @@ public class JSSEContext extends TLSContext { sslEngine = sslContext.createSSLEngine(); } catch (UnsupportedOperationException e) { - // "the underlying provider does not implement the operation" + /* "the underlying provider does not implement the operation" */ throw new SSLException(e); } catch (IllegalStateException e) { - // "the SSLContextImpl requires initialization and init() has not been called" + /* "the SSLContextImpl requires initialization and init() has not been called" */ throw new SSLException(e); } - - sslEngine.setUseClientMode(true); // I am a client - sslEngine.setEnableSessionCreation(true); // can create new sessions + + sslEngine.setUseClientMode(true); /* I am a client */ + sslEngine.setEnableSessionCreation(true); /* can create new sessions */ - int appBufferMax = sslEngine.getSession().getApplicationBufferSize(); - int netBufferMax = sslEngine.getSession().getPacketBufferSize(); + /* Will get "the current size of the largest application data that is + * expected when using this session". + * + * If we get packets larger than this, we'll grow the buffers by this + * amount. + */ + appBufferSize = sslEngine.getSession().getApplicationBufferSize(); + + /* + * Don't grow application buffers bigger than this + */ + appBufferMax = (appBufferSize * 10); + + /* "A SSLEngine using this session may generate SSL/TLS packets of + * any size up to and including the value returned by this method" + * + * Note though, that this doesn't mean we might not be asked to + * process data chunks that are larger than this: we cannot rely on this + * value being big enough to hold anything that comes in through + * "handleDataFromNetwork()". + */ + netBufferSize = sslEngine.getSession().getPacketBufferSize(); + /* + * Don't grow network buffers bigger than this + */ + netBufferMax = (netBufferSize * 10); - // All buffers are normally in "write" mode. Access to all of them - // must be synchronized - plainToSend = ByteBuffer.allocate(appBufferMax + 50); - wrappedToSend = ByteBuffer.allocate(netBufferMax); - encryptedReceived = ByteBuffer.allocate(netBufferMax); - unwrappedReceived = ByteBuffer.allocate(appBufferMax + 50); + /* All buffers are normally in "write" mode. Access to all of them + * must be synchronized + */ + plainToSend = ByteBuffer.allocate(appBufferSize + 50); + wrappedToSend = ByteBuffer.allocate(netBufferSize); - // Note that calling beginHandshake might not actually do anything; - // the SSLEngine may not actually send the handshake until it's had - // some data from the application. And the higher level won't send - // any data until it thinks the handshake is completed. - // - // So this is a hack to force the handshake to occur: on the assumption - // that the first thing to be sent once TLS is running is - // the "<" from the start of a tag, we send a less-than sign now, - // which we'll remember must be removed that from the first message - // we get told to send. + encryptedReceived = ByteBuffer.allocate(netBufferSize); + + unwrappedReceived = ByteBuffer.allocate(appBufferSize + 50); + + + /* Note that calling beginHandshake might not actually do anything; + * the SSLEngine may not actually send the handshake until it's had + * some data from the application. And the higher level won't send + * any data until it thinks the handshake is completed. + * + * So this is a hack to force the handshake to occur: on the assumption + * that the first thing to be sent once TLS is running is + * the "<" from the start of a tag, we send a less-than sign now, + * which we'll remember must be removed that from the first message + * we get told to send. + */ sslEngine.beginHandshake(); @@ -172,63 +208,88 @@ public class JSSEContext extends TLSContext { Status status; int bytesProduced = 0; int bytesConsumed = 0; + int bytesToUnwrap = 0; HandshakeStatus handshakeStatus = null; ByteArray byteArray = null; synchronized(recvMutex) { try { encryptedReceived.flip(); - sslEngineResult = sslEngine.unwrap(encryptedReceived, unwrappedReceived); + + boolean unwrapDone = false; + do { + bytesToUnwrap = encryptedReceived.remaining(); + sslEngineResult = sslEngine.unwrap(encryptedReceived, unwrappedReceived); + status = sslEngineResult.getStatus(); + handshakeStatus = sslEngineResult.getHandshakeStatus(); + /* A call to unwrap can generate a status of FINISHED, which + * you won't get from SSLEngine.getHandshakeStatus. Such + * a status is an indication that we need to re-check whether + * anything's pending to be written + */ + if (handshakeStatus == HandshakeStatus.FINISHED) { + /* Special case will happen when the handshake completes following + * an unwrap. The first time we tried wrapping some plain stuff, + * it triggers the handshake but won't itself have been dealt with. + * So now the handshake has finished, we have to try sending it + * again + */ + handshakeCompleted = true; + wrapAndSendData(); + onConnected.emit(); + status = sslEngineResult.getStatus(); + } + + + switch (status) { + case BUFFER_OVERFLOW : + unwrappedReceived = enlargeBuffer("unwrappedReceived",unwrappedReceived,appBufferSize, appBufferMax); + unwrapDone = false; + break; + + case BUFFER_UNDERFLOW: + /* There's not enough data yet for engine to be able to decode + * a full message. Not a problem; assume that more will come + * in to the socket eventually + */ + unwrapDone = true; + break; + case CLOSED: + /* Engine closed - don't expect this here */ + emitError(null, "SSLEngine.unwrap returned " + status); + return bytesConsumed; + + case OK: + /* Some stuff was unwrapped. */ + bytesConsumed += sslEngineResult.bytesConsumed(); + bytesProduced = sslEngineResult.bytesProduced(); + + /* It may be that the unwrap consumed some, but not all of + * the data. In which case, the loop continues to give it + * another chance to process whatever's remaining + */ + if (sslEngineResult.bytesConsumed() == 0) { + /* No point looping around again */ + unwrapDone = true; + } + else { + /* It consumed some bytes, but perhaps not everything */ + unwrapDone = (sslEngineResult.bytesConsumed() == bytesToUnwrap); + } + break; + } + } while (!unwrapDone); + encryptedReceived.compact(); - // A call to unwrap can generate a status of FINISHED, which - // you won't get from SSLEngine.getHandshakeStatus. Such - // a status is an indication that we need to re-check whether - // anything's pending to be written - handshakeStatus = sslEngineResult.getHandshakeStatus(); bytesConsumed += sslEngineResult.bytesConsumed(); bytesProduced = sslEngineResult.bytesProduced(); } catch (SSLException e) { - throw new RuntimeException("unwrap produced: " + e); - } - - - status = sslEngineResult.getStatus(); - boolean finished = false; - while (!finished) { - switch (status) { - case BUFFER_UNDERFLOW: - // There's not enough data yet for engine to be able to decode - // a full message. Not a problem; assume that more will come - // in to the socket eventually - finished = true; - break; - case OK: - // Unwrap was OK - finished = true; - break; - case BUFFER_OVERFLOW: - // Not enough room in "unwrappedReceived" to write the data - // TODO: need to fix this - case CLOSED: - // Engine closed - don't expect this here - emitError(null, "SSLEngine.unwrap returned " + status); - return bytesConsumed; - } - } - - if (handshakeStatus == HandshakeStatus.FINISHED) { - // Special case will happen when the handshake completes following - // an unwrap. The first time we tried wrapping some plain stuff, - // it triggers the handshake but won't itself have been dealt with. - // So now the handshake has finished, we have to try sending it - // again - handshakeCompleted = true; - wrapAndSendData(); - onConnected.emit(); + emitError(e, "unwrap failed"); + return bytesConsumed; } if (bytesProduced > 0) { @@ -241,7 +302,7 @@ public class JSSEContext extends TLSContext { } - // Now out of synchronized block + /* Now out of synchronized block */ if (byteArray != null) { onDataForApplication.emit(byteArray); } @@ -261,14 +322,16 @@ public class JSSEContext extends TLSContext { ByteArray byteArray = null; SSLEngineResult sslEngineResult = null; Status status = null; + HandshakeStatus handshakeStatus = null; boolean handshakeFinished = false; synchronized(sendMutex) { - // Check if there's anything outstanding to be sent at the - // top of the loop, so that we clear the "wrappedToSend" - // buffer before asking the engine to encrypt anything - // TODO: is this required? I don't think anything gets put in - // wrappedToSend apart from in here? + /* Check if there's anything outstanding to be sent at the + * top of the loop, so that we clear the "wrappedToSend" + * buffer before asking the engine to encrypt anything + * TODO: is this required? I don't think anything gets put in + * wrappedToSend apart from in here? + */ wrappedToSend.flip(); if (wrappedToSend.hasRemaining()) { byte[] b = new byte[(wrappedToSend.remaining())]; @@ -276,49 +339,64 @@ public class JSSEContext extends TLSContext { byteArray = new ByteArray(b); } wrappedToSend.compact(); - } // end synchronized + } /* end synchronized */ if (byteArray != null) { int s = byteArray.getSize(); - onDataForNetwork.emit(byteArray); bytesSentToSocket += s; byteArray = null; } - // There's nothing waiting to be sent. Now see what new data needs - // encrypting + /* There's nothing waiting to be sent. Now see what new data needs + * encrypting + */ synchronized(sendMutex) { plainToSend.flip(); if (!plainToSend.hasRemaining()) { - // Nothing more to be encrypted + /* Nothing more to be encrypted */ plainToSend.compact(); return; } try { - sslEngineResult = sslEngine.wrap(plainToSend, wrappedToSend); + boolean wrapDone = false; + do { + sslEngineResult = sslEngine.wrap(plainToSend, wrappedToSend); + handshakeStatus = sslEngineResult.getHandshakeStatus(); + status = sslEngineResult.getStatus(); + + + if (status == Status.BUFFER_OVERFLOW) { + wrappedToSend = enlargeBuffer( + "wrappedToSend", wrappedToSend, netBufferSize, netBufferMax); + } + else { + wrapDone = true; + } + } + while (!wrapDone); } + catch (SSLException e) { - // TODO: Is there anything more that can be done here? - // TODO: this is called inside the mutex, does this matter? + /* This could result from the "enlargeBuffer" running out of space */ emitError(e,"SSLEngine.wrap failed"); return; } plainToSend.compact(); - status = sslEngineResult.getStatus(); - - // FINISHED can only come back for wrap() or unwrap(); so check to - // see if we just had it - if (sslEngineResult.getHandshakeStatus() == HandshakeStatus.FINISHED) { + /* FINISHED can only come back for wrap() or unwrap(); so check to + * see if we just had it + */ + if (handshakeStatus == HandshakeStatus.FINISHED) { handshakeFinished = true; } switch (status) { case OK: - // This is the only status we expect here. It means the - // data was successfully wrapped and that there's something - // to be sent. + /* This is the only status we expect here. It means the + * data was successfully wrapped and that there's something + * to be sent. + */ wrappedToSend.flip(); if (wrappedToSend.hasRemaining()) { byte[] b = new byte[(wrappedToSend.remaining())]; @@ -329,19 +407,17 @@ public class JSSEContext extends TLSContext { break; case BUFFER_UNDERFLOW: - // Can't happen for a wrap + /* Can't happen for a wrap */ case CLOSED : - // ??? + /* Engine closed - don't expect this here */ case BUFFER_OVERFLOW: - // The "wrappedToSend" buffer, which we had previously made - // sure was empty, isn't big enough. - // TODO: I don't think this can happen though - // TODO: Note that we're in sychronized block here + /* We already dealt with this, so don't expect to come here + */ emitError(null, "SSLEngine.wrap returned " + status); return; } - } // end synchronized + } /* end synchronized */ if (handshakeFinished) { handshakeCompleted = true; @@ -355,8 +431,9 @@ public class JSSEContext extends TLSContext { byteArray = null; } - // Note that there may still be stuff in "plainToSend" that hasn't - // yet been consumed + /* Note that there may still be stuff in "plainToSend" that hasn't + * yet been consumed + */ return; } @@ -369,45 +446,77 @@ public class JSSEContext extends TLSContext { */ private boolean processHandshakeStatus() { HandshakeStatus handshakeStatus; - + handshakeStatus = sslEngine.getHandshakeStatus(); switch (handshakeStatus) { case NOT_HANDSHAKING: - // No handshaking going on - session is available, no more - // handshake status to process + /* No handshaking going on - session is available, no more + * handshake status to process + */ return false; case NEED_TASK: - runDelegatedTasks(false); // false==don't create separate threads + runDelegatedTasks(false); /* false==don't create separate threads */ - // after tasks have run, need to come back here and check - // handshake status again + /* after tasks have run, need to come back here and check + * handshake status again + */ return true; case NEED_WRAP: - // SSLEngine wants some data that it can wrap for sending to the - // other side + /* SSLEngine wants some data that it can wrap for sending to the + * other side + */ wrapAndSendData(); - // after sending data, need to check handshake status again + /* after sending data, need to check handshake status again */ return true; case NEED_UNWRAP: - // SSLEngine wants data from other side that it can unwrap and - // process + /* SSLEngine wants data from other side that it can unwrap and + * process + */ int consumed = unwrapPendingData(); return (consumed > 0); case FINISHED: - // "This value is only generated by a call to wrap/unwrap when - // that call finishes a handshake. It is never generated by - // SSLEngine.getHandshakeStatus()" + /* "This value is only generated by a call to wrap/unwrap when + * that call finishes a handshake. It is never generated by + * SSLEngine.getHandshakeStatus()" + */ default: - // There are no other values, but compiler requires this + /* There are no other values, but compiler requires this */ throw new RuntimeException("unexpected handshake status " + handshakeStatus); } } /** + * Create a ByteBuffer that is a copy of an existing buffer, but with a + * larger capacity. + * @param bufferName the name of the buffer, for logging purposes + * @param bb the original ByteBuffer + * @param growBy how many bytes to grow the buffer by + * @param maxSize the maximum size that the output buffer is allowed to be + * @return a ByteBuffer that will have been enlarged by <em>growBy</em> + * @throws BufferOverflowException if adding <em>growBy</em> would take + * the buffer's size to greater than <em>maxSize</em> + */ + private ByteBuffer enlargeBuffer( + String bufferName, ByteBuffer bb, int growBy, int maxSize) + throws SSLException { + int newSize = bb.capacity() + growBy; + if (newSize <= maxSize) { + logger_.fine("Buffer " + bufferName + + " growing from " + bb.capacity() + " to " + newSize); + ByteBuffer temp = ByteBuffer.allocate(newSize); + bb.flip(); + temp.put(bb); + return temp; + } + throw new SSLException("Buffer for " + bufferName + + " exceeded maximum size of " + maxSize); + } + + /** * Create and start running delegated tasks for all pending delegated tasks * @param createThreads <em>true</em> to run tasks in separate threads, * <em>false</em> to run them all in series in the current thread @@ -459,11 +568,12 @@ public class JSSEContext extends TLSContext { peerCertificate = new JavaCertificate(certs[0]); - // Swiften uses SSL_get_verify_result() for this, and the documentation - // for that says it "while the verification of a certificate can fail - // because of many reasons at the same time. Only the last verification - // error that occurred..is available". - // So once one problem is found, don't bother looking for others. + /* Swiften uses SSL_get_verify_result() for this, and the documentation + * for that says it "while the verification of a certificate can fail + * because of many reasons at the same time. Only the last verification + * error that occurred..is available". + * So once one problem is found, don't bother looking for others. + */ if (certificateException != null) { if (certificateException instanceof CertificateNotYetValidException) { @@ -483,95 +593,186 @@ public class JSSEContext extends TLSContext { @Override public boolean setClientCertificate(PKCS12Certificate cert) { - // TODO: NYI. - // It's possible this is going to change as a result of Alexey's work - // so will leave for now + /* TODO: NYI. + * It's possible this is going to change as a result of Alexey's work + * so will leave for now + */ return false; } @Override public void handleDataFromNetwork(ByteArray data) { if (hasError()) { - // We have previously seen, and reported, an error. Emit again + /* We have previously seen, and reported, an error. Emit again */ onError.emit(); return; } + + /* Note that we need to deal with arbitrarily large ByteArrays here; + * specifically it may be that the number of bytes from the network is + * larger than the value of "netBufferMax" that was used to size the + * encryptedReceived buffer + */ byte[] b = data.getData(); - synchronized(recvMutex) { - try { - // TODO: could "encryptedReceived" already have stuff in it? - encryptedReceived.put(b); - } - catch (BufferOverflowException e) { - emitError(e, "Unable to add data to encryptedReceived"); - return; + /* We need to deal with arbitrarily large ByteArrays here; specifically + * it may be that the number of bytes from the network is + * larger than the value of "netBufferMax" that was used to size the + * encryptedReceived buffer + */ + int remaining = b.length; + int chunkPos = 0; + while (remaining > 0) { + synchronized(recvMutex) { + int chunkSize = encryptedReceived.remaining(); + if (chunkSize == 0) { + try { + encryptedReceived = enlargeBuffer( + "encryptedReceived", encryptedReceived, netBufferSize, netBufferMax); + /* We know that this will now give us a non-zero value */ + chunkSize = encryptedReceived.remaining(); + } + catch (SSLException e) { + /* Enlarging buffer failed */ + emitError(e, "encryptedReceived buffer reached maximum size"); + return; + } + + } + if (remaining <= chunkSize) { + /* There's room in the buffer for all remaining bytes */ + chunkSize = remaining; + } + try { + encryptedReceived.put(b, chunkPos, chunkSize); + remaining = (remaining - chunkSize); + chunkPos = (chunkPos + chunkSize); + } + catch (BufferOverflowException e) { + /* We never expect buffer overflow, because we are being + * careful not to write too much. If this happens, + * then include info in the error that may help + * diagnosis + */ + emitError(e, "Unexpected when writing encryptedReceived; remaining=" + + remaining + + "; chunkPos=" + chunkPos + + "; chunkSize= " + chunkSize + + "; encryptedReceived=" + encryptedReceived); + return; + } } + + unwrapPendingData(); + + /* Now keep checking SSLEngine until no more handshakes are required */ + do { + /* */ + } while (processHandshakeStatus()); + + + /* Loop round so long as there are still bytes from the network + * to be processed + */ } - - unwrapPendingData(); - - // Now keep checking SSLEngine until no more handshakes are required - do { - // - } while (processHandshakeStatus()); - } @Override public void handleDataFromApplication(ByteArray data) { if (hasError()) { - // We have previously seen, and reported, an error. Emit again + /* We have previously seen, and reported, an error. Emit again */ onError.emit(); return; } byte[] b = data.getData(); - - synchronized(sendMutex) { - try { - // Note that "plainToSend" may not be empty, because it's possible - // that calls to SSLEngine.wrap haven't yet consumed everything - // in there - switch (hack) { - case SENDING_FAKE_LT : - plainToSend.put(b); - hack = HackStatus.DISCARD_FIRST_LT; - break; - - case DISCARD_FIRST_LT: - if (b.length > 0) { - if (b[0] == (byte)'<') { - plainToSend.put(b,1,b.length - 1); - hack = HackStatus.HACK_DONE; - } - else { - emitError(null, - "First character sent after TLS started was " + - b[0] + " and not '<'"); + + /* Need to cope in the case that the application sends a ByteArray + * with more data than will fit in the "plainToSend" buffer + */ + int remaining = b.length; + int chunkPos = 0; + while (remaining > 0) { + synchronized(sendMutex) { + int chunkSize = plainToSend.remaining(); + if (chunkSize == 0) { + try { + plainToSend = enlargeBuffer("plainToSend", plainToSend, appBufferSize, appBufferMax); + /* We know that this will now give us a non-zero value */ + chunkSize = plainToSend.remaining(); + } + catch (SSLException e) { + /* Enlarging buffer failed */ + emitError(e, "plainToSend buffer reached maximum size"); + return; + } + } + if (remaining <= chunkSize) { + /* There's room in the buffer for all remaining bytes */ + chunkSize = remaining; + } + try { + + /* Note that "plainToSend" may not be empty, because it's possible + * that calls to SSLEngine.wrap haven't yet consumed everything + * in there + */ + switch (hack) { + case SENDING_FAKE_LT : + plainToSend.put(b, chunkPos, chunkSize); + hack = HackStatus.DISCARD_FIRST_LT; + break; + + case DISCARD_FIRST_LT: + if (b.length > 0) { + if (b[0] == (byte)'<') { + plainToSend.put(b,1,chunkSize - 1); + hack = HackStatus.HACK_DONE; + } + else { + emitError(null, + "First character sent after TLS started was " + + b[0] + " and not '<'"); + return; + } } + break; + case HACK_DONE: + plainToSend.put(b, chunkPos, chunkSize); + break; } - break; - case HACK_DONE: - plainToSend.put(b); - break; + + remaining = (remaining - chunkSize); + chunkPos = (chunkPos + chunkSize); + } + catch (BufferOverflowException e) { + /* We never expect buffer overflow, because we are being + * careful not to write too much. If this happens, then + * include info in the error that may help diagnosis + */ + emitError(e, "Unexpected when writing to plainToSend; remaining=" + + remaining + + "; chunkPos=" + chunkPos + + "; chunkSize=" + chunkSize + + "; plainToSend=" + plainToSend); + return; } } - catch (BufferOverflowException e) { - // TODO: anything else here? - emitError(e, "plainToSend.put failed"); - return; - } - } - wrapAndSendData(); - - // Now keep checking SSLEngine until no more handshakes are required - do { - // - } while (processHandshakeStatus()); + wrapAndSendData(); + + /* Now keep checking SSLEngine until no more handshakes are required */ + do { + /* */ + } while (processHandshakeStatus()); + /* Loop round so long as there are still bytes from the application + * to be processed + */ + } } + + @Override public Certificate getPeerCertificate() { return peerCertificate; @@ -584,8 +785,9 @@ public class JSSEContext extends TLSContext { @Override public ByteArray getFinishMessage() { - // TODO: Doesn't appear to be an obvious way to get this - // information from SSLEngine et al. For now, return null. + /* TODO: Doesn't appear to be an obvious way to get this + * information from SSLEngine et al. For now, return null. + */ return null; } @@ -601,7 +803,7 @@ public class JSSEContext extends TLSContext { } String result = - "JSSEContext with SSLEngine = " + + "JSSEContext(" + hashCode() + ") with SSLEngine = " + sslEngine + "; handshakeCompleted=" + handshakeCompleted; @@ -610,12 +812,12 @@ public class JSSEContext extends TLSContext { } return result + errors; } - + /** * Construct a new JSSEContext object. */ public JSSEContext() { - // + /* */ } /** @@ -629,6 +831,29 @@ public class JSSEContext extends TLSContext { private ByteBuffer plainToSend; /** + * The initial size of the buffer used for application data. This is + * likely to be enough for plaintext buffers, but in cases where the size + * is exceeded (for example, the result of decrypting a particularly huge + * message), the buffer will be increased by this amount. + */ + private int appBufferSize; + + /** + * The maximum amount to grow any buffer for application data + */ + private int appBufferMax; + + /** + * Initial size of buffer used for encrypted data to/from SSL. + */ + private int netBufferSize; + + /** + * The maximum amount to grow any buffer for network data + */ + private int netBufferMax; + + /** * Contains encrypted information produced by the SSLEngine which is * waiting to be sent over the socket */ @@ -671,9 +896,10 @@ public class JSSEContext extends TLSContext { * to send */ private static enum HackStatus { SENDING_FAKE_LT, DISCARD_FIRST_LT, HACK_DONE } - private static HackStatus hack = HackStatus.SENDING_FAKE_LT; + private HackStatus hack = HackStatus.SENDING_FAKE_LT; + private final Logger logger_ = Logger.getLogger(this.getClass().getName()); /** * Set up the SSLContext and JavaTrustManager that will be used for this * JSSEContext. @@ -686,25 +912,74 @@ public class JSSEContext extends TLSContext { */ private SSLContext getSSLContext() { + JavaTrustManager[] tm = null; + try { - JavaTrustManager [] tm = new JavaTrustManager[] { new JavaTrustManager(this)}; - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init( - null, // KeyManager[] - tm, // TrustManager[] - null); // SecureRandom - return sslContext; + tm = new JavaTrustManager[] { new JavaTrustManager(this)}; } catch (SSLException e) { emitError(e, "Couldn't create JavaTrustManager"); } - catch (NoSuchAlgorithmException e) { - emitError(e, "Couldn't create SSLContext"); - } - catch (KeyManagementException e) { - emitError(e, "Couldn't initialise SSLContext"); + + /* + * This is the list of protocols, in preference order, that will be + * used to obtain an SSLContext. + * + * Note that "TLSv1.2" and "TLSv1.1" appear to be available for + * JRE7 but not JRE6 + * + * Note that the actual protocol negotiated will depend on what + * the server can support: the one offered by the client is "best", + * and server may not support that so will use a lesser value + * + * The loop will pick the first protocol that returns an SSLContext. + * + */ + final String protocols[] = { + /* These work for JRE 7 but may not be available for JRE 6*/ + "TLSv1.2", "TLSv1.1", + + /* These work for JRE 6 */ + "TLSv1", "TLS", "SSLv3" }; + + /* Accumulate a list of problems which will be discarded if things + * go well, but including in the error if things fail + */ + String problems = ""; + GeneralSecurityException lastException = null; + + SSLContext sslContext = null; + + for (String protocol:protocols) { + try { + sslContext = SSLContext.getInstance(protocol); + + /* That worked */ + try { + sslContext.init( + null, /* KeyManager[] */ + tm, /* TrustManager[] */ + null); /* SecureRandom */ + + return sslContext; + } + catch (KeyManagementException e) { + lastException = e; + problems += "Could not get SSLContext for " + protocol + " (" + e + ")\n"; + } + } + catch (NoSuchAlgorithmException e) { + lastException = e; + problems += "Could not get SSLContext for " + protocol + " (" + e + ")\n"; + /* Try the next one */ + } } - return null; + /* Fell through without being able to initialise using any + * of the protocols + */ + emitError(lastException, problems); + return null; + } } |