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 = 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
46        public ParsingException(String message) {
47            super(message);
48        }
49
50        public ParsingException(Exception cause) {
51            super(cause);
52        }
53
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> {
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
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
204        protected abstract T fromX509PemInputStream(InputStream pbis) throws ParsingException;
205
206        protected abstract T fromX509DerInputStream(InputStream pbis) throws ParsingException;
207
208        protected abstract List<? extends T> fromPkcs7PemInputStream(InputStream is)
209                throws ParsingException;
210
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
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
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
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
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
311    public Iterator<String> engineGetCertPathEncodings() {
312        return OpenSSLX509CertPath.getEncodingsIterator();
313    }
314
315    @Override
316    public CertPath engineGenerateCertPath(InputStream inStream) throws CertificateException {
317        return OpenSSLX509CertPath.fromEncoding(inStream);
318    }
319
320    @Override
321    public CertPath engineGenerateCertPath(InputStream inStream, String encoding)
322            throws CertificateException {
323        return OpenSSLX509CertPath.fromEncoding(inStream, encoding);
324    }
325
326    @Override
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