/* * Copyright 2002-2006 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the LICENSE file that accompanied this code. * * This code 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 General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package sun.security.x509; import java.io.IOException; import java.util.*; import sun.security.util.BitArray; import sun.security.util.DerOutputStream; import sun.security.util.DerValue; /** * Represent the DistributionPoint sequence used in the CRL * Distribution Points Extension (OID = 2.5.29.31). * <p> * The ASN.1 definition for this is: * <pre> * DistributionPoint ::= SEQUENCE { * distributionPoint [0] DistributionPointName OPTIONAL, * reasons [1] ReasonFlags OPTIONAL, * cRLIssuer [2] GeneralNames OPTIONAL } * * DistributionPointName ::= CHOICE { * fullName [0] GeneralNames, * nameRelativeToCRLIssuer [1] RelativeDistinguishedName } * * ReasonFlags ::= BIT STRING { * unused (0), * keyCompromise (1), * cACompromise (2), * affiliationChanged (3), * superseded (4), * cessationOfOperation (5), * certificateHold (6), * privilegeWithdrawn (7), * aACompromise (8) } * * GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName * * GeneralName ::= CHOICE { * otherName [0] INSTANCE OF OTHER-NAME, * rfc822Name [1] IA5String, * dNSName [2] IA5String, * x400Address [3] ORAddress, * directoryName [4] Name, * ediPartyName [5] EDIPartyName, * uniformResourceIdentifier [6] IA5String, * iPAddress [7] OCTET STRING, * registeredID [8] OBJECT IDENTIFIER } * * RelativeDistinguishedName ::= * SET OF AttributeTypeAndValue * * AttributeTypeAndValue ::= SEQUENCE { * type AttributeType, * value AttributeValue } * * AttributeType ::= OBJECT IDENTIFIER * * AttributeValue ::= ANY DEFINED BY AttributeType * </pre> * <p> * Instances of this class are designed to be immutable. However, since this * is an internal API we do not use defensive cloning for values for * performance reasons. It is the responsibility of the consumer to ensure * that no mutable elements are modified. * * @author Anne Anderson * @author Andreas Sterbenz * @since 1.4.2 * @see CRLDistributionPointsExtension */ public class DistributionPoint { // reason flag bits // NOTE that these are NOT quite the same as the CRL reason code extension public final static int KEY_COMPROMISE = 1; public final static int CA_COMPROMISE = 2; public final static int AFFILIATION_CHANGED = 3; public final static int SUPERSEDED = 4; public final static int CESSATION_OF_OPERATION = 5; public final static int CERTIFICATE_HOLD = 6; public final static int PRIVILEGE_WITHDRAWN = 7; public final static int AA_COMPROMISE = 8; private static final String[] REASON_STRINGS = { null, "key compromise", "CA compromise", "affiliation changed", "superseded", "cessation of operation", "certificate hold", "privilege withdrawn", "AA compromise", }; // context specific tag values private static final byte TAG_DIST_PT = 0; private static final byte TAG_REASONS = 1; private static final byte TAG_ISSUER = 2; private static final byte TAG_FULL_NAME = 0; private static final byte TAG_REL_NAME = 1; // only one of fullName and relativeName can be set private GeneralNames fullName; private RDN relativeName; // reasonFlags or null private boolean[] reasonFlags; // crlIssuer or null private GeneralNames crlIssuer; // cached hashCode value private volatile int hashCode; /** * Constructor for the class using GeneralNames for DistributionPointName * * @param fullName the GeneralNames of the distribution point; may be null * @param reasons the CRL reasons included in the CRL at this distribution * point; may be null * @param issuer the name(s) of the CRL issuer for the CRL at this * distribution point; may be null */ public DistributionPoint(GeneralNames fullName, boolean[] reasonFlags, GeneralNames crlIssuer) { if ((fullName == null) && (crlIssuer == null)) { throw new IllegalArgumentException ("fullName and crlIssuer may not both be null"); } this.fullName = fullName; this.reasonFlags = reasonFlags; this.crlIssuer = crlIssuer; } /** * Constructor for the class using RelativeDistinguishedName for * DistributionPointName * * @param relativeName the RelativeDistinguishedName of the distribution * point; may not be null * @param reasons the CRL reasons included in the CRL at this distribution * point; may be null * @param issuer the name(s) of the CRL issuer for the CRL at this * distribution point; may not be null or empty. */ public DistributionPoint(RDN relativeName, boolean[] reasonFlags, GeneralNames crlIssuer) { if ((relativeName == null) && (crlIssuer == null)) { throw new IllegalArgumentException ("relativeName and crlIssuer may not both be null"); } this.relativeName = relativeName; this.reasonFlags = reasonFlags; this.crlIssuer = crlIssuer; } /** * Create the object from the passed DER encoded form. * * @param val the DER encoded form of the DistributionPoint * @throws IOException on error */ public DistributionPoint(DerValue val) throws IOException { if (val.tag != DerValue.tag_Sequence) { throw new IOException("Invalid encoding of DistributionPoint."); } // Note that all the fields in DistributionPoint are defined as // being OPTIONAL, i.e., there could be an empty SEQUENCE, resulting // in val.data being null. while ((val.data != null) && (val.data.available() != 0)) { DerValue opt = val.data.getDerValue(); if (opt.isContextSpecific(TAG_DIST_PT) && opt.isConstructed()) { if ((fullName != null) || (relativeName != null)) { throw new IOException("Duplicate DistributionPointName in " + "DistributionPoint."); } DerValue distPnt = opt.data.getDerValue(); if (distPnt.isContextSpecific(TAG_FULL_NAME) && distPnt.isConstructed()) { distPnt.resetTag(DerValue.tag_Sequence); fullName = new GeneralNames(distPnt); } else if (distPnt.isContextSpecific(TAG_REL_NAME) && distPnt.isConstructed()) { distPnt.resetTag(DerValue.tag_Set); relativeName = new RDN(distPnt); } else { throw new IOException("Invalid DistributionPointName in " + "DistributionPoint"); } } else if (opt.isContextSpecific(TAG_REASONS) && !opt.isConstructed()) { if (reasonFlags != null) { throw new IOException("Duplicate Reasons in " + "DistributionPoint."); } opt.resetTag(DerValue.tag_BitString); reasonFlags = (opt.getUnalignedBitString()).toBooleanArray(); } else if (opt.isContextSpecific(TAG_ISSUER) && opt.isConstructed()) { if (crlIssuer != null) { throw new IOException("Duplicate CRLIssuer in " + "DistributionPoint."); } opt.resetTag(DerValue.tag_Sequence); crlIssuer = new GeneralNames(opt); } else { throw new IOException("Invalid encoding of " + "DistributionPoint."); } } if ((crlIssuer == null) && (fullName == null) && (relativeName == null)) { throw new IOException("One of fullName, relativeName, " + " and crlIssuer has to be set"); } } /** * Return the full distribution point name or null if not set. */ public GeneralNames getFullName() { return fullName; } /** * Return the relative distribution point name or null if not set. */ public RDN getRelativeName() { return relativeName; } /** * Return the reason flags or null if not set. */ public boolean[] getReasonFlags() { return reasonFlags; } /** * Return the CRL issuer name or null if not set. */ public GeneralNames getCRLIssuer() { return crlIssuer; } /** * Write the DistributionPoint value to the DerOutputStream. * * @param out the DerOutputStream to write the extension to. * @exception IOException on error. */ public void encode(DerOutputStream out) throws IOException { DerOutputStream tagged = new DerOutputStream(); // NOTE: only one of pointNames and pointRDN can be set if ((fullName != null) || (relativeName != null)) { DerOutputStream distributionPoint = new DerOutputStream(); if (fullName != null) { DerOutputStream derOut = new DerOutputStream(); fullName.encode(derOut); distributionPoint.writeImplicit( DerValue.createTag(DerValue.TAG_CONTEXT, true, TAG_FULL_NAME), derOut); } else if (relativeName != null) { DerOutputStream derOut = new DerOutputStream(); relativeName.encode(derOut); distributionPoint.writeImplicit( DerValue.createTag(DerValue.TAG_CONTEXT, true, TAG_REL_NAME), derOut); } tagged.write( DerValue.createTag(DerValue.TAG_CONTEXT, true, TAG_DIST_PT), distributionPoint); } if (reasonFlags != null) { DerOutputStream reasons = new DerOutputStream(); BitArray rf = new BitArray(reasonFlags); reasons.putTruncatedUnalignedBitString(rf); tagged.writeImplicit( DerValue.createTag(DerValue.TAG_CONTEXT, false, TAG_REASONS), reasons); } if (crlIssuer != null) { DerOutputStream issuer = new DerOutputStream(); crlIssuer.encode(issuer); tagged.writeImplicit( DerValue.createTag(DerValue.TAG_CONTEXT, true, TAG_ISSUER), issuer); } out.write(DerValue.tag_Sequence, tagged); } /** * Utility function for a.equals(b) where both a and b may be null. */ private static boolean equals(Object a, Object b) { return (a == null) ? (b == null) : a.equals(b); } /** * Compare an object to this DistributionPoint for equality. * * @param obj Object to be compared to this * @return true if objects match; false otherwise */ public boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof DistributionPoint == false) { return false; } DistributionPoint other = (DistributionPoint)obj; boolean equal = equals(this.fullName, other.fullName) && equals(this.relativeName, other.relativeName) && equals(this.crlIssuer, other.crlIssuer) && Arrays.equals(this.reasonFlags, other.reasonFlags); return equal; } public int hashCode() { int hash = hashCode; if (hash == 0) { hash = 1; if (fullName != null) { hash += fullName.hashCode(); } if (relativeName != null) { hash += relativeName.hashCode(); } if (crlIssuer != null) { hash += crlIssuer.hashCode(); } if (reasonFlags != null) { for (int i = 0; i < reasonFlags.length; i++) { if (reasonFlags[i]) { hash += i; } } } hashCode = hash; } return hash; } /** * Return a string representation for reasonFlag bit 'reason'. */ private static String reasonToString(int reason) { if ((reason > 0) && (reason < REASON_STRINGS.length)) { return REASON_STRINGS[reason]; } return "Unknown reason " + reason; } /** * Return a printable string of the Distribution Point. */ public String toString() { StringBuilder sb = new StringBuilder(); if (fullName != null) { sb.append("DistributionPoint:\n " + fullName + "\n"); } if (relativeName != null) { sb.append("DistributionPoint:\n " + relativeName + "\n"); } if (reasonFlags != null) { sb.append(" ReasonFlags:\n"); for (int i = 0; i < reasonFlags.length; i++) { if (reasonFlags[i]) { sb.append(" " + reasonToString(i) + "\n"); } } } if (crlIssuer != null) { sb.append(" CRLIssuer:" + crlIssuer + "\n"); } return sb.toString(); } }