Skip to content

Commit

Permalink
Use full-handshake for resumption with empty session id.
Browse files Browse the repository at this point in the history
Add no server session id to configuration.
Use creation time for endpoint context, if session id is empty.

Signed-off-by: Achim Kraus <[email protected]>
  • Loading branch information
Achim Kraus committed Jan 10, 2019
1 parent ed2951a commit a90366c
Show file tree
Hide file tree
Showing 9 changed files with 158 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,9 @@ private static final Map<String, String> createMap(String... attributes) {
String value = attributes[++index];
if (null == key) {
throw new NullPointerException((index / 2) + ". key is null");
}
if (null == value) {
} else if (key.isEmpty()) {
throw new IllegalArgumentException((index / 2) + ". key is empty");
} else if (null == value) {
throw new NullPointerException((index / 2) + ". value is null");
}
String old = entries.put(key, value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1658,15 +1658,26 @@ private void sendMessage(final RawData message) throws HandshakeException {
} else {
sessionId = connection.getSessionIdentity();
}
DTLSSession resumableSession = new DTLSSession(sessionId, peerAddress, ticket, 0);
resumableSession.setVirtualHost(message.getEndpointContext().getVirtualHost());
// terminate the previous connection and add the new one to the store
Connection newConnection = new Connection(peerAddress);
connection.cancelPendingFlight();
connectionStore.remove(connection, false);
connectionStore.put(newConnection);
Handshaker handshaker = new ResumingClientHandshaker(resumableSession, this,
newConnection.getSessionListener(), config, maximumTransmissionUnit);
Handshaker handshaker;
if (sessionId.isEmpty()) {
// server may use a empty session id to indicate,
// that resumption is not supported
// https://tools.ietf.org/html/rfc5246#section-7.4.1.3
DTLSSession newSession = new DTLSSession(peerAddress);
newSession.setVirtualHost(message.getEndpointContext().getVirtualHost());
handshaker = new ClientHandshaker(newSession, this, newConnection.getSessionListener(), config,
maximumTransmissionUnit);
} else {
DTLSSession resumableSession = new DTLSSession(sessionId, peerAddress, ticket, 0);
resumableSession.setVirtualHost(message.getEndpointContext().getVirtualHost());
handshaker = new ResumingClientHandshaker(resumableSession, this,
newConnection.getSessionListener(), config, maximumTransmissionUnit);
}
initializeHandshaker(handshaker);
Handshaker previous = connection.getOngoingHandshake();
if (previous != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,12 @@ public final class DtlsConnectorConfig {
*/
private Integer verifyPeersOnResumptionThreshold;

/**
* Indicates, that no session id is used by this server. The sessions are not
* cached by this server and can not be resumed.
*/
private Boolean useNoServerSessionId;

private DtlsConnectorConfig() {
// empty
}
Expand Down Expand Up @@ -583,6 +589,16 @@ public Long getAutoResumptionTimeoutMillis() {
return autoResumptionTimeoutMillis;
}

/**
* Indicates, that no session id is used by this server and so session are
* also not cached by this server and can not be resumed.
*
* @return {@code true} if no session id is used by this server.
*/
public Boolean useNoServerSessionId() {
return useNoServerSessionId;
}

/**
* @return The trust store for raw public keys verified out-of-band for
* DTLS-RPK handshakes
Expand Down Expand Up @@ -626,6 +642,7 @@ protected Object clone() {
cloned.autoResumptionTimeoutMillis = autoResumptionTimeoutMillis;
cloned.sniEnabled = sniEnabled;
cloned.verifyPeersOnResumptionThreshold = verifyPeersOnResumptionThreshold;
cloned.useNoServerSessionId = useNoServerSessionId;
return cloned;
}

Expand Down Expand Up @@ -728,9 +745,10 @@ public Builder setEnableAddressReuse(boolean enable) {
public Builder setClientOnly() {
if (config.clientAuthenticationRequired != null || config.clientAuthenticationWanted != null) {
throw new IllegalStateException("client only is not support with server side client authentication!");
}
if (config.serverOnly != null) {
} else if (config.serverOnly != null) {
throw new IllegalStateException("client only is not support with server only!");
} else if (config.useNoServerSessionId != null && config.useNoServerSessionId.booleanValue()) {
throw new IllegalStateException("client only is not support with no server session id!");
}
clientOnly = true;
return this;
Expand Down Expand Up @@ -1438,7 +1456,7 @@ public Builder setSniEnabled(boolean flag) {
* is based on
* {@link DtlsConnectorConfig#DEFAULT_VERIFY_PEERS_ON_RESUMPTION_THRESHOLD_IN_PERCENT}
* @return this builder for command chaining.
* @throws IllegalArgumentException if threshold is not between 0 and 1000
* @throws IllegalArgumentException if threshold is not between 0 and 100
* @see DtlsConnectorConfig#verifyPeersOnResumptionThreshold
*/
public Builder setVerifyPeersOnResumptionThreshold(int threshold) {
Expand All @@ -1449,6 +1467,22 @@ public Builder setVerifyPeersOnResumptionThreshold(int threshold) {
return this;
}

/**
* Set whether session id is used by this server or not.
*
* @param flag {@code true} if no session id is used by this server.
* @return this builder for command chaining.
* @throws IllegalArgumentException if no session id should be used and
* the configuration is for client only.
*/
public Builder setNoServerSessionId(boolean flag) {
if (clientOnly && flag) {
throw new IllegalArgumentException("not applicable for client only!");
}
config.useNoServerSessionId = flag;
return this;
}

private boolean isConfiguredWithKeyPair() {
return config.privateKey != null && config.publicKey != null;
}
Expand Down Expand Up @@ -1511,6 +1545,9 @@ public DtlsConnectorConfig build() {
if (config.serverOnly == null) {
config.serverOnly = false;
}
if (config.useNoServerSessionId == null) {
config.useNoServerSessionId = false;
}
if (config.outboundMessageBufferSize == null) {
config.outboundMessageBufferSize = 100000;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,25 @@ void setSessionIdentifier(SessionId sessionIdentifier) {
}
}

/**
* System time of session creation in milliseconds.
*
* @return session creation system time in milliseconds
* @see System#currentTimeMillis()
*/
public long getCreationTime() {
return creationTime;
}

/**
* System time tag of last handshake.
*
* @return system time in milliseconds as string of the last handshake
*/
public String getLastHandshakeTime() {
return handshakeTimeTag;
}

/**
* Gets the (virtual) host name for the server that this session
* has been established for.
Expand Down Expand Up @@ -365,15 +384,15 @@ void setSniSupported(boolean flag) {
}

public DtlsEndpointContext getConnectionWriteContext() {

return new DtlsEndpointContext(peer, virtualHost, peerIdentity, sessionIdentifier.toString(),
Integer.toString(writeEpoch), cipherSuite.name(), handshakeTimeTag);
String id = sessionIdentifier.isEmpty() ? "TIME:" + Long.toString(creationTime) : sessionIdentifier.toString();
return new DtlsEndpointContext(peer, virtualHost, peerIdentity, id, Integer.toString(writeEpoch),
cipherSuite.name(), handshakeTimeTag);
}

public DtlsEndpointContext getConnectionReadContext() {

return new DtlsEndpointContext(peer, virtualHost, peerIdentity, sessionIdentifier.toString(),
Integer.toString(readEpoch), cipherSuite.name(), handshakeTimeTag);
String id = sessionIdentifier.isEmpty() ? "TIME:" + Long.toString(creationTime) : sessionIdentifier.toString();
return new DtlsEndpointContext(peer, virtualHost, peerIdentity, id, Integer.toString(readEpoch),
cipherSuite.name(), handshakeTimeTag);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,16 +228,19 @@ public synchronized boolean update(final Connection connection) {
}

public synchronized void putEstablishedSession(final DTLSSession session, final Connection connection) {
connectionsByEstablishedSession.put(session.getSessionIdentifier(), connection);
if (sessionCache != null) {
sessionCache.put(session);
SessionId sessionId = session.getSessionIdentifier();
if (!sessionId.isEmpty()) {
connectionsByEstablishedSession.put(session.getSessionIdentifier(), connection);
if (sessionCache != null) {
sessionCache.put(session);
}
}
}

@Override
public synchronized Connection find(final SessionId id) {

if (id == null) {
if (id == null || id.isEmpty()) {
return null;
} else {
Connection conFromLocalCache = findLocally(id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ public class ServerHandshaker extends Handshaker {
*/
private DTLSFlight lastFlight;

/** Does the server use session id? */
private boolean useNoSessionId = false;

/** Is the client wanted to authenticate itself? */
private boolean clientAuthenticationWanted = false;

Expand Down Expand Up @@ -215,6 +218,7 @@ public ServerHandshaker(int initialMessageSequenceNo, DTLSSession session, Recor
this.sniEnabled = config.isSniEnabled();
this.clientAuthenticationWanted = config.isClientAuthenticationWanted();
this.clientAuthenticationRequired = config.isClientAuthenticationRequired();
this.useNoSessionId = config.useNoServerSessionId();

// the server handshake uses the config with exchanged roles!
this.supportedClientCertificateTypes = config.getTrustCertificateTypes();
Expand Down Expand Up @@ -517,7 +521,8 @@ private void createServerHello(final ClientHello clientHello, final DTLSFlight f
clientRandom = clientHello.getRandom();
serverRandom = new Random();

SessionId sessionId = new SessionId();

SessionId sessionId = useNoSessionId ? SessionId.emptySessionId() : new SessionId();
session.setSessionIdentifier(sessionId);

// currently only NULL compression supported, no negotiation needed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ public byte[] getId() {
return id;
}

public boolean isEmpty() {
return id.length == 0;
}

/**
* Creates a new instance with an empty byte array as the ID.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ public void destroyServer() {
*/
public void cleanUpServer() {
serverConnectionStore.clear();
serverRawDataProcessor.clear();
serverRawDataChannel.setProcessor(serverRawDataProcessor);
server.setAlertHandler(null);
}
Expand Down Expand Up @@ -311,8 +312,10 @@ static interface RawDataProcessor {
RawData getLatestInboundMessage();

EndpointContext getClientEndpointContext();

boolean quiet(long quietMillis, long timeoutMillis) throws InterruptedException;

void clear();
}

static class MessageCapturingProcessor implements RawDataProcessor {
Expand All @@ -332,8 +335,9 @@ public RawData process(RawData request) {

@Override
public EndpointContext getClientEndpointContext() {
if (inboundMessage != null) {
return inboundMessage.get().getEndpointContext();
RawData data = inboundMessage.get();
if (data != null) {
return data.getEndpointContext();
} else {
return null;
}
Expand Down Expand Up @@ -372,6 +376,10 @@ public boolean quiet(long quietMillis, long timeoutMillis) throws InterruptedExc
quiet = false;
}
}

public void clear() {
inboundMessage.set(null);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
import org.eclipse.californium.scandium.rule.DtlsNetworkRule;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
Expand Down Expand Up @@ -147,6 +146,7 @@ public void testConnectorResumesSessionFromNewConnection() throws Exception {
client.forceResumeSessionFor(serverHelper.serverEndpoint);
Connection connection = clientConnectionStore.get(serverHelper.serverEndpoint);
assertArrayEquals(sessionId, connection.getEstablishedSession().getSessionIdentifier().getId());
long time = connection.getEstablishedSession().getCreationTime();

// create a new client with different inetAddress but with the same session store.
InetSocketAddress clientEndpoint = new InetSocketAddress(InetAddress.getLoopbackAddress(), 10001);
Expand All @@ -169,6 +169,7 @@ public void testConnectorResumesSessionFromNewConnection() throws Exception {
// check we use the same session id
connection = clientConnectionStore.get(serverHelper.serverEndpoint);
assertArrayEquals(sessionId, connection.getEstablishedSession().getSessionIdentifier().getId());
assertThat(time, is(connection.getEstablishedSession().getCreationTime()));
assertClientIdentity(RawPublicKeyIdentity.class);
}

Expand Down Expand Up @@ -428,7 +429,7 @@ public void testConnectorPerformsFullHandshakeWhenResumingNonExistingSession() t

// check session id was not equals
connection = clientConnectionStore.get(serverHelper.serverEndpoint);
Assert.assertThat(sessionId, not(equalTo(connection.getEstablishedSession().getSessionIdentifier().getId())));
assertThat(sessionId, not(equalTo(connection.getEstablishedSession().getSessionIdentifier().getId())));
assertClientIdentity(RawPublicKeyIdentity.class);
}

Expand Down Expand Up @@ -457,10 +458,53 @@ public void testConnectorPerformsFullHandshakeWhenResumingWithDifferentSni() thr

// check session id was not equals
connection = clientConnectionStore.get(serverHelper.serverEndpoint);
Assert.assertThat(sessionId, not(equalTo(connection.getEstablishedSession().getSessionIdentifier().getId())));
assertThat(sessionId, not(equalTo(connection.getEstablishedSession().getSessionIdentifier().getId())));
assertClientIdentity(RawPublicKeyIdentity.class);
}

@Test
public void testConnectorPerformsFullHandshakeWhenResumingWithEmptySessionId() throws Exception {
ConnectorHelper serverWithoutSessionId = new ConnectorHelper();
try {
DtlsConnectorConfig.Builder builder = new DtlsConnectorConfig.Builder();
builder.setNoServerSessionId(true);
serverWithoutSessionId.startServer(builder);

// Do a first handshake
RawData raw = RawData.outbound("Hello World".getBytes(),
new AddressEndpointContext(serverWithoutSessionId.serverEndpoint, "server.no", null), null, false);
LatchDecrementingRawDataChannel clientRawDataChannel = serverWithoutSessionId
.givenAnEstablishedSession(client, raw, true);
SessionId sessionId = serverWithoutSessionId.establishedServerSession.getSessionIdentifier();
assertTrue("session id must be empty", sessionId.isEmpty());

// Force a resume session the next time we send data
client.forceResumeSessionFor(serverWithoutSessionId.serverEndpoint);
Connection connection = clientConnectionStore.get(serverWithoutSessionId.serverEndpoint);
assertTrue(connection.getEstablishedSession().getSessionIdentifier().isEmpty());
long time = connection.getEstablishedSession().getCreationTime();
client.start();

// Prepare message sending
final String msg = "Hello Again";
CountDownLatch latch = new CountDownLatch(1);
clientRawDataChannel.setLatch(latch);

// send message
RawData data = RawData.outbound(msg.getBytes(),
new AddressEndpointContext(serverWithoutSessionId.serverEndpoint, "server.no", null), null, false);
client.send(data);
assertTrue(latch.await(MAX_TIME_TO_WAIT_SECS, TimeUnit.SECONDS));

// check session id was not equals
connection = clientConnectionStore.get(serverWithoutSessionId.serverEndpoint);
assertTrue(connection.getEstablishedSession().getSessionIdentifier().isEmpty());
assertThat(time, is(not(connection.getEstablishedSession().getCreationTime())));
} finally {
serverWithoutSessionId.destroyServer();
}
}

private void assertClientIdentity(final Class<?> principalType) {

// assert that client identity is of given type
Expand Down

0 comments on commit a90366c

Please sign in to comment.