Util.java

/*******************************************************************************
 * Copyright (c) 2023 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.
 * 
 * Contributors:
 *    Marco Tiloca (RISE)
 *    Rikard Höglund (RISE)
 *    
 ******************************************************************************/

package org.eclipse.californium.edhoc;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.HashMap;
import java.util.Set;

import org.eclipse.californium.cose.AlgorithmID;
import org.eclipse.californium.cose.Attribute;
import org.eclipse.californium.cose.CoseException;
import org.eclipse.californium.cose.Encrypt0Message;
import org.eclipse.californium.cose.HeaderKeys;
import org.eclipse.californium.cose.KeyKeys;
import org.eclipse.californium.cose.Message;
import org.eclipse.californium.cose.MessageTag;
import org.eclipse.californium.cose.OneKey;
import org.eclipse.californium.cose.Sign1Message;
import org.eclipse.californium.elements.util.StringUtil;
import org.eclipse.californium.oscore.CoapOSException;
import org.eclipse.californium.oscore.OSCoreCtx;
import org.eclipse.californium.oscore.OSCoreCtxDB;
import org.eclipse.californium.oscore.OSException;

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

import net.i2p.crypto.eddsa.EdDSASecurityProvider;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.Security;

public class Util {

    /**
     *  Compute a ciphertext using the COSE Encrypt0 object
     * @param pretectedHeader   The elements to include in the COSE protected header, as a CBOR map
     * @param externalData   The data to use as external_aad
     * @param plaintext   The plaintext to encrypt
     * @param alg   The encryption algorithm to use
     * @param iv   The IV to use for encrypting
     * @param key   The symmetric key to use for encrypting
     * @return  the computed ciphertext, or null in case of invalid input
     */
	public static byte[] encrypt (CBORObject pretectedHeader, byte[] externalData, byte[] plaintext,
			                      AlgorithmID alg, byte[] iv, byte[] key) throws CoseException {
        
		if(pretectedHeader == null || externalData == null || plaintext == null || iv == null || key == null)
        	return null;       
		
        // The elements to include in the COSE protected header must be provided as a CBOR map 
        if(pretectedHeader.getType() != CBORType.Map)
        	return null;
                
        Encrypt0Message msg = new Encrypt0Message();
        
        // Set the protected header of the COSE object
        for(CBORObject label : pretectedHeader.getKeys()) {
        	msg.addAttribute(label, pretectedHeader.get(label), Attribute.PROTECTED);
        }
        
        msg.addAttribute(HeaderKeys.Algorithm, alg.AsCBOR(), Attribute.DO_NOT_SEND);
        msg.addAttribute(HeaderKeys.IV, iv, Attribute.DO_NOT_SEND);
        
        // Set the external_aad to use for the encryption process
        msg.setExternal(externalData);
       
        // Set the payload of the COSE object
        msg.SetContent(plaintext);
        
        // Debug print
        /*
        System.out.println("Protected attributes: " + msg.getProtectedAttributes().toString());
        System.out.println("aad                 : " + StringUtil.byteArray2HexString(msg.getExternal()));
        System.out.println("plaintext           : " + StringUtil.byteArray2HexString(msg.GetContent()));
        */
        
        // Perform the encryption
        msg.encrypt(key);
        
        // Debug print
        /*
        System.out.println("Encrypted content: " + StringUtil.byteArray2HexString(msg.getEncryptedContent()));
        */
        
        return msg.getEncryptedContent();
        
	}
	
    /**
     *  Decrypt a ciphertext using the COSE Encrypt0 object
     * @param pretectedHeader   The elements to include in the COSE protected header, as a CBOR map 
     * @param externalData   The data to use as external_aad
     * @param ciphertext   The ciphertext to decrypt
     * @param alg   The encryption algorithm to use
     * @param iv   The IV to use for decrypting
     * @param key   The symmetric key to use for decrypting
     * @return  the computed plaintext, or null in case of invalid input
     */
	public static byte[] decrypt (CBORObject pretectedHeader, byte[] externalData, byte[] ciphertext,
								  AlgorithmID alg, byte[] iv, byte[] key) throws CoseException {
        
		if(pretectedHeader == null || externalData == null || ciphertext == null || iv == null || key == null)
        	return null;       
		
        // The elements to include in the COSE protected header must be provided as a CBOR map
        if(pretectedHeader.getType() != CBORType.Map)
        	return null;
        
        Encrypt0Message msg = new Encrypt0Message();
        
        // Set the protected header of the COSE object
        for(CBORObject label : pretectedHeader.getKeys()) {
        	msg.addAttribute(label, pretectedHeader.get(label), Attribute.PROTECTED);
        }
        
        msg.addAttribute(HeaderKeys.Algorithm, alg.AsCBOR(), Attribute.DO_NOT_SEND);
        msg.addAttribute(HeaderKeys.IV, iv, Attribute.DO_NOT_SEND);
        
        // Set the external_aad to use for the signing process
        msg.setExternal(externalData);
       
        // Set the payload of the COSE object
        msg.setEncryptedContent(ciphertext);
        
        // Debug print
        /*
        System.out.println("Protected attributes: " + msg.getProtectedAttributes().toString());
        System.out.println("aad                 : " + StringUtil.byteArray2HexString(msg.getExternal()));
        System.out.println("payload             : " + StringUtil.byteArray2HexString(msg.GetContent()));
        */
        
        // Perform the encryption
        msg.decrypt(key);
        
        // Debug print
        /*
        System.out.println("Decrypted content: " + StringUtil.byteArray2HexString(msg.GetContent()));
        */
        
        return msg.GetContent();
        
	}
	
    /**
     *  Compute a signature using the COSE Sign1 object
     * @param idCredX   The ID of the public credential of the signer, as a CBOR map 
     * @param externalData   The data to use as external_aad
     * @param payload   The payload to sign
     * @param signKey   The private key to use for signing
     * @return  the computed signature, or null in case of invalid input
     */
	public static byte[] computeSignature (CBORObject idCredX, byte[] externalData, byte[] payload, OneKey signKey)
			                               throws CoseException {
        
		if(idCredX == null || externalData == null || payload == null || signKey == null)
        	return null;       
		
        // The ID of the public credential has to be a CBOR map ...
        if(idCredX.getType() != CBORType.Map)
        	return null;
        
        // ... and it cannot be empty
        if(idCredX.size() == 0)
        	return null;
        
        Sign1Message msg = new Sign1Message();
        
        // Set the protected header of the COSE object
        for(CBORObject label : idCredX.getKeys()) {
            // All good if the map has only one element, otherwise it needs to be rebuilt deterministically
        	msg.addAttribute(label, idCredX.get(label), Attribute.PROTECTED);
        }
        
		// Identify algorithm used from values in the key
		CBORObject alg = signKey.get(KeyKeys.Algorithm);
		if (alg == null) {
			alg = determineKeyAlgorithm(signKey).AsCBOR();
		}
		msg.addAttribute(HeaderKeys.Algorithm, alg, Attribute.DO_NOT_SEND);
        
        // Set the external_aad to use for the signing process
        msg.setExternal(externalData);
       
        // Set the payload of the COSE object
        msg.SetContent(payload);
        
        // Debug print
        /*
        System.out.println("Protected attributes: " + msg.getProtectedAttributes().toString());
        System.out.println("aad                 : " + StringUtil.byteArray2HexString(msg.getExternal()));
        System.out.println("payload             : " + StringUtil.byteArray2HexString(msg.GetContent()));
        */
        
        // Compute the signature
        msg.sign(signKey);
        
        // Serialize the COSE Sign1 object as a CBOR array
        CBORObject myArray = msg.EncodeToCBORObject();
		
        // Debug print
        /*
        System.out.println("\nCBOR array with signature: " + myArray.toString() + "\n");
        */
        
        // Return the actual signature, as fourth element of the CBOR array
		return myArray.get(3).GetByteString();
		
	}
	
	/**
	 * Identifies the algorithm used by a key from the curve parameters.
	 * 
	 * @param key the key
	 * @return the algorithm used
	 */
	static AlgorithmID determineKeyAlgorithm(OneKey key) {

		if (key.get(KeyKeys.OKP_Curve) == KeyKeys.OKP_Ed25519) {
			return AlgorithmID.EDDSA;
		} else if (key.get(KeyKeys.EC2_Curve) == KeyKeys.EC2_P256) {
			return AlgorithmID.ECDSA_256;
		} else if (key.get(KeyKeys.EC2_Curve) == KeyKeys.EC2_P384) {
			return AlgorithmID.ECDSA_384;
		} else if (key.get(KeyKeys.EC2_Curve) == KeyKeys.EC2_P521) {
			return AlgorithmID.ECDSA_512;
		} else {
			return null;
		}
	}

    /**
     *  Verify a signature using the COSE Sign1 object
     * @param signature   The signature to verify
     * @param idCredX   The ID of the public credential of the signer, as a CBOR map
     * @param externalData   The data to use as external_aad
     * @param payload   The payload to sign
     * @param publicKey   The public key to use for verifying the signature
     * @return  true is the signature is valid, false if the signature is not valid or the input is not valid 
     */
	public static boolean verifySignature (byte[] signature, CBORObject idCredX, byte[] externalData, byte[] payload, OneKey publicKey)
			                               throws CoseException {
	    
        if(signature == null || idCredX == null || externalData == null || payload == null || publicKey == null)
        	return false;
        
        // The ID of the public credential has to be a CBOR map ...
        if (idCredX.getType() != CBORType.Map)
        	return false;
        
        // ... and it cannot be empty
        if (idCredX.size() == 0)
        	return false;
        
        // Prepare the raw COSE Sign1 object as a CBOR array
        CBORObject myArray = CBORObject.NewArray();
        
        // Add the Protected header, i.e. the provided CBOR map wrapped into a CBOR byte string
        myArray.Add(idCredX.EncodeToBytes());
        
        // Add the Unprotected, i.e. a CBOR map specifying the signature algorithm
        CBORObject myMap = CBORObject.NewMap();
        myMap.Add(KeyKeys.Algorithm, publicKey.get(KeyKeys.Algorithm));
        myArray.Add(myMap);
        
        // Add the signed payload
        myArray.Add(payload);
        
        // Add the signature to verify
        myArray.Add(signature);
                
        myArray = CBORObject.FromObjectAndTag(myArray, MessageTag.Sign1.value);
  
        // Debug print
        /*
        System.out.println("\nCBOR array with signature: " + myArray.toString() + "\n");
        */
        
        // Build the COSE Sign1 object from the raw version
        Sign1Message msg = (Sign1Message) Message.DecodeFromBytes(myArray.EncodeToBytes(), MessageTag.Sign1);
        
        // Set the external_aad to use for the signing process
        msg.setExternal(externalData);
        
        // Debug print
        /*
        System.out.println("Protected attributes: " + msg.getProtectedAttributes().toString());
        System.out.println("aad                 : " + StringUtil.byteArray2HexString(msg.getExternal()));
        System.out.println("payload             : " + StringUtil.byteArray2HexString(msg.GetContent()));
        */
        
        // Verify the signature
        return msg.validate(publicKey);
       
	}
	
    /**
     *  Compute a hash value using the specified algorithm 
     * @param input   The content to hash
     * @param algorithm   The name of the hash algorithm to use
     * @return  the computed hash, or null in case of invalid input
     */
	public static byte[] computeHash (byte[] input, String algorithm) throws NoSuchAlgorithmException {
		
		if (input == null)
			return null;
		
		MessageDigest myDigest;
		
		if (algorithm.equals("SHA-256"))
			myDigest = MessageDigest.getInstance("SHA-256");
		else if (algorithm.equals("SHA-512"))
			myDigest = MessageDigest.getInstance("SHA-512");
		else
			return null;
		
		myDigest.reset();
		myDigest.update(input);
		return myDigest.digest();
		
	}

    /**
     *  Prepare a CBOR sequence, given a list of CBOR Objects as input
     * @param objectList   The CBOR Objects to compose the CBOR sequence
     * @return  the CBOR sequence, as an array of bytes
     */
	public static byte[] buildCBORSequence (List<CBORObject> objectList) {
		
		int sequenceLength = 0;
		byte[] mySequence = new byte[0];
		
		List<CBORObject> serializationList = new ArrayList<CBORObject>();
		
		for (int i = 0; i < objectList.size(); i++) {
			byte[] objBytes = objectList.get(i).EncodeToBytes();			
			serializationList.add(CBORObject.FromObject(objBytes));
			sequenceLength += objBytes.length;
		}
		
		int offset = 0;
		mySequence = new byte[sequenceLength];
		
		for (int i = 0; i < serializationList.size(); i++) {
			byte[] objBytes = serializationList.get(i).GetByteString();
			System.arraycopy(objBytes, 0, mySequence, offset, objBytes.length);
			offset += objBytes.length;
		}
		
		return mySequence;
		
	}
	
    /**
     *  Concatenate byte arrays, each of which wrapped as a CBOR byte strings
     * @param objectList   The list of CBOR byte strings wrapping the byte arrays to concatenate
     * @return  the concatenation of all the byte arrays taken as input
     */
	public static byte[] concatenateByteArrays (List<CBORObject> byteStrings) {
		
		int outputLength = 0;
		byte[] myOutput = new byte[0];
		
		if (byteStrings == null || byteStrings.size() == 0)
			return null;
		
		for (int i = 0; i < byteStrings.size(); i++) {
			if (byteStrings.get(i).getType() != CBORType.ByteString)
				return null;
			outputLength += byteStrings.get(i).GetByteString().length;
		}
		
		int offset = 0;
		myOutput = new byte[outputLength];
		
		for (int i = 0; i < byteStrings.size(); i++) {
			byte[] objBytes = byteStrings.get(i).GetByteString();
			System.arraycopy(objBytes, 0, myOutput, offset, objBytes.length);
			offset += objBytes.length;
		}
		
		return myOutput;
		
	}
	
    /**
     *  Build a CBOR map, ensuring the exact order of its entries
     * @param labelList   The labels of the CBOR map entries, already prepared as CBOR objects (uint or tstr)
     * @param valueList   The CBOR Objects to include as values of the CBOR map entries
     * @return  the binary serialization of the CBOR map, or null in case of invalid input
     */
	public static byte[] buildDeterministicCBORMap (List<CBORObject> labelList, List<CBORObject> valueList) {
		
		if (labelList.size() != valueList.size())
			return null;
		
		int numEntries = labelList.size(); 
		
		if (numEntries == 0) {
			CBORObject emptyMap = CBORObject.NewMap();
			return emptyMap.EncodeToBytes();
		}
		
		byte[] mapContent = new byte[0];
		List<CBORObject> pairList = new ArrayList<CBORObject>();
		
		for(int i = 0; i < numEntries; i++) {
			if (labelList.get(i) == null || valueList.get(i) == null)
				return null;
			
			if (labelList.get(i).getType() != CBORType.Integer &&
					labelList.get(i).getType() != CBORType.TextString)
				return null;
			
			pairList.add(labelList.get(i));
			pairList.add(valueList.get(i));
		}
		mapContent = buildCBORSequence(pairList);
		
		// Encode the number N of map entries as a CBOR integer
		CBORObject numEntriesCBOR = CBORObject.FromObject(numEntries);
		byte[] mapHeader = numEntriesCBOR.EncodeToBytes();
		// Change the first byte so that the result is the header of a CBOR map with N entries
		// 0b000_xxxxx & 0b000_11111 --> 0b101_xxxxx  , x ={0,1}
		mapHeader[0] = (byte) (mapHeader[0] & intToBytes(31)[0]);
		byte mapTypeValue = (byte) 0b10100000;
		mapHeader[0] |= mapTypeValue;
		
		byte[] serializedMap = new byte[mapHeader.length + mapContent.length];
		System.arraycopy(mapHeader, 0, serializedMap, 0, mapHeader.length);
		System.arraycopy(mapContent, 0, serializedMap, mapHeader.length, mapContent.length);
		
		return serializedMap;
		
	}
	
    /**
     *  Compute the bitwise xor between two byte arrays of equal length
     * @param arg1   The first byte array
     * @param arg2   The second byte array
     * @return  a byte including the xor result, or null in case of invalid input
     */
	public static byte[] arrayXor (byte[] arg1, byte[] arg2) {
		
		if(arg1 == null || arg2 == null)
			return null;
		
		if(arg1.length != arg2.length)
			return null;
		
		if(arg1.length == 0)
			return null;
		
		int length = arg1.length;
		byte[] result = new byte[length];
		
		for (int i = 0; i < length; i ++) {
			result[i] = (byte) (arg1[i] ^ arg2[i]);
		}
		
		return result;
		
	}
	
    /**
     *  Convert a positive integer into a byte array of minimal size.
     *  The positive integer can be up to 2,147,483,647 
     * @param num
     * @return  the byte array
     */
    public static byte[] intToBytes(final int num) {

    	// Big-endian
    	if (num < 0)
    		return null;
        else if (num < 256) {
            return new byte[] { (byte) (num) };
        } else if (num < 65536) {
            return new byte[] { (byte) (num >>> 8), (byte) num };
        } else if (num < 16777216) {
            return new byte[] { (byte) (num >>> 16), (byte) (num >>> 8), (byte) num };
        } else { // up to 2,147,483,647
            return new byte[]{ (byte) (num >>> 24), (byte) (num >>> 16), (byte) (num >>> 8), (byte) num };
        }
    	
    	// Little-endian
    	/*
    	if (num < 0)
    		return null;
        else if (num < 256) {
            return new byte[] { (byte) (num) };
        } else if (num < 65536) {
            return new byte[] { (byte) num, (byte) (num >>> 8) };
        } else if (num < 16777216){
            return new byte[] { (byte) num, (byte) (num >>> 8), (byte) (num >>> 16) };
        } else{ // up to 2,147,483,647
            return new byte[] { (byte) num, (byte) (num >>> 8), (byte) (num >>> 16), (byte) (num >>> 24) };
        }
    	*/
    	
    }
	
    /**
     * Convert a byte array into an equivalent unsigned integer.
     * The input byte array can be up to 4 bytes in size.
     *
     * N.B. If the input array is 4 bytes in size, the returned integer may be negative! The calling method has to check, if relevant!
     * 
     * @param bytes 
     * @return   the converted integer
     */
    public static int bytesToInt(final byte[] bytes) {
    	
    	if (bytes.length > 4)
    		return -1;
    	
    	int ret = 0;

    	// Big-endian
    	for (int i = 0; i < bytes.length; i++)
    		ret = ret + (bytes[bytes.length - 1 - i] & 0xFF) * (int) (Math.pow(256, i));

    	/*
    	// Little-endian
    	for (int i = 0; i < bytes.length; i++)
    		ret = ret + (bytes[i] & 0xFF) * (int) (Math.pow(256, i));
    	*/
    	
    	return ret;
    	
    }
    
    /**
     * Get an available Connection Identifier to offer to the other peer
     *  
     * @param usedConnectionIds   The set of already allocated Connection Identifiers
     * @param db   The database of OSCORE security contexts when using EDHOC to key OSCORE, it can be null
     * @param forbiddenIdentifier   The connection identifier C_I, it is null when the caller is the Initiator
     * @return   the newly allocated connection identifier, or null in case of errors or if no connection identifier is available
     */
    public static byte[] getConnectionId(Set<CBORObject> usedConnectionIds, OSCoreCtxDB db, byte[] forbiddenIdentifier) {
    	
    	if (usedConnectionIds == null)
    		return null;
    
    	synchronized(usedConnectionIds) {
    		
    		return allocateConnectionId(usedConnectionIds, db, forbiddenIdentifier);
    		
    	}
    	
    }
        
    /**
     * Actually allocate an available Connection Identifier to offer to the other peer
     *  
     * @param usedConnectionIds   The set of already allocated Connection Identifiers
     * @param db   The database of OSCORE security contexts when using EDHOC to key OSCORE, it can be null
     * @param forbiddenIdentifier   The connection identifier C_I, it is null when the caller is the Initiator
     * @return   the newly allocated connection identifier, or null in case of errors or if no connection identifiers are available
     */
     static byte[] allocateConnectionId(Set<CBORObject> usedConnectionIds,
    										   OSCoreCtxDB db, byte[] forbiddenIdentifier) {

        byte[] identifier = null;
         
        /* Check if the empty connection identifier 0x is available */
        
	    identifier = new byte[0];
	    identifier = checkAndCommitConnectionId(identifier, usedConnectionIds, db, forbiddenIdentifier);
        if (identifier != null)
        	return identifier;
        
    	
        /* Check if a 1-byte connection identifier is available */
        
    	// Check the range encoding the values 0..23. These connection identifiers
    	// encode on the wire as a CBOR integer C_X, with numeric value 0..23
        for (int i = 0; i <= 23; i++) {
        	
        	identifier = new byte[1];
        	identifier[0] = (byte) (i & 0xff);
    	    identifier = checkAndCommitConnectionId(identifier, usedConnectionIds, db, forbiddenIdentifier);
            if (identifier != null)
            	return identifier;
        	
    	}
        
    	// Check the range encoding the values 32..55. These connection identifiers
    	// encode on the wire as a CBOR integer C_X, with numeric value -24..-1
        for (int i = 32; i <= 55; i++) {
        	
        	identifier = new byte[1];
        	identifier[0] = (byte) (i & 0xff);
    	    identifier = checkAndCommitConnectionId(identifier, usedConnectionIds, db, forbiddenIdentifier);
            if (identifier != null)
            	return identifier;
        	
    	}

    	// Check the remaining ranges 24..31 and 56..255
        for (int i = 24; i <= 255; i++) {
        	
        	// Skip this range as it was already checked before
        	if (i >= 32 && i <= 55)
        		continue;
        	
        	identifier = new byte[1];
        	identifier[0] = (byte) (i & 0xff);
    	    identifier = checkAndCommitConnectionId(identifier, usedConnectionIds, db, forbiddenIdentifier);    	    	
            if (identifier != null)
            	return identifier;
        	
    	}
    	
    	
    	/* Check if a 2-byte connection identifier is available */
        
        for (int i = 0; i <= 255; i++) {
        	
            identifier = new byte[2];
        	identifier[0] = (byte) (i & 0xff);
        	
        	for (int j = 0; j <= 255; j++) {
        		identifier[1] = (byte) (j & 0xff);
        	    byte[] retIdentifier = checkAndCommitConnectionId(identifier, usedConnectionIds, db, forbiddenIdentifier);
                if (retIdentifier != null) {
                	return retIdentifier;
                }
		    	
        	}
        	
    	}
      
    	/* Check if a 3-byte connection identifier is available */
        
        for (int i = 0; i <= 255; i++) {

        	identifier = new byte[3];
        	identifier[0] = (byte) (i & 0xff);
        	
        	for (int j = 0; j <= 255; j++) {
        		
        		identifier[1] = (byte) (j & 0xff);
            	
            	for (int k = 0; k <= 255; k++) {
            		
            		identifier[2] = (byte) (k & 0xff);
            	    byte[] retIdentifier = checkAndCommitConnectionId(identifier, usedConnectionIds, db, forbiddenIdentifier);
                    if (retIdentifier != null)
                    	return retIdentifier;
        		}
		    	
        	}
        	
    	}
        
        return null;
    	
    }
    
    /**
     * Check if a Connection Identifier is available to offer to the other peer
     *
     * @param identifier   The candidate connection identifier to use as OSCORE Recipient ID
     * @param usedConnectionIds   The set of already allocated Connection Identifiers
     * @param db   The database of OSCORE security contexts when using EDHOC to key OSCORE, it can be null
     * @param forbiddenIdentifier   The connection identifier C_I, it is null when the caller is the Initiator
     * @return   the newly allocated connection identifier, or null in case it is not available
     */
    private static byte[] checkAndCommitConnectionId(byte[] identifier, Set<CBORObject> usedConnectionIds,
    												 OSCoreCtxDB db, byte[] forbiddenIdentifier) {
    	
        if (db != null) {
        	// EDHOC is used for keying OSCORE
        	synchronized (db) {
        		return commitConnectionIdForOSCORE(identifier, db, usedConnectionIds, forbiddenIdentifier);
        	}
        }
        else {
    	    CBORObject identifierCbor = CBORObject.FromObject(identifier);
        	
        	if (usedConnectionIds.contains(identifierCbor) == false) {
	    		usedConnectionIds.add(identifierCbor);
	    		return identifier;
        	}
        }
        
        return null;
    	
    }
    
    /**
     * Check for the availability of an OSCORE Recipient ID and the corresponding, identical EDHOC Connection Identifier.
     * If they are both available, mark them as used and return the Connection Identifier. Otherwise, return null.
     *  
     * @param recipientID   The candidate connection identifier to use as OSCORE Recipient ID
     * @param db   The database of OSCORE security contexts when using EDHOC to key OSCORE
     * @param usedConnectionIds   The set of already allocated Connection Identifiers
     * @param forbiddenIdentifier   The connection identifier to avoid, it can be null if there is no constraint
     * @return   the newly allocated connection identifier, or null in case of errors or unavailability
     */
    private static byte[] commitConnectionIdForOSCORE(byte[] recipientId, OSCoreCtxDB db,
    												  Set<CBORObject> usedConnectionIds, byte[] forbiddenIdentifier) {
    
    	OSCoreCtx ctx = null;
    	
    	if (recipientId == null || db == null)
    		return null;
        try {
            ctx = db.getContext(recipientId, null);
        } catch (CoapOSException e) {
        	// Found multiple OSCORE Security Contexts with the same Recipient ID
        	return null;
        }
    	if (ctx == null) {
    		// The Recipient ID is available for OSCORE, i.e., it is
    		// currently not used in the sets of all the Recipient Contexts
    		
        	// The EDHOC Connection Identifier coincides with the one to avoid (i.e., C_I offered by the Initiator) 
        	if (forbiddenIdentifier != null && Arrays.equals(recipientId, forbiddenIdentifier) == true)
        		return null;
        	CBORObject identifierCbor = CBORObject.FromObject(recipientId);
        	if (usedConnectionIds.contains(identifierCbor) == false) {
	        	// The corresponding EDHOC Connection Identifier is also available
        		
        		usedConnectionIds.add(identifierCbor);
        		
        		// Allocate a non-finalized OSCORE Security Context, to have the Recipient ID as taken
        		try {
        			byte[] emptyArray = new byte[0];
    				ctx = new OSCoreCtx(emptyArray, true, null, null, recipientId, AlgorithmID.HKDF_HMAC_SHA_256, 0, null, null, 0);
    				db.addContext(ctx);
    			} catch (OSException | NullPointerException e) {
    				System.err.println("Error when allocating an EDHOC Connection Identifier to use as "
    						           + "OSCORE Recipient ID" + e.getMessage());
    				
    				// Rollback
    				usedConnectionIds.remove(identifierCbor);
    				if (ctx != null)
    					db.removeContext(ctx);
    				
    				return null;
    			}
        		
        		return recipientId;
        	}
    	}
    	
    	return null;
    	
    }
    
    /**
     * Deallocate a Connection Identifier previously locked to offer to a peer
     * Note that, if this was an OSCORE Recipient ID, the Recipient ID itself will not be deallocated
     *  
     * @param connectionId   The Connection Identifier to release
     * @param usedConnectionIds   The set of already allocated Connection Identifiers
     * @param db   The database of OSCORE security contexts when using EDHOC to key OSCORE, it can be null
     */
    public static void releaseConnectionId (byte[] connectionId, Set<CBORObject> usedConnectionIds, OSCoreCtxDB db) {
    	
    	if (connectionId == null)
    		return;
    	
    	synchronized (usedConnectionIds) {
    		CBORObject connectionIdCbor = CBORObject.FromObject(connectionId); 
    		usedConnectionIds.remove(connectionIdCbor);
    	}
    	
    	if (db != null) {
        	// EDHOC is used for keying OSCORE. The EDHOC connection identifier is the OSCORE Recipient ID.
    		
    		synchronized (db) {
	    		OSCoreCtx ctx = null;
				try {
					ctx = db.getContext(connectionId, null);
				} catch (CoapOSException e) {
					System.err.println("Found multiple OSCORE Security Contexts with the same Recipient ID " +
									   StringUtil.byteArray2HexString(connectionId) + "\n" + e.getMessage());
				}
	    		if (ctx != null) {
	    			db.removeContext(ctx);
	    		}
    		}
    			
    	}
    	
    }
    
	/**
	 * Remove an EDHOC session from the list of active sessions; release the used Connection Identifier; invalidate the session
	 * @param session   The EDHOC session to invalidate
	 * @param connectionIdentifier   The Connection Identifier used for the session to invalidate
	 * @param edhocSessions   The set of active EDHOC sessions of the recipient
     * @param usedConnectionIds   The collection of already allocated Connection Identifiers
	 */
	public static void purgeSession(EdhocSession session, byte[] connectionIdentifier,
									HashMap<CBORObject, EdhocSession> edhocSessions, Set<CBORObject> usedConnectionIds) {
		if (session != null) {
			CBORObject connectionIdentifierCbor = CBORObject.FromObject(connectionIdentifier);
		    edhocSessions.remove(connectionIdentifierCbor);
		    releaseConnectionId(connectionIdentifier, usedConnectionIds, session.getOscoreDb());
		    
		    session.deleteTemporaryMaterial();
		    if(session.getSideProcessor() != null)
		    	session.getSideProcessor().setEdhocSession(null);
		    
		    session = null;
		}
	}
    
    /**
     * Generate an asymmetric key pair, according to the specified elliptic curve
     *  
     * @param keyCurve   The elliptic curve
     * @return    The generated asymmetric key pair, or null in case of error
     */
    public static OneKey generateKeyPair (int keyCurve) {
    	
    	OneKey keyPair = null;
    	
		// Generate the new long-term asymmetric key pair 
		try {
	 		if (keyCurve == KeyKeys.EC2_P256.AsInt32()) {
	 			keyPair = OneKey.generateKey(AlgorithmID.ECDSA_256);
	 		}
	 		else if (keyCurve == KeyKeys.OKP_Ed25519.AsInt32()) {
	    		Provider EdDSA = new EdDSASecurityProvider();
	        	Security.insertProviderAt(EdDSA, 1);
	    		keyPair = OneKey.generateKey(AlgorithmID.EDDSA);
	    	}
	 		else if (keyCurve == KeyKeys.OKP_X25519.AsInt32()) {
				keyPair = SharedSecretCalculation.generateCurve25519OneKey();
	    	}
			
		} catch (CoseException e) {
			System.err.println("Error while generating the key pair");
			return null;
		}
		
		// Print out the base64 serialization of the key pair
		/*
		byte[] keyPairBytes = keyPair.EncodeToBytes();
    	String testKeyBytesBase64 = Base64.encodeBytes(keyPairBytes);
    	System.out.println(testKeyBytesBase64);
    	
    	System.out.println(keyCurve);
    	System.out.println(keyPair.AsCBOR());
    	*/
		
		// Print out the base64 serialization of the public key only
		/*
    	OneKey testPublicKey = keyPair.PublicKey();
    	byte[] testPublicKeyBytes = testPublicKey.EncodeToBytes();
    	String testPublicKeyBytesBase64 = Base64.encodeBytes(testPublicKeyBytes);
    	System.out.println(testPublicKeyBytesBase64);
    	
    	System.out.println(keyCurve);
    	System.out.println(testPublicKey.AsCBOR());
    	*/
    	
    	return keyPair;
    	
    }
    
    /**
     * Print out a byte string in a convenient diagnostic way
     *  
     * @param header   First readable part of the output
     * @param bstr   Actual binary content to print
     */
    public static void nicePrint(String header, byte[] content) {
    	
    	System.out.println(header + " (" + (content.length) + " bytes):");
    	
    	String contentStr = StringUtil.byteArray2HexString(content).toLowerCase();
    	for (int i = 0; i < (content.length * 2); i++) {
    		if ((i != 0) && (i % 20) == 0)
    	    	System.out.println();
    		
        	System.out.print(contentStr.charAt(i));
    		if ((i % 2) == 1)
    	    	System.out.print(" ");
    	}
    	
    	System.out.println("\n");

    }
    
	public static OneKey makeSingleKey(OneKey keyPair, boolean isPrivate) {
		
	    CBORObject key = CBORObject.NewMap();
        OneKey coseKey = null;
	    
        key.Add(KeyKeys.KeyType.AsCBOR(), keyPair.get(KeyKeys.KeyType));
        
	    if (isPrivate) {
	    	if(keyPair.get(KeyKeys.KeyType) == KeyKeys.KeyType_EC2) {	    		
		        key.Add(KeyKeys.EC2_Curve.AsCBOR(), keyPair.get(KeyKeys.EC2_Curve));
		        key.Add(KeyKeys.EC2_D.AsCBOR(), keyPair.get(KeyKeys.EC2_D));

	    	}
	    	else if(keyPair.get(KeyKeys.KeyType) == KeyKeys.KeyType_OKP) {	    		
		        key.Add(KeyKeys.OKP_Curve.AsCBOR(), keyPair.get(KeyKeys.OKP_Curve));
		        key.Add(KeyKeys.OKP_D.AsCBOR(), keyPair.get(KeyKeys.OKP_D));
	    	}
	        
	    }
	    else {
	    	if(keyPair.get(KeyKeys.KeyType) == KeyKeys.KeyType_EC2) {
		        key.Add(KeyKeys.EC2_Curve.AsCBOR(), KeyKeys.EC2_P256);
		        key.Add(KeyKeys.EC2_X.AsCBOR(), keyPair.get(KeyKeys.EC2_X));
		        key.Add(KeyKeys.EC2_Y.AsCBOR(), keyPair.get(KeyKeys.EC2_Y));
	    	}
	    	else if(keyPair.get(KeyKeys.KeyType) == KeyKeys.KeyType_OKP) {	    		
		        key.Add(KeyKeys.OKP_Curve.AsCBOR(), keyPair.get(KeyKeys.OKP_Curve));
		        key.Add(KeyKeys.OKP_X.AsCBOR(), keyPair.get(KeyKeys.OKP_X));
	    	}
	    }
	    
        try {
        	coseKey = new OneKey(key);
		} catch (CoseException e) {
			System.err.println(e.getMessage());
			System.err.println("Error while generating the COSE key");
		}
	    return coseKey;
		
	}
	
    /**
     * Build SUITES_R
     *  
     * @param cipherSuites   The list of supported cipher suites for this peer to include in SUITES_R
     * @return SUITES_R, as a CBOR object
     */
	public static CBORObject buildSuitesR(List<Integer> cipherSuites) {
		
		CBORObject suitesR;
		
		if (cipherSuites.size() == 1) {
			int suite = cipherSuites.get(0).intValue();
			suitesR = CBORObject.FromObject(suite);
		}
		// This peer supports multiple cipher suites
		else {
			suitesR = CBORObject.NewArray();
			for (Integer i : cipherSuites) {
				suitesR.Add(i.intValue());
			}
		}
		
		return suitesR;
		
	}
	
    /**
     * Build an ID_CRED using 'kcwt', with value a CWT as a CBOR array,
     *  
     * @param cwt   The CWT to use
     * @return The ID_CRED, as a CBOR map
     */
	public static CBORObject buildIdCredKcwt(CBORObject cwt) {
		
		CBORObject idCred = CBORObject.NewMap();
		idCred.Add(Constants.COSE_HEADER_PARAM_KCWT, cwt);
		return idCred;
		
	}
	
    /**
     * Build an ID_CRED using 'kccs', with value a CWT Claims Set (CCS) as a CBOR map
     *  
     * @param claimSet   The CWT Claims Set to use, as a CBOR map
     * @return The ID_CRED, as a CBOR map
     */
	public static CBORObject buildIdCredKccs(CBORObject claimSet) {
		
		CBORObject idCred = CBORObject.NewMap();
		idCred.Add(Constants.COSE_HEADER_PARAM_KCCS, claimSet);
		return idCred;
		
	}
	
    /**
     * Build an ID_CRED using 'kid'
     *  
     * @param kid   The kid to use
     * @return The ID_CRED, as a CBOR map
     */
	public static CBORObject buildIdCredKid(byte[] kid) {
		
		CBORObject idCred = CBORObject.NewMap();
		idCred.Add(HeaderKeys.KID.AsCBOR(), kid);
		return idCred;
		
	}
	
    /**
     * Build an ID_CRED using 'x5chain'
     *  
     * @param cert   The binary serialization of the x509 certificate
     * @return The ID_CRED, as a CBOR map
     */
	public static CBORObject buildIdCredX5chain(byte[] cert) {
		
		CBORObject idCred = CBORObject.NewMap();
		
		// Since a single certificate is specified,
		// the map element encodes it as a CBOR byte string
		idCred.Add(Constants.COSE_HEADER_PARAM_X5CHAIN, cert);
		return idCred;
		
	}
	
    /**
     * Build an ID_CRED using 'x5t'
     *  
     * @param cert   The binary serialization of the x509 certificate
     * @return The ID_CRED, as a CBOR map
     */
	public static CBORObject buildIdCredX5t(byte[] cert) {
		
		CBORObject idCred = CBORObject.NewMap();
		
		CBORObject idCredElem = CBORObject.NewArray();
		idCredElem.Add(-15); // SHA-2 256-bit Hash truncated to 64-bits
		byte[] hash = null;
		try {
			hash = Util.computeHash(cert, "SHA-256");
		} catch (NoSuchAlgorithmException e) {
			System.err.println("Error while hashing the x509 certificate: " + e.getMessage());
			return null;
		}
		if (hash == null) {
			return null;
		}
		byte[] truncatedHash = new byte[8];
		System.arraycopy(hash, 0, truncatedHash, 0, 8);
		idCredElem.Add(truncatedHash);
		
		idCred.Add(Constants.COSE_HEADER_PARAM_X5T, idCredElem);
				
		return idCred;
		
	}
	
    /**
     * Build an ID_CRED using 'x5u'
     *  
     * @param uri   The URI pointing to the certificate
     * @return The ID_CRED, as a CBOR map
     */
	public static CBORObject buildIdCredX5u(String uri) {
		
		CBORObject idCred = CBORObject.NewMap();
		idCred.Add(Constants.COSE_HEADER_PARAM_X5U, uri);
		return idCred;
		
	}
	
    /**
     * Build an ID_CRED to use with 'kid'
     *  
     * @param identityKey   The identity key to encode as CRED
     * @param subjectName   The subject name associated to this key, it can be an empty string
     * @return The CRED, as a byte serialization of a deterministic CBOR map
     */
	public static byte[] buildCredRawPublicKey(OneKey identityKey, String subjectName) {
		
		if (identityKey  == null || subjectName == null)
			return null;
		
        List<CBORObject> labelList = new ArrayList<>();
        List<CBORObject> valueList = new ArrayList<>();
        labelList.add(KeyKeys.KeyType.AsCBOR());
        valueList.add(identityKey.get(KeyKeys.KeyType));
        if (identityKey.get(KeyKeys.KeyType) == KeyKeys.KeyType_OKP) {
            labelList.add(KeyKeys.OKP_Curve.AsCBOR());
            valueList.add(identityKey.get(KeyKeys.OKP_Curve));
            labelList.add(KeyKeys.OKP_X.AsCBOR());
            valueList.add(identityKey.get(KeyKeys.OKP_X));
		}
		else if (identityKey.get(KeyKeys.KeyType) == KeyKeys.KeyType_EC2) {
            labelList.add(KeyKeys.EC2_Curve.AsCBOR());
            valueList.add(identityKey.get(KeyKeys.EC2_Curve));
            labelList.add(KeyKeys.EC2_X.AsCBOR());
            valueList.add(identityKey.get(KeyKeys.EC2_X));
            labelList.add(KeyKeys.EC2_Y.AsCBOR());
            valueList.add(identityKey.get(KeyKeys.EC2_Y));
		}
		else {
			return null;
		}
        labelList.add(CBORObject.FromObject("subject name"));
        valueList.add(CBORObject.FromObject(subjectName));
        return Util.buildDeterministicCBORMap(labelList, valueList);
		
	}
	
    /**
     * Build an ID_CRED to use with 'kid2', with value a CWT Claims Set (CCS) 
     *  
     * @param identityKey   The identity key to encode as CRED
     * @param subjectName   The subject name associated to this key, it can be an empty string
     * @param kid   The key identifier associated to this key
     * @return The CRED, as a byte serialization of a CBOR map
     */
	public static byte[] buildCredRawPublicKeyCcs(OneKey identityKey, String subjectName, CBORObject kid) {
		
		if (identityKey  == null || subjectName == null)
			return null;
		
		CBORObject coseKeyMap = CBORObject.NewOrderedMap();
		coseKeyMap.Add(KeyKeys.KeyType.AsCBOR(), identityKey.get(KeyKeys.KeyType));
		coseKeyMap.Add(KeyKeys.KeyId.AsCBOR(), kid);
		if (identityKey.get(KeyKeys.KeyType) == KeyKeys.KeyType_OKP) {
			coseKeyMap.Add(KeyKeys.OKP_Curve.AsCBOR(), identityKey.get(KeyKeys.OKP_Curve));
			coseKeyMap.Add(KeyKeys.OKP_X.AsCBOR(), identityKey.get(KeyKeys.OKP_X));
		}
		else if (identityKey.get(KeyKeys.KeyType) == KeyKeys.KeyType_EC2) {
			coseKeyMap.Add(KeyKeys.EC2_Curve.AsCBOR(), identityKey.get(KeyKeys.EC2_Curve));
			coseKeyMap.Add(KeyKeys.EC2_X.AsCBOR(), identityKey.get(KeyKeys.EC2_X));
			coseKeyMap.Add(KeyKeys.EC2_Y.AsCBOR(), identityKey.get(KeyKeys.EC2_Y));
		}
		else {
			return null;
		}
		
		CBORObject cnfMap = CBORObject.NewOrderedMap();
		cnfMap.Add(Constants.CWT_CNF_COSE_KEY, coseKeyMap);
		
		CBORObject claimSetMap = CBORObject.NewOrderedMap();
		claimSetMap.Add(Constants.CWT_CLAIMS_SUB, subjectName);
		claimSetMap.Add(Constants.CWT_CLAIMS_CNF, cnfMap);

		System.out.println("CCS serialization: " + StringUtil.byteArray2HexString(claimSetMap.EncodeToBytes()));
		
        return claimSetMap.EncodeToBytes();
		
	}
    
    /**
     * Check that a signature key is compliant with the selected cipher suite
     *  
     * @param identityKey   The signature key to check against the selected cipher suite
     * @param selectedCipherSuite   The selected cipher suite used in an EDHOC session
     * @return True in case the key complies with the selected cipher suite, or false otherwise
     */
	public static boolean checkSignatureKeyAgainstCipherSuite(OneKey key, int selectedCipherSuite) {
			
		
		if (selectedCipherSuite == Constants.EDHOC_CIPHER_SUITE_0 || selectedCipherSuite == Constants.EDHOC_CIPHER_SUITE_1) {
			
				if (key.get(KeyKeys.KeyType) != KeyKeys.KeyType_OKP) {
					System.err.println("Invalid key type - Expected key type: OKP");
					return false;
				}
				
			if (key.get(KeyKeys.OKP_Curve) != KeyKeys.OKP_Ed25519) {
				System.err.println("Invalid OKP curve - Expected curve: Ed25519");
				return false;
			}
			
		}
		if (selectedCipherSuite == Constants.EDHOC_CIPHER_SUITE_2 || selectedCipherSuite == Constants.EDHOC_CIPHER_SUITE_3) {
				
			if (key.get(KeyKeys.KeyType) != KeyKeys.KeyType_EC2) {
				System.err.println("Invalid key type - Expected key type: EC2");
				return false;
			}
				
			if (key.get(KeyKeys.EC2_Curve) != KeyKeys.EC2_P256) {
				System.err.println("Invalid EC2 curve - Expected curve: P-256");
				return false;
			}
				
		}
				
		return true;
		
	}
	
    /**
     * Check that a Diffie-Hellman key is compliant with the selected cipher suite
     *  
     * @param identityKey   The signature key to check against the selected cipher suite
     * @param selectedCipherSuite   The selected cipher suite used in an EDHOC session
     * @return True in case the key complies with the selected cipher suite, or false otherwise
     */
	public static boolean checkDiffieHellmanKeyAgainstCipherSuite(OneKey key, int selectedCipherSuite) {
			
		if (selectedCipherSuite == Constants.EDHOC_CIPHER_SUITE_0 || selectedCipherSuite == Constants.EDHOC_CIPHER_SUITE_1) {
		    
			if (key.get(KeyKeys.KeyType) != KeyKeys.KeyType_OKP) {
				System.err.println("Invalid key type - Expected key type: OKP");
				return false;
			}
				
			if (key.get(KeyKeys.OKP_Curve) != KeyKeys.OKP_X25519) {
				System.err.println("Invalid OKP curve - Expected curve: Ed25519");
				return false;
			}
			
		}
		if (selectedCipherSuite == Constants.EDHOC_CIPHER_SUITE_2 || selectedCipherSuite == Constants.EDHOC_CIPHER_SUITE_3) {
				
			if (key.get(KeyKeys.KeyType) != KeyKeys.KeyType_EC2) {
				System.err.println("Invalid key type - Expected key type: EC2");
				return false;
			}
				
			if (key.get(KeyKeys.EC2_Curve) != KeyKeys.EC2_P256) {
				System.err.println("Invalid EC2 curve - Expected curve: P-256");
				return false;
			}
				
		}
		
		return true;
		
	}

	
    /**
     * Check if a CBOR integer complies with deterministic CBOR encoding
     *  
     * @param obj   The CBOR integer to check
     * @return True in case the CBOR integer complies with deterministic CBOR encoding, or false otherwise
     */
	public static boolean isDeterministicCborInteger (CBORObject obj) {
		
		if (obj.getType() != CBORType.Integer)
			return false;
		
		byte[] objBytes = obj.EncodeToBytes();
		
		switch (objBytes.length) {
			case 1:
				return true;
			case 2:
				if (obj.AsInt32() >= -24 && obj.AsInt32() <= 23)
					return false;
				else
					return true;
			case 3:
				if (obj.AsInt32() >= -256 && obj.AsInt32() <= 255)
					return false;
				else
					return true;
			case 5:
				if (obj.AsInt32() >= -65536 && obj.AsInt32() <= 65535)
					return false;
				else
					return true;
			case 9:
				if (obj.AsInt64Value() >= -4294967296L && obj.AsInt64Value() <= 4294967295L)
					return false;
				else
					return true;
			default:
				return false;
		}
		
	}

	/**
	* Install EdDSA crypto provider
	*/
	public static void installCryptoProvider() {
		Provider EdDSA = new EdDSASecurityProvider();
		// Insert EdDSA security provider
		Security.insertProviderAt(EdDSA, 1);
	}   
}