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}