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