Util.java

package se.sics.ace;

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.californium.core.CoapResponse;
import org.eclipse.californium.core.coap.Response;

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 net.i2p.crypto.eddsa.Utils;
import se.sics.ace.rs.TokenRepository;

public class Util {

    /**
     *  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) {
    	return intToBytes(num, 0);
    }
	
    /**
     *  Convert a positive integer into a byte array of the specified length (in bytes).
     *  If the specified length is 0, the byte array will be of minimal size.
     *  The positive integer can be up to 2,147,483,647
     * @param num
     * @param length
     * @return  the byte array
     */
    public static byte[] intToBytes(final int num, final int length) {

    	byte[] ret = null;
    	
    	// Big-endian
    	if (num < 0 || length < 0)
    		return null;
        else if (num < 256) {
            ret = new byte[] { (byte) (num) };
        } else if (num < 65536) {
        	ret = new byte[] { (byte) (num >>> 8), (byte) num };
        } else if (num < 16777216) {
        	ret = new byte[] { (byte) (num >>> 16), (byte) (num >>> 8), (byte) num };
        } else { // up to 2,147,483,647
        	ret = new byte[]{ (byte) (num >>> 24), (byte) (num >>> 16), (byte) (num >>> 8), (byte) num };
        }
    	
    	// Little-endian
    	/*
    	if (num < 0)
    		return null;
        else if (num < 256) {
            ret = new byte[] { (byte) (num) };
        } else if (num < 65536) {
            ret = new byte[] { (byte) num, (byte) (num >>> 8) };
        } else if (num < 16777216){
            ret = new byte[] { (byte) num, (byte) (num >>> 8), (byte) (num >>> 16) };
        } else{ // up to 2,147,483,647
            ret = new byte[] { (byte) num, (byte) (num >>> 8), (byte) (num >>> 16), (byte) (num >>> 24) };
        }
    	*/
    	
    	if (length == 0 || length <= ret.length)
    		return ret;
    	
    	int paddingLength = length - ret.length;
    	byte[] retWithPadding = new byte[ret.length + paddingLength];
    	
    	// Big-endian
    	for (int i = 0; i < paddingLength; i++)
    		retWithPadding[i] = (byte) 0x00;
    	for (int i = 0; i < ret.length; i++)
    		retWithPadding[i + paddingLength] = ret[i];
    	
    	// Little-endian
    	/*
    	for (int i = 0; i < ret.length; i++)
    		retWithPadding[i] = ret[i];
    	for (int i = 0; i < paddingLength; i++)
    		retWithPadding[i + paddingLength] = (byte) 0x00;
    	*/
    	
    	return retWithPadding;
    	
    }
	
    /**
     * 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;
    	
    }
	
    /**
     * Build the "psk_identity" to use in the
     * ClientKeyExchange DTLS Handshake message
     *  
     * @param kid   The 'kid' of the key used as PoP key
     * 
     * @return The "psk_identity" to use in the DTLS Handshake
     */
	public static byte[] buildDtlsPskIdentity(byte[] kid) {
        
        CBORObject identityMap = CBORObject.NewMap();
        CBORObject cnfMap = CBORObject.NewMap();
        CBORObject coseKeyMap = CBORObject.NewMap();
        
        coseKeyMap.Add(CBORObject.FromObject(KeyKeys.KeyType.AsCBOR()), KeyKeys.KeyType_Octet);
        coseKeyMap.Add(CBORObject.FromObject(KeyKeys.KeyId.AsCBOR()), kid);
        cnfMap.Add(Constants.COSE_KEY_CBOR, coseKeyMap);
        identityMap.Add(CBORObject.FromObject(Constants.CNF), cnfMap);
        
        // The serialized identity map to use as "psk_identity" in DTLS
        return identityMap.EncodeToBytes();
		
	}
	
    /**
     * Compute a digital signature
     * 
     * @param signKeyCurve   Elliptic curve used to compute the signature
     * @param privKey  private key of the signer, used to compute the signature
     * @param dataToSign  content to sign
     * @return The computed signature, or null in case of error
     
     */
    public static byte[] computeSignature(int signKeyCurve, PrivateKey privKey, byte[] dataToSign) {

        Signature signCtx = null;
        byte[] signature = null;

        try {
     	   if (signKeyCurve == KeyKeys.EC2_P256.AsInt32())
     		  signCtx = Signature.getInstance("SHA256withECDSA");
     	   else if (signKeyCurve == KeyKeys.OKP_Ed25519.AsInt32())
     		  signCtx = Signature.getInstance("NonewithEdDSA", "EdDSA");
     	   else {
     		  // At the moment, only ECDSA (EC2_P256) and EDDSA (Ed25519) are supported
     		  System.err.println("Unsupported signature algorithm");
     		  return null;
     	   }
            
        }
        catch (NoSuchAlgorithmException e) {
            System.err.println("Unsupported signature algorithm: " + e.getMessage());
            return null;
        }
        catch (NoSuchProviderException e) {
            System.err.println("Unsopported security provider for signature computing: " + e.getMessage());
            return null;
        }
        
        try {
            if (signCtx != null)
            	signCtx.initSign(privKey);
            else {
                System.err.println("Signature algorithm has not been initialized");
                return null;
            }
        }
        catch (InvalidKeyException e) {
            System.err.println("Invalid key excpetion - Invalid private key: " + e.getMessage());
            return null;
        }
        
        try {
        	if (signCtx != null) {
        		signCtx.update(dataToSign);
        		signature = signCtx.sign();
        	}
        } catch (SignatureException e) {
            System.err.println("Failed signature computation: " + e.getMessage());
            return null;
        }
        
        return signature;
        
    }
    
    /**
     * Verify the correctness of a digital signature
     * 
     * @param signKeyCurve   Elliptic curve used to process the signature
     * @param pubKey   Public key of the signer, used to verify the signature
     * @param signedData   Data over which the signature has been computed
     * @param expectedSignature   Signature to verify
     * @return True if the signature verifies correctly, false otherwise
     */
    public static boolean verifySignature(int signKeyCurve, PublicKey pubKey, byte[] signedData, byte[] expectedSignature) {

        Signature signature = null;
        boolean success = false;
        
        try {
           if (signKeyCurve == KeyKeys.EC2_P256.AsInt32())
        	   signature = Signature.getInstance("SHA256withECDSA");
           else if (signKeyCurve == KeyKeys.OKP_Ed25519.AsInt32())
        	   signature = Signature.getInstance("NonewithEdDSA", "EdDSA");
           else {
              System.err.println("Unsupported signature algorithm");
              return false;
           }
             
         }
         catch (NoSuchAlgorithmException e) {
             System.err.println("Unsupported signature algorithm: " + e.getMessage());
             return false;
         }
         catch (NoSuchProviderException e) {
             System.err.println("Unsopported security provider for signature computing: " + e.getMessage());
             return false;
         }
         
         try {
             if (signature != null)
            	 signature.initVerify(pubKey);
             else {
                 System.err.println("Signature algorithm has not been initialized");
                 return false;
             }
         }
         catch (InvalidKeyException e) {
             System.err.println("Invalid key excpetion - Invalid public key: " + e.getMessage());
             return false;
         }
         
         try {
        	 signature.update(signedData);
             success = signature.verify(expectedSignature);
         } catch (SignatureException e) {
             System.err.println("Error during signature verification: " + e.getMessage());
             return false;
         }
         
         return success;

    }
    
    /**
     * Return the cryptographic curve to use for a Signature Algorithm or for a Pairwise Key Agreement Algorithm
     * in an OSCORE group, depending on whether the group uses the group mode or not, respectively
     * 
     * @param parameters   A CBOR Map, specifying the parameters for a Signature Algorithm or
     *                     for a Pairwise Key Agreement Algorithm to use in an OSCORE group
     * 
     * @return The cryptographic curve, as a CBOR Object with value an integer, or null in case of error
     */
    public static CBORObject retrieveCurve(final CBORObject parameters) {
    	
    	CBORObject curve = null;
    	
    	CBORObject keyType = parameters.get(0).get(0);
    	
    	if (keyType == null) {
    		return null;
    	}
		if (keyType.equals(org.eclipse.californium.cose.KeyKeys.KeyType_OKP)
				|| keyType.equals(org.eclipse.californium.cose.KeyKeys.KeyType_EC2)) {
    		curve = parameters.get(1).get(1);
    	}
    	
    	return curve;
    	
    }
    
    /**
     * Return the asymmetric key pair of the Group Manager to use, as a OneKey object
     * 
     * @param gmSigningKeyPairs   Asymmetric key pairs of the Group Manager, to be used for signing operations.
     *                            The map key is the cryptographic curve; the map value is the hex string of the key pair
     * @param gmKeyAgreementKeyPairs   Asymmetric key pairs of the Group Manager, to be used for key agreement operations.
     *                                 The map key is the cryptographic curve; the map value is the hex string of the key pair
     * @param useGroupMode   True if the OSCORE group uses the group mode, or false otherwise
     * @param parameters   A CBOR Map, specifying the parameters for the Signature Algorithm or
     *                     for the Pairwise Key Agreement Algorithm, depending on 'useGroupMode'
     *                     being True of False, respectively
     * 
     * @return The asymmetric key pair of the Group Manager to use, or null in case of error
     */
    public static OneKey retrieveGmKeyPair(final Map<CBORObject, String> gmSigningKeyPairs,
    								       final Map<CBORObject, String> gmKeyAgreementKeyPairs,
    							     	   final boolean useGroupMode,
    								       final CBORObject parameters) {
    	
    	OneKey gmKeyPair = null;
    	
    	// Serialization of the COSE Key including both private and public part
    	byte[] gmKeyPairBytes = null;
    	
    	CBORObject curve = retrieveCurve(parameters);
    	
    	if (curve == null) {
    		// This should never happen
    		return null;
    	}

    	if (useGroupMode) {
    		// This group uses the group mode, thus the authentication credential
    		// of the Group Manager has to be specific for signing operations
			if (curve.AsInt32() == org.eclipse.californium.cose.KeyKeys.EC2_P256.AsInt32()) {
				gmKeyPairBytes = Utils.hexToBytes(gmSigningKeyPairs.get(org.eclipse.californium.cose.KeyKeys.EC2_P256));
    		}
			if (curve.AsInt32() == org.eclipse.californium.cose.KeyKeys.OKP_Ed25519.AsInt32()) {
				gmKeyPairBytes = Utils
						.hexToBytes(gmSigningKeyPairs.get(org.eclipse.californium.cose.KeyKeys.OKP_Ed25519));
    		}
    	}
    	else {
    		// This group uses only the pairwise mode, thus the authentication credential
    		// of the Group Manager has to be specific for key agreement operations
			if (curve.AsInt32() == org.eclipse.californium.cose.KeyKeys.EC2_P256.AsInt32()) {
				gmKeyPairBytes = Utils
						.hexToBytes(gmKeyAgreementKeyPairs.get(org.eclipse.californium.cose.KeyKeys.EC2_P256));
    		}
			if (curve.AsInt32() == org.eclipse.californium.cose.KeyKeys.OKP_X25519.AsInt32()) {
				gmKeyPairBytes = Utils
						.hexToBytes(gmKeyAgreementKeyPairs.get(org.eclipse.californium.cose.KeyKeys.OKP_X25519));
    		}
    	}
    	    	
    	try {
			gmKeyPair = new OneKey(CBORObject.DecodeFromBytes(gmKeyPairBytes));
		} catch (CoseException e) {
			e.printStackTrace();
    		return null;
		}
    	
    	return gmKeyPair;
    	
    }
    
    /**
     * Return the authentication credential of the Group Manager to use, as a byte array
     * 
     * @param credFmt                   The format of public authentication credentials used in the OSCORE group 
     * @param gmSigningPublicAuthCred   The public authentication credentials of the Group Manager,
     *                                  including a public key to be used for key agreement operations.
     *                                  For the outer map, the map key is the type of authentication credential.
     *                                  For the inner map, the map key is the cryptographic curve,
     *                                  while the map value is the hex string of the authentication credential
     * @param gmKeyAgreementPublicAuthCred   The public authentication credentials of the Group Manager,
     *                                       including a public key to be used for key agreement operations.
     *                                       For the outer map, the map key is the type of authentication credential.
     *                                       For the inner map, the map key is the cryptographic curve,
     *                                       while the map value is the hex string of the authentication credential
     * @param useGroupMode   True if the OSCORE group uses the group mode, or false otherwise
     * @param parameters   A CBOR Map, specifying the parameters for the Signature Algorithm or
     *                     for the Pairwise Key Agreement Algorithm, depending on 'useGroupMode'
     *                     being True of False, respectively
     * 
     * @return The authentication credential of the Group Manager to use, or null in case of error
     */
    public static byte[] retrieveGmAuthCred(final int credFmt, 
    								        final Map<Integer,  Map<CBORObject, String>> gmSigningPublicAuthCred,
    								        final Map<Integer,  Map<CBORObject, String>> gmKeyAgreementPublicAuthCred,
    								        final boolean useGroupMode,
    								        final CBORObject parameters) {
    	
    	byte[] gmAuthCred = null;
    	
    	CBORObject curve = retrieveCurve(parameters);
    	
    	if (curve == null) {
    		// This should never happen
    		return null;
    	}
    	
    	// Build the authentication credential according to the format used in the group
    	switch (credFmt) {
	        case Constants.COSE_HEADER_PARAM_KCCS:
	            // A CCS including the public key
			if (curve.AsInt32() == org.eclipse.californium.cose.KeyKeys.EC2_P256.AsInt32()) {
	        		if (useGroupMode) {
					gmAuthCred = Utils.hexToBytes(gmSigningPublicAuthCred.get(Constants.COSE_HEADER_PARAM_KCCS)
							.get(org.eclipse.californium.cose.KeyKeys.EC2_P256));
		        		// gmAuthCred = Utils.hexToBytes("A2026008A101A50102032620012158202236658CA675BB62D7B24623DB0453A3B90533B7C3B221CC1C2C73C4E919D540225820770916BC4C97C3C46604F430B06170C7B3D6062633756628C31180FA3BB65A1B");
	        		}
	        		else {
					gmAuthCred = Utils.hexToBytes(gmKeyAgreementPublicAuthCred.get(Constants.COSE_HEADER_PARAM_KCCS)
							.get(org.eclipse.californium.cose.KeyKeys.EC2_P256));
	        		}		
	        	}
			if (curve.AsInt32() == org.eclipse.californium.cose.KeyKeys.OKP_Ed25519.AsInt32()) {
				gmAuthCred = Utils.hexToBytes(gmSigningPublicAuthCred.get(Constants.COSE_HEADER_PARAM_KCCS)
						.get(org.eclipse.californium.cose.KeyKeys.OKP_Ed25519));
	        		// gmAuthCred = Utils.hexToBytes("A2026008A101A4010103272006215820C6EC665E817BD064340E7C24BB93A11E8EC0735CE48790F9C458F7FA340B8CA3");
	        	}
			if (curve.AsInt32() == org.eclipse.californium.cose.KeyKeys.OKP_X25519.AsInt32()) {
				gmAuthCred = Utils.hexToBytes(gmKeyAgreementPublicAuthCred.get(Constants.COSE_HEADER_PARAM_KCCS)
						.get(org.eclipse.californium.cose.KeyKeys.OKP_X25519));
	        	}
	            break;
	        case Constants.COSE_HEADER_PARAM_KCWT:
	            // A CWT including the public key
	            // TODO
	        	gmAuthCred = null;
	            break;
	        case Constants.COSE_HEADER_PARAM_X5CHAIN:
	            // A certificate including the public key
	            // TODO
	        	gmAuthCred = null;
	            break;
    	}
    	
    	return gmAuthCred;
    	
    }
    
    /**
     * Add 'newRole' to the role set, encoded using the AIF-OSCORE-GROUPCOMM data model
     * 
     * @param currentRoleSet  the current set of roles
     * @param newRole  the role to add to the current set
     * 
      * @return  the updated role set
      * @throws AceException  if the role identifier is less than 1
     */
    public static int addGroupOSCORERole (int currentRoleSet, short newRole) throws AceException {

   	 if (newRole < 1) throw new AceException("Invalid identifier of Group OSCORE role");
   	 
   	 int updatedRoleSet = 0;
   	 updatedRoleSet = currentRoleSet | (1 << newRole);
   	 
   	 return updatedRoleSet; 
   	 
    }
    
    /**
     * Remove 'oldRole' from the role set, encoded using the AIF-OSCORE-GROUPCOMM data model
     * 
     * @param currentRoleSet  the current set of roles
     * @param oldRole  the role to remove from the current set
     * 
      * @return  the updated role set
      * @throws AceException  if the role identifier is less than 1
     */
    public static int removeGroupOSCORERole (int currentRoleSet, short oldRole) throws AceException {

   	 if (oldRole < 1) throw new AceException("Invalid identifier of Group OSCORE role");
   	 
   	 int updatedRoleSet = 0;
   	 updatedRoleSet = currentRoleSet & (~(1 << oldRole));
   	 
   	 return updatedRoleSet; 
   	 
    }
       
    /**
     * Check if a role set includes a specified role, encoded using the AIF-OSCORE-GROUPCOMM data model
     * 
     * @param roleSet  the set of roles
     * @param role  the role to remove from the current set
     * 
      * @return  true if the role set includes the specified role, false otherwise
      * @throws AceException  if the set of roles is inconsistent with the AIF-OSCORE-GROUPCOMM data model
      * 					  or the role identifier is less than 1
     */
    public static boolean checkGroupOSCORERole (int roleSet, short role) throws AceException {
   	 
   	 if ((roleSet < 1) || ((roleSet % 2) == 1)) {
   		 throw new AceException("Invalid set of Group OSCORE roles");
   	 }
   	 
   	 if (role < 1) {
   		 throw new AceException("Invalid identifier of Group OSCORE role");
   	 }
   	 
   	 return ((roleSet & (1 << role)) != 0);
   	 
    }
    
    /**
     * Return the array of roles included in the specified role set, encoded using the AIF-OSCORE-GROUPCOMM data model
     * 
     * @param roleSet  the set of roles, encoded using the AIF-OSCORE-GROUPCOMM data model
     * 
      * @return  The set of role identifiers specified in the role set
      * @throws AceException  if the set of roles is inconsistent with the AIF-OSCORE-GROUPCOMM data model
     */
    public static Set<Integer> getGroupOSCORERoles (int roleSet) throws AceException {
   	 
      	 if ((roleSet < 1) || ((roleSet % 2) == 1)) {
       		 throw new AceException("Invalid set of Group OSCORE roles");
       	 }
	   	 
	   	 Set<Integer> mySet = new HashSet<Integer>();
	   	 int roleIdentifier = 0;
	   	 
	   	 while (roleSet != 0) {
	   		 roleSet = roleSet >>> 1;
	   	 	 roleIdentifier++;
	   	 	 if ((roleSet & 1) != 0) {
	   	 		 mySet.add(Integer.valueOf(roleIdentifier));
	   	 	 }
	   	 }
	   	 
	   	 return mySet;
   	 
    }
    
    /**
     * Return the role sets allowed to a subject in a group, based on all the Access Tokens for that subject
     * 
     * @param subject   Subject identity of the node
     * @param groupName   Group name of the OSCORE group
     * @return The sets of allowed roles for the subject in the specified group using the AIF data model,
     *         or null in case of no results
     */
    public static int[] getGroupOSCORERolesFromToken(String subject, String groupName) {

    	Set<Integer> roleSets = new HashSet<Integer>();
    	
    	String kid = TokenRepository.getInstance().getKid(subject);
    	Set<String> ctis = TokenRepository.getInstance().getCtis(kid);
    	
    	// This should never happen at this point, since a valid Access Token
    	// has just made this request pass through 
    	if (ctis == null)
    		return null;
    	
    	for (String cti : ctis) { // All tokens linked to that pop key
    		
	        // Check if we have the claims for that cti
    		
	        // Get the claims
            Map<Short, CBORObject> claims = TokenRepository.getInstance().getClaims(cti);
            if (claims == null || claims.isEmpty()) {
                // No claims found
        		// Move to the next Access Token for this 'kid'
                continue;
            }
            
	        //Check the scope
            CBORObject scope = claims.get(Constants.SCOPE);
            
        	// This should never happen, since a valid Access Token
            // has just reached a handler at the Group Manager
            if (scope == null) {
        		// Move to the next Access Token for this 'kid'
            	continue;
            }
            
            if (!scope.getType().equals(CBORType.ByteString)) {
        		// Move to the next Access Token for this 'kid'
            	continue;
            }
            
            byte[] rawScope = scope.GetByteString();
        	CBORObject cborScope = CBORObject.DecodeFromBytes(rawScope);
        	
        	if (!cborScope.getType().equals(CBORType.Array)) {
        		// Move to the next Access Token for this 'kid'
                continue;
            }

        	for (int entryIndex = 0; entryIndex < cborScope.size(); entryIndex++) {
            	
        		CBORObject scopeEntry = cborScope.get(entryIndex);
        		
        		if (!scopeEntry.getType().equals(CBORType.Array) || scopeEntry.size() != 2) {
        			// Move to the next Access Token for this 'kid'
                    break;
                }
	        	
	        	// Retrieve the group name of the OSCORE group
	        	String scopeStr;
	      	  	CBORObject scopeElement = scopeEntry.get(0);
	      	  	if (scopeElement.getType().equals(CBORType.TextString)) {
	      	  		scopeStr = scopeElement.AsString();
	      	  		if (!scopeStr.equals(groupName)) {
	      	  		    // Move to the next scope entry
	      	  			continue;
	      	  		}
	      	  	}
	      	  	else {
	      	  		// Move to the next scope entry
	                continue;
	      	  	}
	      	  	
	      	  	// Retrieve the role or list of roles
	      	  	scopeElement = scopeEntry.get(1);
	      	  	
	        	if (!scopeElement.getType().equals(CBORType.Integer)) {
      	  		    // Move to the next scope entry
      	  			continue;
	        	}
	        	
        		int roleSetToken = scopeElement.AsInt32();
        		
        		// According to the AIF-OSCORE-GROUPCOMM data model, a valid combination 
        		// of roles has to be a positive integer of even value (i.e., with last bit 0)
        		if (roleSetToken <= 0 || (roleSetToken % 2 == 1)) {
      	  		    // Move to the next scope entry
      	  			continue;
        		}

        		roleSets.add(roleSetToken);
        			        	
        	}
        	
    	}
    	    	
    	// No Access Token allows this node to have any role
    	// with respect to the specified group
    	if (roleSets.size() == 0) {
    		return null;
    	}
    	else {
    		int[] ret = new int[roleSets.size()];
    		
    		int index = 0;
    		for (Integer i : roleSets) {
    			ret[index] = i.intValue();
    			index++;
    		}
    		
    		return ret;
    	}
    	
    }
    
    /**
     * Add 'newPermission' to the admin permission set, encoded using the AIF-OSCORE-GROUPCOMM data model
     * 
     * @param currentPermissionSet  the current set of admin permissions
     * @param newPermission  the admin permission to add to the current set
     * 
      * @return  the updated set of admin permission
      * @throws AceException  if the permission identifier is less than 1
     */
    public static int addGroupOSCOREAdminPermission (int currentPermissionSet, short newPermission) throws AceException {

   	 if (newPermission < 0) throw new AceException("Invalid identifier of Group OSCORE admin permission");
   	 
   	 int updatedPermissionSet = 0;
   	 updatedPermissionSet = currentPermissionSet | (1 << newPermission);
   	 
   	 return updatedPermissionSet; 
   	 
    }
    
    /**
     * Remove 'oldPermission' from the admin permission set, encoded using the AIF-OSCORE-GROUPCOMM data model
     * 
     * @param currentPermissionSet  the current set of admin permissions
     * @param oldPermission  the permission to remove from the current set
     * 
      * @return  the updated set of admin permissions
      * @throws AceException  if the permission identifier is less than 1
     */
    public static int removeGroupOSCOREAdminPermission (int currentPermissionSet, short oldPermission) throws AceException {

   	 if (oldPermission < 0) throw new AceException("Invalid identifier of Group OSCORE admin permission");
   	 
   	 int updatedPermissionSet = 0;
   	 updatedPermissionSet = currentPermissionSet & (~(1 << oldPermission));
   	 
   	 return updatedPermissionSet; 
   	 
    }
    
    /**
     * Check if a permission set includes a specified admin permission, encoded using the AIF-OSCORE-GROUPCOMM data model
     * 
     * @param permissionSet  the set of admin permissions
     * @param permission  the permission whose presence has to be checked in the set of admin permissions
     * 
      * @return  true if the permission set includes the specified admin permission, false otherwise
      * @throws AceException  if the set of admin permissions is inconsistent with the AIF-OSCORE-GROUPCOMM data model
      * 					  or the permission identifier is less than 1
     */
    public static boolean checkGroupOSCOREAdminPermission (int permissionSet, short permission) throws AceException {
   	 
   	 if ((permissionSet < 1) || ((permissionSet % 2) == 0)) {
   		 throw new AceException("Invalid set of Group OSCORE admin permissions");
   	 }
   	 
   	 if (permission < 0) {
   		 throw new AceException("Invalid identifier of Group OSCORE admin permission");
   	 }
   	 
   	 return ((permissionSet & (1 << permission)) != 0);
   	 
    }
    
    /**
     * Return the array of permissions included in the specified set of admin permissions,
     * encoded using the AIF-OSCORE-GROUPCOMM data model
     * 
     * @param permissionSet  the set of admin permissions, encoded using the AIF-OSCORE-GROUPCOMM data model
     * 
      * @return  The set of permission identifiers specified in the set of admin permission
      * @throws AceException  if the set of permissions is inconsistent with the AIF-OSCORE-GROUPCOMM data model
     */
    public static Set<Integer> getGroupOSCOREAdminPermissions (int permissionSet) throws AceException {
   	 
      	 if ((permissionSet < 1) || ((permissionSet % 2) == 0)) {
       		 throw new AceException("Invalid set of Group OSCORE admin permissions");
       	 }
	   	 
	   	 Set<Integer> mySet = new HashSet<Integer>();
	   	 int permissionIdentifier = 0;
	   	 
	   	 // The admin permission "List" is always set in every admin scope entry
	   	 mySet.add(Integer.valueOf(permissionIdentifier));
	   	 
	   	 permissionSet--;
	   	 while (permissionSet != 0) {
	   		permissionSet = permissionSet >>> 1;
   	 		permissionIdentifier++;
	   	 	 if ((permissionSet & 1) != 0) {
	   	 		 mySet.add(Integer.valueOf(permissionIdentifier));
	   	 	 }
	   	 }
	   	 
	   	 return mySet;
   	 
    }
    
    /**
     * Return the sets of admin permissions allowed to a subject, based on all the Access Tokens for that subject
     * 
     * @param subject   Subject identity of the node
     * @param groupName   Group name of the OSCORE group, or null to retrieve all the admin scope entries
     * @return The sets of scope entries such the group name matches with the specified group name pattern
     *         and for which the subject has admin permissions, or null in case of no results
     */
    public static CBORObject[] getGroupOSCOREAdminPermissionsFromToken(String subject, String groupName) {

    	List<CBORObject> scopeEntries = new ArrayList<CBORObject>();
    	
    	String kid = TokenRepository.getInstance().getKid(subject);
    	Set<String> ctis = TokenRepository.getInstance().getCtis(kid);
    	
    	// This should never happen at this point, since a valid Access Token
    	// has just made this request pass through 
    	if (ctis == null)
    		return null;
    	
    	for (String cti : ctis) { // All tokens linked to that pop key
    		
	        // Check if we have the claims for that cti
    		
	        // Get the claims
            Map<Short, CBORObject> claims = TokenRepository.getInstance().getClaims(cti);
            if (claims == null || claims.isEmpty()) {
                // No claims found
        		// Move to the next Access Token for this 'kid'
                continue;
            }
            
	        //Check the scope
            CBORObject scope = claims.get(Constants.SCOPE);
            
        	// This should never happen, since a valid Access Token
            // has just reached a handler at the Group Manager
            if (scope == null) {
        		// Move to the next Access Token for this 'kid'
            	continue;
            }
            
            if (!scope.getType().equals(CBORType.ByteString)) {
        		// Move to the next Access Token for this 'kid'
            	continue;
            }
            
            byte[] rawScope = scope.GetByteString();
        	CBORObject cborScope = CBORObject.DecodeFromBytes(rawScope);
        	
        	if (!cborScope.getType().equals(CBORType.Array)) {
        		// Move to the next Access Token for this 'kid'
                continue;
            }
        	
        	for (int entryIndex = 0; entryIndex < cborScope.size(); entryIndex++) {
            	
        		CBORObject scopeEntry = cborScope.get(entryIndex);
        		
        		if (!scopeEntry.getType().equals(CBORType.Array) || scopeEntry.size() != 2) {
        			// Move to the next Access Token for this 'kid'
                    break;
                }
        		
	      	  	// Retrieve the role or list of admin permissions
        		CBORObject scopeElement = scopeEntry.get(1);
	      	  	
	        	if (!scopeElement.getType().equals(CBORType.Integer)) {
      	  		    // Move to the next scope entry
      	  			continue;
	        	}
	        	
        		int permissionSetToken = scopeElement.AsInt32();
        		
        		// According to the AIF-OSCORE-GROUPCOMM data model, a valid combination 
        		// of admin permissions has to be a positive integer of odd value (i.e., with last bit 1)
        		if (permissionSetToken <= 0 || (permissionSetToken % 2 == 0)) {
      	  		    // Move to the next scope entry
      	  			continue;
        		}
        		
	      	  	if (groupName == null) {
	      	  		// Include this scope entry in the results to return, and move to the next one
	      	  		scopeEntries.add(scopeEntry);
	      	  		continue;
	      	  	}

	        	// Check if the group name of the OSCORE group matches with the group name pattern
	      	  	scopeElement = scopeEntry.get(0);
	      	    if (matchingGroupOscoreName(groupName, scopeElement)) {
	      	    	// There is a match; include this scope entry in the results to return
	      	    	scopeEntries.add(scopeEntry);
	      	    }
	      	    else {
	      	    	// Move to the next scope entry
	      	    	continue;
	      	    }
        			        	
        	}
        	
    	}
    	    	
    	// No Access Token allows this node to have any admin permission,
    	// altogether or with respect to the specified group
    	int size = scopeEntries.size();
    	if (size == 0) {
    		return null;
    	}
    	else {
    		CBORObject[] ret = new CBORObject[size];
    		
    		int index = 0;
    		for (CBORObject entry : scopeEntries) {
        		// Hard copy
    			byte[] binaryElem = entry.EncodeToBytes();
    			ret[index] = CBORObject.DecodeFromBytes(binaryElem);
    			index++;
    		}
    		
    		return ret;
    	}
    	
    }
    
    /**
     * Check if the name of an OSCORE group matches with the group name pattern
     * specified by Toid in a scope entry of the scope claim, according to the
     * AIF-OSCORE-GROUPCOMM data model
     *  
     * @param groupName   The name of the OSCORE group, as a String
     * @param groupNamePattern   The Toid from the scope entry, as a CBOR Object
     * @return  True if the group name matches with the group name pattern, or false otherwise
     */
    public static boolean matchingGroupOscoreName(final String groupName, final CBORObject groupNamePattern) {
    	
  	  	if (groupNamePattern.equals(CBORObject.True)) {
	  		// The group name pattern is the wildcard
  	  		return true;
  	  	}
  	  		
  	  	if (groupNamePattern.getType().equals(CBORType.TextString)) {
  	  		String groupNamePatternString = groupNamePattern.AsString();
  	  		if (groupNamePattern.HasTag(21065)) {
  	  			// The group name pattern is an I-Regexp regular expression
  	  			
  	  			Pattern pat = Pattern.compile(groupNamePatternString);
  	  			Matcher myMatcher = pat.matcher(groupName);
  	  			if (myMatcher.matches() == false) {
  	  				// The target group name does not match with the regular expression
      	  		    return false;
  	  			}
  	  		}
  	  		else if (!groupNamePatternString.equals(groupName)) {
  	  			// The group name pattern is an exact group name,
  	  			// which does not match with the target group name
  	  		    return false;
  	  		}
  	  		
  	  		// The target group name has matched with the group name pattern
  	  		return true;
  	  	}

  	  	return false;
  	  	
    }
    
    /**
     * Build a CWT Claims Set (CCS) including a COSE Key
     * within a "cnf" claim and an additional "sub" claim
     *  
     * @param identityKey   The public key as a OneKey object
     * @param subjectName   The subject name associated to this key, it can be an empty string
     * @return  The serialization of the CCS, or null in case of errors
     */
	public static byte[] oneKeyToCCS(OneKey identityKey, String subjectName) {
		
		if (identityKey  == null || subjectName == null)
			return null;
		
		CBORObject coseKeyMap = CBORObject.NewMap();
		coseKeyMap.Add(KeyKeys.KeyType.AsCBOR(), identityKey.get(KeyKeys.KeyType));
		if (identityKey.get(KeyKeys.KeyType) == KeyKeys.KeyType_OKP) {
			int curve = identityKey.get(KeyKeys.OKP_Curve).AsInt32();
			if (curve == KeyKeys.OKP_Ed25519.AsInt32() || curve == KeyKeys.OKP_Ed448.AsInt32()) {
				coseKeyMap.Add(KeyKeys.Algorithm.AsCBOR(), AlgorithmID.EDDSA.AsCBOR());
			}
			if (curve == KeyKeys.OKP_X25519.AsInt32() || curve == KeyKeys.OKP_X448.AsInt32()) {
				coseKeyMap.Add(KeyKeys.Algorithm.AsCBOR(), AlgorithmID.ECDH_ES_HKDF_256.AsCBOR());
			}
			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) {
			int curve = identityKey.get(KeyKeys.EC2_Curve).AsInt32();
			if (curve == KeyKeys.EC2_P256 .AsInt32()) {
				coseKeyMap.Add(KeyKeys.Algorithm.AsCBOR(), AlgorithmID.ECDSA_256.AsCBOR());
			}
			if (curve == KeyKeys.EC2_P384 .AsInt32()) {
				coseKeyMap.Add(KeyKeys.Algorithm.AsCBOR(), AlgorithmID.ECDSA_384.AsCBOR());
			}
			if (curve == KeyKeys.EC2_P521.AsInt32()) {
				coseKeyMap.Add(KeyKeys.Algorithm.AsCBOR(), AlgorithmID.ECDSA_512.AsCBOR());
			}
			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.NewMap();
		cnfMap.Add(Constants.COSE_KEY, coseKeyMap);
		
		CBORObject claimSetMap = CBORObject.NewMap();
		claimSetMap.Add(Constants.SUB, subjectName);
		claimSetMap.Add(Constants.CNF, cnfMap);
		
		// Debug print
		System.out.println(claimSetMap);
		
        return claimSetMap.EncodeToBytes();
		
	}
	
    /**
     * Extract a public key from a CWT Claims Set (CCS) and return it as a OneKey object
     *  
     * @param ccs   The CCS as a CBOR map
     * @return  The public key as a OneKey object, or null in case of errors
     */
	public static OneKey ccsToOneKey(CBORObject ccs) {
		
		if (ccs == null)
			return null;
		
		if (ccs.getType() != CBORType.Map)
		    return null;
		
		if (!ccs.ContainsKey(Constants.CNF) || !ccs.get(Constants.CNF).ContainsKey(Constants.COSE_KEY))
			return null;
		
		CBORObject pubKeyCBOR = ccs.get(Constants.CNF).get(Constants.COSE_KEY);
		
		OneKey pubKey = null;
		try {
			pubKey = new OneKey(pubKeyCBOR);
		} catch (CoseException e) {
			System.err.println("Error when building a OneKey from a CCS: " + e.getMessage());
			return null;
		}
		
        return pubKey;
		
	}
	
    /**
     * @param byteArray  the byte array
     * @return  the hex string
     * 
     * Return the printable hexadecimal string corresponding to a byte array
     */
    public static String byteArrayToHexString(final byte[] byteArray) {
    	
    	if (byteArray == null) {
    		return new String("");
    	}
    	else {
    		String str = new String("");
	    	for (byte byteToConvert: byteArray) {
	            str += String.format("%02X", byteToConvert);
	        }
	    	return str;
    	}
    	
    }
	
    /**
     * Read a hex string and transform to bytes
     * 
     * @param hex  the hex string
     * @return  the byte array representation
     */
    public static byte[] hexString2byteArray(String hex) {
        int len = hex.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
                    + Character.digit(hex.charAt(i+1), 16));
        }
        return data;
    }
	
    /**
     * Build a CBOR map specifying a public key, possibly together with the corresponding private key
     * 
     * @param signKeyCurve  the curve of the signature algorithm
     * @param x  the x-coordinate of the public key
     * @param y  the y-coordinate of the public key, or null if not applicable
     * @param d  the private key, or null if the CBOR map specifies only the public key
     * @return  The CBOR map specifying a public key, possibly together with the corresponding private key
     */
    public static CBORObject buildRpkData (int signKeyCurve, String x, String y, String d) {
    	
    	CBORObject rpkData = CBORObject.NewMap();
    	
    	if (signKeyCurve == KeyKeys.EC2_P256.AsInt32()) {
	        rpkData.Add(KeyKeys.KeyType.AsCBOR(), KeyKeys.KeyType_EC2);
	        rpkData.Add(KeyKeys.Algorithm.AsCBOR(), AlgorithmID.ECDSA_256.AsCBOR());
	        rpkData.Add(KeyKeys.EC2_Curve.AsCBOR(), KeyKeys.EC2_P256);
	        CBORObject Cx = CBORObject.FromObject(hexString2byteArray(x));
	        CBORObject Cy = CBORObject.FromObject(hexString2byteArray(y));
	        rpkData.Add(KeyKeys.EC2_X.AsCBOR(), Cx);
	        rpkData.Add(KeyKeys.EC2_Y.AsCBOR(), Cy);
	        if (d != null) {
		        CBORObject Cd = CBORObject.FromObject(hexString2byteArray(d));
		        rpkData.Add(KeyKeys.EC2_D.AsCBOR(), Cd);
	        }
    	}
    	if (signKeyCurve == KeyKeys.OKP_Ed25519.AsInt32()) {
	        rpkData.Add(KeyKeys.KeyType.AsCBOR(), KeyKeys.KeyType_OKP);
	        rpkData.Add(KeyKeys.Algorithm.AsCBOR(), AlgorithmID.EDDSA.AsCBOR());
	        rpkData.Add(KeyKeys.OKP_Curve.AsCBOR(), KeyKeys.OKP_Ed25519);
	        CBORObject Cx = CBORObject.FromObject(hexString2byteArray(x));
	        rpkData.Add(KeyKeys.OKP_X.AsCBOR(), Cx);
	        if (d != null) {
		        CBORObject Cd = CBORObject.FromObject(hexString2byteArray(d));
		        rpkData.Add(KeyKeys.OKP_D.AsCBOR(), Cd);
	        }
    	}
        
    	return rpkData;
    }
    
    /**
     * Return the used major version of Java
     * 
     * @return  The used major version of Java
     */
    public static int getJavaVersion() {
        String version = System.getProperty("java.version");
        
        if(version.startsWith("1.")) {
            version = version.substring(2, 3);
        } else {
            int dot = version.indexOf(".");
            if(dot != -1) {
            	version = version.substring(0, dot);
            }
        }
        
        return Integer.parseInt(version);
    }
    
    public static void prettyPrintCborMap(final CBORObject obj) {

    	if (obj.getType() != CBORType.Map) {
    		System.err.println("Trying to print a CBOR map, while it is not");
    		return;
    	}
    	
    	int counter = 0;
    	System.out.println("{");
    	for (CBORObject elemKey : obj.getKeys()) {
    		System.out.print("  " + elemKey + ": " + obj.get(elemKey));
    		counter++;
    		if (counter != obj.size()) {
    			System.out.println(",");
    		}
    		else {
    			System.out.println("");
    		}
    	}
    	System.out.println("}\n");
    	
    }
    
    public static void printResponsePayloadCBOR(Response res) throws Exception {
        if (res != null) {
            System.out.print(res.getCode().codeClass + ".0" + res.getCode().codeDetail);
            System.out.println(" " + res.getCode().name());

            int contentFormat = res.getOptions().getContentFormat();
            byte[] payload = res.getPayload();
            
            if (payload != null) {
                if (contentFormat == Constants.APPLICATION_ACE_CBOR ||
                	  contentFormat == Constants.APPLICATION_ACE_GROUPCOMM_CBOR ||
                	  contentFormat == Constants.APPLICATION_CONCISE_PROBLEM_DETAILS_CBOR) {
                    CBORObject resCBOR = CBORObject.DecodeFromBytes(payload);
                    System.out.println(resCBOR.toString());
                }
                else {
                    System.out.println(new String(payload));
                }
            }
        } else {
            System.out.println("The response has a null payload!");
        }
    }
    
    /**
     * Returns the size in bytes of the nonce of a COSE encryption algorithm
     * 
     * @param alg  the encryption algorithm
     * @return  The size in bytes of the encryption algorithm, or -1 in case of error
     */
    public static int getSizeOfAlgNonce(final AlgorithmID alg) {
    	
    	switch (alg) {
    		case AES_GCM_128:
    		case AES_GCM_192:
    		case AES_GCM_256:
    			return 12;
    		case AES_CCM_16_64_128:
    		case AES_CCM_16_64_256:
    		case AES_CCM_16_128_128:
    		case AES_CCM_16_128_256:
    			return 13;
    		case AES_CCM_64_64_128:
    		case AES_CCM_64_64_256:
    		case AES_CCM_64_128_128:
    		case AES_CCM_64_128_256:
    			return 7;
    		default:
    			return -1;
    	}
    	
    }
    
}