TrustManagerImpl.java revision 8a720cceee7ce319d647738dfeda3f302879f370
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 class TrustManagerImpl implements X509TrustManager { 48 49 private final CertPathValidator validator; 50 51 private final PKIXParameters params; 52 53 private final X509Certificate[] acceptedIssuers; 54 55 private final Exception err; 56 57 private final CertificateFactory factory; 58 59 /** 60 * Creates trust manager implementation 61 * 62 * @param ks 63 */ 64 public TrustManagerImpl(KeyStore ks) { 65 CertPathValidator validatorLocal = null; 66 CertificateFactory factoryLocal = null; 67 PKIXParameters paramsLocal = null; 68 X509Certificate[] acceptedIssuersLocal = null; 69 Exception errLocal = null; 70 try { 71 validatorLocal = CertPathValidator.getInstance("PKIX"); 72 factoryLocal = CertificateFactory.getInstance("X509"); 73 acceptedIssuersLocal = acceptedIssuers(ks); 74 paramsLocal = new IndexedPKIXParameters(trustAnchors(acceptedIssuersLocal)); 75 paramsLocal.setRevocationEnabled(false); 76 } catch (Exception e) { 77 errLocal = e; 78 } 79 this.validator = validatorLocal; 80 this.factory = factoryLocal; 81 this.params = paramsLocal; 82 this.acceptedIssuers = (acceptedIssuersLocal != null 83 ? acceptedIssuersLocal 84 : new X509Certificate[0]); 85 this.err = errLocal; 86 } 87 88 private static X509Certificate[] acceptedIssuers(KeyStore ks) throws KeyStoreException { 89 // Note that unlike the PKIXParameters code to create a Set of 90 // TrustAnchors from a KeyStore, this version takes from both 91 // TrustedCertificateEntry and PrivateKeyEntry, not just 92 // TrustedCertificateEntry, which is why TrustManagerImpl 93 // cannot just use an PKIXParameters(KeyStore) 94 // constructor. 95 96 // TODO remove duplicates if same cert is found in both a 97 // PrivateKeyEntry and TrustedCertificateEntry 98 List<X509Certificate> trusted = new ArrayList<X509Certificate>(); 99 for (Enumeration<String> en = ks.aliases(); en.hasMoreElements();) { 100 final String alias = en.nextElement(); 101 final X509Certificate cert = (X509Certificate) ks.getCertificate(alias); 102 if (cert != null) { 103 trusted.add(cert); 104 } 105 } 106 return trusted.toArray(new X509Certificate[trusted.size()]); 107 } 108 109 private static Set<TrustAnchor> trustAnchors(X509Certificate[] certs) { 110 Set<TrustAnchor> trustAnchors = new HashSet<TrustAnchor>(certs.length); 111 for (X509Certificate cert : certs) { 112 trustAnchors.add(new TrustAnchor(cert, null)); 113 } 114 return trustAnchors; 115 } 116 117 /** 118 * @see javax.net.ssl.X509TrustManager#checkClientTrusted(X509Certificate[], 119 * String) 120 */ 121 public void checkClientTrusted(X509Certificate[] chain, String authType) 122 throws CertificateException { 123 checkTrusted(chain, authType); 124 } 125 126 /** 127 * @see javax.net.ssl.X509TrustManager#checkServerTrusted(X509Certificate[], 128 * String) 129 */ 130 public void checkServerTrusted(X509Certificate[] chain, String authType) 131 throws CertificateException { 132 checkTrusted(chain, authType); 133 } 134 135 private void checkTrusted(X509Certificate[] chain, String authType) 136 throws CertificateException { 137 if (chain == null || chain.length == 0 || authType == null || authType.length() == 0) { 138 throw new IllegalArgumentException("null or zero-length parameter"); 139 } 140 if (err != null) { 141 throw new CertificateException(err); 142 } 143 144 X509Certificate[] newChain = cleanupCertChain(chain); 145 if (newChain.length == 0) { 146 // chain was entirely trusted, skip the validator 147 return; 148 } 149 CertPath certPath = factory.generateCertPath(Arrays.asList(newChain)); 150 if (!Arrays.equals(chain[0].getEncoded(), 151 certPath.getCertificates().get(0).getEncoded())) { 152 // Sanity check failed (shouldn't ever happen, but we 153 // are using pretty remote code) 154 throw new CertificateException("Certificate chain error"); 155 } 156 try { 157 validator.validate(certPath, params); 158 } catch (InvalidAlgorithmParameterException e) { 159 throw new CertificateException(e); 160 } catch (CertPathValidatorException e) { 161 throw new CertificateException(e); 162 } 163 } 164 165 /** 166 * Clean up the certificate chain, returning a cleaned up chain, 167 * which may be a new array instance if elements were removed. 168 * Theoretically, we shouldn't have to do this, but various web 169 * servers in practice are mis-configured to have out-of-order 170 * certificates, expired self-issued root certificate, or CAs with 171 * unsupported signature algorithms such as 172 * md2WithRSAEncryption. This also handles removing old certs 173 * after bridge CA certs. 174 */ 175 private X509Certificate[] cleanupCertChain(X509Certificate[] chain) { 176 X509Certificate[] original = chain; 177 178 // 1. Clean the received certificates chain. 179 int currIndex; 180 // Start with the first certificate in the chain, assuming it 181 // is the leaf certificate (server or client cert). 182 for (currIndex = 0; currIndex < chain.length; currIndex++) { 183 // If the current cert is a TrustAnchor, we can ignore the rest of the chain. 184 // This avoids including "bridge" CA certs that added for legacy compatability. 185 if (isTrustAnchor(chain[currIndex])) { 186 currIndex--; 187 break; 188 } 189 // Walk the rest of the chain to find a "subject" matching 190 // the "issuer" of the current certificate. In a properly 191 // order chain this should be the next cert and be fast. 192 // If not, we reorder things to be as the validator will 193 // expect. 194 boolean foundNext = false; 195 for (int nextIndex = currIndex + 1; nextIndex < chain.length; nextIndex++) { 196 if (chain[currIndex].getIssuerDN().equals(chain[nextIndex].getSubjectDN())) { 197 foundNext = true; 198 // Exchange certificates so that 0 through currIndex + 1 are in proper order 199 if (nextIndex != currIndex + 1) { 200 // don't mutuate original chain, which may be directly from an SSLSession 201 if (chain == original) { 202 chain = original.clone(); 203 } 204 X509Certificate tempCertificate = chain[nextIndex]; 205 chain[nextIndex] = chain[currIndex + 1]; 206 chain[currIndex + 1] = tempCertificate; 207 } 208 break; 209 } 210 } 211 // If we can't find the next in the chain, just give up 212 // and use what we found so far. This drops unrelated 213 // certificates that have nothing to do with the cert 214 // chain. 215 if (!foundNext) { 216 break; 217 } 218 } 219 220 // 2. If the chain is now shorter, copy to an appropriately sized array. 221 int chainLength = currIndex + 1; 222 if (chainLength == chain.length) { 223 return chain; 224 } 225 return Arrays.copyOf(chain, chainLength); 226 } 227 228 /** 229 * Checks whether the given certificate is found in our trust store. 230 */ 231 private boolean isTrustAnchor(X509Certificate cert) { 232 if (params instanceof IndexedPKIXParameters) { 233 IndexedPKIXParameters index = (IndexedPKIXParameters) params; 234 return index.isTrustAnchor(cert); 235 } 236 return IndexedPKIXParameters.isTrustAnchor(cert, params.getTrustAnchors()); 237 } 238 239 /** 240 * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers() 241 */ 242 public X509Certificate[] getAcceptedIssuers() { 243 return acceptedIssuers.clone(); 244 } 245} 246