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}