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