OpenSSLX509CertificateFactory.java revision 41dbe2157cc4e6c8ec2beb4c17e88caa84ea7dfc
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.apache.harmony.xnet.provider.jsse;
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            try {
120                if (inStream == null || inStream.available() == 0) {
121                    return Collections.emptyList();
122                }
123            } catch (IOException e) {
124                throw new ParsingException("Problem reading input stream", e);
125            }
126
127            final boolean markable = inStream.markSupported();
128            if (markable) {
129                inStream.mark(PUSHBACK_SIZE);
130            }
131
132            /* Attempt to see if this is a PKCS#7 bag. */
133            final PushbackInputStream pbis = new PushbackInputStream(inStream, PUSHBACK_SIZE);
134            try {
135                final byte[] buffer = new byte[PKCS7_MARKER.length];
136
137                final int len = pbis.read(buffer);
138                if (len < 0) {
139                    /* No need to reset here. The stream was empty or EOF. */
140                    throw new ParsingException("inStream is empty");
141                }
142                pbis.unread(buffer, 0, len);
143
144                if (len == PKCS7_MARKER.length && Arrays.equals(PKCS7_MARKER, buffer)) {
145                    return fromPkcs7PemInputStream(pbis);
146                }
147
148                /* PKCS#7 bags have a byte 0x06 at position 4 in the stream. */
149                if (buffer[4] == 0x06) {
150                    return fromPkcs7DerInputStream(pbis);
151                }
152            } catch (Exception e) {
153                if (markable) {
154                    try {
155                        inStream.reset();
156                    } catch (IOException ignored) {
157                    }
158                }
159                throw new ParsingException(e);
160            }
161
162            /*
163             * It wasn't, so just try to keep grabbing certificates until we
164             * can't anymore.
165             */
166            final List<T> coll = new ArrayList<T>();
167            T c = null;
168            do {
169                /*
170                 * If this stream supports marking, try to mark here in case
171                 * there is an error during certificate generation.
172                 */
173                if (markable) {
174                    inStream.mark(PUSHBACK_SIZE);
175                }
176
177                try {
178                    c = generateItem(pbis);
179                    coll.add(c);
180                } catch (ParsingException e) {
181                    /*
182                     * If this stream supports marking, attempt to reset it to
183                     * the mark before the failure.
184                     */
185                    if (markable) {
186                        try {
187                            inStream.reset();
188                        } catch (IOException ignored) {
189                        }
190                    }
191
192                    c = null;
193                }
194            } while (c != null);
195
196            return coll;
197        }
198
199        protected abstract T fromX509PemInputStream(InputStream pbis) throws ParsingException;
200
201        protected abstract T fromX509DerInputStream(InputStream pbis) throws ParsingException;
202
203        protected abstract List<? extends T> fromPkcs7PemInputStream(InputStream is)
204                throws ParsingException;
205
206        protected abstract List<? extends T> fromPkcs7DerInputStream(InputStream is)
207                throws ParsingException;
208    }
209
210    private Parser<OpenSSLX509Certificate> certificateParser =
211            new Parser<OpenSSLX509Certificate>() {
212                @Override
213                public OpenSSLX509Certificate fromX509PemInputStream(InputStream is)
214                        throws ParsingException {
215                    return OpenSSLX509Certificate.fromX509PemInputStream(is);
216                }
217
218                @Override
219                public OpenSSLX509Certificate fromX509DerInputStream(InputStream is)
220                        throws ParsingException {
221                    return OpenSSLX509Certificate.fromX509DerInputStream(is);
222                }
223
224                @Override
225                public List<? extends OpenSSLX509Certificate>
226                        fromPkcs7PemInputStream(InputStream is) throws ParsingException {
227                    return OpenSSLX509Certificate.fromPkcs7PemInputStream(is);
228                }
229
230                @Override
231                public List<? extends OpenSSLX509Certificate>
232                        fromPkcs7DerInputStream(InputStream is) throws ParsingException {
233                    return OpenSSLX509Certificate.fromPkcs7DerInputStream(is);
234                }
235            };
236
237    private Parser<OpenSSLX509CRL> crlParser =
238            new Parser<OpenSSLX509CRL>() {
239                @Override
240                public OpenSSLX509CRL fromX509PemInputStream(InputStream is)
241                        throws ParsingException {
242                    return OpenSSLX509CRL.fromX509PemInputStream(is);
243                }
244
245                @Override
246                public OpenSSLX509CRL fromX509DerInputStream(InputStream is)
247                        throws ParsingException {
248                    return OpenSSLX509CRL.fromX509DerInputStream(is);
249                }
250
251                @Override
252                public List<? extends OpenSSLX509CRL> fromPkcs7PemInputStream(InputStream is)
253                        throws ParsingException {
254                    return OpenSSLX509CRL.fromPkcs7PemInputStream(is);
255                }
256
257                @Override
258                public List<? extends OpenSSLX509CRL> fromPkcs7DerInputStream(InputStream is)
259                        throws ParsingException {
260                    return OpenSSLX509CRL.fromPkcs7DerInputStream(is);
261                }
262            };
263
264    @Override
265    public Certificate engineGenerateCertificate(InputStream inStream) throws CertificateException {
266        try {
267            return certificateParser.generateItem(inStream);
268        } catch (ParsingException e) {
269            throw new CertificateException(e);
270        }
271    }
272
273    @Override
274    public Collection<? extends Certificate> engineGenerateCertificates(
275            InputStream inStream) throws CertificateException {
276        try {
277            return certificateParser.generateItems(inStream);
278        } catch (ParsingException e) {
279            throw new CertificateException(e);
280        }
281    }
282
283    @Override
284    public CRL engineGenerateCRL(InputStream inStream) throws CRLException {
285        try {
286            return crlParser.generateItem(inStream);
287        } catch (ParsingException e) {
288            throw new CRLException(e);
289        }
290    }
291
292    @Override
293    public Collection<? extends CRL> engineGenerateCRLs(InputStream inStream) throws CRLException {
294        try {
295            return crlParser.generateItems(inStream);
296        } catch (ParsingException e) {
297            throw new CRLException(e);
298        }
299    }
300
301    @Override
302    public Iterator<String> engineGetCertPathEncodings() {
303        return OpenSSLX509CertPath.getEncodingsIterator();
304    }
305
306    @Override
307    public CertPath engineGenerateCertPath(InputStream inStream) throws CertificateException {
308        return OpenSSLX509CertPath.fromEncoding(inStream);
309    }
310
311    @Override
312    public CertPath engineGenerateCertPath(InputStream inStream, String encoding)
313            throws CertificateException {
314        return OpenSSLX509CertPath.fromEncoding(inStream, encoding);
315    }
316
317    @Override
318    public CertPath engineGenerateCertPath(List<? extends Certificate> certificates)
319            throws CertificateException {
320        final List<X509Certificate> filtered = new ArrayList<X509Certificate>(certificates.size());
321        for (int i = 0; i < certificates.size(); i++) {
322            final Certificate c = certificates.get(i);
323
324            if (!(c instanceof X509Certificate)) {
325                throw new CertificateException("Certificate not X.509 type at index " + i);
326            }
327
328            filtered.add((X509Certificate) c);
329        }
330
331        return new OpenSSLX509CertPath(filtered);
332    }
333}
334