SideProcessor.java
package org.eclipse.californium.edhoc;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.californium.core.coap.CoAP.ResponseCode;
import com.upokecenter.cbor.CBORObject;
import com.upokecenter.cbor.CBORType;
/*
* During the EDHOC execution, the side processor object temporarily
* takes over the processing of incoming messages in order to:
* i) validate authentication credential of other peers; and
* ii) process EAD items, which can play a role in the previous point.
*
* Due to early pre-parsing of the EAD field, the side processor object
* can receive only EAD items that this peers supports
*/
public class SideProcessor {
// The trust model used to validate authentication credentials of other peers
private int trustModel;
// Authentication credentials of other peers
//
// The map label is a CBOR Map used as ID_CRED_X
// The map value is a CBOR Byte String, with value the serialization of CRED_X
private HashMap<CBORObject, CBORObject> peerCredentials = new HashMap<CBORObject, CBORObject>();
// The EDHOC session this side process object is tied to
private EdhocSession session;
// The following data structures are used to collect the results from the side processing of each incoming EDHOC message.
// For message_2 and message_3, each of those refer to two different data structures, in order to separately collect the
// results of the processing occurred before and after message verification.
//
// The value of the outer map is a list of maps. Each element of the list includes the results from one processing process.
// The key of the outer map uniquely determines the namespace of keys and corresponding values for the inner maps organized into a list.
//
// The key of the outer map is equal to the ead_label of the EAD item the results refer to, with the following exceptions:
//
// - The outer map includes an entry with label 0, with information about the authentication credential of the other peer to use.
// - The outer map includes an entry with label -1, in case the overall side processing fails.
//
private HashMap<Integer, List<HashMap<Integer, CBORObject>>> resMessage1 = new HashMap<Integer, List<HashMap<Integer, CBORObject>>>();
private HashMap<Integer, List<HashMap<Integer, CBORObject>>> resMessage2Pre = new HashMap<Integer, List<HashMap<Integer, CBORObject>>>();
private HashMap<Integer, List<HashMap<Integer, CBORObject>>> resMessage2Post = new HashMap<Integer, List<HashMap<Integer, CBORObject>>>();
private HashMap<Integer, List<HashMap<Integer, CBORObject>>> resMessage3Pre = new HashMap<Integer, List<HashMap<Integer, CBORObject>>>();
private HashMap<Integer, List<HashMap<Integer, CBORObject>>> resMessage3Post = new HashMap<Integer, List<HashMap<Integer, CBORObject>>>();
private HashMap<Integer, List<HashMap<Integer, CBORObject>>> resMessage4 = new HashMap<Integer, List<HashMap<Integer, CBORObject>>>();
// This data structure collects the produced EAD items to include in an outgoing EDHOC message.
//
// The outer map key indicates the outgoing EDHOC message in question.
//
// Each inner list specifies a sequence of element pairs (CBOR integer, CBOR byte string) or of elements (CBOR integer),
// for EAD items that specify or do not specify an ead_value, respectively. The CBOR integer specifies the ead_label in case
// of non-critical EAD item, or the corresponding negative value in case of critical EAD item.
private HashMap<Integer, List<CBORObject>> producedEADs = new HashMap<Integer, List<CBORObject>>();
// This data structure collects instructions provided by the application for producing EAD items
// to include in outgoing EDHOC messages. The production of these EAD items is not related to or
// triggered by the consumption of other EAD items included in incoming EDHOC messages.
//
// This data structure can be null if the application does not specify the production of any of such EAD items.
//
// The outer map key indicates the outgoing EDHOC message in question.
//
// Each inner list specifies a sequence of element pairs (CBOR integer, CBOR map).
// The CBOR integer specifies the ead_label in case of non-critical EAD item,
// or the corresponding negative value in case of critical EAD item.
// The CBOR map provides input on how to produce the EAD item,
// with the map keys from a namespace specific of the ead_label.
private HashMap<Integer, List<CBORObject>> eadProductionInput = new HashMap<Integer, List<CBORObject>>();
public SideProcessor(int trustModel, HashMap<CBORObject, CBORObject> peerCredentials,
HashMap<Integer, List<CBORObject>> eadProductionInput) {
this.trustModel = trustModel;
this.peerCredentials = peerCredentials;
this.session = null;
this.eadProductionInput = eadProductionInput;
}
/**
* Return the results obtained from the side processing
*
* @param messageNumber The number of EDHOC message that the EAD items refer to
* @param postValidation True to select the results of EAD processing after EDHOC message validation, or false otherwise
* @return The results obtained from consuming/producing EAD items for the EDHOC message.
*/
public HashMap<Integer, List<HashMap<Integer, CBORObject>>> getResults(int messageNumber, boolean postValidation) {
return whichResults(messageNumber, postValidation);
}
/**
* Store a result obtained from the side processing
*
* @param messageNumber The number of EDHOC message that the EAD items refer to
* @param postValidation True to select the results of EAD processing after EDHOC message validation, or false otherwise
* @param resultLabel Identifier of the specific map where to store this result
* @param resultContent The result to store
*/
private void addResult(int messageNumber, boolean postValidation, int resultLabel, HashMap<Integer, CBORObject> resultContent) {
HashMap<Integer, List<HashMap<Integer, CBORObject>>> myResults = whichResults(messageNumber, postValidation);
if (!myResults.containsKey(Integer.valueOf(resultLabel))) {
List<HashMap<Integer, CBORObject>> myList = new ArrayList<HashMap<Integer, CBORObject>>();
myResults.put(Integer.valueOf(resultLabel), myList);
}
myResults.get(Integer.valueOf(resultLabel)).add(resultContent);
}
/**
* Delete all the results obtained from the side processing
*/
public void removeResults() {
resMessage1.clear();
resMessage2Pre.clear();
resMessage2Post.clear();
resMessage3Pre.clear();
resMessage3Post.clear();
resMessage4.clear();
}
/**
* Delete all the results from the side processing related to an EDHOC message
*
* @param messageNumber The number of EDHOC message that the EAD items refer to
* @param postValidation True to select the results of EAD processing after EDHOC message validation, or false otherwise
*/
public void removeResults(int messageNumber, boolean postValidation) {
HashMap<Integer, List<HashMap<Integer, CBORObject>>> myResults = whichResults(messageNumber, postValidation);
myResults.clear();
}
/**
* Delete a specific result set obtained from the side processing related to an EDHOC message
*
* @param messageNumber The number of EDHOC message that the EAD items refer to
* @param keyValue The identifier of the result set to delete
* @param postValidation True to select the results of EAD processing after EDHOC message validation, or false otherwise
*/
public void removeResultSet(int messageNumber, int keyValue, boolean postValidation) {
HashMap<Integer, List<HashMap<Integer, CBORObject>>> myResults = whichResults(messageNumber, postValidation);
if (myResults.size() == 0)
return;
myResults.remove(Integer.valueOf(keyValue));
}
/**
* Store an error result obtained from the side processing
*
* @param messageNumber The number of EDHOC message that the EAD items refer to
* @param postValidation True to select the results of EAD processing after EDHOC message validation, or false otherwise
* @param errorMessage The error message
* @param responseCode The CoAP response error code to use, if following up with an EDHOC error message as a CoAP response
*/
private void addErrorResult(int messageNumber, boolean postValidation, String errorMessage, int responseCode) {
HashMap<Integer, CBORObject> errorMap = new HashMap<Integer, CBORObject>();
errorMap.put(Integer.valueOf(Constants.SIDE_PROCESSOR_INNER_ERROR_DESCRIPTION),
CBORObject.FromObject(errorMessage));
errorMap.put(Integer.valueOf(Constants.SIDE_PROCESSOR_INNER_ERROR_RESP_CODE),
CBORObject.FromObject(responseCode));
addResult(messageNumber, postValidation, Constants.SIDE_PROCESSOR_OUTER_ERROR, errorMap);
}
public List<CBORObject> getProducedEADs(int messageNumber) {
return producedEADs.get(Integer.valueOf(messageNumber));
}
/**
* @param messageNumber The number of the outgoing EDHOC message that will include the EAD item
* @param eadLabel The ead_label of the EAD item to include, or its corresponding negative value if the EAD item is critical
* @param eadValue The ead_value of the EAD item to include, or null if the ead_value is not present
*/
private void addProducedEAD(int messageNumber, CBORObject eadLabel, CBORObject eadValue) {
if (!producedEADs.containsKey(Integer.valueOf(messageNumber))) {
producedEADs.put(Integer.valueOf(messageNumber), new ArrayList<CBORObject>());
}
List<CBORObject> myList = producedEADs.get(Integer.valueOf(messageNumber));
myList.add(eadLabel);
if (eadValue != null) {
myList.add(eadValue);
}
}
/**
* Return the correct map to look at, as including the desired results obtained from the side processing
*
* @param messageNumber The number of the outgoing EDHOC message that will include the EAD item
* @param postValidation True to select the results of EAD processing after EDHOC message validation, or false otherwise
* @return The map including the desired results obtained from the side processing
*/
private HashMap<Integer, List<HashMap<Integer, CBORObject>>> whichResults(int messageNumber, boolean postValidation) {
switch(messageNumber) {
case Constants.EDHOC_MESSAGE_1:
return resMessage1;
case Constants.EDHOC_MESSAGE_2:
return (postValidation == false) ? resMessage2Pre : resMessage2Post;
case Constants.EDHOC_MESSAGE_3:
return (postValidation == false) ? resMessage3Pre : resMessage3Post;
case Constants.EDHOC_MESSAGE_4:
return resMessage4;
}
return null;
}
/**
* Associates this SideProcessor object with the EDHOC session to consider
*
* @param session The EDHOC session
*/
public void setEdhocSession(EdhocSession session) {
if (session != null) {
this.session = session;
}
if (this.session != null) {
this.session.setSideProcessor(this);
if (session == null) {
this.session = null;
}
}
}
/**
* Entry point for processing EAD items from EAD_1
*
* @param sideProcessorInfo Information generally required for processing EAD_1
* @param ead1 The EAD items from EAD_1, including only items that the endpoint understands and excluding padding
*/
// sideProcessorInfo includes useful pieces information for processing EAD_1
// 0) A CBOR integer, with value MEHOD
// 1) A CBOR array of integers, including all the integers specified in SUITES_I, in the same order
// 2) A CBOR byte string, with value G_X
// 3) A CBOR byte string, with value C_I (in its original, binary format)
public void sideProcessingMessage1(CBORObject[] sideProcessorInfo, CBORObject[] ead1) {
// Go through the EAD_1 items, if any
//
// For each EAD item, invoke the corresponding consume() method, and then addResult().
// Stop in case the consumption of an EAD item returns a fatal error.
//
// This may further trigger the production of new EAD items to include in the next, outgoing EDHOC message.
// In such a case, invoke eadProductionDispatcher() for each of those EAD items to produce.
//
// ...
//
}
/**
* Entry point for processing EAD items from EAD_2 before message verification
*
* @param sideProcessorInfo Information generally required for processing EAD_2
* @param ead2 The EAD items from EAD_2, including only items that the endpoint understands and excluding padding
*/
// sideProcessorInfo includes useful pieces information for processing EAD_2, in this order:
// 0) A CBOR byte string, with value G_Y
// 1) A CBOR byte string, with value C_R (in its original, binary format)
// 2) A CBOR map, as ID_CRED_R
public void sideProcessingMessage2PreVerification(CBORObject[] sideProcessorInfo, CBORObject[] ead2) {
CBORObject gY = sideProcessorInfo[0];
CBORObject connectionIdentifierResponder = sideProcessorInfo[1];
CBORObject idCredR = sideProcessorInfo[2];
CBORObject peerCredentialCBOR = findValidPeerCredential(idCredR, ead2);
if (peerCredentialCBOR == null) {
addErrorResult(Constants.EDHOC_MESSAGE_2, false,
"Unable to retrieve a valid peer credential from ID_CRED_R",
ResponseCode.BAD_REQUEST.value);
return;
}
else {
HashMap<Integer, CBORObject> resultContent = new HashMap<Integer, CBORObject>();
resultContent.put(Integer.valueOf(Constants.SIDE_PROCESSOR_INNER_CRED_VALUE), peerCredentialCBOR);
addResult(Constants.EDHOC_MESSAGE_2, false, Constants.SIDE_PROCESSOR_OUTER_CRED, resultContent);
}
// Go through the EAD_2 items, if any
//
// For each EAD item, invoke the corresponding consume() method, and then addResult().
// Stop in case the consumption of an EAD item returns a fatal error.
//
// This may further trigger the production of new EAD items to include in the next, outgoing EDHOC message.
// In such a case, invoke eadProductionDispatcher() for each of those EAD items to produce.
//
// ...
//
}
/**
* Entry point for processing EAD items from EAD_2 after message verification
*
* @param sideProcessorInfo Information generally required for processing EAD_2
* @param ead2 The EAD items from EAD_2, including only items that the endpoint understands and excluding padding
*/
// sideProcessorInfo includes useful pieces information for processing EAD_2, in this order:
// 0) A CBOR byte string, with value G_Y
// 1) A CBOR byte string, with value C_R (in its original, binary format)
// 2) A CBOR map, as ID_CRED_R
public void sideProcessingMessage2PostVerification(CBORObject[] sideProcessorInfo, CBORObject[] ead2) {
CBORObject gY = sideProcessorInfo[0];
CBORObject connectionIdentifierResponder = sideProcessorInfo[1];
CBORObject idCredR = sideProcessorInfo[2];
// Go through the EAD_2 items, if any
//
// For each EAD item, invoke the corresponding consume() method, and then addResult().
// Stop in case the consumption of an EAD item returns a fatal error.
//
// This may further trigger the production of new EAD items to include in the next, outgoing EDHOC message.
// In such a case, invoke eadProductionDispatcher() for each of those EAD items to produce.
//
// ...
//
}
/**
* Entry point for processing EAD items from EAD_3 before message verification
*
* @param sideProcessorInfo Information generally required for processing EAD_3
* @param ead3 The EAD items from EAD_3, including only items that the endpoint understands and excluding padding
*/
// sideProcessorInfo includes useful pieces information for processing EAD_3, in this order:
// 0) A CBOR map, as ID_CRED_I
//
public void sideProcessingMessage3PreVerification(CBORObject[] sideProcessorInfo, CBORObject[] ead3) {
CBORObject idCredI = sideProcessorInfo[0];
CBORObject peerCredentialCBOR = findValidPeerCredential(idCredI, ead3);
if (peerCredentialCBOR == null) {
addErrorResult(Constants.EDHOC_MESSAGE_3, false,
"Unable to retrieve a valid peer credential from ID_CRED_I",
ResponseCode.BAD_REQUEST.value);
return;
}
else {
HashMap<Integer, CBORObject> resultContent = new HashMap<Integer, CBORObject>();
resultContent.put(Integer.valueOf(Constants.SIDE_PROCESSOR_INNER_CRED_VALUE), peerCredentialCBOR);
addResult(Constants.EDHOC_MESSAGE_3, false, Constants.SIDE_PROCESSOR_OUTER_CRED, resultContent);
}
// Go through the EAD_3 items, if any
//
// For each EAD item, invoke the corresponding consume() method, and then addResult().
// Stop in case the consumption of an EAD item returns a fatal error.
//
// This may further trigger the production of new EAD items to include in the next, outgoing EDHOC message.
// In such a case, invoke eadProductionDispatcher() for each of those EAD items to produce.
//
// ...
//
}
/**
* Entry point for processing EAD items from EAD_3 before message verification
*
* @param sideProcessorInfo Information generally required for processing EAD_3
* @param ead3 The EAD items from EAD_3, including only items that the endpoint understands and excluding padding
*/
// sideProcessorInfo includes useful pieces information for processing EAD_3, in this order:
// 0) A CBOR map, as ID_CRED_I
//
public void sideProcessingMessage3PostVerification(CBORObject[] sideProcessorInfo, CBORObject[] ead3) {
// Go through the EAD_3 items, if any
//
// For each EAD item, invoke the corresponding consume() method, and then addResult().
// Stop in case the consumption of an EAD item returns a fatal error.
//
// This may further trigger the production of new EAD items to include in the next, outgoing EDHOC message.
// In such a case, invoke eadProductionDispatcher() for each of those EAD items to produce.
//
// ...
//
}
/**
* Entry point for processing EAD items from EAD_4
*
* @param ead4 The EAD items from EAD_4, including only items that the endpoint understands and excluding padding
*/
public void sideProcessingMessage4(CBORObject[] ead4) {
// Go through the EAD_4 items, if any
//
// For each EAD item, invoke the corresponding consume() method, and then addResult().
// Stop in case the consumption of an EAD item returns a fatal error.
//
// This may further trigger the production of new EAD items to include in the next, outgoing EDHOC message.
// In such a case, invoke eadProductionDispatcher() for each of those EAD items to produce.
//
// ...
//
}
/**
* @param messageNumber The number of the outgoing EDHOC message that will include the EAD item
* @return False in case of malformed input, or true otherwise.
* This is not related to the correct/failed production of EAD items.
*/
public boolean produceIndependentEADs(int messageNumber) {
if (eadProductionInput == null || !eadProductionInput.containsKey(Integer.valueOf(messageNumber)))
return true;
List<CBORObject> myList = eadProductionInput.get(Integer.valueOf(messageNumber));
if ((myList.size() % 2) == 1)
return false;
int index = 0;
int size = myList.size();
while (index < size) {
if (myList.get(Integer.valueOf(index)).getType() != CBORType.Integer)
return false;
if (myList.get(Integer.valueOf(index + 1)).getType() != CBORType.Map)
return false;
boolean critical = false;
int eadLabel = myList.get(Integer.valueOf(index)).AsInt32();
if (eadLabel < 0) {
critical = true;
eadLabel = -eadLabel;
}
index++;
CBORObject productionInput = myList.get(Integer.valueOf(index));
CBORObject[] eadItem = eadProductionDispatcher(eadLabel, critical, messageNumber, productionInput);
if (eadItem[0].getType() != CBORType.Integer && eadItem[0].getType() != CBORType.TextString)
return false;
// A fatal error occurred while producing this EAD item
if (eadItem[0].getType() == CBORType.TextString) {
if (eadItem[1].getType() != CBORType.ByteString)
return false;
addErrorResult(messageNumber, true, eadItem[0].AsString(), eadItem[1].AsInt32());
break;
}
addProducedEAD(messageNumber, eadItem[0], eadItem[1]);
index++;
}
return true;
}
/**
* Invoke the produce() method of the right EAD item to produce
*
* @param eadLabel The ead_label of the EAD item to produce
* @param critical True if the EAD item has to be produced as critical, or false otherwise
* @param messageNumber The number of the next, outgoing EDHOC message that will include the produced EAD item
* @param input A CBOR map providing input on how to produce the EAD item. The map keys belong to a namespace specific of the ead_label.
* @return The same result returned by the produce() method of the specific EAD item to produce.
*/
public CBORObject[] eadProductionDispatcher(int eadLabel, boolean critical, int messageNumber, CBORObject input) {
// This has to be populated with the invocation of the produce() method for the EAD item to produce
switch(eadLabel) {
// CASE NNN:
// return EAD_NNN.produce(critical, messageNumber, productionInput);
}
return null; // placeholder, until the invocation to an actual produce() method is included above
}
public void showResultsFromSideProcessing(int messageNumber, boolean postValidation) {
HashMap<Integer, List<HashMap<Integer, CBORObject>>> myResults = whichResults(messageNumber, postValidation);
if (myResults.size() == 0)
return;
String myStr = new String("Results of side processing of message_" + messageNumber);
if (messageNumber == Constants.EDHOC_MESSAGE_2 || messageNumber == Constants.EDHOC_MESSAGE_3) {
myStr = (postValidation == false) ? (myStr + " before") : (myStr + " after");
myStr = myStr + " message verification";
}
System.out.println(myStr);
for (Integer i : myResults.keySet()) {
System.out.println("Processing result for the EAD item with ead_label: " + i.intValue());
List<HashMap<Integer, CBORObject>> myList = myResults.get(i);
// Print the processing results for each instance of this EAD item
for(HashMap<Integer, CBORObject> myMap : myList) {
for (Integer j : myMap.keySet()) {
CBORObject obj = myMap.get(j);
System.out.print("Result element #" + j.intValue() + ": " + obj.toString());
}
}
System.out.println("\n");
}
}
/**
* Look for an authentication credential of the other peer to use, by relying on
* the associated ID_CRED_X specified in the incoming EDHOC message_2 or message_3.
* This considers the trust model used by the endpoint for trusting new authentication credentials.
*
* @param idCredX The identifier of the peer's authentication credential specified in the incoming EDHOC message
* @param ead The EAD items specified in the incoming EDHOC message,
* including only items that the endpoint understands and excluding padding
* @return The peer's authentication credential wrapped into a CBOR byte string,
* or null in case a peer's authentication credential to use is not found.
*/
private CBORObject findValidPeerCredential(CBORObject idCredX, CBORObject[] ead) {
CBORObject peerCredentialCBOR = null;
if (peerCredentials.containsKey(idCredX)) {
peerCredentialCBOR = peerCredentials.get(idCredX);
// TODO: Check whether the authentication credential is still valid (for applicable credential types)
// TODO: Check whether the authentication credential is good to use in the context of this EDHOC session
}
else if (trustModel == Constants.TRUST_MODEL_STRICT) {
return peerCredentialCBOR;
}
// TODO: Add support for the alternative trust models
return peerCredentialCBOR;
}
}