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