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