/* * Part of the CCNx Java Library. * * Copyright (C) 2008-2010, 2012, 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.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.security.DigestOutputStream; import java.security.InvalidKeyException; import java.security.Key; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; import java.security.cert.CertificateEncodingException; import java.util.Arrays; import java.util.logging.Level; import org.ccnx.ccn.ContentVerifier; import org.ccnx.ccn.KeyManager; import org.ccnx.ccn.config.SystemConfiguration; import org.ccnx.ccn.impl.encoding.BinaryXMLCodec; import org.ccnx.ccn.impl.encoding.CCNProtocolDTags; import org.ccnx.ccn.impl.encoding.GenericXMLEncodable; import org.ccnx.ccn.impl.encoding.XMLCodecFactory; 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.CCNSignatureHelper; import org.ccnx.ccn.impl.support.DataUtils; import org.ccnx.ccn.impl.support.Log; import org.ccnx.ccn.io.NullOutputStream; import org.ccnx.ccn.io.content.ContentDecodingException; import org.ccnx.ccn.io.content.ContentEncodingException; import org.ccnx.ccn.protocol.SignedInfo.ContentType; /** * Represents a CCNx data packet. * cf. Interest * * prepareContent() is called to create the MerkelTree hash. That encoding can be cached because * _name, _signedInfo, and _content are only assigned in a constructor or in decode. */ public class ContentObject extends GenericXMLEncodable implements XMLEncodable, Comparable<ContentObject>, ContentNameProvider { public static boolean DEBUG_SIGNING = false; protected ContentName _name; protected SignedInfo _signedInfo; protected byte [] _content; /** * Cache of the complete ContentObject's digest. Set when first calculated. * Used as the implicit last name component. */ protected byte [] _digest = null; protected Signature _signature; /** * We don't specify a required publisher, and right now we don't enforce * that publisherID is the digest of the key used to sign (which could actually * be handy to preserve privacy); we just use the key locator and publisherID * combined to look up keys in caches (though for right now we only put keys in * caches by straight digest; would have to offer option to put keys in caches * using some privacy-preserving function as well.. * * TODO evaluate when the gap between checking verifier and checking * publisherID matters. Probably does; could have bogus publisherID, and * then real key locator and content that uses key locator would then verify * content and user might rely on publisher ID. Make that an option, though, * even if it costs more time to check. */ public static class SimpleVerifier implements ContentVerifier { public static SimpleVerifier _defaultVerifier = null; PublisherPublicKeyDigest _requiredPublisher; KeyManager _keyManager; public synchronized static ContentVerifier getDefaultVerifier() { if (null == _defaultVerifier) { _defaultVerifier = new SimpleVerifier(null); } return _defaultVerifier; } public SimpleVerifier(PublisherPublicKeyDigest requiredPublisher) { _requiredPublisher = requiredPublisher; _keyManager = KeyManager.getDefaultKeyManager(); } public SimpleVerifier(PublisherPublicKeyDigest publisher, KeyManager keyManager) { _requiredPublisher = publisher; _keyManager = (null != keyManager) ? keyManager : KeyManager.getDefaultKeyManager(); } /* (non-Javadoc) * @see com.parc.ccn.data.security.ContentVerifier#verifyBlock(com.parc.ccn.data.ContentObject) */ public boolean verify(ContentObject object) { if (null == object) return false; if (null != _requiredPublisher) { if (!_requiredPublisher.equals(object.signedInfo().getPublisherKeyID())) return false; } try { return object.verify(_keyManager); } catch (Exception e) { if (Log.isLoggable(Level.FINE)) { Log.fine(e.getClass().getName() + " exception attempting to retrieve public key with key locator {0}: " + e.getMessage(), object.signedInfo().getKeyLocator()); Log.logStackTrace(Level.FINE, e); } return false; } } } /** * We copy the content when we get it. The intent is for this object to * be immutable. * @param digestAlgorithm * @param name * @param signedInfo * @param content * @param signature already immutable */ public ContentObject(String digestAlgorithm, // prefer OID ContentName name, SignedInfo signedInfo, byte [] content, Signature signature ) { this(name, signedInfo, content, 0, ((null == content) ? 0 : content.length), signature); } public ContentObject(String digestAlgorithm, // prefer OID ContentName name, SignedInfo signedInfo, byte [] content, int offset, int length, Signature signature) { _name = name; _signedInfo = signedInfo; _content = new byte[length]; if (null != content) System.arraycopy(content, offset, _content, 0, length); _signature = signature; if ((null != signature) && Log.isLoggable(Log.FAC_SIGNING, Level.FINEST)) { try { byte [] digest = CCNDigestHelper.digest(this.encode()); byte [] tbsdigest = CCNDigestHelper.digest(prepareContent()); if (Log.isLoggable(Level.INFO)) { Log.info("Created content object: " + name + " timestamp: " + signedInfo.getTimestamp() + " encoded digest: " + DataUtils.printBytes(digest) + " tbs content: " + DataUtils.printBytes(tbsdigest)); Log.info("Signature: " + this.signature()); } } catch (Exception e) { if (Log.isLoggable(Level.WARNING)) { Log.warning("Exception attempting to verify signature: " + e.getClass().getName() + ": " + e.getMessage()); Log.warningStackTrace(e); } } } } /** * Minimum-copy constructor. * @param digestAlgorithm * @param name * @param signedInfo * @param contentStream a stream from which to read a block of content * @param length number of bytes to try to read; will size content to this * or to the number of bytes left in the stream, whichever is smaller. * DKS TODO -- need timeout? * * Set signature with setSignature or sign once it's constructed. * @throws IOException if no bytes left in stream */ public ContentObject(String digestAlgorithm, // prefer OID ContentName name, SignedInfo signedInfo, InputStream contentStream, int length) throws IOException { _name = name; _signedInfo = signedInfo; _content = new byte[length]; int count = contentStream.read(_content); if (count < _content.length) { if (count < 0) { throw new IOException("End of stream reached when building content object!"); } else { byte [] newContent = new byte[count]; System.arraycopy(_content, 0, newContent, 0, count); _content = newContent; } } } public ContentObject( ContentName name, SignedInfo signedInfo, InputStream contentStream, int length) throws IOException { this(CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM, name, signedInfo, contentStream, length); } public ContentObject(ContentName name, SignedInfo signedInfo, byte [] content, Signature signature) { this(CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM, name, signedInfo, content, signature); } public ContentObject(ContentName name, SignedInfo signedInfo, byte [] content, int offset, int length, Signature signature) { this(CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM, name, signedInfo, content, offset, length, signature); } /** * Generate a signedInfo and a signature. * @throws SignatureException * @throws InvalidKeyException */ public ContentObject(ContentName name, SignedInfo signedInfo, byte [] content, int offset, int length, Key signingKey) throws InvalidKeyException, SignatureException { this(name, signedInfo, content, offset, length, (Signature)null); setSignature(sign(_name, _signedInfo, _content, 0, _content.length, signingKey)); } public ContentObject(ContentName name, SignedInfo signedInfo, byte [] content, Key signingKey) throws InvalidKeyException, SignatureException { this(name, signedInfo, content, 0, ((null == content) ? 0 : content.length), signingKey); } /* * Used for testing and for building small content objects deep in the * library code for specialized applications. */ public static ContentObject buildContentObject(ContentName name, ContentType type, byte[] contents, PublisherPublicKeyDigest publisher, KeyLocator locator, KeyManager keyManager, Integer freshnessSeconds, byte[] finalBlockID) { try { if (null == keyManager) { keyManager = KeyManager.getDefaultKeyManager(); } Key signingKey = keyManager.getSigningKey(publisher); if ((null == publisher) || (null == signingKey)) { signingKey = keyManager.getDefaultSigningKey(); publisher = keyManager.getPublisherKeyID(signingKey); } if (null == locator) locator = keyManager.getKeyLocator(signingKey); return new ContentObject(name, new SignedInfo(publisher, null, type, locator, freshnessSeconds, finalBlockID), contents, signingKey); } catch (Exception e) { Log.warning("Cannot build content object for publisher: {0}", publisher); Log.infoStackTrace(e); } return null; } public static ContentObject buildContentObject(ContentName name, ContentType type, byte[] contents, PublisherPublicKeyDigest publisher, KeyLocator locator, KeyManager keyManager, byte[] finalBlockID) { return buildContentObject(name, type, contents, publisher, locator, keyManager, null, finalBlockID); } public static ContentObject buildContentObject(ContentName name, ContentType type, byte[] contents, PublisherPublicKeyDigest publisher, KeyManager keyManager, byte[] finalBlockID) { return buildContentObject(name, type, contents, publisher, null, keyManager, finalBlockID); } public static ContentObject buildContentObject(ContentName name, byte[] contents, PublisherPublicKeyDigest publisher, KeyManager keyManager, byte[] finalBlockID) { return buildContentObject(name, ContentType.DATA, contents, publisher, keyManager, finalBlockID); } public static ContentObject buildContentObject(ContentName name, byte [] contents) { return buildContentObject(name, contents, null, null, null); } public static ContentObject buildContentObject(ContentName name, ContentType type, byte [] contents) { return buildContentObject(name, type, contents, null, null, null); } public static ContentObject buildContentObject(ContentName name, byte [] contents, PublisherPublicKeyDigest publisher) { return buildContentObject(name, contents, publisher, null, null); } public ContentObject() {} // for use by decoders public ContentObject clone() { // Constructor will clone the _content, signedInfo and signature are immutable types. return new ContentObject(_name, _signedInfo, _content, _signature); } /** * DKS -- return these as final for now; stopgap till refactor that makes * internal version final. * @return Name of the content object - without the final implicit digest component. */ public final ContentName name() { return _name; } /** * To allow ContentObjects to be directly included in ContentName builders. This will supply * the name of the ContentObject *without* the final implicit digest component. */ public final ContentName getContentName() { return _name; } /** * @return Name of the content object, complete with the final implicit digest component. */ public ContentName fullName() { return new ContentName(_name, digest()); } public final SignedInfo signedInfo() { return _signedInfo;} /** * Final here doesn't really make it immutable. There have been * proposals to clone() the content on return, but many places use this * and it would be expensive. * @return */ public final byte [] content() { return _content; } /** * Avoid problems where content().length might be expensive. * @return content length in bytes */ public final int contentLength() { return ((null == _content) ? 0 : _content.length); } public final Signature signature() { return _signature; } /** * Used by NetworkObject to decode the object from a network stream. * @see org.ccnx.ccn.impl.encoding.XMLEncodable */ public void decode(XMLDecoder decoder) throws ContentDecodingException { decoder.readStartElement(getElementLabel()); _signature = new Signature(); _signature.decode(decoder); _name = new ContentName(); _name.decode(decoder); _signedInfo = new SignedInfo(); _signedInfo.decode(decoder); _content = decoder.readBinaryElement(CCNProtocolDTags.Content); decoder.readEndElement(); } /** * Used by NetworkObject to encode the object to a network stream. * @see org.ccnx.ccn.impl.encoding.XMLEncodable */ public void encode(XMLEncoder encoder) throws ContentEncodingException { if (!validate()) { throw new ContentEncodingException("Cannot encode " + this.getClass().getName() + ": field values missing."); } encoder.writeStartElement(getElementLabel()); signature().encode(encoder); name().encode(encoder); signedInfo().encode(encoder); encoder.writeElement(CCNProtocolDTags.Content, _content); encoder.writeEndElement(); } @Override public long getElementLabel() { return CCNProtocolDTags.ContentObject; } @Override public boolean validate() { // recursive? // null content ok return ((null != name()) && (null != signedInfo()) && (null != signature())); } @Override public int hashCode() { final int PRIME = 31; int result = 1; result = PRIME * result + ((_name == null) ? 0 : _name.hashCode()); result = PRIME * result + ((_signedInfo == null) ? 0 : _signedInfo.hashCode()); result = PRIME * result + ((_signature == null) ? 0 : _signature.hashCode()); result = PRIME * result + Arrays.hashCode(_content); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; final ContentObject other = (ContentObject) obj; if (_name == null) { if (other.name() != null) return false; } else if (!_name.equals(other.name())) return false; if (_signedInfo == null) { if (other.signedInfo() != null) return false; } else if (!_signedInfo.equals(other.signedInfo())) return false; if (_signature == null) { if (other.signature() != null) return false; } else if (!_signature.equals(other.signature())) return false; if (!Arrays.equals(_content, other._content)) return false; return true; } /** * External function to set signature if generating it some special way * (e.g. with a bulk signer). * @param signature */ public void setSignature(Signature signature) { if (null != _signature) { // Only do this if FAC_SIGNING is on, as we use it in tests. if (Log.isLoggable(Log.FAC_SIGNING, Level.FINE)) Log.fine(Log.FAC_SIGNING, "Setting signature on content object: " + name() + " after signature already set!"); } if (null == signature) { if (Log.isLoggable(Log.FAC_SIGNING, Level.FINE)) Log.fine(Log.FAC_SIGNING, "Setting signature to null on content object: " + name()); } _signature = signature; } public void sign(Key signingKey) throws InvalidKeyException, SignatureException { // Use _content to avoid case where content() might want to clone. setSignature(sign(this.name(), this.signedInfo(), this._content, 0, this._content.length, signingKey)); } public void sign(String digestAlgorithm, Key signingKey) throws InvalidKeyException, SignatureException, NoSuchAlgorithmException { setSignature(sign(this.name(), this.signedInfo(), this._content, 0, this._content.length, digestAlgorithm, signingKey)); } public static Signature sign(ContentName name, SignedInfo signedInfo, byte [] content, int offset, int length, Key signingKey) throws SignatureException, InvalidKeyException { try { return sign(name, signedInfo, content, offset, length, CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM, signingKey); } catch (NoSuchAlgorithmException e) { if (Log.isLoggable(Level.WARNING)) Log.warning("Cannot find default digest algorithm: " + CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM); Log.warningStackTrace(e); throw new SignatureException(e); } } /** * Generate a signature on a name-content mapping. This * signature is specific to both this content signedInfo * and this name. The SignedInfo no longer contains * a proxy for the content, so we sign the content itself * directly. This is used with simple algorithms that don't * generate a witness. * @throws SignatureException * @throws NoSuchAlgorithmException * @throws InvalidKeyException */ public static Signature sign(ContentName name, SignedInfo signedInfo, byte [] content, int offset, int length, String digestAlgorithm, Key signingKey) throws SignatureException, InvalidKeyException, NoSuchAlgorithmException { // Build XML document byte [] signature = null; try { byte [] toBeSigned = prepareContent(name, signedInfo, content, offset, length); signature = CCNSignatureHelper.sign(digestAlgorithm, toBeSigned, signingKey); } catch (ContentEncodingException e) { Log.logException("Exception encoding internally-generated XML name!", e); throw new SignatureException(e); } return new Signature(digestAlgorithm, null, signature); } /** * @see ContentObject#verify(ContentObject, Key) */ public boolean verify(Key key) throws InvalidKeyException, SignatureException, NoSuchAlgorithmException, ContentEncodingException { return verify(this, key); } public boolean verify(KeyManager keyManager) throws SignatureException, NoSuchAlgorithmException, ContentEncodingException, InvalidKeyException { return verify(this, keyManager); } /** * Want to verify a content object. First compute the * witness result (e.g. Merkle path root, or possibly content * proxy), and make it available to the caller if caller just * needs to check whether it matches a previous round. Then * verify the actual signature. * * @param verifySignature If we have a collection of blocks * all authenticated by the public key signature, we may * only need to verify that signature once. If verifySignature * is true, we do that work. If it is false, we simply verify * that this piece of content matches that signature; assuming * that the caller has already verified that signature. If you're * not sure what all this means, you shouldn't be calling this * one; use the simple verify above. * @param key If the caller already knows a key * that should be used to verify the signature, they can * pass it in. Otherwise, the key locator in the object * will be used to find the key. * @throws SignatureException * @throws NoSuchAlgorithmException * @throws InvalidKeyException */ public static boolean verify(ContentObject object, Key key) throws SignatureException, InvalidKeyException, NoSuchAlgorithmException, ContentEncodingException { if (null == key) { throw new SignatureException("Cannot verify object without a verification key -- key cannot be null!"); } // Start with the cheap part. Derive the content proxy that was signed. This is // either the root of the MerkleHash tree, the content itself, or the digest of // the content. byte [] contentProxy = null; try { // Callers that think they don't need to recompute the signature can just compute // the proxy and check. // The proxy may be dependent on the whole object. If there is a proxy, signature // is over that. Otherwise, signature is over hash of the content and name and signedInfo. contentProxy = object.computeProxy(); } catch (CertificateEncodingException e) { if (Log.isLoggable(Level.INFO)) Log.info("Encoding exception attempting to verify content digest for object: " + object.name() + ". Signature verification fails."); return false; } boolean result; if (null != contentProxy) { result = CCNSignatureHelper.verify(contentProxy, object.signature().signature(), object.signature().digestAlgorithm(), key); } else { result = verify(object.name(), object.signedInfo(), object.content(), object.signature(), key); } if ((!result) && Log.isLoggable(Log.FAC_VERIFY, Level.WARNING)) { Log.info("VERIFICATION FAILURE: " + object.name() + " timestamp: " + object.signedInfo().getTimestamp() + " content length: " + object.contentLength() + " ephemeral digest: " + DataUtils.printBytes(object.digest()) + " to be signed sha256 digest: " + DataUtils.printHexBytes(CCNDigestHelper.digest(object.prepareContent()))); SystemConfiguration.outputDebugObject(object); } return result; } public static boolean verify(ContentObject object, KeyManager keyManager) throws SignatureException, InvalidKeyException, NoSuchAlgorithmException, ContentEncodingException { try { if (null == keyManager) keyManager = KeyManager.getDefaultKeyManager(); Key verificationKey = keyManager.getVerificationKey( object.signedInfo().getPublisherKeyID(), object.signedInfo().getKeyLocator()); if (null == verificationKey) { throw new SignatureException("Cannot obtain key to verify object. Publisher: " + object.signedInfo().getPublisherKeyID() + " Key locator: " + object.signedInfo().getKeyLocator()); } return verify(object, verificationKey); } catch (IOException e) { throw new SignatureException("Cannot obtain key to verify object. Key locator: " + object.signedInfo().getKeyLocator() + " exception: " + e.getMessage(), e); } } /** * Verify the public key signature on a content object. * Does not verify that the content matches the signature, * merely that the signature over the name and content * signedInfo is correct and was performed with the * indicated public key. * @param contentProxy the proxy for the content that was signed. This could * be the content itself, a digest of the content, or the root of a Merkle hash tree. * @return * @throws SignatureException * @throws NoSuchAlgorithmException * @throws ContentEncodingException * @throws InvalidKeyException */ public static boolean verify( ContentName name, SignedInfo signedInfo, byte [] content, Signature signature, Key key) throws SignatureException, InvalidKeyException, NoSuchAlgorithmException, ContentEncodingException { if (null == key) { throw new SignatureException("Cannot verify object without verification key -- key cannot be null!"); } byte [] preparedContent = prepareContent(name, signedInfo, content); // Now, check the signature. boolean result = CCNSignatureHelper.verify(preparedContent, signature.signature(), (signature.digestAlgorithm() == null) ? CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM : signature.digestAlgorithm(), key); return result; } public static boolean verify(byte[] proxy, byte [] signature, SignedInfo signedInfo, String digestAlgorithm, Key key) throws InvalidKeyException, SignatureException, NoSuchAlgorithmException { if (null == key) { throw new SignatureException("Cannot verify object without public key -- public key cannot be null!"); } // Now, check the signature. boolean result = CCNSignatureHelper.verify(proxy, signature, (digestAlgorithm == null) ? CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM : digestAlgorithm, key); return result; } public static boolean verify(byte[] proxy, byte [] signature, SignedInfo signedInfo, String digestAlgorithm, KeyManager keyManager) throws InvalidKeyException, SignatureException, NoSuchAlgorithmException { try { if (null == keyManager) keyManager = KeyManager.getDefaultKeyManager(); Key publicKey = keyManager.getVerificationKey( signedInfo.getPublisherKeyID(), signedInfo.getKeyLocator()); if (null == publicKey) { throw new SignatureException("Cannot obtain public key to verify object. Publisher: " + signedInfo.getPublisherKeyID() + " Key locator: " + signedInfo.getKeyLocator()); } return verify(proxy, signature, signedInfo, digestAlgorithm, publicKey); } catch (IOException e) { throw new SignatureException("IOException attempting to obtain public key to verify object. Key locator: " + signedInfo.getKeyLocator() + " exception: " + e.getClass().getName() + ": " + e.getMessage(), e); } } public byte [] computeProxy() throws CertificateEncodingException, ContentEncodingException { // Given a witness and an object, compute the proxy. if (null == content()) return null; if ((null == signature()) || (null == signature().witness())) { return null; } // Have to eventually handle various forms of witnesses... // Need to take an algorithm to control the digest used. byte[] blockDigest = CCNDigestHelper.digest( prepareContent()); return signature().computeProxy(blockDigest, true); } public byte [] prepareContent() throws ContentEncodingException { return prepareContent(name(), signedInfo(), content()); } public static byte [] prepareContent(ContentName name, SignedInfo signedInfo, byte [] content) throws ContentEncodingException { return prepareContent(name, signedInfo, content, 0, ((null == content) ? 0 : content.length)); } /** * Prepare digest for signature. * DKS TODO -- limit extra copies -- shouldn't be returning a byte array * that is just digested. * @return */ public static byte [] prepareContent(final ContentName name, final SignedInfo signedInfo, final byte [] content, int start, int length) throws ContentEncodingException { if ((null == name) || (null == signedInfo)) { Log.info("Name and signedInfo must not be null."); throw new ContentEncodingException("prepareContent: name, signedInfo must not be null."); } // Do setup. Binary codec doesn't write a preamble or anything. // If allow to pick, text encoder would sometimes write random stuff... ByteArrayOutputStream baos = new ByteArrayOutputStream(); XMLEncoder encoder = XMLCodecFactory.getEncoder(BinaryXMLCodec.CODEC_NAME); encoder.beginEncoding(baos); // We include the tags in what we verify, to allow routers to merely // take a chunk of data from the packet and sign/verify it en masse name.encode(encoder); signedInfo.encode(encoder); // We treat content as a blob according to the binary codec. Want to always // sign the same thing, plus it's really hard to do the automated codec // stuff without doing a whole document, unless we do some serious // rearranging. encoder.writeElement(CCNProtocolDTags.Content, content, start, length); encoder.endEncoding(); return baos.toByteArray(); } /** * Encode this object and calculate the digest. */ protected byte[] calcDigest() { MessageDigest md; try { md = MessageDigest.getInstance(CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM); DigestOutputStream dos = new DigestOutputStream(new NullOutputStream(), md); encode(dos); } catch (NoSuchAlgorithmException e) { // Should never happen since we are using a default algorithm. throw new RuntimeException(e); } catch (ContentEncodingException e) { // Should never happen since we are writing out to make a digest only. throw new RuntimeException(e); } return md.digest(); } /** * Calculates a digest of the wire representation of this ContentObject. * This is used as the implicit final name component. * Note: the value is cached, so subsequent calls are fast. */ public byte [] digest() { if (_digest == null) _digest = calcDigest(); return _digest; } public int compareTo(ContentObject o) { return name().compareTo(o.name()); } /* * Type-checkers for built-in types. */ public boolean isType(ContentType type) { return signedInfo().getType().equals(type); } public boolean isData() { return isType(ContentType.DATA); } public boolean isLink() { return isType(ContentType.LINK); } public boolean isGone() { return isType(ContentType.GONE); } public boolean isNACK() { return isType(ContentType.NACK); } public boolean isKey() { return isType(ContentType.KEY); } /** * To aid debugging we output a human readable summary of this object here. */ public String toString() { StringBuffer s = new StringBuffer(); s.append(String.format("CObj: name=%s, digest=%s, SI:%s len=%d, data=", _name, DataUtils.printHexBytes(digest()), _signedInfo, _content.length)); int len = _content.length; if (len > 16) len = 16; s.append(Component.printURI(_content, 0, len)); return s.toString(); } }