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