/*
* Part of the CCNx Java Library.
*
* Copyright (C) 2010 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.io.content;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import org.ccnx.ccn.CCNHandle;
import org.ccnx.ccn.impl.CCNFlowControl;
import org.ccnx.ccn.impl.CCNFlowControl.SaveType;
import org.ccnx.ccn.impl.support.Log;
import org.ccnx.ccn.io.ErrorStateException;
import org.ccnx.ccn.protocol.ContentName;
import org.ccnx.ccn.protocol.ContentObject;
import org.ccnx.ccn.protocol.KeyLocator;
import org.ccnx.ccn.protocol.PublisherPublicKeyDigest;
import org.ccnx.ccn.protocol.SignedInfo.ContentType;
/**
* A CCNNetworkObject subclass specialized for reading and writing X509Certificates.
* X509Certificates are Serializable. So we could use a subclass of CCNSerializableObject
* to serialize them to CCN. But, we want to control their on-the-wire data format --
* using their serialization interface, the output will contain metadata only
* readable via the Java serialization interface. We want to write raw encoded
* certificates. So have to override the serialization behavior.
*
* While traditional X.509 certificates are somewhat odd in a CCNx context, sometimes
* we need to interface with protocols that will only accept those as a credential
* format. Think of them as a particular way of packaging keys.
*
* This class also serves as an example of how to write a CCNNetworkObject
* subclass that needs to implement its own serialization.
*/
public class X509CertificateObject extends CCNNetworkObject<X509Certificate> {
/**
* Write constructor.
* @param name
* @param data
* @param handle
* @throws IOException
*/
public X509CertificateObject(ContentName name, X509Certificate data, SaveType saveType, CCNHandle handle) throws IOException {
super(X509Certificate.class, false, name, data, saveType, handle);
}
/**
* Write constructor.
* @param name
* @param data
* @param publisher
* @param locator
* @param handle
* @throws IOException
*/
public X509CertificateObject(ContentName name, X509Certificate data, SaveType saveType,
PublisherPublicKeyDigest publisher,
KeyLocator locator, CCNHandle handle) throws IOException {
super(X509Certificate.class, false, name, data, saveType, publisher, locator, handle);
}
/**
* Read constructor.
* @param name
* @param handle
* @throws ContentDecodingException
* @throws IOException
*/
public X509CertificateObject(ContentName name, CCNHandle handle)
throws ContentDecodingException, IOException {
super(X509Certificate.class, false, name, (PublisherPublicKeyDigest)null, handle);
}
/**
* Read constructor.
* @param name
* @param publisher
* @param handle
* @throws ContentDecodingException
* @throws IOException
*/
public X509CertificateObject(ContentName name, PublisherPublicKeyDigest publisher,
CCNHandle handle)
throws ContentDecodingException, IOException {
super(X509Certificate.class, false, name, publisher, handle);
}
/**
* Read constructor if you already have a block.
* @param firstBlock
* @param handle
* @throws ContentDecodingException
* @throws IOException
*/
public X509CertificateObject(ContentObject firstBlock, CCNHandle handle)
throws ContentDecodingException, IOException {
super(X509Certificate.class, false, firstBlock, handle);
}
/**
* Internal constructor used by low-level network operations. Don't use unless you know what
* you are doing.
* @param name name under which to save data
* @param data data to save when save() is called; or null if the next call will be updateInBackground()
* @param publisher key (identity) to use to sign the content (null for default)
* @param locator key locator to use to tell people where to find our key, should match publisher, (null for default for key)
* @param flowControl flow controller to use for network output
* @throws IOException
*/
public X509CertificateObject(ContentName name, X509Certificate data,
PublisherPublicKeyDigest publisher,
KeyLocator locator,
CCNFlowControl flowControl) throws IOException {
super(X509Certificate.class, false, name, data, publisher, locator, flowControl);
}
/**
* Internal constructor used by low-level network operations. Don't use unless you know what
* you are doing.
* @param name name under which to save data
* @param data data to save when save() is called; or null if the next call will be updateInBackground()
* @param publisher key (identity) to use to sign the content (null for default)
* @param locator key locator to use to tell people where to find our key, should match publisher, (null for default for key)
* @param flowControl flow controller to use for network output
* @throws IOException
*/
public X509CertificateObject(ContentName name, PublisherPublicKeyDigest publisher,
CCNFlowControl flowControl) throws ContentDecodingException, IOException {
super(X509Certificate.class, false, name, publisher, flowControl);
}
/**
* Internal constructor used by low-level network operations. Don't use unless you know what
* you are doing.
* @param name name under which to save data
* @param data data to save when save() is called; or null if the next call will be updateInBackground()
* @param publisher key (identity) to use to sign the content (null for default)
* @param locator key locator to use to tell people where to find our key, should match publisher, (null for default for key)
* @param flowControl flow controller to use for network output
* @throws IOException
*/
public X509CertificateObject(ContentObject firstSegment, CCNFlowControl flowControl)
throws ContentDecodingException, IOException {
super(X509Certificate.class, false, firstSegment, flowControl);
}
/**
* Copy constructor.
*/
public X509CertificateObject(CCNNetworkObject<? extends X509Certificate> other) {
super(X509Certificate.class, other);
}
@Override
public ContentType contentType() { return ContentType.DATA; }
public X509Certificate certificate() throws ContentNotReadyException, ContentGoneException, ErrorStateException { return data(); }
public PublisherPublicKeyDigest publicKeyDigest() throws ContentNotReadyException, ContentGoneException, ErrorStateException {
X509Certificate cert = certificate();
if (null != cert) {
return new PublisherPublicKeyDigest(cert.getPublicKey());
}
return null; // don't expect to get here.
}
@Override
protected X509Certificate readObjectImpl(InputStream input) throws ContentDecodingException, IOException {
// assume we read until we have all the bytes, then decode.
// Doesn't give us a good opportunity to check whether it's of type KEY. TODO
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate certificate = (X509Certificate)cf.generateCertificate(input);
return certificate;
} catch (CertificateEncodingException e) {
Log.warning("Cannot decode certificate " + e.getClass().getName() + ": " + e.getMessage());
throw new IOException("Cannot decode certificate " + e.getClass().getName() + ": " + e.getMessage());
} catch (CertificateException e) {
Log.warning("Cannot decode certificate " + e.getClass().getName() + ": " + e.getMessage());
throw new IOException("Cannot decode certificate " + e.getClass().getName() + ": " + e.getMessage());
}
}
@Override
protected void writeObjectImpl(OutputStream output) throws ContentEncodingException, IOException {
if (null == data())
throw new ContentNotReadyException("No content available to save for object " + getBaseName());
byte[] encoded;
try {
encoded = certificate().getEncoded();
} catch (CertificateEncodingException e) {
throw new ContentEncodingException("Cannot encode certificate: " + e.getMessage(), e);
}
output.write(encoded);
}
/**
* Many cryptographic providers don't implement equals() correctly.
* @throws ContentGoneException
* @throws ContentNotReadyException
* @throws ErrorStateException
* @throws CertificateEncodingException
*/
public boolean equalsCertificate(X509Certificate otherCertificate) throws ContentNotReadyException, ContentGoneException, ErrorStateException, CertificateEncodingException {
if (!available())
throw new ContentNotReadyException("No data available to compare!");
if (certificate().equals(otherCertificate))
return true;
// might be that the provider doesn't implement equals()
return Arrays.equals(certificate().getEncoded(), otherCertificate.getEncoded());
}
public boolean equalsCertificate(X509CertificateObject otherCertificateObject) throws ContentNotReadyException, ContentGoneException, ErrorStateException, CertificateEncodingException {
return this.equalsCertificate(otherCertificateObject.certificate());
}
}