/* * Part of the CCNx Java Library. * * Copyright (C) 2008, 2009, 2013 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.protocol; import java.security.cert.CertificateEncodingException; import java.util.Arrays; import org.bouncycastle.asn1.x509.DigestInfo; import org.ccnx.ccn.impl.encoding.CCNProtocolDTags; import org.ccnx.ccn.impl.encoding.GenericXMLEncodable; import org.ccnx.ccn.impl.encoding.XMLDecoder; import org.ccnx.ccn.impl.encoding.XMLEncodable; import org.ccnx.ccn.impl.encoding.XMLEncoder; import org.ccnx.ccn.impl.security.crypto.CCNDigestHelper; import org.ccnx.ccn.impl.security.crypto.MerklePath; import org.ccnx.ccn.impl.security.crypto.util.OIDLookup; import org.ccnx.ccn.impl.support.DataUtils; import org.ccnx.ccn.impl.support.Log; import org.ccnx.ccn.io.content.ContentDecodingException; import org.ccnx.ccn.io.content.ContentEncodingException; /** * A class to encapsulate Signature data within a ContentObject. * A Signature contains three components: the digestAlgorithm used to generate the * digest, the bits of the signature itself, and an optional "witness" which is used in the * verification of aggregated signatures -- signatures that are generated over * multiple objects at once. Each object in such a signature group has its own * witness, which is necessary to verify that object with respect to that signature. * For example, if a set of content objects is digested into a Merkle hash tree, * the signature bits for each member of set would contain the same public * key signature on the root of the hash tree, and the witness would contain * a representation of the path through the Merkle tree that one needs to traverse * to verify that individual block. * * For an explanation of why we separate the digest algorithm in the signature * (rather than no algorithm at all, or a composite signature algorithm), see * ccnx.xsd. */ public class Signature extends GenericXMLEncodable implements XMLEncodable, Comparable<Signature>, Cloneable { byte [] _witness; byte [] _signature; String _digestAlgorithm; /** * Build a Signature * @param digestAlgorithm if null, will use default * @param witness can be null * @param signature */ public Signature(String digestAlgorithm, byte [] witness, byte [] signature) { _witness = witness; _signature = signature; _digestAlgorithm = digestAlgorithm; } /** * Builds a Signature using the default digest algorithm. * @param witness can be null * @param signature */ public Signature(byte [] witness, byte [] signature) { this(null, witness, signature); } /** * Builds a Signature with the default digest algorithm and no witness * @param signature */ public Signature(byte [] signature) { this(null, null, signature); } /** * For use by decoders */ public Signature() {} /** * Get the signature * @return the signature */ public final byte [] signature() { return _signature; } /** * Get the witness * @return the witness */ public final byte [] witness() { return _witness; } /** * Get the digest algorithm * @return the digest algorithm */ public String digestAlgorithm() { if (null == _digestAlgorithm) return CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM; return _digestAlgorithm; } @Override public void decode(XMLDecoder decoder) throws ContentDecodingException { decoder.readStartElement(getElementLabel()); if (decoder.peekStartElement(CCNProtocolDTags.DigestAlgorithm)) { _digestAlgorithm = decoder.readUTF8Element(CCNProtocolDTags.DigestAlgorithm); } if (decoder.peekStartElement(CCNProtocolDTags.Witness)) { _witness = decoder.readBinaryElement(CCNProtocolDTags.Witness); } _signature = decoder.readBinaryElement(CCNProtocolDTags.SignatureBits); decoder.readEndElement(); } @Override public void encode(XMLEncoder encoder) throws ContentEncodingException { if (!validate()) { throw new ContentEncodingException("Cannot encode " + this.getClass().getName() + ": field values missing."); } encoder.writeStartElement(getElementLabel()); if ((null != digestAlgorithm()) && (!digestAlgorithm().equals(CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM))) { encoder.writeElement(CCNProtocolDTags.DigestAlgorithm, OIDLookup.getDigestOID(digestAlgorithm())); } if (null != witness()) { // needs to handle null witness encoder.writeElement(CCNProtocolDTags.Witness, _witness); } encoder.writeElement(CCNProtocolDTags.SignatureBits, _signature); encoder.writeEndElement(); } @Override public long getElementLabel() { return CCNProtocolDTags.Signature; } @Override public boolean validate() { return null != signature(); } /** * Implement Cloneable */ public Signature clone() { Signature s; try { s = (Signature)super.clone(); s._witness = (null == _witness) ? null : _witness.clone(); s._signature = _signature.clone(); return s; } catch (CloneNotSupportedException e) { throw new AssertionError(e); } } /** * Implement Comparable */ public int compareTo(Signature o) { int result = 0; if (null == digestAlgorithm()) { if (null != o.digestAlgorithm()) result = CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM.compareTo(o.digestAlgorithm()); } else { result = digestAlgorithm().compareTo((null == o.digestAlgorithm()) ? CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM : o.digestAlgorithm()); } if (result == 0) result = DataUtils.compare(witness(), o.witness()); if (result == 0) result = DataUtils.compare(this.signature(), o.signature()); return result; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + Arrays.hashCode(_signature); result = prime * result + Arrays.hashCode(_witness); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Signature other = (Signature) obj; if (null == digestAlgorithm()) { if (null != other.digestAlgorithm()) if (!CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM.equals(other.digestAlgorithm())) return false; } else { if (!digestAlgorithm().equals((null == other.digestAlgorithm()) ? CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM : other.digestAlgorithm())) return false; } if (!Arrays.equals(_signature, other._signature)) return false; if (!Arrays.equals(_witness, other._witness)) return false; return true; } /** * Compute the content proxy for a given node. This should likely move somewhere else * @param nodeContent the content stored at this node * @param isDigest is the content already digested * @return the proxy digest (for example, the computed root of the Merkle hash tree) for this node * @throws CertificateEncodingException if we cannot decode the witness */ public byte[] computeProxy(byte[] nodeContent, boolean isDigest) throws CertificateEncodingException { if (null == witness()) return null; DigestInfo info = CCNDigestHelper.digestDecoder(witness()); byte [] proxy = null; if (MerklePath.isMerklePath(info)) { MerklePath mp = new MerklePath(info.getDigest()); proxy = mp.root(nodeContent, isDigest); } else { Log.warning("Unexpected witness type: " + info.getAlgorithmId().toString()); } return proxy; } }