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