1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 /** 19 * @author Alexander Y. Kleymenov 20 * @version $Revision$ 21 */ 22 23 package org.apache.harmony.security.x509; 24 25 import java.io.IOException; 26 import java.util.ArrayList; 27 import java.util.Arrays; 28 import java.util.Collection; 29 import java.util.Collections; 30 import java.util.HashMap; 31 import java.util.HashSet; 32 import java.util.List; 33 import java.util.Set; 34 import javax.security.auth.x500.X500Principal; 35 import org.apache.harmony.security.asn1.ASN1SequenceOf; 36 import org.apache.harmony.security.asn1.ASN1Type; 37 import org.apache.harmony.security.asn1.BerInputStream; 38 39 /** 40 * The class encapsulates the ASN.1 DER encoding/decoding work 41 * with the Extensions part of X.509 certificate 42 * (as specified in RFC 3280 - 43 * Internet X.509 Public Key Infrastructure. 44 * Certificate and Certificate Revocation List (CRL) Profile. 45 * http://www.ietf.org/rfc/rfc3280.txt): 46 * 47 * <pre> 48 * Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension 49 * </pre> 50 */ 51 public final class Extensions { 52 53 // Supported critical extensions oids: 54 private static List SUPPORTED_CRITICAL = Arrays.asList( 55 "2.5.29.15", "2.5.29.19", "2.5.29.32", "2.5.29.17", 56 "2.5.29.30", "2.5.29.36", "2.5.29.37", "2.5.29.54"); 57 58 // the values of extensions of the structure 59 private final List<Extension> extensions; 60 61 // to speed up access, the following fields cache values computed 62 // from the extensions field, initialized using the "single-check 63 // idiom". 64 65 private volatile Set<String> critical; 66 private volatile Set<String> noncritical; 67 // the flag showing is there any unsupported critical extension 68 // in the list of extensions or not. 69 private volatile Boolean hasUnsupported; 70 71 // map containing the oid of extensions as a keys and 72 // Extension objects as values 73 private volatile HashMap<String, Extension> oidMap; 74 75 // the ASN.1 encoded form of Extensions 76 private byte[] encoding; 77 78 /** 79 * Constructs an object representing the value of Extensions. 80 */ 81 public Extensions() { 82 this.extensions = null; 83 } 84 85 public Extensions(List<Extension> extensions) { 86 this.extensions = extensions; 87 } 88 89 public int size() { 90 return (extensions == null) ? 0 : extensions.size(); 91 } 92 93 /** 94 * Returns the list of critical extensions. 95 */ 96 public Set<String> getCriticalExtensions() { 97 Set<String> resultCritical = critical; 98 if (resultCritical == null) { 99 makeOidsLists(); 100 resultCritical = critical; 101 } 102 return resultCritical; 103 } 104 105 /** 106 * Returns the list of critical extensions. 107 */ 108 public Set<String> getNonCriticalExtensions() { 109 Set<String> resultNoncritical = noncritical; 110 if (resultNoncritical == null) { 111 makeOidsLists(); 112 resultNoncritical = noncritical; 113 } 114 return resultNoncritical; 115 } 116 117 public boolean hasUnsupportedCritical() { 118 Boolean resultHasUnsupported = hasUnsupported; 119 if (resultHasUnsupported == null) { 120 makeOidsLists(); 121 resultHasUnsupported = hasUnsupported; 122 } 123 return resultHasUnsupported.booleanValue(); 124 } 125 126 // 127 // Makes the separated lists with oids of critical 128 // and non-critical extensions 129 // 130 private void makeOidsLists() { 131 if (extensions == null) { 132 return; 133 } 134 int size = extensions.size(); 135 Set<String> localCritical = new HashSet<String>(size); 136 Set<String> localNoncritical = new HashSet<String>(size); 137 Boolean localHasUnsupported = Boolean.FALSE; 138 for (Extension extension : extensions) { 139 String oid = extension.getExtnID(); 140 if (extension.getCritical()) { 141 if (!SUPPORTED_CRITICAL.contains(oid)) { 142 localHasUnsupported = Boolean.TRUE; 143 } 144 localCritical.add(oid); 145 } else { 146 localNoncritical.add(oid); 147 } 148 } 149 this.critical = localCritical; 150 this.noncritical = localNoncritical; 151 this.hasUnsupported = localHasUnsupported; 152 } 153 154 /** 155 * Returns the values of extensions. 156 */ 157 public Extension getExtensionByOID(String oid) { 158 if (extensions == null) { 159 return null; 160 } 161 HashMap<String, Extension> localOidMap = oidMap; 162 if (localOidMap == null) { 163 localOidMap = new HashMap<String, Extension>(); 164 for (Extension extension : extensions) { 165 localOidMap.put(extension.getExtnID(), extension); 166 } 167 this.oidMap = localOidMap; 168 } 169 return localOidMap.get(oid); 170 } 171 172 173 /** 174 * Returns the value of Key Usage extension (OID == 2.5.29.15). 175 * The ASN.1 definition of Key Usage Extension is: 176 * 177 * <pre> 178 * id-ce-keyUsage OBJECT IDENTIFIER ::= { id-ce 15 } 179 * 180 * KeyUsage ::= BIT STRING { 181 * digitalSignature (0), 182 * nonRepudiation (1), 183 * keyEncipherment (2), 184 * dataEncipherment (3), 185 * keyAgreement (4), 186 * keyCertSign (5), 187 * cRLSign (6), 188 * encipherOnly (7), 189 * decipherOnly (8) 190 * } 191 * </pre> 192 * (as specified in RFC 3280) 193 * 194 * @return the value of Key Usage Extension if it is in the list, 195 * and null if there is no such extension or its value can not be decoded 196 * otherwise. Note, that the length of returned array can be greater 197 * than 9. 198 */ 199 public boolean[] valueOfKeyUsage() { 200 Extension extension = getExtensionByOID("2.5.29.15"); 201 KeyUsage kUsage; 202 if ((extension == null) || ((kUsage = extension.getKeyUsageValue()) == null)) { 203 return null; 204 } 205 return kUsage.getKeyUsage(); 206 } 207 208 /** 209 * Returns the value of Extended Key Usage extension (OID == 2.5.29.37). 210 * The ASN.1 definition of Extended Key Usage Extension is: 211 * 212 * <pre> 213 * id-ce-extKeyUsage OBJECT IDENTIFIER ::= { id-ce 37 } 214 * 215 * ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId 216 * 217 * KeyPurposeId ::= OBJECT IDENTIFIER 218 * </pre> 219 * (as specified in RFC 3280) 220 * 221 * @return the list with string representations of KeyPurposeId's OIDs 222 * and null 223 * @throws IOException if extension was incorrectly encoded. 224 */ 225 public List<String> valueOfExtendedKeyUsage() throws IOException { 226 Extension extension = getExtensionByOID("2.5.29.37"); 227 if (extension == null) { 228 return null; 229 } 230 return ((ExtendedKeyUsage) extension.getDecodedExtensionValue()).getExtendedKeyUsage(); 231 } 232 233 /** 234 * Returns the value of Basic Constraints Extension (OID = 2.5.29.19). 235 * The ASN.1 definition of Basic Constraints Extension is: 236 * 237 * <pre> 238 * id-ce-basicConstraints OBJECT IDENTIFIER ::= { id-ce 19 } 239 * 240 * BasicConstraints ::= SEQUENCE { 241 * cA BOOLEAN DEFAULT FALSE, 242 * pathLenConstraint INTEGER (0..MAX) OPTIONAL 243 * } 244 * </pre> 245 * (as specified in RFC 3280) 246 * 247 * @return-1 if the Basic Constraints Extension is not present or 248 * it is present but it indicates the certificate is not a 249 * certificate authority. If the certificate is a certificate 250 * authority, returns the path length constraint if present, or 251 * Integer.MAX_VALUE if it is not. 252 */ 253 public int valueOfBasicConstraints() { 254 Extension extension = getExtensionByOID("2.5.29.19"); 255 if (extension == null) { 256 return -1; 257 } 258 BasicConstraints bc = extension.getBasicConstraintsValue(); 259 if (bc == null || !bc.getCa()) { 260 return -1; 261 } 262 return bc.getPathLenConstraint(); 263 } 264 265 /** 266 * Returns the value of Subject Alternative Name (OID = 2.5.29.17). 267 * The ASN.1 definition for Subject Alternative Name is: 268 * 269 * <pre> 270 * id-ce-subjectAltName OBJECT IDENTIFIER ::= { id-ce 17 } 271 * 272 * SubjectAltName ::= GeneralNames 273 * </pre> 274 * (as specified in RFC 3280) 275 * 276 * @return Returns the collection of pairs: 277 * (Integer (tag), Object (name value)) if extension presents, and 278 * null if does not. 279 */ 280 public Collection<List<?>> valueOfSubjectAlternativeName() throws IOException { 281 return decodeGeneralNames(getExtensionByOID("2.5.29.17")); 282 } 283 284 /** 285 * Returns the value of Issuer Alternative Name Extension (OID = 2.5.29.18). 286 * The ASN.1 definition for Issuer Alternative Name is: 287 * 288 * <pre> 289 * id-ce-issuerAltName OBJECT IDENTIFIER ::= { id-ce 18 } 290 * 291 * IssuerAltName ::= GeneralNames 292 * </pre> 293 * (as specified in RFC 3280) 294 * 295 * @return Returns the collection of pairs: 296 * (Integer (tag), Object (name value)) if extension presents, and 297 * null if does not. 298 */ 299 public Collection<List<?>> valueOfIssuerAlternativeName() throws IOException { 300 return decodeGeneralNames(getExtensionByOID("2.5.29.18")); 301 } 302 303 /** 304 * Given an X.509 extension that encodes GeneralNames, return it in the 305 * format expected by APIs. 306 */ 307 private static Collection<List<?>> decodeGeneralNames(Extension extension) 308 throws IOException { 309 if (extension == null) { 310 return null; 311 } 312 313 Collection<List<?>> collection = ((GeneralNames) GeneralNames.ASN1.decode(extension 314 .getExtnValue())).getPairsList(); 315 316 /* 317 * If the extension had any invalid entries, we may have an empty 318 * collection at this point, so just return null. 319 */ 320 if (collection.size() == 0) { 321 return null; 322 } 323 324 return Collections.unmodifiableCollection(collection); 325 } 326 327 /** 328 * Returns the value of Certificate Issuer Extension (OID = 2.5.29.29). 329 * It is a CRL entry extension and contains the GeneralNames describing 330 * the issuer of revoked certificate. Its ASN.1 notation is as follows: 331 * <pre> 332 * id-ce-certificateIssuer OBJECT IDENTIFIER ::= { id-ce 29 } 333 * 334 * certificateIssuer ::= GeneralNames 335 * </pre> 336 * (as specified in RFC 3280) 337 * 338 * @return the value of Certificate Issuer Extension 339 */ 340 public X500Principal valueOfCertificateIssuerExtension() throws IOException { 341 Extension extension = getExtensionByOID("2.5.29.29"); 342 if (extension == null) { 343 return null; 344 } 345 return ((CertificateIssuer) extension.getDecodedExtensionValue()).getIssuer(); 346 } 347 348 /** 349 * Returns ASN.1 encoded form of this X.509 Extensions value. 350 */ 351 public byte[] getEncoded() { 352 if (encoding == null) { 353 encoding = ASN1.encode(this); 354 } 355 return encoding; 356 } 357 358 @Override public boolean equals(Object other) { 359 if (!(other instanceof Extensions)) { 360 return false; 361 } 362 Extensions that = (Extensions) other; 363 return (this.extensions == null || this.extensions.isEmpty()) 364 ? (that.extensions == null || that.extensions.isEmpty()) 365 : (this.extensions.equals(that.extensions)); 366 } 367 368 @Override public int hashCode() { 369 int hashCode = 0; 370 if (extensions != null) { 371 hashCode = extensions.hashCode(); 372 } 373 return hashCode; 374 } 375 376 public void dumpValue(StringBuilder sb, String prefix) { 377 if (extensions == null) { 378 return; 379 } 380 int num = 1; 381 for (Extension extension: extensions) { 382 sb.append('\n').append(prefix).append('[').append(num++).append("]: "); 383 extension.dumpValue(sb, prefix); 384 } 385 } 386 387 /** 388 * Custom X.509 Extensions decoder. 389 */ 390 public static final ASN1Type ASN1 = new ASN1SequenceOf(Extension.ASN1) { 391 @Override public Object getDecodedObject(BerInputStream in) { 392 return new Extensions((List<Extension>) in.content); 393 } 394 395 @Override public Collection getValues(Object object) { 396 Extensions extensions = (Extensions) object; 397 return (extensions.extensions == null) ? new ArrayList() : extensions.extensions; 398 } 399 }; 400 } 401