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 
26 package sun.security.x509;
27 
28 import java.io.IOException;
29 import java.io.OutputStream;
30 import java.util.Enumeration;
31 
32 import 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  */
53 public class BasicConstraintsExtension extends Extension
54 implements 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
encodeThis()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      */
BasicConstraintsExtension(boolean ca, int len)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      */
BasicConstraintsExtension(Boolean critical, boolean ca, int len)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      */
BasicConstraintsExtension(Boolean critical, Object value)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       */
toString()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       */
encode(OutputStream out)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      */
set(String name, Object obj)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      */
get(String name)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      */
delete(String name)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      */
getElements()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      */
getName()271     public String getName() {
272         return (NAME);
273     }
274 }
275