1 /* 2 * Copyright (c) 2002, 2006, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.security.x509; 27 28 import java.io.IOException; 29 import java.util.*; 30 31 import sun.security.util.BitArray; 32 import sun.security.util.DerOutputStream; 33 import sun.security.util.DerValue; 34 35 /** 36 * Represent the DistributionPoint sequence used in the CRL 37 * Distribution Points Extension (OID = 2.5.29.31). 38 * <p> 39 * The ASN.1 definition for this is: 40 * <pre> 41 * DistributionPoint ::= SEQUENCE { 42 * distributionPoint [0] DistributionPointName OPTIONAL, 43 * reasons [1] ReasonFlags OPTIONAL, 44 * cRLIssuer [2] GeneralNames OPTIONAL } 45 * 46 * DistributionPointName ::= CHOICE { 47 * fullName [0] GeneralNames, 48 * nameRelativeToCRLIssuer [1] RelativeDistinguishedName } 49 * 50 * ReasonFlags ::= BIT STRING { 51 * unused (0), 52 * keyCompromise (1), 53 * cACompromise (2), 54 * affiliationChanged (3), 55 * superseded (4), 56 * cessationOfOperation (5), 57 * certificateHold (6), 58 * privilegeWithdrawn (7), 59 * aACompromise (8) } 60 * 61 * GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName 62 * 63 * GeneralName ::= CHOICE { 64 * otherName [0] INSTANCE OF OTHER-NAME, 65 * rfc822Name [1] IA5String, 66 * dNSName [2] IA5String, 67 * x400Address [3] ORAddress, 68 * directoryName [4] Name, 69 * ediPartyName [5] EDIPartyName, 70 * uniformResourceIdentifier [6] IA5String, 71 * iPAddress [7] OCTET STRING, 72 * registeredID [8] OBJECT IDENTIFIER } 73 * 74 * RelativeDistinguishedName ::= 75 * SET OF AttributeTypeAndValue 76 * 77 * AttributeTypeAndValue ::= SEQUENCE { 78 * type AttributeType, 79 * value AttributeValue } 80 * 81 * AttributeType ::= OBJECT IDENTIFIER 82 * 83 * AttributeValue ::= ANY DEFINED BY AttributeType 84 * </pre> 85 * <p> 86 * Instances of this class are designed to be immutable. However, since this 87 * is an internal API we do not use defensive cloning for values for 88 * performance reasons. It is the responsibility of the consumer to ensure 89 * that no mutable elements are modified. 90 * 91 * @author Anne Anderson 92 * @author Andreas Sterbenz 93 * @since 1.4.2 94 * @see CRLDistributionPointsExtension 95 */ 96 public class DistributionPoint { 97 98 // reason flag bits 99 // NOTE that these are NOT quite the same as the CRL reason code extension 100 public final static int KEY_COMPROMISE = 1; 101 public final static int CA_COMPROMISE = 2; 102 public final static int AFFILIATION_CHANGED = 3; 103 public final static int SUPERSEDED = 4; 104 public final static int CESSATION_OF_OPERATION = 5; 105 public final static int CERTIFICATE_HOLD = 6; 106 public final static int PRIVILEGE_WITHDRAWN = 7; 107 public final static int AA_COMPROMISE = 8; 108 109 private static final String[] REASON_STRINGS = { 110 null, 111 "key compromise", 112 "CA compromise", 113 "affiliation changed", 114 "superseded", 115 "cessation of operation", 116 "certificate hold", 117 "privilege withdrawn", 118 "AA compromise", 119 }; 120 121 // context specific tag values 122 private static final byte TAG_DIST_PT = 0; 123 private static final byte TAG_REASONS = 1; 124 private static final byte TAG_ISSUER = 2; 125 126 private static final byte TAG_FULL_NAME = 0; 127 private static final byte TAG_REL_NAME = 1; 128 129 // only one of fullName and relativeName can be set 130 private GeneralNames fullName; 131 private RDN relativeName; 132 133 // reasonFlags or null 134 private boolean[] reasonFlags; 135 136 // crlIssuer or null 137 private GeneralNames crlIssuer; 138 139 // cached hashCode value 140 private volatile int hashCode; 141 142 /** 143 * Constructor for the class using GeneralNames for DistributionPointName 144 * 145 * @param fullName the GeneralNames of the distribution point; may be null 146 * @param reasons the CRL reasons included in the CRL at this distribution 147 * point; may be null 148 * @param issuer the name(s) of the CRL issuer for the CRL at this 149 * distribution point; may be null 150 */ DistributionPoint(GeneralNames fullName, boolean[] reasonFlags, GeneralNames crlIssuer)151 public DistributionPoint(GeneralNames fullName, boolean[] reasonFlags, 152 GeneralNames crlIssuer) { 153 if ((fullName == null) && (crlIssuer == null)) { 154 throw new IllegalArgumentException 155 ("fullName and crlIssuer may not both be null"); 156 } 157 this.fullName = fullName; 158 this.reasonFlags = reasonFlags; 159 this.crlIssuer = crlIssuer; 160 } 161 162 /** 163 * Constructor for the class using RelativeDistinguishedName for 164 * DistributionPointName 165 * 166 * @param relativeName the RelativeDistinguishedName of the distribution 167 * point; may not be null 168 * @param reasons the CRL reasons included in the CRL at this distribution 169 * point; may be null 170 * @param issuer the name(s) of the CRL issuer for the CRL at this 171 * distribution point; may not be null or empty. 172 */ DistributionPoint(RDN relativeName, boolean[] reasonFlags, GeneralNames crlIssuer)173 public DistributionPoint(RDN relativeName, boolean[] reasonFlags, 174 GeneralNames crlIssuer) { 175 if ((relativeName == null) && (crlIssuer == null)) { 176 throw new IllegalArgumentException 177 ("relativeName and crlIssuer may not both be null"); 178 } 179 this.relativeName = relativeName; 180 this.reasonFlags = reasonFlags; 181 this.crlIssuer = crlIssuer; 182 } 183 184 /** 185 * Create the object from the passed DER encoded form. 186 * 187 * @param val the DER encoded form of the DistributionPoint 188 * @throws IOException on error 189 */ DistributionPoint(DerValue val)190 public DistributionPoint(DerValue val) throws IOException { 191 if (val.tag != DerValue.tag_Sequence) { 192 throw new IOException("Invalid encoding of DistributionPoint."); 193 } 194 195 // Note that all the fields in DistributionPoint are defined as 196 // being OPTIONAL, i.e., there could be an empty SEQUENCE, resulting 197 // in val.data being null. 198 while ((val.data != null) && (val.data.available() != 0)) { 199 DerValue opt = val.data.getDerValue(); 200 201 if (opt.isContextSpecific(TAG_DIST_PT) && opt.isConstructed()) { 202 if ((fullName != null) || (relativeName != null)) { 203 throw new IOException("Duplicate DistributionPointName in " 204 + "DistributionPoint."); 205 } 206 DerValue distPnt = opt.data.getDerValue(); 207 if (distPnt.isContextSpecific(TAG_FULL_NAME) 208 && distPnt.isConstructed()) { 209 distPnt.resetTag(DerValue.tag_Sequence); 210 fullName = new GeneralNames(distPnt); 211 } else if (distPnt.isContextSpecific(TAG_REL_NAME) 212 && distPnt.isConstructed()) { 213 distPnt.resetTag(DerValue.tag_Set); 214 relativeName = new RDN(distPnt); 215 } else { 216 throw new IOException("Invalid DistributionPointName in " 217 + "DistributionPoint"); 218 } 219 } else if (opt.isContextSpecific(TAG_REASONS) 220 && !opt.isConstructed()) { 221 if (reasonFlags != null) { 222 throw new IOException("Duplicate Reasons in " + 223 "DistributionPoint."); 224 } 225 opt.resetTag(DerValue.tag_BitString); 226 reasonFlags = (opt.getUnalignedBitString()).toBooleanArray(); 227 } else if (opt.isContextSpecific(TAG_ISSUER) 228 && opt.isConstructed()) { 229 if (crlIssuer != null) { 230 throw new IOException("Duplicate CRLIssuer in " + 231 "DistributionPoint."); 232 } 233 opt.resetTag(DerValue.tag_Sequence); 234 crlIssuer = new GeneralNames(opt); 235 } else { 236 throw new IOException("Invalid encoding of " + 237 "DistributionPoint."); 238 } 239 } 240 if ((crlIssuer == null) && (fullName == null) && (relativeName == null)) { 241 throw new IOException("One of fullName, relativeName, " 242 + " and crlIssuer has to be set"); 243 } 244 } 245 246 /** 247 * Return the full distribution point name or null if not set. 248 */ getFullName()249 public GeneralNames getFullName() { 250 return fullName; 251 } 252 253 /** 254 * Return the relative distribution point name or null if not set. 255 */ getRelativeName()256 public RDN getRelativeName() { 257 return relativeName; 258 } 259 260 /** 261 * Return the reason flags or null if not set. 262 */ getReasonFlags()263 public boolean[] getReasonFlags() { 264 return reasonFlags; 265 } 266 267 /** 268 * Return the CRL issuer name or null if not set. 269 */ getCRLIssuer()270 public GeneralNames getCRLIssuer() { 271 return crlIssuer; 272 } 273 274 /** 275 * Write the DistributionPoint value to the DerOutputStream. 276 * 277 * @param out the DerOutputStream to write the extension to. 278 * @exception IOException on error. 279 */ encode(DerOutputStream out)280 public void encode(DerOutputStream out) throws IOException { 281 DerOutputStream tagged = new DerOutputStream(); 282 283 // NOTE: only one of pointNames and pointRDN can be set 284 if ((fullName != null) || (relativeName != null)) { 285 DerOutputStream distributionPoint = new DerOutputStream(); 286 if (fullName != null) { 287 DerOutputStream derOut = new DerOutputStream(); 288 fullName.encode(derOut); 289 distributionPoint.writeImplicit( 290 DerValue.createTag(DerValue.TAG_CONTEXT, true, TAG_FULL_NAME), 291 derOut); 292 } else if (relativeName != null) { 293 DerOutputStream derOut = new DerOutputStream(); 294 relativeName.encode(derOut); 295 distributionPoint.writeImplicit( 296 DerValue.createTag(DerValue.TAG_CONTEXT, true, TAG_REL_NAME), 297 derOut); 298 } 299 tagged.write( 300 DerValue.createTag(DerValue.TAG_CONTEXT, true, TAG_DIST_PT), 301 distributionPoint); 302 } 303 if (reasonFlags != null) { 304 DerOutputStream reasons = new DerOutputStream(); 305 BitArray rf = new BitArray(reasonFlags); 306 reasons.putTruncatedUnalignedBitString(rf); 307 tagged.writeImplicit( 308 DerValue.createTag(DerValue.TAG_CONTEXT, false, TAG_REASONS), 309 reasons); 310 } 311 if (crlIssuer != null) { 312 DerOutputStream issuer = new DerOutputStream(); 313 crlIssuer.encode(issuer); 314 tagged.writeImplicit( 315 DerValue.createTag(DerValue.TAG_CONTEXT, true, TAG_ISSUER), 316 issuer); 317 } 318 out.write(DerValue.tag_Sequence, tagged); 319 } 320 321 /** 322 * Utility function for a.equals(b) where both a and b may be null. 323 */ equals(Object a, Object b)324 private static boolean equals(Object a, Object b) { 325 return (a == null) ? (b == null) : a.equals(b); 326 } 327 328 /** 329 * Compare an object to this DistributionPoint for equality. 330 * 331 * @param obj Object to be compared to this 332 * @return true if objects match; false otherwise 333 */ equals(Object obj)334 public boolean equals(Object obj) { 335 if (this == obj) { 336 return true; 337 } 338 if (obj instanceof DistributionPoint == false) { 339 return false; 340 } 341 DistributionPoint other = (DistributionPoint)obj; 342 343 boolean equal = equals(this.fullName, other.fullName) 344 && equals(this.relativeName, other.relativeName) 345 && equals(this.crlIssuer, other.crlIssuer) 346 && Arrays.equals(this.reasonFlags, other.reasonFlags); 347 return equal; 348 } 349 hashCode()350 public int hashCode() { 351 int hash = hashCode; 352 if (hash == 0) { 353 hash = 1; 354 if (fullName != null) { 355 hash += fullName.hashCode(); 356 } 357 if (relativeName != null) { 358 hash += relativeName.hashCode(); 359 } 360 if (crlIssuer != null) { 361 hash += crlIssuer.hashCode(); 362 } 363 if (reasonFlags != null) { 364 for (int i = 0; i < reasonFlags.length; i++) { 365 if (reasonFlags[i]) { 366 hash += i; 367 } 368 } 369 } 370 hashCode = hash; 371 } 372 return hash; 373 } 374 375 /** 376 * Return a string representation for reasonFlag bit 'reason'. 377 */ reasonToString(int reason)378 private static String reasonToString(int reason) { 379 if ((reason > 0) && (reason < REASON_STRINGS.length)) { 380 return REASON_STRINGS[reason]; 381 } 382 return "Unknown reason " + reason; 383 } 384 385 /** 386 * Return a printable string of the Distribution Point. 387 */ toString()388 public String toString() { 389 StringBuilder sb = new StringBuilder(); 390 if (fullName != null) { 391 sb.append("DistributionPoint:\n " + fullName + "\n"); 392 } 393 if (relativeName != null) { 394 sb.append("DistributionPoint:\n " + relativeName + "\n"); 395 } 396 397 if (reasonFlags != null) { 398 sb.append(" ReasonFlags:\n"); 399 for (int i = 0; i < reasonFlags.length; i++) { 400 if (reasonFlags[i]) { 401 sb.append(" " + reasonToString(i) + "\n"); 402 } 403 } 404 } 405 if (crlIssuer != null) { 406 sb.append(" CRLIssuer:" + crlIssuer + "\n"); 407 } 408 return sb.toString(); 409 } 410 411 } 412