• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 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.CertPath;
23 import java.security.cert.Certificate;
24 import java.security.cert.CertificateEncodingException;
25 import java.security.cert.CertificateException;
26 import java.security.cert.X509Certificate;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collections;
30 import java.util.Iterator;
31 import java.util.List;
32 import org.conscrypt.OpenSSLX509CertificateFactory.ParsingException;
33 
34 /**
35  * An implementation of {@link CertPath} based on BoringSSL.
36  */
37 final class OpenSSLX509CertPath extends CertPath {
38     private static final long serialVersionUID = -3249106005255170761L;
39 
40     private static final byte[] PKCS7_MARKER = new byte[] {
41             '-', '-', '-', '-', '-', 'B', 'E', 'G', 'I', 'N', ' ', 'P', 'K', 'C', 'S', '7'
42     };
43 
44     private static final int PUSHBACK_SIZE = 64;
45 
46     /**
47      * Supported encoding types for CerthPath. Used by the various APIs that
48      * encode this into bytes such as {@link #getEncoded()}.
49      */
50     private enum Encoding {
51         PKI_PATH("PkiPath"),
52         PKCS7("PKCS7");
53 
54         private final String apiName;
55 
Encoding(String apiName)56         Encoding(String apiName) {
57             this.apiName = apiName;
58         }
59 
findByApiName(String apiName)60         static Encoding findByApiName(String apiName) throws CertificateEncodingException {
61             for (Encoding element : values()) {
62                 if (element.apiName.equals(apiName)) {
63                     return element;
64                 }
65             }
66 
67             return null;
68         }
69     }
70 
71     /** Unmodifiable list of encodings for the API. */
72     private static final List<String> ALL_ENCODINGS = Collections.unmodifiableList(Arrays
73             .asList(new String[] {
74                     Encoding.PKI_PATH.apiName,
75                     Encoding.PKCS7.apiName,
76             }));
77 
78     private static final Encoding DEFAULT_ENCODING = Encoding.PKI_PATH;
79 
80     private final List<? extends X509Certificate> mCertificates;
81 
getEncodingsIterator()82     static Iterator<String> getEncodingsIterator() {
83         return ALL_ENCODINGS.iterator();
84     }
85 
OpenSSLX509CertPath(List<? extends X509Certificate> certificates)86     OpenSSLX509CertPath(List<? extends X509Certificate> certificates) {
87         super("X.509");
88 
89         mCertificates = certificates;
90     }
91 
92     @Override
getCertificates()93     public List<? extends Certificate> getCertificates() {
94         return Collections.unmodifiableList(mCertificates);
95     }
96 
getEncoded(Encoding encoding)97     private byte[] getEncoded(Encoding encoding) throws CertificateEncodingException {
98         final OpenSSLX509Certificate[] certs = new OpenSSLX509Certificate[mCertificates.size()];
99         final long[] certRefs = new long[certs.length];
100 
101         for (int i = 0, j = certs.length - 1; j >= 0; i++, j--) {
102             final X509Certificate cert = mCertificates.get(i);
103 
104             if (cert instanceof OpenSSLX509Certificate) {
105                 certs[j] = (OpenSSLX509Certificate) cert;
106             } else {
107                 certs[j] = OpenSSLX509Certificate.fromX509Der(cert.getEncoded());
108             }
109 
110             certRefs[j] = certs[j].getContext();
111         }
112 
113         switch (encoding) {
114             case PKI_PATH:
115                 return NativeCrypto.ASN1_seq_pack_X509(certRefs);
116             case PKCS7:
117                 return NativeCrypto.i2d_PKCS7(certRefs);
118             default:
119                 throw new CertificateEncodingException("Unknown encoding");
120         }
121     }
122 
123     @Override
getEncoded()124     public byte[] getEncoded() throws CertificateEncodingException {
125         return getEncoded(DEFAULT_ENCODING);
126     }
127 
128     @Override
getEncoded(String encoding)129     public byte[] getEncoded(String encoding) throws CertificateEncodingException {
130         Encoding enc = Encoding.findByApiName(encoding);
131         if (enc == null) {
132             throw new CertificateEncodingException("Invalid encoding: " + encoding);
133         }
134 
135         return getEncoded(enc);
136     }
137 
138     @Override
getEncodings()139     public Iterator<String> getEncodings() {
140         return getEncodingsIterator();
141     }
142 
fromPkiPathEncoding(InputStream inStream)143     private static CertPath fromPkiPathEncoding(InputStream inStream) throws CertificateException {
144         OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(inStream, true);
145 
146         final boolean markable = inStream.markSupported();
147         if (markable) {
148             inStream.mark(PUSHBACK_SIZE);
149         }
150 
151         final long[] certRefs;
152         try {
153             certRefs = NativeCrypto.ASN1_seq_unpack_X509_bio(bis.getBioContext());
154         } catch (Exception e) {
155             if (markable) {
156                 try {
157                     inStream.reset();
158                 } catch (IOException ignored) {
159                 }
160             }
161             throw new CertificateException(e);
162         } finally {
163             bis.release();
164         }
165 
166         if (certRefs == null) {
167             return new OpenSSLX509CertPath(Collections.<X509Certificate> emptyList());
168         }
169 
170         final List<OpenSSLX509Certificate> certs =
171                 new ArrayList<OpenSSLX509Certificate>(certRefs.length);
172         for (int i = certRefs.length - 1; i >= 0; i--) {
173             if (certRefs[i] == 0) {
174                 continue;
175             }
176             certs.add(new OpenSSLX509Certificate(certRefs[i]));
177         }
178 
179         return new OpenSSLX509CertPath(certs);
180     }
181 
fromPkcs7Encoding(InputStream inStream)182     private static CertPath fromPkcs7Encoding(InputStream inStream) throws CertificateException {
183         try {
184             if (inStream == null || inStream.available() == 0) {
185                 return new OpenSSLX509CertPath(Collections.<X509Certificate> emptyList());
186             }
187         } catch (IOException e) {
188             throw new CertificateException("Problem reading input stream", e);
189         }
190 
191         final boolean markable = inStream.markSupported();
192         if (markable) {
193             inStream.mark(PUSHBACK_SIZE);
194         }
195 
196         /* Attempt to see if this is a PKCS#7 bag. */
197         final PushbackInputStream pbis = new PushbackInputStream(inStream, PUSHBACK_SIZE);
198         try {
199             final byte[] buffer = new byte[PKCS7_MARKER.length];
200 
201             final int len = pbis.read(buffer);
202             if (len < 0) {
203                 /* No need to reset here. The stream was empty or EOF. */
204                 throw new ParsingException("inStream is empty");
205             }
206             pbis.unread(buffer, 0, len);
207 
208             if (len == PKCS7_MARKER.length && Arrays.equals(PKCS7_MARKER, buffer)) {
209                 return new OpenSSLX509CertPath(OpenSSLX509Certificate.fromPkcs7PemInputStream(pbis));
210             }
211 
212             return new OpenSSLX509CertPath(OpenSSLX509Certificate.fromPkcs7DerInputStream(pbis));
213         } catch (Exception e) {
214             if (markable) {
215                 try {
216                     inStream.reset();
217                 } catch (IOException ignored) {
218                 }
219             }
220             throw new CertificateException(e);
221         }
222     }
223 
fromEncoding(InputStream inStream, Encoding encoding)224     private static CertPath fromEncoding(InputStream inStream, Encoding encoding)
225             throws CertificateException {
226         switch (encoding) {
227             case PKI_PATH:
228                 return fromPkiPathEncoding(inStream);
229             case PKCS7:
230                 return fromPkcs7Encoding(inStream);
231             default:
232                 throw new CertificateEncodingException("Unknown encoding");
233         }
234     }
235 
fromEncoding(InputStream inStream, String encoding)236     static CertPath fromEncoding(InputStream inStream, String encoding)
237             throws CertificateException {
238         if (inStream == null) {
239             throw new CertificateException("inStream == null");
240         }
241 
242         Encoding enc = Encoding.findByApiName(encoding);
243         if (enc == null) {
244             throw new CertificateException("Invalid encoding: " + encoding);
245         }
246 
247         return fromEncoding(inStream, enc);
248     }
249 
fromEncoding(InputStream inStream)250     static CertPath fromEncoding(InputStream inStream) throws CertificateException {
251         if (inStream == null) {
252             throw new CertificateException("inStream == null");
253         }
254 
255         return fromEncoding(inStream, DEFAULT_ENCODING);
256     }
257 }
258