1 /*
   2  * Copyright (c) 2002, 2011, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.security.x509;
  27 
  28 import java.io.IOException;
  29 import java.util.*;
  30 
  31 import sun.security.util.BitArray;
  32 import sun.security.util.DerOutputStream;
  33 import sun.security.util.DerValue;
  34 
  35 /**
  36  * Represent the DistributionPoint sequence used in the CRL
  37  * Distribution Points Extension (OID = 2.5.29.31).
  38  * <p>
  39  * The ASN.1 definition for this is:
  40  * <pre>
  41  * DistributionPoint ::= SEQUENCE {
  42  *      distributionPoint       [0]     DistributionPointName OPTIONAL,
  43  *      reasons                 [1]     ReasonFlags OPTIONAL,
  44  *      cRLIssuer               [2]     GeneralNames OPTIONAL }
  45  *
  46  * DistributionPointName ::= CHOICE {
  47  *      fullName                [0]     GeneralNames,
  48  *      nameRelativeToCRLIssuer [1]     RelativeDistinguishedName }
  49  *
  50  * ReasonFlags ::= BIT STRING {
  51  *      unused                  (0),
  52  *      keyCompromise           (1),
  53  *      cACompromise            (2),
  54  *      affiliationChanged      (3),
  55  *      superseded              (4),
  56  *      cessationOfOperation    (5),
  57  *      certificateHold         (6),
  58  *      privilegeWithdrawn      (7),
  59  *      aACompromise            (8) }
  60  *
  61  * GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
  62  *
  63  * GeneralName ::= CHOICE {
  64  *         otherName                   [0] INSTANCE OF OTHER-NAME,
  65  *         rfc822Name                  [1] IA5String,
  66  *         dNSName                     [2] IA5String,
  67  *         x400Address                 [3] ORAddress,
  68  *         directoryName               [4] Name,
  69  *         ediPartyName                [5] EDIPartyName,
  70  *         uniformResourceIdentifier   [6] IA5String,
  71  *         iPAddress                   [7] OCTET STRING,
  72  *         registeredID                [8] OBJECT IDENTIFIER }
  73  *
  74  * RelativeDistinguishedName ::=
  75  *   SET OF AttributeTypeAndValue
  76  *
  77  * AttributeTypeAndValue ::= SEQUENCE {
  78  *   type     AttributeType,
  79  *   value    AttributeValue }
  80  *
  81  * AttributeType ::= OBJECT IDENTIFIER
  82  *
  83  * AttributeValue ::= ANY DEFINED BY AttributeType
  84  * </pre>
  85  * <p>
  86  * Instances of this class are designed to be immutable. However, since this
  87  * is an internal API we do not use defensive cloning for values for
  88  * performance reasons. It is the responsibility of the consumer to ensure
  89  * that no mutable elements are modified.
  90  *
  91  * @author Anne Anderson
  92  * @author Andreas Sterbenz
  93  * @since 1.4.2
  94  * @see CRLDistributionPointsExtension
  95  */
  96 public class DistributionPoint {
  97 
  98     // reason flag bits
  99     // NOTE that these are NOT quite the same as the CRL reason code extension
 100     public final static int KEY_COMPROMISE         = 1;
 101     public final static int CA_COMPROMISE          = 2;
 102     public final static int AFFILIATION_CHANGED    = 3;
 103     public final static int SUPERSEDED             = 4;
 104     public final static int CESSATION_OF_OPERATION = 5;
 105     public final static int CERTIFICATE_HOLD       = 6;
 106     public final static int PRIVILEGE_WITHDRAWN    = 7;
 107     public final static int AA_COMPROMISE          = 8;
 108 
 109     private static final String[] REASON_STRINGS = {
 110         null,
 111         "key compromise",
 112         "CA compromise",
 113         "affiliation changed",
 114         "superseded",
 115         "cessation of operation",
 116         "certificate hold",
 117         "privilege withdrawn",
 118         "AA compromise",
 119     };
 120 
 121     // context specific tag values
 122     private static final byte TAG_DIST_PT = 0;
 123     private static final byte TAG_REASONS = 1;
 124     private static final byte TAG_ISSUER = 2;
 125 
 126     private static final byte TAG_FULL_NAME = 0;
 127     private static final byte TAG_REL_NAME = 1;
 128 
 129     // only one of fullName and relativeName can be set
 130     private GeneralNames fullName;
 131     private RDN relativeName;
 132 
 133     // reasonFlags or null
 134     private boolean[] reasonFlags;
 135 
 136     // crlIssuer or null
 137     private GeneralNames crlIssuer;
 138 
 139     // cached hashCode value
 140     private volatile int hashCode;
 141 
 142     /**
 143      * Constructor for the class using GeneralNames for DistributionPointName
 144      *
 145      * @param fullName the GeneralNames of the distribution point; may be null
 146      * @param reasons the CRL reasons included in the CRL at this distribution
 147      *        point; may be null
 148      * @param issuer the name(s) of the CRL issuer for the CRL at this
 149      *        distribution point; may be null
 150      */
 151     public DistributionPoint(GeneralNames fullName, boolean[] reasonFlags,
 152             GeneralNames crlIssuer) {
 153         if ((fullName == null) && (crlIssuer == null)) {
 154             throw new IllegalArgumentException
 155                         ("fullName and crlIssuer may not both be null");
 156         }
 157         this.fullName = fullName;
 158         this.reasonFlags = reasonFlags;
 159         this.crlIssuer = crlIssuer;
 160     }
 161 
 162     /**
 163      * Constructor for the class using RelativeDistinguishedName for
 164      * DistributionPointName
 165      *
 166      * @param relativeName the RelativeDistinguishedName of the distribution
 167      *        point; may not be null
 168      * @param reasons the CRL reasons included in the CRL at this distribution
 169      *        point; may be null
 170      * @param issuer the name(s) of the CRL issuer for the CRL at this
 171      *        distribution point; may not be null or empty.
 172      */
 173     public DistributionPoint(RDN relativeName, boolean[] reasonFlags,
 174             GeneralNames crlIssuer) {
 175         if ((relativeName == null) && (crlIssuer == null)) {
 176             throw new IllegalArgumentException
 177                         ("relativeName and crlIssuer may not both be null");
 178         }
 179         this.relativeName = relativeName;
 180         this.reasonFlags = reasonFlags;
 181         this.crlIssuer = crlIssuer;
 182     }
 183 
 184     /**
 185      * Create the object from the passed DER encoded form.
 186      *
 187      * @param val the DER encoded form of the DistributionPoint
 188      * @throws IOException on error
 189      */
 190     public DistributionPoint(DerValue val) throws IOException {
 191         if (val.tag != DerValue.tag_Sequence) {
 192             throw new IOException("Invalid encoding of DistributionPoint.");
 193         }
 194 
 195         // Note that all the fields in DistributionPoint are defined as
 196         // being OPTIONAL, i.e., there could be an empty SEQUENCE, resulting
 197         // in val.data being null.
 198         while ((val.data != null) && (val.data.available() != 0)) {
 199             DerValue opt = val.data.getDerValue();
 200 
 201             if (opt.isContextSpecific(TAG_DIST_PT) && opt.isConstructed()) {
 202                 if ((fullName != null) || (relativeName != null)) {
 203                     throw new IOException("Duplicate DistributionPointName in "
 204                                           + "DistributionPoint.");
 205                 }
 206                 DerValue distPnt = opt.data.getDerValue();
 207                 if (distPnt.isContextSpecific(TAG_FULL_NAME)
 208                         && distPnt.isConstructed()) {
 209                     distPnt.resetTag(DerValue.tag_Sequence);
 210                     fullName = new GeneralNames(distPnt);
 211                 } else if (distPnt.isContextSpecific(TAG_REL_NAME)
 212                         && distPnt.isConstructed()) {
 213                     distPnt.resetTag(DerValue.tag_Set);
 214                     relativeName = new RDN(distPnt);
 215                 } else {
 216                     throw new IOException("Invalid DistributionPointName in "
 217                                           + "DistributionPoint");
 218                 }
 219             } else if (opt.isContextSpecific(TAG_REASONS)
 220                                                 && !opt.isConstructed()) {
 221                 if (reasonFlags != null) {
 222                     throw new IOException("Duplicate Reasons in " +
 223                                           "DistributionPoint.");
 224                 }
 225                 opt.resetTag(DerValue.tag_BitString);
 226                 reasonFlags = (opt.getUnalignedBitString()).toBooleanArray();
 227             } else if (opt.isContextSpecific(TAG_ISSUER)
 228                                                 && opt.isConstructed()) {
 229                 if (crlIssuer != null) {
 230                     throw new IOException("Duplicate CRLIssuer in " +
 231                                           "DistributionPoint.");
 232                 }
 233                 opt.resetTag(DerValue.tag_Sequence);
 234                 crlIssuer = new GeneralNames(opt);
 235             } else {
 236                 throw new IOException("Invalid encoding of " +
 237                                       "DistributionPoint.");
 238             }
 239         }
 240         if ((crlIssuer == null) && (fullName == null) && (relativeName == null)) {
 241             throw new IOException("One of fullName, relativeName, "
 242                 + " and crlIssuer has to be set");
 243         }
 244     }
 245 
 246     /**
 247      * Return the full distribution point name or null if not set.
 248      */
 249     public GeneralNames getFullName() {
 250         return fullName;
 251     }
 252 
 253     /**
 254      * Return the relative distribution point name or null if not set.
 255      */
 256     public RDN getRelativeName() {
 257         return relativeName;
 258     }
 259 
 260     /**
 261      * Return the reason flags or null if not set.
 262      */
 263     public boolean[] getReasonFlags() {
 264         return reasonFlags;
 265     }
 266 
 267     /**
 268      * Return the CRL issuer name or null if not set.
 269      */
 270     public GeneralNames getCRLIssuer() {
 271         return crlIssuer;
 272     }
 273 
 274     /**
 275      * Write the DistributionPoint value to the DerOutputStream.
 276      *
 277      * @param out the DerOutputStream to write the extension to.
 278      * @exception IOException on error.
 279      */
 280     public void encode(DerOutputStream out) throws IOException {
 281         DerOutputStream tagged = new DerOutputStream();
 282 
 283         // NOTE: only one of pointNames and pointRDN can be set
 284         if ((fullName != null) || (relativeName != null)) {
 285             DerOutputStream distributionPoint = new DerOutputStream();
 286             if (fullName != null) {
 287                 DerOutputStream derOut = new DerOutputStream();
 288                 fullName.encode(derOut);
 289                 distributionPoint.writeImplicit(
 290                     DerValue.createTag(DerValue.TAG_CONTEXT, true, TAG_FULL_NAME),
 291                     derOut);
 292             } else if (relativeName != null) {
 293                 DerOutputStream derOut = new DerOutputStream();
 294                 relativeName.encode(derOut);
 295                 distributionPoint.writeImplicit(
 296                     DerValue.createTag(DerValue.TAG_CONTEXT, true, TAG_REL_NAME),
 297                     derOut);
 298             }
 299             tagged.write(
 300                 DerValue.createTag(DerValue.TAG_CONTEXT, true, TAG_DIST_PT),
 301                 distributionPoint);
 302         }
 303         if (reasonFlags != null) {
 304             DerOutputStream reasons = new DerOutputStream();
 305             BitArray rf = new BitArray(reasonFlags);
 306             reasons.putTruncatedUnalignedBitString(rf);
 307             tagged.writeImplicit(
 308                 DerValue.createTag(DerValue.TAG_CONTEXT, false, TAG_REASONS),
 309                 reasons);
 310         }
 311         if (crlIssuer != null) {
 312             DerOutputStream issuer = new DerOutputStream();
 313             crlIssuer.encode(issuer);
 314             tagged.writeImplicit(
 315                 DerValue.createTag(DerValue.TAG_CONTEXT, true, TAG_ISSUER),
 316                 issuer);
 317         }
 318         out.write(DerValue.tag_Sequence, tagged);
 319     }
 320 
 321     /**
 322      * Compare an object to this DistributionPoint for equality.
 323      *
 324      * @param obj Object to be compared to this
 325      * @return true if objects match; false otherwise
 326      */
 327     public boolean equals(Object obj) {
 328         if (this == obj) {
 329             return true;
 330         }
 331         if (obj instanceof DistributionPoint == false) {
 332             return false;
 333         }
 334         DistributionPoint other = (DistributionPoint)obj;
 335 
 336         boolean equal = Objects.equals(this.fullName, other.fullName)
 337                      && Objects.equals(this.relativeName, other.relativeName)
 338                      && Objects.equals(this.crlIssuer, other.crlIssuer)
 339                      && Arrays.equals(this.reasonFlags, other.reasonFlags);
 340         return equal;
 341     }
 342 
 343     public int hashCode() {
 344         int hash = hashCode;
 345         if (hash == 0) {
 346             hash = 1;
 347             if (fullName != null) {
 348                 hash += fullName.hashCode();
 349             }
 350             if (relativeName != null) {
 351                 hash += relativeName.hashCode();
 352             }
 353             if (crlIssuer != null) {
 354                 hash += crlIssuer.hashCode();
 355             }
 356             if (reasonFlags != null) {
 357                 for (int i = 0; i < reasonFlags.length; i++) {
 358                     if (reasonFlags[i]) {
 359                         hash += i;
 360                     }
 361                 }
 362             }
 363             hashCode = hash;
 364         }
 365         return hash;
 366     }
 367 
 368     /**
 369      * Return a string representation for reasonFlag bit 'reason'.
 370      */
 371     private static String reasonToString(int reason) {
 372         if ((reason > 0) && (reason < REASON_STRINGS.length)) {
 373             return REASON_STRINGS[reason];
 374         }
 375         return "Unknown reason " + reason;
 376     }
 377 
 378     /**
 379      * Return a printable string of the Distribution Point.
 380      */
 381     public String toString() {
 382         StringBuilder sb = new StringBuilder();
 383         if (fullName != null) {
 384             sb.append("DistributionPoint:\n     " + fullName + "\n");
 385         }
 386         if (relativeName != null) {
 387             sb.append("DistributionPoint:\n     " + relativeName + "\n");
 388         }
 389 
 390         if (reasonFlags != null) {
 391             sb.append("   ReasonFlags:\n");
 392             for (int i = 0; i < reasonFlags.length; i++) {
 393                 if (reasonFlags[i]) {
 394                     sb.append("    " + reasonToString(i) + "\n");
 395                 }
 396             }
 397         }
 398         if (crlIssuer != null) {
 399             sb.append("   CRLIssuer:" + crlIssuer + "\n");
 400         }
 401         return sb.toString();
 402     }
 403 
 404 }