GroupOSCOREGroupMembershipResource.java

 /*******************************************************************************
 * Copyright (c) 2025, RISE AB
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions 
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, 
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *******************************************************************************/
package se.sics.ace.oscore.rs.oscoreGroupManager;

import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.eclipse.californium.core.CoapResource;
import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.coap.Request;
import org.eclipse.californium.core.coap.Response;
import org.eclipse.californium.core.server.resources.CoapExchange;
import org.eclipse.californium.core.server.resources.Resource;

import com.upokecenter.cbor.CBORObject;
import com.upokecenter.cbor.CBORType;

import org.eclipse.californium.cose.AlgorithmID;
import org.eclipse.californium.cose.CoseException;
import org.eclipse.californium.cose.KeyKeys;
import org.eclipse.californium.cose.OneKey;
import org.postgresql.core.Utils;

import se.sics.ace.AceException;
import se.sics.ace.Constants;
import se.sics.ace.GroupcommErrors;
import se.sics.ace.GroupcommParameters;
import se.sics.ace.Util;
import se.sics.ace.coap.CoapReq;
import se.sics.ace.oscore.GroupInfo;
import se.sics.ace.oscore.GroupOSCOREInputMaterialObjectParameters;
import se.sics.ace.oscore.OSCOREInputMaterialObjectParameters;
import se.sics.ace.oscore.rs.GroupOSCOREValidator;
import se.sics.ace.rs.TokenRepository;

/**
 * Definition of the Group OSCORE group-membership resource
 */
public class GroupOSCOREGroupMembershipResource extends CoapResource {
	
	private Map<String, GroupInfo> existingGroupInfo = new HashMap<>();
	
	private final String rootGroupMembershipResourcePath; 
	
	private Map<String, Map<String, Set<Short>>> myScopes;
	
	private GroupOSCOREValidator valid;
	
	/**
     * Constructor
     * @param resId  the resource identifier
     * @param existingGroupInfo  the set of information of the existing OSCORE groups
     * @param rootGroupMembershipResourcePath  the path of the root group-membership resource
     * @param myScopes  the scopes of this OSCORE Group Manager
     * @param valid  the access validator of this OSCORE Group Manager
     */
    public GroupOSCOREGroupMembershipResource(String resId,
    										  Map<String, GroupInfo> existingGroupInfo,
    										  String rootGroupMembershipResourcePath,
    										  Map<String, Map<String, Set<Short>>> myScopes,
    										  GroupOSCOREValidator valid) {
        
        // set resource identifier
        super(resId);
        
        // set display name
        getAttributes().setTitle("Group OSCORE Group-Membership Resource " + resId);
     
        this.existingGroupInfo = existingGroupInfo;
        this.rootGroupMembershipResourcePath = rootGroupMembershipResourcePath;
        this.myScopes = myScopes;
        this.valid = valid;
    }
    
    @Override
    public void handleGET(CoapExchange exchange) {
    	System.out.println("GET request reached the GM");
    	
    	// Retrieve the entry for the target group, using the last path segment
    	// of the URI path as the name of the OSCORE group
    	GroupInfo targetedGroup = existingGroupInfo.get(this.getName());
    	
    	// This should never happen if existing groups are maintained properly
    	if (targetedGroup == null) {
        	exchange.respond(CoAP.ResponseCode.SERVICE_UNAVAILABLE,
        					 "Error when retrieving material for the OSCORE group");
        	return;
    	}
    	
    	String groupName = targetedGroup.getGroupName();
    	
    	// This should never happen if active groups are maintained properly
	  		if (!groupName.equals(this.getName())) {
        	exchange.respond(CoAP.ResponseCode.SERVICE_UNAVAILABLE,
        					 "Error when retrieving material for the OSCORE group");
				return;
			}  
    	
    	String subject = null;
    	Request request = exchange.advanced().getCurrentRequest();
        
        try {
			subject = CoapReq.getInstance(request).getSenderId();
		} catch (AceException e) {
		    System.err.println("Error while retrieving the client identity: " + e.getMessage());
		}
        if (subject == null) {
        	// At this point, this should not really happen,
        	// due to the earlier check at the Token Repository
        	exchange.respond(CoAP.ResponseCode.UNAUTHORIZED,
        					 "Unauthenticated client tried to get access");
        	return;
        }
    	
    	if (!targetedGroup.isGroupMember(subject)) {	
    		// The requester is not a current group member.
    		CBORObject responseMap = CBORObject.NewMap();
    		
    		CBORObject aceGroupcommError = CBORObject.NewMap();
    		aceGroupcommError.Add(0, GroupcommErrors.ONLY_FOR_GROUP_MEMBERS);
    		responseMap.Add(Constants.PROBLEM_DETAIL_ACE_GROUPCOMM_ERROR, aceGroupcommError);
    		responseMap.Add(Constants.PROBLEM_DETAIL_KEY_TITLE, GroupcommErrors.DESCRIPTION[GroupcommErrors.ONLY_FOR_GROUP_MEMBERS]);

    		byte[] responsePayload = responseMap.EncodeToBytes();
    		exchange.respond(CoAP.ResponseCode.FORBIDDEN,
    						 responsePayload,
    						 Constants.APPLICATION_CONCISE_PROBLEM_DETAILS_CBOR);
    		
    		return;
    	}
        
    	// Respond to the Key Distribution Request
        
    	CBORObject myResponse = CBORObject.NewMap();
    	
    	// Key Type Value assigned to the Group_OSCORE_Input_Material object.
    	myResponse.Add(GroupcommParameters.GKTY, CBORObject.FromObject(GroupcommParameters.GROUP_OSCORE_INPUT_MATERIAL_OBJECT));
    	
    	// This map is filled as the Group_OSCORE_Input_Material object
    	CBORObject myMap = CBORObject.NewMap();
    	
    	// Fill the 'key' parameter
    	// Note that no Sender ID is included
    	myMap.Add(OSCOREInputMaterialObjectParameters.hkdf, targetedGroup.getHkdf().AsCBOR());
    	myMap.Add(OSCOREInputMaterialObjectParameters.salt, targetedGroup.getMasterSalt());
    	myMap.Add(OSCOREInputMaterialObjectParameters.ms, targetedGroup.getMasterSecret());
    	myMap.Add(OSCOREInputMaterialObjectParameters.contextId, targetedGroup.getGroupId());
    	myMap.Add(GroupOSCOREInputMaterialObjectParameters.cred_fmt, targetedGroup.getAuthCredFormat());
    	if (targetedGroup.getMode() != GroupcommParameters.GROUP_OSCORE_PAIRWISE_MODE_ONLY) {
    		// The group mode is used
    		myMap.Add(GroupOSCOREInputMaterialObjectParameters.gp_enc_alg, targetedGroup.getGpEncAlg().AsCBOR());
        	myMap.Add(GroupOSCOREInputMaterialObjectParameters.sign_alg, targetedGroup.getSignAlg().AsCBOR());
        	if (targetedGroup.getSignParams().size() != 0)
        		myMap.Add(GroupOSCOREInputMaterialObjectParameters.sign_params, targetedGroup.getSignParams());
    	}
    	if (targetedGroup.getMode() != GroupcommParameters.GROUP_OSCORE_GROUP_MODE_ONLY) {
    		// The pairwise mode is used
    		myMap.Add(OSCOREInputMaterialObjectParameters.alg, targetedGroup.getAlg().AsCBOR());
        	myMap.Add(GroupOSCOREInputMaterialObjectParameters.ecdh_alg, targetedGroup.getEcdhAlg().AsCBOR());
        	if (targetedGroup.getEcdhParams().size() != 0)
        		myMap.Add(GroupOSCOREInputMaterialObjectParameters.ecdh_params, targetedGroup.getEcdhParams());
    	}
    	myResponse.Add(GroupcommParameters.KEY, myMap);
    	
    	// The current version of the symmetric keying material
    	myResponse.Add(GroupcommParameters.NUM, CBORObject.FromObject(targetedGroup.getVersion()));
    	
    	// CBOR Value assigned to the coap_group_oscore profile.
    	myResponse.Add(GroupcommParameters.ACE_GROUPCOMM_PROFILE, CBORObject.FromObject(GroupcommParameters.COAP_GROUP_OSCORE_APP));
    	
    	long expValue = targetedGroup.getExp();
    	long exiValue = expValue - (System.currentTimeMillis() / 1000L);
    	
    	// Expiration time in seconds, after which the OSCORE Security Context
    	// derived from the 'k' parameter is not valid anymore.
    	myResponse.Add(GroupcommParameters.EXP, CBORObject.FromObject(expValue));
    	
    	// Number of seconds after which the OSCORE Security Context
    	// derived from the 'k' parameter is not valid anymore.
    	myResponse.Add(GroupcommParameters.EXI, CBORObject.FromObject(exiValue));

    	byte[] responsePayload = myResponse.EncodeToBytes();
    	
    	Response coapResponse = new Response(CoAP.ResponseCode.CONTENT);
    	coapResponse.setPayload(responsePayload);
    	coapResponse.getOptions().setContentFormat(Constants.APPLICATION_ACE_GROUPCOMM_CBOR);

    	exchange.respond(coapResponse);

    }
    
    @Override
    public void handlePOST(CoapExchange exchange) {
        
    	System.out.println("POST request reached the GM");
    	
    	String groupName;
    	Set<String> roles = new HashSet<>();
    	boolean provideAuthCreds = false;
    	
    	String subject = null;
    	Request request = exchange.advanced().getCurrentRequest();
        
        try {
			subject = CoapReq.getInstance(request).getSenderId();
		} catch (AceException e) {
		    System.err.println("Error while retrieving the client identity: " + e.getMessage());
		}
        if (subject == null) {
        	// At this point, this should not really happen, due to the earlier check at the Token Repository
        	exchange.respond(CoAP.ResponseCode.UNAUTHORIZED,
        				     "Unauthenticated client tried to get access");
        	return;
        }
        
        String rsNonceString = TokenRepository.getInstance().getRsnonce(subject);
        
        if(rsNonceString == null) {
        	// Return an error response, with a new nonce for PoP of
        	// the Client's private key in the next Join Request
    	    CBORObject responseMap = CBORObject.NewMap();
            byte[] rsnonce = new byte[8];
            new SecureRandom().nextBytes(rsnonce);
            responseMap.Add(GroupcommParameters.KDCCHALLENGE, rsnonce);
            TokenRepository.getInstance().setRsnonce(subject, Base64.getEncoder().encodeToString(rsnonce));
            byte[] responsePayload = responseMap.EncodeToBytes();
			exchange.respond(CoAP.ResponseCode.BAD_REQUEST,
        					 responsePayload,
        					 Constants.APPLICATION_ACE_GROUPCOMM_CBOR);
        	return;
        }
                    
        byte[] rsnonce = Base64.getDecoder().decode(rsNonceString);
    	
    	byte[] requestPayload = exchange.getRequestPayload();
    	
    	if(requestPayload == null) {
			exchange.respond(CoAP.ResponseCode.BAD_REQUEST,
    						 "A payload must be present");
    		return;
    	}
    	
    	CBORObject joinRequest = CBORObject.DecodeFromBytes(requestPayload);
    	
		CBORObject errorResponseMap = CBORObject.NewMap();
    	
    	// Prepare the 'sign_info' and 'ecdh_info' parameter,
		// to possibly return it in a 4.00 (Bad Request) response        	
		CBORObject signInfo = CBORObject.NewArray();
    	CBORObject ecdhInfo = CBORObject.NewArray();
			
    	// Retrieve the entry for the target group, using the last path segment of
    	// the URI path as the name of the OSCORE group
    	GroupInfo targetedGroup = existingGroupInfo.get(this.getName());
		
    	// This should never happen if existing groups are maintained properly
    	if (targetedGroup == null) {
        	exchange.respond(CoAP.ResponseCode.SERVICE_UNAVAILABLE,
        					 "Error when retrieving material for the OSCORE group");
        	return;
    	}
    	
    	if (!targetedGroup.getStatus()) {
    		// The group is currently inactive and no new members are admitted
    		CBORObject responseMap = CBORObject.NewMap();
    		
    		CBORObject aceGroupcommError = CBORObject.NewMap();
    		aceGroupcommError.Add(0, GroupcommErrors.GROUP_NOT_ACTIVE);
    		responseMap.Add(Constants.PROBLEM_DETAIL_ACE_GROUPCOMM_ERROR, aceGroupcommError);
    		responseMap.Add(Constants.PROBLEM_DETAIL_KEY_TITLE, GroupcommErrors.DESCRIPTION[GroupcommErrors.GROUP_NOT_ACTIVE]);
    		
    		byte[] responsePayload = responseMap.EncodeToBytes();
    		exchange.respond(CoAP.ResponseCode.SERVICE_UNAVAILABLE,
    						 responsePayload,
    						 Constants.APPLICATION_CONCISE_PROBLEM_DETAILS_CBOR);
    		
    		return;
    	}
    	
    	// The group mode is used
    	if (targetedGroup.getMode() != GroupcommParameters.GROUP_OSCORE_PAIRWISE_MODE_ONLY) {
			CBORObject signInfoEntry = CBORObject.NewArray();
			signInfoEntry.Add(CBORObject.FromObject(targetedGroup.getGroupName())); // 'id' element
			signInfoEntry.Add(targetedGroup.getSignAlg().AsCBOR()); // 'sign_alg' element
			
			// 'sign_parameters' element (The algorithm capabilities)
	    	CBORObject arrayElem = targetedGroup.getSignParams().get(0);
	    	if (arrayElem == null)
	    		signInfoEntry.Add(CBORObject.Null);
	    	else
	    		signInfoEntry.Add(arrayElem);
	    	
	    	// 'sign_key_parameters' element (The key type capabilities)
	    	arrayElem = targetedGroup.getSignParams().get(1);
	    	if (arrayElem == null)
	    		signInfoEntry.Add(CBORObject.Null);
	    	else
	    		signInfoEntry.Add(arrayElem);
	    	
	    	// 'cred_fmt' element
	    	signInfoEntry.Add(targetedGroup.getAuthCredFormat());
		    signInfo.Add(signInfoEntry);
		    errorResponseMap.Add(GroupcommParameters.SIGN_INFO, signInfo);
    	}
    	
    	// The pairwise mode is used
    	if (targetedGroup.getMode() != GroupcommParameters.GROUP_OSCORE_GROUP_MODE_ONLY) {
			CBORObject ecdhInfoEntry = CBORObject.NewArray();
			ecdhInfoEntry.Add(CBORObject.FromObject(targetedGroup.getGroupName())); // 'id' element
			ecdhInfoEntry.Add(targetedGroup.getEcdhAlg().AsCBOR()); // 'ecdh_alg' element
			
			// 'ecdh_parameters' element (The algorithm capabilities)
	    	CBORObject arrayElem = targetedGroup.getEcdhParams().get(0);
	    	if (arrayElem == null)
	    		ecdhInfoEntry.Add(CBORObject.Null);
	    	else
	    		ecdhInfoEntry.Add(arrayElem);
	    	
	    	// 'ecdh_key_parameters' element (The key type capabilities)
	    	arrayElem = targetedGroup.getEcdhParams().get(1);
	    	if (arrayElem == null)
	    		ecdhInfoEntry.Add(CBORObject.Null);
	    	else
	    		ecdhInfoEntry.Add(arrayElem);
	    	
	    	// 'cred_fmt' element
	    	ecdhInfoEntry.Add(targetedGroup.getAuthCredFormat());
		    ecdhInfo.Add(ecdhInfoEntry);
		    errorResponseMap.Add(GroupcommParameters.ECDH_INFO, ecdhInfo);
    	}
	    
	    
    	// The payload of the join request must be a CBOR Map
    	if (!joinRequest.getType().equals(CBORType.Map)) {
    		byte[] errorResponsePayload = errorResponseMap.EncodeToBytes();
			exchange.respond(CoAP.ResponseCode.BAD_REQUEST,
    						 errorResponsePayload,
    						 Constants.APPLICATION_ACE_GROUPCOMM_CBOR);
    		return;
    	}
    		
    	// More steps follow:
    	//
    	// Retrieve 'scope' from the map; check the GroupID against the name of the resource, just for consistency.
    	//
    	// Retrieve the role(s) to possibly reduce the set of material to provide to the joining node.
    	//
    	// Any other check is performed through the method canAccess() of the TokenRepository, which is
    	// in turn invoked by the deliverRequest() method of CoapDeliverer, upon getting the join request.
    	// The actual checks of legitimate access are performed by scopeMatchResource() and scopeMatch()
    	// of the GroupOSCOREJoinValidator used as Scope/Audience Validator.
    	
    	// Retrieve scope
    	CBORObject scope = joinRequest.get(CBORObject.FromObject(GroupcommParameters.SCOPE));
    	
    	// Scope must be included for joining OSCORE groups
    	if (scope == null) {
    		byte[] errorResponsePayload = errorResponseMap.EncodeToBytes();
			exchange.respond(CoAP.ResponseCode.BAD_REQUEST,
    						 errorResponsePayload,
    						 Constants.APPLICATION_ACE_GROUPCOMM_CBOR);
    		return;
    	}
    	// Scope must be wrapped in a binary string for joining OSCORE groups
    	if (!scope.getType().equals(CBORType.ByteString)) {
    		byte[] errorResponsePayload = errorResponseMap.EncodeToBytes();
			exchange.respond(CoAP.ResponseCode.BAD_REQUEST,
    						 errorResponsePayload,
    						 Constants.APPLICATION_ACE_GROUPCOMM_CBOR);
    		return;
        }
    	
    	byte[] rawScope = scope.GetByteString();
    	CBORObject cborScope = CBORObject.DecodeFromBytes(rawScope);
    	
    	// Invalid scope format for joining OSCORE groups
    	if (!cborScope.getType().equals(CBORType.Array)) {
    		byte[] errorResponsePayload = errorResponseMap.EncodeToBytes();
			exchange.respond(CoAP.ResponseCode.BAD_REQUEST,
    						 errorResponsePayload,
    						 Constants.APPLICATION_ACE_GROUPCOMM_CBOR);
    		return;
        }
    	
    	// Invalid scope format for joining OSCORE groups
    	if (cborScope.size() != 2) {
    		byte[] errorResponsePayload = errorResponseMap.EncodeToBytes();
			exchange.respond(CoAP.ResponseCode.BAD_REQUEST,
    						 errorResponsePayload,
    						 Constants.APPLICATION_ACE_GROUPCOMM_CBOR);
    		return;
        }
    	
    	// Retrieve the name of the OSCORE group
  	  	CBORObject scopeElement = cborScope.get(0);
  	  	if (scopeElement.getType().equals(CBORType.TextString)) {
  	  		groupName = scopeElement.AsString();

  	  		// The group name in 'scope' is not pertinent for this group-membership resource
  	  		if (!groupName.equals(this.getName())) {
        		byte[] errorResponsePayload = errorResponseMap.EncodeToBytes();
				exchange.respond(CoAP.ResponseCode.BAD_REQUEST,
  								 errorResponsePayload,
  								 Constants.APPLICATION_ACE_GROUPCOMM_CBOR);
  				return;
  			}      	  		
  	  	}
  	  	// Invalid scope format for joining OSCORE groups
  	  	else {
    		byte[] errorResponsePayload = errorResponseMap.EncodeToBytes();
			exchange.respond(CoAP.ResponseCode.BAD_REQUEST,
  	  				 		 errorResponsePayload,
  	  						 Constants.APPLICATION_ACE_GROUPCOMM_CBOR);
    		return;
  	  	}
  	  	
  	  	// Retrieve the role or list of roles
  	  	scopeElement = cborScope.get(1);
  	  	
  	  	int roleSet = 0;
  	  	
    	if (scopeElement.getType().equals(CBORType.Integer)) {
    		roleSet = scopeElement.AsInt32();
    		
    		// Invalid format of roles
    		if (roleSet < 0) {
        		byte[] errorResponsePayload = errorResponseMap.EncodeToBytes();
				exchange.respond(CoAP.ResponseCode.BAD_REQUEST,
  	  							 errorResponsePayload,
  	  							 Constants.APPLICATION_ACE_GROUPCOMM_CBOR);
        		return;
    		}
 	  		// Invalid combination of roles
    		if(!GroupcommParameters.getValidGroupOSCORERoleCombinations().contains(roleSet)) {
        		byte[] errorResponsePayload = errorResponseMap.EncodeToBytes();
				exchange.respond(CoAP.ResponseCode.BAD_REQUEST,
									 errorResponsePayload,
									 Constants.APPLICATION_ACE_GROUPCOMM_CBOR);
					return;
    		}
    		Set<Integer> roleIdSet = new HashSet<Integer>();
    		try {
        		roleIdSet = Util.getGroupOSCORERoles(roleSet);
    		}
    		catch(AceException e) {
    			System.err.println(e.getMessage());
    		}
    		short[] roleIdArray = new short[roleIdSet.size()];
    		int index = 0;
    		for (Integer elem : roleIdSet)
    		    roleIdArray[index++] = elem.shortValue(); 
    		for (int i=0; i<roleIdArray.length; i++) {
    			short roleIdentifier = roleIdArray[i];
    			// Silently ignore unrecognized roles
    			if (roleIdentifier < GroupcommParameters.GROUP_OSCORE_ROLES.length)
    				roles.add(GroupcommParameters.GROUP_OSCORE_ROLES[roleIdentifier]);
    		}
    		  
    	}
  	  	
  	  	
    	// Invalid format of roles
  	  	else {
    		byte[] errorResponsePayload = errorResponseMap.EncodeToBytes();
			exchange.respond(CoAP.ResponseCode.BAD_REQUEST,
  	  						 errorResponsePayload,
  	  						 Constants.APPLICATION_ACE_GROUPCOMM_CBOR);
    		return;
  	  	}
    
    	// Check that the indicated roles for this group are actually allowed by the Access Token 
    	boolean allowed = false;
    	int[] roleSetToken = Util.getGroupOSCORERolesFromToken(subject, groupName);
    	if (roleSetToken == null) {
    		exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR,
    						 "Error when retrieving allowed roles from Access Tokens");
    		return;
    	}
    	else {
    		for (int index = 0; index < roleSetToken.length; index++) {
        		if ((roleSet & roleSetToken[index]) == roleSet) {
        			// 'scope' in at least one Access Token admits all the roles indicated
        			// for this group in the Joining Request
        			allowed = true;
        			break;
        		}
    		}	
    	}
    	
    	if (!allowed) {
    		byte[] errorResponsePayload = errorResponseMap.EncodeToBytes();
    		exchange.respond(CoAP.ResponseCode.FORBIDDEN,
    						 errorResponsePayload,
    						 Constants.APPLICATION_ACE_GROUPCOMM_CBOR);
    		return;
    	}
    	
		// Retrieve the nonce from the Client
		CBORObject cnonce = joinRequest.get(CBORObject.FromObject(GroupcommParameters.CNONCE));
    	
		// A client nonce must be included for proof-of-possession for joining OSCORE groups
    	if (cnonce == null) {
    		byte[] errorResponsePayload = errorResponseMap.EncodeToBytes();
			exchange.respond(CoAP.ResponseCode.BAD_REQUEST, errorResponsePayload,
    						 Constants.APPLICATION_ACE_GROUPCOMM_CBOR);
    		return;
    	}

    	// The client nonce must be wrapped in a binary string for joining OSCORE groups
    	if (!cnonce.getType().equals(CBORType.ByteString)) {
    		byte[] errorResponsePayload = errorResponseMap.EncodeToBytes();
			exchange.respond(CoAP.ResponseCode.BAD_REQUEST, errorResponsePayload,
    						 Constants.APPLICATION_ACE_GROUPCOMM_CBOR);
    		return;
        }
    	
    	byte[] serializedCNonceCBOR = cnonce.EncodeToBytes();
    	
    	// Retrieve 'get_creds'
    	// If present, this parameter must be a CBOR array or the CBOR simple value Null
    	CBORObject getCreds = joinRequest.get(CBORObject.FromObject((GroupcommParameters.GET_CREDS)));
    	if (getCreds != null) {
    		
    		// Invalid format of 'get_creds'
    		if (!getCreds.getType().equals(CBORType.Array) && !getCreds.equals(CBORObject.Null)) {
        		byte[] errorResponsePayload = errorResponseMap.EncodeToBytes();
				exchange.respond(CoAP.ResponseCode.BAD_REQUEST,
    							 errorResponsePayload,
    							 Constants.APPLICATION_ACE_GROUPCOMM_CBOR);
        		return;
    		}

    		// Invalid format of 'get_creds'
    		if (getCreds.getType().equals(CBORType.Array)) {
        		if ( getCreds.size() != 3 ||
        	        !getCreds.get(0).getType().equals(CBORType.Boolean) ||
        	         getCreds.get(0).AsBoolean() != true ||
        			!getCreds.get(1).getType().equals(CBORType.Array) ||
        			!getCreds.get(2).getType().equals(CBORType.Array) || 
        			 getCreds.get(2).size() != 0) {
            		
            		byte[] errorResponsePayload = errorResponseMap.EncodeToBytes();
					exchange.respond(CoAP.ResponseCode.BAD_REQUEST,
        							 errorResponsePayload,
        							 Constants.APPLICATION_ACE_GROUPCOMM_CBOR);
            		return;
            		
        		}
    		}

    		// Invalid format of 'get_creds'
    		if (getCreds.getType().equals(CBORType.Array)) {
    			for (int i = 0; i < getCreds.get(1).size(); i++) {
    				// Possible elements of the first array have to be all integers and
    				// express a valid combination of roles encoded in the AIF data model
    				if (!getCreds.get(1).get(i).getType().equals(CBORType.Integer) ||
    					!GroupcommParameters.getValidGroupOSCORERoleCombinations().contains(getCreds.get(1).get(i).AsInt32())) {
    					
                		byte[] errorResponsePayload = errorResponseMap.EncodeToBytes();
						exchange.respond(CoAP.ResponseCode.BAD_REQUEST,
            							 errorResponsePayload,
            							 Constants.APPLICATION_ACE_GROUPCOMM_CBOR);
                		return;
    					
    				}
    			}
    		}

    		provideAuthCreds = true;
    		
    	}
    	
    	// Retrieve the entry for the target OSCORE group, using the group name
    	GroupInfo myGroup = existingGroupInfo.get(groupName);
    	
    	String nodeName = null;
    	byte[] senderId = null;
        int signKeyCurve = 0;

    	// Assign a Sender ID to the joining node, unless it is a monitor
    	if (roleSet != (1 << GroupcommParameters.GROUP_OSCORE_MONITOR)) {
        	// For the sake of testing, a particular Sender ID is used as known to be available.
            senderId = new byte[] { (byte) 0x25 };
            
        	myGroup.allocateSenderId(senderId);
    	}

    	if (senderId == null) {
    		// All possible values are already in use for this OSCORE group
    		CBORObject responseMap = CBORObject.NewMap();
    		
    		CBORObject aceGroupcommError = CBORObject.NewMap();
    		aceGroupcommError.Add(0, GroupcommErrors.UNAVAILABLE_INDIVIDUAL_KEYING_MATERIAL);
    		responseMap.Add(Constants.PROBLEM_DETAIL_ACE_GROUPCOMM_ERROR, aceGroupcommError);
    		responseMap.Add(Constants.PROBLEM_DETAIL_KEY_TITLE, GroupcommErrors.DESCRIPTION[GroupcommErrors.UNAVAILABLE_INDIVIDUAL_KEYING_MATERIAL]);

    		byte[] responsePayload = responseMap.EncodeToBytes();
    		exchange.respond(CoAP.ResponseCode.SERVICE_UNAVAILABLE,
    						 responsePayload,
    						 Constants.APPLICATION_CONCISE_PROBLEM_DETAILS_CBOR);
    		
    		return;
    	}
    	
    	nodeName = myGroup.allocateNodeName(senderId);
    	
    	if (nodeName == null) {
    		exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR, "Error when assigning a node name");
    		return;
    	}

    	// Retrieve 'client_cred'
    	CBORObject clientCred = joinRequest.get(CBORObject.FromObject(GroupcommParameters.CLIENT_CRED));
    	
    	if (clientCred == null && (roleSet != (1 << GroupcommParameters.GROUP_OSCORE_MONITOR))) {
    		
    		// TODO: check if the Group Manager already owns this client's public key
    		
    	}
    	if (clientCred == null && (roleSet != (1 << GroupcommParameters.GROUP_OSCORE_MONITOR))) {
			exchange.respond(CoAP.ResponseCode.BAD_REQUEST,
    						 "A public key was neither provided nor found as already stored");
    		return;
    	}
    	
    	// Process the public key of the joining node
    	else if (roleSet != (1 << GroupcommParameters.GROUP_OSCORE_MONITOR)) {
    		
    		OneKey publicKey = null;
    		boolean valid = false;
    		
    		if (clientCred.getType() != CBORType.ByteString) {
				exchange.respond(CoAP.ResponseCode.BAD_REQUEST,
						 			 "The parameter 'client_cred' must be a CBOR byte string");
        		return;
    		}
    		
    		byte[] clientCredBytes = clientCred.GetByteString();
    		switch(myGroup.getAuthCredFormat()) {
    		    case Constants.COSE_HEADER_PARAM_KCCS:
    		        CBORObject ccs = CBORObject.DecodeFromBytes(clientCredBytes);
    		        if (ccs.getType() == CBORType.Map) {
    		            // Retrieve the public key from the CCS
    		            publicKey = Util.ccsToOneKey(ccs);
    		            valid = true;
    		        }
    		        else {
					exchange.respond(CoAP.ResponseCode.BAD_REQUEST,
					 			 "Invalid format of authentication credential");
    	        		return;
    		        }
    		        break;
    		    case Constants.COSE_HEADER_PARAM_KCWT:
    		        CBORObject cwt = CBORObject.DecodeFromBytes(clientCredBytes);
    		        if (cwt.getType() == CBORType.Array) {
    		            // Retrieve the public key from the CWT
    		            // TODO
    		        }
    		        else {
					exchange.respond(CoAP.ResponseCode.BAD_REQUEST,
					 			 "Invalid format of authentication credential");
    	        		return;
    		        }
    		        break;
    		    case Constants.COSE_HEADER_PARAM_X5CHAIN:
    		        // Retrieve the public key from the certificate
    		        if (clientCred.getType() == CBORType.ByteString) {
    		            // TODO
    		        }
    		        else {
					exchange.respond(CoAP.ResponseCode.BAD_REQUEST,
					 			 "Invalid format of authentication credential");
    	        		return;
    		        }
    		        break;
    		    default:
				exchange.respond(CoAP.ResponseCode.BAD_REQUEST,
				 			 "Invalid format of authentication credential");
	        		return;
    		}
    		if (publicKey == null ||  valid == false) {
        		byte[] errorResponsePayload = errorResponseMap.EncodeToBytes();
				exchange.respond(CoAP.ResponseCode.BAD_REQUEST, errorResponsePayload,
        						 Constants.APPLICATION_ACE_GROUPCOMM_CBOR);
        		return;
    		}
    		
    		        		
    		// Sanity check on the type of public key        		
    		if (myGroup.getSignAlg().equals(AlgorithmID.ECDSA_256) ||
    		    myGroup.getSignAlg().equals(AlgorithmID.ECDSA_384) ||
    		    myGroup.getSignAlg().equals(AlgorithmID.ECDSA_512)) {
    			
    			// Invalid public key format
    			if (!publicKey.get(KeyKeys.KeyType).
    					equals(myGroup.getSignParams().get(0).get(0)) || // alg capability: key type
               		!publicKey.get(KeyKeys.KeyType).
               			equals(myGroup.getSignParams().get(1).get(0)) || // key capability: key type
               		!publicKey.get(KeyKeys.EC2_Curve).
               			equals(myGroup.getSignParams().get(1).get(1)))   // key capability: curve
    			{ 
    					
            			myGroup.deallocateSenderId(senderId);
            			
            			CBORObject aceGroupcommError = CBORObject.NewMap();
            			aceGroupcommError.Add(0, GroupcommErrors.INCOMPATIBLE_CRED);
            			errorResponseMap.Add(Constants.PROBLEM_DETAIL_ACE_GROUPCOMM_ERROR, aceGroupcommError);
            			errorResponseMap.Add(Constants.PROBLEM_DETAIL_KEY_TITLE, GroupcommErrors.DESCRIPTION[GroupcommErrors.INCOMPATIBLE_CRED]);
            			
                		byte[] errorResponsePayload = errorResponseMap.EncodeToBytes();
					exchange.respond(CoAP.ResponseCode.BAD_REQUEST,
                						 errorResponsePayload,
                						 Constants.APPLICATION_CONCISE_PROBLEM_DETAILS_CBOR);
            			return;
                    		
            	}

    		}
    		
    		if (myGroup.getSignAlg().equals(AlgorithmID.EDDSA)) {
    			
    			// Invalid public key format
    			if (!publicKey.get(KeyKeys.KeyType).
    					equals(myGroup.getSignParams().get(0).get(0)) || // alg capability: key type
           			!publicKey.get(KeyKeys.KeyType).
           				equals(myGroup.getSignParams().get(1).get(0)) || // key capability: key type
           			!publicKey.get(KeyKeys.OKP_Curve).
           				equals(myGroup.getSignParams().get(1).get(1)))   // key capability: curve
    			{
	            			
						myGroup.deallocateSenderId(senderId);

						CBORObject aceGroupcommError = CBORObject.NewMap();
						aceGroupcommError.Add(0, GroupcommErrors.INCOMPATIBLE_CRED);
						errorResponseMap.Add(Constants.PROBLEM_DETAIL_ACE_GROUPCOMM_ERROR, aceGroupcommError);
						errorResponseMap.Add(Constants.PROBLEM_DETAIL_KEY_TITLE, GroupcommErrors.DESCRIPTION[GroupcommErrors.INCOMPATIBLE_CRED]);
						
                		byte[] errorResponsePayload = errorResponseMap.EncodeToBytes();
					exchange.respond(CoAP.ResponseCode.BAD_REQUEST,
                						 errorResponsePayload,
                						 Constants.APPLICATION_CONCISE_PROBLEM_DETAILS_CBOR);
                		
            			return;
                		
        		}
    				
    		}
        	        		
    		// Check the proof-of-possession evidence over
        	// (scope | rsnonce | cnonce), using the Client's public key
        	CBORObject clientPopEvidence = joinRequest.
        				get(CBORObject.FromObject(GroupcommParameters.CLIENT_CRED_VERIFY));
        	
        	// A client PoP evidence must be included
        	if (clientPopEvidence == null) {
        		byte[] errorResponsePayload = errorResponseMap.EncodeToBytes();
				exchange.respond(CoAP.ResponseCode.BAD_REQUEST, errorResponsePayload,
        						 Constants.APPLICATION_ACE_GROUPCOMM_CBOR);
        		return;
        	}

        	// The client PoP evidence must be wrapped in a binary string
        	if (!clientPopEvidence.getType().equals(CBORType.ByteString)) {
        		byte[] errorResponsePayload = errorResponseMap.EncodeToBytes();
				exchange.respond(CoAP.ResponseCode.BAD_REQUEST, errorResponsePayload,
        						 Constants.APPLICATION_ACE_GROUPCOMM_CBOR);
        		return;
            }
        	
        	byte[] rawClientPopEvidence = clientPopEvidence.GetByteString();
    		
        	PublicKey pubKey = null;
            try {
				pubKey = publicKey.AsPublicKey();
			} catch (CoseException e) {
				System.out.println(e.getMessage());
				exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR,
								 "Failed to use the Client's public key to verify the PoP evidence");
        		return;
			}
            if (pubKey == null) {
            	exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR,
            					 "Failed to use the Client's public key to verify the PoP evidence");
        		return;
            }

            int offset = 0;
            
            byte[] serializedScopeCBOR = CBORObject.FromObject(scope).EncodeToBytes();
            byte[] serializedGMNonceCBOR = CBORObject.FromObject(rsnonce).EncodeToBytes();
            byte[] popInput = new byte [serializedScopeCBOR.length +
                                        serializedGMNonceCBOR.length +
                                        serializedCNonceCBOR.length];
            System.arraycopy(serializedScopeCBOR, 0, popInput, offset, serializedScopeCBOR.length);
            offset += serializedScopeCBOR.length;
            System.arraycopy(serializedGMNonceCBOR, 0, popInput, offset, serializedGMNonceCBOR.length);
            offset += serializedGMNonceCBOR.length;
            System.arraycopy(serializedCNonceCBOR, 0, popInput, offset, serializedCNonceCBOR.length);


            // The group mode is used. The PoP evidence is a signature
            if (targetedGroup.getMode() != GroupcommParameters.GROUP_OSCORE_PAIRWISE_MODE_ONLY) {
                
                if (publicKey.get(KeyKeys.KeyType).equals(org.eclipse.californium.cose.KeyKeys.KeyType_EC2))
                    signKeyCurve = publicKey.get(KeyKeys.EC2_Curve).AsInt32();
                else if (publicKey.get(KeyKeys.KeyType).equals(org.eclipse.californium.cose.KeyKeys.KeyType_OKP))
                    signKeyCurve = publicKey.get(KeyKeys.OKP_Curve).AsInt32();

                // This should never happen, due to the previous sanity checks
                if (signKeyCurve == 0) {
                    exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR,
                    				 "Error when setting up the signature verification");
                    return;
                }

                // Invalid Client's PoP signature
                if (!Util.verifySignature(signKeyCurve, pubKey, popInput, rawClientPopEvidence)) {
                	
                	CBORObject aceGroupcommError = CBORObject.NewMap();
                	aceGroupcommError.Add(0, GroupcommErrors.INVALID_POP_EVIDENCE);
                	errorResponseMap.Add(Constants.PROBLEM_DETAIL_ACE_GROUPCOMM_ERROR, aceGroupcommError);
                	errorResponseMap.Add(Constants.PROBLEM_DETAIL_KEY_TITLE, GroupcommErrors.DESCRIPTION[GroupcommErrors.INVALID_POP_EVIDENCE]);
                	
                	byte[] errorResponsePayload = errorResponseMap.EncodeToBytes();
					exchange.respond(CoAP.ResponseCode.BAD_REQUEST,
                					 errorResponsePayload,
                					 Constants.APPLICATION_CONCISE_PROBLEM_DETAILS_CBOR);
                	
                    return;
                }
            }
            // Only the pairwise mode is used. The PoP evidence is a MAC
            else {
                // TODO
            }
    		        		
            if (!myGroup.storeAuthCred(senderId, clientCred)) {
    			myGroup.deallocateSenderId(senderId);
				exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR,
								 "Error when storing the authentication credential");
        		return;
    			
    		}
    		
    	}
    	
    	boolean rejoin = false;
    	if (myGroup.isGroupMember(subject) == true) {
    		// This node is re-joining the group without having left
    		
    		rejoin = true;
        	String oldNodeName = myGroup.getGroupMemberName(subject);
        	
        	Resource staleResource = getChild("nodes").getChild(oldNodeName);
    		this.getChild("nodes").getChild(oldNodeName).delete(staleResource);
    		
    		myGroup.removeGroupMemberBySubject(subject);

    	}
    	
    	if (!myGroup.addGroupMember(senderId, nodeName, roleSet, subject)) {
    		// The joining node is not a monitor; its node name is its Sender ID encoded as a String
			if (senderId != null) {
				myGroup.deallocateSenderId(senderId);
				
				// Add the old Sender ID to the set of stale Sender IDs for this version of the symmetric keying material
	    		myGroup.addStaleSenderId(senderId);
			}
			// The joining node is a monitor; it got a node name but not a Sender ID
			else {
				myGroup.deallocateNodeName(nodeName);
			}
			myGroup.deleteBirthGid(nodeName);
			exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR,
							 "Error when adding the new group member");
    		return;
    	}
    	
    	// Create and add the sub-resource associated to the new group member
    	try {
    		valid.setGroupMembershipResources(Collections.singleton(rootGroupMembershipResourcePath + "/" +
    							   			  groupName + "/nodes/" + nodeName));
    		valid.setGroupMembershipResources(Collections.singleton(rootGroupMembershipResourcePath + "/" +
    							   			  groupName + "/nodes/" + nodeName + "/cred"));
		}
		catch(AceException e) {
			myGroup.removeGroupMemberBySubject(subject);
			
			// The joining node is not a monitor
			if (senderId != null) {
    			myGroup.deallocateSenderId(senderId);
    			myGroup.deleteAuthCred(senderId);
			}
			
			exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR,
							 "Error when creating the node sub-resource");
    		return;
		}

    	Set<Short> actions = new HashSet<>();
    	actions.add(Constants.GET);
    	actions.add(Constants.POST);
    	actions.add(Constants.DELETE);
    	myScopes.get(rootGroupMembershipResourcePath + "/" + groupName)
    	        .put(rootGroupMembershipResourcePath + "/" + groupName + "/nodes/" + nodeName, actions);
    	Resource nodeCoAPResource = new GroupOSCORESubResourceNodename(nodeName, existingGroupInfo);
    	this.getChild("nodes").add(nodeCoAPResource);
    	
    	actions = new HashSet<>();
    	actions.add(Constants.POST);
    	myScopes.get(rootGroupMembershipResourcePath + "/" + groupName)
                .put(rootGroupMembershipResourcePath + "/" + groupName + "/nodes/" + nodeName + "/cred", actions);
    	nodeCoAPResource = new GroupOSCORESubResourceNodenameCred("cred", existingGroupInfo);
    	this.getChild("nodes").getChild(nodeName).add(nodeCoAPResource);
    	
    	
        // Respond to the Join Request
        
    	CBORObject joinResponse = CBORObject.NewMap();
    	
    	// Key Type Value assigned to the Group_OSCORE_Input_Material object.
    	joinResponse.Add(GroupcommParameters.GKTY, CBORObject.FromObject(GroupcommParameters.GROUP_OSCORE_INPUT_MATERIAL_OBJECT));
    	
    	// This map is filled as the Group_OSCORE_Input_Material object
    	CBORObject myMap = CBORObject.NewMap();
    	
    	// Fill the 'key' parameter
    	if (senderId != null) {
			// The joining node is not a monitor
    		myMap.Add(GroupOSCOREInputMaterialObjectParameters.group_SenderID, senderId);
    	}
    	myMap.Add(OSCOREInputMaterialObjectParameters.hkdf, targetedGroup.getHkdf().AsCBOR());
    	myMap.Add(OSCOREInputMaterialObjectParameters.salt, targetedGroup.getMasterSalt());
    	myMap.Add(OSCOREInputMaterialObjectParameters.ms, targetedGroup.getMasterSecret());
    	myMap.Add(OSCOREInputMaterialObjectParameters.contextId, targetedGroup.getGroupId());
    	myMap.Add(GroupOSCOREInputMaterialObjectParameters.cred_fmt, targetedGroup.getAuthCredFormat());
    	if (targetedGroup.getMode() != GroupcommParameters.GROUP_OSCORE_PAIRWISE_MODE_ONLY) {
    	    // The group mode is used
    	    myMap.Add(GroupOSCOREInputMaterialObjectParameters.gp_enc_alg, targetedGroup.getGpEncAlg().AsCBOR());
    	    myMap.Add(GroupOSCOREInputMaterialObjectParameters.sign_alg, targetedGroup.getSignAlg().AsCBOR());
    	    if (targetedGroup.getSignParams().size() != 0)
    	        myMap.Add(GroupOSCOREInputMaterialObjectParameters.sign_params, targetedGroup.getSignParams());
    	}
    	if (targetedGroup.getMode() != GroupcommParameters.GROUP_OSCORE_GROUP_MODE_ONLY) {
    	    // The pairwise mode is used
    	    myMap.Add(OSCOREInputMaterialObjectParameters.alg, targetedGroup.getAlg().AsCBOR());
    	    myMap.Add(GroupOSCOREInputMaterialObjectParameters.ecdh_alg, targetedGroup.getEcdhAlg().AsCBOR());
    	    if (targetedGroup.getEcdhParams().size() != 0)
    	        myMap.Add(GroupOSCOREInputMaterialObjectParameters.ecdh_params, targetedGroup.getEcdhParams());
    	}
    	int detHashAlg = targetedGroup.getDetHashAlg();
    	if (detHashAlg != 0) {
    		byte[] detClientSenderId = targetedGroup.getDetClientSenderId();
    		if (detClientSenderId != null) {
    			// At this point, there must really be a Sender ID allocated to the Deterministic Client
	    		myMap.Add(GroupOSCOREInputMaterialObjectParameters.det_senderId, detClientSenderId);
	    		myMap.Add(GroupOSCOREInputMaterialObjectParameters.det_hash_alg, detHashAlg);
    		}
    	}
    	
    	joinResponse.Add(GroupcommParameters.KEY, myMap);
    	
    	// If backward security has to be preserved:
    	//
    	// 1) The Epoch part of the Group ID should be incremented
    	// myGroup.incrementGroupIdEpoch();
    	//
    	// 2) The OSCORE group should be rekeyed

    	// The current version of the symmetric keying material
    	joinResponse.Add(GroupcommParameters.NUM, CBORObject.FromObject(myGroup.getVersion()));
    	
    	// CBOR Value assigned to the coap_group_oscore profile.
    	joinResponse.Add(GroupcommParameters.ACE_GROUPCOMM_PROFILE, CBORObject.FromObject(GroupcommParameters.COAP_GROUP_OSCORE_APP));
    	
    	long expValue = targetedGroup.getExp();
    	long exiValue = expValue - (System.currentTimeMillis() / 1000L);
    	
    	// Expiration time in seconds, after which the OSCORE Security Context
    	// derived from the 'k' parameter is not valid anymore.
    	joinResponse.Add(GroupcommParameters.EXP, CBORObject.FromObject(expValue));
    	
    	// Number of seconds after which the OSCORE Security Context
    	// derived from the 'k' parameter is not valid anymore.
    	joinResponse.Add(GroupcommParameters.EXI, CBORObject.FromObject(exiValue));
    	
    	if (provideAuthCreds) {

    		CBORObject authCredsArray = CBORObject.NewArray();        		
    		CBORObject peerRoles = CBORObject.NewArray();
    		CBORObject peerIdentifiers = CBORObject.NewArray();
    		
    		Map<CBORObject, CBORObject> authCreds = myGroup.getAuthCreds();
        	
    		for (CBORObject sid : authCreds.keySet()) {
    			// This should never happen; silently ignore
    			if (authCreds.get(sid) == null)
    				continue;

    			byte[] peerSenderId = sid.GetByteString();
    			// Skip the authentication credential of the just-added joining node
    			if ((senderId != null) && Arrays.equals(senderId, peerSenderId))
    				continue;
    			
    			boolean includeAuthCred = false;
            	
    			// Authentication credentials of all group members are requested
    			if (getCreds.equals(CBORObject.Null)) {
    				includeAuthCred = true;
    			}
    			// Only authentication credentials of group members with certain roles are requested
    			else {
    				for (int i = 0; i < getCreds.get(1).size(); i++) {
    					int filterRoles = getCreds.get(1).get(i).AsInt32();
    					int memberRoles = myGroup.getGroupMemberRoles(peerSenderId);
    					// The owner of this authentication credential does not have all its roles
    					// indicated in this AIF integer filter
    					if (filterRoles != (filterRoles & memberRoles)) {
    						continue;
    					}
    					else {
    						includeAuthCred = true;
    						break;
    					}
    				}
    			}
    			
    			if (includeAuthCred) {
    				authCredsArray.Add(authCreds.get(sid));
        			peerRoles.Add(myGroup.getGroupMemberRoles(peerSenderId));
        			peerIdentifiers.Add(peerSenderId);
    			}

    		}
    		    			
    		joinResponse.Add(GroupcommParameters.CREDS, authCredsArray);
			joinResponse.Add(GroupcommParameters.PEER_ROLES, peerRoles);
			joinResponse.Add(GroupcommParameters.PEER_IDENTIFIERS, peerIdentifiers);
    			
    		
    		// Debug:
    		// 1) Print 'kid' as equal to the Sender ID of the key owner
    		// 2) Print 'kty' of each public key
    		/*
    		for (int i = 0; i < coseKeySet.size(); i++) {
    			byte[] kid = coseKeySet.get(i).get(KeyKeys.KeyId.AsCBOR()).GetByteString();
    			for (int j = 0; j < kid.length; j++)
    				System.out.printf("0x%02X", kid[j]);
    			System.out.println("\n" + coseKeySet.get(i).get(KeyKeys.KeyType.AsCBOR()));
    		}
    		*/
    		
    	}
    	
    	// Group Policies
    	joinResponse.Add(GroupcommParameters.GROUP_POLICIES, myGroup.getGroupPolicies());
    	
    	
    	// Authentication Credential of the Group Manager together with proof-of-possession evidence
    	byte[] kdcNonce = new byte[8];
    	new SecureRandom().nextBytes(kdcNonce);
    	joinResponse.Add(GroupcommParameters.KDC_NONCE, kdcNonce);
    	
    	CBORObject authCred = CBORObject.FromObject(targetedGroup.getGmAuthCred());
    	
    	joinResponse.Add(GroupcommParameters.KDC_CRED, authCred);
    	
    	PrivateKey gmPrivKey;
		try {
			gmPrivKey = targetedGroup.getGmKeyPair().AsPrivateKey();
		} catch (CoseException e) {
			System.err.println("Error when computing the GM PoP evidence " + e.getMessage());
			exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR,
							 "Error when computing the GM PoP evidence");
    		return;
		}

		int offset =  0;
		byte[] serializedGMNonceCBOR = CBORObject.FromObject(kdcNonce).EncodeToBytes();
    	byte[] popInput = new byte[serializedCNonceCBOR.length + serializedGMNonceCBOR.length];
    	System.arraycopy(serializedCNonceCBOR, 0, popInput, offset, serializedCNonceCBOR.length);
    	offset += serializedCNonceCBOR.length;
    	System.arraycopy(serializedGMNonceCBOR, 0, popInput, offset, serializedGMNonceCBOR.length);
    	
		byte[] gmSignature = Util.computeSignature(signKeyCurve, gmPrivKey, popInput);
    	if (gmSignature != null) {
    	    joinResponse.Add(GroupcommParameters.KDC_CRED_VERIFY, gmSignature);
    	}
    	else {
			exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR,
							 "Error when computing the GM PoP evidence");
    		return;
    	}
    	
    	byte[] responsePayload = joinResponse.EncodeToBytes();
    	String uriNodeResource = new String(rootGroupMembershipResourcePath + "/" +
    										groupName + "/nodes/" + nodeName);
    	
    	CoAP.ResponseCode responseCode = rejoin ? CoAP.ResponseCode.CHANGED : CoAP.ResponseCode.CREATED;
    	
    	Response coapJoinResponse = new Response(responseCode);
    	coapJoinResponse.setPayload(responsePayload);
    	coapJoinResponse.getOptions().setContentFormat(Constants.APPLICATION_ACE_GROUPCOMM_CBOR);
    	coapJoinResponse.getOptions().setLocationPath(uriNodeResource);

    	// Store cnonce for this joining node
    	TokenRepository.getInstance().setCnonce(subject, Base64.getEncoder().encodeToString(cnonce.GetByteString()));
    	
    	exchange.respond(coapJoinResponse);
    	
    }
}