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