001/*
002 * Copyright 2017-2020 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2017-2020 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2017-2020 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.util.ssl.cert;
037
038
039
040import java.util.ArrayList;
041
042import com.unboundid.asn1.ASN1Boolean;
043import com.unboundid.asn1.ASN1Constants;
044import com.unboundid.asn1.ASN1Element;
045import com.unboundid.asn1.ASN1Integer;
046import com.unboundid.asn1.ASN1Sequence;
047import com.unboundid.util.Debug;
048import com.unboundid.util.NotMutable;
049import com.unboundid.util.OID;
050import com.unboundid.util.StaticUtils;
051import com.unboundid.util.ThreadSafety;
052import com.unboundid.util.ThreadSafetyLevel;
053
054import static com.unboundid.util.ssl.cert.CertMessages.*;
055
056
057
058/**
059 * This class provides an implementation of the basic constraints X.509
060 * certificate extension as described in
061 * <A HREF="https://www.ietf.org/rfc/rfc5280.txt">RFC 5280</A> section 4.2.1.9.
062 * This can be used to indicate whether a certificate is a certification
063 * authority (CA), and the maximum depth of certification paths that include
064 * this certificate.
065 * <BR><BR>
066 * The OID for this extension is 2.5.29.19 and the value has the following
067 * encoding:
068 * <PRE>
069 *   BasicConstraints ::= SEQUENCE {
070 *        cA                      BOOLEAN DEFAULT FALSE,
071 *        pathLenConstraint       INTEGER (0..MAX) OPTIONAL }
072 * </PRE>
073 */
074@NotMutable()
075@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
076public final class BasicConstraintsExtension
077       extends X509CertificateExtension
078{
079  /**
080   * The OID (2.5.29.19) for basic constraints extensions.
081   */
082  public static final OID BASIC_CONSTRAINTS_OID = new OID("2.5.29.19");
083
084
085
086  /**
087   * The serial version UID for this serializable class.
088   */
089  private static final long serialVersionUID = 7597324354728536247L;
090
091
092
093  // Indicates whether the certificate is a certification authority.
094  private final boolean isCA;
095
096  // The path length constraint for paths that include the certificate.
097  private final Integer pathLengthConstraint;
098
099
100
101  /**
102   * Creates a new basic constraints extension from the provided information.
103   *
104   * @param  isCritical            Indicates whether this extension should be
105   *                               considered critical.
106   * @param  isCA                  Indicates whether the associated certificate
107   *                               is a certification authority.
108   * @param  pathLengthConstraint  The path length constraint for paths that
109   *                               include the certificate.  This may be
110   *                               {@code null} if it should not be included in
111   *                               the extension.
112   */
113  BasicConstraintsExtension(final boolean isCritical, final boolean isCA,
114                            final Integer pathLengthConstraint)
115  {
116    super(BASIC_CONSTRAINTS_OID, isCritical,
117         encodeValue(isCA, pathLengthConstraint));
118
119    this.isCA = isCA;
120    this.pathLengthConstraint = pathLengthConstraint;
121  }
122
123
124
125  /**
126   * Creates a new basic constraints extension from the provided generic
127   * extension.
128   *
129   * @param  extension  The extension to decode as a basic constraints
130   *                    extension.
131   *
132   * @throws  CertException  If the provided extension cannot be decoded as a
133   *                         basic constraints extension.
134   */
135  BasicConstraintsExtension(final X509CertificateExtension extension)
136       throws CertException
137  {
138    super(extension);
139
140    try
141    {
142      boolean ca = false;
143      Integer lengthConstraint = null;
144      for (final ASN1Element e :
145           ASN1Sequence.decodeAsSequence(extension.getValue()).elements())
146      {
147        switch (e.getType())
148        {
149          case ASN1Constants.UNIVERSAL_BOOLEAN_TYPE:
150            ca = e.decodeAsBoolean().booleanValue();
151            break;
152          case ASN1Constants.UNIVERSAL_INTEGER_TYPE:
153            lengthConstraint = e.decodeAsInteger().intValue();
154            break;
155        }
156      }
157
158      isCA = ca;
159      pathLengthConstraint = lengthConstraint;
160    }
161    catch (final Exception e)
162    {
163      Debug.debugException(e);
164      throw new CertException(
165           ERR_BASIC_CONSTRAINTS_EXTENSION_CANNOT_PARSE.get(
166                String.valueOf(extension), StaticUtils.getExceptionMessage(e)),
167           e);
168    }
169  }
170
171
172
173  /**
174   * Encodes the provided information into a value for this extension.
175   *
176   * @param  isCA                  Indicates whether the associated certificate
177   *                               is a certification authority.
178   * @param  pathLengthConstraint  The path length constraint for paths that
179   *                               include the certificate.  This may be
180   *                               {@code null} if it should not be included in
181   *                               the extension.
182   *
183   * @return  The encoded extension value.
184   */
185  private static byte[] encodeValue(final boolean isCA,
186                                    final Integer pathLengthConstraint)
187  {
188    final ArrayList<ASN1Element> elements = new ArrayList<>(2);
189    if (isCA)
190    {
191      elements.add(new ASN1Boolean(isCA));
192    }
193
194    if (pathLengthConstraint != null)
195    {
196      elements.add(new ASN1Integer(pathLengthConstraint));
197    }
198
199    return new ASN1Sequence(elements).encode();
200  }
201
202
203  /**
204   * Indicates whether the associated certificate is a certification authority
205   * (that is, can be used to sign other certificates).
206   *
207   * @return  {@code true} if the associated certificate is a certification
208   *          authority, or {@code false} if not.
209   */
210  public boolean isCA()
211  {
212    return isCA;
213  }
214
215
216
217  /**
218   * Retrieves the path length constraint for the associated certificate, if
219   * defined.  If {@link #isCA()} returns {@code true} and this method returns
220   * a non-{@code null} value, then any certificate chain that includes the
221   * associated certificate should not be trusted if the chain contains more
222   * than this number of certificates.
223   *
224   * @return  The path length constraint for the associated certificate, or
225   *          {@code null} if no path length constraint is defined.
226   */
227  public Integer getPathLengthConstraint()
228  {
229    return pathLengthConstraint;
230  }
231
232
233
234  /**
235   * {@inheritDoc}
236   */
237  @Override()
238  public String getExtensionName()
239  {
240    return INFO_BASIC_CONSTRAINTS_EXTENSION_NAME.get();
241  }
242
243
244
245  /**
246   * {@inheritDoc}
247   */
248  @Override()
249  public void toString(final StringBuilder buffer)
250  {
251    buffer.append("BasicConstraintsExtension(oid='");
252    buffer.append(getOID());
253    buffer.append("', isCritical=");
254    buffer.append(isCritical());
255    buffer.append(", isCA=");
256    buffer.append(isCA);
257
258    if (pathLengthConstraint != null)
259    {
260      buffer.append(", pathLengthConstraint=");
261      buffer.append(pathLengthConstraint);
262    }
263
264    buffer.append(')');
265  }
266}