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