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 List<Extension> extensions; 60 private Set<String> critical; 61 private Set<String> noncritical; 62 // the flag showing is there any unsupported critical extension 63 // in the list of extensions or not. 64 private boolean hasUnsupported; 65 // map containing the oid of extensions as a keys and 66 // Extension objects as values 67 private HashMap<String, Extension> oidMap; 68 // the ASN.1 encoded form of Extensions 69 private byte[] encoding; 70 71 /** 72 * Constructs an object representing the value of Extensions. 73 */ 74 public Extensions() {} 75 76 public Extensions(List<Extension> extensions) { 77 this.extensions = extensions; 78 } 79 80 public int size() { 81 return (extensions == null) ? 0 : extensions.size(); 82 } 83 84 /** 85 * Returns the list of critical extensions. 86 */ 87 public Set<String> getCriticalExtensions() { 88 if (critical == null) { 89 makeOidsLists(); 90 } 91 return critical; 92 } 93 94 /** 95 * Returns the list of critical extensions. 96 */ 97 public Set<String> getNonCriticalExtensions() { 98 if (noncritical == null) { 99 makeOidsLists(); 100 } 101 return noncritical; 102 } 103 104 public boolean hasUnsupportedCritical() { 105 if (critical == null) { 106 makeOidsLists(); 107 } 108 return hasUnsupported; 109 } 110 111 // 112 // Makes the separated lists with oids of critical 113 // and non-critical extensions 114 // 115 private void makeOidsLists() { 116 if (extensions == null) { 117 return; 118 } 119 int size = extensions.size(); 120 critical = new HashSet<String>(size); 121 noncritical = new HashSet<String>(size); 122 for (Extension extension : extensions) { 123 String oid = extension.getExtnID(); 124 if (extension.getCritical()) { 125 if (!SUPPORTED_CRITICAL.contains(oid)) { 126 hasUnsupported = true; 127 } 128 critical.add(oid); 129 } else { 130 noncritical.add(oid); 131 } 132 } 133 } 134 135 /** 136 * Returns the values of extensions. 137 */ 138 public Extension getExtensionByOID(String oid) { 139 if (extensions == null) { 140 return null; 141 } 142 if (oidMap == null) { 143 oidMap = new HashMap<String, Extension>(); 144 for (Extension extension : extensions) { 145 oidMap.put(extension.getExtnID(), extension); 146 } 147 } 148 return oidMap.get(oid); 149 } 150 151 152 /** 153 * Returns the value of Key Usage extension (OID == 2.5.29.15). 154 * The ASN.1 definition of Key Usage Extension is: 155 * 156 * <pre> 157 * id-ce-keyUsage OBJECT IDENTIFIER ::= { id-ce 15 } 158 * 159 * KeyUsage ::= BIT STRING { 160 * digitalSignature (0), 161 * nonRepudiation (1), 162 * keyEncipherment (2), 163 * dataEncipherment (3), 164 * keyAgreement (4), 165 * keyCertSign (5), 166 * cRLSign (6), 167 * encipherOnly (7), 168 * decipherOnly (8) 169 * } 170 * </pre> 171 * (as specified in RFC 3280) 172 * 173 * @return the value of Key Usage Extension if it is in the list, 174 * and null if there is no such extension or its value can not be decoded 175 * otherwise. Note, that the length of returned array can be greater 176 * than 9. 177 */ 178 public boolean[] valueOfKeyUsage() { 179 Extension extension = getExtensionByOID("2.5.29.15"); 180 KeyUsage kUsage; 181 if ((extension == null) || ((kUsage = extension.getKeyUsageValue()) == null)) { 182 return null; 183 } 184 return kUsage.getKeyUsage(); 185 } 186 187 /** 188 * Returns the value of Extended Key Usage extension (OID == 2.5.29.37). 189 * The ASN.1 definition of Extended Key Usage Extension is: 190 * 191 * <pre> 192 * id-ce-extKeyUsage OBJECT IDENTIFIER ::= { id-ce 37 } 193 * 194 * ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId 195 * 196 * KeyPurposeId ::= OBJECT IDENTIFIER 197 * </pre> 198 * (as specified in RFC 3280) 199 * 200 * @return the list with string representations of KeyPurposeId's OIDs 201 * and null 202 * @throws IOException if extension was incorrectly encoded. 203 */ 204 public List<String> valueOfExtendedKeyUsage() throws IOException { 205 Extension extension = getExtensionByOID("2.5.29.37"); 206 if (extension == null) { 207 return null; 208 } 209 return ((ExtendedKeyUsage) extension.getDecodedExtensionValue()).getExtendedKeyUsage(); 210 } 211 212 /** 213 * Returns the value of Basic Constraints Extension (OID = 2.5.29.19). 214 * The ASN.1 definition of Basic Constraints Extension is: 215 * 216 * <pre> 217 * id-ce-basicConstraints OBJECT IDENTIFIER ::= { id-ce 19 } 218 * 219 * BasicConstraints ::= SEQUENCE { 220 * cA BOOLEAN DEFAULT FALSE, 221 * pathLenConstraint INTEGER (0..MAX) OPTIONAL 222 * } 223 * </pre> 224 * (as specified in RFC 3280) 225 * 226 * @return-1 if the Basic Constraints Extension is not present or 227 * it is present but it indicates the certificate is not a 228 * certificate authority. If the certificate is a certificate 229 * authority, returns the path length constraint if present, or 230 * Integer.MAX_VALUE if it is not. 231 */ 232 public int valueOfBasicConstraints() { 233 Extension extension = getExtensionByOID("2.5.29.19"); 234 if (extension == null) { 235 return -1; 236 } 237 BasicConstraints bc = extension.getBasicConstraintsValue(); 238 if (bc == null || !bc.getCa()) { 239 return -1; 240 } 241 return bc.getPathLenConstraint(); 242 } 243 244 /** 245 * Returns the value of Subject Alternative Name (OID = 2.5.29.17). 246 * The ASN.1 definition for Subject Alternative Name is: 247 * 248 * <pre> 249 * id-ce-subjectAltName OBJECT IDENTIFIER ::= { id-ce 17 } 250 * 251 * SubjectAltName ::= GeneralNames 252 * </pre> 253 * (as specified in RFC 3280) 254 * 255 * @return Returns the collection of pairs: 256 * (Integer (tag), Object (name value)) if extension presents, and 257 * null if does not. 258 */ 259 public Collection<List<?>> valueOfSubjectAlternativeName() throws IOException { 260 return decodeGeneralNames(getExtensionByOID("2.5.29.17")); 261 } 262 263 /** 264 * Returns the value of Issuer Alternative Name Extension (OID = 2.5.29.18). 265 * The ASN.1 definition for Issuer Alternative Name is: 266 * 267 * <pre> 268 * id-ce-issuerAltName OBJECT IDENTIFIER ::= { id-ce 18 } 269 * 270 * IssuerAltName ::= GeneralNames 271 * </pre> 272 * (as specified in RFC 3280) 273 * 274 * @return Returns the collection of pairs: 275 * (Integer (tag), Object (name value)) if extension presents, and 276 * null if does not. 277 */ 278 public Collection<List<?>> valueOfIssuerAlternativeName() throws IOException { 279 return decodeGeneralNames(getExtensionByOID("2.5.29.18")); 280 } 281 282 /** 283 * Given an X.509 extension that encodes GeneralNames, return it in the 284 * format expected by APIs. 285 */ 286 private static Collection<List<?>> decodeGeneralNames(Extension extension) 287 throws IOException { 288 if (extension == null) { 289 return null; 290 } 291 292 Collection<List<?>> collection = ((GeneralNames) GeneralNames.ASN1.decode(extension 293 .getExtnValue())).getPairsList(); 294 295 /* 296 * If the extension had any invalid entries, we may have an empty 297 * collection at this point, so just return null. 298 */ 299 if (collection.size() == 0) { 300 return null; 301 } 302 303 return Collections.unmodifiableCollection(collection); 304 } 305 306 /** 307 * Returns the value of Certificate Issuer Extension (OID = 2.5.29.29). 308 * It is a CRL entry extension and contains the GeneralNames describing 309 * the issuer of revoked certificate. Its ASN.1 notation is as follows: 310 * <pre> 311 * id-ce-certificateIssuer OBJECT IDENTIFIER ::= { id-ce 29 } 312 * 313 * certificateIssuer ::= GeneralNames 314 * </pre> 315 * (as specified in RFC 3280) 316 * 317 * @return the value of Certificate Issuer Extension 318 */ 319 public X500Principal valueOfCertificateIssuerExtension() throws IOException { 320 Extension extension = getExtensionByOID("2.5.29.29"); 321 if (extension == null) { 322 return null; 323 } 324 return ((CertificateIssuer) extension.getDecodedExtensionValue()).getIssuer(); 325 } 326 327 /** 328 * Returns ASN.1 encoded form of this X.509 Extensions value. 329 */ 330 public byte[] getEncoded() { 331 if (encoding == null) { 332 encoding = ASN1.encode(this); 333 } 334 return encoding; 335 } 336 337 @Override public boolean equals(Object other) { 338 if (!(other instanceof Extensions)) { 339 return false; 340 } 341 Extensions that = (Extensions) other; 342 return (this.extensions == null || this.extensions.isEmpty()) 343 ? (that.extensions == null || that.extensions.isEmpty()) 344 : (this.extensions.equals(that.extensions)); 345 } 346 347 @Override public int hashCode() { 348 int hashCode = 0; 349 if (extensions != null) { 350 hashCode = extensions.hashCode(); 351 } 352 return hashCode; 353 } 354 355 public void dumpValue(StringBuilder sb, String prefix) { 356 if (extensions == null) { 357 return; 358 } 359 int num = 1; 360 for (Extension extension: extensions) { 361 sb.append('\n').append(prefix).append('[').append(num++).append("]: "); 362 extension.dumpValue(sb, prefix); 363 } 364 } 365 366 /** 367 * Custom X.509 Extensions decoder. 368 */ 369 public static final ASN1Type ASN1 = new ASN1SequenceOf(Extension.ASN1) { 370 @Override public Object getDecodedObject(BerInputStream in) { 371 return new Extensions((List<Extension>) in.content); 372 } 373 374 @Override public Collection getValues(Object object) { 375 Extensions extensions = (Extensions) object; 376 return (extensions.extensions == null) ? new ArrayList() : extensions.extensions; 377 } 378 }; 379 } 380