1 /*
2  * Copyright (C) 2016 Square, Inc.
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 package okhttp3.internal.tls;
17
18 import java.security.GeneralSecurityException;
19 import java.security.cert.Certificate;
20 import java.security.cert.X509Certificate;
21 import java.util.ArrayDeque;
22 import java.util.ArrayList;
23 import java.util.Deque;
24 import java.util.Iterator;
25 import java.util.List;
26 import javax.net.ssl.SSLPeerUnverifiedException;
27
28 /**
29  * A certificate chain cleaner that uses a set of trusted root certificates to build the trusted
30  * chain. This class duplicates the clean chain building performed during the TLS handshake. We
31  * prefer other mechanisms where they exist, such as with
32  * {@code okhttp3.internal.platform.AndroidPlatform.AndroidCertificateChainCleaner}.
33  *
34  * <p>This class includes code from <a href="https://conscrypt.org/">Conscrypt's</a> {@code
35  * TrustManagerImpl} and {@code TrustedCertificateIndex}.
36  */

37 public final class BasicCertificateChainCleaner extends CertificateChainCleaner {
38   /** The maximum number of signers in a chain. We use 9 for consistency with OpenSSL. */
39   private static final int MAX_SIGNERS = 9;
40
41   private final TrustRootIndex trustRootIndex;
42
43   public BasicCertificateChainCleaner(TrustRootIndex trustRootIndex) {
44     this.trustRootIndex = trustRootIndex;
45   }
46
47   /**
48    * Returns a cleaned chain for {@code chain}.
49    *
50    * <p>This method throws if the complete chain to a trusted CA certificate cannot be constructed.
51    * This is unexpected unless the trust root index in this class has a different trust manager than
52    * what was used to establish {@code chain}.
53    */

54   @Override public List<Certificate> clean(List<Certificate> chain, String hostname)
55       throws SSLPeerUnverifiedException {
56     Deque<Certificate> queue = new ArrayDeque<>(chain);
57     List<Certificate> result = new ArrayList<>();
58     result.add(queue.removeFirst());
59     boolean foundTrustedCertificate = false;
60
61     followIssuerChain:
62     for (int c = 0; c < MAX_SIGNERS; c++) {
63       X509Certificate toVerify = (X509Certificate) result.get(result.size() - 1);
64
65       // If this cert has been signed by a trusted cert, use that. Add the trusted certificate to
66       // the end of the chain unless it's already present. (That would happen if the first
67       // certificate in the chain is itself a self-signed and trusted CA certificate.)
68       X509Certificate trustedCert = trustRootIndex.findByIssuerAndSignature(toVerify);
69       if (trustedCert != null) {
70         if (result.size() > 1 || !toVerify.equals(trustedCert)) {
71           result.add(trustedCert);
72         }
73         if (verifySignature(trustedCert, trustedCert)) {
74           return result; // The self-signed cert is a root CA. We're done.
75         }
76         foundTrustedCertificate = true;
77         continue;
78       }
79
80       // Search for the certificate in the chain that signed this certificate. This is typically
81       // the next element in the chain, but it could be any element.
82       for (Iterator<Certificate> i = queue.iterator(); i.hasNext(); ) {
83         X509Certificate signingCert = (X509Certificate) i.next();
84         if (verifySignature(toVerify, signingCert)) {
85           i.remove();
86           result.add(signingCert);
87           continue followIssuerChain;
88         }
89       }
90
91       // We've reached the end of the chain. If any cert in the chain is trusted, we're done.
92       if (foundTrustedCertificate) {
93         return result;
94       }
95
96       // The last link isn't trusted. Fail.
97       throw new SSLPeerUnverifiedException(
98           "Failed to find a trusted cert that signed " + toVerify);
99     }
100
101     throw new SSLPeerUnverifiedException("Certificate chain too long: " + result);
102   }
103
104   /** Returns true if {@code toVerify} was signed by {@code signingCert}'s public key. */
105   private boolean verifySignature(X509Certificate toVerify, X509Certificate signingCert) {
106     if (!toVerify.getIssuerDN().equals(signingCert.getSubjectDN())) return false;
107     try {
108       toVerify.verify(signingCert.getPublicKey());
109       return true;
110     } catch (GeneralSecurityException verifyFailed) {
111       return false;
112     }
113   }
114
115   @Override public int hashCode() {
116     return trustRootIndex.hashCode();
117   }
118
119   @Override public boolean equals(Object other) {
120     if (other == thisreturn true;
121     return other instanceof BasicCertificateChainCleaner
122         && ((BasicCertificateChainCleaner) other).trustRootIndex.equals(trustRootIndex);
123   }
124 }
125