OneKeyDecoder.java

/*******************************************************************************
 * Copyright (c) 2023 RISE SICS 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:
 *    Rikard Höglund (RISE SICS)
 *    
 ******************************************************************************/
package org.eclipse.californium.oscore.group;

import org.eclipse.californium.cose.AlgorithmID;
import org.eclipse.californium.cose.CoseException;
import org.eclipse.californium.cose.KeyKeys;
import org.eclipse.californium.cose.OneKey;
import org.eclipse.californium.elements.util.Base64;
import org.eclipse.californium.elements.util.StringUtil;

import com.upokecenter.cbor.CBORObject;

import net.i2p.crypto.eddsa.Utils;

/**
 * Various functions for decoding OneKeys that can be useful for interop
 * testing.
 * 
 */
public class OneKeyDecoder {

	/**
	 * Create OneKey from raw bytes representing a public key. This is what one
	 * party had during the last interop test.
	 * 
	 * @param alg the algorithm used
	 * @param publicKey the bytes of the public key
	 * 
	 * @return the built OneKey object
	 */
	public static OneKey fromRawPublicBytes(AlgorithmID alg, byte[] publicKey) {

		switch (alg) {
		case EDDSA:
			String keyStringStart = "{1: 1, -2: h'";
			String keyStringEnd = "', -1: 6, 3: -8}";
			String publicKeyString = Utils.bytesToHex(publicKey);
			String fullKeyString = keyStringStart + publicKeyString + keyStringEnd;
			return parseDiagnostic(fullKeyString);
		default:
			System.err.println("Conversion using this algorithm not supported.");
			return null;
		}
	}

	/**
	 * As below but returns a CBOR Object.
	 * 
	 * @param keyString string representing a OneKey in diagnostic notation
	 * @return a CBOR object built from the string
	 */
	public static CBORObject parseDiagnosticToCbor(String keyString) {
		return parseDiagnostic(keyString).AsCBOR();
	}

	/**
	 * As below but returns a Base64 encoded string.
	 * 
	 * @param keyString string representing a OneKey in diagnostic notation
	 * @return a base 64 encoded representation built from the string
	 */
	public static String parseDiagnosticToBase64(String keyString) {

		OneKey key = parseDiagnostic(keyString);
		byte[] keyObjectBytes = key.EncodeToBytes();
		String base64Encoded = Base64.encodeBytes(keyObjectBytes);

		return base64Encoded;
	}

	/**
	 * Parse a string representing a COSE OneKey in diagnostic notation. This
	 * method first builds a CBOR Object from the values in the string. A COSE
	 * OneKey is then created from that CBOR Object.
	 * 
	 * @param keyString string representing a OneKey in diagnostic notation
	 * @return a OneKey object built from the string
	 */
	public static OneKey parseDiagnostic(String keyString) {

		// Add algorithm to key if missing
		boolean addAlgorithm = false;

		// Change alternative version of single quotes
		keyString = keyString.replace("’", "'");

		// Convert to lower case
		keyString = keyString.toLowerCase();

		// Remove { and } characters
		keyString = keyString.replace("{", "");
		keyString = keyString.replace("}", "");
		// Remove spaces
		keyString = keyString.replace(" ", "");

		// Split the string into sections at the , and : character
		String[] segments = keyString.split("[,:]");

		// Build CBOR Object from the segments
		CBORObject keyCbor = CBORObject.NewMap();

		for (int i = 0; i < segments.length; i += 2) {
			int key = Integer.parseInt(segments[i]);
			String value = segments[i + 1];

			// Handle byte array values
			if (value.length() >= 2 && value.substring(0, 2).equals("h'")) {
				String arrayString = value.replace("h'", "").replace("'", "");
				byte[] array = StringUtil.hex2ByteArray(arrayString);
				keyCbor.Add(key, array);
			} else {
				// Handle integer values
				int valueInt = Integer.parseInt(value);
				keyCbor.Add(key, valueInt);
			}
		}

		// Set the algorithm if missing (which it sometimes is) TODO: Needed?
		if (addAlgorithm && keyCbor.get(KeyKeys.Algorithm.AsCBOR()) == null) {

			// System.out.println("AlgorithmID in diagnostic string is null,
			// setting it.");

			AlgorithmID countersignAlg = OneKeyDecoder.getAlgFromCurve(keyCbor);
			keyCbor.set(KeyKeys.Algorithm.AsCBOR(), countersignAlg.AsCBOR());
		}

		// Create a COSE key from CBOR Object
		OneKey key = null;
		try {
			key = new OneKey(keyCbor);
		} catch (CoseException e) {
			System.err.println("Error: Failed to decode COSE OneKey from diagnostic notation.");
			e.printStackTrace();
		}

		return key;
	}

	/**
	 * Get the algorithm used from the curve information in a OneKey
	 * 
	 * @param key the OneKey to check
	 * @return the algorithm used
	 */
	public static AlgorithmID getAlgFromCurve(OneKey key) {
		CBORObject ec2Curve = null;
		CBORObject okpCurve = null;

		try {
			okpCurve = key.get(KeyKeys.OKP_Curve.AsCBOR());
			ec2Curve = key.get(KeyKeys.EC2_Curve.AsCBOR());
		} catch (CoseException e) {
			System.err.println("Failed to identify algorithm used from curve.");
			e.printStackTrace();
		}

		// Checks and returns the algorithm by looking at the curve used
		if (ec2Curve == KeyKeys.EC2_P256) {
			// ECDSA 256
			return AlgorithmID.ECDSA_256;
		} else if (ec2Curve == KeyKeys.EC2_P384) {
			// ECDSA 384
			return AlgorithmID.ECDSA_384;
		} else if (ec2Curve == KeyKeys.EC2_P521) {
			// ECDSA 512
			return AlgorithmID.ECDSA_256;
		} else if (okpCurve == KeyKeys.OKP_Ed25519) {
			// EdDSA
			return AlgorithmID.EDDSA;
		} else {
			return null;
		}
	}

	/**
	 * Get the algorithm used from the curve information in a CBOR Object
	 * 
	 * @param key the CBOR Object to check
	 * @return the algorithm used
	 */
	public static AlgorithmID getAlgFromCurve(CBORObject key) {
		CBORObject ec2Curve = key.get(KeyKeys.EC2_Curve.AsCBOR());
		CBORObject okpCurve = key.get(KeyKeys.OKP_Curve.AsCBOR());

		// Checks and returns the algorithm by looking at the curve used
		if (ec2Curve == KeyKeys.EC2_P256) {
			// ECDSA 256
			return AlgorithmID.ECDSA_256;
		} else if (ec2Curve == KeyKeys.EC2_P384) {
			// ECDSA 384
			return AlgorithmID.ECDSA_384;
		} else if (ec2Curve == KeyKeys.EC2_P521) {
			// ECDSA 512
			return AlgorithmID.ECDSA_256;
		} else if (okpCurve == KeyKeys.OKP_Ed25519) {
			// EdDSA
			return AlgorithmID.EDDSA;
		} else {
			return null;
		}
	}

}