1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.apache.harmony.xnet.provider.jsse; 18 19 import java.io.IOException; 20 import java.io.InputStream; 21 import java.io.PushbackInputStream; 22 import java.security.cert.CRL; 23 import java.security.cert.CRLException; 24 import java.security.cert.CertPath; 25 import java.security.cert.Certificate; 26 import java.security.cert.CertificateException; 27 import java.security.cert.CertificateFactorySpi; 28 import java.security.cert.X509Certificate; 29 import java.util.ArrayList; 30 import java.util.Arrays; 31 import java.util.Collection; 32 import java.util.Collections; 33 import java.util.Iterator; 34 import java.util.List; 35 36 public class OpenSSLX509CertificateFactory extends CertificateFactorySpi { 37 private static final byte[] PKCS7_MARKER = "-----BEGIN PKCS7".getBytes(); 38 39 private static final int PUSHBACK_SIZE = 64; 40 41 static class ParsingException extends Exception { 42 private static final long serialVersionUID = 8390802697728301325L; 43 44 public ParsingException(String message) { 45 super(message); 46 } 47 48 public ParsingException(Exception cause) { 49 super(cause); 50 } 51 52 public ParsingException(String message, Exception cause) { 53 super(message, cause); 54 } 55 } 56 57 /** 58 * The code for X509 Certificates and CRL is pretty much the same. We use 59 * this abstract class to share the code between them. This makes it ugly, 60 * but it's already written in this language anyway. 61 */ 62 private static abstract class Parser<T> { 63 public T generateItem(InputStream inStream) throws ParsingException { 64 if (inStream == null) { 65 throw new ParsingException("inStream == null"); 66 } 67 68 final boolean markable = inStream.markSupported(); 69 if (markable) { 70 inStream.mark(PKCS7_MARKER.length); 71 } 72 73 final PushbackInputStream pbis = new PushbackInputStream(inStream, PUSHBACK_SIZE); 74 try { 75 final byte[] buffer = new byte[PKCS7_MARKER.length]; 76 77 final int len = pbis.read(buffer); 78 if (len < 0) { 79 /* No need to reset here. The stream was empty or EOF. */ 80 throw new ParsingException("inStream is empty"); 81 } 82 pbis.unread(buffer, 0, len); 83 84 if (buffer[0] == '-') { 85 if (len == PKCS7_MARKER.length && Arrays.equals(PKCS7_MARKER, buffer)) { 86 List<? extends T> items = fromPkcs7PemInputStream(pbis); 87 if (items.size() == 0) { 88 return null; 89 } 90 items.get(0); 91 } else { 92 return fromX509PemInputStream(pbis); 93 } 94 } 95 96 /* PKCS#7 bags have a byte 0x06 at position 4 in the stream. */ 97 if (buffer[4] == 0x06) { 98 List<? extends T> certs = fromPkcs7DerInputStream(pbis); 99 if (certs.size() == 0) { 100 return null; 101 } 102 return certs.get(0); 103 } else { 104 return fromX509DerInputStream(pbis); 105 } 106 } catch (Exception e) { 107 if (markable) { 108 try { 109 inStream.reset(); 110 } catch (IOException ignored) { 111 } 112 } 113 throw new ParsingException(e); 114 } 115 } 116 117 public Collection<? extends T> generateItems(InputStream inStream) 118 throws ParsingException { 119 if (inStream == null) { 120 throw new ParsingException("inStream == null"); 121 } 122 try { 123 if (inStream.available() == 0) { 124 return Collections.emptyList(); 125 } 126 } catch (IOException e) { 127 throw new ParsingException("Problem reading input stream", e); 128 } 129 130 final boolean markable = inStream.markSupported(); 131 if (markable) { 132 inStream.mark(PUSHBACK_SIZE); 133 } 134 135 /* Attempt to see if this is a PKCS#7 bag. */ 136 final PushbackInputStream pbis = new PushbackInputStream(inStream, PUSHBACK_SIZE); 137 try { 138 final byte[] buffer = new byte[PKCS7_MARKER.length]; 139 140 final int len = pbis.read(buffer); 141 if (len < 0) { 142 /* No need to reset here. The stream was empty or EOF. */ 143 throw new ParsingException("inStream is empty"); 144 } 145 pbis.unread(buffer, 0, len); 146 147 if (len == PKCS7_MARKER.length && Arrays.equals(PKCS7_MARKER, buffer)) { 148 return fromPkcs7PemInputStream(pbis); 149 } 150 151 /* PKCS#7 bags have a byte 0x06 at position 4 in the stream. */ 152 if (buffer[4] == 0x06) { 153 return fromPkcs7DerInputStream(pbis); 154 } 155 } catch (Exception e) { 156 if (markable) { 157 try { 158 inStream.reset(); 159 } catch (IOException ignored) { 160 } 161 } 162 throw new ParsingException(e); 163 } 164 165 /* 166 * It wasn't, so just try to keep grabbing certificates until we 167 * can't anymore. 168 */ 169 final List<T> coll = new ArrayList<T>(); 170 T c = null; 171 do { 172 /* 173 * If this stream supports marking, try to mark here in case 174 * there is an error during certificate generation. 175 */ 176 if (markable) { 177 inStream.mark(PUSHBACK_SIZE); 178 } 179 180 try { 181 c = generateItem(pbis); 182 coll.add(c); 183 } catch (ParsingException e) { 184 /* 185 * If this stream supports marking, attempt to reset it to 186 * the mark before the failure. 187 */ 188 if (markable) { 189 try { 190 inStream.reset(); 191 } catch (IOException ignored) { 192 } 193 } 194 195 c = null; 196 } 197 } while (c != null); 198 199 return coll; 200 } 201 202 protected abstract T fromX509PemInputStream(InputStream pbis) throws ParsingException; 203 204 protected abstract T fromX509DerInputStream(InputStream pbis) throws ParsingException; 205 206 protected abstract List<? extends T> fromPkcs7PemInputStream(InputStream is) 207 throws ParsingException; 208 209 protected abstract List<? extends T> fromPkcs7DerInputStream(InputStream is) 210 throws ParsingException; 211 } 212 213 private Parser<OpenSSLX509Certificate> certificateParser = 214 new Parser<OpenSSLX509Certificate>() { 215 @Override 216 public OpenSSLX509Certificate fromX509PemInputStream(InputStream is) 217 throws ParsingException { 218 return OpenSSLX509Certificate.fromX509PemInputStream(is); 219 } 220 221 @Override 222 public OpenSSLX509Certificate fromX509DerInputStream(InputStream is) 223 throws ParsingException { 224 return OpenSSLX509Certificate.fromX509DerInputStream(is); 225 } 226 227 @Override 228 public List<? extends OpenSSLX509Certificate> 229 fromPkcs7PemInputStream(InputStream is) throws ParsingException { 230 return OpenSSLX509Certificate.fromPkcs7PemInputStream(is); 231 } 232 233 @Override 234 public List<? extends OpenSSLX509Certificate> 235 fromPkcs7DerInputStream(InputStream is) throws ParsingException { 236 return OpenSSLX509Certificate.fromPkcs7DerInputStream(is); 237 } 238 }; 239 240 private Parser<OpenSSLX509CRL> crlParser = 241 new Parser<OpenSSLX509CRL>() { 242 @Override 243 public OpenSSLX509CRL fromX509PemInputStream(InputStream is) 244 throws ParsingException { 245 return OpenSSLX509CRL.fromX509PemInputStream(is); 246 } 247 248 @Override 249 public OpenSSLX509CRL fromX509DerInputStream(InputStream is) 250 throws ParsingException { 251 return OpenSSLX509CRL.fromX509DerInputStream(is); 252 } 253 254 @Override 255 public List<? extends OpenSSLX509CRL> fromPkcs7PemInputStream(InputStream is) 256 throws ParsingException { 257 return OpenSSLX509CRL.fromPkcs7PemInputStream(is); 258 } 259 260 @Override 261 public List<? extends OpenSSLX509CRL> fromPkcs7DerInputStream(InputStream is) 262 throws ParsingException { 263 return OpenSSLX509CRL.fromPkcs7DerInputStream(is); 264 } 265 }; 266 267 @Override 268 public Certificate engineGenerateCertificate(InputStream inStream) throws CertificateException { 269 try { 270 return certificateParser.generateItem(inStream); 271 } catch (ParsingException e) { 272 throw new CertificateException(e); 273 } 274 } 275 276 @Override 277 public Collection<? extends Certificate> engineGenerateCertificates( 278 InputStream inStream) throws CertificateException { 279 try { 280 return certificateParser.generateItems(inStream); 281 } catch (ParsingException e) { 282 throw new CertificateException(e); 283 } 284 } 285 286 @Override 287 public CRL engineGenerateCRL(InputStream inStream) throws CRLException { 288 try { 289 return crlParser.generateItem(inStream); 290 } catch (ParsingException e) { 291 throw new CRLException(e); 292 } 293 } 294 295 @Override 296 public Collection<? extends CRL> engineGenerateCRLs(InputStream inStream) throws CRLException { 297 if (inStream == null) { 298 return Collections.emptyList(); 299 } 300 301 try { 302 return crlParser.generateItems(inStream); 303 } catch (ParsingException e) { 304 throw new CRLException(e); 305 } 306 } 307 308 @Override 309 public Iterator<String> engineGetCertPathEncodings() { 310 return OpenSSLX509CertPath.getEncodingsIterator(); 311 } 312 313 @Override 314 public CertPath engineGenerateCertPath(InputStream inStream) throws CertificateException { 315 return OpenSSLX509CertPath.fromEncoding(inStream); 316 } 317 318 @Override 319 public CertPath engineGenerateCertPath(InputStream inStream, String encoding) 320 throws CertificateException { 321 return OpenSSLX509CertPath.fromEncoding(inStream, encoding); 322 } 323 324 @Override 325 public CertPath engineGenerateCertPath(List<? extends Certificate> certificates) 326 throws CertificateException { 327 final List<X509Certificate> filtered = new ArrayList<X509Certificate>(certificates.size()); 328 for (int i = 0; i < certificates.size(); i++) { 329 final Certificate c = certificates.get(i); 330 331 if (!(c instanceof X509Certificate)) { 332 throw new CertificateException("Certificate not X.509 type at index " + i); 333 } 334 335 filtered.add((X509Certificate) c); 336 } 337 338 return new OpenSSLX509CertPath(filtered); 339 } 340 } 341