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