| /* SSLEngineImpl.java -- implementation of SSLEngine. |
| Copyright (C) 2006 Free Software Foundation, Inc. |
| |
| This file is a part of GNU Classpath. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2 of the License, or (at |
| your option) any later version. |
| |
| GNU Classpath is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; if not, write to the Free Software |
| Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 |
| USA |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| |
| package gnu.javax.net.ssl.provider; |
| |
| import gnu.classpath.debug.Component; |
| import gnu.classpath.debug.SystemLogger; |
| |
| import gnu.java.security.util.ByteBufferOutputStream; |
| import gnu.javax.net.ssl.Session; |
| import gnu.javax.net.ssl.SSLRecordHandler; |
| |
| import java.nio.BufferOverflowException; |
| import java.nio.ByteBuffer; |
| import java.nio.ByteOrder; |
| |
| import java.security.NoSuchAlgorithmException; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.zip.DataFormatException; |
| |
| import javax.crypto.IllegalBlockSizeException; |
| import javax.crypto.ShortBufferException; |
| import javax.net.ssl.SSLEngine; |
| import javax.net.ssl.SSLEngineResult; |
| import javax.net.ssl.SSLException; |
| import javax.net.ssl.SSLSession; |
| import javax.net.ssl.SSLEngineResult.HandshakeStatus; |
| import javax.net.ssl.SSLEngineResult.Status; |
| |
| public final class SSLEngineImpl extends SSLEngine |
| { |
| final SSLContextImpl contextImpl; |
| private SSLRecordHandler[] handlers; |
| private static final SystemLogger logger = SystemLogger.SYSTEM; |
| private SessionImpl session; |
| private InputSecurityParameters insec; |
| private OutputSecurityParameters outsec; |
| private boolean inClosed; |
| private boolean outClosed; |
| private boolean createSessions; |
| private boolean needClientAuth; |
| private boolean wantClientAuth; |
| private boolean initialHandshakeDone; |
| private AbstractHandshake handshake; |
| private Alert lastAlert; |
| private SSLEngineResult.HandshakeStatus handshakeStatus; |
| private boolean changeCipherSpec; |
| |
| private String[] enabledSuites; |
| private String[] enabledProtocols; |
| |
| /** |
| * We can receive any message chunked across multiple records, |
| * including alerts, even though all alert messages are only two |
| * bytes long. Handshake messages are de-chunked in the handshake |
| * handler, change-cipher-spec messages are always empty, and we |
| * don't care about chunking of application messages. |
| * |
| * This buffer will hold the incomplete alert that we receive, if |
| * any. |
| */ |
| private final ByteBuffer alertBuffer; |
| |
| private Mode mode; |
| |
| private enum Mode { SERVER, CLIENT } |
| |
| SSLEngineImpl (SSLContextImpl contextImpl, String host, int port) |
| { |
| super(host, port); |
| this.contextImpl = contextImpl; |
| handlers = new SSLRecordHandler[256]; |
| session = new SessionImpl(); |
| session.suite = CipherSuite.TLS_NULL_WITH_NULL_NULL; |
| session.version = ProtocolVersion.TLS_1_1; |
| byte[] sid = new byte[32]; |
| contextImpl.random.nextBytes(sid); |
| session.setId(new Session.ID(sid)); |
| session.setRandom(contextImpl.random); |
| |
| if (Debug.DEBUG) |
| logger.logv(Component.SSL_RECORD_LAYER, "generated session ID {0} with random {1}", |
| session.id(), contextImpl.random); |
| |
| // Begin with no encryption. |
| insec = new InputSecurityParameters (null, null, null, session, |
| CipherSuite.TLS_NULL_WITH_NULL_NULL); |
| outsec = new OutputSecurityParameters (null, null, null, session, |
| CipherSuite.TLS_NULL_WITH_NULL_NULL); |
| inClosed = false; |
| outClosed = false; |
| needClientAuth = false; |
| wantClientAuth = false; |
| createSessions = true; |
| initialHandshakeDone = false; |
| alertBuffer = ByteBuffer.wrap (new byte[2]); |
| mode = null; |
| lastAlert = null; |
| handshakeStatus = SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; |
| changeCipherSpec = false; |
| |
| // Set up default protocols and suites. |
| enabledProtocols = new String[] { |
| ProtocolVersion.TLS_1_1.toString(), |
| ProtocolVersion.TLS_1.toString(), |
| ProtocolVersion.SSL_3.toString() |
| }; |
| enabledSuites = defaultSuites(); |
| } |
| |
| static String[] defaultSuites() |
| { |
| return new String[] { |
| CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA.toString(), |
| CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA.toString(), |
| CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA.toString(), |
| CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA.toString(), |
| CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA.toString(), |
| CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA.toString(), |
| CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA.toString(), |
| CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA.toString(), |
| CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA.toString(), |
| CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA.toString(), |
| CipherSuite.TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA.toString(), |
| CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA.toString(), |
| CipherSuite.TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA.toString(), |
| CipherSuite.TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA.toString(), |
| CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA.toString(), |
| CipherSuite.TLS_RSA_WITH_RC4_128_MD5.toString(), |
| CipherSuite.TLS_RSA_WITH_RC4_128_SHA.toString(), |
| CipherSuite.TLS_DHE_DSS_WITH_DES_CBC_SHA.toString(), |
| CipherSuite.TLS_DHE_RSA_WITH_DES_CBC_SHA.toString(), |
| CipherSuite.TLS_DH_DSS_WITH_DES_CBC_SHA.toString(), |
| CipherSuite.TLS_DH_RSA_WITH_DES_CBC_SHA.toString(), |
| CipherSuite.TLS_RSA_WITH_DES_CBC_SHA.toString(), |
| CipherSuite.TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA.toString(), |
| CipherSuite.TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA.toString(), |
| CipherSuite.TLS_RSA_EXPORT_WITH_DES40_CBC_SHA.toString(), |
| CipherSuite.TLS_RSA_EXPORT_WITH_RC4_40_MD5.toString(), |
| CipherSuite.TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA.toString(), |
| CipherSuite.TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA.toString(), |
| CipherSuite.TLS_RSA_WITH_NULL_MD5.toString(), |
| CipherSuite.TLS_RSA_WITH_NULL_SHA.toString() |
| }; |
| } |
| |
| // XXX implement? |
| /*public void registerHandler (final int contentType, |
| SSLRecordHandler handler) |
| throws SSLException |
| { |
| if (type.equals (ContentType.CHANGE_CIPHER_SPEC) |
| || type.equals (ContentType.ALERT) |
| || type.equals (ContentType.HANDSHAKE) |
| || type.equals (ContentType.APPLICATION_DATA)) |
| throw new SSLException ("can't override handler for content type " + type); |
| int i = type.getValue (); |
| if (i < 0 || i > 255) |
| throw new SSLException ("illegal content type: " + type); |
| handlers[i] = handler; |
| }*/ |
| |
| @Override |
| public void beginHandshake () throws SSLException |
| { |
| if (Debug.DEBUG) |
| logger.log(Component.SSL_HANDSHAKE, "{0} handshake begins", mode); |
| |
| if (mode == null) |
| throw new IllegalStateException("setUseClientMode was never used"); |
| |
| switch (mode) |
| { |
| case SERVER: |
| if (getHandshakeStatus() != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) |
| throw new SSLException("handshake already in progress"); |
| try |
| { |
| handshake = new ServerHandshake(initialHandshakeDone, this); |
| } |
| catch (NoSuchAlgorithmException nsae) |
| { |
| throw new SSLException(nsae); |
| } |
| break; |
| |
| case CLIENT: |
| try |
| { |
| handshake = new ClientHandshake(this); |
| } |
| catch (NoSuchAlgorithmException nsae) |
| { |
| throw new SSLException(nsae); |
| } |
| break; |
| } |
| } |
| |
| @Override |
| public void closeInbound() |
| { |
| inClosed = true; |
| } |
| |
| @Override |
| public void closeOutbound() |
| { |
| lastAlert = new Alert(Alert.Level.WARNING, Alert.Description.CLOSE_NOTIFY); |
| } |
| |
| @Override |
| public Runnable getDelegatedTask() |
| { |
| if (handshake == null) |
| return null; |
| return handshake.getTask(); |
| } |
| |
| @Override |
| public String[] getEnabledCipherSuites() |
| { |
| return (String[]) enabledSuites.clone(); |
| } |
| |
| @Override |
| public String[] getEnabledProtocols() |
| { |
| return (String[]) enabledProtocols.clone(); |
| } |
| |
| @Override |
| public boolean getEnableSessionCreation() |
| { |
| return createSessions; |
| } |
| |
| @Override |
| public HandshakeStatus getHandshakeStatus() |
| { |
| if (handshake == null) |
| return HandshakeStatus.NOT_HANDSHAKING; |
| return handshake.status(); |
| } |
| |
| @Override |
| public boolean getNeedClientAuth() |
| { |
| return needClientAuth; |
| } |
| |
| @Override |
| public SSLSession getSession() |
| { |
| return session; |
| } |
| |
| @Override |
| public boolean getUseClientMode () |
| { |
| return (mode == Mode.CLIENT); |
| } |
| |
| @Override |
| public boolean getWantClientAuth() |
| { |
| return wantClientAuth; |
| } |
| |
| @Override |
| public boolean isInboundDone() |
| { |
| return inClosed; |
| } |
| |
| @Override |
| public boolean isOutboundDone() |
| { |
| return outClosed; |
| } |
| |
| @Override |
| public void setEnableSessionCreation(final boolean createSessions) |
| { |
| this.createSessions = createSessions; |
| } |
| |
| @Override |
| public void setEnabledCipherSuites(final String[] suites) |
| { |
| if (suites.length == 0) |
| throw new IllegalArgumentException("need at least one suite"); |
| enabledSuites = (String[]) suites.clone(); |
| } |
| |
| @Override |
| public void setEnabledProtocols(final String[] protocols) |
| { |
| if (protocols.length == 0) |
| throw new IllegalArgumentException("need at least one protocol"); |
| enabledProtocols = (String[]) protocols.clone(); |
| } |
| |
| @Override |
| public String[] getSupportedCipherSuites() |
| { |
| // XXX if we ever want to support "pluggable" cipher suites, we'll need |
| // to figure this out. |
| |
| return CipherSuite.availableSuiteNames().toArray(new String[0]); |
| } |
| |
| @Override |
| public String[] getSupportedProtocols() |
| { |
| return new String[] { ProtocolVersion.SSL_3.toString(), |
| ProtocolVersion.TLS_1.toString(), |
| ProtocolVersion.TLS_1_1.toString() }; |
| } |
| |
| @Override |
| public void setNeedClientAuth(final boolean needClientAuth) |
| { |
| this.needClientAuth = needClientAuth; |
| } |
| |
| @Override |
| public void setUseClientMode (final boolean clientMode) |
| { |
| if (clientMode) |
| mode = Mode.CLIENT; |
| else |
| mode = Mode.SERVER; |
| } |
| |
| public @Override void setWantClientAuth(final boolean wantClientAuth) |
| { |
| this.wantClientAuth = wantClientAuth; |
| } |
| |
| public @Override SSLEngineResult unwrap (final ByteBuffer source, |
| final ByteBuffer[] sinks, |
| final int offset, final int length) |
| throws SSLException |
| { |
| if (mode == null) |
| throw new IllegalStateException ("setUseClientMode was never called"); |
| |
| if (inClosed) |
| return new SSLEngineResult(SSLEngineResult.Status.CLOSED, |
| handshakeStatus, 0, 0); |
| |
| if (source.remaining() < 5) |
| { |
| return new SSLEngineResult(SSLEngineResult.Status.BUFFER_UNDERFLOW, |
| handshakeStatus, 0, 0); |
| } |
| |
| Record record = null; |
| boolean helloV2 = false; |
| |
| // XXX: messages may be chunked across multiple records; does this |
| // include the SSLv2 message? I don't think it does, but we should |
| // make sure. |
| if (!getUseClientMode() && (source.get(source.position()) & 0x80) == 0x80) |
| { |
| if (handshake == null) |
| beginHandshake(); |
| int hellolen = source.getShort(source.position()) & 0x7FFF; |
| this.handshake.handleV2Hello(source.slice()); |
| if (!insec.cipherSuite().equals (CipherSuite.TLS_NULL_WITH_NULL_NULL)) |
| throw new SSLException ("received SSLv2 client hello in encrypted " |
| + "session; this is invalid."); |
| if (Debug.DEBUG) |
| logger.log (Component.SSL_RECORD_LAYER, |
| "converting SSLv2 client hello to version 3 hello"); |
| |
| source.getShort(); // skip length |
| ClientHelloV2 v2 = new ClientHelloV2(source.slice()); |
| |
| if (Debug.DEBUG) |
| logger.log(Component.SSL_RECORD_LAYER, "v2 hello: {0}", v2); |
| |
| List<CipherSuite> suites = v2.cipherSpecs(); |
| |
| ClientHelloBuilder hello = new ClientHelloBuilder(); |
| hello.setVersion(v2.version ()); |
| |
| Random random = hello.random(); |
| byte[] challenge = v2.challenge(); |
| if (challenge.length < 32) |
| { |
| byte[] b = new byte[32]; |
| System.arraycopy(challenge, 0, b, b.length - challenge.length, |
| challenge.length); |
| challenge = b; |
| } |
| random.setGmtUnixTime((challenge[0] & 0xFF) << 24 |
| | (challenge[1] & 0xFF) << 16 |
| | (challenge[2] & 0xFF) << 8 |
| | (challenge[3] & 0xFF)); |
| random.setRandomBytes(challenge, 4); |
| |
| byte[] sessionId = v2.sessionId(); |
| hello.setSessionId(sessionId, 0, sessionId.length); |
| hello.setCipherSuites(suites); |
| ArrayList<CompressionMethod> comps = new ArrayList<CompressionMethod>(1); |
| comps.add(CompressionMethod.NULL); |
| hello.setCompressionMethods(comps); |
| |
| record = new Record(ByteBuffer.allocate(hello.length() + 9)); |
| record.setContentType(ContentType.HANDSHAKE); |
| record.setVersion(v2.version()); |
| record.setLength(hello.length() + 4); |
| |
| Handshake handshake = new Handshake(record.fragment()); |
| handshake.setLength(hello.length()); |
| handshake.setType(Handshake.Type.CLIENT_HELLO); |
| |
| handshake.bodyBuffer().put(hello.buffer()); |
| source.position(source.position() + hellolen); |
| helloV2 = true; |
| } |
| else |
| record = new Record(source); |
| |
| ContentType type = record.contentType (); |
| |
| if (Debug.DEBUG) |
| logger.log(Component.SSL_RECORD_LAYER, "input record:\n{0}", record); |
| |
| if (record.length() > session.getPacketBufferSize() - 5) |
| { |
| lastAlert = new Alert(Alert.Level.FATAL, |
| Alert.Description.RECORD_OVERFLOW); |
| throw new AlertException(lastAlert); |
| } |
| |
| ByteBufferOutputStream sysMsg = null; |
| ByteBuffer msg = null; |
| |
| int produced = 0; |
| try |
| { |
| // Application data will get decrypted directly into the user's |
| // output buffers. |
| if (record.contentType() == ContentType.APPLICATION_DATA) |
| produced = insec.decrypt(record, sinks, offset, length); |
| else |
| { |
| if (insec.cipherSuite() == CipherSuite.TLS_NULL_WITH_NULL_NULL) |
| msg = record.fragment(); |
| else |
| { |
| sysMsg = new ByteBufferOutputStream(); |
| insec.decrypt(record, sysMsg); |
| } |
| } |
| |
| // Advance the input buffer past the record we just read. |
| if (!helloV2) |
| source.position(source.position() + record.length() + 5); |
| } |
| catch (BufferOverflowException boe) |
| { |
| // We throw this if the output buffers are not large enough; signal |
| // the caller about this. |
| logger.log(Component.SSL_RECORD_LAYER, "buffer overflow when decrypting", boe); |
| return new SSLEngineResult(SSLEngineResult.Status.BUFFER_OVERFLOW, |
| handshakeStatus, 0, 0); |
| } |
| catch (IllegalBlockSizeException ibse) |
| { |
| lastAlert = new Alert(Alert.Level.FATAL, |
| Alert.Description.BAD_RECORD_MAC); |
| throw new AlertException(lastAlert, ibse); |
| } |
| catch (DataFormatException dfe) |
| { |
| lastAlert = new Alert(Alert.Level.FATAL, |
| Alert.Description.DECOMPRESSION_FAILURE); |
| throw new AlertException(lastAlert, dfe); |
| } |
| catch (MacException me) |
| { |
| lastAlert = new Alert(Alert.Level.FATAL, |
| Alert.Description.BAD_RECORD_MAC); |
| throw new AlertException(lastAlert, me); |
| } |
| catch (ShortBufferException sbe) |
| { |
| // We've messed up if this happens. |
| lastAlert = new Alert(Alert.Level.FATAL, |
| Alert.Description.INTERNAL_ERROR); |
| throw new AlertException(lastAlert, sbe); |
| } |
| |
| SSLEngineResult result = null; |
| |
| // If we need to handle the output here, do it. Otherwise, the output |
| // has been stored in the supplied output buffers. |
| if (sysMsg != null) |
| { |
| if (Debug.DEBUG) |
| logger.logv(Component.SSL_RECORD_LAYER, "sysmessage {0}", sysMsg); |
| msg = sysMsg.buffer(); |
| } |
| |
| if (type == ContentType.CHANGE_CIPHER_SPEC) |
| { |
| // We *may* get a partial message, even though the message is only |
| // one byte long. |
| if (msg.remaining() == 0) |
| { |
| result = new SSLEngineResult (SSLEngineResult.Status.OK, |
| handshakeStatus, |
| record.length() + 5, 0); |
| } |
| else |
| { |
| byte b = msg.get(); |
| if (b != 1) |
| throw new SSLException ("unknown ChangeCipherSpec value: " + (b & 0xFF)); |
| InputSecurityParameters params = handshake.getInputParams(); |
| logger.log (Component.SSL_RECORD_LAYER, |
| "switching to input security parameters {0}", |
| params.cipherSuite()); |
| insec = params; |
| result = new SSLEngineResult (SSLEngineResult.Status.OK, |
| handshakeStatus, |
| record.length() + 5, 0); |
| } |
| } |
| else if (type == ContentType.ALERT) |
| { |
| int len = 0; |
| if (alertBuffer.position() > 0) |
| { |
| alertBuffer.put(msg.get()); |
| len = 1; |
| } |
| if (Debug.DEBUG) |
| logger.logv(Component.SSL_RECORD_LAYER, "processing alerts {0}", |
| Util.wrapBuffer(msg)); |
| len += msg.remaining() / 2; |
| Alert[] alerts = new Alert[len]; |
| int i = 0; |
| if (alertBuffer.position() > 0) |
| { |
| alertBuffer.flip(); |
| alerts[0] = new Alert(alertBuffer); |
| i++; |
| } |
| while (i < alerts.length) |
| { |
| alerts[i++] = new Alert(msg.duplicate()); |
| msg.position(msg.position() + 2); |
| } |
| if (Debug.DEBUG) |
| logger.logv(Component.SSL_RECORD_LAYER, "alerts: {0}", alerts.length); |
| |
| for (i = 0; i < alerts.length; i++) |
| { |
| if (alerts[i].level() == Alert.Level.FATAL) |
| throw new AlertException(alerts[i], false); |
| if (alerts[i].description() != Alert.Description.CLOSE_NOTIFY) |
| logger.log(java.util.logging.Level.WARNING, |
| "received alert: {0}", alerts[i]); |
| if (alerts[i].description() == Alert.Description.CLOSE_NOTIFY) |
| inClosed = true; |
| } |
| |
| if (msg.hasRemaining()) |
| alertBuffer.position(0).limit(2); |
| |
| result = new SSLEngineResult (SSLEngineResult.Status.OK, |
| handshakeStatus, |
| record.length() + 5, 0); |
| } |
| else if (type == ContentType.HANDSHAKE) |
| { |
| if (handshake == null) |
| beginHandshake(); |
| try |
| { |
| handshakeStatus = handshake.handleInput(msg); |
| } |
| catch (AlertException ae) |
| { |
| lastAlert = ae.alert(); |
| return new SSLEngineResult(SSLEngineResult.Status.OK, |
| SSLEngineResult.HandshakeStatus.NEED_WRAP, |
| 0, 0); |
| } |
| if (Debug.DEBUG) |
| logger.logv(Component.SSL_HANDSHAKE, "handshake status {0}", handshakeStatus); |
| result = new SSLEngineResult(SSLEngineResult.Status.OK, |
| handshakeStatus, |
| record.length() + 5, |
| 0); |
| if (handshakeStatus == HandshakeStatus.FINISHED) |
| { |
| handshake = null; |
| handshakeStatus = HandshakeStatus.NOT_HANDSHAKING; |
| } |
| } |
| else if (type == ContentType.APPLICATION_DATA) |
| { |
| // Do nothing more; the application data has been put into |
| // the output buffers. |
| result = new SSLEngineResult(SSLEngineResult.Status.OK, |
| handshakeStatus, |
| record.length() + 5, |
| produced); |
| } |
| else |
| { |
| SSLRecordHandler handler = handlers[type.getValue()]; |
| if (handler != null) |
| { |
| result = new SSLEngineResult(SSLEngineResult.Status.OK, |
| handshakeStatus, |
| record.length() + 5, |
| 0); |
| } |
| else |
| throw new SSLException ("unknown content type: " + type); |
| } |
| |
| if (Debug.DEBUG) |
| logger.logv(Component.SSL_RECORD_LAYER, "return result: {0}", result); |
| |
| return result; |
| } |
| |
| public @Override SSLEngineResult wrap (ByteBuffer[] sources, int offset, int length, |
| ByteBuffer sink) |
| throws SSLException |
| { |
| if (mode == null) |
| throw new IllegalStateException ("setUseClientMode was never called"); |
| |
| if (outClosed) |
| return new SSLEngineResult(SSLEngineResult.Status.CLOSED, |
| handshakeStatus, 0, 0); |
| |
| ContentType type = null; |
| ByteBuffer sysMessage = null; |
| if (Debug.DEBUG) |
| logger.logv(Component.SSL_RECORD_LAYER, "wrap {0} {1} {2} {3} / {4}", |
| sources, offset, length, sink, getHandshakeStatus()); |
| if (lastAlert != null) |
| { |
| type = ContentType.ALERT; |
| sysMessage = ByteBuffer.allocate(2); |
| Alert alert = new Alert(sysMessage); |
| alert.setDescription(lastAlert.description()); |
| alert.setLevel(lastAlert.level()); |
| if (lastAlert.description() == Alert.Description.CLOSE_NOTIFY) |
| outClosed = true; |
| } |
| else if (changeCipherSpec) |
| { |
| type = ContentType.CHANGE_CIPHER_SPEC; |
| sysMessage = ByteBuffer.allocate(1); |
| sysMessage.put(0, (byte) 1); |
| } |
| else if (getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP) |
| { |
| // If we are not encrypting, optimize the handshake to fill |
| // the buffer directly. |
| if (outsec.suite() == CipherSuite.TLS_NULL_WITH_NULL_NULL) |
| { |
| int orig = sink.position(); |
| sink.order(ByteOrder.BIG_ENDIAN); |
| sink.put((byte) ContentType.HANDSHAKE.getValue()); |
| sink.putShort((short) session.version.rawValue()); |
| sink.putShort((short) 0); |
| handshakeStatus = handshake.handleOutput(sink); |
| int produced = sink.position() - orig; |
| sink.putShort(orig + 3, (short) (produced - 5)); |
| if (Debug.DEBUG) |
| logger.logv(Component.SSL_RECORD_LAYER, "emitting record:\n{0}", |
| new Record((ByteBuffer) sink.duplicate().position(orig))); |
| SSLEngineResult result = new SSLEngineResult(SSLEngineResult.Status.OK, |
| handshakeStatus, 0, produced); |
| |
| // Note, this will only happen if we transition from |
| // TLS_NULL_WITH_NULL_NULL *to* TLS_NULL_WITH_NULL_NULL, which |
| // doesn't make a lot of sense, but we support it anyway. |
| if (handshakeStatus == HandshakeStatus.FINISHED) |
| { |
| handshake = null; // finished with it. |
| handshakeStatus = HandshakeStatus.NOT_HANDSHAKING; |
| } |
| return result; |
| } |
| |
| // Rough guideline; XXX. |
| sysMessage = ByteBuffer.allocate(sink.remaining() - 2048); |
| type = ContentType.HANDSHAKE; |
| try |
| { |
| handshakeStatus = handshake.handleOutput(sysMessage); |
| } |
| catch (AlertException ae) |
| { |
| lastAlert = ae.alert(); |
| return new SSLEngineResult(Status.OK, |
| HandshakeStatus.NEED_WRAP, 0, 0); |
| } |
| sysMessage.flip(); |
| if (Debug.DEBUG) |
| logger.logv(Component.SSL_HANDSHAKE, "handshake status {0}", |
| handshakeStatus); |
| } |
| |
| int produced = 0; |
| int consumed = 0; |
| |
| try |
| { |
| int orig = sink.position(); |
| int[] inout = null; |
| if (sysMessage != null) |
| { |
| if (Debug.DEBUG) |
| logger.logv(Component.SSL_RECORD_LAYER, "encrypt system message {0} to {1}", sysMessage, sink); |
| inout = outsec.encrypt(new ByteBuffer[] { sysMessage }, 0, 1, |
| type, sink); |
| produced = inout[1]; |
| } |
| else |
| { |
| if (outsec.needToSplitPayload()) |
| { |
| inout = outsec.encrypt(sources, offset, 1, |
| ContentType.APPLICATION_DATA, sink); |
| consumed = inout[0]; |
| produced = inout[1]; |
| if (length > 1) |
| { |
| inout = outsec.encrypt(sources, offset+1, length-1, |
| ContentType.APPLICATION_DATA, sink); |
| consumed += inout[0]; |
| produced += inout[1]; |
| } |
| } |
| else |
| { |
| inout = outsec.encrypt(sources, offset, length, |
| ContentType.APPLICATION_DATA, sink); |
| consumed = inout[0]; |
| produced = inout[1]; |
| } |
| } |
| |
| if (Debug.DEBUG) |
| logger.logv(Component.SSL_RECORD_LAYER, "emitting record:\n{0}", |
| new Record((ByteBuffer) sink.duplicate().position(orig).limit(produced))); |
| } |
| catch (ShortBufferException sbe) |
| { |
| // We don't expect this to happen, except for bugs; signal an |
| // internal error. |
| lastAlert = new Alert(Alert.Level.FATAL, Alert.Description.INTERNAL_ERROR); |
| return new SSLEngineResult(SSLEngineResult.Status.OK, handshakeStatus, 0, 0); |
| } |
| catch (IllegalBlockSizeException ibse) |
| { |
| // We don't expect this to happen, except for bugs; signal an |
| // internal error. |
| lastAlert = new Alert(Alert.Level.FATAL, Alert.Description.INTERNAL_ERROR); |
| return new SSLEngineResult(SSLEngineResult.Status.OK, handshakeStatus, 0, 0); |
| } |
| catch (DataFormatException dfe) |
| { |
| // We don't expect this to happen; signal an internal error. |
| lastAlert = new Alert(Alert.Level.FATAL, Alert.Description.INTERNAL_ERROR); |
| return new SSLEngineResult(SSLEngineResult.Status.OK, handshakeStatus, 0, 0); |
| } |
| |
| if (lastAlert != null && lastAlert.level() == Alert.Level.FATAL) |
| { |
| AlertException ae = new AlertException(lastAlert); |
| lastAlert = null; |
| throw ae; |
| } |
| |
| if (changeCipherSpec) |
| { |
| outsec = handshake.getOutputParams(); |
| changeCipherSpec = false; |
| } |
| SSLEngineResult result |
| = new SSLEngineResult(outClosed ? SSLEngineResult.Status.CLOSED |
| : SSLEngineResult.Status.OK, |
| handshakeStatus, consumed, produced); |
| if (handshakeStatus == HandshakeStatus.FINISHED) |
| { |
| handshake = null; // done with it. |
| handshakeStatus = HandshakeStatus.NOT_HANDSHAKING; |
| } |
| return result; |
| } |
| |
| // Package-private methods. |
| |
| SessionImpl session () |
| { |
| return session; |
| } |
| |
| void setSession(SessionImpl session) |
| { |
| this.session = session; |
| } |
| |
| void changeCipherSpec() |
| { |
| changeCipherSpec = true; |
| } |
| } |