/*
* Part of the CCNx Java Library.
*
* Copyright (C) 2008-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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Serializable;
import java.security.PublicKey;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.util.logging.Level;
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.util.CryptoUtil;
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 KeyLocator specifies where a content consumer or intermediary can find the public key
* necessary to verify a piece of content. It might include the key itself, a certificate
* containing the key, or a CCN name pointing to a location where the key can be found.
*/
public class KeyLocator extends GenericXMLEncodable implements XMLEncodable, Serializable, ContentNameProvider, Cloneable {
private static final long serialVersionUID = 7608180398885293453L;
/**
* KeyLocator(name) must allow for a complete name -- i.e.
* a name and authentication information.
*/
public enum KeyLocatorType {
NAME, KEY, CERTIFICATE
}
// Fake out a union.
protected KeyName _keyName; // null if wrong type
protected PublicKey _key;
protected X509Certificate _certificate;
/**
* Make a KeyLocator containing only a name
* @param name the name
*/
public KeyLocator(ContentName name) {
this (name, (PublisherID)null);
}
/**
* Make a KeyLocator containing a key name and the desired publisher
* @param name the key name
* @param publisher the desired publisher
*/
public KeyLocator(ContentName name, PublisherID publisher) {
this(new KeyName(name, publisher));
}
/**
* Make a KeyLocator containing a key name and the desired publisher
* @param name the key name
* @param publisher the desired publisher
*/
public KeyLocator(ContentName name, PublisherPublicKeyDigest publisher) {
this(new KeyName(name, publisher));
}
/**
* Make a KeyLocator specifying a KeyName -- a structure combining the name
* at which to find the key and authentication information by which to verify it
* @param keyName the KeyName to use to retrieve and authenticate the key
*/
public KeyLocator(KeyName keyName) {
_keyName = keyName;
}
/**
* Make a KeyLocator containing an explicit public key
* @param key the key
*/
public KeyLocator(PublicKey key) {
_key = key;
}
/**
* Make a KeyLocator containing an explicit X509Certificate.
* @param certificate the certificate
*/
public KeyLocator(X509Certificate certificate) {
_certificate = certificate;
}
/**
* Internal constructor.
* @param name
* @param key
* @param certificate
*/
protected KeyLocator(KeyName name, PublicKey key, X509Certificate certificate) {
_keyName = name;
_key = key;
_certificate = certificate;
}
/**
* Implement Cloneable
*/
public KeyLocator clone() {
KeyLocator kl;
try {
kl = (KeyLocator)super.clone();
kl._keyName = _keyName;
kl._key = _key;
kl._certificate = _certificate;
return kl;
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
/**
* For use by decoders.
*/
public KeyLocator() {}
/**
* Get the key if present
* @return the key or null
*/
public PublicKey key() { return _key; }
/**
* Get the KeyName if present
* @return the KeyName or null
*/
public KeyName name() { return _keyName; }
/**
* Get the certificate if present
* @return the certificate or null
*/
public X509Certificate certificate() { return _certificate; }
/**
* Return the type of data stored in this KeyLocator - KEY, CERTIFICATE, or NAME.
* @return the type
*/
public KeyLocatorType type() {
if (null != certificate())
return KeyLocatorType.CERTIFICATE;
if (null != key())
return KeyLocatorType.KEY;
return KeyLocatorType.NAME;
}
@Override
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = PRIME * result + ((_key == null) ? 0 : _key.hashCode());
result = PRIME * result + ((_keyName == null) ? 0 : _keyName.hashCode());
result = PRIME * result + ((_certificate == null) ? 0 : _certificate.hashCode());
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 KeyLocator other = (KeyLocator) obj;
if (_key == null) {
if (other._key != null)
return false;
} else if (!_key.equals(other._key))
return false;
if (_keyName == null) {
if (other.name() != null)
return false;
} else if (!_keyName.equals(other.name()))
return false;
return true;
}
@Override
public void decode(XMLDecoder decoder) throws ContentDecodingException {
decoder.readStartElement(getElementLabel());
if (decoder.peekStartElement(CCNProtocolDTags.Key)) {
try {
byte [] encodedKey = decoder.readBinaryElement(CCNProtocolDTags.Key);
// This is a DER-encoded SubjectPublicKeyInfo.
_key = CryptoUtil.getPublicKey(encodedKey);
} catch (CertificateEncodingException e) {
Log.warning("Cannot parse stored key: error: " + e.getMessage());
throw new ContentDecodingException("Cannot parse key: ", e);
} catch (InvalidKeySpecException e) {
Log.warning("Cannot turn stored key " + " into key of appropriate type.");
throw new ContentDecodingException("Cannot turn stored key " + " into key of appropriate type.");
}
if (null == _key) {
throw new ContentDecodingException("Cannot parse key: ");
}
} else if (decoder.peekStartElement(CCNProtocolDTags.Certificate)) {
try {
byte [] encodedCert = decoder.readBinaryElement(CCNProtocolDTags.Certificate);
CertificateFactory factory = CertificateFactory.getInstance("X.509");
_certificate = (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(encodedCert));
} catch (CertificateException e) {
throw new ContentDecodingException("Cannot decode certificate: " + e.getMessage(), e);
}
if (null == _certificate) {
throw new ContentDecodingException("Cannot parse certificate! ");
}
} else {
_keyName = new KeyName();
_keyName.decode(decoder);
}
decoder.readEndElement();
}
public byte [] getEncoded() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
encode(baos);
} catch (ContentEncodingException e) {
Log.log(Level.WARNING, "This should not happen: cannot encode KeyLocator to byte array.");
Log.warningStackTrace(e);
// DKS currently returning invalid byte array...
}
return baos.toByteArray();
}
@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 (type() == KeyLocatorType.KEY) {
encoder.writeElement(CCNProtocolDTags.Key, key().getEncoded());
} else if (type() == KeyLocatorType.CERTIFICATE) {
try {
encoder.writeElement(CCNProtocolDTags.Certificate, certificate().getEncoded());
} catch (CertificateEncodingException e) {
Log.warning("CertificateEncodingException attempting to write key locator: " + e.getMessage());
throw new ContentEncodingException("CertificateEncodingException attempting to write key locator: " + e.getMessage(), e);
}
} else if (type() == KeyLocatorType.NAME) {
name().encode(encoder);
}
encoder.writeEndElement();
}
@Override
public long getElementLabel() { return CCNProtocolDTags.KeyLocator; }
@Override
public boolean validate() {
return ((null != name() || (null != key()) || (null != certificate())));
}
@Override
public String toString() {
String output = type().name() + ": ";
if (type() == KeyLocatorType.KEY) {
return output + new PublisherPublicKeyDigest(key()).toString();
} else if (type() == KeyLocatorType.CERTIFICATE) {
try {
return output + DataUtils.printHexBytes(CryptoUtil.generateCertID(certificate()));
} catch (CertificateEncodingException e) {
return output + "Unable to encode certificate: " + e.getMessage();
}
} else if (type() == KeyLocatorType.NAME) {
return output + name();
}
return output + " UNKNOWN";
}
/**
* Enables a KeyLocator to be used directly in a ContentName builder.
* @return Gets the ContentName from the KeyName
* @see ContentNameProvider
* @see ContentName#builder(org.ccnx.ccn.protocol.ContentName.StringParser, Object[])
*/
public ContentName getContentName() {
return _keyName.name();
}
}