/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.net.http; import android.os.SystemClock; import android.security.Sha1MessageDigest; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.CertPath; import java.security.GeneralSecurityException; import java.util.Arrays; import java.util.HashMap; import java.util.Random; /** * Validator cache used to speed-up certificate chain validation. The idea is * to keep each secure domain name associated with a cryptographically secure * hash of the certificate chain successfully used to validate the domain. If * we establish connection with the domain more than once and each time receive * the same list of certificates, we do not have to re-validate. * * {@hide} */ class CertificateValidatorCache { // TODO: debug only! public static long mSave = 0; public static long mCost = 0; // TODO: debug only! /** * The cache-entry lifetime in milliseconds (here, 10 minutes) */ private static final long CACHE_ENTRY_LIFETIME = 10 * 60 * 1000; /** * The certificate factory */ private static CertificateFactory sCertificateFactory; /** * The certificate validator cache map (domain to a cache entry) */ private HashMap<Integer, CacheEntry> mCacheMap; /** * Random salt */ private int mBigScrew; /** * @param certificate The array of server certificates to compute a * secure hash from * @return The secure hash computed from server certificates */ public static byte[] secureHash(Certificate[] certificates) { byte[] secureHash = null; // TODO: debug only! long beg = SystemClock.uptimeMillis(); // TODO: debug only! if (certificates != null && certificates.length != 0) { byte[] encodedCertPath = null; try { synchronized (CertificateValidatorCache.class) { if (sCertificateFactory == null) { try { sCertificateFactory = CertificateFactory.getInstance("X.509"); } catch(GeneralSecurityException e) { if (HttpLog.LOGV) { HttpLog.v("CertificateValidatorCache:" + " failed to create the certificate factory"); } } } } CertPath certPath = sCertificateFactory.generateCertPath(Arrays.asList(certificates)); if (certPath != null) { encodedCertPath = certPath.getEncoded(); if (encodedCertPath != null) { Sha1MessageDigest messageDigest = new Sha1MessageDigest(); secureHash = messageDigest.digest(encodedCertPath); } } } catch (GeneralSecurityException e) {} } // TODO: debug only! long end = SystemClock.uptimeMillis(); mCost += (end - beg); // TODO: debug only! return secureHash; } /** * Creates a new certificate-validator cache */ public CertificateValidatorCache() { Random random = new Random(); mBigScrew = random.nextInt(); mCacheMap = new HashMap<Integer, CacheEntry>(); } /** * @param domain The domain to check against * @param secureHash The secure hash to check against * @return True iff there is a valid (not expired) cache entry * associated with the domain and the secure hash */ public boolean has(String domain, byte[] secureHash) { boolean rval = false; if (domain != null && domain.length() != 0) { if (secureHash != null && secureHash.length != 0) { CacheEntry cacheEntry = (CacheEntry)mCacheMap.get( new Integer(mBigScrew ^ domain.hashCode())); if (cacheEntry != null) { if (!cacheEntry.expired()) { rval = cacheEntry.has(domain, secureHash); // TODO: debug only! if (rval) { mSave += cacheEntry.mSave; } // TODO: debug only! } else { mCacheMap.remove(cacheEntry); } } } } return rval; } /** * Adds the (domain, secureHash) tuple to the cache * @param domain The domain to be added to the cache * @param secureHash The secure hash to be added to the cache * @return True iff succeeds */ public boolean put(String domain, byte[] secureHash, long save) { if (domain != null && domain.length() != 0) { if (secureHash != null && secureHash.length != 0) { mCacheMap.put( new Integer(mBigScrew ^ domain.hashCode()), new CacheEntry(domain, secureHash, save)); return true; } } return false; } /** * Certificate-validator cache entry. We have one per domain */ private class CacheEntry { /** * The hash associated with this cache entry */ private byte[] mHash; /** * The time associated with this cache entry */ private long mTime; // TODO: debug only! public long mSave; // TODO: debug only! /** * The host associated with this cache entry */ private String mDomain; /** * Creates a new certificate-validator cache entry * @param domain The domain to be associated with this cache entry * @param secureHash The secure hash to be associated with this cache * entry */ public CacheEntry(String domain, byte[] secureHash, long save) { mDomain = domain; mHash = secureHash; // TODO: debug only! mSave = save; // TODO: debug only! mTime = SystemClock.uptimeMillis(); } /** * @return True iff the cache item has expired */ public boolean expired() { return CACHE_ENTRY_LIFETIME < SystemClock.uptimeMillis() - mTime; } /** * @param domain The domain to check * @param secureHash The secure hash to check * @return True iff the given domain and hash match those associated * with this entry */ public boolean has(String domain, byte[] secureHash) { if (domain != null && 0 < domain.length()) { if (!mDomain.equals(domain)) { return false; } } int hashLength = secureHash.length; if (secureHash != null && 0 < hashLength) { if (hashLength == mHash.length) { for (int i = 0; i < hashLength; ++i) { if (secureHash[i] != mHash[i]) { return false; } } return true; } } return false; } } };