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.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 = new LinkedHashSet<>(elements.length);
131      for (final ASN1Element e : elements)
132      {
133        ids.add(e.decodeAsObjectIdentifier().getOID());
134      }
135
136      keyPurposeIDs = Collections.unmodifiableSet(ids);
137    }
138    catch (final Exception e)
139    {
140      Debug.debugException(e);
141      throw new CertException(
142           ERR_EXTENDED_KEY_USAGE_EXTENSION_CANNOT_PARSE.get(
143                String.valueOf(extension), StaticUtils.getExceptionMessage(e)),
144           e);
145    }
146  }
147
148
149
150  /**
151   * Encodes the provided information for use as the value of this extension.
152   *
153   * @param  keyPurposeIDs  The set of key purpose IDs included in this
154   *                        extension.  It must not be {@code null}.
155   *
156   * @return  The encoded value for this extension.
157   *
158   * @throws  CertException  If a problem is encountered while encoding the
159   *                         value.
160   */
161  private static byte[] encodeValue(final List<OID> keyPurposeIDs)
162          throws CertException
163  {
164    try
165    {
166      final ArrayList<ASN1Element> elements =
167           new ArrayList<>(keyPurposeIDs.size());
168      for (final OID oid : keyPurposeIDs)
169      {
170        elements.add(new ASN1ObjectIdentifier(oid));
171      }
172
173      return new ASN1Sequence(elements).encode();
174    }
175    catch (final Exception e)
176    {
177      Debug.debugException(e);
178      throw new CertException(
179           ERR_EXTENDED_KEY_USAGE_EXTENSION_CANNOT_ENCODE.get(
180                String.valueOf(keyPurposeIDs),
181                StaticUtils.getExceptionMessage(e)),
182           e);
183    }
184  }
185
186
187
188  /**
189   * Retrieves the OIDs of the key purpose values contained in this extension.
190   * Some, all, or none of the OIDs contained in this extension may correspond
191   * to values in the {@link ExtendedKeyUsageID} enumeration.
192   *
193   * @return  The OIDs of the key purpose values contained in this extension.
194   */
195  public Set<OID> getKeyPurposeIDs()
196  {
197    return keyPurposeIDs;
198  }
199
200
201
202  /**
203   * {@inheritDoc}
204   */
205  @Override()
206  public String getExtensionName()
207  {
208    return INFO_EXTENDED_KEY_USAGE_EXTENSION_NAME.get();
209  }
210
211
212
213  /**
214   * {@inheritDoc}
215   */
216  @Override()
217  public void toString(final StringBuilder buffer)
218  {
219    buffer.append("ExtendedKeyUsageExtension(oid='");
220    buffer.append(getOID());
221    buffer.append("', isCritical=");
222    buffer.append(isCritical());
223    buffer.append(", keyPurposeIDs={");
224
225    final Iterator<OID> oidIterator = keyPurposeIDs.iterator();
226    while (oidIterator.hasNext())
227    {
228      buffer.append('\'');
229      buffer.append(ExtendedKeyUsageID.getNameOrOID(oidIterator.next()));
230      buffer.append('\'');
231
232      if (oidIterator.hasNext())
233      {
234        buffer.append(", ");
235      }
236    }
237
238    buffer.append("})");
239  }
240}