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