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