001/*
002 * Copyright 2017-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2017-2024 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-2024 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.NotNull;
050import com.unboundid.util.Nullable;
051import com.unboundid.util.OID;
052import com.unboundid.util.StaticUtils;
053import com.unboundid.util.ThreadSafety;
054import com.unboundid.util.ThreadSafetyLevel;
055
056import static com.unboundid.util.ssl.cert.CertMessages.*;
057
058
059
060/**
061 * This class provides an implementation of the basic constraints X.509
062 * certificate extension as described in
063 * <A HREF="https://www.ietf.org/rfc/rfc5280.txt">RFC 5280</A> section 4.2.1.9.
064 * This can be used to indicate whether a certificate is a certification
065 * authority (CA), and the maximum depth of certification paths that include
066 * this certificate.
067 * <BR><BR>
068 * The OID for this extension is 2.5.29.19 and the value has the following
069 * encoding:
070 * <PRE>
071 *   BasicConstraints ::= SEQUENCE {
072 *        cA                      BOOLEAN DEFAULT FALSE,
073 *        pathLenConstraint       INTEGER (0..MAX) OPTIONAL }
074 * </PRE>
075 */
076@NotMutable()
077@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
078public final class BasicConstraintsExtension
079       extends X509CertificateExtension
080{
081  /**
082   * The OID (2.5.29.19) for basic constraints extensions.
083   */
084  @NotNull public static final OID BASIC_CONSTRAINTS_OID = new OID("2.5.29.19");
085
086
087
088  /**
089   * The serial version UID for this serializable class.
090   */
091  private static final long serialVersionUID = 7597324354728536247L;
092
093
094
095  // Indicates whether the certificate is a certification authority.
096  private final boolean isCA;
097
098  // The path length constraint for paths that include the certificate.
099  @Nullable private final Integer pathLengthConstraint;
100
101
102
103  /**
104   * Creates a new basic constraints extension from the provided information.
105   *
106   * @param  isCritical            Indicates whether this extension should be
107   *                               considered critical.
108   * @param  isCA                  Indicates whether the associated certificate
109   *                               is a certification authority.
110   * @param  pathLengthConstraint  The path length constraint for paths that
111   *                               include the certificate.  This may be
112   *                               {@code null} if it should not be included in
113   *                               the extension.
114   */
115  BasicConstraintsExtension(final boolean isCritical, final boolean isCA,
116                            @Nullable final Integer pathLengthConstraint)
117  {
118    super(BASIC_CONSTRAINTS_OID, isCritical,
119         encodeValue(isCA, pathLengthConstraint));
120
121    this.isCA = isCA;
122    this.pathLengthConstraint = pathLengthConstraint;
123  }
124
125
126
127  /**
128   * Creates a new basic constraints extension from the provided generic
129   * extension.
130   *
131   * @param  extension  The extension to decode as a basic constraints
132   *                    extension.
133   *
134   * @throws  CertException  If the provided extension cannot be decoded as a
135   *                         basic constraints extension.
136   */
137  BasicConstraintsExtension(@NotNull final X509CertificateExtension extension)
138       throws CertException
139  {
140    super(extension);
141
142    try
143    {
144      boolean ca = false;
145      Integer lengthConstraint = null;
146      for (final ASN1Element e :
147           ASN1Sequence.decodeAsSequence(extension.getValue()).elements())
148      {
149        switch (e.getType())
150        {
151          case ASN1Constants.UNIVERSAL_BOOLEAN_TYPE:
152            ca = e.decodeAsBoolean().booleanValue();
153            break;
154          case ASN1Constants.UNIVERSAL_INTEGER_TYPE:
155            lengthConstraint = e.decodeAsInteger().intValue();
156            break;
157        }
158      }
159
160      isCA = ca;
161      pathLengthConstraint = lengthConstraint;
162    }
163    catch (final Exception e)
164    {
165      Debug.debugException(e);
166      throw new CertException(
167           ERR_BASIC_CONSTRAINTS_EXTENSION_CANNOT_PARSE.get(
168                String.valueOf(extension), StaticUtils.getExceptionMessage(e)),
169           e);
170    }
171  }
172
173
174
175  /**
176   * Encodes the provided information into a value for this extension.
177   *
178   * @param  isCA                  Indicates whether the associated certificate
179   *                               is a certification authority.
180   * @param  pathLengthConstraint  The path length constraint for paths that
181   *                               include the certificate.  This may be
182   *                               {@code null} if it should not be included in
183   *                               the extension.
184   *
185   * @return  The encoded extension value.
186   */
187  @NotNull()
188  private static byte[] encodeValue(final boolean isCA,
189                             @Nullable final Integer pathLengthConstraint)
190  {
191    final ArrayList<ASN1Element> elements = new ArrayList<>(2);
192    if (isCA)
193    {
194      elements.add(new ASN1Boolean(isCA));
195    }
196
197    if (pathLengthConstraint != null)
198    {
199      elements.add(new ASN1Integer(pathLengthConstraint));
200    }
201
202    return new ASN1Sequence(elements).encode();
203  }
204
205
206  /**
207   * Indicates whether the associated certificate is a certification authority
208   * (that is, can be used to sign other certificates).
209   *
210   * @return  {@code true} if the associated certificate is a certification
211   *          authority, or {@code false} if not.
212   */
213  public boolean isCA()
214  {
215    return isCA;
216  }
217
218
219
220  /**
221   * Retrieves the path length constraint for the associated certificate, if
222   * defined.  If {@link #isCA()} returns {@code true} and this method returns
223   * a non-{@code null} value, then any certificate chain that includes the
224   * associated certificate should not be trusted if the chain contains more
225   * than this number of certificates.
226   *
227   * @return  The path length constraint for the associated certificate, or
228   *          {@code null} if no path length constraint is defined.
229   */
230  @Nullable()
231  public Integer getPathLengthConstraint()
232  {
233    return pathLengthConstraint;
234  }
235
236
237
238  /**
239   * {@inheritDoc}
240   */
241  @Override()
242  @NotNull()
243  public String getExtensionName()
244  {
245    return INFO_BASIC_CONSTRAINTS_EXTENSION_NAME.get();
246  }
247
248
249
250  /**
251   * {@inheritDoc}
252   */
253  @Override()
254  public void toString(@NotNull final StringBuilder buffer)
255  {
256    buffer.append("BasicConstraintsExtension(oid='");
257    buffer.append(getOID());
258    buffer.append("', isCritical=");
259    buffer.append(isCritical());
260    buffer.append(", isCA=");
261    buffer.append(isCA);
262
263    if (pathLengthConstraint != null)
264    {
265      buffer.append(", pathLengthConstraint=");
266      buffer.append(pathLengthConstraint);
267    }
268
269    buffer.append(')');
270  }
271}