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
26package sun.security.x509;
27
28import java.io.IOException;
29import java.util.*;
30
31import sun.security.util.BitArray;
32import sun.security.util.DerOutputStream;
33import 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 */
96public 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}
405