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.io.Serializable;
026import java.util.ArrayList;
027import java.util.Collections;
028import java.util.EnumSet;
029import java.util.Iterator;
030import java.util.Set;
031
032import com.unboundid.asn1.ASN1Element;
033import com.unboundid.asn1.ASN1ObjectIdentifier;
034import com.unboundid.asn1.ASN1Sequence;
035import com.unboundid.asn1.ASN1Set;
036import com.unboundid.asn1.ASN1UTF8String;
037import com.unboundid.ldap.sdk.RDN;
038import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
039import com.unboundid.ldap.sdk.schema.Schema;
040import com.unboundid.util.Debug;
041import com.unboundid.util.NotMutable;
042import com.unboundid.util.OID;
043import com.unboundid.util.StaticUtils;
044import com.unboundid.util.ThreadSafety;
045import com.unboundid.util.ThreadSafetyLevel;
046
047import static com.unboundid.util.ssl.cert.CertMessages.*;
048
049
050
051/**
052 * This class implements a data structure that provides information about a
053 * CRL distribution point for use in conjunction with the
054 * {@link CRLDistributionPointsExtension}.  A CRL distribution point has the
055 * following ASN.1 encoding:
056 * <PRE>
057 *   DistributionPoint ::= SEQUENCE {
058 *        distributionPoint       [0]     DistributionPointName OPTIONAL,
059 *        reasons                 [1]     ReasonFlags OPTIONAL,
060 *        cRLIssuer               [2]     GeneralNames OPTIONAL }
061 *
062 *   DistributionPointName ::= CHOICE {
063 *        fullName                [0]     GeneralNames,
064 *        nameRelativeToCRLIssuer [1]     RelativeDistinguishedName }
065 *
066 *   ReasonFlags ::= BIT STRING {
067 *        unused                  (0),
068 *        keyCompromise           (1),
069 *        cACompromise            (2),
070 *        affiliationChanged      (3),
071 *        superseded              (4),
072 *        cessationOfOperation    (5),
073 *        certificateHold         (6),
074 *        privilegeWithdrawn      (7),
075 *        aACompromise            (8) }
076 * </PRE>
077 */
078@NotMutable()
079@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
080public final class CRLDistributionPoint
081       implements Serializable
082{
083  /**
084   * The DER type for the distribution point element in the value sequence.
085   */
086  private static final byte TYPE_DISTRIBUTION_POINT = (byte) 0xA0;
087
088
089
090  /**
091   * The DER type for the reasons element in the value sequence.
092   */
093  private static final byte TYPE_REASONS = (byte) 0x81;
094
095
096
097  /**
098   * The DER type for the CRL issuer element in the value sequence.
099   */
100  private static final byte TYPE_CRL_ISSUER = (byte) 0xA2;
101
102
103
104  /**
105   * The DER type for the distribution point name element in the distribution
106   * point CHOICE element.
107   */
108  private static final byte TYPE_FULL_NAME = (byte) 0xA0;
109
110
111
112  /**
113   * The DER type for the name relative to CRL issuer element in the
114   * distribution point CHOICE element.
115   */
116  private static final byte TYPE_NAME_RELATIVE_TO_CRL_ISSUER = (byte) 0xA1;
117
118
119
120  /**
121   * The serial version UID for this serializable class.
122   */
123  private static final long serialVersionUID = -8461308509960278714L;
124
125
126
127  // The full set of names for the entity that signs the CRL.
128  private final GeneralNames crlIssuer;
129
130  // The full set of names for this CRL distribution point.
131  private final GeneralNames fullName;
132
133  // The name of the distribution point relative to the CRL issuer.
134  private final RDN nameRelativeToCRLIssuer;
135
136  // The set of reasons that the CRL distribution point may revoke a
137  // certificate.
138  private final Set<CRLDistributionPointRevocationReason> revocationReasons;
139
140
141
142  /**
143   * Creates a new CRL distribution point with the provided information.
144   *
145   * @param  fullName           The full name for the CRL distribution point.
146   *                            This may be {@code null} if it should not be
147   *                            included.
148   * @param  revocationReasons  The set of reasons that the CRL distribution
149   *                            point may revoke a certificate.  This may be
150   *                            {@code null} if all of the defined reasons
151   *                            should be considered valid.
152   * @param  crlIssuer          The full name for the entity that signs the CRL.
153   */
154  CRLDistributionPoint(final GeneralNames fullName,
155       final Set<CRLDistributionPointRevocationReason> revocationReasons,
156       final GeneralNames crlIssuer)
157  {
158    this.fullName = fullName;
159    this.crlIssuer = crlIssuer;
160
161    nameRelativeToCRLIssuer = null;
162
163    if (revocationReasons == null)
164    {
165      this.revocationReasons = Collections.unmodifiableSet(EnumSet.allOf(
166           CRLDistributionPointRevocationReason.class));
167    }
168    else
169    {
170      this.revocationReasons = Collections.unmodifiableSet(revocationReasons);
171    }
172  }
173
174
175
176  /**
177   * Creates a new CRL distribution point with the provided information.
178   *
179   * @param  nameRelativeToCRLIssuer  The name of the distribution point
180   *                                  relative to that of the CRL issuer.  This
181   *                                  may be {@code null} if it should not be
182   *                                  included.
183   * @param  revocationReasons        The set of reasons that the CRL
184   *                                  distribution point may revoke a
185   *                                  certificate.  This may be {@code null} if
186   *                                  all of the defined reasons should be
187   *                                  considered valid.
188   * @param  crlIssuer                The full name for the entity that signs
189   *                                  the CRL.
190   */
191  CRLDistributionPoint(final RDN nameRelativeToCRLIssuer,
192       final Set<CRLDistributionPointRevocationReason> revocationReasons,
193       final GeneralNames crlIssuer)
194  {
195    this.nameRelativeToCRLIssuer = nameRelativeToCRLIssuer;
196    this.crlIssuer = crlIssuer;
197
198    fullName = null;
199
200    if (revocationReasons == null)
201    {
202      this.revocationReasons = Collections.unmodifiableSet(EnumSet.allOf(
203           CRLDistributionPointRevocationReason.class));
204    }
205    else
206    {
207      this.revocationReasons = Collections.unmodifiableSet(revocationReasons);
208    }
209  }
210
211
212
213  /**
214   * Creates a new CLR distribution point object that is decoded from the
215   * provided ASN.1 element.
216   *
217   * @param  element  The element to decode as a CRL distribution point.
218   *
219   * @throws  CertException  If the provided element cannot be decoded as a CRL
220   *                         distribution point.
221   */
222  CRLDistributionPoint(final ASN1Element element)
223       throws CertException
224  {
225    try
226    {
227      GeneralNames dpFullName = null;
228      GeneralNames issuer = null;
229      RDN dpRDN = null;
230      Set<CRLDistributionPointRevocationReason> reasons =
231           EnumSet.allOf(CRLDistributionPointRevocationReason.class);
232
233      for (final ASN1Element e : element.decodeAsSequence().elements())
234      {
235        switch (e.getType())
236        {
237          case TYPE_DISTRIBUTION_POINT:
238            final ASN1Element innerElement = ASN1Element.decode(e.getValue());
239            switch (innerElement.getType())
240            {
241              case TYPE_FULL_NAME:
242                dpFullName = new GeneralNames(innerElement);
243                break;
244
245              case TYPE_NAME_RELATIVE_TO_CRL_ISSUER:
246                final Schema schema = Schema.getDefaultStandardSchema();
247                final ASN1Element[] attributeSetElements =
248                     innerElement.decodeAsSet().elements();
249                final String[] attributeNames =
250                     new String[attributeSetElements.length];
251                final byte[][] attributeValues =
252                     new byte[attributeSetElements.length][];
253                for (int j=0; j < attributeSetElements.length; j++)
254                {
255                  final ASN1Element[] attributeTypeAndValueElements =
256                       attributeSetElements[j].decodeAsSequence().elements();
257                  final OID attributeTypeOID = attributeTypeAndValueElements[0].
258                       decodeAsObjectIdentifier().getOID();
259                  final AttributeTypeDefinition attributeType =
260                       schema.getAttributeType(attributeTypeOID.toString());
261                  if (attributeType == null)
262                  {
263                    attributeNames[j] = attributeTypeOID.toString();
264                  }
265                  else
266                  {
267                    attributeNames[j] =
268                         attributeType.getNameOrOID().toUpperCase();
269                  }
270
271                  attributeValues[j] = attributeTypeAndValueElements[1].
272                       decodeAsOctetString().getValue();
273                }
274
275                dpRDN = new RDN(attributeNames, attributeValues, schema);
276                break;
277
278              default:
279                throw new CertException(
280                     ERR_CRL_DP_UNRECOGNIZED_NAME_ELEMENT_TYPE.get(
281                          StaticUtils.toHex(innerElement.getType())));
282            }
283            break;
284
285          case TYPE_REASONS:
286            reasons = CRLDistributionPointRevocationReason.getReasonSet(
287                 e.decodeAsBitString());
288            break;
289
290          case TYPE_CRL_ISSUER:
291            issuer = new GeneralNames(e);
292            break;
293        }
294      }
295
296      fullName = dpFullName;
297      nameRelativeToCRLIssuer = dpRDN;
298      revocationReasons = Collections.unmodifiableSet(reasons);
299      crlIssuer = issuer;
300    }
301    catch (final CertException e)
302    {
303      Debug.debugException(e);
304      throw e;
305    }
306    catch (final Exception e)
307    {
308      Debug.debugException(e);
309      throw new CertException(
310           ERR_CRL_DP_CANNOT_DECODE.get(StaticUtils.getExceptionMessage(e)), e);
311    }
312  }
313
314
315
316  /**
317   * Encodes this CRL distribution point to an ASN.1 element.
318   *
319   * @return  The encoded CRL distribution point.
320   *
321   * @throws  CertException  If a problem is encountered while encoding this
322   *                         CRL distribution point.
323   */
324  ASN1Element encode()
325       throws CertException
326  {
327    final ArrayList<ASN1Element> elements = new ArrayList<>(3);
328
329    ASN1Element distributionPointElement = null;
330    if (fullName != null)
331    {
332      distributionPointElement =
333           new ASN1Element(TYPE_FULL_NAME, fullName.encode().getValue());
334    }
335    else if (nameRelativeToCRLIssuer != null)
336    {
337      final Schema schema;
338      try
339      {
340        schema = Schema.getDefaultStandardSchema();
341      }
342      catch (final Exception e)
343      {
344        Debug.debugException(e);
345        throw new CertException(
346             ERR_CRL_DP_ENCODE_CANNOT_GET_SCHEMA.get(toString(),
347                  String.valueOf(nameRelativeToCRLIssuer),
348                  StaticUtils.getExceptionMessage(e)),
349             e);
350      }
351
352      final String[] names = nameRelativeToCRLIssuer.getAttributeNames();
353      final String[] values = nameRelativeToCRLIssuer.getAttributeValues();
354      final ArrayList<ASN1Element> rdnElements = new ArrayList<>(names.length);
355      for (int i=0; i < names.length; i++)
356      {
357        final AttributeTypeDefinition at = schema.getAttributeType(names[i]);
358        if (at == null)
359        {
360          throw new CertException(ERR_CRL_DP_ENCODE_UNKNOWN_ATTR_TYPE.get(
361               toString(), String.valueOf(nameRelativeToCRLIssuer), names[i]));
362        }
363
364        try
365        {
366          rdnElements.add(new ASN1Sequence(
367               new ASN1ObjectIdentifier(at.getOID()),
368               new ASN1UTF8String(values[i])));
369        }
370        catch (final Exception e)
371        {
372          Debug.debugException(e);
373          throw new CertException(
374               ERR_CRL_DP_ENCODE_ERROR.get(toString(),
375                    String.valueOf(nameRelativeToCRLIssuer),
376                    StaticUtils.getExceptionMessage(e)),
377               e);
378        }
379      }
380
381      distributionPointElement =
382           new ASN1Set(TYPE_NAME_RELATIVE_TO_CRL_ISSUER, rdnElements);
383    }
384
385    if (distributionPointElement != null)
386    {
387      elements.add(new ASN1Element(TYPE_DISTRIBUTION_POINT,
388           distributionPointElement.encode()));
389    }
390
391    if (! revocationReasons.equals(EnumSet.allOf(
392         CRLDistributionPointRevocationReason.class)))
393    {
394      elements.add(CRLDistributionPointRevocationReason.toBitString(
395           TYPE_REASONS, revocationReasons));
396    }
397
398    if (crlIssuer != null)
399    {
400      elements.add(new ASN1Element(TYPE_CRL_ISSUER,
401           crlIssuer.encode().getValue()));
402    }
403
404    return new ASN1Sequence(elements);
405  }
406
407
408
409  /**
410   * Retrieves the full set of names for this CRL distribution point, if
411   * available.
412   *
413   * @return  The full set of names for this CRL distribution point, or
414   *          {@code null} if it was not included in the extension.
415   */
416  public GeneralNames getFullName()
417  {
418    return fullName;
419  }
420
421
422
423  /**
424   * Retrieves the name relative to the CRL issuer for this CRL distribution
425   * point, if available.
426   *
427   * @return  The name relative to the CRL issuer for this CRL distribution
428   *          point, or {@code null} if it was not included in the extension.
429   */
430  public RDN getNameRelativeToCRLIssuer()
431  {
432    return nameRelativeToCRLIssuer;
433  }
434
435
436
437  /**
438   * Retrieves a set of potential reasons that the CRL distribution point may
439   * list a certificate as revoked.
440   *
441   * @return  A set of potential reasons that the CRL distribution point may
442   *          list a certificate as revoked.
443   */
444  public Set<CRLDistributionPointRevocationReason>
445              getPotentialRevocationReasons()
446  {
447    return revocationReasons;
448  }
449
450
451
452  /**
453   * Retrieves the full set of names for the CRL issuer, if available.
454   *
455   * @return  The full set of names for the CRL issuer, or {@code null} if it
456   *          was not included in the extension.
457   */
458  public GeneralNames getCRLIssuer()
459  {
460    return crlIssuer;
461  }
462
463
464
465  /**
466   * Retrieves a string representation of this CRL distribution point.
467   *
468   * @return  A string representation of this CRL distribution point.
469   */
470  @Override()
471  public String toString()
472  {
473    final StringBuilder buffer = new StringBuilder();
474    toString(buffer);
475    return buffer.toString();
476  }
477
478
479
480  /**
481   * Appends a string representation of this CRL distribution point to the
482   * provided buffer.
483   *
484   * @param  buffer  The buffer to which the information should be appended.
485   */
486  public void toString(final StringBuilder buffer)
487  {
488    buffer.append("CRLDistributionPoint(");
489
490    if (fullName != null)
491    {
492      buffer.append("fullName=");
493      fullName.toString(buffer);
494      buffer.append(", ");
495    }
496    else if (nameRelativeToCRLIssuer != null)
497    {
498      buffer.append("nameRelativeToCRLIssuer='");
499      nameRelativeToCRLIssuer.toString(buffer);
500      buffer.append("', ");
501    }
502
503    buffer.append("potentialRevocationReasons={");
504
505    final Iterator<CRLDistributionPointRevocationReason> reasonIterator =
506         revocationReasons.iterator();
507    while (reasonIterator.hasNext())
508    {
509      buffer.append('\'');
510      buffer.append(reasonIterator.next().getName());
511      buffer.append('\'');
512
513      if (reasonIterator.hasNext())
514      {
515        buffer.append(',');
516      }
517    }
518
519    if (crlIssuer != null)
520    {
521      buffer.append(", crlIssuer=");
522      crlIssuer.toString(buffer);
523    }
524
525    buffer.append('}');
526  }
527}