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