return HandshakeStatus.NOT_HANDSHAKING; } } synchronized private void checkTaskThrown() throws SSLException { if (handshaker != null) { handshaker.checkThrown(); } } // // Handshaking and connection state code // /* * Provides "this" synchronization for connection state. * Otherwise, you can access it directly. */ synchronized private int getConnectionState() { return connectionState; } synchronized private void setConnectionState(int state) { connectionState = state; } /* * Get the Access Control Context. * * Used for a known context to * run tasks in, and for determining which credentials * to use for Subject-based (JAAS) decisions. */ AccessControlContext getAcc() { return acc; } /* * Is a handshake currently underway? */ public SSLEngineResult.HandshakeStatus getHandshakeStatus() { return getHSStatus(null); } /* * When a connection finishes handshaking by enabling use of a newly * negotiated session, each end learns about it in two halves (read, * and write). When both read and write ciphers have changed, and the * last handshake message has been read, the connection has joined * (rejoined) the new session. * * NOTE: The SSLv3 spec is rather unclear on the concepts here. * Sessions don't change once they're established (including cipher * suite and master secret) but connections can join them (and leave * them). They're created by handshaking, though sometime handshaking * causes connections to join up with pre-established sessions. * * Synchronized on "this" from readRecord. */ private void changeReadCiphers() throws SSLException { if (connectionState != cs_HANDSHAKE && connectionState != cs_RENEGOTIATE) { throw new SSLProtocolException( "State error, change cipher specs"); } // ... create decompressor CipherBox oldCipher = readCipher; try { readCipher = handshaker.newReadCipher(); readMAC = handshaker.newReadMAC(); } catch (GeneralSecurityException e) { // "can't happen" throw (SSLException)new SSLException ("Algorithm missing: ").initCause(e); } /* * Dispose of any intermediate state in the underlying cipher. * For PKCS11 ciphers, this will release any attached sessions, * and thus make finalization faster. * * Since MAC's doFinal() is called for every SSL/TLS packet, it's * not necessary to do the same with MAC's. */ oldCipher.dispose(); } /* * used by Handshaker to change the active write cipher, follows * the output of the CCS message. * * Also synchronized on "this" from readRecord/delegatedTask. */ void changeWriteCiphers() throws SSLException { if (connectionState != cs_HANDSHAKE && connectionState != cs_RENEGOTIATE) { throw new SSLProtocolException( "State error, change cipher specs"); } // ... create compressor CipherBox oldCipher = writeCipher; try { writeCipher = handshaker.newWriteCipher(); writeMAC = handshaker.newWriteMAC(); } catch (GeneralSecurityException e) { // "can't happen" throw (SSLException)new SSLException ("Algorithm missing: ").initCause(e); } // See comment above. oldCipher.dispose(); } /* * Updates the SSL version associated with this connection. * Called from Handshaker once it has determined the negotiated version. */ synchronized void setVersion(ProtocolVersion protocolVersion) { this.protocolVersion = protocolVersion; outputRecord.setVersion(protocolVersion); } /** * Kickstart the handshake if it is not already in progress. * This means: * * . if handshaking is already underway, do nothing and return * * . if the engine is not connected or already closed, throw an * Exception. * * . otherwise, call initHandshake() to initialize the handshaker * object and progress the state. Then, send the initial * handshaking message if appropriate (always on clients and * on servers when renegotiating). */ private synchronized void kickstartHandshake() throws IOException { switch (connectionState) { case cs_START: if (!serverModeSet) { throw new IllegalStateException( "Client/Server mode not yet set."); } initHandshaker(); break; case cs_HANDSHAKE: // handshaker already setup, proceed break; case cs_DATA: if (!secureRenegotiation && !Handshaker.allowUnsafeRenegotiation) { throw new SSLHandshakeException( "Insecure renegotiation is not allowed"); } if (!secureRenegotiation) { if (debug != null && Debug.isOn("handshake")) { System.out.println( "Warning: Using insecure renegotiation"); } } // initialize the handshaker, move to cs_RENEGOTIATE initHandshaker(); break; case cs_RENEGOTIATE: // handshaking already in progress, return return; default: // cs_ERROR/cs_CLOSED throw new SSLException("SSLEngine is closing/closed"); } // // Kickstart handshake state machine if we need to ... // // Note that handshaker.kickstart() writes the message // to its HandshakeOutStream, which calls back into // SSLSocketImpl.writeRecord() to send it. // if (!handshaker.activated()) { // prior to handshaking, activate the handshake if (connectionState == cs_RENEGOTIATE) { // don't use SSLv2Hello when renegotiating handshaker.activate(protocolVersion); } else { handshaker.activate(null); } if (handshaker instanceof ClientHandshaker) { // send client hello handshaker.kickstart(); } else { // instanceof ServerHandshaker if (connectionState == cs_HANDSHAKE) { // initial handshake, no kickstart message to send } else { // we want to renegotiate, send hello request handshaker.kickstart(); // hello request is not included in the handshake // hashes, reset them handshaker.handshakeHash.reset(); } } } } /* * Start a SSLEngine handshake */ public void beginHandshake() throws SSLException { try { kickstartHandshake(); } catch (Exception e) { fatal(Alerts.alert_handshake_failure, "Couldn't kickstart handshaking", e); } } // // Read/unwrap side // /** * Unwraps a buffer. Does a variety of checks before grabbing * the unwrapLock, which blocks multiple unwraps from occuring. */ public SSLEngineResult unwrap(ByteBuffer netData, ByteBuffer [] appData, int offset, int length) throws SSLException { EngineArgs ea = new EngineArgs(netData, appData, offset, length); try { synchronized (unwrapLock) { return readNetRecord(ea); } } catch (Exception e) { /* * Don't reset position so it looks like we didn't * consume anything. We did consume something, and it * got us into this situation, so report that much back. * Our days of consuming are now over anyway. */ fatal(Alerts.alert_internal_error, "problem unwrapping net record", e); return null; // make compiler happy } finally { /* * Just in case something failed to reset limits properly. */ ea.resetLim(); } } /* * Makes additional checks for unwrap, but this time more * specific to this packet and the current state of the machine. */ private SSLEngineResult readNetRecord(EngineArgs ea) throws IOException { Status status = null; HandshakeStatus hsStatus = null; /* * See if the handshaker needs to report back some SSLException. */ checkTaskThrown(); /* * Check if we are closing/closed. */ if (isInboundDone()) { return new SSLEngineResult(Status.CLOSED, getHSStatus(null), 0, 0); } /* * If we're still in cs_HANDSHAKE, make sure it's been * started. */ synchronized (this) { if ((connectionState == cs_HANDSHAKE) || (connectionState == cs_START)) { kickstartHandshake(); /* * If there's still outbound data to flush, we * can return without trying to unwrap anything. */ hsStatus = getHSStatus(null); if (hsStatus == HandshakeStatus.NEED_WRAP) { return new SSLEngineResult(Status.OK, hsStatus, 0, 0); } } } /* * Grab a copy of this if it doesn't already exist, * and we can use it several places before anything major * happens on this side. Races aren't critical * here. */ if (hsStatus == null) { hsStatus = getHSStatus(null); } /* * If we have a task outstanding, this *MUST* be done before * doing any more unwrapping, because we could be in the middle * of receiving a handshake message, for example, a finished * message which would change the ciphers. */ if (hsStatus == HandshakeStatus.NEED_TASK) { return new SSLEngineResult( Status.OK, hsStatus, 0, 0); } /* * Check the packet to make sure enough is here. * This will also indirectly check for 0 len packets. */ int packetLen = inputRecord.bytesInCompletePacket(ea.netData); // Is this packet bigger than SSL/TLS normally allows? if (packetLen > sess.getPacketBufferSize()) { if (packetLen > Record.maxLargeRecordSize) { throw new SSLProtocolException( "Input SSL/TLS record too big: max = " + Record.maxLargeRecordSize + " len = " + packetLen); } else { // Expand the expected maximum packet/application buffer // sizes. sess.expandBufferSizes(); } } /* * Check for OVERFLOW. * * To be considered: We could delay enforcing the application buffer * free space requirement until after the initial handshaking. */ if ((packetLen - Record.headerSize) > ea.getAppRemaining()) { return new SSLEngineResult(Status.BUFFER_OVERFLOW, hsStatus, 0, 0); } // check for UNDERFLOW. if ((packetLen == -1) || (ea.netData.remaining() < packetLen)) { return new SSLEngineResult( Status.BUFFER_UNDERFLOW, hsStatus, 0, 0); } /* * We're now ready to actually do the read. * The only result code we really need to be exactly * right is the HS finished, for signaling to * HandshakeCompletedListeners. */ try { hsStatus = readRecord(ea); } catch (SSLException e) { throw e; } catch (IOException e) { SSLException ex = new SSLException("readRecord"); ex.initCause(e); throw ex; } /* * Check the various condition that we could be reporting. * * It's *possible* something might have happened between the * above and now, but it was better to minimally lock "this" * during the read process. We'll return the current * status, which is more representative of the current state. * * status above should cover: FINISHED, NEED_TASK */ status = (isInboundDone() ? Status.CLOSED : Status.OK); hsStatus = getHSStatus(hsStatus); return new SSLEngineResult(status, hsStatus, ea.deltaNet(), ea.deltaApp()); } /* * Actually do the read record processing. * * Returns a Status if it can make specific determinations * of the engine state. In particular, we need to signal * that a handshake just completed. * * It would be nice to be symmetrical with the write side and move * the majority of this to EngineInputRecord, but there's too much * SSLEngine state to do that cleanly. It must still live here. */ private HandshakeStatus readRecord(EngineArgs ea) throws IOException { HandshakeStatus hsStatus = null; /* * The various operations will return new sliced BB's, * this will avoid having to worry about positions and * limits in the netBB. */ ByteBuffer readBB = null; ByteBuffer decryptedBB = null; if (getConnectionState() != cs_ERROR) { /* * Read a record ... maybe emitting an alert if we get a * comprehensible but unsupported "hello" message during * format checking (e.g. V2). */ try { readBB = inputRecord.read(ea.netData); } catch (IOException e) { fatal(Alerts.alert_unexpected_message, e); } /* * The basic SSLv3 record protection involves (optional) * encryption for privacy, and an integrity check ensuring * data origin authentication. We do them both here, and * throw a fatal alert if the integrity check fails. */ try { decryptedBB = inputRecord.decrypt(readCipher, readBB); } catch (BadPaddingException e) { // RFC 2246 states that decryption_failed should be used // for this purpose. However, that allows certain attacks, // so we just send bad record MAC. We also need to make // sure to always check the MAC to avoid a timing attack // for the same issue. See paper by Vaudenay et al. // // rewind the BB if necessary. readBB.rewind(); inputRecord.checkMAC(readMAC, readBB); // use the same alert types as for MAC failure below byte alertType = (inputRecord.contentType() == Record.ct_handshake) ? Alerts.alert_handshake_failure : Alerts.alert_bad_record_mac; fatal(alertType, "Invalid padding", e); } if (!inputRecord.checkMAC(readMAC, decryptedBB)) { if (inputRecord.contentType() == Record.ct_handshake) { fatal(Alerts.alert_handshake_failure, "bad handshake record MAC"); } else { fatal(Alerts.alert_bad_record_mac, "bad record MAC"); } } // if (!inputRecord.decompress(c)) // fatal(Alerts.alert_decompression_failure, // "decompression failure"); /* * Process the record. */ synchronized (this) { switch (inputRecord.contentType()) { case Record.ct_handshake: /* * Handshake messages always go to a pending session * handshaker ... if there isn't one, create one. This * must work asynchronously, for renegotiation. * * NOTE that handshaking will either resume a session * which was in the cache (and which might have other * connections in it already), or else will start a new * session (new keys exchanged) with just this connection * in it. */ initHandshaker(); if (!handshaker.activated()) { // prior to handshaking, activate the handshakeLines 501–1000 of 2073. Previous lines Load more
return HandshakeStatus.NOT_HANDSHAKING; } } synchronized private void checkTaskThrown() throws SSLException { if (handshaker != null) { handshaker.checkThrown(); } } // // Handshaking and connection state code // /* * Provides "this" synchronization for connection state. * Otherwise, you can access it directly. */ synchronized private int getConnectionState() { return connectionState; } synchronized private void setConnectionState(int state) { connectionState = state; } /* * Get the Access Control Context. * * Used for a known context to * run tasks in, and for determining which credentials * to use for Subject-based (JAAS) decisions. */ AccessControlContext getAcc() { return acc; } /* * Is a handshake currently underway? */ public SSLEngineResult.HandshakeStatus getHandshakeStatus() { return getHSStatus(null); } /* * When a connection finishes handshaking by enabling use of a newly * negotiated session, each end learns about it in two halves (read, * and write). When both read and write ciphers have changed, and the * last handshake message has been read, the connection has joined * (rejoined) the new session. * * NOTE: The SSLv3 spec is rather unclear on the concepts here. * Sessions don't change once they're established (including cipher * suite and master secret) but connections can join them (and leave * them). They're created by handshaking, though sometime handshaking * causes connections to join up with pre-established sessions. * * Synchronized on "this" from readRecord. */ private void changeReadCiphers() throws SSLException { if (connectionState != cs_HANDSHAKE && connectionState != cs_RENEGOTIATE) { throw new SSLProtocolException( "State error, change cipher specs"); } // ... create decompressor CipherBox oldCipher = readCipher; try { readCipher = handshaker.newReadCipher(); readMAC = handshaker.newReadMAC(); } catch (GeneralSecurityException e) { // "can't happen" throw (SSLException)new SSLException ("Algorithm missing: ").initCause(e); } /* * Dispose of any intermediate state in the underlying cipher. * For PKCS11 ciphers, this will release any attached sessions, * and thus make finalization faster. * * Since MAC's doFinal() is called for every SSL/TLS packet, it's * not necessary to do the same with MAC's. */ oldCipher.dispose(); } /* * used by Handshaker to change the active write cipher, follows * the output of the CCS message. * * Also synchronized on "this" from readRecord/delegatedTask. */ void changeWriteCiphers() throws SSLException { if (connectionState != cs_HANDSHAKE && connectionState != cs_RENEGOTIATE) { throw new SSLProtocolException( "State error, change cipher specs"); } // ... create compressor CipherBox oldCipher = writeCipher; try { writeCipher = handshaker.newWriteCipher(); writeMAC = handshaker.newWriteMAC(); } catch (GeneralSecurityException e) { // "can't happen" throw (SSLException)new SSLException ("Algorithm missing: ").initCause(e); } // See comment above. oldCipher.dispose(); } /* * Updates the SSL version associated with this connection. * Called from Handshaker once it has determined the negotiated version. */ synchronized void setVersion(ProtocolVersion protocolVersion) { this.protocolVersion = protocolVersion; outputRecord.setVersion(protocolVersion); } /** * Kickstart the handshake if it is not already in progress. * This means: * * . if handshaking is already underway, do nothing and return * * . if the engine is not connected or already closed, throw an * Exception. * * . otherwise, call initHandshake() to initialize the handshaker * object and progress the state. Then, send the initial * handshaking message if appropriate (always on clients and * on servers when renegotiating). */ private synchronized void kickstartHandshake() throws IOException { switch (connectionState) { case cs_START: if (!serverModeSet) { throw new IllegalStateException( "Client/Server mode not yet set."); } initHandshaker(); break; case cs_HANDSHAKE: // handshaker already setup, proceed break; case cs_DATA: if (!secureRenegotiation && !Handshaker.allowUnsafeRenegotiation) { throw new SSLHandshakeException( "Insecure renegotiation is not allowed"); } if (!secureRenegotiation) { if (debug != null && Debug.isOn("handshake")) { System.out.println( "Warning: Using insecure renegotiation"); } } // initialize the handshaker, move to cs_RENEGOTIATE initHandshaker(); break; case cs_RENEGOTIATE: // handshaking already in progress, return return; default: // cs_ERROR/cs_CLOSED throw new SSLException("SSLEngine is closing/closed"); } // // Kickstart handshake state machine if we need to ... // // Note that handshaker.kickstart() writes the message // to its HandshakeOutStream, which calls back into // SSLSocketImpl.writeRecord() to send it. // if (!handshaker.activated()) { // prior to handshaking, activate the handshake if (connectionState == cs_RENEGOTIATE) { // don't use SSLv2Hello when renegotiating handshaker.activate(protocolVersion); } else { handshaker.activate(null); } if (handshaker instanceof ClientHandshaker) { // send client hello handshaker.kickstart(); } else { // instanceof ServerHandshaker if (connectionState == cs_HANDSHAKE) { // initial handshake, no kickstart message to send } else { // we want to renegotiate, send hello request handshaker.kickstart(); // hello request is not included in the handshake // hashes, reset them handshaker.handshakeHash.reset(); } } } } /* * Start a SSLEngine handshake */ public void beginHandshake() throws SSLException { try { kickstartHandshake(); } catch (Exception e) { fatal(Alerts.alert_handshake_failure, "Couldn't kickstart handshaking", e); } } // // Read/unwrap side // /** * Unwraps a buffer. Does a variety of checks before grabbing * the unwrapLock, which blocks multiple unwraps from occuring. */ public SSLEngineResult unwrap(ByteBuffer netData, ByteBuffer [] appData, int offset, int length) throws SSLException { EngineArgs ea = new EngineArgs(netData, appData, offset, length); try { synchronized (unwrapLock) { return readNetRecord(ea); } } catch (Exception e) { /* * Don't reset position so it looks like we didn't * consume anything. We did consume something, and it * got us into this situation, so report that much back. * Our days of consuming are now over anyway. */ fatal(Alerts.alert_internal_error, "problem unwrapping net record", e); return null; // make compiler happy } finally { /* * Just in case something failed to reset limits properly. */ ea.resetLim(); } } /* * Makes additional checks for unwrap, but this time more * specific to this packet and the current state of the machine. */ private SSLEngineResult readNetRecord(EngineArgs ea) throws IOException { Status status = null; HandshakeStatus hsStatus = null; /* * See if the handshaker needs to report back some SSLException. */ checkTaskThrown(); /* * Check if we are closing/closed. */ if (isInboundDone()) { return new SSLEngineResult(Status.CLOSED, getHSStatus(null), 0, 0); } /* * If we're still in cs_HANDSHAKE, make sure it's been * started. */ synchronized (this) { if ((connectionState == cs_HANDSHAKE) || (connectionState == cs_START)) { kickstartHandshake(); /* * If there's still outbound data to flush, we * can return without trying to unwrap anything. */ hsStatus = getHSStatus(null); if (hsStatus == HandshakeStatus.NEED_WRAP) { return new SSLEngineResult(Status.OK, hsStatus, 0, 0); } } } /* * Grab a copy of this if it doesn't already exist, * and we can use it several places before anything major * happens on this side. Races aren't critical * here. */ if (hsStatus == null) { hsStatus = getHSStatus(null); } /* * If we have a task outstanding, this *MUST* be done before * doing any more unwrapping, because we could be in the middle * of receiving a handshake message, for example, a finished * message which would change the ciphers. */ if (hsStatus == HandshakeStatus.NEED_TASK) { return new SSLEngineResult( Status.OK, hsStatus, 0, 0); } /* * Check the packet to make sure enough is here. * This will also indirectly check for 0 len packets. */ int packetLen = inputRecord.bytesInCompletePacket(ea.netData); // Is this packet bigger than SSL/TLS normally allows? if (packetLen > sess.getPacketBufferSize()) { if (packetLen > Record.maxLargeRecordSize) { throw new SSLProtocolException( "Input SSL/TLS record too big: max = " + Record.maxLargeRecordSize + " len = " + packetLen); } else { // Expand the expected maximum packet/application buffer // sizes. sess.expandBufferSizes(); } } /* * Check for OVERFLOW. * * To be considered: We could delay enforcing the application buffer * free space requirement until after the initial handshaking. */ if ((packetLen - Record.headerSize) > ea.getAppRemaining()) { return new SSLEngineResult(Status.BUFFER_OVERFLOW, hsStatus, 0, 0); } // check for UNDERFLOW. if ((packetLen == -1) || (ea.netData.remaining() < packetLen)) { return new SSLEngineResult( Status.BUFFER_UNDERFLOW, hsStatus, 0, 0); } /* * We're now ready to actually do the read. * The only result code we really need to be exactly * right is the HS finished, for signaling to * HandshakeCompletedListeners. */ try { hsStatus = readRecord(ea); } catch (SSLException e) { throw e; } catch (IOException e) { SSLException ex = new SSLException("readRecord"); ex.initCause(e); throw ex; } /* * Check the various condition that we could be reporting. * * It's *possible* something might have happened between the * above and now, but it was better to minimally lock "this" * during the read process. We'll return the current * status, which is more representative of the current state. * * status above should cover: FINISHED, NEED_TASK */ status = (isInboundDone() ? Status.CLOSED : Status.OK); hsStatus = getHSStatus(hsStatus); return new SSLEngineResult(status, hsStatus, ea.deltaNet(), ea.deltaApp()); } /* * Actually do the read record processing. * * Returns a Status if it can make specific determinations * of the engine state. In particular, we need to signal * that a handshake just completed. * * It would be nice to be symmetrical with the write side and move * the majority of this to EngineInputRecord, but there's too much * SSLEngine state to do that cleanly. It must still live here. */ private HandshakeStatus readRecord(EngineArgs ea) throws IOException { HandshakeStatus hsStatus = null; /* * The various operations will return new sliced BB's, * this will avoid having to worry about positions and * limits in the netBB. */ ByteBuffer readBB = null; ByteBuffer decryptedBB = null; if (getConnectionState() != cs_ERROR) { /* * Read a record ... maybe emitting an alert if we get a * comprehensible but unsupported "hello" message during * format checking (e.g. V2). */ try { readBB = inputRecord.read(ea.netData); } catch (IOException e) { fatal(Alerts.alert_unexpected_message, e); } /* * The basic SSLv3 record protection involves (optional) * encryption for privacy, and an integrity check ensuring * data origin authentication. We do them both here, and * throw a fatal alert if the integrity check fails. */ try { decryptedBB = inputRecord.decrypt(readCipher, readBB); } catch (BadPaddingException e) { // RFC 2246 states that decryption_failed should be used // for this purpose. However, that allows certain attacks, // so we just send bad record MAC. We also need to make // sure to always check the MAC to avoid a timing attack // for the same issue. See paper by Vaudenay et al. // // rewind the BB if necessary. readBB.rewind(); inputRecord.checkMAC(readMAC, readBB); // use the same alert types as for MAC failure below byte alertType = (inputRecord.contentType() == Record.ct_handshake) ? Alerts.alert_handshake_failure : Alerts.alert_bad_record_mac; fatal(alertType, "Invalid padding", e); } if (!inputRecord.checkMAC(readMAC, decryptedBB)) { if (inputRecord.contentType() == Record.ct_handshake) { fatal(Alerts.alert_handshake_failure, "bad handshake record MAC"); } else { fatal(Alerts.alert_bad_record_mac, "bad record MAC"); } } // if (!inputRecord.decompress(c)) // fatal(Alerts.alert_decompression_failure, // "decompression failure"); /* * Process the record. */ synchronized (this) { switch (inputRecord.contentType()) { case Record.ct_handshake: /* * Handshake messages always go to a pending session * handshaker ... if there isn't one, create one. This * must work asynchronously, for renegotiation. * * NOTE that handshaking will either resume a session * which was in the cache (and which might have other * connections in it already), or else will start a new * session (new keys exchanged) with just this connection * in it. */ initHandshaker(); if (!handshaker.activated()) { // prior to handshaking, activate the handshake