001/*
002 * Copyright 2017-2018 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2017-2018 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  /**
112   * Creates a new basic constraints extension from the provided generic
113   * extension.
114   *
115   * @param  extension  The extension to decode as a basic constraints
116   *                    extension.
117   *
118   * @throws  CertException  If the provided extension cannot be decoded as a
119   *                         basic constraints extension.
120   */
121  BasicConstraintsExtension(final X509CertificateExtension extension)
122       throws CertException
123  {
124    super(extension);
125
126    try
127    {
128      boolean ca = false;
129      Integer lengthConstraint = null;
130      for (final ASN1Element e :
131           ASN1Sequence.decodeAsSequence(extension.getValue()).elements())
132      {
133        switch (e.getType())
134        {
135          case ASN1Constants.UNIVERSAL_BOOLEAN_TYPE:
136            ca = e.decodeAsBoolean().booleanValue();
137            break;
138          case ASN1Constants.UNIVERSAL_INTEGER_TYPE:
139            lengthConstraint = e.decodeAsInteger().intValue();
140            break;
141        }
142      }
143
144      isCA = ca;
145      pathLengthConstraint = lengthConstraint;
146    }
147    catch (final Exception e)
148    {
149      Debug.debugException(e);
150      throw new CertException(
151           ERR_BASIC_CONSTRAINTS_EXTENSION_CANNOT_PARSE.get(
152                String.valueOf(extension), StaticUtils.getExceptionMessage(e)),
153           e);
154    }
155  }
156
157
158
159  /**
160   * Encodes the provided information into a value for this extension.
161   *
162   * @param  isCA                  Indicates whether the associated certificate
163   *                               is a certification authority.
164   * @param  pathLengthConstraint  The path length constraint for paths that
165   *                               include the certificate.  This may be
166   *                               {@code null} if it should not be included in
167   *                               the extension.
168   *
169   * @return  The encoded extension value.
170   */
171  private static byte[] encodeValue(final boolean isCA,
172                                    final Integer pathLengthConstraint)
173  {
174    final ArrayList<ASN1Element> elements = new ArrayList<>(2);
175    if (isCA)
176    {
177      elements.add(new ASN1Boolean(isCA));
178    }
179
180    if (pathLengthConstraint != null)
181    {
182      elements.add(new ASN1Integer(pathLengthConstraint));
183    }
184
185    return new ASN1Sequence(elements).encode();
186  }
187
188
189  /**
190   * Indicates whether the associated certificate is a certification authority
191   * (that is, can be used to sign other certificates).
192   *
193   * @return  {@code true} if the associated certificate is a certification
194   *          authority, or {@code false} if not.
195   */
196  public boolean isCA()
197  {
198    return isCA;
199  }
200
201
202
203  /**
204   * Retrieves the path length constraint for the associated certificate, if
205   * defined.  If {@link #isCA()} returns {@code true} and this method returns
206   * a non-{@code null} value, then any certificate chain that includes the
207   * associated certificate should not be trusted if the chain contains more
208   * than this number of certificates.
209   *
210   * @return  The path length constraint for the associated certificate, or
211   *          {@code null} if no path length constraint is defined.
212   */
213  public Integer getPathLengthConstraint()
214  {
215    return pathLengthConstraint;
216  }
217
218
219
220  /**
221   * {@inheritDoc}
222   */
223  @Override()
224  public String getExtensionName()
225  {
226    return INFO_BASIC_CONSTRAINTS_EXTENSION_NAME.get();
227  }
228
229
230
231  /**
232   * {@inheritDoc}
233   */
234  @Override()
235  public void toString(final StringBuilder buffer)
236  {
237    buffer.append("BasicConstraintsExtension(oid='");
238    buffer.append(getOID());
239    buffer.append("', isCritical=");
240    buffer.append(isCritical());
241    buffer.append(", isCA=");
242    buffer.append(isCA);
243
244    if (pathLengthConstraint != null)
245    {
246      buffer.append(", pathLengthConstraint=");
247      buffer.append(pathLengthConstraint);
248    }
249
250    buffer.append(')');
251  }
252}