Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add configuration to disable reuse of tokens on blockwise transfer. #2088

Merged
merged 1 commit into from
Nov 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -611,12 +611,27 @@ public enum CongestionControlMode {
* adapt the block size when receiving a 4.13 Entity too large response
* code.
* <p>
* See https://tools.ietf.org/html/rfc7959#section-2.9.3 for more details.
* @see <a href="https://tools.ietf.org/html/rfc7959#section-2.9.3" target="_blank">RFC7959, 2.9.3. - 4.13 Request Entity Too Large</a>
*/
public static final BooleanDefinition BLOCKWISE_ENTITY_TOO_LARGE_AUTO_FAILOVER = new BooleanDefinition(
MODULE + "BLOCKWISE_ENTITY_TOO_LARGE_AUTO_FAILOVER",
"Enable automatic failover on \"entity too large\" response.",
DEFAULT_BLOCKWISE_ENTITY_TOO_LARGE_AUTO_FAILOVER);
/**
* Property to indicate that blockwise follow-up requests are reusing the
* same token for traceability.
* <p>
* <b>Note:</b> reusing tokens may introduce a vulnerability, if
* requests/response are captured and sent later without protecting the
* integrity of the payload by other means.
* </p>
*
* @see <a href="https://github.com/core-wg/attacks-on-coap" target="_blank">attacks-on-coap</a>
* @since 3.8
*/
public static final BooleanDefinition BLOCKWISE_REUSE_TOKEN = new BooleanDefinition(
MODULE + "BLOCKWISE_REUSE_TOKEN",
"Reuse token for blockwise requests. Ease traceability but may introduce vulnerability.", false);

/**
* Time interval for a coap-server to check the client's interest in further
Expand Down Expand Up @@ -794,6 +809,7 @@ public void applyDefinitions(Configuration config) {
config.set(BLOCKWISE_STATUS_INTERVAL, DEFAULT_BLOCKWISE_STATUS_INTERVAL_IN_SECONDS, TimeUnit.SECONDS);
config.set(BLOCKWISE_STRICT_BLOCK2_OPTION, DEFAULT_BLOCKWISE_STRICT_BLOCK2_OPTION);
config.set(BLOCKWISE_ENTITY_TOO_LARGE_AUTO_FAILOVER, DEFAULT_BLOCKWISE_ENTITY_TOO_LARGE_AUTO_FAILOVER);
config.set(BLOCKWISE_REUSE_TOKEN, false);
// BERT enabled, when > 1
config.set(TCP_NUMBER_OF_BULK_BLOCKS, 4);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ public final class Block1BlockwiseStatus extends BlockwiseStatus {

private static final Logger LOGGER = LoggerFactory.getLogger(Block1BlockwiseStatus.class);

/**
* Current pending block wise request.
*
* @since 3.8
*/
private Request current;

/**
* Create block1wise status.
*
Expand All @@ -47,6 +54,7 @@ public final class Block1BlockwiseStatus extends BlockwiseStatus {
private Block1BlockwiseStatus(KeyUri keyUri, RemoveHandler removeHandler, Exchange exchange, Request request,
int maxSize, int maxTcpBertBulkBlocks) {
super(keyUri, removeHandler, exchange, request, maxSize, maxTcpBertBulkBlocks);
current = request;
}

/**
Expand Down Expand Up @@ -189,6 +197,7 @@ public synchronized Request getNextRequestBlock(int blockSzx) throws BlockwiseTr
block.getOptions().setBlock1(blockSzx, m, num);

setComplete(!m);
current = block;
return block;
}

Expand All @@ -214,13 +223,15 @@ public boolean cancelRequest() {
}

/**
* Checks whether a response has the same token as the request that
* initiated the block1 transfer that this is the tracker for.
* Checks whether a response has the same token as the current request of
* this tracker.
*
* @param response The response to check.
* @return {@code true} if the tokens match.
* @since 3.8 use the current request instead of the initial request to
* support blockwise transfer with changing tokens.
*/
public boolean hasMatchingToken(final Response response) {
return response.getToken().equals(firstMessage.getToken());
public synchronized boolean hasMatchingToken(final Response response) {
return response.getToken().equals(current.getToken());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ public void remove(BlockwiseStatus status) {
private volatile boolean enableStatus;
private ScheduledFuture<?> statusLogger;
private ScheduledFuture<?> cleanup;
private final long healthStatusInterval;
private final int maxTcpBertBulkBlocks;
private final int maxMessageSize;
private final int preferredBlockSzx;
Expand All @@ -226,12 +227,23 @@ public void remove(BlockwiseStatus status) {
private final int maxResourceBodySize;
private final boolean strictBlock1Option;
private final boolean strictBlock2Option;
private final long healthStatusInterval;
/**
* Reuse tokens for follow-up requests.
* <p>
* <b>Note:</b> reusing tokens may introduce a vulnerability, if
* requests/response are captured and sent later without protecting the
* integrity of the payload by other means.
* </p>
*
* @see <a href="https://github.com/core-wg/attacks-on-coap" target="_blank">attacks-on-coap</a>
* @since 3.8
*/
private final boolean reuseToken;
/* @since 2.4 */
private final boolean enableAutoFailoverOn413;

private final EndpointContextMatcher matchingStrategy;

/**
* Creates a new blockwise layer for a configuration.
* <p>
Expand Down Expand Up @@ -372,7 +384,7 @@ public void onEviction(Block2BlockwiseStatus status) {
});
strictBlock1Option = config.get(CoapConfig.BLOCKWISE_STRICT_BLOCK1_OPTION);
strictBlock2Option = config.get(CoapConfig.BLOCKWISE_STRICT_BLOCK2_OPTION);

reuseToken = config.get(CoapConfig.BLOCKWISE_REUSE_TOKEN);
healthStatusInterval = config.get(SystemConfig.HEALTH_STATUS_INTERVAL, TimeUnit.MILLISECONDS);

enableAutoFailoverOn413 = config.get(CoapConfig.BLOCKWISE_ENTITY_TOO_LARGE_AUTO_FAILOVER);
Expand Down Expand Up @@ -760,6 +772,46 @@ public void sendResponse(final Exchange exchange, final Response response) {
lower().sendResponse(exchange, responseToSend);
}

/**
* Get outer response to pass to application.
*
* The outer response matches to initial application request.
*
* @param exchange exchange
* @param response actual response
* @return outer application response
* @since 3.8
*/
private Response getOuterResponse(Exchange exchange, Response response) {
// check, if response is for original request
if (exchange.getRequest() != exchange.getCurrentRequest()) {
// prepare the response as response to the original request
Response outerResponse = new Response(response.getCode());
// adjust the token using the original request
outerResponse.setToken(exchange.getRequest().getToken());
if (exchange.getRequest().getType() == Type.CON) {
outerResponse.setType(Type.ACK);
// adjust MID also
outerResponse.setMID(exchange.getRequest().getMID());
} else {
outerResponse.setType(Type.NON);
}
outerResponse.setSourceContext(response.getSourceContext());
outerResponse.setPayload(response.getPayload());
outerResponse.setOptions(response.getOptions());
outerResponse.setApplicationRttNanos(exchange.calculateApplicationRtt());
Long rtt = response.getTransmissionRttNanos();
if (rtt != null) {
outerResponse.setTransmissionRttNanos(rtt);
}
exchange.setResponse(outerResponse);
return outerResponse;
} else {
exchange.setResponse(response);
return response;
}
}

/**
* Invoked when a response has been received from a peer.
* <p>
Expand Down Expand Up @@ -800,32 +852,7 @@ public void receiveResponse(final Exchange exchange, final Response response) {
default:
}

// check, if response is for original request
if (exchange.getRequest() != exchange.getCurrentRequest()) {
// prepare the response as response to the original request
Response resp = new Response(response.getCode());
// adjust the token using the original request
resp.setToken(exchange.getRequest().getToken());
if (exchange.getRequest().getType() == Type.CON) {
resp.setType(Type.ACK);
// adjust MID also
resp.setMID(exchange.getRequest().getMID());
} else {
resp.setType(Type.NON);
}
resp.setSourceContext(response.getSourceContext());
resp.setPayload(response.getPayload());
resp.setOptions(response.getOptions());
resp.setApplicationRttNanos(exchange.calculateApplicationRtt());
Long rtt = response.getTransmissionRttNanos();
if (rtt != null) {
resp.setTransmissionRttNanos(rtt);
}
exchange.setResponse(resp);
upper().receiveResponse(exchange, resp);
} else {
upper().receiveResponse(exchange, response);
}
upper().receiveResponse(exchange, getOuterResponse(exchange, response));
return;
}

Expand Down Expand Up @@ -1039,11 +1066,9 @@ private void handleBlock1Response(final Exchange exchange, final Response respon
LOGGER.debug("{}Block1 followed by Block2 transfer", tag);
} else {
// All request blocks have been acknowledged and we have
// received a
// response that does not need blockwise transfer. Thus, deliver
// it.
exchange.setResponse(response);
upper().receiveResponse(exchange, response);
// received a response that does not need blockwise transfer.
// Thus, deliver it.
upper().receiveResponse(exchange, getOuterResponse(exchange, response));
}
}
}
Expand All @@ -1058,8 +1083,10 @@ private void sendNextBlock(Exchange exchange, Response response, Block1Blockwise
int blockSzx = Math.min(response.getOptions().getBlock1().getSzx(), preferredBlockSzx);
nextBlock = status.getNextRequestBlock(blockSzx);

// we use the same token to ease traceability
nextBlock.setToken(response.getToken());
if (reuseToken) {
// we use the same token to ease traceability
nextBlock.setToken(response.getToken());
}
nextBlock.setDestinationContext(status.getFollowUpEndpointContext(response.getSourceContext()));

LOGGER.debug("{}sending (next) Block1 [num={}]: {}", tag, nextBlock.getOptions().getBlock1().getNum(),
Expand Down Expand Up @@ -1152,8 +1179,7 @@ private void handleBlock2Response(final Exchange exchange, final Response respon

if (response.isNotification()) {
// We have received a notification for an observed resource that
// the
// application layer is no longer interested in.
// the application layer is no longer interested in.
// Let upper layers decide what to do with the notification.
upper().receiveResponse(exchange, response);
}
Expand Down Expand Up @@ -1253,7 +1279,8 @@ private void requestNextBlock(Exchange exchange, Response response, Block2Blockw
* a different KeyToken in exchangesByToken, which is cleaned up
* with the CleanupMessageObserver above.
*/
if (!response.isNotification()) {
if (reuseToken && !response.isNotification()) {
// we use the same token to ease traceability
block.setToken(response.getToken());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -844,7 +844,7 @@ public void testConcurrentBlock1TransferCancelsOriginalRequest() throws Exceptio
server.sendResponse(ACK, CONTINUE).loadBoth("B").block1(1, true, 128).go();

server.expectRequest(CON, PUT, path).storeBoth("B").block1(2, false, 128).payload(reqtPayload, 256, 300).go();
server.sendResponse(ACK, CHANGED).loadBoth("B").go();
server.sendResponse(ACK, CHANGED).loadBoth("B").block1(2, false, 128).go();

Response response = concurrentRequest.waitForResponse(ERROR_TIMEOUT_IN_MS);
assertThat(response, is(notNullValue()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,9 +233,9 @@ public void receiveResponse(Exchange exchange, Response response) {
// Handle incoming OSCORE responses that have been re-assembled by the
// block-wise layer (for outer block-wise). If a response was not
// processed by OSCORE in the ObjectSecurityLayer it will happen here.
boolean outerBlockwise = exchange.getCurrentResponse() != null
&& exchange.getCurrentResponse().getOptions().hasBlock2()
&& ctxDb.getContextByToken(exchange.getCurrentResponse().getToken()) != null;
Response rawResponse = exchange.getCurrentResponse();
boolean outerBlockwise = rawResponse.getOptions().hasBlock2()
&& ctxDb.getContextByToken(rawResponse.getToken()) != null;
if (outerBlockwise) {

LOGGER.debug("Incoming OSCORE response uses outer block-wise");
Expand Down Expand Up @@ -270,8 +270,6 @@ public void receiveResponse(Exchange exchange, Response response) {
if (exchange.getRequest().isObserveCancel()) {
ctxDb.removeToken(response.getToken());
}

super.receiveResponse(exchange, response);
}

super.receiveResponse(exchange, response);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,19 @@ public void sendRequest(final Exchange exchange, final Request request) {
// Handle outgoing requests for more data from a responder that
// is responding with outer block-wise. These requests should
// not be processed with OSCORE.
boolean outerBlockwise = request.getOptions().hasBlock2() && exchange.getCurrentResponse() != null
&& ctxDb.getContextByToken(exchange.getCurrentResponse().getToken()) != null;
if (outerBlockwise) {
super.sendRequest(exchange, req);
return;
if (request.getOptions().hasBlock2() && exchange.getCurrentResponse() != null) {
final OSCoreCtx ctx = ctxDb.getContextByToken(exchange.getCurrentResponse().getToken());
if (ctx != null) {
request.addMessageObserver(0, new MessageObserverAdapter() {

@Override
public void onReadyToSend() {
ctxDb.addContext(request.getToken(), ctx);
}
});
super.sendRequest(exchange, request);
return;
}
}

final String uri;
Expand Down Expand Up @@ -169,7 +177,7 @@ public void sendRequest(final Exchange exchange, final Request request) {
final Request preparedRequest = prepareSend(ctxDb, request);
final OSCoreCtx finalCtx = ctxDb.getContext(uri);

if (outgoingExceedsMaxUnfragSize(preparedRequest, outerBlockwise, ctx.getMaxUnfragmentedSize())) {
if (outgoingExceedsMaxUnfragSize(preparedRequest, false, ctx.getMaxUnfragmentedSize())) {
throw new IllegalStateException("outgoing request is exceeding the MAX_UNFRAGMENTED_SIZE!");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -602,18 +602,30 @@ public int getCounter() {
*/
private void createSimpleProxy(final boolean proxyRequestBlockwise, final boolean proxyResponseBlockwiseEnabled) {

final CoapClient proxyClient = new CoapClient();
proxyClient.setTimeout(1000L);

CoapEndpoint.Builder builder = new CoapEndpoint.Builder();
builder.setCoapStackFactory(CoapEndpoint.STANDARD_COAP_STACK_FACTORY);
if (proxyRequestBlockwise) {
builder.setConfiguration(blockwiseConfig);
}
CoapEndpoint proxyClientEndpoint = builder.build();
proxyClient.setEndpoint(proxyClientEndpoint);
cleanup.add(proxyClientEndpoint);

final Coap2CoapTranslator coapTranslator = new Coap2CoapTranslator();

// Create endpoint for proxy server side
CoapEndpoint.Builder builder = new CoapEndpoint.Builder();
builder = new CoapEndpoint.Builder();
builder.setCoapStackFactory(CoapEndpoint.STANDARD_COAP_STACK_FACTORY);
builder.setInetSocketAddress(TestTools.LOCALHOST_EPHEMERAL);
if (proxyResponseBlockwiseEnabled) {
builder.setConfiguration(blockwiseConfig);
}

CoapEndpoint proxyServerEndpoint = builder.build();

// Create proxy
CoapServer proxy = new CoapServer();
cleanup.add(proxy);
Expand All @@ -632,21 +644,13 @@ public void deliverRequest(Exchange exchange) {
coapTranslator.getExposedInterface(incomingRequest));
Request outgoingRequest = coapTranslator.getRequest(finalDestinationUri, incomingRequest);

CoapClient proxyClient = new CoapClient();

// Create endpoint for proxy client side
CoapEndpoint.Builder builder = new CoapEndpoint.Builder();
builder.setCoapStackFactory(CoapEndpoint.STANDARD_COAP_STACK_FACTORY);
if (proxyRequestBlockwise) {
builder.setConfiguration(blockwiseConfig);
}
CoapEndpoint proxyClientEndpoint = builder.build();
proxyClient.setEndpoint(proxyClientEndpoint);
cleanup.add(proxyClientEndpoint);

// Now receive the response from the server and prepare the
// final response to the client
CoapResponse incomingResponse = proxyClient.advanced(outgoingRequest);
if (incomingResponse == null) {
System.err.println("Missing response.");
fail();
}
outgoingResponse = coapTranslator.getResponse(incomingResponse.advanced());
} catch (org.eclipse.californium.proxy2.TranslationException | ConnectorException | IOException e) {
System.err.println("Processing on proxy failed.");
Expand Down