Skip to content

Commit

Permalink
Fix sending HelloVerifyRequest, if a fallback to a full-handshake is
Browse files Browse the repository at this point in the history
required.

Signed-off-by: Achim Kraus <achim.kraus@cloudcoap.net>
  • Loading branch information
boaks committed Jul 7, 2022
1 parent b2362d5 commit 0cc953a
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 75 deletions.
Expand Up @@ -232,6 +232,7 @@
import org.eclipse.californium.scandium.dtls.cipher.InvalidMacException;
import org.eclipse.californium.scandium.dtls.pskstore.AdvancedPskStore;
import org.eclipse.californium.scandium.dtls.resumption.ConnectionStoreResumptionVerifier;
import org.eclipse.californium.scandium.dtls.resumption.ExtendedResumptionVerifier;
import org.eclipse.californium.scandium.dtls.resumption.ResumptionVerifier;
import org.eclipse.californium.scandium.dtls.x509.CertificateProvider;
import org.eclipse.californium.scandium.dtls.x509.NewAdvancedCertificateVerifier;
Expand Down Expand Up @@ -369,6 +370,19 @@ public class DTLSConnector implements Connector, PersistentConnector, Persistent
* @since 3.0
*/
private final boolean useHelloVerifyRequest;
/**
* Support Server Name Indication TLS extension.
*
* @since 3.6
*/
protected final boolean sniEnabled;

/**
* Send the extended master secret extension.
*
* @since 3.6
*/
protected final ExtendedMasterSecretMode extendedMasterSecretMode;

private final int thresholdHandshakesWithoutVerifiedPeer;
/**
Expand Down Expand Up @@ -608,6 +622,8 @@ protected DTLSConnector(final DtlsConnectorConfig configuration,
this.useFilter = config.get(DtlsConfig.DTLS_USE_ANTI_REPLAY_FILTER);
this.useCidUpdateAddressOnNewerRecordFilter = config.get(DtlsConfig.DTLS_UPDATE_ADDRESS_USING_CID_ON_NEWER_RECORDS);
this.maxConnections = config.get(DtlsConfig.DTLS_MAX_CONNECTIONS);
this.sniEnabled = config.get(DtlsConfig.DTLS_USE_SERVER_NAME_INDICATION);
this.extendedMasterSecretMode = config.get(DtlsConfig.DTLS_EXTENDED_MASTER_SECRET_MODE);
this.datagramFilter = config.getDatagramFilter();
this.connectionListener = config.getConnectionListener();
this.customSessionListener = config.getSessionListener();
Expand Down Expand Up @@ -2638,7 +2654,12 @@ private boolean isClientInControlOfSourceIpAddress(InetSocketAddress peer, Clien
LOGGER.trace("pending fast resumptions [{}], threshold [{}]", pending,
thresholdHandshakesWithoutVerifiedPeer);
if (pending < thresholdHandshakesWithoutVerifiedPeer) {
return resumptionVerifier.skipRequestHelloVerify(clientHello.getSessionId());
if (resumptionVerifier instanceof ExtendedResumptionVerifier) {
return ((ExtendedResumptionVerifier) resumptionVerifier).skipRequestHelloVerify(clientHello,
sniEnabled, extendedMasterSecretMode);
} else {
return resumptionVerifier.skipRequestHelloVerify(clientHello.getSessionId());
}
}
}
}
Expand Down
Expand Up @@ -42,6 +42,8 @@
import org.eclipse.californium.scandium.dtls.resumption.ResumptionVerifier;
import org.eclipse.californium.scandium.util.SecretUtil;
import org.eclipse.californium.scandium.util.ServerNames;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* The resuming server handshaker executes an abbreviated handshake when
Expand Down Expand Up @@ -82,6 +84,8 @@
*/
@NoPublicAPI
public class ResumingServerHandshaker extends ServerHandshaker {
private static final Logger LOGGER = LoggerFactory.getLogger(ResumingServerHandshaker.class);

private static final HandshakeState[] ABBREVIATED_HANDSHAKE = {
new HandshakeState(ContentType.CHANGE_CIPHER_SPEC),
new HandshakeState(HandshakeType.FINISHED) };
Expand Down Expand Up @@ -244,7 +248,7 @@ private void processResumptionVerificationResult(ResumptionVerificationResult re
ClientHello clientHello = pendingClientHello;
pendingClientHello = null;
DTLSSession session = resumptionResult.getDTLSSession();
fullHandshake = !validateResumption(session, clientHello);
fullHandshake = !validateResumption(session, clientHello, sniEnabled, extendedMasterSecretMode);
if (fullHandshake) {
LOGGER.debug("DTLS session {} not available, switch to full-handshake with peer [{}]!",
clientHello.getSessionId(), peerToLog);
Expand All @@ -259,35 +263,86 @@ private void processResumptionVerificationResult(ResumptionVerificationResult re
}

/**
* Checks, if the session and client hello valid for an resumption
* Process client hello resuming the available and valid session.
*
* The server generates new keys from the old master secret and sends
* ChangeCipherSpec and Finished message. The ClientHello contains a fresh
* random value which will be needed to generate the new keys.
*
* @param clientHello the client's hello message.
* @throws HandshakeException if the server's handshake records cannot be
* created
* @since 3.0
*/
private void processResumingClientHello(ClientHello clientHello) throws HandshakeException {
DTLSSession session = getSession();
CipherSuite cipherSuite = session.getCipherSuite();

LOGGER.debug("Start resumption-handshake with peer [{}].", peerToLog);
clientRandom = clientHello.getRandom();

flightNumber += 2;
DTLSFlight flight = createFlight();

ServerHello serverHello = new ServerHello(clientHello.getProtocolVersion(),
session.getSessionIdentifier(), cipherSuite, session.getCompressionMethod());
addHelloExtensions(clientHello, serverHello);
wrapMessage(flight, serverHello);
serverRandom = serverHello.getRandom();

ChangeCipherSpecMessage changeCipherSpecMessage = new ChangeCipherSpecMessage();
wrapMessage(flight, changeCipherSpecMessage);

MessageDigest md = getHandshakeMessageDigest();

MessageDigest mdWithServerFinished = cloneMessageDigest(md);

resumeMasterSecret();

setCurrentWriteState();

Finished finished = createFinishedMessage(md.digest());
wrapMessage(flight, finished);

mdWithServerFinished.update(finished.toByteArray());
handshakeHash = mdWithServerFinished.digest();
sendFlight(flight);
setExpectedStates(ABBREVIATED_HANDSHAKE);
expectChangeCipherSpecMessage();
}

/**
* Checks, if the session and client hello are valid for an resumption
* handshake.
*
* @param session the DTLS session to resume.
* @param clientHello the client's hello message.
* @param sniEnabled {@code true}, if SNI is enabled, {@code false},
* otherwise.
* @param extendedMasterSecretMode the extended master secret mode.
* @return {@code true}, the session and client hello are valid for
* resumption, {@code false}, if not and a fall back to a
* full-handshake is required.
* @since 3.0
* @since 3.6
*/
private boolean validateResumption(DTLSSession session, ClientHello clientHello) {
public static boolean validateResumption(DTLSSession session, ClientHello clientHello, boolean sniEnabled,
ExtendedMasterSecretMode extendedMasterSecretMode) {
if (session == null) {
LOGGER.debug("DTLS session {} not available, switch to full-handshake with peer [{}]!",
clientHello.getSessionId(), peerToLog);
LOGGER.debug("DTLS session {} not available, switch to full-handshake!", clientHello.getSessionId());
return false;
}
CipherSuite cipherSuite = session.getCipherSuite();
CompressionMethod compressionMethod = session.getCompressionMethod();
if (!clientHello.getCipherSuites().contains(cipherSuite)) {
LOGGER.debug("Cipher-suite {} changed by client hello, switch to full-handshake with peer [{}]!",
cipherSuite, peerToLog);
LOGGER.debug("Cipher-suite {} changed by client hello, switch to full-handshake!", cipherSuite);
return false;
} else if (!session.getProtocolVersion().equals(clientHello.getProtocolVersion())) {
LOGGER.debug("Protocol version {} changed by client hello {}, switch to full-handshake with peer [{}]!",
session.getProtocolVersion(), clientHello.getProtocolVersion(), peerToLog);
LOGGER.debug("Protocol version {} changed by client hello {}, switch to full-handshake!",
session.getProtocolVersion(), clientHello.getProtocolVersion());
return false;
} else if (!clientHello.getCompressionMethods().contains(compressionMethod)) {
LOGGER.debug("Compression method {} changed by client hello, switch to full-handshake with peer [{}]!",
session.getCompressionMethod(), peerToLog);
LOGGER.debug("Compression method {} changed by client hello, switch to full-handshake!",
session.getCompressionMethod());
return false;
} else if (extendedMasterSecretMode.is(ExtendedMasterSecretMode.ENABLED)
&& !clientHello.hasExtendedMasterSecretExtension()) {
Expand All @@ -305,9 +360,7 @@ private boolean validateResumption(DTLSSession session, ClientHello clientHello)
// insecure resumption, the connection is no longer
// protected by the mechanisms in this document, and the
// server should follow the guidelines in Section 5.4.
LOGGER.debug(
"Missing extended master secret extension in client hello, switch to full-handshake with peer [{}]!",
peerToLog);
LOGGER.debug("Missing extended master secret extension in client hello, switch to full-handshake!");
return false;
} else if (extendedMasterSecretMode == ExtendedMasterSecretMode.OPTIONAL && session.useExtendedMasterSecret()
&& !clientHello.hasExtendedMasterSecretExtension()) {
Expand All @@ -317,69 +370,17 @@ private boolean validateResumption(DTLSSession session, ClientHello clientHello)
// "extended_master_secret" extension but the new
// ClientHello does not contain it, the server
// MUST abort the abbreviated handshake
LOGGER.debug(
"Disabled extended master secret extension in client hello, switch to full-handshake with peer [{}]!",
peerToLog);
LOGGER.debug("Disabled extended master secret extension in client hello, switch to full-handshake!");
return false;
} else if (sniEnabled) {
ServerNames serverNames = getServerNames();
ServerNames serverNames = session.getServerNames();
ServerNames clientServerNames = clientHello.getServerNames();
if (!Objects.equals(serverNames, clientServerNames)) {
LOGGER.debug("SNI {} changed by client hello {}, switch to full-handshake with peer [{}]!", serverNames,
clientServerNames, peerToLog);
LOGGER.debug("SNI {} changed by client hello {}, switch to full-handshake!", serverNames,
clientServerNames);
return false;
}
}
return true;
}

/**
* Process client hello resuming the available and valid session.
*
* The server generates new keys from the old master secret and sends
* ChangeCipherSpec and Finished message. The ClientHello contains a fresh
* random value which will be needed to generate the new keys.
*
* @param clientHello the client's hello message.
* @throws HandshakeException if the server's handshake records cannot be
* created
* @since 3.0
*/
private void processResumingClientHello(ClientHello clientHello) throws HandshakeException {
DTLSSession session = getSession();
CipherSuite cipherSuite = session.getCipherSuite();

LOGGER.debug("Start resumption-handshake with peer [{}].", peerToLog);
clientRandom = clientHello.getRandom();

flightNumber += 2;
DTLSFlight flight = createFlight();

ServerHello serverHello = new ServerHello(clientHello.getProtocolVersion(),
session.getSessionIdentifier(), cipherSuite, session.getCompressionMethod());
addHelloExtensions(clientHello, serverHello);
wrapMessage(flight, serverHello);
serverRandom = serverHello.getRandom();

ChangeCipherSpecMessage changeCipherSpecMessage = new ChangeCipherSpecMessage();
wrapMessage(flight, changeCipherSpecMessage);

MessageDigest md = getHandshakeMessageDigest();

MessageDigest mdWithServerFinished = cloneMessageDigest(md);

resumeMasterSecret();

setCurrentWriteState();

Finished finished = createFinishedMessage(md.digest());
wrapMessage(flight, finished);

mdWithServerFinished.update(finished.toByteArray());
handshakeHash = mdWithServerFinished.digest();
sendFlight(flight);
setExpectedStates(ABBREVIATED_HANDSHAKE);
expectChangeCipherSpecMessage();
}

}
Expand Up @@ -16,11 +16,14 @@
package org.eclipse.californium.scandium.dtls.resumption;

import org.eclipse.californium.scandium.DTLSConnector;
import org.eclipse.californium.scandium.dtls.ClientHello;
import org.eclipse.californium.scandium.dtls.ConnectionId;
import org.eclipse.californium.scandium.dtls.DTLSSession;
import org.eclipse.californium.scandium.dtls.ExtendedMasterSecretMode;
import org.eclipse.californium.scandium.dtls.HandshakeResultHandler;
import org.eclipse.californium.scandium.dtls.ResumptionVerificationResult;
import org.eclipse.californium.scandium.dtls.ResumingServerHandshaker;
import org.eclipse.californium.scandium.dtls.ResumptionSupportingConnectionStore;
import org.eclipse.californium.scandium.dtls.ResumptionVerificationResult;
import org.eclipse.californium.scandium.dtls.SessionId;
import org.eclipse.californium.scandium.util.SecretUtil;
import org.eclipse.californium.scandium.util.ServerNames;
Expand All @@ -36,7 +39,7 @@
*
* @since 3.0
*/
public class ConnectionStoreResumptionVerifier implements ResumptionVerifier {
public class ConnectionStoreResumptionVerifier implements ExtendedResumptionVerifier {

/**
* Connection store to lookup the dtls session.
Expand Down Expand Up @@ -95,6 +98,22 @@ public boolean skipRequestHelloVerify(SessionId sessionId) {
return result;
}

@Override
public boolean skipRequestHelloVerify(ClientHello clientHello, boolean sniEnabled,
ExtendedMasterSecretMode extendedMasterSecretMode) {
boolean result = false;
ResumptionSupportingConnectionStore store = connectionStore;
if (store != null) {
DTLSSession session = store.find(clientHello.getSessionId());
if (session != null) {
result = ResumingServerHandshaker.validateResumption(session, clientHello, sniEnabled,
extendedMasterSecretMode);
SecretUtil.destroy(session);
}
}
return result;
}

@Override
public ResumptionVerificationResult verifyResumptionRequest(final ConnectionId cid, final ServerNames serverName,
final SessionId sessionId) {
Expand All @@ -106,5 +125,4 @@ public ResumptionVerificationResult verifyResumptionRequest(final ConnectionId c
public void setResultHandler(HandshakeResultHandler resultHandler) {
// empty implementation
}

}
@@ -0,0 +1,47 @@
/*******************************************************************************
* Copyright (c) 2022 Achim Kraus and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v20.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.html.
*
* Contributors:
* Achim Kraus - initial creation
******************************************************************************/
package org.eclipse.californium.scandium.dtls.resumption;

import org.eclipse.californium.scandium.dtls.ClientHello;
import org.eclipse.californium.scandium.dtls.ExtendedMasterSecretMode;

/**
* Extended Resumption verifier.
*
* An extended resumption verifier checks additionally, if no fallback to a full
* handshake is required.
*
* @since 3.6
*/
public interface ExtendedResumptionVerifier extends ResumptionVerifier {

/**
* Checks, if the session id is matching and no fallback to a full handshake
* is required. If so, the client hello may bypass the cookie validation
* without using a hello verify request.
*
* Note: this function must return immediately.
*
* @param clientHello client hello message
* @param sniEnabled {@code true}, if SNI is enabled, {@code false},
* otherwise.
* @param extendedMasterSecretMode the extended master secret mode.
* @return {@code true}, if valid and no hello verify request is required,
* {@code false}, otherwise.
*/
boolean skipRequestHelloVerify(ClientHello clientHello, boolean sniEnabled,
ExtendedMasterSecretMode extendedMasterSecretMode);
}

0 comments on commit 0cc953a

Please sign in to comment.