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