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