ResponseDecryptor.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
 *    Ludwig Seitz (RISE SICS)
 *    Tobias Andersson (RISE SICS)
 *    Rikard Höglund (RISE SICS)
 *    
 ******************************************************************************/
package org.eclipse.californium.oscore;

import java.io.ByteArrayInputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.upokecenter.cbor.CBORObject;

import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.coap.OptionSet;
import org.eclipse.californium.core.coap.Response;
import org.eclipse.californium.core.coap.Token;
import org.eclipse.californium.core.network.serialization.UdpDataParser;
import org.eclipse.californium.core.coap.CoAP.ResponseCode;
import org.eclipse.californium.cose.Encrypt0Message;
import org.eclipse.californium.elements.util.DatagramReader;
import org.eclipse.californium.oscore.group.GroupDynamicContextDerivation;
import org.eclipse.californium.oscore.group.GroupRecipientCtx;

/**
 * 
 * Decrypts an OSCORE encrypted Response.
 *
 */
public class ResponseDecryptor extends Decryptor {

	/**
	 * The logger
	 */
	private static final Logger LOGGER = LoggerFactory.getLogger(ResponseDecryptor.class);

	/**
	 * Decrypt the response.
	 * 
	 * TODO: If request was pairwise (RID is null initially), need to get RID in
	 * a different. Can add map of Partial IV, KID and KID Context in the Group
	 * Context, db.getSeqByToken(token) can be used to get the Partial IV. When
	 * response is processed, delete the entry from the map (unless the response
	 * had the observe option).
	 * 
	 * Need request here, could have metadata associated to the request to know
	 * if it was in pairwise mode (and what KID it was sent to). Could change
	 * the "db.getSeqByToken(token)" to get PIV straight from the request.
	 *
	 * @param db the context database used
	 * @param response the response
	 * 
	 * @return the decrypted response
	 * 
	 * @throws OSException when decryption fails
	 * 
	 */
	public static Response decrypt(OSCoreCtxDB db, Response response) throws OSException {

		LOGGER.info("Removes E options from outer options which are not allowed there");
		discardEOptions(response);

		byte[] protectedData = response.getPayload();
		Encrypt0Message enc = null;
		Token token = response.getToken();
		OSCoreCtx ctx = null;
		OptionSet uOptions = response.getOptions();

		if (token != null) {
			ctx = db.getContextByToken(token);

			/*
			 * For a Group OSCORE context, get the specific Recipient Context.
			 * The KID (Recipient ID) is included in the message and the ID
			 * Context retrieved from the sender context
			 */
			if (ctx != null && ctx.isGroupContext()) {
				byte[] rid = OptionJuggle.getRid(uOptions.getOscore());
				byte[] idContext = ctx.getIdContext();

				// Retrieve the context
				ctx = db.getContext(rid, idContext);

				// If context is not found attempt dynamic context generation
				if (ctx == null) {
					ctx = GroupDynamicContextDerivation.derive(db, rid, idContext);
				}

				// If a context is found it should be a GroupRecipientCtx
				if (ctx != null) {
					assert (ctx instanceof GroupRecipientCtx);
				}
			}

			if (ctx == null) {
				LOGGER.error(ErrorDescriptions.TOKEN_INVALID);
				throw new OSException(ErrorDescriptions.TOKEN_INVALID);
			}
			enc = decompression(protectedData, response);
		} else {
			LOGGER.error(ErrorDescriptions.TOKEN_NULL);
			throw new OSException(ErrorDescriptions.TOKEN_NULL);
		}

		// Retrieve Context ID (kid context) from the actual message
		CBORObject kidContext = enc.findAttribute(CBORObject.FromObject(10));
		byte[] contextID = null;
		if (kidContext != null) {
			contextID = kidContext.GetByteString();
		}

		// Perform context re-derivation procedure if ongoing
		try {
			ctx = ContextRederivation.incomingResponse(db, ctx, contextID);
		} catch (OSException e) {
			LOGGER.error(ErrorDescriptions.CONTEXT_REGENERATION_FAILED);
			throw new OSException(ErrorDescriptions.CONTEXT_REGENERATION_FAILED);
		}

		//Check if parsing of response plaintext succeeds
		try {
			byte[] plaintext = decryptAndDecode(enc, response, ctx, db.getSeqByToken(token));
	
			DatagramReader reader = new DatagramReader(new ByteArrayInputStream(plaintext));
			
			
			response = OptionJuggle.setRealCodeResponse(response,
					CoAP.ResponseCode.valueOf(reader.read(CoAP.MessageFormat.CODE_BITS)));
		
			
			// resets option so eOptions gets priority during parse
			response.setOptions(EMPTY);
			new UdpDataParser().parseOptionsAndPayload(reader, response);
		} catch (Exception e) {
			LOGGER.error(ErrorDescriptions.DECRYPTION_FAILED);
			throw new OSException(ErrorDescriptions.DECRYPTION_FAILED);
		}

		OptionSet eOptions = response.getOptions();
		eOptions = OptionJuggle.merge(eOptions, uOptions);
		response.setOptions(eOptions);

		/*
		 * Remove token after response is received, unless it has Observe or is
		 * using Group OSCORE. If it has Observe it will be removed after
		 * cancellation elsewhere.
		 */
		if (response.getOptions().hasObserve() == false && ctx.isGroupContext() == false) {
			db.removeToken(token);
		}

		//Set information about the OSCORE context used in the endpoint context of this response
		OSCoreEndpointContextInfo.receivingResponse(ctx, response);

		return response;
	}
}