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.HashSet;
import java.util.Set;

import com.upokecenter.cbor.CBORObject;

import org.eclipse.californium.cose.AlgorithmID;
import org.eclipse.californium.cose.CoseException;
import org.eclipse.californium.cose.KeyKeys;
import org.eclipse.californium.cose.OneKey;

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;

    }
    
    /**
     * 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 role identifier is less than 1
     */
    public static boolean checkGroupOSCORERole (int roleSet, short role) throws AceException {

   	 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 reserved role is requested (identifier 1, hence 'roleSet' has an odd value)
     */
    
    
    public static Set<Integer> getGroupOSCORERoles (int roleSet) throws AceException {
   	 
   	 if ((roleSet % 2) == 1) throw new AceException("Invalid identifier of Group OSCORE role");
   	 
   	 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;
   	 
    }
    
    /**
     * 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 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 CCS as a CBOR map, or null in case of errors
     */
	public static OneKey ccsToOneKey(CBORObject ccs) {
		
		if (ccs  == null)
			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;
		
	}
	
    /**
     * 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);
    }
    
}