/* * Sends a change cipher spec message and updates the write side * cipher state so that future messages use the just-negotiated spec. */ void sendChangeCipherSpec(Finished mesg, boolean lastMessage) throws IOException { output.flush(); // i.e. handshake data /* * The write cipher state is protected by the connection write lock * so we must grab it while making the change. We also * make sure no writes occur between sending the ChangeCipherSpec * message, installing the new cipher state, and sending the * Finished message. * * We already hold SSLEngine/SSLSocket "this" by virtue * of this being called from the readRecord code. */ OutputRecord r; if (conn != null) { r = new OutputRecord(Record.ct_change_cipher_spec); } else { r = new EngineOutputRecord(Record.ct_change_cipher_spec, engine); } r.setVersion(protocolVersion); r.write(1); // single byte of data if (conn != null) { conn.writeLock.lock(); try { conn.writeRecord(r); conn.changeWriteCiphers(); if (debug != null && Debug.isOn("handshake")) { mesg.print(System.out); } mesg.write(output); output.flush(); } finally { conn.writeLock.unlock(); } } else { synchronized (engine.writeLock) { engine.writeRecord((EngineOutputRecord) r); engine.changeWriteCiphers(); if (debug != null && Debug.isOn("handshake")) { mesg.print(System.out); } mesg.write(output); if (lastMessage) { output.setFinishedMsg(); } output.flush(); } } }
/** * Called when the client received the server's finished message. If the data can be verified, * encrypted application data can be sent. * * @param message the {@link Finished} message. * @return the list * @throws HandshakeException */ private DTLSFlight receivedServerFinished(Finished message) throws HandshakeException { DTLSFlight flight = new DTLSFlight(); message.verifyData(getMasterSecret(), false, handshakeHash); state = HandshakeType.FINISHED.getCode(); session.setActive(true); // received server's Finished message, now able to send encrypted // message ApplicationMessage applicationMessage = new ApplicationMessage(this.message.getBytes()); flight.addMessage(wrapMessage(applicationMessage)); // application data is not retransmitted flight.setRetransmissionNeeded(false); return flight; }
/** * The ServerHelloDone message is sent by the server to indicate the end of the ServerHello and * associated messages. The client prepares all necessary messages (depending on server's previous * flight) and returns the next flight. * * @return the client's next flight to be sent. * @throws HandshakeException */ private DTLSFlight receivedServerHelloDone(ServerHelloDone message) throws HandshakeException { DTLSFlight flight = new DTLSFlight(); if (serverHelloDone != null && (serverHelloDone.getMessageSeq() == message.getMessageSeq())) { // discard duplicate message return flight; } serverHelloDone = message; /* * All possible handshake messages sent in this flight. Used to compute * handshake hash. */ CertificateMessage clientCertificate = null; ClientKeyExchange clientKeyExchange = null; CertificateVerify certificateVerify = null; /* * First, if required by server, send Certificate. */ if (certificateRequest != null) { // TODO load the client's certificate according to the allowed // parameters in the CertificateRequest clientCertificate = new CertificateMessage(certificates, session.sendRawPublicKey()); flight.addMessage(wrapMessage(clientCertificate)); } /* * Second, send ClientKeyExchange as specified by the key exchange * algorithm. */ byte[] premasterSecret; switch (keyExchange) { case EC_DIFFIE_HELLMAN: clientKeyExchange = new ECDHClientKeyExchange(ecdhe.getPublicKey()); premasterSecret = ecdhe.getSecret(ephemeralServerPublicKey).getEncoded(); generateKeys(premasterSecret); break; case PSK: String identity = ScProperties.std.getProperty("PSK_IDENTITY"); clientKeyExchange = new PSKClientKeyExchange(identity); byte[] psk = sharedKeys.get(identity); if (psk == null) { AlertMessage alert = new AlertMessage(AlertLevel.FATAL, AlertDescription.HANDSHAKE_FAILURE); throw new HandshakeException( "No preshared secret found for identity: " + identity, alert); } LOGGER.info("Using PSK identity: " + identity); premasterSecret = generatePremasterSecretFromPSK(psk); generateKeys(premasterSecret); break; case NULL: clientKeyExchange = new NULLClientKeyExchange(); // We assume, that the premaster secret is empty generateKeys(new byte[] {}); break; default: AlertMessage alert = new AlertMessage(AlertLevel.FATAL, AlertDescription.HANDSHAKE_FAILURE); throw new HandshakeException("Unknown key exchange algorithm: " + keyExchange, alert); } flight.addMessage(wrapMessage(clientKeyExchange)); /* * Third, send CertificateVerify message if necessary. */ if (certificateRequest != null) { // prepare handshake messages handshakeMessages = ByteArrayUtils.concatenate(handshakeMessages, clientHello.toByteArray()); handshakeMessages = ByteArrayUtils.concatenate(handshakeMessages, serverHello.toByteArray()); handshakeMessages = ByteArrayUtils.concatenate(handshakeMessages, serverCertificate.toByteArray()); handshakeMessages = ByteArrayUtils.concatenate(handshakeMessages, serverKeyExchange.toByteArray()); handshakeMessages = ByteArrayUtils.concatenate(handshakeMessages, certificateRequest.toByteArray()); handshakeMessages = ByteArrayUtils.concatenate(handshakeMessages, serverHelloDone.toByteArray()); handshakeMessages = ByteArrayUtils.concatenate(handshakeMessages, clientCertificate.toByteArray()); handshakeMessages = ByteArrayUtils.concatenate(handshakeMessages, clientKeyExchange.toByteArray()); // TODO make sure, that signature is supported SignatureAndHashAlgorithm signatureAndHashAlgorithm = certificateRequest.getSupportedSignatureAlgorithms().get(0); certificateVerify = new CertificateVerify(signatureAndHashAlgorithm, privateKey, handshakeMessages); flight.addMessage(wrapMessage(certificateVerify)); } /* * Fourth, send ChangeCipherSpec */ ChangeCipherSpecMessage changeCipherSpecMessage = new ChangeCipherSpecMessage(); flight.addMessage(wrapMessage(changeCipherSpecMessage)); setCurrentWriteState(); session.incrementWriteEpoch(); /* * Fifth, send the finished message. */ try { // create hash of handshake messages // can't do this on the fly, since there is no explicit ordering of // messages MessageDigest md = MessageDigest.getInstance("SHA-256"); md.update(clientHello.toByteArray()); md.update(serverHello.toByteArray()); if (serverCertificate != null) { md.update(serverCertificate.toByteArray()); } if (serverKeyExchange != null) { md.update(serverKeyExchange.toByteArray()); } if (certificateRequest != null) { md.update(certificateRequest.toByteArray()); } md.update(serverHelloDone.toByteArray()); if (clientCertificate != null) { md.update(clientCertificate.toByteArray()); } md.update(clientKeyExchange.toByteArray()); if (certificateVerify != null) { md.update(certificateVerify.toByteArray()); } MessageDigest mdWithClientFinished = null; try { mdWithClientFinished = (MessageDigest) md.clone(); } catch (CloneNotSupportedException e) { LOGGER.severe("Clone not supported."); e.printStackTrace(); } handshakeHash = md.digest(); Finished finished = new Finished(getMasterSecret(), isClient, handshakeHash); flight.addMessage(wrapMessage(finished)); // compute handshake hash with client's finished message also // included, used for server's finished message mdWithClientFinished.update(finished.toByteArray()); handshakeHash = mdWithClientFinished.digest(); } catch (NoSuchAlgorithmException e) { LOGGER.severe("No such Message Digest Algorithm available."); e.printStackTrace(); } return flight; }