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