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
17package org.conscrypt;
18
19import java.io.IOException;
20import java.io.InputStream;
21import java.io.PushbackInputStream;
22import java.security.cert.CRL;
23import java.security.cert.CRLException;
24import java.security.cert.CertPath;
25import java.security.cert.Certificate;
26import java.security.cert.CertificateException;
27import java.security.cert.CertificateFactorySpi;
28import java.security.cert.X509Certificate;
29import java.util.ArrayList;
30import java.util.Arrays;
31import java.util.Collection;
32import java.util.Collections;
33import java.util.Iterator;
34import java.util.List;
35
36public 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