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