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