2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android.net.http;
19 import android.os.SystemClock;
21 import android.security.Sha1MessageDigest;
23 import java.security.cert.Certificate;
24 import java.security.cert.CertificateFactory;
25 import java.security.cert.CertPath;
26 import java.security.GeneralSecurityException;
28 import java.util.Arrays;
29 import java.util.HashMap;
30 import java.util.Random;
34 * Validator cache used to speed-up certificate chain validation. The idea is
35 * to keep each secure domain name associated with a cryptographically secure
36 * hash of the certificate chain successfully used to validate the domain. If
37 * we establish connection with the domain more than once and each time receive
38 * the same list of certificates, we do not have to re-validate.
42 class CertificateValidatorCache {
45 public static long mSave = 0;
46 public static long mCost = 0;
50 * The cache-entry lifetime in milliseconds (here, 10 minutes)
52 private static final long CACHE_ENTRY_LIFETIME = 10 * 60 * 1000;
55 * The certificate factory
57 private static CertificateFactory sCertificateFactory;
60 * The certificate validator cache map (domain to a cache entry)
62 private HashMap<Integer, CacheEntry> mCacheMap;
67 private int mBigScrew;
70 * @param certificate The array of server certificates to compute a
72 * @return The secure hash computed from server certificates
74 public static byte[] secureHash(Certificate[] certificates) {
75 byte[] secureHash = null;
78 long beg = SystemClock.uptimeMillis();
81 if (certificates != null && certificates.length != 0) {
82 byte[] encodedCertPath = null;
84 synchronized (CertificateValidatorCache.class) {
85 if (sCertificateFactory == null) {
88 CertificateFactory.getInstance("X.509");
89 } catch(GeneralSecurityException e) {
91 HttpLog.v("CertificateValidatorCache:" +
92 " failed to create the certificate factory");
99 sCertificateFactory.generateCertPath(Arrays.asList(certificates));
100 if (certPath != null) {
101 encodedCertPath = certPath.getEncoded();
102 if (encodedCertPath != null) {
103 Sha1MessageDigest messageDigest =
104 new Sha1MessageDigest();
105 secureHash = messageDigest.digest(encodedCertPath);
108 } catch (GeneralSecurityException e) {}
112 long end = SystemClock.uptimeMillis();
113 mCost += (end - beg);
120 * Creates a new certificate-validator cache
122 public CertificateValidatorCache() {
123 Random random = new Random();
124 mBigScrew = random.nextInt();
126 mCacheMap = new HashMap<Integer, CacheEntry>();
130 * @param domain The domain to check against
131 * @param secureHash The secure hash to check against
132 * @return True iff there is a valid (not expired) cache entry
133 * associated with the domain and the secure hash
135 public boolean has(String domain, byte[] secureHash) {
136 boolean rval = false;
138 if (domain != null && domain.length() != 0) {
139 if (secureHash != null && secureHash.length != 0) {
140 CacheEntry cacheEntry = (CacheEntry)mCacheMap.get(
141 new Integer(mBigScrew ^ domain.hashCode()));
142 if (cacheEntry != null) {
143 if (!cacheEntry.expired()) {
144 rval = cacheEntry.has(domain, secureHash);
147 mSave += cacheEntry.mSave;
151 mCacheMap.remove(cacheEntry);
161 * Adds the (domain, secureHash) tuple to the cache
162 * @param domain The domain to be added to the cache
163 * @param secureHash The secure hash to be added to the cache
164 * @return True iff succeeds
166 public boolean put(String domain, byte[] secureHash, long save) {
167 if (domain != null && domain.length() != 0) {
168 if (secureHash != null && secureHash.length != 0) {
170 new Integer(mBigScrew ^ domain.hashCode()),
171 new CacheEntry(domain, secureHash, save));
181 * Certificate-validator cache entry. We have one per domain
183 private class CacheEntry {
186 * The hash associated with this cache entry
188 private byte[] mHash;
191 * The time associated with this cache entry
200 * The host associated with this cache entry
202 private String mDomain;
205 * Creates a new certificate-validator cache entry
206 * @param domain The domain to be associated with this cache entry
207 * @param secureHash The secure hash to be associated with this cache
210 public CacheEntry(String domain, byte[] secureHash, long save) {
216 mTime = SystemClock.uptimeMillis();
220 * @return True iff the cache item has expired
222 public boolean expired() {
223 return CACHE_ENTRY_LIFETIME < SystemClock.uptimeMillis() - mTime;
227 * @param domain The domain to check
228 * @param secureHash The secure hash to check
229 * @return True iff the given domain and hash match those associated
232 public boolean has(String domain, byte[] secureHash) {
233 if (domain != null && 0 < domain.length()) {
234 if (!mDomain.equals(domain)) {
239 if (secureHash != null) {
240 int hashLength = secureHash.length;
241 if (0 < hashLength) {
242 if (hashLength == mHash.length) {
243 for (int i = 0; i < hashLength; ++i) {
244 if (secureHash[i] != mHash[i]) {