/* * 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.protocol; import java.io.Serializable; import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.Arrays; 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.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; /** * Helper wrapper class for publisher IDs. This encodes and decodes * as one of 4 inline options, one of which also appears separately * as the PublisherPublicKeyDigest. */ public class PublisherID extends GenericXMLEncodable implements XMLEncodable, Comparable<PublisherID>, Serializable { private static final long serialVersionUID = 1766988029384725706L; /** * Move this to a centralized configuration location. */ public static final String PUBLISHER_ID_DIGEST_ALGORITHM = "SHA-256"; public static final int PUBLISHER_ID_LEN = 256/8; /** * Encoded as an inline choice. Have to map from the type field to the encoding value. */ public enum PublisherType { KEY (CCNProtocolDTags.PublisherPublicKeyDigest), CERTIFICATE (CCNProtocolDTags.PublisherCertificateDigest), ISSUER_KEY (CCNProtocolDTags.PublisherIssuerKeyDigest), ISSUER_CERTIFICATE (CCNProtocolDTags.PublisherIssuerCertificateDigest); final int _tag; PublisherType(int tag) { this._tag = tag; } public int getTag() { return _tag; } public static boolean isTypeTagVal(long tagVal) { if ((tagVal == CCNProtocolDTags.PublisherPublicKeyDigest) || (tagVal == CCNProtocolDTags.PublisherCertificateDigest) || (tagVal == CCNProtocolDTags.PublisherIssuerKeyDigest) || (tagVal == CCNProtocolDTags.PublisherIssuerCertificateDigest)) { return true; } return false; } public static PublisherType tagValToType(long tagVal) { if (tagVal == CCNProtocolDTags.PublisherPublicKeyDigest) { return KEY; } if (tagVal == CCNProtocolDTags.PublisherCertificateDigest) { return CERTIFICATE; } if (tagVal == CCNProtocolDTags.PublisherIssuerKeyDigest) { return ISSUER_KEY; } if (tagVal == CCNProtocolDTags.PublisherIssuerCertificateDigest) { return ISSUER_CERTIFICATE; } return null; } }; protected byte [] _publisherID; protected PublisherType _publisherType; /** * Create a PublisherID specifying a public key as a signer or issuer * @param key the key * @param isIssuer false if it signed the content directly, true if it signed the key of the content signer */ public PublisherID(PublicKey key, boolean isIssuer) { _publisherID = generatePublicKeyDigest(key); _publisherType = isIssuer ? PublisherType.ISSUER_KEY : PublisherType.KEY; } /** * Create a PublisherID specifying a public key in a certificate as a signer or issuer * @param cert the certificate * @param isIssuer false if it signed the content directly, true if it signed the key of the content signer */ public PublisherID(X509Certificate cert, boolean isIssuer) throws CertificateEncodingException { _publisherID = generateCertificateDigest(cert); _publisherType = isIssuer ? PublisherType.ISSUER_CERTIFICATE : PublisherType.CERTIFICATE; } /** * Create a PublisherID from a raw digest and a type * @param publisherID the digest * @param publisherType the type */ public PublisherID(byte [] publisherID, PublisherType publisherType) { if ((null == publisherID) || (publisherID.length != PUBLISHER_ID_LEN)) { throw new IllegalArgumentException("Invalid publisherID!"); } // Alas, Arrays.copyOf doesn't exist in 1.5, and we'd like // to be mostly 1.5 compatible for the macs... // _publisherPublicKeyDigest = Arrays.copyOf(publisherID, PUBLISHER_ID_LEN); _publisherID = new byte[PUBLISHER_ID_LEN]; System.arraycopy(publisherID, 0, _publisherID, 0, publisherID.length); _publisherType = publisherType; } /** * Create a signer PublisherID from an existing PublisherPublicKeyDigest * @param keyID the key digest */ public PublisherID(PublisherPublicKeyDigest keyID) { this(keyID.digest(), PublisherType.KEY); } /** * For use by decoders */ public PublisherID() {} /** * Get the id * @return id */ public byte [] id() { return _publisherID; } /** * Get the type * @return type */ public PublisherType type() { return _publisherType; } @Override public int hashCode() { final int PRIME = 31; int result = 1; result = PRIME * result + Arrays.hashCode(_publisherID); result = PRIME * result + ((_publisherType == null) ? 0 : _publisherType.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (PublisherPublicKeyDigest.class == obj.getClass()) { if (PublisherType.KEY == this.type()) return (Arrays.equals(_publisherID, ((PublisherPublicKeyDigest)obj).digest())); // TODO DKS fill in... throw new UnsupportedOperationException("Have to finish up equals!"); } if (getClass() != obj.getClass()) return false; final PublisherID other = (PublisherID) obj; if (!Arrays.equals(_publisherID, other._publisherID)) return false; if (_publisherType == null) { if (other.type() != null) return false; } else if (!_publisherType.equals(other.type())) return false; return true; } /** * Type classification methods * @return */ public boolean isSigner() { return ((PublisherType.KEY == type()) || (PublisherType.CERTIFICATE == type())); } /** * Type classification methods * @return */ public boolean isCertifier() { return ((PublisherType.ISSUER_CERTIFICATE == type()) || (PublisherType.ISSUER_KEY == type())); } /** * Type classification methods * @return */ public static boolean isPublisherType(String name) { try { if (null != PublisherType.valueOf(name)) { return true; } } catch (IllegalArgumentException e) { } return false; } /** * This is a choice. Make it possible for users of this class to peek it * when it might be optional, without them having to know about the structure. */ public static boolean peek(XMLDecoder decoder) throws ContentDecodingException { Long nextTag = decoder.peekStartElementAsLong(); if (null == nextTag) { // on end element return false; } return (PublisherType.isTypeTagVal(nextTag)); } @Override public void decode(XMLDecoder decoder) throws ContentDecodingException { // We have a choice here of one of 4 binary element types. Long nextTag = decoder.peekStartElementAsLong(); if (null == nextTag) { throw new ContentDecodingException("Cannot parse publisher ID."); } _publisherType = PublisherType.tagValToType(nextTag); if (null == _publisherType) { throw new ContentDecodingException("Invalid publisher ID, got unexpected type: " + nextTag); } _publisherID = decoder.readBinaryElement(nextTag); if (null == _publisherID) { throw new ContentDecodingException("Cannot parse publisher ID of type : " + nextTag + "."); } } @Override public void encode(XMLEncoder encoder) throws ContentEncodingException { if (!validate()) { throw new ContentEncodingException("Cannot encode " + this.getClass().getName() + ": field values missing."); } // The format of a publisher ID is a choice, a binary element tagged with // one of the 4 publisher types. encoder.writeElement(getElementLabel(), id()); } @Override public long getElementLabel() { return type().getTag(); } @Override public boolean validate() { return ((null != id() && (null != type()))); } /** * Helper method to generate a public key digest * @param key the key to digest * @return the digest */ public static byte [] generatePublicKeyDigest(Key key) { return CryptoUtil.generateKeyID(PUBLISHER_ID_DIGEST_ALGORITHM, key); } /** * Helper method to generate a certificate digest * @param cert the certificate to digest * @return the digest * @throws CertificateEncodingException */ public static byte [] generateCertificateDigest(X509Certificate cert) throws CertificateEncodingException { try { return generateCertificateDigest(PUBLISHER_ID_DIGEST_ALGORITHM, cert); } catch (NoSuchAlgorithmException e) { // DKS --big configuration problem Log.warning("Fatal Error: cannot find default algorithm " + PUBLISHER_ID_DIGEST_ALGORITHM); throw new RuntimeException("Error: can't find default algorithm " + PUBLISHER_ID_DIGEST_ALGORITHM + "! " + e.toString()); } } /** * Helper method to generate a certificate digest * @param digestAlg the digest algorithm to use * @param cert the certificate to digest * @return the digest * @throws CertificateEncodingException * @throws NoSuchAlgorithmException */ public static byte [] generateCertificateDigest(String digestAlg, X509Certificate cert) throws CertificateEncodingException, NoSuchAlgorithmException { byte [] id = null; try { byte [] encoding = cert.getEncoded(); id = CCNDigestHelper.digest(digestAlg, encoding); } catch (CertificateEncodingException e) { Log.warning("Cannot encode certificate in PublisherID.generateCertificateID: " + e.getMessage()); Log.warningStackTrace(e); throw e; } return id; } /** * Implement Comparable */ public int compareTo(PublisherID o) { int result = DataUtils.compare(this.id(), o.id()); if (0 == result) { result = (this.type().name()).compareTo(o.type().name()); } return result; } @Override public String toString() { // 16 would be the most familiar option, but 32 is shorter return type().name() + ":" + CCNDigestHelper.printBytes(id(), 32); } }