/* * 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? } } } }