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 org.eclipse.californium.cose.OneKey;
import org.eclipse.californium.elements.util.StringUtil;
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
private HashMap<CBORObject, OneKey> peerPublicKeys = new HashMap<CBORObject, OneKey>();
// 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>>();
// This data structure collects the number of occurrences of EAD items in different EDHOC messages
//
// The outer map key is the EAD label
//
// The inner map key is a value from (1, 2, 3, 4), denoting one of the four EDHOC messages
// The inner map value is the number of times that the EAD item with that EAD label has occurred in that EDHOC message
private HashMap<Integer, HashMap<Integer, Integer>> eadItemsOccurrences = new HashMap<Integer, HashMap<Integer, Integer>>();
public SideProcessor(int trustModel, HashMap<CBORObject, OneKey> peerPublicKeys,
HashMap<CBORObject, CBORObject> peerCredentials,
HashMap<Integer, List<CBORObject>> eadProductionInput) {
this.trustModel = trustModel;
this.peerPublicKeys = peerPublicKeys;
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() {
removeResults(Constants.EDHOC_MESSAGE_1, false);
removeResults(Constants.EDHOC_MESSAGE_2, false);
removeResults(Constants.EDHOC_MESSAGE_2, true);
removeResults(Constants.EDHOC_MESSAGE_3, false);
removeResults(Constants.EDHOC_MESSAGE_3, true);
removeResults(Constants.EDHOC_MESSAGE_4, false);
}
/**
* 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);
for (Integer index : myResults.keySet()) {
eadSpecificCleanup(myResults, index.intValue());
}
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;
eadSpecificCleanup(myResults, keyValue);
myResults.remove(Integer.valueOf(keyValue));
}
/**
* Contextually with the deletion of the results from the processing
* of an EAD item, perform cleanup actions specific to that EAD item,
*
* @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
*/
private void eadSpecificCleanup(HashMap<Integer, List<HashMap<Integer, CBORObject>>> myResults, final int eadLabel) {
List<HashMap<Integer, CBORObject>> resultList = myResults.get(Integer.valueOf(eadLabel));
if (resultList == null) {
return;
}
CBORObject peerCred = null;
CBORObject ownCred = null;
/*
* Template for each entry
*
if (eadLabel == Constants.EAD_LABEL_TBD) {
// TBD
}
*/
}
/**
* 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.
//
// ...
//
if (ead1 != null && ead1.length > 0) {
if (eadConsumptionDispatcher(org.eclipse.californium.edhoc.Constants.EDHOC_MESSAGE_1, false, sideProcessorInfo, ead1) == false) {
return;
}
}
}
/**
* 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) {
// 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.
//
// ...
//
if (ead2 != null && ead2.length > 0) {
if (eadConsumptionDispatcher(org.eclipse.californium.edhoc.Constants.EDHOC_MESSAGE_2, false, sideProcessorInfo, ead2) == false) {
return;
}
}
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);
}
}
/**
* 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.
//
// ...
//
if (ead2 != null && ead2.length > 0) {
if (eadConsumptionDispatcher(org.eclipse.californium.edhoc.Constants.EDHOC_MESSAGE_2, true, sideProcessorInfo, ead2) == false) {
return;
}
}
}
/**
* 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) {
// 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.
//
// ...
//
if (ead3 != null && ead3.length > 0) {
if (eadConsumptionDispatcher(org.eclipse.californium.edhoc.Constants.EDHOC_MESSAGE_3, false, sideProcessorInfo, ead3) == false) {
return;
}
}
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);
}
}
/**
* 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.
//
// ...
//
if (ead3 != null && ead3.length > 0) {
if (eadConsumptionDispatcher(org.eclipse.californium.edhoc.Constants.EDHOC_MESSAGE_3, true, sideProcessorInfo, ead3) == false) {
return;
}
}
}
/**
* 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.
//
// ...
//
if (ead4 != null && ead4.length > 0) {
if (eadConsumptionDispatcher(org.eclipse.californium.edhoc.Constants.EDHOC_MESSAGE_4, false, null, ead4) == false) {
return;
}
}
}
/**
* @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);
// The production of this EAD item is actually not supported. Silently continue.
if (eadItem == null) {
continue;
}
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.Integer)
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
}
/**
* Invoke the consume() method of the right EAD item to consume
*
* Due to early parsing of the EAD field when processing the EDHOC message, an EAD item considered here is always supported
*
* @param messageNumber The number of the incoming EDHOC message that includes the EAD item to consume
* @param postValidation True to indicate EAD processing after EDHOC message validation, or false otherwise
* @param sideProcessorInfo Information generally required for processing the EAD field. It can be null, when processing the EAD_4 field
* @param eadField The EAD field from the incoming EDHOC message
* @return True in case of no error when processing any critical item, in order to continue the EDHOC session can continue
* False in case of error when processing any critical item, in order to abort the EDHOC session
*/
public boolean eadConsumptionDispatcher(int messageNumber, boolean postValidation, CBORObject[] sideProcessorInfo, CBORObject[] eadField) {
int index = 0;
boolean success = true;
while (index < eadField.length) {
int eadLabel = eadField[index].AsInt32();
byte[] eadValue = null;
index++;
if ((index < eadField.length) && ((eadField[index].getType()) == CBORType.ByteString)) {
eadValue = eadField[index].GetByteString();
index++;
}
boolean critical = false;
if (eadLabel < 0) {
critical = true;
eadLabel = -eadLabel;
}
if (eadItemsOccurrences.containsKey(Integer.valueOf(eadLabel)) == false) {
HashMap<Integer, Integer> innerMap = new HashMap<Integer, Integer>();
innerMap.put(Integer.valueOf(Constants.EDHOC_MESSAGE_1), Integer.valueOf(0));
innerMap.put(Integer.valueOf(Constants.EDHOC_MESSAGE_2), Integer.valueOf(0));
innerMap.put(Integer.valueOf(Constants.EDHOC_MESSAGE_3), Integer.valueOf(0));
innerMap.put(Integer.valueOf(Constants.EDHOC_MESSAGE_4), Integer.valueOf(0));
eadItemsOccurrences.put(Integer.valueOf(eadLabel), innerMap);
}
// This has to be populated with the invocation of the consume() method for the EAD item to produce
switch(eadLabel) {
/*
Template case
case Constants.EAD_LABEL_TBD:
if (postValidation == false) {
// This EAD item is intended to be processed only before validating the peer's authentication credential
success = eadConsumeTBD(critical, messageNumber, postValidation, sideProcessorInfo, eadValue);
}
break;
*/
}
if (success == false) {
break;
}
}
return success;
}
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.println("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) {
boolean newCredential = true;
CBORObject peerCredentialContainer = null;
CBORObject peerCredentialCBOR = null;
if (peerCredentials.containsKey(idCredX)) {
newCredential = false;
peerCredentialContainer = peerCredentials.get(idCredX);
peerCredentialCBOR = CBORObject.DecodeFromBytes(peerCredentialContainer.GetByteString());
}
if (peerCredentialContainer == null) {
// CRED_X was not found among the stored authentication credentials.
// Then, ID_CRED_X has to specify CRED_X by value.
Set<CBORObject> credTypesForCredByValue = new HashSet<>();
credTypesForCredByValue.add(CBORObject.FromObject(Constants.COSE_HEADER_PARAM_KCWT));
credTypesForCredByValue.add(CBORObject.FromObject(Constants.COSE_HEADER_PARAM_KCCS));
credTypesForCredByValue.add(CBORObject.FromObject(Constants.COSE_HEADER_PARAM_X5CHAIN));
boolean credByValue = false;
for (CBORObject obj : idCredX.getKeys()) {
if (credTypesForCredByValue.contains(obj)) {
peerCredentialCBOR = idCredX.get(obj);
credByValue = true;
break;
}
}
if (credByValue == false) {
// ID_CRED_X does not transport CRED_X by value
// Check for any relevant EAD items that transport the authentication credential by value
return null;
}
if (trustModel == Constants.TRUST_MODEL_NO_LEARNING) {
// Only already known CRED_X are admitted to use
// Admit potential exception for well-defined circumstances
System.err.println("New authentication credentials cannot be learned during an EDHOC session");
return null;
}
}
int credentialType = -1;
if (idCredX.getKeys().contains(CBORObject.FromObject(Constants.COSE_HEADER_PARAM_KID))) {
if (peerCredentialCBOR.getType().equals(CBORType.Array)) {
credentialType = Constants.CRED_TYPE_CWT;
}
if (peerCredentialCBOR.getType().equals(CBORType.Map)) {
credentialType = Constants.CRED_TYPE_CCS;
}
}
if (idCredX.getKeys().contains(CBORObject.FromObject(Constants.COSE_HEADER_PARAM_KCWT))) {
credentialType = Constants.CRED_TYPE_CWT;
}
if (idCredX.getKeys().contains(CBORObject.FromObject(Constants.COSE_HEADER_PARAM_KCCS))) {
credentialType = Constants.CRED_TYPE_CCS;
}
if (idCredX.getKeys().contains(CBORObject.FromObject(Constants.COSE_HEADER_PARAM_X5CHAIN)) ||
idCredX.getKeys().contains(CBORObject.FromObject(Constants.COSE_HEADER_PARAM_X5T)) ||
idCredX.getKeys().contains(CBORObject.FromObject(Constants.COSE_HEADER_PARAM_X5U))) {
credentialType = Constants.CRED_TYPE_X509;
}
if (credentialType < 0) {
return null;
}
// Check whether the authentication credential is valid (for applicable credential types)
boolean validCred = false;
switch(credentialType) {
case Constants.CRED_TYPE_CWT:
validCred = validateCWT(peerCredentialCBOR, newCredential);
if (validCred && newCredential) {
if (storeNewCWT(peerCredentialCBOR) == false) {
return null;
}
}
break;
case Constants.CRED_TYPE_CCS:
validCred = validateCCS(peerCredentialCBOR, newCredential);
if (validCred && newCredential) {
if (storeNewCCS(peerCredentialCBOR) == false) {
return null;
}
}
break;
case Constants.CRED_TYPE_X509:
validCred = validateX5chain(peerCredentialCBOR, newCredential);
if (validCred && newCredential) {
if (storeNewX509(peerCredentialCBOR) == false) {
return null;
}
}
break;
}
if (validCred == false) {
if (newCredential == false) {
// Remove all the stored entries for the authentication credential corresponding public key
this.peerCredentials.remove(idCredX);
this.peerPublicKeys.remove(idCredX);
for (CBORObject key : this.peerCredentials.keySet()) {
if (this.peerCredentials.get(key).equals(peerCredentialContainer)) {
this.peerCredentials.remove(key);
this.peerPublicKeys.remove(key);
}
}
}
return null;
}
if (peerCredentialContainer == null) {
// If this point is reached, the authentication credential is valid and learned now.
// The container to return was stored in the appropriate data structure and can be retrieved from there.
peerCredentialContainer = this.peerCredentials.get(idCredX);
}
// TODO: Check whether the authentication credential is good to use in the context of this EDHOC session
return peerCredentialContainer;
}
/**
* Store a CWT as the authentication credential of another peer,
* together with the corresponding public key specified therein
*
* @param cwt The CWT as a CBOR array
* @return True if the storing succeeds, or false otherwise.
*/
private boolean storeNewCWT(CBORObject cwt) {
// Store two entries, using the COSE Header Parameters 'kcwt' and 'kid', thus allowing
// a retrieval in case a later ID_CRED_X specifies the credential by value or by reference
// TBD
return true;
}
/**
* Store a CCS as the authentication credential of another peer,
* together with the corresponding public key specified therein
*
* @param ccs The CCS as a CBOR map
* @return True if the storing succeeds, or false otherwise.
*/
private boolean storeNewCCS(CBORObject ccs) {
// Store two entries, using the COSE Header Parameters 'kccs' and 'kid', thus allowing
// a retrieval in case a later ID_CRED_X specifies the credential by value or by reference
OneKey peerPublicKey = null;
CBORObject coseKey = ccs.get(CBORObject.FromObject(Constants.CWT_CLAIMS_CNF)).
get(CBORObject.FromObject(Constants.CWT_CNF_COSE_KEY));
int curve = 0;
int keyType = coseKey.get(Constants.COSE_KEY_COMMON_PARAM_KTY).AsInt32();
if (keyType == Constants.COSE_KEY_TYPE_OKP || keyType == Constants.COSE_KEY_TYPE_EC2) {
curve = coseKey.get(Constants.COSE_KEY_TYPE_PARAM_CRV).AsInt32();
byte[] x = null;
byte[] y = null;
x = coseKey.get(Constants.COSE_KEY_TYPE_PARAM_X).GetByteString();
if (keyType == Constants.COSE_KEY_TYPE_EC2) {
y = coseKey.get(Constants.COSE_KEY_TYPE_PARAM_Y).GetByteString();
}
if (curve == Constants.CURVE_X25519) {
peerPublicKey = SharedSecretCalculation.buildCurve25519OneKey(null, x);
}
if (curve == Constants.CURVE_Ed25519) {
peerPublicKey = SharedSecretCalculation.buildEd25519OneKey(null, x);
}
if (curve == Constants.CURVE_P256) {
peerPublicKey = SharedSecretCalculation.buildEcdsa256OneKey(null, x, y);
}
if (peerPublicKey == null) {
return false;
}
}
CBORObject peerCredentialContainer = CBORObject.FromObject(ccs.EncodeToBytes());
CBORObject idCredKccs = Util.buildIdCredKccs(ccs);
peerPublicKeys.put(idCredKccs, peerPublicKey);
peerCredentials.put(idCredKccs, peerCredentialContainer);
// If the COSE Key specifies 'kid', store one additional entry identified by the 'kid' value
if (coseKey.ContainsKey(Constants.COSE_KEY_COMMON_PARAM_KID)) {
CBORObject kidCBOR = coseKey.get(Constants.COSE_KEY_COMMON_PARAM_KID);
if (kidCBOR.getType().equals(CBORType.ByteString)) {
byte[] kid = coseKey.get(Constants.COSE_KEY_COMMON_PARAM_KID).GetByteString();
CBORObject idCredKid = Util.buildIdCredKid(kid);
peerPublicKeys.put(idCredKid, peerPublicKey);
peerCredentials.put(idCredKid, peerCredentialContainer);
}
}
return true;
}
/**
* Store an X.509 certificate as the authentication credential of another peer,
* together with the corresponding public key specified therein.
*
* Note that only the end-entity certificate associated with the other peer is considered.
*
* @param cwt A CBOR byte string with value an end-entity X.509 certificate
* @return True if the storing succeeds, or false otherwise.
*/
private boolean storeNewX509(CBORObject x509) {
// Store two entries, using the COSE Header Parameters 'x5chain' and 'x5t', thus allowing
// a retrieval in case a later ID_CRED_X specifies the credential by value or by reference
// TBD
return true;
}
/**
* Determine whether a CWT is valid or not
*
* @param cwt The CWT as a CBOR array
* @param newCredential True if the CWT was not already stored when invoking this method, or false otherwise
* @return True if the CWT is valid, or false otherwise.
*/
private boolean validateCWT(final CBORObject cwt, final boolean newCredential) {
if (newCredential) {
// The credential is new, so more thorough checks are required
if (cwt.getType().equals(CBORType.Array) == false) {
return false;
}
// TBD
}
// TBD
return true;
}
/**
* Determine whether a CCS is valid or not
*
* @param ccs The CCS as a CBOR map
* @param newCredential True if the CCS was not already stored when invoking this method, or false otherwise
* @return True if the CCS is valid, or false otherwise.
*/
private boolean validateCCS(final CBORObject ccs, final boolean newCredential) {
if (newCredential) {
// The credential is new, so more thorough checks are required
if (ccs.getType().equals(CBORType.Map) == false) {
return false;
}
if (ccs.ContainsKey(CBORObject.FromObject(Constants.CWT_CLAIMS_CNF)) == false) {
return false;
}
CBORObject cnfValue = ccs.get(CBORObject.FromObject(Constants.CWT_CLAIMS_CNF));
if (cnfValue.getType().equals(CBORType.Map) == false) {
return false;
}
if (cnfValue.ContainsKey(CBORObject.FromObject(Constants.CWT_CNF_COSE_KEY)) == false) {
return false;
}
CBORObject coseKeyValue = cnfValue.get(CBORObject.FromObject(Constants.CWT_CNF_COSE_KEY));
if (checkCoseKey(coseKeyValue) == false) {
return false;
}
}
if (ccs.ContainsKey(Constants.CWT_CLAIMS_EXP)) {
Long expValue = ccs.get(Constants.CWT_CLAIMS_EXP).AsInt64Value();
if (expValue < (System.currentTimeMillis() / 1000)) {
// The credential is expired
return false;
}
}
return true;
}
/**
* Determine whether an end-entity X.509 certificate is valid or not
*
* @param x5chain A CBOR byte string with value the serialization of an x5chain.
* - If the credential is not new, the value of the CBOR byte string is the binary encoding
* of a CBOR byte string, whose value is the end-entity X.509 certificate of the other peer
* - If the credential is new, the value of the CBOR byte string is the binary encoding
* of a chain of X.509 certificates, i.e., either:
* - The binary encoding of a CBOR byte string, whose value is the end-entity X.509 certificate of the other peer; or
* - The binary encoding of a CBOR array. Each element of the array is a CBOR byte string, whose value
* is an X.509 certificate. The first element corresponds to the end-entity X.509 certificate of the other peer.
*
* @param newCredential True if the end-entity X.509 certificate was not already stored
* when invoking this method, or false otherwise
* @return True if the end-entity X.509 certificate is valid, or false otherwise.
*/
private boolean validateX5chain(final CBORObject x5chain, final boolean newCredential) {
if (newCredential) {
// The credential is new, so more thorough checks are required
CBORType cborType = x5chain.getType();
if ((cborType.equals(CBORType.ByteString) == false) && (cborType.equals(CBORType.Array) == false)) {
return false;
}
if (cborType.equals(CBORType.Array)) {
int size = x5chain.size();
if (size < 2) {
return false;
}
for (int i = 0; i < size; i++) {
if (x5chain.get(i).getType().equals(CBORType.ByteString) == false) {
return false;
}
}
}
// TBD
}
// TBD
return true;
}
/**
* Check whether a COSE Key is well-formed
*
* This method does not perform cryptographic-relevant validation (e.g., correctness
* of the public key coordinates), which is left to later invocation of the COSE library
*
* @param coseKey The COSE Key as a CBOR map
* @return True if the COSE Key is well-formed, or false otherwise.
*/
private boolean checkCoseKey(final CBORObject coseKey) {
if (coseKey.getType().equals(CBORType.Map) == false) {
return false;
}
if (coseKey.ContainsKey(CBORObject.FromObject(Constants.COSE_KEY_COMMON_PARAM_KTY)) == false) {
return false;
}
if (coseKey.get(CBORObject.FromObject(Constants.COSE_KEY_COMMON_PARAM_KTY)).getType().equals(CBORType.Integer) == false) {
return false;
}
int curve = 0;
int keyType = coseKey.get(CBORObject.FromObject(Constants.COSE_KEY_COMMON_PARAM_KTY)).AsInt32();
if ((keyType == Constants.COSE_KEY_TYPE_OKP) || (keyType == Constants.COSE_KEY_TYPE_EC2)) {
if (coseKey.ContainsKey(CBORObject.FromObject(Constants.COSE_KEY_TYPE_PARAM_CRV)) == false ||
coseKey.ContainsKey(CBORObject.FromObject(Constants.COSE_KEY_TYPE_PARAM_X)) == false) {
return false;
}
if (coseKey.get(CBORObject.FromObject(Constants.COSE_KEY_TYPE_PARAM_CRV)).getType().equals(CBORType.Integer) == false) {
return false;
}
if (coseKey.get(CBORObject.FromObject(Constants.COSE_KEY_TYPE_PARAM_X)).getType().equals(CBORType.ByteString) == false) {
return false;
}
curve = coseKey.get(CBORObject.FromObject(Constants.COSE_KEY_TYPE_PARAM_CRV)).AsInt32();
}
else {
return false;
}
if (keyType == Constants.COSE_KEY_TYPE_OKP) {
if (curve != Constants.CURVE_X25519 && curve != Constants.CURVE_Ed25519) {
return false;
}
}
if (keyType == Constants.COSE_KEY_TYPE_EC2) {
if (curve != Constants.CURVE_P256) {
return false;
}
if (coseKey.ContainsKey(CBORObject.FromObject(Constants.COSE_KEY_TYPE_PARAM_Y)) == false) {
return false;
}
if (coseKey.get(CBORObject.FromObject(Constants.COSE_KEY_TYPE_PARAM_Y)).getType().equals(CBORType.ByteString) == false) {
return false;
}
}
return true;
}
/*
* After successfully completing an EDHOC session, perform follow-up actions related to EAD items provided in the session
*/
public void eadProcessingFollowUp() {
for (Integer i : this.resMessage1.keySet()) {
// If processing results for a certain EAD item are present, invoke the
// corresponding method to perform follow-up actions based on those
}
for (Integer i : this.resMessage2Pre.keySet()) {
// If processing results for a certain EAD item are present, invoke the
// corresponding method to perform follow-up actions based on those
}
for (Integer i : this.resMessage2Post.keySet()) {
// If processing results for a certain EAD item are present, invoke the
// corresponding method to perform follow-up actions based on those
}
for (Integer i : this.resMessage3Pre.keySet()) {
// If processing results for a certain EAD item are present, invoke the
// corresponding method to perform follow-up actions based on those
}
for (Integer i : this.resMessage3Post.keySet()) {
// If processing results for a certain EAD item are present, invoke the
// corresponding method to perform follow-up actions based on those
}
for (Integer i : this.resMessage4.keySet()) {
// If processing results for a certain EAD item are present, invoke the
// corresponding method to perform follow-up actions based on those
}
}
}