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