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