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