| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.conscrypt; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.PushbackInputStream; |
| import java.security.cert.CRL; |
| import java.security.cert.CRLException; |
| import java.security.cert.CertPath; |
| import java.security.cert.Certificate; |
| import java.security.cert.CertificateException; |
| import java.security.cert.CertificateFactorySpi; |
| import java.security.cert.X509Certificate; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| public class OpenSSLX509CertificateFactory extends CertificateFactorySpi { |
| private static final byte[] PKCS7_MARKER = new byte[] { |
| '-', '-', '-', '-', '-', 'B', 'E', 'G', 'I', 'N', ' ', 'P', 'K', 'C', 'S', '7' |
| }; |
| |
| private static final int PUSHBACK_SIZE = 64; |
| |
| static class ParsingException extends Exception { |
| private static final long serialVersionUID = 8390802697728301325L; |
| |
| public ParsingException(String message) { |
| super(message); |
| } |
| |
| public ParsingException(Exception cause) { |
| super(cause); |
| } |
| |
| public ParsingException(String message, Exception cause) { |
| super(message, cause); |
| } |
| } |
| |
| /** |
| * The code for X509 Certificates and CRL is pretty much the same. We use |
| * this abstract class to share the code between them. This makes it ugly, |
| * but it's already written in this language anyway. |
| */ |
| private static abstract class Parser<T> { |
| public T generateItem(InputStream inStream) throws ParsingException { |
| if (inStream == null) { |
| throw new ParsingException("inStream == null"); |
| } |
| |
| final boolean markable = inStream.markSupported(); |
| if (markable) { |
| inStream.mark(PKCS7_MARKER.length); |
| } |
| |
| final PushbackInputStream pbis = new PushbackInputStream(inStream, PUSHBACK_SIZE); |
| try { |
| final byte[] buffer = new byte[PKCS7_MARKER.length]; |
| |
| final int len = pbis.read(buffer); |
| if (len < 0) { |
| /* No need to reset here. The stream was empty or EOF. */ |
| throw new ParsingException("inStream is empty"); |
| } |
| pbis.unread(buffer, 0, len); |
| |
| if (buffer[0] == '-') { |
| if (len == PKCS7_MARKER.length && Arrays.equals(PKCS7_MARKER, buffer)) { |
| List<? extends T> items = fromPkcs7PemInputStream(pbis); |
| if (items.size() == 0) { |
| return null; |
| } |
| items.get(0); |
| } else { |
| return fromX509PemInputStream(pbis); |
| } |
| } |
| |
| /* PKCS#7 bags have a byte 0x06 at position 4 in the stream. */ |
| if (buffer[4] == 0x06) { |
| List<? extends T> certs = fromPkcs7DerInputStream(pbis); |
| if (certs.size() == 0) { |
| return null; |
| } |
| return certs.get(0); |
| } else { |
| return fromX509DerInputStream(pbis); |
| } |
| } catch (Exception e) { |
| if (markable) { |
| try { |
| inStream.reset(); |
| } catch (IOException ignored) { |
| } |
| } |
| throw new ParsingException(e); |
| } |
| } |
| |
| public Collection<? extends T> generateItems(InputStream inStream) |
| throws ParsingException { |
| if (inStream == null) { |
| throw new ParsingException("inStream == null"); |
| } |
| try { |
| if (inStream.available() == 0) { |
| return Collections.emptyList(); |
| } |
| } catch (IOException e) { |
| throw new ParsingException("Problem reading input stream", e); |
| } |
| |
| final boolean markable = inStream.markSupported(); |
| if (markable) { |
| inStream.mark(PUSHBACK_SIZE); |
| } |
| |
| /* Attempt to see if this is a PKCS#7 bag. */ |
| final PushbackInputStream pbis = new PushbackInputStream(inStream, PUSHBACK_SIZE); |
| try { |
| final byte[] buffer = new byte[PKCS7_MARKER.length]; |
| |
| final int len = pbis.read(buffer); |
| if (len < 0) { |
| /* No need to reset here. The stream was empty or EOF. */ |
| throw new ParsingException("inStream is empty"); |
| } |
| pbis.unread(buffer, 0, len); |
| |
| if (len == PKCS7_MARKER.length && Arrays.equals(PKCS7_MARKER, buffer)) { |
| return fromPkcs7PemInputStream(pbis); |
| } |
| |
| /* PKCS#7 bags have a byte 0x06 at position 4 in the stream. */ |
| if (buffer[4] == 0x06) { |
| return fromPkcs7DerInputStream(pbis); |
| } |
| } catch (Exception e) { |
| if (markable) { |
| try { |
| inStream.reset(); |
| } catch (IOException ignored) { |
| } |
| } |
| throw new ParsingException(e); |
| } |
| |
| /* |
| * It wasn't, so just try to keep grabbing certificates until we |
| * can't anymore. |
| */ |
| final List<T> coll = new ArrayList<T>(); |
| T c = null; |
| do { |
| /* |
| * If this stream supports marking, try to mark here in case |
| * there is an error during certificate generation. |
| */ |
| if (markable) { |
| inStream.mark(PUSHBACK_SIZE); |
| } |
| |
| try { |
| c = generateItem(pbis); |
| coll.add(c); |
| } catch (ParsingException e) { |
| /* |
| * If this stream supports marking, attempt to reset it to |
| * the mark before the failure. |
| */ |
| if (markable) { |
| try { |
| inStream.reset(); |
| } catch (IOException ignored) { |
| } |
| } |
| |
| c = null; |
| } |
| } while (c != null); |
| |
| return coll; |
| } |
| |
| protected abstract T fromX509PemInputStream(InputStream pbis) throws ParsingException; |
| |
| protected abstract T fromX509DerInputStream(InputStream pbis) throws ParsingException; |
| |
| protected abstract List<? extends T> fromPkcs7PemInputStream(InputStream is) |
| throws ParsingException; |
| |
| protected abstract List<? extends T> fromPkcs7DerInputStream(InputStream is) |
| throws ParsingException; |
| } |
| |
| private Parser<OpenSSLX509Certificate> certificateParser = |
| new Parser<OpenSSLX509Certificate>() { |
| @Override |
| public OpenSSLX509Certificate fromX509PemInputStream(InputStream is) |
| throws ParsingException { |
| return OpenSSLX509Certificate.fromX509PemInputStream(is); |
| } |
| |
| @Override |
| public OpenSSLX509Certificate fromX509DerInputStream(InputStream is) |
| throws ParsingException { |
| return OpenSSLX509Certificate.fromX509DerInputStream(is); |
| } |
| |
| @Override |
| public List<? extends OpenSSLX509Certificate> |
| fromPkcs7PemInputStream(InputStream is) throws ParsingException { |
| return OpenSSLX509Certificate.fromPkcs7PemInputStream(is); |
| } |
| |
| @Override |
| public List<? extends OpenSSLX509Certificate> |
| fromPkcs7DerInputStream(InputStream is) throws ParsingException { |
| return OpenSSLX509Certificate.fromPkcs7DerInputStream(is); |
| } |
| }; |
| |
| private Parser<OpenSSLX509CRL> crlParser = |
| new Parser<OpenSSLX509CRL>() { |
| @Override |
| public OpenSSLX509CRL fromX509PemInputStream(InputStream is) |
| throws ParsingException { |
| return OpenSSLX509CRL.fromX509PemInputStream(is); |
| } |
| |
| @Override |
| public OpenSSLX509CRL fromX509DerInputStream(InputStream is) |
| throws ParsingException { |
| return OpenSSLX509CRL.fromX509DerInputStream(is); |
| } |
| |
| @Override |
| public List<? extends OpenSSLX509CRL> fromPkcs7PemInputStream(InputStream is) |
| throws ParsingException { |
| return OpenSSLX509CRL.fromPkcs7PemInputStream(is); |
| } |
| |
| @Override |
| public List<? extends OpenSSLX509CRL> fromPkcs7DerInputStream(InputStream is) |
| throws ParsingException { |
| return OpenSSLX509CRL.fromPkcs7DerInputStream(is); |
| } |
| }; |
| |
| @Override |
| public Certificate engineGenerateCertificate(InputStream inStream) throws CertificateException { |
| try { |
| return certificateParser.generateItem(inStream); |
| } catch (ParsingException e) { |
| throw new CertificateException(e); |
| } |
| } |
| |
| @Override |
| public Collection<? extends Certificate> engineGenerateCertificates( |
| InputStream inStream) throws CertificateException { |
| try { |
| return certificateParser.generateItems(inStream); |
| } catch (ParsingException e) { |
| throw new CertificateException(e); |
| } |
| } |
| |
| @Override |
| public CRL engineGenerateCRL(InputStream inStream) throws CRLException { |
| try { |
| return crlParser.generateItem(inStream); |
| } catch (ParsingException e) { |
| throw new CRLException(e); |
| } |
| } |
| |
| @Override |
| public Collection<? extends CRL> engineGenerateCRLs(InputStream inStream) throws CRLException { |
| if (inStream == null) { |
| return Collections.emptyList(); |
| } |
| |
| try { |
| return crlParser.generateItems(inStream); |
| } catch (ParsingException e) { |
| throw new CRLException(e); |
| } |
| } |
| |
| @Override |
| public Iterator<String> engineGetCertPathEncodings() { |
| return OpenSSLX509CertPath.getEncodingsIterator(); |
| } |
| |
| @Override |
| public CertPath engineGenerateCertPath(InputStream inStream) throws CertificateException { |
| return OpenSSLX509CertPath.fromEncoding(inStream); |
| } |
| |
| @Override |
| public CertPath engineGenerateCertPath(InputStream inStream, String encoding) |
| throws CertificateException { |
| return OpenSSLX509CertPath.fromEncoding(inStream, encoding); |
| } |
| |
| @Override |
| public CertPath engineGenerateCertPath(List<? extends Certificate> certificates) |
| throws CertificateException { |
| final List<X509Certificate> filtered = new ArrayList<X509Certificate>(certificates.size()); |
| for (int i = 0; i < certificates.size(); i++) { |
| final Certificate c = certificates.get(i); |
| |
| if (!(c instanceof X509Certificate)) { |
| throw new CertificateException("Certificate not X.509 type at index " + i); |
| } |
| |
| filtered.add((X509Certificate) c); |
| } |
| |
| return new OpenSSLX509CertPath(filtered); |
| } |
| } |