OscoreSecurityContext.java

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

import java.util.logging.Logger;

import org.eclipse.californium.cose.AlgorithmID;
import org.eclipse.californium.cose.CoseException;
import org.eclipse.californium.oscore.OSCoreCtx;
import org.eclipse.californium.oscore.OSException;

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

import se.sics.ace.AceException;
import se.sics.ace.Constants;

/**
 * Utility class to parse, verify and access  OSCORE_Input_Material in a cnf element
 * 
 * @author Ludwig Seitz and Marco Tiloca
 *
 */
public class OscoreSecurityContext {
    
    /**
     * The logger
     */
    private static final Logger LOGGER 
        = Logger.getLogger(OscoreSecurityContext.class.getName());
    
    /**
     * The Master Secret
     */
    private byte[] ms;
    
    /**
     * The OSCORE Input Material Identifier
     */
    private byte[] id;
    
    /**
     * The server identifier
     */
    private byte[] serverId;
    
    /**
     * The client identifier
     */
    private byte[] clientId;
    
    /**
     * The context id, can be null
     */
    private byte[] contextId;
    
    /**
     * The key derivation function, can be null for default: HMAC_SHA_256
     */
    private AlgorithmID hkdf;
    
    /**
     * The encryption algorithm, can be null for default: AES_CCM_16_64_128
     */
    private AlgorithmID alg;
    
    /**
     * The Master Salt, can be null
     */
    private byte[] salt;

    /**
     * The replay window size
     */
    private Integer replaySize;
    
    /**
     * Max unfragmented size parameter for OSCORE
     */
    private final static int MAX_UNFRAGMENTED_SIZE = 4096;
    
    /**
     * Constructor.
     * 
     * @param cnf  the confirmation CBORObject containing 
     *      the OSCORE Security Context.
     * 
     * @throws AceException 
     */
    public OscoreSecurityContext(CBORObject cnf) throws AceException {
        CBORObject osc = cnf.get(Constants.OSCORE_Input_Material);
        if (osc == null || !osc.getType().equals(CBORType.Map)) {
            LOGGER.info("Missing or invalid parameter type for "
                    + "'OSCORE_Input_Material', must be CBOR-map");
            throw new AceException("invalid/missing OSCORE_Input_Material");
        }
        
        CBORObject algC = osc.get(Constants.OS_ALG);
        this.alg = null;
        if (algC != null) {
            try {
                this.alg = AlgorithmID.FromCBOR(algC);
            } catch (CoseException e) {
                LOGGER.info("Invalid algorithmId: " + e.getMessage());
                throw new AceException(
                        "Malformed algorithm Id in OSCORE security context");
            }
        }
        
        CBORObject clientIdC = osc.get(Constants.OS_CLIENTID);
        if (clientIdC != null) {
            if (!clientIdC.getType().equals(CBORType.ByteString)) {
                LOGGER.info("Invalid parameter: 'clientId',"
                        + " must be byte-array");
                throw new AceException(
                        "Malformed client Id in OSCORE security context");
            }
            this.clientId = clientIdC.GetByteString();
        }
               
        CBORObject ctxIdC = osc.get(Constants.OS_CONTEXTID);
        if (ctxIdC != null) {
            if (!ctxIdC.getType().equals(CBORType.ByteString)) {
                LOGGER.info("Invalid parameter: 'contextID',"
                        + "must be byte-array");
                throw new AceException( 
                        "Malformed context Id in OSCORE security context");
            }
            this.contextId = ctxIdC.GetByteString();
        }
        else {
        	this.contextId = null;
        }

        CBORObject kdfC = osc.get(Constants.OS_HKDF);
        if (kdfC != null) {
            try {
                this.hkdf = AlgorithmID.FromCBOR(kdfC);
            } catch (CoseException e) {
                LOGGER.info("Invalid kdf: " + e.getMessage());
                throw new AceException(
                        "Malformed KDF in OSCORE security context");
            }
        }

        CBORObject msC = osc.get(Constants.OS_MS);
        if (msC == null || !msC.getType().equals(CBORType.ByteString)) {
            LOGGER.info("Missing or invalid parameter: 'master secret',"
                    + " must be byte-array");
            throw new AceException("malformed or missing master secret"
                    + " in OSCORE security context");
        }
        this.ms = msC.GetByteString();

        CBORObject idC = osc.get(Constants.OS_ID);
        if (idC == null || !idC.getType().equals(CBORType.ByteString)) {
            LOGGER.info("Missing or invalid parameter: 'id',"
                    + " must be byte-array");
            throw new AceException("malformed or missing input material identifier"
                    + " in OSCORE security context");
        }
        this.id = idC.GetByteString();
        
        CBORObject saltC = osc.get(Constants.OS_SALT);
        if (saltC != null) {
            if (!saltC.getType().equals(CBORType.ByteString)) {
                LOGGER.info("Invalid parameter: 'master salt',"
                        + " must be byte-array");
                throw new AceException("malformed master salt"
                        + " in OSCORE security context");
            }
            this.salt = saltC.GetByteString();
        }        
        
        CBORObject serverIdC = osc.get(Constants.OS_SERVERID);
        if (serverIdC == null 
                || !serverIdC.getType().equals(CBORType.ByteString)) {
            LOGGER.info("Missing or invalid parameter: 'serverId',"
                    + " must be byte-array");
            throw new AceException("malformed or missing server id"
                    + " in OSCORE security context");
        }
        this.serverId = serverIdC.GetByteString();
        
    }
    
    /**
     * @param isClient
     * @param n1  the client's nonce
     * @param n2  the server's nonce
     * @return  an OSCORE context based on this object 
     * @throws OSException 
     */
    public OSCoreCtx getContext(boolean isClient, byte[] n1, byte[] n2) 
            throws OSException {
        byte[] senderId;
        byte[] recipientId;

        byte[] finalSalt;
        
        // The final Master Salt is the concatenation of whole CBOR byte strings
        byte[] saltEncoded = null;
        byte[] n1Encoded = null;
        byte[] n2Encoded = null;
        if (this.salt != null) {
            CBORObject saltCBOR = CBORObject.FromObject(this.salt);
        	saltEncoded  = saltCBOR.EncodeToBytes();
        }
        CBORObject n1CBOR = CBORObject.FromObject(n1);
        CBORObject n2CBOR = CBORObject.FromObject(n2);
        n1Encoded = n1CBOR.EncodeToBytes();
        n2Encoded = n2CBOR.EncodeToBytes();
        if (saltEncoded != null) {
            finalSalt = new byte[saltEncoded.length + n1Encoded.length + n2Encoded.length];
            System.arraycopy(saltEncoded, 0, finalSalt, 0, saltEncoded.length);
            System.arraycopy(n1Encoded, 0, finalSalt, saltEncoded.length, n1Encoded.length);
            System.arraycopy(n2Encoded, 0, finalSalt, saltEncoded.length + n1Encoded.length, n2Encoded.length);
        } else {
            finalSalt = new byte[n1Encoded.length + n2Encoded.length];
            System.arraycopy(n1Encoded, 0, finalSalt, 0, n1Encoded.length);
            System.arraycopy(n2Encoded, 0, finalSalt, n1Encoded.length, n2Encoded.length);
        }
                
        if (isClient) {
        	/*
            senderId = id2;
            recipientId = id1;
            */
            senderId = this.clientId;
            recipientId = this.serverId;
            
        } else {
        	/*
            senderId = id1;
            recipientId = id2;
            */
            senderId = this.serverId;
            recipientId = this.clientId;
        }
        
        org.eclipse.californium.cose.AlgorithmID algId = null;
        org.eclipse.californium.cose.AlgorithmID hkdfId = null;
        try {
            if(this.alg != null) {
                algId = org.eclipse.californium.cose.AlgorithmID.FromCBOR(this.alg.AsCBOR());
            }
            
            if(this.hkdf != null) {
                hkdfId = org.eclipse.californium.cose.AlgorithmID.FromCBOR(this.hkdf.AsCBOR());
            }
        } catch (org.eclipse.californium.cose.CoseException e) {
            System.err.println("Failed conversion of alg or hkdf to create OSCORE Context!");
            e.printStackTrace();
        }
        
        return new OSCoreCtx(this.ms, isClient, algId, senderId, 
                recipientId, hkdfId, this.replaySize, finalSalt, 
                this.contextId, MAX_UNFRAGMENTED_SIZE);
    }
    
    /**
     * @return  the client identifier
     */
    public byte[] getClientId() {
        return this.clientId;
    }
    
    /**
     * @return  the server identifier
     */
    public byte[] getServerId() {
        return this.serverId;
    }
    
    /**
     * @return  the input material identifier
     */
    public byte[] getId() {
        return this.id;
    }
    
    /**
     * @return  the context id
     */
    public byte[] getContextId() {
        return this.contextId;
    }

}