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}