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
36/**
37 * An implementation of {@link java.security.cert.CertificateFactory} based on BoringSSL.
38 *
39 * @hide
40 */
41@Internal
42public 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
52        public ParsingException(String message) {
53            super(message);
54        }
55
56        public ParsingException(Exception cause) {
57            super(cause);
58        }
59
60        public 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> {
71        public 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
125        public 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 = null;
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
210        protected abstract T fromX509PemInputStream(InputStream pbis) throws ParsingException;
211
212        protected abstract T fromX509DerInputStream(InputStream pbis) throws ParsingException;
213
214        protected abstract List<? extends T> fromPkcs7PemInputStream(InputStream is)
215                throws ParsingException;
216
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
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
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
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
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
317    public Iterator<String> engineGetCertPathEncodings() {
318        return OpenSSLX509CertPath.getEncodingsIterator();
319    }
320
321    @Override
322    public CertPath engineGenerateCertPath(InputStream inStream) throws CertificateException {
323        return OpenSSLX509CertPath.fromEncoding(inStream);
324    }
325
326    @Override
327    public CertPath engineGenerateCertPath(InputStream inStream, String encoding)
328            throws CertificateException {
329        return OpenSSLX509CertPath.fromEncoding(inStream, encoding);
330    }
331
332    @Override
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