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;
026import java.util.Collections;
027import java.util.Iterator;
028import java.util.LinkedHashSet;
029import java.util.List;
030import java.util.Set;
031
032import com.unboundid.asn1.ASN1Element;
033import com.unboundid.asn1.ASN1ObjectIdentifier;
034import com.unboundid.asn1.ASN1Sequence;
035import com.unboundid.util.Debug;
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 provides an implementation of the extended key usage X.509
048 * certificate extension as described in
049 * <A HREF="https://www.ietf.org/rfc/rfc5280.txt">RFC 5280</A> section 4.2.1.12.
050 * This can be used to provide an extensible list of OIDs that identify ways
051 * that a certificate is intended to be used.
052 * <BR><BR>
053 * The OID for this extension is 2.5.29.37 and the value has the following
054 * encoding:
055 * <PRE>
056 *   ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId
057 *
058 *   KeyPurposeId ::= OBJECT IDENTIFIER
059 * </PRE>
060 *
061 * @see  ExtendedKeyUsageID
062 */
063@NotMutable()
064@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
065public final class ExtendedKeyUsageExtension
066       extends X509CertificateExtension
067{
068  /**
069   * The OID (2.5.29.37) for extended key usage extensions.
070   */
071  public static final OID EXTENDED_KEY_USAGE_OID = new OID("2.5.29.37");
072
073
074
075  /**
076   * The serial version UID for this serializable class.
077   */
078  private static final long serialVersionUID = -8208115548961483723L;
079
080
081
082  // The set of key purpose IDs included in this extension.
083  private final Set<OID> keyPurposeIDs;
084
085
086
087  /**
088   * Creates a new extended key usage extension with the provided set of key
089   * purpose IDs.
090   *
091   * @param  isCritical     Indicates whether this extension should be
092   *                        considered critical.
093   * @param  keyPurposeIDs  The set of key purpose IDs included in this
094   *                        extension.  It must not be {@code null}.
095   *
096   * @throws  CertException  If a problem is encountered while encoding the
097   *                         value for this extension.
098   */
099  ExtendedKeyUsageExtension(final boolean isCritical,
100                            final List<OID> keyPurposeIDs)
101       throws CertException
102  {
103    super(EXTENDED_KEY_USAGE_OID, isCritical, encodeValue(keyPurposeIDs));
104
105    this.keyPurposeIDs =
106         Collections.unmodifiableSet(new LinkedHashSet<>(keyPurposeIDs));
107  }
108
109
110
111  /**
112   * Creates a new extended key usage extension from the provided generic
113   * extension.
114   *
115   * @param  extension  The extension to decode as an extended key usage
116   *                    extension.
117   *
118   * @throws  CertException  If the provided extension cannot be decoded as an
119   *                         extended key usage extension.
120   */
121  ExtendedKeyUsageExtension(final X509CertificateExtension extension)
122       throws CertException
123  {
124    super(extension);
125
126    try
127    {
128      final ASN1Element[] elements =
129           ASN1Sequence.decodeAsSequence(extension.getValue()).elements();
130      final LinkedHashSet<OID> ids =
131           new LinkedHashSet<>(StaticUtils.computeMapCapacity(elements.length));
132      for (final ASN1Element e : elements)
133      {
134        ids.add(e.decodeAsObjectIdentifier().getOID());
135      }
136
137      keyPurposeIDs = Collections.unmodifiableSet(ids);
138    }
139    catch (final Exception e)
140    {
141      Debug.debugException(e);
142      throw new CertException(
143           ERR_EXTENDED_KEY_USAGE_EXTENSION_CANNOT_PARSE.get(
144                String.valueOf(extension), StaticUtils.getExceptionMessage(e)),
145           e);
146    }
147  }
148
149
150
151  /**
152   * Encodes the provided information for use as the value of this extension.
153   *
154   * @param  keyPurposeIDs  The set of key purpose IDs included in this
155   *                        extension.  It must not be {@code null}.
156   *
157   * @return  The encoded value for this extension.
158   *
159   * @throws  CertException  If a problem is encountered while encoding the
160   *                         value.
161   */
162  private static byte[] encodeValue(final List<OID> keyPurposeIDs)
163          throws CertException
164  {
165    try
166    {
167      final ArrayList<ASN1Element> elements =
168           new ArrayList<>(keyPurposeIDs.size());
169      for (final OID oid : keyPurposeIDs)
170      {
171        elements.add(new ASN1ObjectIdentifier(oid));
172      }
173
174      return new ASN1Sequence(elements).encode();
175    }
176    catch (final Exception e)
177    {
178      Debug.debugException(e);
179      throw new CertException(
180           ERR_EXTENDED_KEY_USAGE_EXTENSION_CANNOT_ENCODE.get(
181                String.valueOf(keyPurposeIDs),
182                StaticUtils.getExceptionMessage(e)),
183           e);
184    }
185  }
186
187
188
189  /**
190   * Retrieves the OIDs of the key purpose values contained in this extension.
191   * Some, all, or none of the OIDs contained in this extension may correspond
192   * to values in the {@link ExtendedKeyUsageID} enumeration.
193   *
194   * @return  The OIDs of the key purpose values contained in this extension.
195   */
196  public Set<OID> getKeyPurposeIDs()
197  {
198    return keyPurposeIDs;
199  }
200
201
202
203  /**
204   * {@inheritDoc}
205   */
206  @Override()
207  public String getExtensionName()
208  {
209    return INFO_EXTENDED_KEY_USAGE_EXTENSION_NAME.get();
210  }
211
212
213
214  /**
215   * {@inheritDoc}
216   */
217  @Override()
218  public void toString(final StringBuilder buffer)
219  {
220    buffer.append("ExtendedKeyUsageExtension(oid='");
221    buffer.append(getOID());
222    buffer.append("', isCritical=");
223    buffer.append(isCritical());
224    buffer.append(", keyPurposeIDs={");
225
226    final Iterator<OID> oidIterator = keyPurposeIDs.iterator();
227    while (oidIterator.hasNext())
228    {
229      buffer.append('\'');
230      buffer.append(ExtendedKeyUsageID.getNameOrOID(oidIterator.next()));
231      buffer.append('\'');
232
233      if (oidIterator.hasNext())
234      {
235        buffer.append(", ");
236      }
237    }
238
239    buffer.append("})");
240  }
241}