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