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