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.provider.cert; 24 25import java.io.IOException; 26import java.io.InputStream; 27import java.security.cert.CertPath; 28import java.security.cert.CertificateEncodingException; 29import java.security.cert.CertificateException; 30import java.security.cert.X509Certificate; 31import java.util.ArrayList; 32import java.util.Arrays; 33import java.util.Collection; 34import java.util.Collections; 35import java.util.Iterator; 36import java.util.List; 37 38import org.apache.harmony.security.asn1.ASN1Any; 39import org.apache.harmony.security.asn1.ASN1Explicit; 40import org.apache.harmony.security.asn1.ASN1Implicit; 41import org.apache.harmony.security.asn1.ASN1Oid; 42import org.apache.harmony.security.asn1.ASN1Sequence; 43import org.apache.harmony.security.asn1.ASN1SequenceOf; 44import org.apache.harmony.security.asn1.ASN1Type; 45import org.apache.harmony.security.asn1.BerInputStream; 46import org.apache.harmony.security.pkcs7.ContentInfo; 47import org.apache.harmony.security.pkcs7.SignedData; 48import org.apache.harmony.security.x509.Certificate; 49 50/** 51 * This class is an implementation of X.509 CertPath. This implementation 52 * provides ability to create the instance of X.509 Certification Path 53 * by several means:<br> 54 * 55 * 1. It can be created over the list of X.509 certificates 56 * (implementations of X509Certificate class) provided in constructor.<br> 57 * 58 * 2. It can be created by means of <code>getInstance</code> methods 59 * on the base of the following ASN.1 DER encoded forms:<br> 60 * 61 * - PkiPath as defined in 62 * ITU-T Recommendation X.509(2000) Corrigendum 1(2001) 63 * (can be seen at 64 * ftp://ftp.bull.com/pub/OSIdirectory/DefectResolution/TechnicalCorrigenda/ApprovedTechnicalCorrigendaToX.509/8%7CX.509-TC1(4th).pdf) 65 * <br> 66 * - PKCS #7 SignedData object provided in the form of 67 * ContentInfo structure. CertPath object is generated on the base of 68 * certificates presented in <code>certificates</code> field of the SignedData 69 * object which in its turn is retrieved from ContentInfo structure. 70 * (see http://www.ietf.org/rfc/rfc2315.txt 71 * for more info on PKCS #7) 72 * <br> 73 * 74 */ 75public class X509CertPathImpl extends CertPath { 76 /** 77 * @serial 78 */ 79 private static final long serialVersionUID = 7989755106209515436L; 80 81 /** 82 * Supported encoding types for CerthPath. Used by the various APIs that 83 * encode this into bytes such as {@link #getEncoded()}. 84 */ 85 private enum Encoding { 86 PKI_PATH("PkiPath"), 87 PKCS7("PKCS7"); 88 89 private final String apiName; 90 91 Encoding(String apiName) { 92 this.apiName = apiName; 93 } 94 95 static Encoding findByApiName(String apiName) throws CertificateEncodingException { 96 for (Encoding element : values()) { 97 if (element.apiName.equals(apiName)) { 98 return element; 99 } 100 } 101 102 return null; 103 } 104 } 105 106 /** Unmodifiable list of encodings for the API. */ 107 static final List<String> encodings = Collections.unmodifiableList(Arrays.asList(new String[] { 108 Encoding.PKI_PATH.apiName, 109 Encoding.PKCS7.apiName, 110 })); 111 112 /** The list of certificates in the order of target toward trust anchor. */ 113 private final List<X509Certificate> certificates; 114 115 /** PkiPath encoding of the certification path. */ 116 private byte[] pkiPathEncoding; 117 118 /** PKCS7 encoding of the certification path. */ 119 private byte[] pkcs7Encoding; 120 121 /** 122 * Creates an instance of X.509 CertPath over the specified list of 123 * certificates. 124 * 125 * @throws CertificateException if some of the object in the list is not an 126 * instance of subclass of X509Certificate. 127 */ 128 public X509CertPathImpl(List<? extends java.security.cert.Certificate> certs) 129 throws CertificateException { 130 super("X.509"); 131 132 final int size = certs.size(); 133 certificates = new ArrayList<X509Certificate>(size); 134 135 for (int i = 0; i < size; i++) { 136 final java.security.cert.Certificate cert = certs.get(i); 137 if (!(cert instanceof X509Certificate)) { 138 throw new CertificateException("Certificate " + i + " is not an X.509 certificate"); 139 } 140 141 certificates.add((X509Certificate) cert); 142 } 143 } 144 145 /** 146 * Creates an X.509 CertPath over the specified {@code certs}. The 147 * {@code certs} should be sorted correctly when calling into the 148 * constructor. Additionally, the {@code encodedPath} should match the 149 * expected output for the {@code type} of encoding. 150 */ 151 private X509CertPathImpl(List<X509Certificate> certs, Encoding type) { 152 super("X.509"); 153 154 certificates = certs; 155 } 156 157 /** 158 * Extract a CertPath from a PKCS#7 {@code contentInfo} object. 159 */ 160 private static X509CertPathImpl getCertPathFromContentInfo(ContentInfo contentInfo) 161 throws CertificateException { 162 final SignedData sd = contentInfo.getSignedData(); 163 if (sd == null) { 164 throw new CertificateException("Incorrect PKCS7 encoded form: missing signed data"); 165 } 166 167 List<Certificate> certs = sd.getCertificates(); 168 if (certs == null) { 169 certs = Collections.emptyList(); 170 } 171 172 final List<X509Certificate> result = new ArrayList<X509Certificate>(certs.size()); 173 for (Certificate cert : certs) { 174 result.add(new X509CertImpl(cert)); 175 } 176 177 return new X509CertPathImpl(result, Encoding.PKCS7); 178 } 179 180 /** 181 * Generates certification path object on the base of PkiPath encoded form 182 * provided via input stream. 183 * 184 * @throws CertificateException if some problems occurred during the 185 * decoding. 186 */ 187 public static X509CertPathImpl getInstance(InputStream in) throws CertificateException { 188 try { 189 return (X509CertPathImpl) ASN1.decode(in); 190 } catch (IOException e) { 191 throw new CertificateException("Failed to decode CertPath", e); 192 } 193 } 194 195 /** 196 * Generates certification path object on the basis of encoding provided via 197 * input stream. The format of provided encoded form is specified by 198 * parameter <code>encoding</code>. 199 * 200 * @throws CertificateException if specified encoding form is not supported, 201 * or some problems occurred during the decoding. 202 */ 203 public static X509CertPathImpl getInstance(InputStream in, String encoding) 204 throws CertificateException { 205 try { 206 final Encoding encType = Encoding.findByApiName(encoding); 207 if (encType == null) { 208 throw new CertificateException("Unsupported encoding: " + encoding); 209 } 210 211 switch (encType) { 212 case PKI_PATH: 213 return (X509CertPathImpl) ASN1.decode(in); 214 case PKCS7: 215 return getCertPathFromContentInfo((ContentInfo) ContentInfo.ASN1.decode(in)); 216 default: 217 throw new CertificateException("Unsupported encoding: " + encoding); 218 } 219 } catch (IOException e) { 220 throw new CertificateException("Failed to decode CertPath", e); 221 } 222 } 223 224 /** 225 * Generates certification path object on the base of PkiPath 226 * encoded form provided via array of bytes. 227 * @throws CertificateException if some problems occurred during 228 * the decoding. 229 */ 230 public static X509CertPathImpl getInstance(byte[] in) throws CertificateException { 231 try { 232 return (X509CertPathImpl) ASN1.decode(in); 233 } catch (IOException e) { 234 throw new CertificateException("Failed to decode CertPath", e); 235 } 236 } 237 238 /** 239 * Generates certification path object on the base of encoding provided via 240 * array of bytes. The format of provided encoded form is specified by 241 * parameter {@code encoding}. 242 * 243 * @throws CertificateException if specified encoding form is not supported, 244 * or some problems occurred during the decoding. 245 */ 246 public static X509CertPathImpl getInstance(byte[] in, String encoding) 247 throws CertificateException { 248 try { 249 final Encoding encType = Encoding.findByApiName(encoding); 250 if (encType == null) { 251 throw new CertificateException("Unsupported encoding: " + encoding); 252 } 253 254 switch (encType) { 255 case PKI_PATH: 256 return (X509CertPathImpl) ASN1.decode(in); 257 case PKCS7: 258 return getCertPathFromContentInfo((ContentInfo) ContentInfo.ASN1.decode(in)); 259 default: 260 throw new CertificateException("Unsupported encoding: " + encoding); 261 } 262 } catch (IOException e) { 263 throw new CertificateException("Failed to decode CertPath", e); 264 } 265 } 266 267 // --------------------------------------------------------------------- 268 // ---- java.security.cert.CertPath abstract method implementations ---- 269 // --------------------------------------------------------------------- 270 271 /** 272 * @see java.security.cert.CertPath#getCertificates() 273 * method documentation for more info 274 */ 275 @Override 276 public List<X509Certificate> getCertificates() { 277 return Collections.unmodifiableList(certificates); 278 } 279 280 /** 281 * Returns in PkiPath format which is our default encoding. 282 * 283 * @see java.security.cert.CertPath#getEncoded() 284 */ 285 @Override 286 public byte[] getEncoded() throws CertificateEncodingException { 287 return getEncoded(Encoding.PKI_PATH); 288 } 289 290 /** 291 * @see #getEncoded(String) 292 */ 293 private byte[] getEncoded(Encoding encoding) throws CertificateEncodingException { 294 switch (encoding) { 295 case PKI_PATH: 296 if (pkiPathEncoding == null) { 297 pkiPathEncoding = ASN1.encode(this); 298 } 299 300 return pkiPathEncoding.clone(); 301 case PKCS7: 302 if (pkcs7Encoding == null) { 303 pkcs7Encoding = PKCS7_SIGNED_DATA_OBJECT.encode(this); 304 } 305 306 return pkcs7Encoding.clone(); 307 default: 308 throw new CertificateEncodingException("Unsupported encoding: " + encoding); 309 } 310 } 311 312 /** 313 * @see java.security.cert.CertPath#getEncoded(String) 314 */ 315 @Override 316 public byte[] getEncoded(String encoding) throws CertificateEncodingException { 317 final Encoding encType = Encoding.findByApiName(encoding); 318 if (encType == null) { 319 throw new CertificateEncodingException("Unsupported encoding: " + encoding); 320 } 321 322 return getEncoded(encType); 323 } 324 325 /** 326 * @see java.security.cert.CertPath#getEncodings() 327 * method documentation for more info 328 */ 329 @Override 330 public Iterator<String> getEncodings() { 331 return encodings.iterator(); 332 } 333 334 /** 335 * ASN.1 DER Encoder/Decoder for PkiPath structure. 336 */ 337 public static final ASN1SequenceOf ASN1 = new ASN1SequenceOf(ASN1Any.getInstance()) { 338 /** 339 * Builds the instance of X509CertPathImpl on the base of the list of 340 * ASN.1 encodings of X.509 certificates provided via PkiPath structure. 341 * This method participates in decoding process. 342 */ 343 public Object getDecodedObject(BerInputStream in) throws IOException { 344 // retrieve the decoded content 345 final List<byte[]> encodedCerts = (List<byte[]>) in.content; 346 347 final int size = encodedCerts.size(); 348 final List<X509Certificate> certificates = new ArrayList<X509Certificate>(size); 349 350 for (int i = size - 1; i >= 0; i--) { 351 // create the X.509 certificate on the base of its encoded form 352 // and add it to the list. 353 certificates.add(new X509CertImpl((Certificate) Certificate.ASN1 354 .decode(encodedCerts.get(i)))); 355 } 356 357 // create and return the resulting object 358 return new X509CertPathImpl(certificates, Encoding.PKI_PATH); 359 } 360 361 /** 362 * Returns the Collection of the encoded form of certificates contained 363 * in the X509CertPathImpl object to be encoded. 364 * This method participates in encoding process. 365 */ 366 public Collection<byte[]> getValues(Object object) { 367 // object to be encoded 368 final X509CertPathImpl cp = (X509CertPathImpl) object; 369 370 // if it has no certificates in it - create the sequence of size 0 371 if (cp.certificates == null) { 372 return Collections.emptyList(); 373 } 374 375 final int size = cp.certificates.size(); 376 final List<byte[]> encodings = new ArrayList<byte[]>(size); 377 378 try { 379 for (int i = size - 1; i >= 0; i--) { 380 // get the encoded form of certificate and place it into the 381 // list to be encoded in PkiPath format 382 encodings.add(cp.certificates.get(i).getEncoded()); 383 } 384 } catch (CertificateEncodingException e) { 385 throw new IllegalArgumentException("Encoding error occurred", e); 386 } 387 388 return encodings; 389 } 390 }; 391 392 393 /** 394 * Encoder for PKCS#7 SignedData. It is assumed that only certificate field 395 * is important all other fields contain pre-calculated encodings. 396 */ 397 private static final ASN1Sequence ASN1_SIGNED_DATA = new ASN1Sequence( 398 new ASN1Type[] { 399 // version ,digestAlgorithms, content info 400 ASN1Any.getInstance(), 401 // certificates 402 new ASN1Implicit(0, ASN1), 403 // set of crls is optional and is missed here 404 ASN1Any.getInstance(),// signers info 405 }) { 406 407 // precalculated ASN.1 encodings for 408 // version ,digestAlgorithms, content info field of SignedData 409 private final byte[] PRECALCULATED_HEAD = new byte[] { 0x02, 0x01, 410 0x01,// version (v1) 411 0x31, 0x00,// empty set of DigestAlgorithms 412 0x30, 0x03, 0x06, 0x01, 0x00 // empty ContentInfo with oid=0 413 }; 414 415 // precalculated empty set of SignerInfos 416 private final byte[] SIGNERS_INFO = new byte[] { 0x31, 0x00 }; 417 418 protected void getValues(Object object, Object[] values) { 419 values[0] = PRECALCULATED_HEAD; 420 values[1] = object; // pass X509CertPathImpl object 421 values[2] = SIGNERS_INFO; 422 } 423 424 // stub to prevent using the instance as decoder 425 public Object decode(BerInputStream in) throws IOException { 426 throw new RuntimeException( 427 "Invalid use of encoder for PKCS#7 SignedData object"); 428 } 429 }; 430 431 private static final ASN1Sequence PKCS7_SIGNED_DATA_OBJECT = new ASN1Sequence( 432 new ASN1Type[] { ASN1Any.getInstance(), // contentType 433 new ASN1Explicit(0, ASN1_SIGNED_DATA) // SignedData 434 }) { 435 436 // precalculated ASN.1 encoding for SignedData object oid 437 private final byte[] SIGNED_DATA_OID = ASN1Oid.getInstance().encode( 438 ContentInfo.SIGNED_DATA); 439 440 protected void getValues(Object object, Object[] values) { 441 values[0] = SIGNED_DATA_OID; 442 values[1] = object; // pass X509CertPathImpl object 443 } 444 445 // stub to prevent using the instance as decoder 446 public Object decode(BerInputStream in) throws IOException { 447 throw new RuntimeException( 448 "Invalid use of encoder for PKCS#7 SignedData object"); 449 } 450 }; 451} 452