1/*
2 * Copyright (c) 1997, 2009, 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.io.OutputStream;
30import java.util.Enumeration;
31
32import sun.security.util.*;
33
34/**
35 * This class represents the Basic Constraints Extension.
36 *
37 * <p>The basic constraints extension identifies whether the subject of the
38 * certificate is a CA and how deep a certification path may exist
39 * through that CA.
40 *
41 * <pre>
42 * The ASN.1 syntax for this extension is:
43 * BasicConstraints ::= SEQUENCE {
44 *     cA                BOOLEAN DEFAULT FALSE,
45 *     pathLenConstraint INTEGER (0..MAX) OPTIONAL
46 * }
47 * </pre>
48 * @author Amit Kapoor
49 * @author Hemma Prafullchandra
50 * @see CertAttrSet
51 * @see Extension
52 */
53public class BasicConstraintsExtension extends Extension
54implements CertAttrSet<String> {
55    /**
56     * Identifier for this attribute, to be used with the
57     * get, set, delete methods of Certificate, x509 type.
58     */
59    public static final String IDENT = "x509.info.extensions.BasicConstraints";
60    /**
61     * Attribute names.
62     */
63    public static final String NAME = "BasicConstraints";
64    public static final String IS_CA = "is_ca";
65    public static final String PATH_LEN = "path_len";
66
67    // Private data members
68    private boolean     ca = false;
69    private int pathLen = -1;
70
71    // Encode this extension value
72    private void encodeThis() throws IOException {
73        DerOutputStream out = new DerOutputStream();
74        DerOutputStream tmp = new DerOutputStream();
75
76        if (ca) {
77            tmp.putBoolean(ca);
78            // Only encode pathLen when ca == true
79            if (pathLen >= 0) {
80                tmp.putInteger(pathLen);
81            }
82        }
83        out.write(DerValue.tag_Sequence, tmp);
84        this.extensionValue = out.toByteArray();
85    }
86
87    /**
88     * Default constructor for this object. The extension is marked
89     * critical if the ca flag is true, false otherwise.
90     *
91     * @param ca true, if the subject of the Certificate is a CA.
92     * @param len specifies the depth of the certification path.
93     */
94    public BasicConstraintsExtension(boolean ca, int len) throws IOException {
95        this(Boolean.valueOf(ca), ca, len);
96    }
97
98    /**
99     * Constructor for this object with specified criticality.
100     *
101     * @param critical true, if the extension should be marked critical
102     * @param ca true, if the subject of the Certificate is a CA.
103     * @param len specifies the depth of the certification path.
104     */
105    public BasicConstraintsExtension(Boolean critical, boolean ca, int len)
106    throws IOException {
107        this.ca = ca;
108        this.pathLen = len;
109        this.extensionId = PKIXExtensions.BasicConstraints_Id;
110        this.critical = critical.booleanValue();
111        encodeThis();
112    }
113
114    /**
115     * Create the extension from the passed DER encoded value of the same.
116     *
117     * @param critical flag indicating if extension is critical or not
118     * @param value an array containing the DER encoded bytes of the extension.
119     * @exception ClassCastException if value is not an array of bytes
120     * @exception IOException on error.
121     */
122     public BasicConstraintsExtension(Boolean critical, Object value)
123         throws IOException
124    {
125         this.extensionId = PKIXExtensions.BasicConstraints_Id;
126         this.critical = critical.booleanValue();
127
128         this.extensionValue = (byte[]) value;
129         DerValue val = new DerValue(this.extensionValue);
130         if (val.tag != DerValue.tag_Sequence) {
131             throw new IOException("Invalid encoding of BasicConstraints");
132         }
133
134         if (val.data == null || val.data.available() == 0) {
135             // non-CA cert ("cA" field is FALSE by default), return -1
136             return;
137         }
138         DerValue opt = val.data.getDerValue();
139         if (opt.tag != DerValue.tag_Boolean) {
140             // non-CA cert ("cA" field is FALSE by default), return -1
141             return;
142         }
143
144         this.ca = opt.getBoolean();
145         if (val.data.available() == 0) {
146             // From PKIX profile:
147             // Where pathLenConstraint does not appear, there is no
148             // limit to the allowed length of the certification path.
149             this.pathLen = Integer.MAX_VALUE;
150             return;
151         }
152
153         opt = val.data.getDerValue();
154         if (opt.tag != DerValue.tag_Integer) {
155             throw new IOException("Invalid encoding of BasicConstraints");
156         }
157         this.pathLen = opt.getInteger();
158         /*
159          * Activate this check once again after PKIX profiling
160          * is a standard and this check no longer imposes an
161          * interoperability barrier.
162          * if (ca) {
163          *   if (!this.critical) {
164          *   throw new IOException("Criticality cannot be false for CA.");
165          *   }
166          * }
167          */
168     }
169
170     /**
171      * Return user readable form of extension.
172      */
173     public String toString() {
174         String s = super.toString() + "BasicConstraints:[\n";
175
176         s += ((ca) ? ("  CA:true") : ("  CA:false")) + "\n";
177         if (pathLen >= 0) {
178             s += "  PathLen:" + pathLen + "\n";
179         } else {
180             s += "  PathLen: undefined\n";
181         }
182         return (s + "]\n");
183     }
184
185     /**
186      * Encode this extension value to the output stream.
187      *
188      * @param out the DerOutputStream to encode the extension to.
189      */
190     public void encode(OutputStream out) throws IOException {
191         DerOutputStream tmp = new DerOutputStream();
192         if (extensionValue == null) {
193             this.extensionId = PKIXExtensions.BasicConstraints_Id;
194             if (ca) {
195                 critical = true;
196             } else {
197                 critical = false;
198             }
199             encodeThis();
200         }
201         super.encode(tmp);
202
203         out.write(tmp.toByteArray());
204     }
205
206    /**
207     * Set the attribute value.
208     */
209    public void set(String name, Object obj) throws IOException {
210        if (name.equalsIgnoreCase(IS_CA)) {
211            if (!(obj instanceof Boolean)) {
212              throw new IOException("Attribute value should be of type Boolean.");
213            }
214            ca = ((Boolean)obj).booleanValue();
215        } else if (name.equalsIgnoreCase(PATH_LEN)) {
216            if (!(obj instanceof Integer)) {
217              throw new IOException("Attribute value should be of type Integer.");
218            }
219            pathLen = ((Integer)obj).intValue();
220        } else {
221          throw new IOException("Attribute name not recognized by " +
222                                "CertAttrSet:BasicConstraints.");
223        }
224        encodeThis();
225    }
226
227    /**
228     * Get the attribute value.
229     */
230    public Object get(String name) throws IOException {
231        if (name.equalsIgnoreCase(IS_CA)) {
232            return (Boolean.valueOf(ca));
233        } else if (name.equalsIgnoreCase(PATH_LEN)) {
234            return (Integer.valueOf(pathLen));
235        } else {
236          throw new IOException("Attribute name not recognized by " +
237                                "CertAttrSet:BasicConstraints.");
238        }
239    }
240
241    /**
242     * Delete the attribute value.
243     */
244    public void delete(String name) throws IOException {
245        if (name.equalsIgnoreCase(IS_CA)) {
246            ca = false;
247        } else if (name.equalsIgnoreCase(PATH_LEN)) {
248            pathLen = -1;
249        } else {
250          throw new IOException("Attribute name not recognized by " +
251                                "CertAttrSet:BasicConstraints.");
252        }
253        encodeThis();
254    }
255
256    /**
257     * Return an enumeration of names of attributes existing within this
258     * attribute.
259     */
260    public Enumeration<String> getElements() {
261        AttributeNameEnumeration elements = new AttributeNameEnumeration();
262        elements.addElement(IS_CA);
263        elements.addElement(PATH_LEN);
264
265        return (elements.elements());
266    }
267
268    /**
269     * Return the name of this attribute.
270     */
271    public String getName() {
272        return (NAME);
273    }
274}
275