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.io.Serializable;
041import java.util.ArrayList;
042
043import com.unboundid.asn1.ASN1Boolean;
044import com.unboundid.asn1.ASN1Constants;
045import com.unboundid.asn1.ASN1Element;
046import com.unboundid.asn1.ASN1ObjectIdentifier;
047import com.unboundid.asn1.ASN1OctetString;
048import com.unboundid.asn1.ASN1Sequence;
049import com.unboundid.util.Debug;
050import com.unboundid.util.NotExtensible;
051import com.unboundid.util.NotMutable;
052import com.unboundid.util.NotNull;
053import com.unboundid.util.OID;
054import com.unboundid.util.StaticUtils;
055import com.unboundid.util.ThreadSafety;
056import com.unboundid.util.ThreadSafetyLevel;
057
058import static com.unboundid.util.ssl.cert.CertMessages.*;
059
060
061
062/**
063 * This class represents a data structure that holds information about an X.509
064 * certificate extension.
065 */
066@NotExtensible()
067@NotMutable()
068@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
069public class X509CertificateExtension
070       implements Serializable
071{
072  /**
073   * The serial version UID for this serializable class.
074   */
075  private static final long serialVersionUID = -4044598072050168580L;
076
077
078
079  // Indicates whether this extension is considered critical.
080  private final boolean isCritical;
081
082  // The value for this extension.
083  @NotNull private final byte[] value;
084
085  // The OID for this extension.
086  @NotNull private final OID oid;
087
088
089
090  /**
091   * Creates a new X.509 certificate extension that wraps the provided
092   * extension.
093   *
094   * @param  extension  The extension to wrap.
095   */
096  protected X509CertificateExtension(
097                 @NotNull final X509CertificateExtension extension)
098  {
099    oid = extension.oid;
100    isCritical = extension.isCritical;
101    value = extension.value;
102  }
103
104
105
106  /**
107   * Creates a new X.509 certificate extension with the provided information.
108   *
109   * @param  oid         The OID for this extension.
110   * @param  isCritical  Indicates whether this extension is considered
111   *                     critical.
112   * @param  value       The value for this extension.
113   */
114  public X509CertificateExtension(@NotNull final OID oid,
115                                  final boolean isCritical,
116                                  @NotNull final byte[] value)
117  {
118    this.oid = oid;
119    this.isCritical = isCritical;
120    this.value = value;
121  }
122
123
124
125  /**
126   * Decodes the provided ASN.1 element as an X.509 certificate extension.
127   *
128   * @param  extensionElement  The ASN.1 element containing the encoded
129   *                           extension.
130   *
131   * @return  The decoded extension.
132   *
133   * @throws  CertException  If a problem is encountered while attempting to
134   *                         decode the extension.
135   */
136  @NotNull()
137  static X509CertificateExtension decode(
138              @NotNull final ASN1Element extensionElement)
139         throws CertException
140  {
141    final OID oid;
142    final X509CertificateExtension extension;
143    try
144    {
145      final ASN1Element[] elements =
146           extensionElement.decodeAsSequence().elements();
147      oid = elements[0].decodeAsObjectIdentifier().getOID();
148
149      final boolean isCritical;
150      final byte[] value;
151      if (elements[1].getType() == ASN1Constants.UNIVERSAL_BOOLEAN_TYPE)
152      {
153        isCritical = elements[1].decodeAsBoolean().booleanValue();
154        value = elements[2].decodeAsOctetString().getValue();
155      }
156      else
157      {
158        isCritical = false;
159        value = elements[1].decodeAsOctetString().getValue();
160      }
161
162      extension = new X509CertificateExtension(oid, isCritical, value);
163    }
164    catch (final Exception e)
165    {
166      Debug.debugException(e);
167      throw new CertException(
168           ERR_EXTENSION_DECODE_ERROR.get(
169                StaticUtils.getExceptionMessage(e)),
170           e);
171    }
172
173    if (oid.equals(AuthorityKeyIdentifierExtension.
174         AUTHORITY_KEY_IDENTIFIER_OID))
175    {
176      try
177      {
178        return new AuthorityKeyIdentifierExtension(extension);
179      }
180      catch (final Exception e)
181      {
182        Debug.debugException(e);
183      }
184    }
185    else if (oid.equals(SubjectKeyIdentifierExtension.
186         SUBJECT_KEY_IDENTIFIER_OID))
187    {
188      try
189      {
190        return new SubjectKeyIdentifierExtension(extension);
191      }
192      catch (final Exception e)
193      {
194        Debug.debugException(e);
195      }
196    }
197    else if (oid.equals(KeyUsageExtension.KEY_USAGE_OID))
198    {
199      try
200      {
201        return new KeyUsageExtension(extension);
202      }
203      catch (final Exception e)
204      {
205        Debug.debugException(e);
206      }
207    }
208    else if (oid.equals(SubjectAlternativeNameExtension.
209         SUBJECT_ALTERNATIVE_NAME_OID))
210    {
211      try
212      {
213        return new SubjectAlternativeNameExtension(extension);
214      }
215      catch (final Exception e)
216      {
217        Debug.debugException(e);
218      }
219    }
220    else if (oid.equals(IssuerAlternativeNameExtension.
221         ISSUER_ALTERNATIVE_NAME_OID))
222    {
223      try
224      {
225        return new IssuerAlternativeNameExtension(extension);
226      }
227      catch (final Exception e)
228      {
229        Debug.debugException(e);
230      }
231    }
232    else if (oid.equals(BasicConstraintsExtension.
233         BASIC_CONSTRAINTS_OID))
234    {
235      try
236      {
237        return new BasicConstraintsExtension(extension);
238      }
239      catch (final Exception e)
240      {
241        Debug.debugException(e);
242      }
243    }
244    else if (oid.equals(ExtendedKeyUsageExtension.
245         EXTENDED_KEY_USAGE_OID))
246    {
247      try
248      {
249        return new ExtendedKeyUsageExtension(extension);
250      }
251      catch (final Exception e)
252      {
253        Debug.debugException(e);
254      }
255    }
256    else if (oid.equals(CRLDistributionPointsExtension.
257         CRL_DISTRIBUTION_POINTS_OID))
258    {
259      try
260      {
261        return new CRLDistributionPointsExtension(extension);
262      }
263      catch (final Exception e)
264      {
265        Debug.debugException(e);
266      }
267    }
268
269    return extension;
270  }
271
272
273
274  /**
275   * Retrieves the OID for this extension.
276   *
277   * @return  The OID for this extension.
278   */
279  @NotNull()
280  public final OID getOID()
281  {
282    return oid;
283  }
284
285
286
287  /**
288   * Indicates whether this extension is considered critical.
289   *
290   * @return  {@code true} if this extension is considered critical, or
291   *          {@code false} if not.
292   */
293  public final boolean isCritical()
294  {
295    return isCritical;
296  }
297
298
299
300  /**
301   * Retrieves the value for this extension.
302   *
303   * @return  The value for this extension.
304   */
305  @NotNull()
306  public final byte[] getValue()
307  {
308    return value;
309  }
310
311
312
313  /**
314   * Encodes this extension to an ASN.1 element suitable for inclusion in an
315   * encoded X.509 certificate.
316   *
317   * @return  The encoded representation of this extension.
318   *
319   * @throws  CertException  If a problem is encountered while encoding the
320   *                         extension.
321   */
322  @NotNull()
323  ASN1Sequence encode()
324       throws CertException
325  {
326    try
327    {
328      final ArrayList<ASN1Element> elements = new ArrayList<>(3);
329      elements.add(new ASN1ObjectIdentifier(oid));
330
331      if (isCritical)
332      {
333        elements.add(ASN1Boolean.UNIVERSAL_BOOLEAN_TRUE_ELEMENT);
334      }
335
336      elements.add(new ASN1OctetString(value));
337      return new ASN1Sequence(elements);
338    }
339    catch (final Exception e)
340    {
341      Debug.debugException(e);
342      throw new CertException(
343           ERR_EXTENSION_ENCODE_ERROR.get(toString(),
344                StaticUtils.getExceptionMessage(e)),
345           e);
346    }
347  }
348
349
350
351  /**
352   * Retrieves the name for this extension.
353   *
354   * @return  The name for this extension.
355   */
356  @NotNull()
357  public String getExtensionName()
358  {
359    return oid.toString();
360  }
361
362
363
364  /**
365   * Retrieves a string representation of this extension.
366   *
367   * @return  A string representation of this extension.
368   */
369  @NotNull()
370  public final String toString()
371  {
372    final StringBuilder buffer = new StringBuilder();
373    toString(buffer);
374    return buffer.toString();
375  }
376
377
378
379  /**
380   * Appends a string representation of this certificate extension to the
381   * provided buffer.
382   *
383   * @param  buffer  The buffer to which the information should be appended.
384   */
385  public void toString(@NotNull final StringBuilder buffer)
386  {
387    buffer.append("X509CertificateExtension(oid='");
388    buffer.append(oid.toString());
389    buffer.append("', isCritical=");
390    buffer.append(isCritical);
391
392    if (StaticUtils.isPrintableString(value))
393    {
394      buffer.append(", value='");
395      buffer.append(StaticUtils.toUTF8String(value));
396      buffer.append('\'');
397    }
398    else
399    {
400      buffer.append(", valueLength=");
401      buffer.append(value.length);
402    }
403
404    buffer.append(')');
405  }
406}