OpenSSLX509CertificateFactory.java revision 75dc9601af8ab3c65114e3c8c57d29ce5ac64125
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.Certificate;
25import java.security.cert.CertificateException;
26import java.security.cert.CertificateFactorySpi;
27import java.util.ArrayList;
28import java.util.Arrays;
29import java.util.Collection;
30import java.util.Collections;
31import java.util.List;
32
33public class OpenSSLX509CertificateFactory extends CertificateFactorySpi {
34    private static final byte[] PKCS7_MARKER = "-----BEGIN PKCS7".getBytes();
35
36    private static final int PUSHBACK_SIZE = 64;
37
38    static class ParsingException extends Exception {
39        private static final long serialVersionUID = 8390802697728301325L;
40
41        public ParsingException(String message) {
42            super(message);
43        }
44
45        public ParsingException(Exception cause) {
46            super(cause);
47        }
48
49        public ParsingException(String message, Exception cause) {
50            super(message, cause);
51        }
52    }
53
54    /**
55     * The code for X509 Certificates and CRL is pretty much the same. We use
56     * this abstract class to share the code between them. This makes it ugly,
57     * but it's already written in this language anyway.
58     */
59    private static abstract class Parser<T> {
60        public T generateItem(InputStream inStream) throws ParsingException {
61            final boolean markable = inStream.markSupported();
62            if (markable) {
63                inStream.mark(PKCS7_MARKER.length);
64            }
65
66            final PushbackInputStream pbis = new PushbackInputStream(inStream, PUSHBACK_SIZE);
67            try {
68                final byte[] buffer = new byte[PKCS7_MARKER.length];
69
70                final int len = pbis.read(buffer);
71                if (len < 0) {
72                    /* No need to reset here. The stream was empty or EOF. */
73                    throw new ParsingException("inStream is empty");
74                }
75                pbis.unread(buffer, 0, len);
76
77                if (buffer[0] == '-') {
78                    if (len == PKCS7_MARKER.length && Arrays.equals(PKCS7_MARKER, buffer)) {
79                        List<? extends T> items = fromPkcs7PemInputStream(pbis);
80                        if (items.size() == 0) {
81                            return null;
82                        }
83                        items.get(0);
84                    } else {
85                        return fromX509PemInputStream(pbis);
86                    }
87                }
88
89                /* PKCS#7 bags have a byte 0x06 at position 4 in the stream. */
90                if (buffer[4] == 0x06) {
91                    List<? extends T> certs = fromPkcs7DerInputStream(pbis);
92                    if (certs.size() == 0) {
93                        return null;
94                    }
95                    return certs.get(0);
96                } else {
97                    return fromX509DerInputStream(pbis);
98                }
99            } catch (Exception e) {
100                if (markable) {
101                    try {
102                        inStream.reset();
103                    } catch (IOException ignored) {
104                    }
105                }
106                throw new ParsingException(e);
107            }
108        }
109
110        public Collection<? extends T> generateItems(InputStream inStream)
111                throws ParsingException {
112            try {
113                if (inStream == null || inStream.available() == 0) {
114                    return Collections.emptyList();
115                }
116            } catch (IOException e) {
117                throw new ParsingException("Problem reading input stream", e);
118            }
119
120            final boolean markable = inStream.markSupported();
121            if (markable) {
122                inStream.mark(PUSHBACK_SIZE);
123            }
124
125            /* Attempt to see if this is a PKCS#7 bag. */
126            final PushbackInputStream pbis = new PushbackInputStream(inStream, PUSHBACK_SIZE);
127            try {
128                final byte[] buffer = new byte[PKCS7_MARKER.length];
129
130                final int len = pbis.read(buffer);
131                if (len < 0) {
132                    /* No need to reset here. The stream was empty or EOF. */
133                    throw new ParsingException("inStream is empty");
134                }
135                pbis.unread(buffer, 0, len);
136
137                if (len == PKCS7_MARKER.length && Arrays.equals(PKCS7_MARKER, buffer)) {
138                    return fromPkcs7PemInputStream(pbis);
139                }
140
141                /* PKCS#7 bags have a byte 0x06 at position 4 in the stream. */
142                if (buffer[4] == 0x06) {
143                    return fromPkcs7DerInputStream(pbis);
144                }
145            } catch (Exception e) {
146                if (markable) {
147                    try {
148                        inStream.reset();
149                    } catch (IOException ignored) {
150                    }
151                }
152                throw new ParsingException(e);
153            }
154
155            /*
156             * It wasn't, so just try to keep grabbing certificates until we
157             * can't anymore.
158             */
159            final List<T> coll = new ArrayList<T>();
160            T c = null;
161            do {
162                /*
163                 * If this stream supports marking, try to mark here in case
164                 * there is an error during certificate generation.
165                 */
166                if (markable) {
167                    inStream.mark(PUSHBACK_SIZE);
168                }
169
170                try {
171                    c = generateItem(pbis);
172                    coll.add(c);
173                } catch (ParsingException e) {
174                    /*
175                     * If this stream supports marking, attempt to reset it to
176                     * the mark before the failure.
177                     */
178                    if (markable) {
179                        try {
180                            inStream.reset();
181                        } catch (IOException ignored) {
182                        }
183                    }
184
185                    c = null;
186                }
187            } while (c != null);
188
189            return coll;
190        }
191
192        protected abstract T fromX509PemInputStream(InputStream pbis) throws ParsingException;
193
194        protected abstract T fromX509DerInputStream(InputStream pbis) throws ParsingException;
195
196        protected abstract List<? extends T> fromPkcs7PemInputStream(InputStream is)
197                throws ParsingException;
198
199        protected abstract List<? extends T> fromPkcs7DerInputStream(InputStream is)
200                throws ParsingException;
201    }
202
203    private Parser<OpenSSLX509Certificate> certificateParser =
204            new Parser<OpenSSLX509Certificate>() {
205                @Override
206                public OpenSSLX509Certificate fromX509PemInputStream(InputStream is)
207                        throws ParsingException {
208                    return OpenSSLX509Certificate.fromX509PemInputStream(is);
209                }
210
211                @Override
212                public OpenSSLX509Certificate fromX509DerInputStream(InputStream is)
213                        throws ParsingException {
214                    return OpenSSLX509Certificate.fromX509DerInputStream(is);
215                }
216
217                @Override
218                public List<? extends OpenSSLX509Certificate>
219                        fromPkcs7PemInputStream(InputStream is) throws ParsingException {
220                    return OpenSSLX509Certificate.fromPkcs7PemInputStream(is);
221                }
222
223                @Override
224                public List<? extends OpenSSLX509Certificate>
225                        fromPkcs7DerInputStream(InputStream is) throws ParsingException {
226                    return OpenSSLX509Certificate.fromPkcs7DerInputStream(is);
227                }
228            };
229
230    private Parser<OpenSSLX509CRL> crlParser =
231            new Parser<OpenSSLX509CRL>() {
232                @Override
233                public OpenSSLX509CRL fromX509PemInputStream(InputStream is)
234                        throws ParsingException {
235                    return OpenSSLX509CRL.fromX509PemInputStream(is);
236                }
237
238                @Override
239                public OpenSSLX509CRL fromX509DerInputStream(InputStream is)
240                        throws ParsingException {
241                    return OpenSSLX509CRL.fromX509DerInputStream(is);
242                }
243
244                @Override
245                public List<? extends OpenSSLX509CRL> fromPkcs7PemInputStream(InputStream is)
246                        throws ParsingException {
247                    return OpenSSLX509CRL.fromPkcs7PemInputStream(is);
248                }
249
250                @Override
251                public List<? extends OpenSSLX509CRL> fromPkcs7DerInputStream(InputStream is)
252                        throws ParsingException {
253                    return OpenSSLX509CRL.fromPkcs7DerInputStream(is);
254                }
255            };
256
257    @Override
258    public Certificate engineGenerateCertificate(InputStream inStream) throws CertificateException {
259        try {
260            return certificateParser.generateItem(inStream);
261        } catch (ParsingException e) {
262            throw new CertificateException(e);
263        }
264    }
265
266    @SuppressWarnings("unchecked")
267    @Override
268    public Collection<? extends Certificate> engineGenerateCertificates(
269            InputStream inStream) throws CertificateException {
270        try {
271            return certificateParser.generateItems(inStream);
272        } catch (ParsingException e) {
273            throw new CertificateException(e);
274        }
275    }
276
277    @Override
278    public CRL engineGenerateCRL(InputStream inStream) throws CRLException {
279        try {
280            return crlParser.generateItem(inStream);
281        } catch (ParsingException e) {
282            throw new CRLException(e);
283        }
284    }
285
286    @SuppressWarnings("unchecked")
287    @Override
288    public Collection<? extends CRL> engineGenerateCRLs(InputStream inStream) throws CRLException {
289        try {
290            return crlParser.generateItems(inStream);
291        } catch (ParsingException e) {
292            throw new CRLException(e);
293        }
294    }
295}
296