/*
* Part of the CCNx Java Library.
*
* Copyright (C) 2008, 2009 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.cert.CertificateEncodingException;
import java.util.Arrays;
import java.util.Enumeration;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERInteger;
import org.bouncycastle.asn1.DERObject;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.DigestInfo;
import org.ccnx.ccn.impl.security.crypto.util.CryptoUtil;
import org.ccnx.ccn.impl.security.crypto.util.OIDLookup;
/**
* A representation of a path through a MerkleTree.
*/
public class MerklePath {
int _leafNodeIndex;
DEROctetString [] _path = null;
// DKS TODO: implement lookup mechanism to get MHT
// OID from component digest OID. Right now just pull
// from CCNMerkleTree.
/**
* Create a MerklePath for a given leaf
* @param leafNodeIndex the leaf index
* @param path the node digests necessary to verify that leaf
*/
public MerklePath(int leafNodeIndex, DEROctetString [] path) {
_leafNodeIndex = leafNodeIndex;
_path = path;
}
/**
* Decode a DER encoded MerklePath
* @param derEncodedPath the encoded path
* @throws CertificateEncodingException if there is a decoding error
*/
public MerklePath(byte [] derEncodedPath) throws CertificateEncodingException {
DERObject decoded = CryptoUtil.decode(derEncodedPath);
ASN1Sequence seq = (ASN1Sequence)decoded;
DERInteger intVal = (DERInteger)seq.getObjectAt(0);
_leafNodeIndex = intVal.getValue().intValue();
ASN1Sequence seqOf = (ASN1Sequence)seq.getObjectAt(1);
_path = new DEROctetString[seqOf.size()];
Enumeration<?> en = seqOf.getObjects();
int i=0;
while (en.hasMoreElements()) {
_path[i++] = (DEROctetString)en.nextElement();
}
}
/**
* Compute the parent digest of the current node
* @param node the current node
* @param length the length of the path at this point
* @param pathDigest the previously computed digest along this path
* @return the parent digest
*/
protected byte [] computeParent(int node, int length, byte [] pathDigest) {
byte [] parentDigest = null;
if (MerkleTree.isRight(node)) {
parentDigest = MerkleTree.computeNodeDigest(entry(length-1).getOctets(), pathDigest);
} else {
parentDigest = MerkleTree.computeNodeDigest(pathDigest, entry(length-1).getOctets());
}
return parentDigest;
}
/**
* Take the content block for which this is the MerklePath,
* and compute the root digest for verification. The caller then needs
* to check whether it matches the root, and
* the root is authentic (signed by a trusted key).
* @param nodeContent either the content of the block or its
* digest. If a subclass of MerkleTree overrides computeBlockDigest,
* a caller must hand in the digest, as this uses the MerkleTree default.
* @param isDigest was this node already digested, or do we need to digest it
* @return the computed root digest
*/
public byte [] root(byte [] nodeContent, boolean isDigest) {
if ((leafNodeIndex() < MerkleTree.ROOT_NODE) || (_path == null) ||
(_path.length == 0) || (null == nodeContent)) {
throw new IllegalArgumentException("MerklePath value illegal -- cannot verify!");
}
// subclasses must hand in the precomputed digest if they
// override computeBlockDigest.
byte [] leafDigest = (isDigest ? nodeContent :
MerkleTree.computeBlockDigest(nodeContent));
// Now, work our way up through the nodes in the path.
int length = pathLength();
int node = leafNodeIndex();
byte [] pathDigest = leafDigest;
// With the extended binary tree, this becomes simple -- all paths are
// full, it's just that some are shorter than others...
while (node != MerkleTree.ROOT_NODE) {
pathDigest = computeParent(node, length, pathDigest);
length--;
node = MerkleTree.parent(node);
}
return pathDigest;
}
/**
* Get an in the path, where i is the index into the path array.
* @param i the entry we want
* @return the entry
*/
public DEROctetString entry(int i) {
if ((i < 0) || (i >= _path.length))
return null;
return _path[i];
}
/**
* Return the leaf node this path is for
* @return the leaf node index
*/
public int leafNodeIndex() { return _leafNodeIndex; }
public int pathLength() {
if ((null == _path) || (_path.length == 0))
return 0;
return _path.length;
}
/**
* DER-encode the path. Embed it in a DigestInfo
* with the appropriate algorithm identifier.
* @return the DER-encoded path
*/
public byte [] derEncodedPath() {
/**
* Sequence of OCTET STRING
*/
DERSequence sequenceOf = new DERSequence(_path);
/**
* Sequence of INTEGER, SEQUENCE OF OCTET STRING
*/
DERInteger intVal = new DERInteger(leafNodeIndex());
ASN1Encodable [] pathStruct = new ASN1Encodable[]{intVal, sequenceOf};
DERSequence encodablePath = new DERSequence(pathStruct);
byte [] encodedPath = encodablePath.getDEREncoded();
// Wrap it up in a DigestInfo
return CCNDigestHelper.digestEncoder(CCNMerkleTree.DEFAULT_MHT_ALGORITHM, encodedPath);
}
/**
* Determine whether a given DigestInfo contains a MerklePath
* @param info the DigestInfo
* @return true if this is a MerklePath, false otherwise
*/
public static boolean isMerklePath(DigestInfo info) {
AlgorithmIdentifier digestAlg =
new AlgorithmIdentifier(OIDLookup.getDigestOID(CCNMerkleTree.DEFAULT_MHT_ALGORITHM));
return (info.getAlgorithmId().equals(digestAlg));
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + _leafNodeIndex;
result = prime * result + Arrays.hashCode(_path);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MerklePath other = (MerklePath) obj;
if (_leafNodeIndex != other._leafNodeIndex)
return false;
if (!Arrays.equals(_path, other._path))
return false;
return true;
}
}