TrustManagerImpl.java revision 3258b52429c7768ea91bda93c5a15257cdd390e5
1/* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package org.apache.harmony.xnet.provider.jsse; 19 20import java.security.InvalidAlgorithmParameterException; 21import java.security.KeyStore; 22import java.security.KeyStoreException; 23import java.security.cert.CertPath; 24import java.security.cert.CertPathValidator; 25import java.security.cert.CertPathValidatorException; 26import java.security.cert.CertificateException; 27import java.security.cert.CertificateFactory; 28import java.security.cert.PKIXParameters; 29import java.security.cert.TrustAnchor; 30import java.security.cert.X509Certificate; 31import java.util.Arrays; 32import java.util.ArrayList; 33import java.util.Enumeration; 34import java.util.HashSet; 35import java.util.List; 36import java.util.Set; 37import javax.net.ssl.X509TrustManager; 38 39/** 40 * 41 * TrustManager implementation. The implementation is based on CertPathValidator 42 * PKIX and CertificateFactory X509 implementations. This implementations should 43 * be provided by some certification provider. 44 * 45 * @see javax.net.ssl.X509TrustManager 46 */ 47public final class TrustManagerImpl implements X509TrustManager { 48 49 private final CertPathValidator validator; 50 51 private final IndexedPKIXParameters params; 52 53 private final X509Certificate[] acceptedIssuers; 54 55 private final Exception err; 56 57 private final CertificateFactory factory; 58 59 public final IndexedPKIXParameters getIndexedPKIXParameters() { 60 return params; 61 } 62 63 /** 64 * Creates trust manager implementation 65 * 66 * @param ks 67 */ 68 public TrustManagerImpl(KeyStore ks) { 69 CertPathValidator validatorLocal = null; 70 CertificateFactory factoryLocal = null; 71 IndexedPKIXParameters paramsLocal = null; 72 X509Certificate[] acceptedIssuersLocal = null; 73 Exception errLocal = null; 74 try { 75 validatorLocal = CertPathValidator.getInstance("PKIX"); 76 factoryLocal = CertificateFactory.getInstance("X509"); 77 acceptedIssuersLocal = acceptedIssuers(ks); 78 paramsLocal = new IndexedPKIXParameters(trustAnchors(acceptedIssuersLocal)); 79 paramsLocal.setRevocationEnabled(false); 80 } catch (Exception e) { 81 errLocal = e; 82 } 83 this.validator = validatorLocal; 84 this.factory = factoryLocal; 85 this.params = paramsLocal; 86 this.acceptedIssuers = (acceptedIssuersLocal != null 87 ? acceptedIssuersLocal 88 : new X509Certificate[0]); 89 this.err = errLocal; 90 } 91 92 private static X509Certificate[] acceptedIssuers(KeyStore ks) throws KeyStoreException { 93 // Note that unlike the PKIXParameters code to create a Set of 94 // TrustAnchors from a KeyStore, this version takes from both 95 // TrustedCertificateEntry and PrivateKeyEntry, not just 96 // TrustedCertificateEntry, which is why TrustManagerImpl 97 // cannot just use an PKIXParameters(KeyStore) 98 // constructor. 99 100 // TODO remove duplicates if same cert is found in both a 101 // PrivateKeyEntry and TrustedCertificateEntry 102 List<X509Certificate> trusted = new ArrayList<X509Certificate>(); 103 for (Enumeration<String> en = ks.aliases(); en.hasMoreElements();) { 104 final String alias = en.nextElement(); 105 final X509Certificate cert = (X509Certificate) ks.getCertificate(alias); 106 if (cert != null) { 107 trusted.add(cert); 108 } 109 } 110 return trusted.toArray(new X509Certificate[trusted.size()]); 111 } 112 113 private static Set<TrustAnchor> trustAnchors(X509Certificate[] certs) { 114 Set<TrustAnchor> trustAnchors = new HashSet<TrustAnchor>(certs.length); 115 for (X509Certificate cert : certs) { 116 trustAnchors.add(new TrustAnchor(cert, null)); 117 } 118 return trustAnchors; 119 } 120 121 /** 122 * @see javax.net.ssl.X509TrustManager#checkClientTrusted(X509Certificate[], 123 * String) 124 */ 125 @Override public void checkClientTrusted(X509Certificate[] chain, String authType) 126 throws CertificateException { 127 checkTrusted(chain, authType); 128 } 129 130 /** 131 * @see javax.net.ssl.X509TrustManager#checkServerTrusted(X509Certificate[], 132 * String) 133 */ 134 @Override public void checkServerTrusted(X509Certificate[] chain, String authType) 135 throws CertificateException { 136 checkTrusted(chain, authType); 137 } 138 139 private void checkTrusted(X509Certificate[] chain, String authType) 140 throws CertificateException { 141 if (chain == null || chain.length == 0 || authType == null || authType.length() == 0) { 142 throw new IllegalArgumentException("null or zero-length parameter"); 143 } 144 if (err != null) { 145 throw new CertificateException(err); 146 } 147 148 X509Certificate[] newChain = cleanupCertChain(chain); 149 if (newChain.length == 0) { 150 // chain was entirely trusted, skip the validator 151 return; 152 } 153 CertPath certPath = factory.generateCertPath(Arrays.asList(newChain)); 154 if (!Arrays.equals(chain[0].getEncoded(), 155 certPath.getCertificates().get(0).getEncoded())) { 156 // Sanity check failed (shouldn't ever happen, but we 157 // are using pretty remote code) 158 throw new CertificateException("Certificate chain error"); 159 } 160 try { 161 validator.validate(certPath, params); 162 // Add intermediate CAs to the index to tolerate sites 163 // that assume that the browser will have cached these. 164 // The server certificate is skipped by skipping the 165 // zeroth element of new chain and note that the root CA 166 // will have been removed in cleanupCertChain. 167 // http://b/3404902 168 for (int i = 1; i < newChain.length; i++) { 169 params.index(new TrustAnchor(newChain[i], null)); 170 } 171 } catch (InvalidAlgorithmParameterException e) { 172 throw new CertificateException(e); 173 } catch (CertPathValidatorException e) { 174 throw new CertificateException(e); 175 } 176 } 177 178 /** 179 * Clean up the certificate chain, returning a cleaned up chain, 180 * which may be a new array instance if elements were removed. 181 * Theoretically, we shouldn't have to do this, but various web 182 * servers in practice are mis-configured to have out-of-order 183 * certificates, expired self-issued root certificate, or CAs with 184 * unsupported signature algorithms such as 185 * md2WithRSAEncryption. This also handles removing old certs 186 * after bridge CA certs. 187 */ 188 private X509Certificate[] cleanupCertChain(X509Certificate[] chain) { 189 X509Certificate[] original = chain; 190 191 // 1. Clean the received certificates chain. 192 int currIndex; 193 // Start with the first certificate in the chain, assuming it 194 // is the leaf certificate (server or client cert). 195 for (currIndex = 0; currIndex < chain.length; currIndex++) { 196 // If the current cert is a TrustAnchor, we can ignore the rest of the chain. 197 // This avoids including "bridge" CA certs that added for legacy compatability. 198 if (params.isTrustAnchor(chain[currIndex])) { 199 currIndex--; 200 break; 201 } 202 // Walk the rest of the chain to find a "subject" matching 203 // the "issuer" of the current certificate. In a properly 204 // order chain this should be the next cert and be fast. 205 // If not, we reorder things to be as the validator will 206 // expect. 207 boolean foundNext = false; 208 for (int nextIndex = currIndex + 1; nextIndex < chain.length; nextIndex++) { 209 if (chain[currIndex].getIssuerDN().equals(chain[nextIndex].getSubjectDN())) { 210 foundNext = true; 211 // Exchange certificates so that 0 through currIndex + 1 are in proper order 212 if (nextIndex != currIndex + 1) { 213 // don't mutuate original chain, which may be directly from an SSLSession 214 if (chain == original) { 215 chain = original.clone(); 216 } 217 X509Certificate tempCertificate = chain[nextIndex]; 218 chain[nextIndex] = chain[currIndex + 1]; 219 chain[currIndex + 1] = tempCertificate; 220 } 221 break; 222 } 223 } 224 // If we can't find the next in the chain, just give up 225 // and use what we found so far. This drops unrelated 226 // certificates that have nothing to do with the cert 227 // chain. 228 if (!foundNext) { 229 break; 230 } 231 } 232 233 // 2. If the chain is now shorter, copy to an appropriately sized array. 234 int chainLength = currIndex + 1; 235 if (chainLength == chain.length) { 236 return chain; 237 } 238 return Arrays.copyOf(chain, chainLength); 239 } 240 241 /** 242 * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers() 243 */ 244 @Override public X509Certificate[] getAcceptedIssuers() { 245 return acceptedIssuers.clone(); 246 } 247} 248