ClientEdhocExecutor.java
/*******************************************************************************
* Copyright (c) 2020 RISE 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.
*
* This class is based on org.eclipse.californium.examples.HelloWorldServer
*
* Contributors:
* Marco Tiloca (RISE)
* Rikard Höglund (RISE)
*
******************************************************************************/
package org.eclipse.californium.edhoc;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import org.eclipse.californium.core.CoapClient;
import org.eclipse.californium.core.CoapResponse;
import org.eclipse.californium.core.Utils;
import org.eclipse.californium.core.coap.Request;
import org.eclipse.californium.core.coap.CoAP.Code;
import org.eclipse.californium.core.coap.CoAP.ResponseCode;
import org.eclipse.californium.core.coap.CoAP.Type;
import org.eclipse.californium.cose.AlgorithmID;
import org.eclipse.californium.cose.OneKey;
import org.eclipse.californium.elements.exception.ConnectorException;
import org.eclipse.californium.elements.util.Bytes;
import org.eclipse.californium.oscore.OSCoreCtx;
import org.eclipse.californium.oscore.OSException;
import com.upokecenter.cbor.CBORObject;
import com.upokecenter.cbor.CBORType;
public class ClientEdhocExecutor {
private final boolean debugPrint = true;
// Used to store the created EDHOC session, to be accessible for the application
private EdhocSession edhocSession = null;
// Used to store the list of EDHOC cipher suites supported by the other peer, as learned
// from an EDHOC error message with ERR_CODE = 1 received as a reply to EDHOC message_1
List<Integer> learnedPeerSupportedCipherSuites = new ArrayList<Integer>();
// Used to store the application response to the EDHOC+OSCORE combined request, to be accessible for the application
private CoapResponse appResponseToCombinedRequest = null;
// Simpler version, for when the client does not use the EDHOC + OSCORE combined request
/**
* Start EDHOC as a CoAP client, i.e., by sending EDHOC message_1 as a CoAP request
*
* @param authenticationMethod The authentication method to include in EDHOC message_1
* @param peerSupportedCipherSuites The EDHOC cipher suites supported by the other peer, as far as this peer knows
* @param ownIdCreds Each element is the ID_CRED_X used for an authentication credential associated to this peer
* @param edhocEndpointInfo The set of information for this EDHOC endpoint
* @return The result from this EDHOC execution.
* When EDHOC is used for OSCORE, it is true if EDHOC has completed successfully and the
* OSCORE Security Context has been correctly derived and installed. Otherwise, it is false.
* When EDHOC is not used for OSCORE, it is true if EDHOC has completed successfully. Otherwise, it is false.
*/
public boolean startEdhocExchangeAsInitiator(final int authenticationMethod, List<Integer> peerSupportedCipherSuites,
final Set<CBORObject> ownIdCreds, EdhocEndpointInfo edhocEndpointInfo) {
return startEdhocExchangeAsInitiator(authenticationMethod, peerSupportedCipherSuites, ownIdCreds,
edhocEndpointInfo, false, null, null, null, null);
}
// Extended version, for controlling the use of the EDHOC + OSCORE combined request
/**
* Start EDHOC as a CoAP client, i.e., by sending EDHOC message_1 as a CoAP request
*
* @param authenticationMethod The authentication method to include in EDHOC message_1
* @param peerSupportedCipherSuites The EDHOC cipher suites supported by the other peer, as far as this peer knows
* @param ownIdCreds Each element is the ID_CRED_X used for an authentication credential associated to this peer
* @param edhocEndpointInfo The set of information for this EDHOC endpoint
* @param OSCORE_EDHOC_COMBINED True if the EDHOC + OSCORE combined request has to be used, or false otherwise
* @param edhocCombinedRequestURI URI of the application resource to target with the EDHOC + OSCORE combined request
* @param combinedRequestAppCode CoAP method to use for the application request sent within
* an EDHOC + OSCORE combined request
* @param combinedRequestAppType CoAP message type to use (CON or NON) for the application request
* sent within an EDHOC + OSCORE combined request
* @param combinedRequestAppPayload Payload of the application request sent within
* an EDHOC + OSCORE combined request. It can be null
* @return The result from this EDHOC execution.
* When EDHOC is used for OSCORE, it is true if EDHOC has completed successfully and the
* OSCORE Security Context has been correctly derived and installed. Otherwise, it is false.
* When EDHOC is not used for OSCORE, it is true if EDHOC has completed successfully. Otherwise, it is false.
*/
public boolean startEdhocExchangeAsInitiator(final int authenticationMethod, List<Integer> peerSupportedCipherSuites,
final Set<CBORObject> ownIdCreds, EdhocEndpointInfo edhocEndpointInfo,
boolean OSCORE_EDHOC_COMBINED, String edhocCombinedRequestURI,
Code combinedRequestAppCode, Type combinedRequestAppType,
byte[] combinedRequestAppPayload) {
HashMap<CBORObject, EdhocSession> edhocSessions = edhocEndpointInfo.getEdhocSessions();
Set<CBORObject> usedConnectionIds = edhocEndpointInfo.getUsedConnectionIds();
HashMap<CBORObject, OneKey> peerPublicKeys = edhocEndpointInfo.getPeerPublicKeys();
HashMap<CBORObject, CBORObject> peerCredentials = edhocEndpointInfo.getPeerCredentials();
String edhocURI = edhocEndpointInfo.getUri();
AppProfile appProfile = edhocEndpointInfo.getAppProfiles().get(edhocURI);
URI targetUri = null;
try {
targetUri = new URI(edhocURI);
} catch (URISyntaxException e) {
System.err.println("Invalid URI: " + e.getMessage());
return false;
}
CoapClient client = new CoapClient(targetUri);
/*
// Simple sending of a GET request
CoapResponse response = null;
try {
response = client.get();
} catch (ConnectorException | IOException e) {
System.err.println("Got an error: " + e);
}
if (response != null) {
System.out.println(response.getCode());
System.out.println(response.getOptions());
if (args.length > 1) {
try (FileOutputStream out = new FileOutputStream(args[1])) {
out.write(response.getPayload());
} catch (IOException e) {
System.err.println("Error while writing the response payload to file: " + e.getMessage());
}
} else {
System.out.println(response.getResponseText());
System.out.println(System.lineSeparator() + "ADVANCED" + System.lineSeparator());
// access advanced API with access to more details through
// .advanced()
System.out.println(Utils.prettyPrint(response));
}
} else {
System.out.println("No response received.");
}
*/
// Simple test with a dummy payload
/*
byte[] requestPayload = { (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03 };
Request edhocMessage1 = new Request(Code.POST, Type.CON);
edhocMessage1.setPayload(requestPayload);
// Submit the request
System.out.println("\nSent EDHOC Message1\n");
CoapResponse edhocMessage2;
try {
edhocMessage2 = client.advanced(edhocMessage1);
} catch (ConnectorException e) {
System.err.println("ConnectorException when sending EDHOC Message1");
return;
} catch (IOException e) {
System.err.println("IOException when sending EDHOC Message1");
return;
}
byte[] responsePayload = edhocMessage2.getPayload();
System.out.println("\nResponse: " + new String(responsePayload) + "\n");
*/
/* Prepare and send EDHOC Message 1 */
EdhocSession session = MessageProcessor.createSessionAsInitiator(authenticationMethod,
edhocEndpointInfo.getKeyPairs(),
edhocEndpointInfo.getIdCreds(),
edhocEndpointInfo.getCreds(),
edhocEndpointInfo.getSupportedCipherSuites(),
peerSupportedCipherSuites,
edhocEndpointInfo.getSupportedEADs(),
edhocEndpointInfo.getEadProductionInput(),
edhocEndpointInfo.getUsedConnectionIds(),
appProfile, edhocEndpointInfo.getTrustModel(),
edhocEndpointInfo.getOscoreDb());
SideProcessor sideProcessor = new SideProcessor(edhocEndpointInfo.getTrustModel(),
edhocEndpointInfo.getPeerCredentials(),
edhocEndpointInfo.getEadProductionInput());
// Provide the side processor object with the just created EDHOC session.
// A reference to the sideProcessor is also going to be stored in the EDHOC session.
sideProcessor.setEdhocSession(session);
// Store a reference to the EDHOC session, to be accessible for the application after EDHOC completion
this.edhocSession = session;
// At this point, the initiator may overwrite the information in the EDHOC session about the supported cipher suites
// and the selected cipher suite, based on a previously received EDHOC Error Message
byte[] nextPayload = MessageProcessor.writeMessage1(session);
if (nextPayload == null || session.getCurrentStep() != Constants.EDHOC_BEFORE_M1) {
System.err.println("Inconsistent state before sending EDHOC Message 1");
session.deleteTemporaryMaterial();
session = null;
client.shutdown();
return false;
}
// Add the new session to the list of existing EDHOC sessions
session.setCurrentStep(Constants.EDHOC_AFTER_M1);
// Compute and store the hash of EDHOC Message 1
// The first byte 0xf5 sent in the CoAP request must be skipped
byte[] hashInput = new byte[nextPayload.length - 1];
System.arraycopy(nextPayload, 1, hashInput, 0, hashInput.length);
session.setHashMessage1(hashInput);
byte[] connectionIdentifier = session.getConnectionId();
CBORObject connectionIdentifierCbor = CBORObject.FromObject(connectionIdentifier);
edhocSessions.put(connectionIdentifierCbor, session);
Request edhocMessageReq = new Request(Code.POST, Type.CON);
edhocMessageReq.getOptions().setContentFormat(Constants.APPLICATION_CID_EDHOC_CBOR_SEQ);
edhocMessageReq.setPayload(nextPayload);
System.out.println("Sent EDHOC Message 1\n");
CoapResponse edhocMessageResp;
try {
session.setCurrentStep(Constants.EDHOC_SENT_M1);
edhocMessageResp = client.advanced(edhocMessageReq);
} catch (ConnectorException e) {
System.err.println("ConnectorException when sending EDHOC Message 1");
Util.purgeSession(session, connectionIdentifier, edhocSessions, usedConnectionIds);
client.shutdown();
return false;
} catch (IOException e) {
System.err.println("IOException when sending EDHOC Message 1");
Util.purgeSession(session, connectionIdentifier, edhocSessions, usedConnectionIds);
client.shutdown();
return false;
}
boolean discontinue = false;
int responseType = -1;
byte[] responsePayload = null;
if (edhocMessageResp != null)
responsePayload = edhocMessageResp.getPayload();
if (responsePayload == null) {
discontinue = true;
}
else {
responseType = MessageProcessor.messageType(responsePayload, false, edhocSessions, connectionIdentifier);
if (responseType != Constants.EDHOC_MESSAGE_2 && responseType != Constants.EDHOC_ERROR_MESSAGE)
discontinue = true;
}
if (discontinue == true) {
System.err.println("Received invalid reply to EDHOC Message 1");
Util.purgeSession(session, connectionIdentifier, edhocSessions, usedConnectionIds);
client.shutdown();
return false;
}
String myString = (responseType == Constants.EDHOC_MESSAGE_2) ? "EDHOC Message 2" : "EDHOC Error Message";
System.out.println("Determined EDHOC message type: " + myString + "\n");
Util.nicePrint("EDHOC message " + responseType, responsePayload);
/* Process the received response */
// This response relates to the previous request through the CoAP Token.
// Hence, the Initiator knows what session to refer to, from which the correct C_I can be retrieved
nextPayload = new byte[] {};
// The received message is an EDHOC Error Message
if (responseType == Constants.EDHOC_ERROR_MESSAGE) {
CBORObject[] objectList = MessageProcessor.readErrorMessage(responsePayload, connectionIdentifier, edhocSessions);
processErrorMessageAsResponse(objectList, Constants.EDHOC_MESSAGE_1);
Util.purgeSession(session, connectionIdentifier, edhocSessions, usedConnectionIds);
client.shutdown();
return false;
}
// The received message is an EDHOC Message 2
if (responseType == Constants.EDHOC_MESSAGE_2) {
OSCoreCtx ctx = null;
List<CBORObject> processingResult = new ArrayList<CBORObject>();
/* Start handling EDHOC Message 2 */
processingResult = MessageProcessor.readMessage2(responsePayload, false, connectionIdentifier, edhocSessions,
peerPublicKeys, peerCredentials, usedConnectionIds, ownIdCreds);
if (processingResult.get(0) == null || processingResult.get(0).getType() != CBORType.ByteString) {
System.err.println("Error when processing EDHOC Message 2");
Util.purgeSession(session, connectionIdentifier, edhocSessions, usedConnectionIds);
client.shutdown();
return false;
}
// A non-zero length response payload would be an EDHOC Error Message
nextPayload = processingResult.get(0).GetByteString();
// Prepare EDHOC Message 3
if (nextPayload.length == 0) {
session.setCurrentStep(Constants.EDHOC_AFTER_M2);
nextPayload = MessageProcessor.writeMessage3(session);
if (nextPayload == null || session.getCurrentStep() != Constants.EDHOC_AFTER_M3) {
System.err.println("Inconsistent state before sending EDHOC Message 3");
Util.purgeSession(session, connectionIdentifier, edhocSessions, usedConnectionIds);
client.shutdown();
return false;
}
}
int requestType = MessageProcessor.messageType(nextPayload, true, edhocSessions, connectionIdentifier);
if (requestType != Constants.EDHOC_MESSAGE_3 && requestType != Constants.EDHOC_ERROR_MESSAGE) {
System.err.println("Error when producing EDHOC message_3");
Util.purgeSession(session, connectionIdentifier, edhocSessions, usedConnectionIds);
client.shutdown();
return false;
}
myString = (requestType == Constants.EDHOC_MESSAGE_3) ? "EDHOC Message 3" : "EDHOC Error Message";
if (requestType == Constants.EDHOC_MESSAGE_3) {
System.out.println("Sent EDHOC Message 3\n");
if (session.getApplicationProfile().getUsedForOSCORE() == true) {
/* Invoke the EDHOC-Exporter to produce OSCORE input material */
byte[] masterSecret = EdhocSession.getMasterSecretOSCORE(session);
byte[] masterSalt = EdhocSession.getMasterSaltOSCORE(session);
if (debugPrint) {
Util.nicePrint("OSCORE Master Secret", masterSecret);
Util.nicePrint("OSCORE Master Salt", masterSalt);
}
/* Setup the OSCORE Security Context */
// The Sender ID of this peer is the EDHOC connection identifier of the other peer
byte[] senderId = session.getPeerConnectionId();
int selectedCipherSuite = session.getSelectedCipherSuite();
AlgorithmID alg = EdhocSession.getAppAEAD(selectedCipherSuite);
AlgorithmID hkdf = EdhocSession.getAppHkdf(selectedCipherSuite);
byte[] recipientId = connectionIdentifier;
if (Arrays.equals(senderId, recipientId)) {
System.err.println("Error: the Sender ID coincides with the Recipient ID " + Utils.toHexString(senderId));
Util.purgeSession(session, connectionIdentifier, edhocSessions, usedConnectionIds);
client.shutdown();
return false;
}
try {
int OSCORE_REPLAY_WINDOW = edhocEndpointInfo.getOscoreReplayWindow();
int MAX_UNFRAGMENTED_SIZE = edhocEndpointInfo.getOscoreMaxUnfragmentedSize();
ctx = new OSCoreCtx(masterSecret, true, alg, senderId, recipientId, hkdf,
OSCORE_REPLAY_WINDOW, masterSalt, null, MAX_UNFRAGMENTED_SIZE);
} catch (OSException e) {
System.err.println("Error when deriving the OSCORE Security Context " + e.getMessage());
Util.purgeSession(session, connectionIdentifier, edhocSessions, usedConnectionIds);
client.shutdown();
return false;
}
try {
edhocEndpointInfo.getOscoreDb().addContext(edhocURI, ctx);
} catch (OSException e) {
System.err.println("Error when adding the OSCORE Security Context to the context database " + e.getMessage());
Util.purgeSession(session, connectionIdentifier, edhocSessions, usedConnectionIds);
client.shutdown();
return false;
}
}
}
else if (requestType == Constants.EDHOC_ERROR_MESSAGE) {
// The Error Message was generated while reading EDHOC Message 2,
Util.purgeSession(session, connectionIdentifier, edhocSessions, usedConnectionIds);
edhocEndpointInfo.getOscoreDb().removeContext(ctx); // Delete the previously derived OSCORE Security Context
System.out.println("Sent EDHOC Error Message\n");
if (debugPrint) {
// Since the EDHOC error message is transported in a CoAP request, do not print the prepended C_R
byte[] sequenceBytesToPrint;
CBORObject[] objectList = null;
try {
objectList = CBORObject.DecodeSequenceFromBytes(nextPayload);
}
catch (Exception e) {
System.err.println("Error while preparing an EDHOC error message");
client.shutdown();
return false;
}
List<CBORObject> trimmedSequence = new ArrayList<CBORObject>();
for (int i = 1; i < objectList.length; i++) {
trimmedSequence.add(objectList[i]);
}
sequenceBytesToPrint = Util.buildCBORSequence(trimmedSequence);
Util.nicePrint("EDHOC Error Message", sequenceBytesToPrint);
}
}
CoapResponse edhocMessageResp2 = null;
try {
Request edhocMessageReq2 = new Request(Code.POST, Type.CON);
edhocMessageReq2.setPayload(nextPayload);
// If EDHOC message_3 has to be combined with the first
// OSCORE-protected request include the EDHOC option in the request
if (OSCORE_EDHOC_COMBINED == true && requestType == Constants.EDHOC_MESSAGE_3 &&
session.getApplicationProfile().getUsedForOSCORE() == true &&
session.getApplicationProfile().getSupportCombinedRequest() == true) {
// The combined request cannot be used if the Responder has to send message_4
if (session.getApplicationProfile().getUseMessage4() == true) {
System.err.println("Cannot send the EDHOC + OSCORE combined request if message_4 is expected\n");
Util.purgeSession(session, connectionIdentifier, edhocSessions, usedConnectionIds);
edhocEndpointInfo.getOscoreDb().removeContext(ctx); // Delete the previously derived OSCORE Security Context
client.shutdown();
return false;
}
client = new CoapClient(edhocCombinedRequestURI);
CoapResponse protectedResponse = null;
edhocMessageReq2 = new Request(combinedRequestAppCode, combinedRequestAppType);
if ((combinedRequestAppCode == Code.POST || combinedRequestAppCode == Code.PUT ||
combinedRequestAppCode == Code.FETCH || combinedRequestAppCode == Code.PATCH ||
combinedRequestAppCode == Code.IPATCH) && combinedRequestAppPayload != null) {
edhocMessageReq2.setPayload(combinedRequestAppPayload);
}
edhocMessageReq2.getOptions().setOscore(Bytes.EMPTY);
edhocMessageReq2.getOptions().setEdhoc(true);
session.setMessage3(nextPayload);
try {
// Send the EDHOC+OSCORE combined request
System.out.println("Sent EDHOC Message 3 as part of an EDHOC+OSCORE combined request\n");
session.setCurrentStep(Constants.EDHOC_SENT_M3);
protectedResponse = client.advanced(edhocMessageReq2);
} catch (ConnectorException e) {
System.err.println("ConnectorException when sending a protected request\n");
Util.purgeSession(session, connectionIdentifier, edhocSessions, usedConnectionIds);
edhocEndpointInfo.getOscoreDb().removeContext(ctx); // Delete the previously derived OSCORE Security Context
client.shutdown();
return false;
} catch (IOException e) {
System.err.println("IOException when sending a protected request\n");
Util.purgeSession(session, connectionIdentifier, edhocSessions, usedConnectionIds);
edhocEndpointInfo.getOscoreDb().removeContext(ctx); // Delete the previously derived OSCORE Security Context
client.shutdown();
return false;
}
boolean error = false;
byte[] myPayload = null;
if (protectedResponse != null) {
if (protectedResponse.advanced().isError() && !protectedResponse.getOptions().hasOscore()) {
// This error response was produced by the server before a possible successful decryption with OSCORE.
// Hence, this is a CoAP error response not protected with OSCORE. Later checks assess whether this is
// specifically an EDHOC error message. Regardless, the ongoing EDHOC session is going to be purged.
System.out.println(Utils.prettyPrint(protectedResponse) + "\n");
error = true;
}
myPayload = protectedResponse.getPayload();
}
if (myPayload != null) {
int contentFormat = protectedResponse.getOptions().getContentFormat();
int restCode = protectedResponse.getCode().value;
// Check if it is an EDHOC Error Message returned by the server
// when processing the EDHOC+OSCORE combined request
if (contentFormat == Constants.APPLICATION_EDHOC_CBOR_SEQ &&
((restCode == ResponseCode.BAD_REQUEST.value) || (restCode == ResponseCode.INTERNAL_SERVER_ERROR.value)) ) {
responseType = MessageProcessor.messageType(myPayload, false, edhocSessions, connectionIdentifier);
if (responseType == Constants.EDHOC_ERROR_MESSAGE) {
System.err.println("Received an EDHOC Error Message");
CBORObject[] objectList = MessageProcessor.readErrorMessage(myPayload, connectionIdentifier,
edhocSessions);
processErrorMessageAsResponse(objectList, Constants.EDHOC_MESSAGE_3);
}
else {
System.err.println("Received invalid reply to the EDHOC+OSCORE combined request");
}
}
}
if (error == true) {
Util.purgeSession(session, connectionIdentifier, edhocSessions, usedConnectionIds);
edhocEndpointInfo.getOscoreDb().removeContext(ctx); // Delete the previously derived OSCORE Security Context
client.shutdown();
return false;
}
this.appResponseToCombinedRequest = protectedResponse;
session.cleanMessage3();
} // End preparing the EDHOC+OSCORE combined request, if that was the intention
else {
if (requestType == Constants.EDHOC_ERROR_MESSAGE) {
// The request to send is an EDHOC Error Message
edhocMessageReq2.setConfirmable(true);
edhocMessageReq2.setURI(targetUri);
edhocMessageResp2 = client.advanced(edhocMessageReq2);
client.shutdown();
return false;
}
// The request to send is EDHOC message_3
session.setCurrentStep(Constants.EDHOC_SENT_M3);
edhocMessageReq2.getOptions().setContentFormat(Constants.APPLICATION_CID_EDHOC_CBOR_SEQ);
edhocMessageResp2 = client.advanced(edhocMessageReq2);
}
} catch (ConnectorException e) {
System.err.println("ConnectorException when sending " + myString + "\n");
Util.purgeSession(session, connectionIdentifier, edhocSessions, usedConnectionIds);
edhocEndpointInfo.getOscoreDb().removeContext(ctx); // Delete the previously derived OSCORE Security Context
client.shutdown();
return false;
} catch (IOException e) {
System.err.println("IOException when sending " + myString + "\n");
Util.purgeSession(session, connectionIdentifier, edhocSessions, usedConnectionIds);
edhocEndpointInfo.getOscoreDb().removeContext(ctx); // Delete the previously derived OSCORE Security Context
client.shutdown();
return false;
}
// Wait for a possible response. For how long?
// Only an EDHOC message_4 or an EDHOC Error Message is a legitimate EDHOC message at this point
if (edhocMessageResp2 != null) {
responseType = -1;
responsePayload = null;
boolean expectMessage4 = session.getApplicationProfile().getUseMessage4();
if (edhocMessageResp2 != null) {
responsePayload = edhocMessageResp2.getPayload();
}
if (responsePayload == null) {
discontinue = true;
}
else {
responseType = MessageProcessor.messageType(responsePayload, false, edhocSessions, connectionIdentifier);
// It is always consistent to receive an Error Message
if (responseType != Constants.EDHOC_ERROR_MESSAGE) {
if (responseType == Constants.EDHOC_MESSAGE_4) {
if (expectMessage4 == false) {
discontinue = true;
}
// Else it is fine, i.e., it is message_4 and it is expected
}
else {
// Any other message than message_4 and Error Message
if (expectMessage4 == true) {
System.err.println("Received invalid reply to EDHOC Message 3 while expecting Message 4");
System.err.println("responseType: " + responseType);
discontinue = true;
}
else {
// This is a generic response received as reply to EDHOC Message 3
System.out.println("here");
processResponseAfterEdhoc(edhocMessageResp2);
}
}
}
// It is an EDHOC Error Message
else {
System.err.println("Received an EDHOC Error Message");
Util.nicePrint("EDHOC Error Message", responsePayload);
CBORObject[] objectList = MessageProcessor.readErrorMessage(responsePayload, connectionIdentifier, edhocSessions);
processErrorMessageAsResponse(objectList, Constants.EDHOC_MESSAGE_3);
discontinue = true;
}
}
if (discontinue == true) {
Util.purgeSession(session, connectionIdentifier, edhocSessions, usedConnectionIds);
edhocEndpointInfo.getOscoreDb().removeContext(ctx); // Delete the previously derived OSCORE Security Context
client.shutdown();
return false;
}
if (responseType == Constants.EDHOC_MESSAGE_4) {
processingResult = MessageProcessor.readMessage4(responsePayload, false, connectionIdentifier,
edhocSessions, usedConnectionIds);
if (processingResult.get(0) == null || processingResult.get(0).getType() != CBORType.ByteString) {
System.err.println("Error when processing EDHOC Message 4");
Util.purgeSession(session, connectionIdentifier, edhocSessions, usedConnectionIds);
edhocEndpointInfo.getOscoreDb().removeContext(ctx); // Delete the previously derived OSCORE Security Context
client.shutdown();
return false;
}
// A non-zero length response payload would be an EDHOC Error Message
byte[] nextMessage = processingResult.get(0).GetByteString();
// The EDHOC message_4 was successfully processed
if (nextMessage.length == 0) {
// If message_4 was a Confirmable response, send an empty ACK
if (edhocMessageResp2.advanced().isConfirmable()) {
edhocMessageResp2.advanced().acknowledge();
}
}
// An EDHOC error message has to be returned in reply to EDHOC message_4
else {
Request edhocMessageReq3 = new Request(Code.POST, Type.CON);
edhocMessageReq3.setPayload(nextMessage);
try {
edhocMessageResp = client.advanced(edhocMessageReq3);
} catch (ConnectorException e) {
System.err.println("ConnectorException when sending EDHOC Error Message");
} catch (IOException e) {
System.err.println("IOException when sending EDHOC Error Message");
}
Util.purgeSession(session, connectionIdentifier, edhocSessions, usedConnectionIds);
edhocEndpointInfo.getOscoreDb().removeContext(ctx); // Delete the previously derived OSCORE Security Context
client.shutdown();
return false;
}
}
} // End handling of reception of EDHOC message_4 or EDHOC error message, after having sent EDHOC message_3
} // End handling of reception of EDHOC message_2
client.shutdown();
return true;
} // End of startEdhocExchangeAsInitiator()
/*
* Process a generic response received as reply to EDHOC Message 3
*/
private void processResponseAfterEdhoc(CoapResponse msg) {
// Do nothing
System.out.println("ResponseAfterEdhoc()");
}
/*
* Process an EDHOC Error Message as a CoAP response
*/
private void processErrorMessageAsResponse(CBORObject[] objectList, int messageNumber) {
if (objectList != null) {
int index = 0;
// Retrieve ERR_CODE
int errorCode = objectList[index].AsInt32();
System.out.println("ERR_CODE: " + errorCode + "\n");
index++;
// Retrieve ERR_INFO
if (errorCode == Constants.ERR_CODE_UNSPECIFIED_ERROR) {
String errMsg = objectList[index].toString();
System.out.println("DIAG_MSG: " + errMsg + "\n");
}
else if (errorCode == Constants.ERR_CODE_WRONG_SELECTED_CIPHER_SUITE) {
learnedPeerSupportedCipherSuites = new ArrayList<Integer>();
CBORObject suitesR = objectList[index];
if (suitesR.getType() == CBORType.Integer) {
int suite = suitesR.AsInt32();
learnedPeerSupportedCipherSuites.add(Integer.valueOf(suite));
System.out.println("SUITES_R: " + suite + "\n");
}
else if (suitesR.getType() == CBORType.Array) {
System.out.print("SUITES_R: [ " );
for (int i = 0; i < suitesR.size(); i++) {
int suite = suitesR.get(i).AsInt32();
learnedPeerSupportedCipherSuites.add(Integer.valueOf(suite));
System.out.print(suite + " " );
}
System.out.println("]\n");
}
}
}
}
/**
* Retrieve the EDHOC session associated with this EDHOC exchange
*
* @return The EDHOC session associated with this EDHOC exchange
*/
public EdhocSession getEdhocSession() {
return this.edhocSession;
}
/**
* Retrieve the list of EDHOC cipher suites supported by the other peer, as learned
* from an EDHOC error message with ERR_CODE = 1 received as a reply to EDHOC message_1
*
* @return The learned list of EDHOC cipher suites supported by the other peer
*/
public List<Integer> getLearnedPeerSupportedCipherSuites() {
return this.learnedPeerSupportedCipherSuites;
}
/**
* Retrieve the application response to the EDHOC+OSCORE combined request
*
* @return The result application response to the EDHOC+OSCORE combined request
*/
public CoapResponse getAppResponseToCombinedRequest() {
return this.appResponseToCombinedRequest;
}
}