ObjectSecurityContextLayer.java
/*******************************************************************************
* Copyright (c) 2019 RISE SICS 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:
* Joakim Brorsson
* Tobias Andersson (RISE SICS)
* Rikard Höglund (RISE SICS)
*
******************************************************************************/
package org.eclipse.californium.oscore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.eclipse.californium.core.coap.EmptyMessage;
import org.eclipse.californium.core.coap.Message;
import org.eclipse.californium.core.coap.MessageObserverAdapter;
import org.eclipse.californium.core.coap.OptionNumberRegistry;
import org.eclipse.californium.core.coap.OptionSet;
import org.eclipse.californium.core.coap.Request;
import org.eclipse.californium.core.coap.Response;
import org.eclipse.californium.core.network.Exchange;
import org.eclipse.californium.core.network.Exchange.Origin;
import org.eclipse.californium.core.network.stack.AbstractLayer;
import org.eclipse.californium.elements.util.Bytes;
import org.eclipse.californium.oscore.ContextRederivation.PHASE;
/**
*
* Applies OSCORE mechanics at stack layer.
*
* Handles functionality for context re-derivation and outer block-wise.
* https://tools.ietf.org/html/rfc8613#appendix-B.2
* https://tools.ietf.org/html/rfc8613#section-4.1.3.4.2
*
*/
public class ObjectSecurityContextLayer extends AbstractLayer {
/**
* The logger
*/
private static final Logger LOGGER = LoggerFactory.getLogger(ObjectSecurityContextLayer.class);
private final OSCoreCtxDB ctxDb;
public ObjectSecurityContextLayer(OSCoreCtxDB ctxDb) {
if (ctxDb == null) {
throw new NullPointerException("OSCoreCtxDB must be provided!");
}
this.ctxDb = ctxDb;
}
@Override
public void receiveRequest(Exchange exchange, Request request) {
// Handle incoming OSCORE requests that have been re-assembled by the
// block-wise layer (for outer block-wise). If an incoming request has
// already been processed by OSCORE the option will be empty. If not it
// is a re-assembled request to be processed here.
boolean outerBlockwise = request.getOptions().hasOscore() && request.getOptions().getOscore().length != 0
&& exchange.getCurrentRequest() != null && exchange.getCurrentRequest().getOptions().hasBlock1();
if (isProtected(request) && outerBlockwise) {
LOGGER.debug("Incoming OSCORE request uses outer block-wise");
// Retrieve the OSCORE context for this RID and ID Context
byte[] rid = OptionJuggle.getRid(request.getOptions().getOscore());
byte[] IDContext = OptionJuggle.getIDContext(request.getOptions().getOscore());
OSCoreCtx ctx = null;
try {
ctx = ctxDb.getContext(rid, IDContext);
} catch (CoapOSException e) {
LOGGER.error("Error while receiving OSCore request: " + e.getMessage());
Response error;
error = CoapOSExceptionHandler.manageError(e, request);
if (error != null) {
super.sendResponse(exchange, error);
}
return;
}
try {
request = RequestDecryptor.decrypt(ctxDb, request, ctx);
rid = request.getOptions().getOscore();
request.getOptions().setOscore(Bytes.EMPTY);
exchange.setRequest(request);
} catch (CoapOSException e) {
LOGGER.error("Error while receiving OSCore request: " + e.getMessage());
Response error;
error = CoapOSExceptionHandler.manageError(e, request);
if (error != null) {
super.sendResponse(exchange, error);
}
return;
}
exchange.setCryptographicContextID(rid);
}
super.receiveRequest(exchange, request);
}
@Override
public void sendRequest(final Exchange exchange, final Request request) {
if (shouldProtectRequest(request)) {
try {
final String uri = request.getURI();
if (uri == null) {
LOGGER.error(ErrorDescriptions.URI_NULL);
throw new OSException(ErrorDescriptions.URI_NULL);
}
OSCoreCtx ctx = ctxDb.getContext(uri);
// if (ctx == null) {
// LOGGER.error(ErrorDescriptions.CTX_NULL);
// throw new OSException(ErrorDescriptions.CTX_NULL);
// }
// Initiate context re-derivation procedure if flag is set
if (ctx != null && ctx.getContextRederivationPhase() == PHASE.CLIENT_INITIATE) {
ContextRederivation.setLostContext(ctxDb, uri);
// send dummy request before to rederive the new context
// and then send the original request using this new context
final Request startRederivation = Request.newGet();
startRederivation.setScheme(request.getScheme());
startRederivation.setDestinationContext(request.getDestinationContext());
startRederivation.getOptions().setOscore(Bytes.EMPTY);
startRederivation.getOptions().setUriPath("/rederivation/blackhole");
startRederivation.addMessageObserver(new MessageObserverAdapter() {
@Override
public void onResponse(final Response response) {
try {
OSCoreCtx ctx = ctxDb.getContext(uri);
if (ctx == null) {
LOGGER.error(ErrorDescriptions.CTX_NULL);
} else if (ctx.getContextRederivationPhase() != PHASE.CLIENT_PHASE_2) {
LOGGER.error("Expected phase 2, but is {}", ctx.getContextRederivationPhase());
}
} catch (OSException e) {
}
// send original request, if the start rederivation receives a response
exchange.execute(new Runnable() {
@Override
public void run() {
LOGGER.info("Original Request: " + exchange.getRequest().toString());
ObjectSecurityContextLayer.super.sendRequest(exchange, request);
}
});
}
@Override
public void onReject() {
// forward rejection to original request
request.setRejected(true);
}
@Override
public void onCancel() {
// forward cancel to original request
request.setCanceled(true);
}
@Override
public void onTimeout() {
// forward timeout to original request
request.setTimedOut(true);
}
@Override
public void onConnecting() {
// forward on connect to original request
request.onConnecting();
}
@Override
public void onDtlsRetransmission(int flight) {
// forward dtls handshake retransmission to original request
request.onDtlsRetransmission(flight);
}
@Override
// forward send error to original request
public void onSendError(Throwable error) {
request.setSendError(error);
}
});
// send start rederivation request
LOGGER.info("Auxiliary Request: " + exchange.getRequest().toString());
final Exchange newExchange = new Exchange(startRederivation, exchange.getPeersIdentity(), Origin.LOCAL, executor);
newExchange.execute(new Runnable() {
@Override
public void run() {
ObjectSecurityContextLayer.super.sendRequest(newExchange, startRederivation);
}
});
return;
}
} catch (OSException e) {
LOGGER.error("Error sending request: " + e.getMessage());
return;
} catch (IllegalArgumentException e) {
LOGGER.error("Unable to send request because of illegal argument: " + e.getMessage());
return;
}
}
LOGGER.info("Request: " + exchange.getRequest().toString());
super.sendRequest(exchange, request);
}
@Override
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;
if (outerBlockwise) {
LOGGER.debug("Incoming OSCORE response uses outer block-wise");
Request request = exchange.getCurrentRequest();
if (request == null) {
LOGGER.error("No request tied to this response");
return;
}
try {
// If response is protected with OSCORE parse it first with
// prepareReceive
if (isProtected(response)) {
response = ObjectSecurityLayer.prepareReceive(ctxDb, response);
}
} catch (OSException e) {
LOGGER.error("Error while receiving OSCore response: " + e.getMessage());
EmptyMessage error = CoapOSExceptionHandler.manageError(e, response);
if (error != null) {
sendEmptyMessage(exchange, error);
}
return;
}
// Remove token if this is a response to a Observe cancellation
// request
if (exchange.getRequest().isObserveCancel()) {
ctxDb.removeToken(response.getToken());
}
super.receiveResponse(exchange, response);
}
super.receiveResponse(exchange, response);
}
private static boolean shouldProtectRequest(Request request) {
OptionSet options = request.getOptions();
return options.hasOption(OptionNumberRegistry.OSCORE);
}
private static boolean isProtected(Message message) {
OptionSet options = message.getOptions();
return options.hasOption(OptionNumberRegistry.OSCORE);
}
}