1 /* 2 * Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.security.provider.certpath; 27 28 import java.io.ByteArrayInputStream; 29 import java.io.ByteArrayOutputStream; 30 import java.io.IOException; 31 import java.io.InputStream; 32 import java.security.cert.CertificateEncodingException; 33 import java.security.cert.Certificate; 34 import java.security.cert.CertificateException; 35 import java.security.cert.CertificateFactory; 36 import java.security.cert.CertPath; 37 import java.security.cert.X509Certificate; 38 import java.util.*; 39 40 import sun.security.pkcs.ContentInfo; 41 import sun.security.pkcs.PKCS7; 42 import sun.security.pkcs.SignerInfo; 43 import sun.security.x509.AlgorithmId; 44 import sun.security.util.DerValue; 45 import sun.security.util.DerOutputStream; 46 import sun.security.util.DerInputStream; 47 48 /** 49 * A {@link java.security.cert.CertPath CertPath} (certification path) 50 * consisting exclusively of 51 * {@link java.security.cert.X509Certificate X509Certificate}s. 52 * <p> 53 * By convention, X.509 <code>CertPath</code>s are stored from target 54 * to trust anchor. 55 * That is, the issuer of one certificate is the subject of the following 56 * one. However, unvalidated X.509 <code>CertPath</code>s may not follow 57 * this convention. PKIX <code>CertPathValidator</code>s will detect any 58 * departure from this convention and throw a 59 * <code>CertPathValidatorException</code>. 60 * 61 * @author Yassir Elley 62 * @since 1.4 63 */ 64 public class X509CertPath extends CertPath { 65 66 private static final long serialVersionUID = 4989800333263052980L; 67 68 /** 69 * List of certificates in this chain 70 */ 71 private List<X509Certificate> certs; 72 73 /** 74 * The names of our encodings. PkiPath is the default. 75 */ 76 private static final String COUNT_ENCODING = "count"; 77 private static final String PKCS7_ENCODING = "PKCS7"; 78 private static final String PKIPATH_ENCODING = "PkiPath"; 79 80 /** 81 * List of supported encodings 82 */ 83 private static final Collection<String> encodingList; 84 85 static { 86 List<String> list = new ArrayList<>(2); 87 list.add(PKIPATH_ENCODING); 88 list.add(PKCS7_ENCODING); 89 encodingList = Collections.unmodifiableCollection(list); 90 } 91 92 /** 93 * Creates an <code>X509CertPath</code> from a <code>List</code> of 94 * <code>X509Certificate</code>s. 95 * <p> 96 * The certificates are copied out of the supplied <code>List</code> 97 * object. 98 * 99 * @param certs a <code>List</code> of <code>X509Certificate</code>s 100 * @exception CertificateException if <code>certs</code> contains an element 101 * that is not an <code>X509Certificate</code> 102 */ 103 @SuppressWarnings("unchecked") X509CertPath(List<? extends Certificate> certs)104 public X509CertPath(List<? extends Certificate> certs) throws CertificateException { 105 super("X.509"); 106 107 // Ensure that the List contains only X509Certificates 108 // 109 // Note; The certs parameter is not necessarily to be of Certificate 110 // for some old code. For compatibility, to make sure the exception 111 // is CertificateException, rather than ClassCastException, please 112 // don't use 113 // for (Certificate obj : certs) 114 for (Object obj : certs) { 115 if (obj instanceof X509Certificate == false) { 116 throw new CertificateException 117 ("List is not all X509Certificates: " 118 + obj.getClass().getName()); 119 } 120 } 121 122 // Assumes that the resulting List is thread-safe. This is true 123 // because we ensure that it cannot be modified after construction 124 // and the methods in the Sun JDK 1.4 implementation of ArrayList that 125 // allow read-only access are thread-safe. 126 this.certs = Collections.unmodifiableList( 127 new ArrayList<X509Certificate>((List<X509Certificate>)certs)); 128 } 129 130 /** 131 * Creates an <code>X509CertPath</code>, reading the encoded form 132 * from an <code>InputStream</code>. The data is assumed to be in 133 * the default encoding. 134 * 135 * @param is the <code>InputStream</code> to read the data from 136 * @exception CertificateException if an exception occurs while decoding 137 */ X509CertPath(InputStream is)138 public X509CertPath(InputStream is) throws CertificateException { 139 this(is, PKIPATH_ENCODING); 140 } 141 142 /** 143 * Creates an <code>X509CertPath</code>, reading the encoded form 144 * from an InputStream. The data is assumed to be in the specified 145 * encoding. 146 * 147 * @param is the <code>InputStream</code> to read the data from 148 * @param encoding the encoding used 149 * @exception CertificateException if an exception occurs while decoding or 150 * the encoding requested is not supported 151 */ X509CertPath(InputStream is, String encoding)152 public X509CertPath(InputStream is, String encoding) 153 throws CertificateException { 154 super("X.509"); 155 156 switch (encoding) { 157 case PKIPATH_ENCODING: 158 certs = parsePKIPATH(is); 159 break; 160 case PKCS7_ENCODING: 161 certs = parsePKCS7(is); 162 break; 163 default: 164 throw new CertificateException("unsupported encoding"); 165 } 166 } 167 168 /** 169 * Parse a PKIPATH format CertPath from an InputStream. Return an 170 * unmodifiable List of the certificates. 171 * 172 * @param is the <code>InputStream</code> to read the data from 173 * @return an unmodifiable List of the certificates 174 * @exception CertificateException if an exception occurs 175 */ parsePKIPATH(InputStream is)176 private static List<X509Certificate> parsePKIPATH(InputStream is) 177 throws CertificateException { 178 List<X509Certificate> certList = null; 179 CertificateFactory certFac = null; 180 181 if (is == null) { 182 throw new CertificateException("input stream is null"); 183 } 184 185 try { 186 DerInputStream dis = new DerInputStream(readAllBytes(is)); 187 DerValue[] seq = dis.getSequence(3); 188 if (seq.length == 0) { 189 return Collections.<X509Certificate>emptyList(); 190 } 191 192 certFac = CertificateFactory.getInstance("X.509"); 193 certList = new ArrayList<X509Certificate>(seq.length); 194 195 // append certs in reverse order (target to trust anchor) 196 for (int i = seq.length-1; i >= 0; i--) { 197 certList.add((X509Certificate)certFac.generateCertificate 198 (new ByteArrayInputStream(seq[i].toByteArray()))); 199 } 200 201 return Collections.unmodifiableList(certList); 202 203 } catch (IOException ioe) { 204 throw new CertificateException("IOException parsing PkiPath data: " 205 + ioe, ioe); 206 } 207 } 208 209 /** 210 * Parse a PKCS#7 format CertPath from an InputStream. Return an 211 * unmodifiable List of the certificates. 212 * 213 * @param is the <code>InputStream</code> to read the data from 214 * @return an unmodifiable List of the certificates 215 * @exception CertificateException if an exception occurs 216 */ parsePKCS7(InputStream is)217 private static List<X509Certificate> parsePKCS7(InputStream is) 218 throws CertificateException { 219 List<X509Certificate> certList; 220 221 if (is == null) { 222 throw new CertificateException("input stream is null"); 223 } 224 225 try { 226 if (is.markSupported() == false) { 227 // Copy the entire input stream into an InputStream that does 228 // support mark 229 is = new ByteArrayInputStream(readAllBytes(is)); 230 } 231 PKCS7 pkcs7 = new PKCS7(is); 232 233 X509Certificate[] certArray = pkcs7.getCertificates(); 234 // certs are optional in PKCS #7 235 if (certArray != null) { 236 certList = Arrays.asList(certArray); 237 } else { 238 // no certs provided 239 certList = new ArrayList<X509Certificate>(0); 240 } 241 } catch (IOException ioe) { 242 throw new CertificateException("IOException parsing PKCS7 data: " + 243 ioe); 244 } 245 // Assumes that the resulting List is thread-safe. This is true 246 // because we ensure that it cannot be modified after construction 247 // and the methods in the Sun JDK 1.4 implementation of ArrayList that 248 // allow read-only access are thread-safe. 249 return Collections.unmodifiableList(certList); 250 } 251 252 /* 253 * Reads the entire contents of an InputStream into a byte array. 254 * 255 * @param is the InputStream to read from 256 * @return the bytes read from the InputStream 257 */ readAllBytes(InputStream is)258 private static byte[] readAllBytes(InputStream is) throws IOException { 259 byte[] buffer = new byte[8192]; 260 ByteArrayOutputStream baos = new ByteArrayOutputStream(2048); 261 int n; 262 while ((n = is.read(buffer)) != -1) { 263 baos.write(buffer, 0, n); 264 } 265 return baos.toByteArray(); 266 } 267 268 /** 269 * Returns the encoded form of this certification path, using the 270 * default encoding. 271 * 272 * @return the encoded bytes 273 * @exception CertificateEncodingException if an encoding error occurs 274 */ 275 @Override getEncoded()276 public byte[] getEncoded() throws CertificateEncodingException { 277 // @@@ Should cache the encoded form 278 return encodePKIPATH(); 279 } 280 281 /** 282 * Encode the CertPath using PKIPATH format. 283 * 284 * @return a byte array containing the binary encoding of the PkiPath object 285 * @exception CertificateEncodingException if an exception occurs 286 */ encodePKIPATH()287 private byte[] encodePKIPATH() throws CertificateEncodingException { 288 289 ListIterator<X509Certificate> li = certs.listIterator(certs.size()); 290 try { 291 DerOutputStream bytes = new DerOutputStream(); 292 // encode certs in reverse order (trust anchor to target) 293 // according to PkiPath format 294 while (li.hasPrevious()) { 295 X509Certificate cert = li.previous(); 296 // check for duplicate cert 297 if (certs.lastIndexOf(cert) != certs.indexOf(cert)) { 298 throw new CertificateEncodingException 299 ("Duplicate Certificate"); 300 } 301 // get encoded certificates 302 byte[] encoded = cert.getEncoded(); 303 bytes.write(encoded); 304 } 305 306 // Wrap the data in a SEQUENCE 307 DerOutputStream derout = new DerOutputStream(); 308 derout.write(DerValue.tag_SequenceOf, bytes); 309 return derout.toByteArray(); 310 311 } catch (IOException ioe) { 312 throw new CertificateEncodingException("IOException encoding " + 313 "PkiPath data: " + ioe, ioe); 314 } 315 } 316 317 /** 318 * Encode the CertPath using PKCS#7 format. 319 * 320 * @return a byte array containing the binary encoding of the PKCS#7 object 321 * @exception CertificateEncodingException if an exception occurs 322 */ encodePKCS7()323 private byte[] encodePKCS7() throws CertificateEncodingException { 324 PKCS7 p7 = new PKCS7(new AlgorithmId[0], 325 new ContentInfo(ContentInfo.DATA_OID, null), 326 certs.toArray(new X509Certificate[certs.size()]), 327 new SignerInfo[0]); 328 DerOutputStream derout = new DerOutputStream(); 329 try { 330 p7.encodeSignedData(derout); 331 } catch (IOException ioe) { 332 throw new CertificateEncodingException(ioe.getMessage()); 333 } 334 return derout.toByteArray(); 335 } 336 337 /** 338 * Returns the encoded form of this certification path, using the 339 * specified encoding. 340 * 341 * @param encoding the name of the encoding to use 342 * @return the encoded bytes 343 * @exception CertificateEncodingException if an encoding error occurs or 344 * the encoding requested is not supported 345 */ 346 @Override getEncoded(String encoding)347 public byte[] getEncoded(String encoding) 348 throws CertificateEncodingException { 349 switch (encoding) { 350 case PKIPATH_ENCODING: 351 return encodePKIPATH(); 352 case PKCS7_ENCODING: 353 return encodePKCS7(); 354 default: 355 throw new CertificateEncodingException("unsupported encoding"); 356 } 357 } 358 359 /** 360 * Returns the encodings supported by this certification path, with the 361 * default encoding first. 362 * 363 * @return an <code>Iterator</code> over the names of the supported 364 * encodings (as Strings) 365 */ getEncodingsStatic()366 public static Iterator<String> getEncodingsStatic() { 367 return encodingList.iterator(); 368 } 369 370 /** 371 * Returns an iteration of the encodings supported by this certification 372 * path, with the default encoding first. 373 * <p> 374 * Attempts to modify the returned <code>Iterator</code> via its 375 * <code>remove</code> method result in an 376 * <code>UnsupportedOperationException</code>. 377 * 378 * @return an <code>Iterator</code> over the names of the supported 379 * encodings (as Strings) 380 */ 381 @Override getEncodings()382 public Iterator<String> getEncodings() { 383 return getEncodingsStatic(); 384 } 385 386 /** 387 * Returns the list of certificates in this certification path. 388 * The <code>List</code> returned must be immutable and thread-safe. 389 * 390 * @return an immutable <code>List</code> of <code>X509Certificate</code>s 391 * (may be empty, but not null) 392 */ 393 @Override getCertificates()394 public List<X509Certificate> getCertificates() { 395 return certs; 396 } 397 } 398