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