1 /* 2 * Copyright (C) 2015 The Android Open Source Project 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 17 package android.security.net.config; 18 19 import android.util.ArraySet; 20 import android.util.Log; 21 22 import libcore.io.IoUtils; 23 24 import java.io.BufferedInputStream; 25 import java.io.File; 26 import java.io.FileInputStream; 27 import java.io.IOException; 28 import java.io.InputStream; 29 import java.security.MessageDigest; 30 import java.security.NoSuchAlgorithmException; 31 import java.security.cert.CertificateException; 32 import java.security.cert.CertificateFactory; 33 import java.security.cert.X509Certificate; 34 import java.util.Collections; 35 import java.util.Set; 36 37 import javax.security.auth.x500.X500Principal; 38 39 /** 40 * {@link CertificateSource} based on a directory where certificates are stored as individual files 41 * named after a hash of their SubjectName for more efficient lookups. 42 * @hide 43 */ 44 abstract class DirectoryCertificateSource implements CertificateSource { 45 private static final String LOG_TAG = "DirectoryCertificateSrc"; 46 private final File mDir; 47 private final Object mLock = new Object(); 48 private final CertificateFactory mCertFactory; 49 50 private Set<X509Certificate> mCertificates; 51 DirectoryCertificateSource(File caDir)52 protected DirectoryCertificateSource(File caDir) { 53 mDir = caDir; 54 try { 55 mCertFactory = CertificateFactory.getInstance("X.509"); 56 } catch (CertificateException e) { 57 throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e); 58 } 59 } 60 isCertMarkedAsRemoved(String caFile)61 protected abstract boolean isCertMarkedAsRemoved(String caFile); 62 63 @Override getCertificates()64 public Set<X509Certificate> getCertificates() { 65 // TODO: loading all of these is wasteful, we should instead use a keystore style API. 66 synchronized (mLock) { 67 if (mCertificates != null) { 68 return mCertificates; 69 } 70 71 Set<X509Certificate> certs = new ArraySet<X509Certificate>(); 72 if (mDir.isDirectory()) { 73 for (String caFile : mDir.list()) { 74 if (isCertMarkedAsRemoved(caFile)) { 75 continue; 76 } 77 X509Certificate cert = readCertificate(caFile); 78 if (cert != null) { 79 certs.add(cert); 80 } 81 } 82 } 83 mCertificates = certs; 84 return mCertificates; 85 } 86 } 87 88 @Override findBySubjectAndPublicKey(final X509Certificate cert)89 public X509Certificate findBySubjectAndPublicKey(final X509Certificate cert) { 90 return findCert(cert.getSubjectX500Principal(), new CertSelector() { 91 @Override 92 public boolean match(X509Certificate ca) { 93 return ca.getPublicKey().equals(cert.getPublicKey()); 94 } 95 }); 96 } 97 98 @Override 99 public X509Certificate findByIssuerAndSignature(final X509Certificate cert) { 100 return findCert(cert.getIssuerX500Principal(), new CertSelector() { 101 @Override 102 public boolean match(X509Certificate ca) { 103 try { 104 cert.verify(ca.getPublicKey()); 105 return true; 106 } catch (Exception e) { 107 return false; 108 } 109 } 110 }); 111 } 112 113 @Override 114 public Set<X509Certificate> findAllByIssuerAndSignature(final X509Certificate cert) { 115 return findCerts(cert.getIssuerX500Principal(), new CertSelector() { 116 @Override 117 public boolean match(X509Certificate ca) { 118 try { 119 cert.verify(ca.getPublicKey()); 120 return true; 121 } catch (Exception e) { 122 return false; 123 } 124 } 125 }); 126 } 127 128 @Override 129 public void handleTrustStorageUpdate() { 130 synchronized (mLock) { 131 mCertificates = null; 132 } 133 } 134 135 private static interface CertSelector { 136 boolean match(X509Certificate cert); 137 } 138 139 private Set<X509Certificate> findCerts(X500Principal subj, CertSelector selector) { 140 String hash = getHash(subj); 141 Set<X509Certificate> certs = null; 142 for (int index = 0; index >= 0; index++) { 143 String fileName = hash + "." + index; 144 if (!new File(mDir, fileName).exists()) { 145 break; 146 } 147 if (isCertMarkedAsRemoved(fileName)) { 148 continue; 149 } 150 X509Certificate cert = readCertificate(fileName); 151 if (cert == null) { 152 continue; 153 } 154 if (!subj.equals(cert.getSubjectX500Principal())) { 155 continue; 156 } 157 if (selector.match(cert)) { 158 if (certs == null) { 159 certs = new ArraySet<X509Certificate>(); 160 } 161 certs.add(cert); 162 } 163 } 164 return certs != null ? certs : Collections.<X509Certificate>emptySet(); 165 } 166 167 private X509Certificate findCert(X500Principal subj, CertSelector selector) { 168 String hash = getHash(subj); 169 for (int index = 0; index >= 0; index++) { 170 String fileName = hash + "." + index; 171 if (!new File(mDir, fileName).exists()) { 172 break; 173 } 174 if (isCertMarkedAsRemoved(fileName)) { 175 continue; 176 } 177 X509Certificate cert = readCertificate(fileName); 178 if (cert == null) { 179 continue; 180 } 181 if (!subj.equals(cert.getSubjectX500Principal())) { 182 continue; 183 } 184 if (selector.match(cert)) { 185 return cert; 186 } 187 } 188 return null; 189 } 190 191 private String getHash(X500Principal name) { 192 int hash = hashName(name); 193 return intToHexString(hash, 8); 194 } 195 196 private static final char[] DIGITS = { 197 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; 198 199 private static String intToHexString(int i, int minWidth) { 200 int bufLen = 8; // Max number of hex digits in an int 201 char[] buf = new char[bufLen]; 202 int cursor = bufLen; 203 204 do { 205 buf[--cursor] = DIGITS[i & 0xf]; 206 } while ((i >>>= 4) != 0 || (bufLen - cursor < minWidth)); 207 208 return new String(buf, cursor, bufLen - cursor); 209 } 210 211 // This code matches the code in X509_NAME_hash_old() in Conscrypt, which in turn matches 212 // the names of certificate files. It must be kept in sync. 213 private static int hashName(X500Principal principal) { 214 try { 215 byte[] digest = MessageDigest.getInstance("MD5").digest(principal.getEncoded()); 216 int offset = 0; 217 return (((digest[offset++] & 0xff) << 0) | ((digest[offset++] & 0xff) << 8) 218 | ((digest[offset++] & 0xff) << 16) | ((digest[offset] & 0xff) << 24)); 219 } catch (NoSuchAlgorithmException e) { 220 throw new AssertionError(e); 221 } 222 } 223 224 private X509Certificate readCertificate(String file) { 225 InputStream is = null; 226 try { 227 is = new BufferedInputStream(new FileInputStream(new File(mDir, file))); 228 return (X509Certificate) mCertFactory.generateCertificate(is); 229 } catch (CertificateException | IOException e) { 230 Log.e(LOG_TAG, "Failed to read certificate from " + file, e); 231 return null; 232 } finally { 233 IoUtils.closeQuietly(is); 234 } 235 } 236 } 237