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      */
DistributionPoint(GeneralNames fullName, boolean[] reasonFlags, GeneralNames crlIssuer)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      */
DistributionPoint(RDN relativeName, boolean[] reasonFlags, GeneralNames crlIssuer)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      */
DistributionPoint(DerValue val)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      */
getFullName()249     public GeneralNames getFullName() {
250         return fullName;
251     }
252 
253     /**
254      * Return the relative distribution point name or null if not set.
255      */
getRelativeName()256     public RDN getRelativeName() {
257         return relativeName;
258     }
259 
260     /**
261      * Return the reason flags or null if not set.
262      */
getReasonFlags()263     public boolean[] getReasonFlags() {
264         return reasonFlags;
265     }
266 
267     /**
268      * Return the CRL issuer name or null if not set.
269      */
getCRLIssuer()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      */
encode(DerOutputStream out)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      */
equals(Object obj)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 
hashCode()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      */
reasonToString(int reason)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      */
toString()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 }
405