org.ccnx.ccn.impl.security.crypto.CCNMerkleTree.java Source code

Java tutorial

Introduction

Here is the source code for org.ccnx.ccn.impl.security.crypto.CCNMerkleTree.java

Source

/*
 * Part of the CCNx Java Library.
 *
 * Copyright (C) 2008, 2009, 2012 Palo Alto Research Center, Inc.
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation. 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details. You should have received
 * a copy of the GNU Lesser General Public License along with this library;
 * if not, write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301 USA.
 */

package org.ccnx.ccn.impl.security.crypto;

import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.util.logging.Level;

import org.bouncycastle.asn1.DEROctetString;
import org.ccnx.ccn.impl.support.DataUtils;
import org.ccnx.ccn.impl.support.Log;
import org.ccnx.ccn.io.content.ContentEncodingException;
import org.ccnx.ccn.protocol.ContentName;
import org.ccnx.ccn.protocol.ContentObject;
import org.ccnx.ccn.protocol.Signature;
import org.ccnx.ccn.protocol.SignedInfo;

/**
 * Extends the basic MerkleTree for use in CCN.
 * It incorporates the CCN ContentName for an object at each node, so that
 * names are authenticated as well as content in a
 * way that intermediary CCN nodes can verify.
 * 
 * For each leaf node in the CCNMerkleTree, we compute its
 * digest in exactly the same way we would compute the digest of a ContentObject
 * node for signing on its own (incorporating the name, authentication metadata,
 * and content). We then combine all these leaf digests together into a MerkleTree,
 * and sign the root node.
 * 
 * To generate a leaf block digest, therefore, we need to know
 * - the content of the block
 * - the name for the block (which, for segmented content, includes the segmented
 *       number. If we're buffering content and building trees per buffer, the
 *       fragment numbers may carry across buffers (e.g. leaf 0 of this tree might
 *     be fragment 37 of the content as a whole)
 *     
 * - the authentication metadata. In the case of fragmented content, this is
 *     likely to be the same for all blocks. In the case of other content, the
 *     publisher is likely to be the same, but the timestamp and even maybe the
 *     type could be different -- i.e. you could use a CCNMerkleTree to amortize
 *     signature costs over any collection of data, not just a set of fragments.
 *     
 * So, we either need to hand in all the names, or have a function to call to get
 * the name for each block.
 * 
 * Note: There is no requirement that a CCNMerkleTree be built only from the segments
 *     of a single piece of content, although that is the most common use. One
 *     can build and verify a CCNMerkleTree built out of an arbitrary set of
 *     ContentObjects; this may be a useful way of limiting the number of
 *     signatures generated on constrained platforms. Eventually the CCNSegmenter
 *     will be extended to handle such collections of arbitrary objects.
 *     
 */
public class CCNMerkleTree extends MerkleTree {

    public static final String DEFAULT_MHT_ALGORITHM = "SHA256MHT";

    byte[] _rootSignature = null;
    ContentObject[] _segmentObjects = null;

    /**
     * Build a CCNMerkleTree from a set of leaf ContentObjects. 
     * @param contentObjects must be at least 2 blocks, or will throw IllegalArgumentException.
     * @param signingKey key to sign the root with
     * @throws NoSuchAlgorithmException if key or DEFAULT_DIGEST_ALGORITHM are unknown
     * @throws InvalidKeyException if signingKey is invalid
     * @throws SignatureException if we cannot sign
     */
    public CCNMerkleTree(ContentObject[] contentObjects, Key signingKey)
            throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {

        super(CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM, ((null != contentObjects) ? contentObjects.length : 0));
        _segmentObjects = contentObjects;
        if (null == _segmentObjects) {
            throw new IllegalArgumentException("Contained objects cannot be null!");
        }

        // Compute leaves and tree
        // DKS TODO -- all we're essentially doing is running the constructor now.
        // Maybe make a static method to process blocks.
        initializeTree(contentObjects);
        _rootSignature = computeRootSignature(root(), signingKey);
        setSignatures();
        if (Log.isLoggable(Log.FAC_SIGNING, Level.FINE))
            Log.fine(Log.FAC_SIGNING, "CCNMerkleTree: built a tree of " + contentObjects.length + " objects.");
    }

    /**
     * Returns the root signature on the tree.
     * @return the root signature
     */
    public byte[] rootSignature() {
        return _rootSignature;
    }

    /**
     * Generate the name of segment leafIndex, where leafIndex is the leaf number in this
     * tree. The overall index of leafIndex should be leafIndex + baseNameIndex().
     * @param leafIndex the leaf whose blockName to generate
     * @return the name
     */
    public ContentName segmentName(int leafIndex) {
        if ((leafIndex < 0) || (leafIndex > _segmentObjects.length))
            throw new IllegalArgumentException("Index out of range!");
        if ((leafIndex < _segmentObjects.length) && (null != _segmentObjects[leafIndex]))
            return _segmentObjects[leafIndex].name();
        return null;
    }

    /**
     * Return the SignedInfo for a given segment.
     * @param leafIndex the index of the leaf whose SignedInfo we want
     * @return the SignedInfo
     */
    public SignedInfo segmentSignedInfo(int leafIndex) {
        if ((leafIndex < 0) || (leafIndex > _segmentObjects.length))
            throw new IllegalArgumentException("Index out of range!");
        if (null != _segmentObjects[leafIndex]) {
            return _segmentObjects[leafIndex].signedInfo();
        }
        return null;
    }

    /**
     * Set the signature for a particular segment.
     * @param leafIndex the leaf segment to set the signature for
     * @return the Signature
     */
    public Signature segmentSignature(int leafIndex) {
        if ((leafIndex < 0) || (leafIndex > _segmentObjects.length))
            throw new IllegalArgumentException("Index out of range!");

        if (null != _segmentObjects[leafIndex]) {
            if (null == _segmentObjects[leafIndex].signature()) {
                _segmentObjects[leafIndex].setSignature(computeSignature(leafIndex));
            }
            return _segmentObjects[leafIndex].signature();
        }

        return null;
    }

    /**
     * Sets the signatures of all the contained ContentObjects.
     */
    public void setSignatures() {
        for (int i = 0; i < numLeaves(); ++i) {
            segmentSignature(i); // DKS TODO refactor, sets signature as a side effect
        }
    }

    /**
     * A version of initializeTree to go with the CCNMerkleTree(ContentObject []) constructor.
     * @param contentObjects objects to build into the tree
     * @throws NoSuchAlgorithmException if the default digest algorithm unknown
     */
    protected void initializeTree(ContentObject[] contentObjects) throws NoSuchAlgorithmException {
        if (contentObjects.length < numLeaves())
            throw new IllegalArgumentException("MerkleTree: cannot build tree from more blocks than given! Have "
                    + contentObjects.length + " blocks, asked to use: " + (numLeaves()));

        computeLeafValues(contentObjects);
        computeNodeValues();
    }

    /**
     * Construct the Signature for a given leaf. This is composed of the rootSignature(),
     * which is the same for all nodes, and the DER encoded MerklePath for this leaf as the
     * witness.
     * @param leafIndex the leaf to compute the signature for
     * @return the signature
     */
    protected Signature computeSignature(int leafIndex) {
        MerklePath path = path(leafIndex);
        return new Signature(path.derEncodedPath(), rootSignature());
    }

    /**
     * Compute the signature on the root node. It's already a digest, so in
     * theory we could just wrap it up in some PKCS#1 padding, encrypt it
     * with our private key, and voila! A signature. But there are basically
     * no crypto software packages that provide signature primitives that take
     * already-digested data and just do the padding and encryption, and so we'd
     * be asking anyone attempting to implement CCN MHT signing (including ourselves)
     * to re-implement a very complicated wheel, across a number of signature algorithms.
     * We might also want to sign with a key that does not support the digest algorithm
     * we used to compute the root (for example, DSA).
     * So take the computationally very slightly more expensive, but vastly simpler
     * (implementation-wise) approach of taking our digest and signing it with
     * a standard signing API -- which means digesting it one more time for the
     * signature. So we sign (digest + encrypt) the root digest. 
     * 
     * @param root the root digest to sign
     * @param signingKey the key to sign with
     * @return the bytes of the signature
     * @throws InvalidKeyException
     * @throws SignatureException
     * @throws NoSuchAlgorithmException
     */
    protected static byte[] computeRootSignature(byte[] root, Key signingKey)
            throws InvalidKeyException, SignatureException, NoSuchAlgorithmException {
        // Given the root of the authentication tree, compute a signature over it
        // Right now, this will digest again. It's actually quite hard to get at the raw
        // signature guts for various platforms to avoid re-digesting; too dependent on
        // the sig alg used.
        return CCNSignatureHelper.sign(null, root, signingKey);
    }

    /**
     * Compute the leaf values of the ContentObjects in this tree
     * @param contentObjects the content
     * @throws NoSuchAlgorithmException if the digestAlgorithm unknown
     */
    protected void computeLeafValues(ContentObject[] contentObjects) throws NoSuchAlgorithmException {
        // Hash the leaves
        for (int i = 0; i < numLeaves(); ++i) {
            // DKS -- need to make sure content() doesn't clone
            try {
                ContentObject co = contentObjects[i];
                byte[] blockDigest = CCNDigestHelper.digest(co.prepareContent());
                _tree[leafNodeIndex(i) - 1] = new DEROctetString(blockDigest);

                if (Log.isLoggable(Log.FAC_SIGNING, Level.FINER)) {
                    Log.finer(Log.FAC_SIGNING, "offset: " + 0 + " block length: " + co.contentLength()
                            + " blockDigest " + DataUtils.printBytes(blockDigest) + " content digest: "
                            + DataUtils.printBytes(CCNDigestHelper.digest(co.content(), 0, co.contentLength())));
                }

            } catch (ContentEncodingException e) {
                Log.info("Exception in computeBlockDigest, leaf: " + i + " out of " + numLeaves() + " type: "
                        + e.getClass().getName() + ": " + e.getMessage());
                e.printStackTrace();
                // DKS todo -- what to throw?
            }
        }
    }
}